Introduce stable server IDs for subscription state
This commit is contained in:
@@ -13,7 +13,7 @@ import { createOperationRegistry } from './state/operations.js';
|
||||
|
||||
function App() {
|
||||
const previewReady = new URLSearchParams(window.location.search).has('preview-ready');
|
||||
const [{ snapshot: state, pendingServerId: pendingTag, transport }, dispatch] = useReducer(
|
||||
const [{ snapshot: state, pendingServerId, transport }, dispatch] = useReducer(
|
||||
harborReducer,
|
||||
initialHarborState,
|
||||
);
|
||||
@@ -27,7 +27,7 @@ function App() {
|
||||
operationRegistry.current = createOperationRegistry(setOperations);
|
||||
}
|
||||
|
||||
function setPendingTag(serverId) {
|
||||
function setPendingServerId(serverId) {
|
||||
dispatch({ type: 'select-server', serverId });
|
||||
}
|
||||
|
||||
@@ -144,19 +144,32 @@ function App() {
|
||||
<div className="app-body client-mode">
|
||||
<main className="app-main">
|
||||
<ClientOverviewPage
|
||||
state={previewReady ? { ...state, mode: 'client', hasSubscription: true, subscriptionHost: 'harbor.example', selectedTag: 'Amsterdam', proxyPort: 8082 } : state}
|
||||
state={previewReady ? {
|
||||
...state,
|
||||
mode: 'client',
|
||||
hasSubscription: true,
|
||||
subscriptionHost: 'harbor.example',
|
||||
selection: { desiredServerId: 'preview-amsterdam', appliedServerId: 'preview-amsterdam' },
|
||||
proxyPort: 8082,
|
||||
} : state}
|
||||
versionInfo={versionInfo}
|
||||
operations={operations}
|
||||
error={error}
|
||||
subscriptionUrl={subscriptionUrl}
|
||||
setSubscriptionUrl={setSubscriptionUrl}
|
||||
servers={previewReady ? [{ tag: 'Amsterdam', server: '127.0.0.1', server_port: 443 }] : state.servers || []}
|
||||
pendingTag={previewReady ? 'Amsterdam' : pendingTag}
|
||||
setPendingTag={setPendingTag}
|
||||
servers={previewReady ? [{
|
||||
id: 'preview-amsterdam',
|
||||
label: 'Amsterdam',
|
||||
host: '127.0.0.1',
|
||||
port: 443,
|
||||
protocol: 'vless',
|
||||
}] : state.servers || []}
|
||||
pendingServerId={previewReady ? 'preview-amsterdam' : pendingServerId}
|
||||
setPendingServerId={setPendingServerId}
|
||||
onFetchSubscription={fetchSubscription}
|
||||
onRefreshSubscription={refreshSubscription}
|
||||
onForgetSubscription={forgetSubscription}
|
||||
onApply={(tag) => run('serverApply', () => api.apply(tag), 'connection')}
|
||||
onApply={(serverId) => run('serverApply', () => api.apply(serverId), 'connection')}
|
||||
onRestart={() => run('connection', api.singbox.restart, 'connection')}
|
||||
onStop={() => run('connection', api.singbox.stop, 'connection')}
|
||||
onSetGatewayAuto={(enabled) => run('gatewayAuto', () => api.gatewayAuto.setEnabled(enabled), 'connection')}
|
||||
|
||||
@@ -57,9 +57,10 @@ export const api = {
|
||||
refresh: () => request('/api/subscription/refresh', { method: 'POST' }),
|
||||
forget: () => request('/api/subscription', { method: 'DELETE' }),
|
||||
},
|
||||
apply: (selectedTag) => request('/api/apply', {
|
||||
apply: (serverId) => request('/api/apply', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ selectedTag }),
|
||||
// selectedTag keeps this client compatible with pre-ID Harbor backends.
|
||||
body: JSON.stringify({ serverId, selectedTag: serverId }),
|
||||
}),
|
||||
gatewayAuto: {
|
||||
setEnabled: (enabled) => request('/api/gateway-auto', {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export function connectionAction({ connected, selectedTag, configExists }) {
|
||||
export function connectionAction({ connected, selectedServerId, configExists }) {
|
||||
if (connected) return { type: 'stop' };
|
||||
if (selectedTag) return { type: 'apply', selectedTag };
|
||||
if (selectedServerId) return { type: 'apply', serverId: selectedServerId };
|
||||
if (configExists) return { type: 'restart' };
|
||||
return null;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user