Files
harbor-net/src/web/App.jsx
Dmitriy Petrov 322f5a125b
All checks were successful
Build and Deploy Gateway / build-and-push (push) Successful in 13s
Build and Deploy Gateway / deploy (push) Successful in 1s
Polish client duration and tooltip interactions
2026-07-11 16:12:09 +03:00

117 lines
3.7 KiB
JavaScript

import React, { useEffect, useState } from 'react';
import { createRoot } from 'react-dom/client';
import './styles.css';
import { api } from './api.js';
import { ClientOverviewPage } from './components/ClientOverviewPage.jsx';
function App() {
const previewReady = new URLSearchParams(window.location.search).has('preview-ready');
const [state, setState] = useState(null);
const [subscriptionUrl, setSubscriptionUrl] = useState('');
const [servers, setServers] = useState([]);
const [pendingTag, setPendingTag] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
async function loadState() {
const data = await api.state();
setState(data);
setServers(data.servers || []);
setPendingTag((current) => (
(data.servers || []).some((server) => server.tag === current)
? current
: data.selectedTag || ''
));
}
useEffect(() => {
loadState().catch((err) => setError(err.message));
const timer = setInterval(() => loadState().catch(() => {}), 5000);
return () => clearInterval(timer);
}, []);
useEffect(() => {
if (!state?.mode) return;
const isGateway = state.mode === 'gateway';
document.title = isGateway ? 'Harbor Gateway' : 'Harbor Connect';
document.getElementById('harbor-favicon').href = isGateway
? '/harbor-gateway.svg'
: '/harbor-connect.svg';
}, [state?.mode]);
async function run(action) {
setBusy(true);
setError('');
try {
await action();
await loadState();
} catch (err) {
setError(err.message);
throw err;
} finally {
setBusy(false);
}
}
async function fetchSubscription() {
return run(async () => {
const data = await api.subscription.fetch(subscriptionUrl);
setServers(data.servers || []);
setPendingTag('');
});
}
async function refreshSubscription() {
const data = await api.subscription.refresh();
setState((current) => current ? {
...current,
userInfo: data.userInfo,
fetchedAt: data.fetchedAt,
servers: data.servers,
selectedTag: data.selectedTag,
} : current);
setServers(data.servers || []);
setPendingTag(data.selectedTag || '');
return data;
}
async function forgetSubscription() {
return run(async () => {
await api.subscription.forget();
setSubscriptionUrl('');
setServers([]);
setPendingTag('');
});
}
if (!state) return <div className="app-loading">Harbor</div>;
return (
<div className="app client-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}
busy={busy}
error={error}
subscriptionUrl={subscriptionUrl}
setSubscriptionUrl={setSubscriptionUrl}
servers={previewReady ? [{ tag: 'Amsterdam', server: '127.0.0.1', server_port: 443 }] : servers}
pendingTag={previewReady ? 'Amsterdam' : pendingTag}
setPendingTag={setPendingTag}
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))}
/>
</main>
</div>
</div>
);
}
createRoot(document.getElementById('root')).render(<App />);