Different tables used different date formats: Servers & Tasks used a fixed "YYYY-MM-DD HH:mm:ss" (24h), while Audit Log, DNS, Integrations (Tailscale/Semaphore), and Users called plain .toLocaleString() with no options, which renders using the browser's own locale — different per browser/OS, and inconsistent with the other pages' fixed format. - New Settings -> Date & Time page: pick date order (YYYY-MM-DD, DD/MM/YYYY, MM/DD/YYYY) and 12h vs 24h clock, with a live preview. - utils/date.ts's formatDateTime() now reads these settings instead of being hardcoded to sv-SE/24h; the setting is fetched once at app startup (alongside /api/me) via a new non-secret GET /api/settings/display (any signed-in user, same rationale as the badge-color endpoints) and applied immediately on save too, without needing a page reload. - Switched every remaining raw new Date(...).toLocaleString() call (Audit Log, DNS zone sync time, Tailscale/Semaphore last-seen, Users' last login) over to the shared formatter, so every table now renders dates identically. Verified the formatter's date-order x 12h logic against all six combinations plus the midnight/noon 12h edge cases, and the settings endpoints end-to-end (defaults, partial updates, validation rejection, audit logging) against the real dev server. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
109 lines
3.4 KiB
TypeScript
109 lines
3.4 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 AuditLog from "./pages/AuditLog";
|
|
import Secrets from "./pages/Secrets";
|
|
import Ipam from "./pages/Ipam";
|
|
import Dns from "./pages/Dns";
|
|
import ServersTasks from "./pages/ServersTasks";
|
|
import ServerDetail from "./pages/ServerDetail";
|
|
import Integrations from "./pages/Integrations";
|
|
import Settings from "./pages/Settings";
|
|
import NotificationSettings from "./pages/settings/NotificationSettings";
|
|
import BadgeSettings from "./pages/settings/BadgeSettings";
|
|
import DateTimeSettings from "./pages/settings/DateTimeSettings";
|
|
import CacheSettings from "./pages/settings/CacheSettings";
|
|
import AppShell from "./layout/AppShell";
|
|
import { setDateTimeSettings } from "./utils/date";
|
|
|
|
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)).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={<ServersTasks 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="/secrets" element={<Secrets user={user} />} />
|
|
<Route path="/integrations" element={<Integrations user={user} />} />
|
|
<Route
|
|
path="/users"
|
|
element={
|
|
<RequireRole user={user} minRole="admin">
|
|
<Users />
|
|
</RequireRole>
|
|
}
|
|
/>
|
|
<Route
|
|
path="/audit-log"
|
|
element={
|
|
<RequireRole user={user} minRole="operator">
|
|
<AuditLog />
|
|
</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="date-time" element={<DateTimeSettings />} />
|
|
<Route path="cache" element={<CacheSettings />} />
|
|
</Route>
|
|
</Routes>
|
|
</AppShell>
|
|
);
|
|
}
|