One list of the current problems across servers and integrations, instead of waiting for a notification or visiting each page: servers that stopped reporting, full or nearly full disks and volumes (critical from 95%), Synology volume/disk problems, failed or uncovered Proxmox backups, failed Proxmox Backup Server verifications, container image updates, expired or expiring secrets/domains/Tailscale keys, failed Semaphore and Gitea runs, Uptime Kuma monitors that are down, overdue osTicket tickets, and integrations whose calls keep failing. Visible to every role, with severity and kind filters, search, sorting, CSV export and "Check now". It runs the same checks that send the notifications rather than a second copy of them: the detection in the health, automation, Proxmox backup, PBS, Docker update and Tailscale key checks is pulled out into shared collectors that both the schedulers and the page call, so the two can't disagree about what counts as a problem. Notification behaviour is unchanged, including the scheduled backup checks skipping integrations under a maintenance window. Unlike the notifications the page ignores the on/off toggles, and keeps problems under a maintenance window, marked silenced and counted apart. It reads live, so a result is reused for a minute (and Refresh can't re-run everything more than once every ten seconds), and every source has a 20 s limit so one hung integration can't hang the page. Anything it couldn't read is called out at the top instead of looking like all clear, and server checks pause for the same 20 minutes after a restart as the notifications do, with a note saying so. Also gives the newer integrations (PBS, osTicket, Uptime Kuma, phpIPAM) proper names in "integration down" notifications instead of their ids. Verified through the real routes against a scratch database with fake backends (offline and full-disk servers, secrets and domains, a silenced server, a fake PBS with failed verification, a hanging integration, a refused one, a failing-calls streak, caching, the restart grace period, auth), and by rendering the real page against that data in a browser: filters, search, silenced toggle, sorting, Check now, dark mode. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
173 lines
6.2 KiB
TypeScript
173 lines
6.2 KiB
TypeScript
import { useEffect, useState, type ReactNode } from "react";
|
|
import { Routes, Route, Navigate } from "react-router-dom";
|
|
import { api, type CurrentUser, type UserRole, UnauthorizedError } from "./api/client";
|
|
import Login from "./pages/Login";
|
|
import Dashboard from "./pages/Dashboard";
|
|
import Users from "./pages/Users";
|
|
import Sessions from "./pages/Sessions";
|
|
import AuditLog from "./pages/AuditLog";
|
|
import DiagLog from "./pages/DiagLog";
|
|
import Secrets from "./pages/Secrets";
|
|
import Ipam from "./pages/Ipam";
|
|
import Ports from "./pages/Ports";
|
|
import Dns from "./pages/Dns";
|
|
import Servers from "./pages/Servers";
|
|
import ServerDetail from "./pages/ServerDetail";
|
|
import Integrations from "./pages/Integrations";
|
|
import Docker from "./pages/Docker";
|
|
import Tailscale from "./pages/Tailscale";
|
|
import Semaphore from "./pages/Semaphore";
|
|
import Gitea from "./pages/Gitea";
|
|
import Proxmox from "./pages/Proxmox";
|
|
import Synology from "./pages/Synology";
|
|
import UptimeKuma from "./pages/UptimeKuma";
|
|
import PbsBackup from "./pages/PbsBackup";
|
|
import OsTicket from "./pages/OsTicket";
|
|
import AdminLinks from "./pages/AdminLinks";
|
|
import Alerts from "./pages/Alerts";
|
|
import Generator from "./pages/Generator";
|
|
import Maintenance from "./pages/Maintenance";
|
|
import Domains from "./pages/Domains";
|
|
import Consistency from "./pages/Consistency";
|
|
import Privacy from "./pages/Privacy";
|
|
import Settings from "./pages/Settings";
|
|
import NotificationSettings from "./pages/settings/NotificationSettings";
|
|
import BadgeSettings from "./pages/settings/BadgeSettings";
|
|
import DisplaySettings from "./pages/settings/DisplaySettings";
|
|
import TagSettings from "./pages/settings/TagSettings";
|
|
import CacheSettings from "./pages/settings/CacheSettings";
|
|
import LogSettings from "./pages/settings/LogSettings";
|
|
import BackupSettings from "./pages/settings/BackupSettings";
|
|
import AppShell from "./layout/AppShell";
|
|
import DialogHost from "./components/DialogHost";
|
|
import { setDateTimeSettings } from "./utils/date";
|
|
import { setPageSize } from "./utils/pageSize";
|
|
|
|
const roleRank: Record<UserRole, number> = { viewer: 0, operator: 1, admin: 2 };
|
|
|
|
function RequireRole({
|
|
user,
|
|
minRole,
|
|
children,
|
|
}: {
|
|
user: CurrentUser;
|
|
minRole: UserRole;
|
|
children: ReactNode;
|
|
}) {
|
|
if (roleRank[user.role] < roleRank[minRole]) {
|
|
return (
|
|
<div className="alert alert-danger">You don't have permission to view this page.</div>
|
|
);
|
|
}
|
|
return <>{children}</>;
|
|
}
|
|
|
|
export default function App() {
|
|
const [user, setUser] = useState<CurrentUser | null | undefined>(undefined);
|
|
|
|
useEffect(() => {
|
|
api.me().then(
|
|
(res) => setUser(res.user),
|
|
(err) => {
|
|
if (!(err instanceof UnauthorizedError)) {
|
|
console.error(err);
|
|
}
|
|
setUser(null);
|
|
},
|
|
);
|
|
api.settings.display().then((res) => {
|
|
setDateTimeSettings(res.display);
|
|
setPageSize(res.display.pageSize);
|
|
}).catch(() => {});
|
|
}, []);
|
|
|
|
if (user === undefined) {
|
|
return <div className="loading-screen">Loading…</div>;
|
|
}
|
|
|
|
if (user === null) {
|
|
return <Login />;
|
|
}
|
|
|
|
return (
|
|
<AppShell user={user}>
|
|
<Routes>
|
|
<Route path="/" element={<Dashboard user={user} />} />
|
|
<Route path="/servers" element={<Servers user={user} />} />
|
|
<Route path="/servers/:id" element={<ServerDetail user={user} />} />
|
|
<Route path="/dns" element={<Dns user={user} />} />
|
|
<Route path="/ipam" element={<Ipam user={user} />} />
|
|
<Route path="/ports" element={<Ports user={user} />} />
|
|
<Route path="/secrets" element={<Secrets user={user} />} />
|
|
<Route path="/integrations" element={<Integrations user={user} />} />
|
|
<Route path="/generator" element={<Generator />} />
|
|
<Route path="/privacy" element={<Privacy />} />
|
|
<Route path="/consistency" element={<Consistency user={user} />} />
|
|
<Route path="/domains" element={<Domains user={user} />} />
|
|
<Route path="/maintenance" element={<Maintenance user={user} />} />
|
|
<Route path="/docker" element={<Docker user={user} />} />
|
|
<Route path="/tailscale" element={<Tailscale user={user} />} />
|
|
<Route path="/semaphore" element={<Semaphore user={user} />} />
|
|
<Route path="/gitea" element={<Gitea user={user} />} />
|
|
<Route path="/proxmox" element={<Proxmox user={user} />} />
|
|
<Route path="/synology" element={<Synology user={user} />} />
|
|
<Route path="/uptime-kuma" element={<UptimeKuma user={user} />} />
|
|
<Route path="/pbs" element={<PbsBackup user={user} />} />
|
|
<Route path="/osticket" element={<OsTicket user={user} />} />
|
|
<Route path="/admin-links" element={<AdminLinks user={user} />} />
|
|
<Route path="/alerts" element={<Alerts user={user} />} />
|
|
<Route
|
|
path="/users"
|
|
element={
|
|
<RequireRole user={user} minRole="admin">
|
|
<Users />
|
|
</RequireRole>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/sessions"
|
|
element={
|
|
<RequireRole user={user} minRole="admin">
|
|
<Sessions />
|
|
</RequireRole>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/audit-log"
|
|
element={
|
|
<RequireRole user={user} minRole="operator">
|
|
<AuditLog />
|
|
</RequireRole>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/diag-log"
|
|
element={
|
|
<RequireRole user={user} minRole="admin">
|
|
<DiagLog />
|
|
</RequireRole>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/settings"
|
|
element={
|
|
<RequireRole user={user} minRole="admin">
|
|
<Settings />
|
|
</RequireRole>
|
|
}
|
|
>
|
|
<Route index element={<Navigate to="notifications" replace />} />
|
|
<Route path="notifications" element={<NotificationSettings />} />
|
|
<Route path="badges" element={<BadgeSettings />} />
|
|
<Route path="display" element={<DisplaySettings />} />
|
|
<Route path="tags" element={<TagSettings />} />
|
|
<Route path="cache" element={<CacheSettings />} />
|
|
<Route path="logs" element={<LogSettings />} />
|
|
<Route path="backup" element={<BackupSettings />} />
|
|
</Route>
|
|
</Routes>
|
|
<DialogHost />
|
|
</AppShell>
|
|
);
|
|
}
|