feat(proxy-fitness): on/off toggle for egress geo probe

Header toggle in Proxy Fitness persists settings.poolGeoProbeEnabled
(default on); the probe scheduler reads it each pass and skips when off.
The env POOL_GEO_PROBE_DISABLED remains a hard override. Smart rotation is
unaffected — it runs off the fitness registry from real request failures.
This commit is contained in:
MUH. IQRAM BAHRING
2026-08-07 08:01:55 +08:00
parent a443834f53
commit 37d03aa0d5
2 changed files with 49 additions and 1 deletions
@@ -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})` : ""}
</Button>
)}
<Toggle
size="sm"
checked={geoEnabled}
onChange={handleGeoToggle}
disabled={geoUpdating}
label="Geo probe"
description="Probe egress IP/country per pool ±30 min (pemakaian kuota geo-API)"
/>
<Button variant="secondary" size="sm" icon="refresh" onClick={fetchAll}>Refresh</Button>
</div>
</div>
+8
View File
@@ -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);