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;
previewText: string;
summary: string;
statusLabel: string;
statusColor: string;
statusLabel: string | null;
statusColor: string | null;
} {
switch (eventType) {
case "site_online":
@@ -63,8 +63,8 @@ function getEventMeta(eventType: AlertEventType): {
heading: "Site Status Changed",
previewText: "A site in your organization has changed status.",
summary: "A site in your organization has changed status.",
statusLabel: "Status Changed",
statusColor: "#f59e0b"
statusLabel: null,
statusColor: null
};
case "health_check_healthy":
return {
@@ -93,8 +93,8 @@ function getEventMeta(eventType: AlertEventType): {
"A health check in your organization has changed status.",
summary:
"A health check in your organization has changed status.",
statusLabel: "Status Changed",
statusColor: "#f59e0b"
statusLabel: null,
statusColor: null
};
case "resource_healthy":
return {
@@ -120,8 +120,8 @@ function getEventMeta(eventType: AlertEventType): {
previewText:
"A resource in your organization has changed status.",
summary: "A resource in your organization has changed status.",
statusLabel: "Status Changed",
statusColor: "#f59e0b"
statusLabel: null,
statusColor: null
};
default:
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(
data: Record<string, unknown>
): { label: string; value: React.ReactNode }[] {
return Object.entries(data)
.filter(([key]) => key !== "orgId")
.filter(([key]) => key !== "orgId" && key !== "status")
.map(([key, value]) => ({
label: key
.replace(/([A-Z])/g, " $1")
@@ -154,16 +169,36 @@ export const AlertNotification = (props: AlertNotificationProps) => {
const meta = getEventMeta(eventType);
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 }[] = [
{ label: "Organization", value: orgId },
{
label: "Status",
value: (
<span style={{ color: meta.statusColor, fontWeight: 600 }}>
{meta.statusLabel}
</span>
)
},
...(resolvedStatus != null
? [
{
label: "Status",
value: (
<span
style={{
color: resolvedStatus.color,
fontWeight: 600
}}
>
{resolvedStatus.label}
</span>
)
}
]
: []),
{ label: "Time", value: new Date().toUTCString() },
...dataItems
];

View File

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

View File

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

View File

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

View File

@@ -42,6 +42,7 @@ export async function sendAlertWebhook(
const payload = {
event: context.eventType,
timestamp: new Date().toISOString(),
status: deriveStatus(context.eventType, context.data),
data: {
orgId: context.orgId,
...context.data
@@ -117,6 +118,38 @@ export async function sendAlertWebhook(
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)
// ---------------------------------------------------------------------------