generated from methanium/template
Big updates
This commit is contained in:
parent
de54eb0b90
commit
56ae701d02
5 changed files with 401 additions and 214 deletions
|
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue