feat(ui): phase 5 — graphs, widgets, theming, PWA, boards, i18n

P5a: 24h uptime bar strip + latency chart on monitor detail, iframe
embed widget, generic JSON-path widget, accent/wallpaper/custom CSS
settings, PWA manifest + service worker, demo GIF.

P5b: transport picker + per-transport fields + test button in
settings; per-monitor alert-rule fields in dialogs.

P5c: container cpu/mem columns, SMART/ZFS/GPU sections on system
detail, subdomains section on domain detail.

P5d: login/setup page + auth middleware client handling, board
switcher + /b/:slug routes, full string extraction to typed t() keys
with {param} interpolation, complete cs locale, language select.

Authored By: TDvorak <[email protected]>

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas DvorakandDevin committed 2026-09-22 12:49:51 +02:00
1 parent f3143af351
commit e0ef60eca6
49 files changed
+2131 -426

No files matched your search

+12 -11
View File
@@ -2,6 +2,7 @@ import { useCallback, useEffect, useState } from "react";
import { api } from "@/api";
import type { PublicStatus } from "@/types";
import { cn } from "@/lib/utils";
import { t } from "@/i18n";
// Public status page — clean read-only view; refreshes every 30s.
// Route: /status/:slug -> GET /api/status-pages/:slug/public.
@@ -9,10 +10,10 @@ import { cn } from "@/lib/utils";
// status strings arrive as plain `string` — map with functions, not
// open dictionaries, so the lookup type stays honest.
function overallBanner(overall?: string) {
if (overall === "down") return { label: "Major outage", cls: "bg-down" };
if (overall === "maintenance") return { label: "Under maintenance", cls: "bg-text-faint/60" };
if (overall === "up") return { label: "All systems operational", cls: "bg-up" };
return { label: "Checking…", cls: "bg-text-faint/40" };
if (overall === "down") return { label: t("statusPublic.outage"), cls: "bg-down" };
if (overall === "maintenance") return { label: t("statusPublic.underMaintenance"), cls: "bg-text-faint/60" };
if (overall === "up") return { label: t("statusPublic.operational"), cls: "bg-up" };
return { label: t("statusPublic.checking"), cls: "bg-text-faint/40" };
}
function dotCls(status?: string) {
@@ -38,8 +39,8 @@ export function StatusPublicPage({ slug }: { slug: string }) {
return () => clearInterval(t);
}, [load]);
if (missing) return <main className="mx-auto max-w-2xl px-7 py-16 text-center text-[13px] text-text-faint">Status page not found.</main>;
if (!st) return <main className="mx-auto max-w-2xl px-7 py-16 text-center text-[13px] text-text-faint">Loading…</main>;
if (missing) return <main className="mx-auto max-w-2xl px-7 py-16 text-center text-[13px] text-text-faint">{t("statusPublic.notFound")}</main>;
if (!st) return <main className="mx-auto max-w-2xl px-7 py-16 text-center text-[13px] text-text-faint">{t("common.loading")}</main>;
const banner = overallBanner(st.overall);
@@ -56,7 +57,7 @@ export function StatusPublicPage({ slug }: { slug: string }) {
{(st.monitors ?? []).length > 0 && (
<section className="mb-6">
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">Services</h2>
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">{t("statusPublic.services")}</h2>
<div className="overflow-hidden rounded-[10px] border border-border">
{(st.monitors ?? []).map((m) => (
<div key={m.id} className="flex items-center gap-3 border-b border-border/60 px-4 py-2.5 last:border-0">
@@ -72,7 +73,7 @@ export function StatusPublicPage({ slug }: { slug: string }) {
{(st.systems ?? []).length > 0 && (
<section className="mb-6">
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">Systems</h2>
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">{t("statusPublic.systems")}</h2>
<div className="overflow-hidden rounded-[10px] border border-border">
{(st.systems ?? []).map((s) => (
<div key={s.id} className="flex items-center gap-3 border-b border-border/60 px-4 py-2.5 last:border-0">
@@ -87,7 +88,7 @@ export function StatusPublicPage({ slug }: { slug: string }) {
{(st.maintenance ?? []).length > 0 && (
<section className="mb-6">
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">Maintenance</h2>
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">{t("statusPublic.maintenance")}</h2>
<div className="overflow-hidden rounded-[10px] border border-border">
{(st.maintenance ?? []).map((w) => (
<div key={w.id} className="flex items-center gap-3 border-b border-border/60 px-4 py-2.5 last:border-0">
@@ -103,7 +104,7 @@ export function StatusPublicPage({ slug }: { slug: string }) {
{(st.incidents ?? []).length > 0 && (
<section className="mb-6">
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">Incidents</h2>
<h2 className="mb-2 font-mono text-[10.5px] uppercase tracking-[0.12em] text-text-faint">{t("statusPublic.incidents")}</h2>
<div className="flex flex-col gap-2">
{(st.incidents ?? []).map((inc) => (
<div key={inc.id} className="rounded-[10px] border border-border px-4 py-3">
@@ -122,7 +123,7 @@ export function StatusPublicPage({ slug }: { slug: string }) {
</section>
)}
<p className="mt-10 text-center font-mono text-[10px] text-text-faint">powered by dash</p>
<p className="mt-10 text-center font-mono text-[10px] text-text-faint">{t("statusPublic.footer")}</p>
</main>
);
}