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.
This commit is contained in:
Marc-André Bombeck committed 2026-05-15 22:21:03 +02:00
1 parent 4eb22387a2
commit 82a23069a5
8 files changed
+99

No files matched your search

+6
View File
@@ -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",
+6
View File
@@ -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",
+6
View File
@@ -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",
+6
View File
@@ -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",
+6
View File
@@ -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",
+6
View File
@@ -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",
@@ -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 (
<div className="space-y-6">
@@ -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 && (
<StatusItem
icon={Map}
label={t("admin.offlineGeoLabel")}
value={
version.offlineGeoEnabled
? t("admin.offlineGeoEnabled")
: t("admin.offlineGeoFallback")
}
className={
version.offlineGeoEnabled
? "text-dracula-green"
: "text-dracula-yellow"
}
/>
)}
</div>
) : isError ? (
// P19: surface load failures inline instead of leaving the
@@ -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 && (
<StatusItem
icon={Globe}
label={t("admin.overview.snapshotOfflineGeo")}
value={
version.offlineGeoEnabled
? t("admin.overview.snapshotOfflineGeoOn")
: t("admin.overview.snapshotOfflineGeoOff")
}
className={
version.offlineGeoEnabled
? "text-dracula-green"
: "text-dracula-yellow"
}
/>
)}
</div>
) : isError ? (
<div