mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-08 12:32:43 +00:00
fix(admin): read a geolocation provider the operator named as a decision, not a warning
This commit is contained in:
1 parent
88836452eb
commit
0a6811c03f
16 files changed
+157
-14
No files matched your search
@@ -36058,6 +36058,18 @@ components:
|
||||
type: string
|
||||
description: Host of the online geolocation provider the fallback would use, so an operator surface names the real one
|
||||
instead of assuming the default.
|
||||
geoLookup:
|
||||
type: string
|
||||
enum:
|
||||
- offline
|
||||
- online
|
||||
- off
|
||||
description: "How login IPs are placed (since v1.42): `offline` from the GeoLite2 databases, `online` by the provider
|
||||
above, `off` not at all (`IP_GEO_LOOKUP_DISABLED`)."
|
||||
geoProviderChosen:
|
||||
type: boolean
|
||||
description: The operator named the online provider in `IP_GEO_LOOKUP_URL` (since v1.42). False while the default is in
|
||||
use.
|
||||
required:
|
||||
- version
|
||||
- buildSha
|
||||
@@ -36068,6 +36080,8 @@ components:
|
||||
- docs
|
||||
- offlineGeoEnabled
|
||||
- geoProviderHost
|
||||
- geoLookup
|
||||
- geoProviderChosen
|
||||
additionalProperties: false
|
||||
description: The running build's identity plus the offline-geolocation state. Public — no authentication, no account data.
|
||||
AssistantFlagsEnvelope:
|
||||
|
||||
@@ -7292,6 +7292,8 @@
|
||||
"offlineGeoLabel": "Geodaten",
|
||||
"offlineGeoEnabled": "Offline (GeoLite2)",
|
||||
"offlineGeoFallback": "Online ({host}) — GeoLite2-Datenbanken einbinden und GEOLITE2_DIR setzen",
|
||||
"offlineGeoOff": "Aus",
|
||||
"offlineGeoChosen": "Online ({host})",
|
||||
"apiGlobal": "API-Endpunkte und API-Tokens erlauben.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Web-Analyse mit Umami.",
|
||||
|
||||
@@ -7292,6 +7292,8 @@
|
||||
"offlineGeoLabel": "Geo data",
|
||||
"offlineGeoEnabled": "Offline (GeoLite2)",
|
||||
"offlineGeoFallback": "Online ({host}) — mount GeoLite2 databases and set GEOLITE2_DIR",
|
||||
"offlineGeoOff": "Off",
|
||||
"offlineGeoChosen": "Online ({host})",
|
||||
"apiGlobal": "Allow API endpoints and API tokens.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Web analytics with Umami.",
|
||||
|
||||
@@ -7292,6 +7292,8 @@
|
||||
"offlineGeoLabel": "Datos geográficos",
|
||||
"offlineGeoEnabled": "Sin conexión (GeoLite2)",
|
||||
"offlineGeoFallback": "En línea ({host}) — monta las bases GeoLite2 y define GEOLITE2_DIR",
|
||||
"offlineGeoOff": "Desactivado",
|
||||
"offlineGeoChosen": "En línea ({host})",
|
||||
"apiGlobal": "Permitir endpoints de API y tokens de API.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Analítica web con Umami.",
|
||||
|
||||
@@ -7292,6 +7292,8 @@
|
||||
"offlineGeoLabel": "Données géographiques",
|
||||
"offlineGeoEnabled": "Hors ligne (GeoLite2)",
|
||||
"offlineGeoFallback": "En ligne ({host}) — montez les bases GeoLite2 et définissez GEOLITE2_DIR",
|
||||
"offlineGeoOff": "Désactivé",
|
||||
"offlineGeoChosen": "En ligne ({host})",
|
||||
"apiGlobal": "Autoriser les points de terminaison d'API et les jetons d'API.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Analyse web avec Umami.",
|
||||
|
||||
@@ -7292,6 +7292,8 @@
|
||||
"offlineGeoLabel": "Dati geografici",
|
||||
"offlineGeoEnabled": "Offline (GeoLite2)",
|
||||
"offlineGeoFallback": "Online ({host}) — monta i database GeoLite2 e imposta GEOLITE2_DIR",
|
||||
"offlineGeoOff": "Disattivato",
|
||||
"offlineGeoChosen": "Online ({host})",
|
||||
"apiGlobal": "Consentire endpoint API e token API.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Analisi web con Umami.",
|
||||
|
||||
@@ -7268,6 +7268,8 @@
|
||||
"offlineGeoLabel": "위치 데이터",
|
||||
"offlineGeoEnabled": "오프라인 (GeoLite2)",
|
||||
"offlineGeoFallback": "온라인 ({host}) — GeoLite2 데이터베이스를 마운트하고 GEOLITE2_DIR을 설정하세요",
|
||||
"offlineGeoOff": "꺼짐",
|
||||
"offlineGeoChosen": "온라인 ({host})",
|
||||
"apiGlobal": "API 엔드포인트와 API 토큰을 허용해요.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Umami 웹 분석이에요.",
|
||||
|
||||
@@ -7292,6 +7292,8 @@
|
||||
"offlineGeoLabel": "Dane geograficzne",
|
||||
"offlineGeoEnabled": "Offline (GeoLite2)",
|
||||
"offlineGeoFallback": "Online ({host}) — podłącz bazy GeoLite2 i ustaw GEOLITE2_DIR",
|
||||
"offlineGeoOff": "Wyłączone",
|
||||
"offlineGeoChosen": "Online ({host})",
|
||||
"apiGlobal": "Zezwól na punkty końcowe API i tokeny API.",
|
||||
"umamiTitle": "Umami",
|
||||
"umamiDescription": "Analityka internetowa z Umami.",
|
||||
|
||||
@@ -9,6 +9,8 @@ const resolveGeoProviderHostMock = vi.fn(() => "ipwho.is");
|
||||
vi.mock("@/lib/geo", () => ({
|
||||
offlineGeoReady: () => offlineGeoReadyMock(),
|
||||
resolveGeoProviderHost: () => resolveGeoProviderHostMock(),
|
||||
geoLookupMode: () => (offlineGeoReadyMock() ? "offline" : "online"),
|
||||
geoProviderChosen: () => false,
|
||||
}));
|
||||
|
||||
import { GET } from "../route";
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { apiHandler } from "@/lib/api-handler";
|
||||
import { apiSuccess } from "@/lib/api-response";
|
||||
import { offlineGeoReady, resolveGeoProviderHost } from "@/lib/geo";
|
||||
import {
|
||||
geoLookupMode,
|
||||
geoProviderChosen,
|
||||
offlineGeoReady,
|
||||
resolveGeoProviderHost,
|
||||
} from "@/lib/geo";
|
||||
import packageJson from "../../../../package.json";
|
||||
|
||||
// v1.4.27 R5 — `offlineGeoEnabled` reads from the same source the geo
|
||||
@@ -64,5 +69,7 @@ export const GET = apiHandler(async () => {
|
||||
docs: "https://docs.healthlog.dev",
|
||||
offlineGeoEnabled,
|
||||
geoProviderHost,
|
||||
geoLookup: geoLookupMode(),
|
||||
geoProviderChosen: geoProviderChosen(),
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,41 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { geoLookupState, type PublicVersion } from "../_shared";
|
||||
|
||||
const base: PublicVersion = {
|
||||
version: "1.42.0",
|
||||
buildSha: null,
|
||||
builtAt: null,
|
||||
};
|
||||
|
||||
describe("geoLookupState", () => {
|
||||
it("warns only about the unconfigured default provider", () => {
|
||||
expect(
|
||||
geoLookupState({
|
||||
...base,
|
||||
offlineGeoEnabled: false,
|
||||
geoLookup: "online",
|
||||
}),
|
||||
).toBe("default");
|
||||
});
|
||||
|
||||
it("treats a provider the operator named as a decision", () => {
|
||||
expect(
|
||||
geoLookupState({
|
||||
...base,
|
||||
offlineGeoEnabled: false,
|
||||
geoLookup: "online",
|
||||
geoProviderChosen: true,
|
||||
}),
|
||||
).toBe("chosen");
|
||||
});
|
||||
|
||||
it("reads a switched-off lookup and the offline databases as settled", () => {
|
||||
expect(
|
||||
geoLookupState({ ...base, offlineGeoEnabled: false, geoLookup: "off" }),
|
||||
).toBe("off");
|
||||
expect(geoLookupState({ ...base, offlineGeoEnabled: true })).toBe(
|
||||
"offline",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -590,6 +590,23 @@ export interface PublicVersion {
|
||||
// offline tier is absent (host of IP_GEO_LOOKUP_URL, default ipwho.is).
|
||||
// Optional so legacy /api/version responses still satisfy the shape.
|
||||
geoProviderHost?: string;
|
||||
/** v1.42 — `offline`, `online` or `off` (IP_GEO_LOOKUP_DISABLED). */
|
||||
geoLookup?: "offline" | "online" | "off";
|
||||
/** v1.42 — the operator named the provider in IP_GEO_LOOKUP_URL. */
|
||||
geoProviderChosen?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* The geo row's state. Offline, switched off, or a provider the operator
|
||||
* named are all decisions and read as fine; only the unconfigured default
|
||||
* warns, because it sends every login IP to a third party nobody chose.
|
||||
*/
|
||||
export function geoLookupState(
|
||||
version: PublicVersion,
|
||||
): "offline" | "off" | "chosen" | "default" {
|
||||
if (version.offlineGeoEnabled) return "offline";
|
||||
if (version.geoLookup === "off") return "off";
|
||||
return version.geoProviderChosen ? "chosen" : "default";
|
||||
}
|
||||
|
||||
/** Shared `/api/version` reader — the system-status section + overview
|
||||
|
||||
@@ -23,6 +23,7 @@ import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { useFormatters, useTranslations } from "@/lib/i18n/context";
|
||||
import {
|
||||
geoLookupState,
|
||||
StatusItem,
|
||||
usePublicVersion,
|
||||
useSystemStatus,
|
||||
@@ -245,13 +246,20 @@ export function SystemStatusSection() {
|
||||
icon={Map}
|
||||
label={t("admin.offlineGeoLabel")}
|
||||
value={
|
||||
version.offlineGeoEnabled
|
||||
? t("admin.offlineGeoEnabled")
|
||||
: t("admin.offlineGeoFallback", {
|
||||
host: version.geoProviderHost ?? "ipwho.is",
|
||||
})
|
||||
{
|
||||
offline: t("admin.offlineGeoEnabled"),
|
||||
off: t("admin.offlineGeoOff"),
|
||||
chosen: t("admin.offlineGeoChosen", {
|
||||
host: version.geoProviderHost ?? "ipwho.is",
|
||||
}),
|
||||
default: t("admin.offlineGeoFallback", {
|
||||
host: version.geoProviderHost ?? "ipwho.is",
|
||||
}),
|
||||
}[geoLookupState(version)]
|
||||
}
|
||||
tone={
|
||||
geoLookupState(version) === "default" ? "warning" : "success"
|
||||
}
|
||||
tone={version.offlineGeoEnabled ? "success" : "warning"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -26,7 +26,12 @@ import { SettingsCard } from "@/components/settings/settings-card";
|
||||
import { SettingsCardHeader } from "@/components/settings/_card-header";
|
||||
import { QueryErrorRow } from "@/components/ui/query-error-row";
|
||||
import { useTranslations } from "@/lib/i18n/context";
|
||||
import { StatusItem, usePublicVersion, useSystemStatus } from "./_shared";
|
||||
import {
|
||||
geoLookupState,
|
||||
StatusItem,
|
||||
usePublicVersion,
|
||||
useSystemStatus,
|
||||
} from "./_shared";
|
||||
import { useKeyBackupStatus } from "./use-key-backup-status";
|
||||
|
||||
export function SystemStatusSummary() {
|
||||
@@ -95,13 +100,20 @@ export function SystemStatusSummary() {
|
||||
icon={Globe}
|
||||
label={t("admin.overview.snapshotOfflineGeo")}
|
||||
value={
|
||||
version.offlineGeoEnabled
|
||||
? t("admin.overview.snapshotOfflineGeoOn")
|
||||
: t("admin.overview.snapshotOfflineGeoOff", {
|
||||
host: version.geoProviderHost ?? "ipwho.is",
|
||||
})
|
||||
{
|
||||
offline: t("admin.overview.snapshotOfflineGeoOn"),
|
||||
off: t("admin.offlineGeoOff"),
|
||||
chosen: t("admin.offlineGeoChosen", {
|
||||
host: version.geoProviderHost ?? "ipwho.is",
|
||||
}),
|
||||
default: t("admin.overview.snapshotOfflineGeoOff", {
|
||||
host: version.geoProviderHost ?? "ipwho.is",
|
||||
}),
|
||||
}[geoLookupState(version)]
|
||||
}
|
||||
tone={
|
||||
geoLookupState(version) === "default" ? "warning" : "success"
|
||||
}
|
||||
tone={version.offlineGeoEnabled ? "success" : "warning"}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -169,6 +169,24 @@ export function resolveGeoProviderHost(): string {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* How login IPs are placed on this host: from the mounted GeoLite2 databases,
|
||||
* by the online provider, or not at all (`IP_GEO_LOOKUP_DISABLED`).
|
||||
*/
|
||||
export function geoLookupMode(): "offline" | "online" | "off" {
|
||||
if (offlineGeoReady()) return "offline";
|
||||
return envFlag("IP_GEO_LOOKUP_DISABLED") ? "off" : "online";
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the operator named the online provider (`IP_GEO_LOOKUP_URL`).
|
||||
* An explicit provider is a decision about where login IPs go; the default
|
||||
* is not, so the admin status only warns about the default.
|
||||
*/
|
||||
export function geoProviderChosen(): boolean {
|
||||
return Boolean(process.env.IP_GEO_LOOKUP_URL?.trim());
|
||||
}
|
||||
|
||||
/**
|
||||
* Directory the offline MMDBs are read from.
|
||||
*
|
||||
|
||||
@@ -239,6 +239,16 @@ const versionResponse = z
|
||||
.describe(
|
||||
"Host of the online geolocation provider the fallback would use, so an operator surface names the real one instead of assuming the default.",
|
||||
),
|
||||
geoLookup: z
|
||||
.enum(["offline", "online", "off"])
|
||||
.describe(
|
||||
"How login IPs are placed (since v1.42): `offline` from the GeoLite2 databases, `online` by the provider above, `off` not at all (`IP_GEO_LOOKUP_DISABLED`).",
|
||||
),
|
||||
geoProviderChosen: z
|
||||
.boolean()
|
||||
.describe(
|
||||
"The operator named the online provider in `IP_GEO_LOOKUP_URL` (since v1.42). False while the default is in use.",
|
||||
),
|
||||
})
|
||||
.meta({
|
||||
id: "VersionResponse",
|
||||
|
||||
Reference in new issue
Block a user