Big updates

This commit is contained in:
Alois 2026-07-29 22:14:01 +02:00
commit 56ae701d02
Signed by: alois
SSH key fingerprint: SHA256:GBzT2DXvAuGV9XIV5W3WrzVpjU54FThmxHXdbz95J24
5 changed files with 401 additions and 214 deletions

View file

@ -2,6 +2,7 @@ import { createFileRoute } from "@tanstack/react-router";
import {
AlertTriangle,
Check,
ChevronRight,
Clock3,
ExternalLink,
RefreshCw,
@ -10,7 +11,7 @@ import {
} from "lucide-react";
import { useEffect, useRef, useState } from "react";
import type { Incident, Service, ServiceStatus, Severity } from "../status";
import type { Incident, Service, ServiceStatus } from "../status";
import { useStatus } from "../use-status";
import { formatTime } from "../utils";
import {
@ -30,6 +31,7 @@ export const Route = createFileRoute("/")({ component: Home });
function Home() {
const { snapshot } = useStatus();
const [showPastIncidents, setShowPastIncidents] = useState(false);
const services =
snapshot?.categories.flatMap((category) => category.services) ?? [];
const failures = services.filter(
@ -37,6 +39,11 @@ function Home() {
);
const activeIncidents =
snapshot?.incidents.filter((incident) => !incident.resolvedAt) ?? [];
const pastIncidents =
snapshot?.incidents
.filter((incident) => incident.resolvedAt)
.toReversed()
.slice(0, 5) ?? [];
const pending = services.some((service) => service.status === "unknown");
const ready = snapshot !== null && !pending;
const healthy =
@ -72,11 +79,13 @@ function Home() {
</div>
</div>
<div className="grid w-full grid-cols-1 justify-center gap-6 pb-10 sm:grid-cols-[repeat(auto-fit,24rem)]">
{activeIncidents.map((incident) => (
<ActiveIncidentCard key={incident.id} incident={incident} />
))}
</div>
{activeIncidents.length > 0 && (
<div className="grid w-full grid-cols-1 justify-center gap-6 pb-10 sm:grid-cols-[repeat(auto-fit,24rem)]">
{activeIncidents.map((incident) => (
<IncidentCard key={incident.id} incident={incident} />
))}
</div>
)}
<div className="w-full flex flex-col lg:flex-row! gap-15 items-center justify-center">
{snapshot?.categories.map((category) => (
@ -103,27 +112,40 @@ function Home() {
))}
</div>
{!!snapshot?.incidents.some((incident) => incident.resolvedAt) && (
<section className="mt-12" aria-labelledby="resolved-incidents">
<h2 id="resolved-incidents" className="mb-4 text-lg font-semibold">
Recently resolved
</h2>
<div className="space-y-3">
{snapshot.incidents
.filter((incident) => incident.resolvedAt)
.reverse()
.slice(0, 5)
.map((incident) => (
<IncidentCard key={incident.id} incident={incident} resolved />
{pastIncidents.length > 0 && (
<section className="pt-10 w-full flex flex-col items-center">
<Button
type="button"
variant="ghost"
aria-expanded={showPastIncidents}
aria-controls="past-incidents"
onClick={() => setShowPastIncidents((visible) => !visible)}
>
<ChevronRight
className={cn(
"size-4 transition-transform",
showPastIncidents && "rotate-90",
)}
/>
Show Past Incidents
</Button>
{showPastIncidents && (
<div
id="past-incidents"
className="mt-4 grid w-full grid-cols-1 justify-center gap-6 sm:grid-cols-[repeat(auto-fit,24rem)]"
>
{pastIncidents.map((incident) => (
<IncidentCard key={incident.id} incident={incident} />
))}
</div>
</div>
)}
</section>
)}
</main>
);
}
function ActiveIncidentCard({ incident }: { incident: Incident }) {
function IncidentCard({ incident }: { incident: Incident }) {
const messageRef = useRef<HTMLParagraphElement>(null);
const [truncated, setTruncated] = useState(false);
@ -173,7 +195,9 @@ function ActiveIncidentCard({ incident }: { incident: Incident }) {
<DialogHeader>
<DialogTitle>{incident.title}</DialogTitle>
<DialogDescription>
{severity} incident started {formatTime(incident.createdAt)}
{severity} incident{" "}
{incident.resolvedAt ? "resolved" : "started"}{" "}
{formatTime(incident.resolvedAt ?? incident.createdAt)}
</DialogDescription>
</DialogHeader>
<p className="max-h-[60vh] overflow-y-auto whitespace-pre-wrap text-foreground [overflow-wrap:anywhere]">
@ -184,7 +208,10 @@ function ActiveIncidentCard({ incident }: { incident: Incident }) {
)}
<div className="mt-auto flex gap-3 border-t border-[#C5CED6] px-4 py-2 dark:border-[#242D38]">
<p>{severity}</p>
<p>Started {formatTime(incident.createdAt)}</p>
<p>
{incident.resolvedAt ? "Resolved" : "Started"}{" "}
{formatTime(incident.resolvedAt ?? incident.createdAt)}
</p>
</div>
</CardContent>
</article>
@ -260,43 +287,3 @@ function ServiceRow({
</div>
);
}
const severityStyles: Record<Severity, string> = {
minor:
"border-amber-500/30 bg-amber-500/[0.07] text-amber-700 dark:text-amber-300",
major:
"border-orange-500/30 bg-orange-500/[0.07] text-orange-700 dark:text-orange-300",
critical:
"border-red-500/35 bg-red-500/[0.08] text-red-700 dark:text-red-300",
};
function IncidentCard({
incident,
resolved = false,
}: {
incident: Incident;
resolved?: boolean;
}) {
return (
<article
className={`rounded-xl border p-5 ${resolved ? "border-border bg-card/60" : severityStyles[incident.severity]}`}
>
<div className="flex flex-wrap items-center justify-between gap-2">
<h3 className="font-semibold">{incident.title}</h3>
<span className="text-[0.65rem] font-bold uppercase tracking-[0.18em]">
{resolved ? "Resolved" : incident.severity}
</span>
</div>
<p
className={`mt-2 text-sm leading-6 ${resolved ? "text-muted-foreground" : "text-foreground/80"}`}
>
{incident.message}
</p>
<p className="mt-3 text-xs text-muted-foreground">
{resolved && incident.resolvedAt
? `Resolved ${formatTime(incident.resolvedAt)}`
: `Started ${formatTime(incident.createdAt)}`}
</p>
</article>
);
}