From 82a23069a51e8cc5480111ea5aee61d6cddae818 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Bombeck?= Date: Fri, 15 May 2026 22:21:03 +0200 Subject: [PATCH] feat(admin): surface the GeoLite2 availability state on the system status page Both the `/admin` overview snapshot and the full `/admin/system-status` page now read `offlineGeoEnabled` from `/api/version` and render a status row with a green chip when the offline databases are loaded or a yellow chip with the configuration hint when the runtime is on the ipwho.is fallback. The row only renders when the field is present so legacy `/api/version` responses do not produce a placeholder slot. Adds six locale keys per bundle (`admin.offlineGeoLabel`, `offlineGeoEnabled`, `offlineGeoFallback` plus the three `admin.overview.snapshotOfflineGeo*` siblings) across all six locales. --- messages/de.json | 6 +++ messages/en.json | 6 +++ messages/es.json | 6 +++ messages/fr.json | 6 +++ messages/it.json | 6 +++ messages/pl.json | 6 +++ .../admin/system-status-section.tsx | 41 +++++++++++++++++++ .../admin/system-status-summary.tsx | 22 ++++++++++ 8 files changed, 99 insertions(+) diff --git a/messages/de.json b/messages/de.json index f95878e3d..8d059356a 100644 --- a/messages/de.json +++ b/messages/de.json @@ -1996,6 +1996,9 @@ "webPushGlobal": "Browser-Push-Benachrichtigungen erlauben.", "integrationWebPush": "Web Push", "integrationBugReport": "Bug-Reports", + "offlineGeoLabel": "Geodaten", + "offlineGeoEnabled": "Offline (GeoLite2)", + "offlineGeoFallback": "Fallback (ipwho.is) — MAXMIND_LICENSE_KEY in CI setzen", "apiGlobal": "API-Endpunkte und API-Tokens erlauben.", "umamiTitle": "Umami", "umamiDescription": "Web-Analyse mit Umami", @@ -2176,6 +2179,9 @@ "snapshotStarted": "Gestartet", "snapshotBuildSha": "Build-SHA", "snapshotBuiltAt": "Gebaut am", + "snapshotOfflineGeo": "Geodaten", + "snapshotOfflineGeoOn": "Offline (GeoLite2)", + "snapshotOfflineGeoOff": "Fallback (ipwho.is)", "snapshotLoadError": "System-Snapshot konnte nicht geladen werden.", "snapshotLoading": "Snapshot wird geladen...", "auditTitle": "Letzte Aktivitäten", diff --git a/messages/en.json b/messages/en.json index 389a70156..71589ba8c 100644 --- a/messages/en.json +++ b/messages/en.json @@ -1996,6 +1996,9 @@ "webPushGlobal": "Allow browser push notifications.", "integrationWebPush": "Web Push", "integrationBugReport": "Bug Report", + "offlineGeoLabel": "Geo data", + "offlineGeoEnabled": "Offline (GeoLite2)", + "offlineGeoFallback": "Fallback (ipwho.is) — set MAXMIND_LICENSE_KEY in CI", "apiGlobal": "Allow API endpoints and API tokens.", "umamiTitle": "Umami", "umamiDescription": "Web analytics with Umami", @@ -2176,6 +2179,9 @@ "snapshotStarted": "Started", "snapshotBuildSha": "Build SHA", "snapshotBuiltAt": "Built at", + "snapshotOfflineGeo": "Geo data", + "snapshotOfflineGeoOn": "Offline (GeoLite2)", + "snapshotOfflineGeoOff": "Fallback (ipwho.is)", "snapshotLoadError": "Failed to load system snapshot.", "snapshotLoading": "Loading snapshot...", "auditTitle": "Recent activity", diff --git a/messages/es.json b/messages/es.json index 76e836274..f2c3d9c56 100644 --- a/messages/es.json +++ b/messages/es.json @@ -1996,6 +1996,9 @@ "webPushGlobal": "Allow browser push notifications.", "integrationWebPush": "Web Push", "integrationBugReport": "Bug Report", + "offlineGeoLabel": "Datos geográficos", + "offlineGeoEnabled": "Sin conexión (GeoLite2)", + "offlineGeoFallback": "Respaldo (ipwho.is) — configura MAXMIND_LICENSE_KEY en CI", "apiGlobal": "Allow API endpoints and API tokens.", "umamiTitle": "Umami", "umamiDescription": "Web analytics with Umami", @@ -2176,6 +2179,9 @@ "snapshotStarted": "Started", "snapshotBuildSha": "Build SHA", "snapshotBuiltAt": "Built at", + "snapshotOfflineGeo": "Datos geográficos", + "snapshotOfflineGeoOn": "Sin conexión (GeoLite2)", + "snapshotOfflineGeoOff": "Respaldo (ipwho.is)", "snapshotLoadError": "Failed to load system snapshot.", "snapshotLoading": "Loading snapshot...", "auditTitle": "Recent activity", diff --git a/messages/fr.json b/messages/fr.json index 65c94975d..6935e8e9d 100644 --- a/messages/fr.json +++ b/messages/fr.json @@ -1996,6 +1996,9 @@ "webPushGlobal": "Allow browser push notifications.", "integrationWebPush": "Web Push", "integrationBugReport": "Bug Report", + "offlineGeoLabel": "Données géographiques", + "offlineGeoEnabled": "Hors ligne (GeoLite2)", + "offlineGeoFallback": "Fallback (ipwho.is) — définissez MAXMIND_LICENSE_KEY dans la CI", "apiGlobal": "Allow API endpoints and API tokens.", "umamiTitle": "Umami", "umamiDescription": "Web analytics with Umami", @@ -2176,6 +2179,9 @@ "snapshotStarted": "Started", "snapshotBuildSha": "Build SHA", "snapshotBuiltAt": "Built at", + "snapshotOfflineGeo": "Données géographiques", + "snapshotOfflineGeoOn": "Hors ligne (GeoLite2)", + "snapshotOfflineGeoOff": "Fallback (ipwho.is)", "snapshotLoadError": "Failed to load system snapshot.", "snapshotLoading": "Loading snapshot...", "auditTitle": "Recent activity", diff --git a/messages/it.json b/messages/it.json index 21534cb01..18e171790 100644 --- a/messages/it.json +++ b/messages/it.json @@ -1996,6 +1996,9 @@ "webPushGlobal": "Allow browser push notifications.", "integrationWebPush": "Web Push", "integrationBugReport": "Bug Report", + "offlineGeoLabel": "Dati geografici", + "offlineGeoEnabled": "Offline (GeoLite2)", + "offlineGeoFallback": "Fallback (ipwho.is) — imposta MAXMIND_LICENSE_KEY in CI", "apiGlobal": "Allow API endpoints and API tokens.", "umamiTitle": "Umami", "umamiDescription": "Web analytics with Umami", @@ -2176,6 +2179,9 @@ "snapshotStarted": "Started", "snapshotBuildSha": "Build SHA", "snapshotBuiltAt": "Built at", + "snapshotOfflineGeo": "Dati geografici", + "snapshotOfflineGeoOn": "Offline (GeoLite2)", + "snapshotOfflineGeoOff": "Fallback (ipwho.is)", "snapshotLoadError": "Failed to load system snapshot.", "snapshotLoading": "Loading snapshot...", "auditTitle": "Recent activity", diff --git a/messages/pl.json b/messages/pl.json index ed0bfc772..29cc73add 100644 --- a/messages/pl.json +++ b/messages/pl.json @@ -1996,6 +1996,9 @@ "webPushGlobal": "Allow browser push notifications.", "integrationWebPush": "Web Push", "integrationBugReport": "Bug Report", + "offlineGeoLabel": "Dane geograficzne", + "offlineGeoEnabled": "Offline (GeoLite2)", + "offlineGeoFallback": "Awaryjne (ipwho.is) — ustaw MAXMIND_LICENSE_KEY w CI", "apiGlobal": "Allow API endpoints and API tokens.", "umamiTitle": "Umami", "umamiDescription": "Web analytics with Umami", @@ -2176,6 +2179,9 @@ "snapshotStarted": "Started", "snapshotBuildSha": "Build SHA", "snapshotBuiltAt": "Built at", + "snapshotOfflineGeo": "Dane geograficzne", + "snapshotOfflineGeoOn": "Offline (GeoLite2)", + "snapshotOfflineGeoOff": "Awaryjne (ipwho.is)", "snapshotLoadError": "Failed to load system snapshot.", "snapshotLoading": "Loading snapshot...", "auditTitle": "Recent activity", diff --git a/src/components/admin/system-status-section.tsx b/src/components/admin/system-status-section.tsx index 54a01e49e..221287c11 100644 --- a/src/components/admin/system-status-section.tsx +++ b/src/components/admin/system-status-section.tsx @@ -13,15 +13,36 @@ import { Globe, Key, Loader2, + Map, RotateCw, Server, Users, } from "lucide-react"; +import { useQuery } from "@tanstack/react-query"; import { Button } from "@/components/ui/button"; import { formatDateTime } from "@/lib/format"; import { useFormatters, useTranslations } from "@/lib/i18n/context"; import { StatusItem, useSystemStatus } from "./_shared"; +interface VersionResponse { + version: string; + buildSha: string | null; + builtAt: string | null; + offlineGeoEnabled?: boolean; +} + +function useOfflineGeoState() { + return useQuery({ + queryKey: ["public", "version"], + queryFn: async () => { + const res = await fetch("/api/version"); + if (!res.ok) throw new Error("Failed to load version"); + return (await res.json()).data as VersionResponse; + }, + staleTime: 5 * 60_000, + }); +} + // v1.4.16 phase B3: 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 @@ -44,6 +65,7 @@ export function SystemStatusSection() { const { t } = useTranslations(); const fmt = useFormatters(); const { data: status, isError, refetch, isFetching } = useSystemStatus(); + const { data: version } = useOfflineGeoState(); return (
@@ -163,6 +185,25 @@ export function SystemStatusSection() { className="text-dracula-green" /> )} + {/* v1.4.27 R5 — offline GeoLite2 availability. Renders only + when /api/version returns the new field so legacy + responses do not produce a placeholder row. */} + {version?.offlineGeoEnabled !== undefined && ( + + )}
) : isError ? ( // P19: surface load failures inline instead of leaving the diff --git a/src/components/admin/system-status-summary.tsx b/src/components/admin/system-status-summary.tsx index fe269e43b..985423b2a 100644 --- a/src/components/admin/system-status-summary.tsx +++ b/src/components/admin/system-status-summary.tsx @@ -17,6 +17,7 @@ import { Cog, Database, GitCommit, + Globe, Loader2, Server, Tag, @@ -30,6 +31,7 @@ interface VersionResponse { version: string; buildSha: string | null; builtAt: string | null; + offlineGeoEnabled?: boolean; } function useVersion() { @@ -116,6 +118,26 @@ export function SystemStatusSummary() { value={formatDateTime(version.builtAt)} /> )} + {/* v1.4.27 R5 — surface the offline-geo state so the maintainer + spots the missing MAXMIND_LICENSE_KEY without crawling logs. + The field is undefined on legacy responses; the row only + renders when /api/version answers the new shape. */} + {version?.offlineGeoEnabled !== undefined && ( + + )} ) : isError ? (