diff --git a/src/app/(dashboard)/dashboard/proxy-fitness/page.js b/src/app/(dashboard)/dashboard/proxy-fitness/page.js
index 5d886489..36a71044 100644
--- a/src/app/(dashboard)/dashboard/proxy-fitness/page.js
+++ b/src/app/(dashboard)/dashboard/proxy-fitness/page.js
@@ -1,7 +1,7 @@
"use client";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
-import { Badge, Button, Card, CardSkeleton, Input, ConfirmModal } from "@/shared/components";
+import { Badge, Button, Card, CardSkeleton, Input, ConfirmModal, Toggle } from "@/shared/components";
import ProviderIcon from "@/shared/components/ProviderIcon";
import { useNotificationStore } from "@/store/notificationStore";
@@ -69,6 +69,8 @@ export default function ProxyFitnessPage() {
const [clearingScope, setClearingScope] = useState(null); // poolId::scope while clearing
const [confirmClearAll, setConfirmClearAll] = useState(false);
const [clearingAll, setClearingAll] = useState(false);
+ const [geoEnabled, setGeoEnabled] = useState(true);
+ const [geoUpdating, setGeoUpdating] = useState(false);
const providerMenuRef = useRef(null);
const notify = useNotificationStore();
@@ -109,6 +111,36 @@ export default function ProxyFitnessPage() {
fetchAll();
}, [fetchAll]);
+ useEffect(() => {
+ fetch("/api/settings", { cache: "no-store" })
+ .then((r) => (r.ok ? r.json() : {}))
+ .then((s) => {
+ if (typeof s.poolGeoProbeEnabled === "boolean") setGeoEnabled(s.poolGeoProbeEnabled);
+ })
+ .catch(() => {});
+ }, []);
+
+ const handleGeoToggle = async (next) => {
+ setGeoUpdating(true);
+ setGeoEnabled(next);
+ try {
+ const res = await fetch("/api/settings", {
+ method: "PATCH",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ poolGeoProbeEnabled: next }),
+ });
+ if (!res.ok) {
+ setGeoEnabled(!next);
+ throw new Error(`HTTP ${res.status}`);
+ }
+ notify.success(next ? "Geo probe enabled" : "Geo probe disabled");
+ } catch (err) {
+ notify.error(`Update failed: ${err.message}`);
+ } finally {
+ setGeoUpdating(false);
+ }
+ };
+
const records = useMemo(() => buildRecords(fitness, pools), [fitness, pools]);
// Providers present in the fitness data (filter options)
@@ -215,6 +247,14 @@ export default function ProxyFitnessPage() {
Clear All{providerFilter !== "all" ? ` (${providerFilter})` : ""}
)}
+
diff --git a/src/lib/network/poolEgressProbe.js b/src/lib/network/poolEgressProbe.js
index 43a84eb6..61b1e4b7 100644
--- a/src/lib/network/poolEgressProbe.js
+++ b/src/lib/network/poolEgressProbe.js
@@ -10,6 +10,7 @@
import { getProxyPools } from "@/models";
import { probePoolGeo, setPoolGeo, getPoolGeo } from "open-sse/services/poolGeo.js";
import { isNonServerRuntime } from "@/sse/services/backgroundTokenRefresh.js";
+import { getSettings } from "@/lib/localDb";
const PROBE_INTERVAL_MS = 30 * 60 * 1000;
const INITIAL_DELAY_MS = 15 * 1000;
@@ -44,6 +45,13 @@ async function probeAll() {
if (probing) return;
probing = true;
try {
+ // UI toggle (settings.poolGeoProbeEnabled) gates the whole feature; the
+ // env var remains a hard override for headless setups.
+ try {
+ const settings = await getSettings();
+ if (settings?.poolGeoProbeEnabled === false) return;
+ } catch { /* DB hiccup — keep probing (fail-open) */ }
+
const pools = await getProxyPools({ isActive: true });
const now = Date.now();
const active = (pools || []).filter((p) => !!p?.proxyUrl);