Show status in messages

This commit is contained in:
Owen
2026-04-22 20:44:35 -07:00
parent 90a2ed2f10
commit bcb5b7b4a7
5 changed files with 91 additions and 17 deletions

View File

@@ -36,8 +36,8 @@ function getEventMeta(eventType: AlertEventType): {
heading: string; heading: string;
previewText: string; previewText: string;
summary: string; summary: string;
statusLabel: string; statusLabel: string | null;
statusColor: string; statusColor: string | null;
} { } {
switch (eventType) { switch (eventType) {
case "site_online": case "site_online":
@@ -63,8 +63,8 @@ function getEventMeta(eventType: AlertEventType): {
heading: "Site Status Changed", heading: "Site Status Changed",
previewText: "A site in your organization has changed status.", previewText: "A site in your organization has changed status.",
summary: "A site in your organization has changed status.", summary: "A site in your organization has changed status.",
statusLabel: "Status Changed", statusLabel: null,
statusColor: "#f59e0b" statusColor: null
}; };
case "health_check_healthy": case "health_check_healthy":
return { return {
@@ -93,8 +93,8 @@ function getEventMeta(eventType: AlertEventType): {
"A health check in your organization has changed status.", "A health check in your organization has changed status.",
summary: summary:
"A health check in your organization has changed status.", "A health check in your organization has changed status.",
statusLabel: "Status Changed", statusLabel: null,
statusColor: "#f59e0b" statusColor: null
}; };
case "resource_healthy": case "resource_healthy":
return { return {
@@ -120,8 +120,8 @@ function getEventMeta(eventType: AlertEventType): {
previewText: previewText:
"A resource in your organization has changed status.", "A resource in your organization has changed status.",
summary: "A resource in your organization has changed status.", summary: "A resource in your organization has changed status.",
statusLabel: "Status Changed", statusLabel: null,
statusColor: "#f59e0b" statusColor: null
}; };
default: default:
return { return {
@@ -135,11 +135,26 @@ function getEventMeta(eventType: AlertEventType): {
} }
} }
function resolveToggleStatus(status: unknown): { label: string; color: string } {
switch (String(status).toLowerCase()) {
case "online":
return { label: "Online", color: "#16a34a" };
case "offline":
return { label: "Offline", color: "#dc2626" };
case "healthy":
return { label: "Healthy", color: "#16a34a" };
case "unhealthy":
return { label: "Unhealthy", color: "#dc2626" };
default:
return { label: String(status ?? "Unknown"), color: "#f59e0b" };
}
}
function formatDataItems( function formatDataItems(
data: Record<string, unknown> data: Record<string, unknown>
): { label: string; value: React.ReactNode }[] { ): { label: string; value: React.ReactNode }[] {
return Object.entries(data) return Object.entries(data)
.filter(([key]) => key !== "orgId") .filter(([key]) => key !== "orgId" && key !== "status")
.map(([key, value]) => ({ .map(([key, value]) => ({
label: key label: key
.replace(/([A-Z])/g, " $1") .replace(/([A-Z])/g, " $1")
@@ -154,16 +169,36 @@ export const AlertNotification = (props: AlertNotificationProps) => {
const meta = getEventMeta(eventType); const meta = getEventMeta(eventType);
const dataItems = formatDataItems(data); const dataItems = formatDataItems(data);
const isToggle =
eventType === "site_toggle" ||
eventType === "health_check_toggle" ||
eventType === "resource_toggle";
const resolvedStatus = isToggle
? resolveToggleStatus(data.status)
: meta.statusLabel != null
? { label: meta.statusLabel, color: meta.statusColor! }
: null;
const allItems: { label: string; value: React.ReactNode }[] = [ const allItems: { label: string; value: React.ReactNode }[] = [
{ label: "Organization", value: orgId }, { label: "Organization", value: orgId },
{ ...(resolvedStatus != null
label: "Status", ? [
value: ( {
<span style={{ color: meta.statusColor, fontWeight: 600 }}> label: "Status",
{meta.statusLabel} value: (
</span> <span
) style={{
}, color: resolvedStatus.color,
fontWeight: 600
}}
>
{resolvedStatus.label}
</span>
)
}
]
: []),
{ label: "Time", value: new Date().toUTCString() }, { label: "Time", value: new Date().toUTCString() },
...dataItems ...dataItems
]; ];

View File

@@ -76,6 +76,7 @@ export async function fireHealthCheckHealthyAlert(
healthCheckId, healthCheckId,
data: { data: {
healthCheckId, healthCheckId,
status: "healthy",
...(healthCheckName != null ? { healthCheckName } : {}), ...(healthCheckName != null ? { healthCheckName } : {}),
...extra ...extra
} }
@@ -133,6 +134,7 @@ export async function fireHealthCheckUnhealthyAlert(
healthCheckId, healthCheckId,
data: { data: {
healthCheckId, healthCheckId,
status: "unhealthy",
...(healthCheckName != null ? { healthCheckName } : {}), ...(healthCheckName != null ? { healthCheckName } : {}),
...extra ...extra
} }

View File

@@ -61,6 +61,7 @@ export async function fireResourceHealthyAlert(
resourceId, resourceId,
data: { data: {
resourceId, resourceId,
status: "healthy",
...(resourceName != null ? { resourceName } : {}), ...(resourceName != null ? { resourceName } : {}),
...extra ...extra
} }
@@ -115,6 +116,7 @@ export async function fireResourceUnhealthyAlert(
resourceId, resourceId,
data: { data: {
resourceId, resourceId,
status: "unhealthy",
...(resourceName != null ? { resourceName } : {}), ...(resourceName != null ? { resourceName } : {}),
...extra ...extra
} }

View File

@@ -63,6 +63,7 @@ export async function fireSiteOnlineAlert(
siteId, siteId,
data: { data: {
siteId, siteId,
status: "online",
...(siteName != null ? { siteName } : {}), ...(siteName != null ? { siteName } : {}),
...extra ...extra
} }
@@ -143,6 +144,7 @@ export async function fireSiteOfflineAlert(
siteId, siteId,
data: { data: {
siteId, siteId,
status: "offline",
...(siteName != null ? { siteName } : {}), ...(siteName != null ? { siteName } : {}),
...extra ...extra
} }

View File

@@ -42,6 +42,7 @@ export async function sendAlertWebhook(
const payload = { const payload = {
event: context.eventType, event: context.eventType,
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
status: deriveStatus(context.eventType, context.data),
data: { data: {
orgId: context.orgId, orgId: context.orgId,
...context.data ...context.data
@@ -117,6 +118,38 @@ export async function sendAlertWebhook(
throw lastError ?? new Error(`Alert webhook: all ${MAX_RETRIES} attempts failed for "${url}"`); throw lastError ?? new Error(`Alert webhook: all ${MAX_RETRIES} attempts failed for "${url}"`);
} }
// ---------------------------------------------------------------------------
// Status derivation
// ---------------------------------------------------------------------------
function deriveStatus(
eventType: AlertContext["eventType"],
data: Record<string, unknown>
): string {
switch (eventType) {
case "site_online":
return "online";
case "site_offline":
return "offline";
case "site_toggle":
return String(data.status ?? "unknown");
case "health_check_healthy":
case "resource_healthy":
return "healthy";
case "health_check_unhealthy":
case "resource_unhealthy":
return "unhealthy";
case "health_check_toggle":
case "resource_toggle":
return String(data.status ?? "unknown");
default: {
const _exhaustive: never = eventType;
void _exhaustive;
return "unknown";
}
}
}
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// Header construction (mirrors HttpLogDestination.buildHeaders) // Header construction (mirrors HttpLogDestination.buildHeaders)
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------