Unify Harbor error handling across server and client
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 15s
Build and Deploy Gateway / deploy (push) Successful in 12s

This commit is contained in:
2026-07-11 20:38:41 +03:00
parent 457dd912d1
commit 9da4fef1f0
16 changed files with 493 additions and 100 deletions

View File

@@ -1,7 +1,7 @@
import React, { useEffect, useReducer, useRef, useState } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
import { api } from './api.js';
import { api, HarborApiError } from './api.js';
import { ClientOverviewPage } from './components/ClientOverviewPage.jsx';
import { BootStatePage, StaleBanner } from './components/SyncStatus.jsx';
import {
@@ -18,7 +18,7 @@ function App() {
);
const [subscriptionUrl, setSubscriptionUrl] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [error, setError] = useState(null);
const pollGeneration = useRef(0);
function setPendingTag(serverId) {
@@ -60,14 +60,21 @@ function App() {
: '/harbor-connect.svg?v=2';
}, [state?.mode]);
async function run(action) {
async function run(action, context) {
setBusy(true);
setError('');
setError(null);
try {
return await applyMutation(action);
} catch (err) {
setError(err.message);
throw err;
const safeError = err instanceof HarborApiError ? err : new HarborApiError();
setError({
context,
message: safeError.message,
code: safeError.code,
correlationId: safeError.correlationId,
retry: safeError.retryable ? () => run(action, context) : null,
});
return false;
} finally {
setBusy(false);
}
@@ -91,11 +98,11 @@ function App() {
const data = await api.subscription.fetch(subscriptionUrl);
dispatch({ type: 'clear-pending-server' });
return data;
});
}, 'subscription');
}
async function refreshSubscription() {
return applyMutation(api.subscription.refresh);
return run(api.subscription.refresh, 'subscription');
}
async function forgetSubscription() {
@@ -104,7 +111,7 @@ function App() {
setSubscriptionUrl('');
dispatch({ type: 'clear-pending-server' });
return data;
});
}, 'subscription');
}
if (!state) return <BootStatePage transport={transport} onRetry={() => loadState({ retry: true })} />;
@@ -126,10 +133,10 @@ function App() {
onFetchSubscription={fetchSubscription}
onRefreshSubscription={refreshSubscription}
onForgetSubscription={forgetSubscription}
onApply={(tag) => run(() => api.apply(tag))}
onRestart={() => run(api.singbox.restart)}
onStop={() => run(api.singbox.stop)}
onSetGatewayAuto={(enabled) => run(() => api.gatewayAuto.setEnabled(enabled))}
onApply={(tag) => run(() => api.apply(tag), 'connection')}
onRestart={() => run(api.singbox.restart, 'connection')}
onStop={() => run(api.singbox.stop, 'connection')}
onSetGatewayAuto={(enabled) => run(() => api.gatewayAuto.setEnabled(enabled), 'connection')}
/>
</main>
</div>