mirror of
https://github.com/fosrl/pangolin.git
synced 2026-06-02 22:11:16 +00:00
Add toggle types
This commit is contained in:
@@ -475,8 +475,10 @@ export const alertRules = pgTable("alertRules", {
|
|||||||
.$type<
|
.$type<
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_not_healthy"
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle"
|
||||||
>()
|
>()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
// Nullable depending on eventType
|
// Nullable depending on eventType
|
||||||
|
|||||||
@@ -467,8 +467,10 @@ export const alertRules = sqliteTable("alertRules", {
|
|||||||
.$type<
|
.$type<
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_not_healthy"
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle"
|
||||||
>()
|
>()
|
||||||
.notNull(),
|
.notNull(),
|
||||||
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
enabled: integer("enabled", { mode: "boolean" }).notNull().default(true),
|
||||||
|
|||||||
@@ -15,8 +15,10 @@ import {
|
|||||||
export type AlertEventType =
|
export type AlertEventType =
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_not_healthy";
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
eventType: AlertEventType;
|
eventType: AlertEventType;
|
||||||
@@ -50,6 +52,15 @@ function getEventMeta(eventType: AlertEventType): {
|
|||||||
statusLabel: "Offline",
|
statusLabel: "Offline",
|
||||||
statusColor: "#dc2626"
|
statusColor: "#dc2626"
|
||||||
};
|
};
|
||||||
|
case "site_toggle":
|
||||||
|
return {
|
||||||
|
heading: "Site Status Changed",
|
||||||
|
previewText: "A site in your organization has changed status.",
|
||||||
|
summary:
|
||||||
|
"A site in your organization has changed status. Please review the details below and take action if needed.",
|
||||||
|
statusLabel: "Status Changed",
|
||||||
|
statusColor: "#f59e0b"
|
||||||
|
};
|
||||||
case "health_check_healthy":
|
case "health_check_healthy":
|
||||||
return {
|
return {
|
||||||
heading: "Health Check Recovered",
|
heading: "Health Check Recovered",
|
||||||
@@ -60,7 +71,7 @@ function getEventMeta(eventType: AlertEventType): {
|
|||||||
statusLabel: "Healthy",
|
statusLabel: "Healthy",
|
||||||
statusColor: "#16a34a"
|
statusColor: "#16a34a"
|
||||||
};
|
};
|
||||||
case "health_check_not_healthy":
|
case "health_check_unhealthy":
|
||||||
return {
|
return {
|
||||||
heading: "Health Check Failing",
|
heading: "Health Check Failing",
|
||||||
previewText:
|
previewText:
|
||||||
@@ -70,6 +81,25 @@ function getEventMeta(eventType: AlertEventType): {
|
|||||||
statusLabel: "Not Healthy",
|
statusLabel: "Not Healthy",
|
||||||
statusColor: "#dc2626"
|
statusColor: "#dc2626"
|
||||||
};
|
};
|
||||||
|
case "health_check_toggle":
|
||||||
|
return {
|
||||||
|
heading: "Health Check Status Changed",
|
||||||
|
previewText:
|
||||||
|
"A health check in your organization has changed status.",
|
||||||
|
summary:
|
||||||
|
"A health check in your organization has changed status. Please review the details below and take action if needed.",
|
||||||
|
statusLabel: "Status Changed",
|
||||||
|
statusColor: "#f59e0b"
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
return {
|
||||||
|
heading: "Alert Notification",
|
||||||
|
previewText: "An alert event has occurred in your organization.",
|
||||||
|
summary:
|
||||||
|
"An alert event has occurred in your organization. Please review the details below and take action if needed.",
|
||||||
|
statusLabel: "Alert",
|
||||||
|
statusColor: "#f59e0b"
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export async function fireHealthCheckHealthyAlert(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fire a `health_check_not_healthy` alert for the given health check.
|
* Fire a `health_check_unhealthy` alert for the given health check.
|
||||||
*
|
*
|
||||||
* Call this after a health check has been detected as failing so that any
|
* Call this after a health check has been detected as failing so that any
|
||||||
* matching `alertRules` can dispatch their email and webhook actions.
|
* matching `alertRules` can dispatch their email and webhook actions.
|
||||||
@@ -73,7 +73,7 @@ export async function fireHealthCheckNotHealthyAlert(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await processAlerts({
|
await processAlerts({
|
||||||
eventType: "health_check_not_healthy",
|
eventType: "health_check_unhealthy",
|
||||||
orgId,
|
orgId,
|
||||||
healthCheckId,
|
healthCheckId,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export async function fireHealthCheckHealthyAlert(
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fire a `health_check_not_healthy` alert for the given health check.
|
* Fire a `health_check_unhealthy` alert for the given health check.
|
||||||
*
|
*
|
||||||
* Call this after a health check has been detected as failing so that any
|
* Call this after a health check has been detected as failing so that any
|
||||||
* matching `alertRules` can dispatch their email and webhook actions.
|
* matching `alertRules` can dispatch their email and webhook actions.
|
||||||
@@ -73,7 +73,7 @@ export async function fireHealthCheckNotHealthyAlert(
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await processAlerts({
|
await processAlerts({
|
||||||
eventType: "health_check_not_healthy",
|
eventType: "health_check_unhealthy",
|
||||||
orgId,
|
orgId,
|
||||||
healthCheckId,
|
healthCheckId,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -72,10 +72,14 @@ function buildSubject(context: AlertContext): string {
|
|||||||
return "[Alert] Site Back Online";
|
return "[Alert] Site Back Online";
|
||||||
case "site_offline":
|
case "site_offline":
|
||||||
return "[Alert] Site Offline";
|
return "[Alert] Site Offline";
|
||||||
|
case "site_toggle":
|
||||||
|
return "[Alert] Site Toggled";
|
||||||
case "health_check_healthy":
|
case "health_check_healthy":
|
||||||
return "[Alert] Health Check Recovered";
|
return "[Alert] Health Check Recovered";
|
||||||
case "health_check_not_healthy":
|
case "health_check_unhealthy":
|
||||||
return "[Alert] Health Check Failing";
|
return "[Alert] Health Check Failing";
|
||||||
|
case "health_check_toggle":
|
||||||
|
return "[Alert] Health Check Toggled";
|
||||||
default: {
|
default: {
|
||||||
// Exhaustiveness fallback – should never be reached with a
|
// Exhaustiveness fallback – should never be reached with a
|
||||||
// well-typed caller, but keeps runtime behaviour predictable.
|
// well-typed caller, but keeps runtime behaviour predictable.
|
||||||
@@ -84,4 +88,4 @@ function buildSubject(context: AlertContext): string {
|
|||||||
return "[Alert] Event Notification";
|
return "[Alert] Event Notification";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,8 +18,10 @@
|
|||||||
export type AlertEventType =
|
export type AlertEventType =
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_not_healthy";
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle";
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Webhook authentication config (stored as encrypted JSON in the DB)
|
// Webhook authentication config (stored as encrypted JSON in the DB)
|
||||||
@@ -60,4 +62,4 @@ export interface AlertContext {
|
|||||||
healthCheckId?: number;
|
healthCheckId?: number;
|
||||||
/** Human-readable context data included in emails and webhook payloads */
|
/** Human-readable context data included in emails and webhook payloads */
|
||||||
data: Record<string, unknown>;
|
data: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,12 +30,12 @@ import { fromError } from "zod-validation-error";
|
|||||||
import { OpenAPITags, registry } from "@server/openApi";
|
import { OpenAPITags, registry } from "@server/openApi";
|
||||||
import { encrypt } from "@server/lib/crypto";
|
import { encrypt } from "@server/lib/crypto";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
import { and, eq } from "drizzle-orm";
|
|
||||||
|
|
||||||
const SITE_EVENT_TYPES = ["site_online", "site_offline"] as const;
|
export const SITE_EVENT_TYPES = ["site_online", "site_offline", "site_toggle"] as const;
|
||||||
const HC_EVENT_TYPES = [
|
export const HC_EVENT_TYPES = [
|
||||||
"health_check_healthy",
|
"health_check_healthy",
|
||||||
"health_check_not_healthy"
|
"health_check_unhealthy",
|
||||||
|
"health_check_toggle"
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
const paramsSchema = z.strictObject({
|
const paramsSchema = z.strictObject({
|
||||||
@@ -52,10 +52,8 @@ const bodySchema = z
|
|||||||
.strictObject({
|
.strictObject({
|
||||||
name: z.string().nonempty(),
|
name: z.string().nonempty(),
|
||||||
eventType: z.enum([
|
eventType: z.enum([
|
||||||
"site_online",
|
...HC_EVENT_TYPES,
|
||||||
"site_offline",
|
...SITE_EVENT_TYPES
|
||||||
"health_check_healthy",
|
|
||||||
"health_check_not_healthy"
|
|
||||||
]),
|
]),
|
||||||
enabled: z.boolean().optional().default(true),
|
enabled: z.boolean().optional().default(true),
|
||||||
cooldownSeconds: z.number().int().nonnegative().optional().default(300),
|
cooldownSeconds: z.number().int().nonnegative().optional().default(300),
|
||||||
|
|||||||
@@ -47,8 +47,10 @@ export type GetAlertRuleResponse = {
|
|||||||
eventType:
|
eventType:
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_not_healthy";
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle";
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
cooldownSeconds: number;
|
cooldownSeconds: number;
|
||||||
lastTriggeredAt: number | null;
|
lastTriggeredAt: number | null;
|
||||||
@@ -59,7 +61,7 @@ export type GetAlertRuleResponse = {
|
|||||||
recipients: {
|
recipients: {
|
||||||
recipientId: number;
|
recipientId: number;
|
||||||
userId: string | null;
|
userId: string | null;
|
||||||
roleId: string | null;
|
roleId: number | null;
|
||||||
email: string | null;
|
email: string | null;
|
||||||
}[];
|
}[];
|
||||||
webhookActions: {
|
webhookActions: {
|
||||||
@@ -177,24 +179,27 @@ export async function getAlertRule(
|
|||||||
healthCheckIds: healthCheckRows.map((r) => r.healthCheckId),
|
healthCheckIds: healthCheckRows.map((r) => r.healthCheckId),
|
||||||
recipients,
|
recipients,
|
||||||
webhookActions: webhooks.map((w) => {
|
webhookActions: webhooks.map((w) => {
|
||||||
let parsedConfig: WebhookAlertConfig | null = null;
|
let parsedConfig: WebhookAlertConfig | null = null;
|
||||||
if (w.config) {
|
if (w.config) {
|
||||||
try {
|
try {
|
||||||
const serverSecret = config.getRawConfig().server.secret!;
|
const serverSecret =
|
||||||
const decrypted = decrypt(w.config, serverSecret);
|
config.getRawConfig().server.secret!;
|
||||||
parsedConfig = JSON.parse(decrypted) as WebhookAlertConfig;
|
const decrypted = decrypt(w.config, serverSecret);
|
||||||
} catch {
|
parsedConfig = JSON.parse(
|
||||||
// best-effort – return null if decryption fails
|
decrypted
|
||||||
}
|
) as WebhookAlertConfig;
|
||||||
}
|
} catch {
|
||||||
return {
|
// best-effort – return null if decryption fails
|
||||||
webhookActionId: w.webhookActionId,
|
}
|
||||||
webhookUrl: w.webhookUrl,
|
}
|
||||||
enabled: w.enabled,
|
return {
|
||||||
lastSentAt: w.lastSentAt ?? null,
|
webhookActionId: w.webhookActionId,
|
||||||
config: parsedConfig
|
webhookUrl: w.webhookUrl,
|
||||||
};
|
enabled: w.enabled,
|
||||||
})
|
lastSentAt: w.lastSentAt ?? null,
|
||||||
|
config: parsedConfig
|
||||||
|
};
|
||||||
|
})
|
||||||
},
|
},
|
||||||
success: true,
|
success: true,
|
||||||
error: false,
|
error: false,
|
||||||
@@ -207,4 +212,4 @@ export async function getAlertRule(
|
|||||||
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
createHttpError(HttpCode.INTERNAL_SERVER_ERROR, "An error occurred")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,12 +31,7 @@ import { OpenAPITags, registry } from "@server/openApi";
|
|||||||
import { and, eq } from "drizzle-orm";
|
import { and, eq } from "drizzle-orm";
|
||||||
import { encrypt } from "@server/lib/crypto";
|
import { encrypt } from "@server/lib/crypto";
|
||||||
import config from "@server/lib/config";
|
import config from "@server/lib/config";
|
||||||
|
import { HC_EVENT_TYPES, SITE_EVENT_TYPES } from "./createAlertRule";
|
||||||
const SITE_EVENT_TYPES = ["site_online", "site_offline"] as const;
|
|
||||||
const HC_EVENT_TYPES = [
|
|
||||||
"health_check_healthy",
|
|
||||||
"health_check_not_healthy"
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
const paramsSchema = z
|
const paramsSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -57,10 +52,8 @@ const bodySchema = z
|
|||||||
name: z.string().nonempty().optional(),
|
name: z.string().nonempty().optional(),
|
||||||
eventType: z
|
eventType: z
|
||||||
.enum([
|
.enum([
|
||||||
"site_online",
|
...HC_EVENT_TYPES,
|
||||||
"site_offline",
|
...SITE_EVENT_TYPES
|
||||||
"health_check_healthy",
|
|
||||||
"health_check_not_healthy"
|
|
||||||
])
|
])
|
||||||
.optional(),
|
.optional(),
|
||||||
enabled: z.boolean().optional(),
|
enabled: z.boolean().optional(),
|
||||||
|
|||||||
@@ -71,10 +71,14 @@ function triggerLabel(
|
|||||||
return t("alertingTriggerSiteOnline");
|
return t("alertingTriggerSiteOnline");
|
||||||
case "site_offline":
|
case "site_offline":
|
||||||
return t("alertingTriggerSiteOffline");
|
return t("alertingTriggerSiteOffline");
|
||||||
|
case "site_toggle":
|
||||||
|
return t("alertingTriggerSiteToggle");
|
||||||
case "health_check_healthy":
|
case "health_check_healthy":
|
||||||
return t("alertingTriggerHcHealthy");
|
return t("alertingTriggerHcHealthy");
|
||||||
case "health_check_not_healthy":
|
case "health_check_unhealthy":
|
||||||
return t("alertingTriggerHcUnhealthy");
|
return t("alertingTriggerHcUnhealthy");
|
||||||
|
case "health_check_toggle":
|
||||||
|
return t("alertingTriggerHcToggle");
|
||||||
default:
|
default:
|
||||||
return rule.eventType;
|
return rule.eventType;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -888,7 +888,8 @@ export function AlertRuleSourceFields({
|
|||||||
if (next === "site") {
|
if (next === "site") {
|
||||||
if (
|
if (
|
||||||
curTrigger !== "site_online" &&
|
curTrigger !== "site_online" &&
|
||||||
curTrigger !== "site_offline"
|
curTrigger !== "site_offline" &&
|
||||||
|
curTrigger !== "site_toggle"
|
||||||
) {
|
) {
|
||||||
setValue("trigger", "site_offline", {
|
setValue("trigger", "site_offline", {
|
||||||
shouldValidate: true
|
shouldValidate: true
|
||||||
@@ -896,7 +897,8 @@ export function AlertRuleSourceFields({
|
|||||||
}
|
}
|
||||||
} else if (
|
} else if (
|
||||||
curTrigger !== "health_check_healthy" &&
|
curTrigger !== "health_check_healthy" &&
|
||||||
curTrigger !== "health_check_unhealthy"
|
curTrigger !== "health_check_unhealthy" &&
|
||||||
|
curTrigger !== "health_check_toggle"
|
||||||
) {
|
) {
|
||||||
setValue(
|
setValue(
|
||||||
"trigger",
|
"trigger",
|
||||||
@@ -996,6 +998,9 @@ export function AlertRuleTriggerFields({
|
|||||||
<SelectItem value="site_offline">
|
<SelectItem value="site_offline">
|
||||||
{t("alertingTriggerSiteOffline")}
|
{t("alertingTriggerSiteOffline")}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
<SelectItem value="site_toggle">
|
||||||
|
{t("alertingTriggerSiteToggle")}
|
||||||
|
</SelectItem>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -1005,6 +1010,9 @@ export function AlertRuleTriggerFields({
|
|||||||
<SelectItem value="health_check_unhealthy">
|
<SelectItem value="health_check_unhealthy">
|
||||||
{t("alertingTriggerHcUnhealthy")}
|
{t("alertingTriggerHcUnhealthy")}
|
||||||
</SelectItem>
|
</SelectItem>
|
||||||
|
<SelectItem value="health_check_toggle">
|
||||||
|
{t("alertingTriggerHcToggle")}
|
||||||
|
</SelectItem>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</SelectContent>
|
</SelectContent>
|
||||||
|
|||||||
@@ -94,10 +94,14 @@ function summarizeTrigger(v: AlertRuleFormValues, t: AlertRuleT) {
|
|||||||
return t("alertingTriggerSiteOnline");
|
return t("alertingTriggerSiteOnline");
|
||||||
case "site_offline":
|
case "site_offline":
|
||||||
return t("alertingTriggerSiteOffline");
|
return t("alertingTriggerSiteOffline");
|
||||||
|
case "site_toggle":
|
||||||
|
return t("alertingTriggerSiteToggle");
|
||||||
case "health_check_healthy":
|
case "health_check_healthy":
|
||||||
return t("alertingTriggerHcHealthy");
|
return t("alertingTriggerHcHealthy");
|
||||||
case "health_check_unhealthy":
|
case "health_check_unhealthy":
|
||||||
return t("alertingTriggerHcUnhealthy");
|
return t("alertingTriggerHcUnhealthy");
|
||||||
|
case "health_check_toggle":
|
||||||
|
return t("alertingTriggerHcToggle");
|
||||||
default:
|
default:
|
||||||
return v.trigger;
|
return v.trigger;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,16 @@ export const tagSchema = z.object({
|
|||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Form-layer types
|
// Form-layer types
|
||||||
// NOTE: the form uses "health_check_unhealthy" internally; it maps to the
|
// NOTE: the form uses "health_check_unhealthy" internally; it maps to the
|
||||||
// backend's "health_check_not_healthy" at the API boundary.
|
// backend's "health_check_unhealthy" at the API boundary.
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
||||||
export type AlertTrigger =
|
export type AlertTrigger =
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_unhealthy";
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle";
|
||||||
|
|
||||||
export type AlertRuleFormAction =
|
export type AlertRuleFormAction =
|
||||||
| {
|
| {
|
||||||
@@ -60,8 +62,10 @@ export type AlertRuleApiPayload = {
|
|||||||
eventType:
|
eventType:
|
||||||
| "site_online"
|
| "site_online"
|
||||||
| "site_offline"
|
| "site_offline"
|
||||||
|
| "site_toggle"
|
||||||
| "health_check_healthy"
|
| "health_check_healthy"
|
||||||
| "health_check_not_healthy";
|
| "health_check_unhealthy"
|
||||||
|
| "health_check_toggle";
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
siteIds: number[];
|
siteIds: number[];
|
||||||
healthCheckIds: number[];
|
healthCheckIds: number[];
|
||||||
@@ -111,26 +115,6 @@ export type AlertRuleApiResponse = {
|
|||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
// Helpers
|
|
||||||
// ---------------------------------------------------------------------------
|
|
||||||
|
|
||||||
function triggerToEventType(
|
|
||||||
trigger: AlertTrigger
|
|
||||||
): AlertRuleApiPayload["eventType"] {
|
|
||||||
if (trigger === "health_check_unhealthy") {
|
|
||||||
return "health_check_not_healthy";
|
|
||||||
}
|
|
||||||
return trigger as AlertRuleApiPayload["eventType"];
|
|
||||||
}
|
|
||||||
|
|
||||||
function eventTypeToTrigger(eventType: string): AlertTrigger {
|
|
||||||
if (eventType === "health_check_not_healthy") {
|
|
||||||
return "health_check_unhealthy";
|
|
||||||
}
|
|
||||||
return eventType as AlertTrigger;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// Zod form schema (for react-hook-form validation)
|
// Zod form schema (for react-hook-form validation)
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
@@ -138,7 +122,9 @@ function eventTypeToTrigger(eventType: string): AlertTrigger {
|
|||||||
export function buildFormSchema(t: (k: string) => string) {
|
export function buildFormSchema(t: (k: string) => string) {
|
||||||
return z
|
return z
|
||||||
.object({
|
.object({
|
||||||
name: z.string().min(1, { message: t("alertingErrorNameRequired") }),
|
name: z
|
||||||
|
.string()
|
||||||
|
.min(1, { message: t("alertingErrorNameRequired") }),
|
||||||
enabled: z.boolean(),
|
enabled: z.boolean(),
|
||||||
sourceType: z.enum(["site", "health_check"]),
|
sourceType: z.enum(["site", "health_check"]),
|
||||||
siteIds: z.array(z.number()),
|
siteIds: z.array(z.number()),
|
||||||
@@ -146,36 +132,37 @@ export function buildFormSchema(t: (k: string) => string) {
|
|||||||
trigger: z.enum([
|
trigger: z.enum([
|
||||||
"site_online",
|
"site_online",
|
||||||
"site_offline",
|
"site_offline",
|
||||||
|
"site_toggle",
|
||||||
"health_check_healthy",
|
"health_check_healthy",
|
||||||
"health_check_unhealthy"
|
"health_check_unhealthy",
|
||||||
|
"health_check_toggle"
|
||||||
]),
|
]),
|
||||||
actions: z
|
actions: z.array(
|
||||||
.array(
|
z.discriminatedUnion("type", [
|
||||||
z.discriminatedUnion("type", [
|
z.object({
|
||||||
z.object({
|
type: z.literal("notify"),
|
||||||
type: z.literal("notify"),
|
userTags: z.array(tagSchema),
|
||||||
userTags: z.array(tagSchema),
|
roleTags: z.array(tagSchema),
|
||||||
roleTags: z.array(tagSchema),
|
emailTags: z.array(tagSchema)
|
||||||
emailTags: z.array(tagSchema)
|
}),
|
||||||
}),
|
z.object({
|
||||||
z.object({
|
type: z.literal("webhook"),
|
||||||
type: z.literal("webhook"),
|
url: z.string(),
|
||||||
url: z.string(),
|
method: z.string(),
|
||||||
method: z.string(),
|
headers: z.array(
|
||||||
headers: z.array(
|
z.object({
|
||||||
z.object({
|
key: z.string(),
|
||||||
key: z.string(),
|
value: z.string()
|
||||||
value: z.string()
|
})
|
||||||
})
|
),
|
||||||
),
|
authType: z.enum(["none", "bearer", "basic", "custom"]),
|
||||||
authType: z.enum(["none", "bearer", "basic", "custom"]),
|
bearerToken: z.string(),
|
||||||
bearerToken: z.string(),
|
basicCredentials: z.string(),
|
||||||
basicCredentials: z.string(),
|
customHeaderName: z.string(),
|
||||||
customHeaderName: z.string(),
|
customHeaderValue: z.string()
|
||||||
customHeaderValue: z.string()
|
})
|
||||||
})
|
])
|
||||||
])
|
)
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.superRefine((val, ctx) => {
|
.superRefine((val, ctx) => {
|
||||||
if (val.actions.length === 0) {
|
if (val.actions.length === 0) {
|
||||||
@@ -202,10 +189,15 @@ export function buildFormSchema(t: (k: string) => string) {
|
|||||||
path: ["healthCheckIds"]
|
path: ["healthCheckIds"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
const siteTriggers: AlertTrigger[] = ["site_online", "site_offline"];
|
const siteTriggers: AlertTrigger[] = [
|
||||||
|
"site_online",
|
||||||
|
"site_offline",
|
||||||
|
"site_toggle"
|
||||||
|
];
|
||||||
const hcTriggers: AlertTrigger[] = [
|
const hcTriggers: AlertTrigger[] = [
|
||||||
"health_check_healthy",
|
"health_check_healthy",
|
||||||
"health_check_unhealthy"
|
"health_check_unhealthy",
|
||||||
|
"health_check_toggle"
|
||||||
];
|
];
|
||||||
if (
|
if (
|
||||||
val.sourceType === "site" &&
|
val.sourceType === "site" &&
|
||||||
@@ -286,7 +278,7 @@ export function defaultFormValues(): AlertRuleFormValues {
|
|||||||
export function apiResponseToFormValues(
|
export function apiResponseToFormValues(
|
||||||
rule: AlertRuleApiResponse
|
rule: AlertRuleApiResponse
|
||||||
): AlertRuleFormValues {
|
): AlertRuleFormValues {
|
||||||
const trigger = eventTypeToTrigger(rule.eventType);
|
const trigger = rule.eventType;
|
||||||
const sourceType = rule.eventType.startsWith("site_")
|
const sourceType = rule.eventType.startsWith("site_")
|
||||||
? "site"
|
? "site"
|
||||||
: "health_check";
|
: "health_check";
|
||||||
@@ -318,7 +310,9 @@ export function apiResponseToFormValues(
|
|||||||
headers: cfg?.headers?.length
|
headers: cfg?.headers?.length
|
||||||
? cfg.headers
|
? cfg.headers
|
||||||
: [{ key: "", value: "" }],
|
: [{ key: "", value: "" }],
|
||||||
authType: (cfg?.authType as "none" | "bearer" | "basic" | "custom") ?? "none",
|
authType:
|
||||||
|
(cfg?.authType as "none" | "bearer" | "basic" | "custom") ??
|
||||||
|
"none",
|
||||||
bearerToken: cfg?.bearerToken ?? "",
|
bearerToken: cfg?.bearerToken ?? "",
|
||||||
basicCredentials: cfg?.basicCredentials ?? "",
|
basicCredentials: cfg?.basicCredentials ?? "",
|
||||||
customHeaderName: cfg?.customHeaderName ?? "",
|
customHeaderName: cfg?.customHeaderName ?? "",
|
||||||
@@ -342,7 +336,7 @@ export function apiResponseToFormValues(
|
|||||||
sourceType,
|
sourceType,
|
||||||
siteIds: rule.siteIds,
|
siteIds: rule.siteIds,
|
||||||
healthCheckIds: rule.healthCheckIds,
|
healthCheckIds: rule.healthCheckIds,
|
||||||
trigger,
|
trigger: trigger as AlertTrigger,
|
||||||
actions
|
actions
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -354,7 +348,7 @@ export function apiResponseToFormValues(
|
|||||||
export function formValuesToApiPayload(
|
export function formValuesToApiPayload(
|
||||||
values: AlertRuleFormValues
|
values: AlertRuleFormValues
|
||||||
): AlertRuleApiPayload {
|
): AlertRuleApiPayload {
|
||||||
const eventType = triggerToEventType(values.trigger);
|
const eventType = values.trigger;
|
||||||
|
|
||||||
// Collect all notify-type actions and merge their recipient lists
|
// Collect all notify-type actions and merge their recipient lists
|
||||||
const allUserIds: string[] = [];
|
const allUserIds: string[] = [];
|
||||||
@@ -368,9 +362,7 @@ export function formValuesToApiPayload(
|
|||||||
allUserIds.push(...action.userTags.map((t) => t.id));
|
allUserIds.push(...action.userTags.map((t) => t.id));
|
||||||
allRoleIds.push(...action.roleTags.map((t) => Number(t.id)));
|
allRoleIds.push(...action.roleTags.map((t) => Number(t.id)));
|
||||||
allEmails.push(
|
allEmails.push(
|
||||||
...action.emailTags
|
...action.emailTags.map((t) => t.text.trim()).filter(Boolean)
|
||||||
.map((t) => t.text.trim())
|
|
||||||
.filter(Boolean)
|
|
||||||
);
|
);
|
||||||
} else if (action.type === "webhook") {
|
} else if (action.type === "webhook") {
|
||||||
webhookActions.push({
|
webhookActions.push({
|
||||||
|
|||||||
Reference in New Issue
Block a user