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:
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user