Introduce stable server IDs for subscription state
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 7s

This commit is contained in:
2026-07-12 11:59:22 +03:00
parent 005c7a101b
commit 267afc5c7e
16 changed files with 323 additions and 145 deletions

View File

@@ -566,8 +566,8 @@ export function ClientOverviewPage({
subscriptionUrl,
setSubscriptionUrl,
servers,
pendingTag,
setPendingTag,
pendingServerId,
setPendingServerId,
onFetchSubscription,
onRefreshSubscription,
onForgetSubscription,
@@ -583,9 +583,9 @@ export function ClientOverviewPage({
const gatewayAvailable = !isGateway && Boolean(state?.gatewayAuto?.available);
const connected = Boolean(state?.singboxRunning);
const hasSubscription = Boolean(state?.hasSubscription);
const selectedTag = pendingTag || state?.selectedTag || '';
const showPower = hasSubscription && Boolean(selectedTag);
const canStart = Boolean(selectedTag || state?.configExists);
const selectedServerId = pendingServerId || state?.selection?.desiredServerId || '';
const showPower = hasSubscription && Boolean(selectedServerId);
const canStart = Boolean(selectedServerId || state?.configExists);
const [now, setNow] = useState(Date.now());
const [durationMode, setDurationMode] = useState(() => {
try {
@@ -620,7 +620,7 @@ export function ClientOverviewPage({
const localRulesToggleRef = useRef(null);
const localRulesBaselineRef = useRef('[]');
const previousHasSubscriptionRef = useRef(hasSubscription);
const serverKey = servers.map((server) => `${server.tag}:${server.server}:${server.server_port}`).join('|');
const serverKey = servers.map((server) => server.id).join('|');
const gatewayAddress = isGateway ? window.location.hostname : '127.0.0.1';
const proxyUrls = localProxyUrls(state?.proxyPort, gatewayAddress);
const usage = subscriptionUsage(state?.userInfo);
@@ -674,18 +674,18 @@ export function ClientOverviewPage({
}
let cancelled = false;
setPings(Object.fromEntries(servers.map((server) => [server.tag, { checking: true }])));
setPings(Object.fromEntries(servers.map((server) => [server.id, { checking: true }])));
api.servers.pingAll()
.then((data) => {
if (cancelled) return;
setPings(Object.fromEntries((data.results || []).map((ping) => [
String(ping.tag || '').trim(),
ping.id || ping.tag,
ping,
])));
})
.catch(() => {
if (!cancelled) {
setPings(Object.fromEntries(servers.map((server) => [server.tag, { ok: false }])));
setPings(Object.fromEntries(servers.map((server) => [server.id, { ok: false }])));
}
});
@@ -822,15 +822,15 @@ export function ClientOverviewPage({
}, [instructionsOpen]);
async function toggleConnection() {
const action = connectionAction({ connected, selectedTag, configExists: state?.configExists });
const action = connectionAction({ connected, selectedServerId, configExists: state?.configExists });
if (action?.type === 'stop') return onStop();
if (action?.type === 'apply') return onApply(action.selectedTag);
if (action?.type === 'apply') return onApply(action.serverId);
if (action?.type === 'restart') return onRestart();
}
function selectServer(tag) {
setPendingTag(tag);
if (connected && tag) onApply(tag);
function selectServer(serverId) {
setPendingServerId(serverId);
if (connected && serverId) onApply(serverId);
}
async function submitSubscription(event) {
@@ -1314,8 +1314,8 @@ export function ClientOverviewPage({
key={`${serverKey}:${serverRevealVersion}`}
>
{servers.map((server, index) => {
const ping = pings[server.tag];
const selected = server.tag === selectedTag;
const ping = pings[server.id];
const selected = server.id === selectedServerId;
const pingText = ping?.checking
? 'Проверка…'
: ping?.ok ? `${ping.latency} ms` : 'Недоступен';
@@ -1327,13 +1327,14 @@ export function ClientOverviewPage({
<button
className={`client-server ${selected ? 'is-selected' : ''}`}
type="button"
key={server.tag}
key={server.id}
disabled={serverApplyBlocked}
aria-pressed={selected}
aria-label={`${server.label}, ${server.host}:${server.port}, ${pingText}`}
style={{ '--server-index': index }}
onClick={() => selectServer(server.tag)}
onClick={() => selectServer(server.id)}
>
<strong>{server.tag}</strong>
<strong>{server.label}</strong>
<small className={pingClass}>{pingText}</small>
</button>
);