Spaces:
Sleeping
Sleeping
File size: 5,572 Bytes
1460b37 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 | "use client";
import { useCallback, useEffect, useState } from "react";
import PropTypes from "prop-types";
import Card from "./Card";
import Select from "./Select";
import Badge from "./Badge";
const NONE_PROXY_POOL_VALUE = "__none__";
const STRATEGIES = [
{ value: "none", label: "None (single pool)" },
{ value: "round-robin", label: "Round-robin" },
{ value: "random", label: "Random" },
];
export default function NoAuthProxyCard({ providerId }) {
const [proxyPools, setProxyPools] = useState([]);
const [proxyPoolId, setProxyPoolId] = useState(NONE_PROXY_POOL_VALUE);
const [rotateStrategy, setRotateStrategy] = useState("none");
const [saving, setSaving] = useState(false);
const [savedFlash, setSavedFlash] = useState(false);
useEffect(() => {
let cancelled = false;
Promise.all([
fetch("/api/proxy-pools?isActive=true", { cache: "no-store" }).then((r) => r.ok ? r.json() : { proxyPools: [] }),
fetch("/api/settings", { cache: "no-store" }).then((r) => r.ok ? r.json() : {}),
]).then(([poolData, settingsData]) => {
if (cancelled) return;
setProxyPools(poolData.proxyPools || []);
const override = (settingsData.providerStrategies || {})[providerId] || {};
setProxyPoolId(override.proxyPoolId || NONE_PROXY_POOL_VALUE);
setRotateStrategy(override.rotateStrategy || "none");
}).catch(() => {});
return () => { cancelled = true; };
}, [providerId]);
const save = useCallback(async (poolId, strategy) => {
setSaving(true);
try {
const res = await fetch("/api/settings", { cache: "no-store" });
const data = res.ok ? await res.json() : {};
const current = data.providerStrategies || {};
const override = { ...(current[providerId] || {}) };
if (poolId === NONE_PROXY_POOL_VALUE) delete override.proxyPoolId;
else override.proxyPoolId = poolId;
if (strategy === "none") delete override.rotateStrategy;
else override.rotateStrategy = strategy;
const updated = { ...current };
if (Object.keys(override).length === 0) delete updated[providerId];
else updated[providerId] = override;
await fetch("/api/settings", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ providerStrategies: updated }),
});
setSavedFlash(true);
setTimeout(() => setSavedFlash(false), 1500);
} catch (e) {
console.log("Save proxy config error:", e);
} finally {
setSaving(false);
}
}, [providerId]);
const handlePoolChange = (newPoolId) => {
setProxyPoolId(newPoolId);
save(newPoolId, rotateStrategy);
};
const handleStrategyChange = (newStrategy) => {
setRotateStrategy(newStrategy);
save(proxyPoolId, newStrategy);
};
const canRotate = proxyPools.length >= 2;
const isRotation = rotateStrategy !== "none";
return (
<Card>
<div className="flex items-center gap-3 mb-4">
<div className="inline-flex items-center justify-center w-10 h-10 rounded-full bg-green-500/10 text-green-500">
<span className="material-symbols-outlined text-[20px]">lock_open</span>
</div>
<div className="flex-1">
<p className="text-sm font-medium">No authentication required</p>
<p className="text-xs text-text-muted">This provider is ready to use. Optionally route requests through a proxy pool to bypass IP-based limits.</p>
</div>
{savedFlash && <Badge variant="success" size="sm">Saved</Badge>}
</div>
<Select
label="Proxy Pool"
value={proxyPoolId}
onChange={(e) => handlePoolChange(e.target.value)}
disabled={saving || isRotation}
options={[
{ value: NONE_PROXY_POOL_VALUE, label: "None (direct)" },
...proxyPools.map((pool) => ({ value: pool.id, label: pool.name })),
]}
hint={isRotation ? "Pool selector is ignored when rotation is active — all active pools are used." : undefined}
/>
<div className="flex flex-col gap-2 mt-4">
<label className="text-sm font-medium text-text-main">Rotation Strategy</label>
<select
value={rotateStrategy}
onChange={(e) => handleStrategyChange(e.target.value)}
disabled={saving}
className="py-2 px-3 text-sm text-text-main bg-white dark:bg-white/5 border border-black/10 dark:border-white/10 rounded-md focus:ring-1 focus:ring-primary/30 focus:border-primary/50 focus:outline-none transition-all disabled:opacity-50"
>
{STRATEGIES.map((s) => (
<option key={s.value} value={s.value} disabled={s.value !== "none" && !canRotate}>
{s.label}
</option>
))}
</select>
<p className="text-xs text-text-muted">
{!canRotate
? `Need at least 2 active proxy pools for rotation.`
: isRotation
? rotateStrategy === "round-robin"
? `Rotating through all ${proxyPools.length} active pools in order. State is in-memory (resets on restart).`
: `Picking a random pool from ${proxyPools.length} active pools each request.`
: `Uses the selected pool above. Set to Round-robin or Random to rotate across all active pools.`}
</p>
</div>
</Card>
);
}
NoAuthProxyCard.propTypes = {
providerId: PropTypes.string.isRequired,
};
|