Add a Date & Time setting and fix inconsistent date formats app-wide
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>
This commit is contained in:
1 parent
f7357141eb
commit
f92f8de96e
12 files changed
+206
-17
No files matched your search
@@ -27,6 +27,13 @@ settingsRouter.get("/integration-colors", asyncHandler(async (_req, res) => {
|
||||
res.json({ integrationColors });
|
||||
}));
|
||||
|
||||
// Display prefs (date/time format) affect every page, so any signed-in user
|
||||
// can read them — same non-secret rationale as the badge-color endpoints.
|
||||
settingsRouter.get("/display", asyncHandler(async (_req, res) => {
|
||||
const { display } = await getSettings();
|
||||
res.json({ display });
|
||||
}));
|
||||
|
||||
const updateSchema = z.object({
|
||||
gotify: z.object({ enabled: z.boolean(), url: z.string(), token: z.string(), priority: z.number() }).partial().optional(),
|
||||
ntfy: z.object({ enabled: z.boolean(), url: z.string(), topic: z.string(), token: z.string(), priority: z.number() }).partial().optional(),
|
||||
@@ -57,6 +64,7 @@ const updateSchema = z.object({
|
||||
.optional(),
|
||||
providerColors: z.record(z.string()).optional(),
|
||||
integrationColors: z.record(z.string()).optional(),
|
||||
display: z.object({ dateFormat: z.enum(["ymd", "dmy", "mdy"]), timeFormat: z.enum(["24h", "12h"]) }).partial().optional(),
|
||||
});
|
||||
|
||||
settingsRouter.put("/", requireRole("admin"), asyncHandler(async (req, res) => {
|
||||
|
||||
@@ -46,6 +46,14 @@ export interface NotificationEvents {
|
||||
export type ProviderColors = Record<string, string>;
|
||||
export type IntegrationColors = Record<string, string>;
|
||||
|
||||
export type DateFormat = "ymd" | "dmy" | "mdy";
|
||||
export type TimeFormat = "24h" | "12h";
|
||||
|
||||
export interface DisplaySettings {
|
||||
dateFormat: DateFormat;
|
||||
timeFormat: TimeFormat;
|
||||
}
|
||||
|
||||
export interface AppSettings {
|
||||
gotify: GotifySettings;
|
||||
ntfy: NtfySettings;
|
||||
@@ -54,6 +62,7 @@ export interface AppSettings {
|
||||
notifications: NotificationEvents;
|
||||
providerColors: ProviderColors;
|
||||
integrationColors: IntegrationColors;
|
||||
display: DisplaySettings;
|
||||
}
|
||||
|
||||
const DEFAULTS: AppSettings = {
|
||||
@@ -71,6 +80,7 @@ const DEFAULTS: AppSettings = {
|
||||
},
|
||||
providerColors: {},
|
||||
integrationColors: {},
|
||||
display: { dateFormat: "ymd", timeFormat: "24h" },
|
||||
};
|
||||
|
||||
const KEYS = Object.keys(DEFAULTS) as (keyof AppSettings)[];
|
||||
|
||||
Reference in new issue
Block a user