"use client"; import dynamic from "next/dynamic"; import { Activity, AlertTriangle, Bell, BellRing, Clock, Cog, Database, Globe, Key, Loader2, Map, MemoryStick, Server, Users, } from "lucide-react"; import { QueryErrorRow } from "@/components/ui/query-error-row"; import { Skeleton } from "@/components/ui/skeleton"; import { formatDateTime } from "@/lib/format"; import { SettingsCard } from "@/components/settings/settings-card"; import { SettingsCardHeader } from "@/components/settings/_card-header"; import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { geoLookupState, StatusItem, useSystemStatus, type FailingQueue, } from "./_shared"; import { EmptyState } from "@/components/ui/empty-state"; // v1.4.16: Recharts is ~108 KiB Brotli — defer-load the host // metrics chart so the system-status page only ships it when an admin // actually opens this view. Splitting at the wrapper boundary (instead // of per-primitive) keeps Recharts' `findAllByType` reconciliation // working — same pattern the insights page uses for the scatter chart. const HostMetricsChart = dynamic( () => import("@/components/charts/chart-runtime").then((mod) => ({ default: mod.HostMetricsChart, })), { ssr: false, loading: () => ( ), }, ); /** * The queues whose jobs gave up, named. * * Before this, a background job that threw on every single run showed up as one * digit in an unnamed `errors` counter, and the module it fed rendered an empty * screen as if empty were the answer. A broken queue has to say its own name, * so this card is rendered whenever there is anything to report and stays * silent otherwise — an operator should be able to trust that a quiet card * means quiet queues, which is why the "cannot ask" case renders its own line * rather than looking identical to health. */ export function FailingJobsCard({ failingJobs, }: { failingJobs: { windowHours: number; queues: FailingQueue[] } | null; }) { const { t, tCount } = useTranslations(); if (failingJobs === null) { return (

{t("admin.failingJobs.unavailable")}

); } return ( {failingJobs.queues.length === 0 ? ( ) : (
    {failingJobs.queues.map((queue) => (
  • {queue.queue}{" "} {tCount("admin.failingJobs.count", queue.failures, { count: queue.failures, })}

    {t("admin.failingJobs.lastFailed", { when: formatDateTime(queue.lastFailedAt), })}

    {queue.lastError.length > 0 && (

    {queue.lastError}

    )}
  • ))}
)}
); } export function SystemStatusSection() { const { t } = useTranslations(); const fmt = useFormatters(); const { data: status, isError, error, refetch } = useSystemStatus(); return (
{status ? (
{status.worker.lastReminderCheck && ( )} {status.integrations.umami && ( )} {status.integrations.glitchtip && ( )} {status.integrations.webPush && ( )} {/* v1.4.27 — offline GeoLite2 availability, read from the admin status rather than the public /api/version. */}
) : isError ? ( // P19: surface load failures inline instead of leaving the // section spinning forever. The hook throws on non-OK // responses, so isError is true on 500s, network errors, etc. // v1.4.16 Wave-C MED — pair the alert with a Retry button so a // transient 500 (rolling deploy, DB blip) doesn't require a // full page reload to recover. void refetch()} retryLabel={t("admin.systemStatusRetry")} retrySlot="system-status-retry" /> ) : (
{t("admin.loadingStatus")}
)}
{status && }
); }