Add Gateway connectivity diagnostics and traffic chart improvements
Build and Deploy Gateway / build-and-push (push) Successful in 14s
Build and Deploy Gateway / deploy (push) Successful in 13s

This commit is contained in:
2026-08-07 20:48:43 +03:00
parent e6666558b7
commit 12375006d3
18 changed files with 1133 additions and 206 deletions
+58
View File
@@ -16,6 +16,7 @@ import { instructionBlocks } from '../instructions.js';
import { operationBlocked } from '../state/operations.js';
import { ConfirmationPopup } from './ConfirmationPopup.jsx';
import { DevicesPanel } from './DevicesPanel.jsx';
import { ConnectivityDiagnosticsPanel } from './ConnectivityDiagnosticsPanel.jsx';
import { ServerPicker } from './ServerPicker.jsx';
import { ERROR_DEFINITIONS } from '../../shared/errors.js';
import { canAppendRouteRule } from '../../shared/routingRules.js';
@@ -642,6 +643,7 @@ export function ClientOverviewPage({
const [instructionsOpen, setInstructionsOpen] = useState(false);
const [localRulesOpen, setLocalRulesOpen] = useState(false);
const [devicesOpen, setDevicesOpen] = useState(false);
const [diagnosticsOpen, setDiagnosticsOpen] = useState(false);
const [localRulesDraft, setLocalRulesDraft] = useState([]);
const [localRulesRevision, setLocalRulesRevision] = useState(state?.route?.localRulesRevision || 0);
const [confirmingLocalRulesClose, setConfirmingLocalRulesClose] = useState(false);
@@ -660,6 +662,9 @@ export function ClientOverviewPage({
const devicesPanelRef = useRef(null);
const devicesToggleRef = useRef(null);
const devicesCloseRef = useRef(null);
const diagnosticsPanelRef = useRef(null);
const diagnosticsToggleRef = useRef(null);
const diagnosticsCloseRef = useRef(null);
const localRulesBaselineRef = useRef('[]');
const previousHasSubscriptionRef = useRef(hasSubscription);
const gatewayAddress = isGateway ? window.location.hostname : '127.0.0.1';
@@ -794,6 +799,8 @@ export function ClientOverviewPage({
setEditingSubscription(true);
setInstructionsOpen(false);
setLocalRulesOpen(false);
setDevicesOpen(false);
setDiagnosticsOpen(false);
}
}, [hasSubscription]);
@@ -876,6 +883,28 @@ export function ClientOverviewPage({
};
}, [devicesOpen]);
useEffect(() => {
if (!diagnosticsOpen) return undefined;
const frame = requestAnimationFrame(() => diagnosticsCloseRef.current?.focus());
const closeDiagnostics = (event) => {
if (event.type === 'keydown' && event.key !== 'Escape') return;
if (event.type !== 'keydown' && (
diagnosticsPanelRef.current?.contains(event.target) || diagnosticsToggleRef.current?.contains(event.target)
)) return;
setDiagnosticsOpen(false);
};
document.addEventListener('pointerdown', closeDiagnostics);
document.addEventListener('keydown', closeDiagnostics);
return () => {
cancelAnimationFrame(frame);
document.removeEventListener('pointerdown', closeDiagnostics);
document.removeEventListener('keydown', closeDiagnostics);
requestAnimationFrame(() => {
if (diagnosticsPanelRef.current?.contains(document.activeElement)) diagnosticsToggleRef.current?.focus();
});
};
}, [diagnosticsOpen]);
useEffect(() => {
if (!localRulesOpen) return undefined;
const frame = requestAnimationFrame(() => localRulesCloseRef.current?.focus());
@@ -1021,6 +1050,7 @@ export function ClientOverviewPage({
const rules = state?.route?.localRules || [];
setInstructionsOpen(false);
setDevicesOpen(false);
setDiagnosticsOpen(false);
localRulesBaselineRef.current = localRulesSignature(rules);
setLocalRulesDraft(rules.map(createLocalRuleDraft));
setLocalRulesRevision(state?.route?.localRulesRevision || 0);
@@ -1107,6 +1137,7 @@ export function ClientOverviewPage({
onClick={() => {
if (localRulesOpen && !requestCloseLocalRules()) return;
setDevicesOpen(false);
setDiagnosticsOpen(false);
setInstructionsOpen((open) => !open);
}}
>
@@ -1126,6 +1157,7 @@ export function ClientOverviewPage({
onClick={() => {
if (localRulesOpen && !requestCloseLocalRules()) return;
setInstructionsOpen(false);
setDiagnosticsOpen(false);
setDevicesOpen((open) => !open);
}}
>
@@ -1136,6 +1168,25 @@ export function ClientOverviewPage({
</svg>
<span>Устройства</span>
</button>}
{isGateway && <button
ref={diagnosticsToggleRef}
className={`client-instructions-toggle client-diagnostics-toggle${diagnosticsOpen ? ' is-open' : ''}`}
type="button"
aria-expanded={diagnosticsOpen}
aria-controls="client-diagnostics"
aria-label={diagnosticsOpen ? 'Закрыть диагностику' : 'Проверить маршруты Gateway'}
onClick={() => {
if (localRulesOpen && !requestCloseLocalRules()) return;
setInstructionsOpen(false);
setDevicesOpen(false);
setDiagnosticsOpen((open) => !open);
}}
>
<svg viewBox="0 0 24 24" aria-hidden="true">
<path d="M3 12h4l2.2-5 4.2 10 2.1-5H21" />
</svg>
<span>Диагностика</span>
</button>}
<button
ref={localRulesToggleRef}
className={`client-local-rules-toggle${localRulesOpen ? ' is-open' : ''}${localRulesPendingRestart ? ' has-pending' : ''}`}
@@ -1485,6 +1536,13 @@ export function ClientOverviewPage({
onClose={() => setDevicesOpen(false)}
/>}
{hasSubscription && subscriptionContentReady && isGateway && <ConnectivityDiagnosticsPanel
open={diagnosticsOpen}
panelRef={diagnosticsPanelRef}
closeRef={diagnosticsCloseRef}
onClose={() => setDiagnosticsOpen(false)}
/>}
{hasSubscription && subscriptionContentReady && <LocalRulesPanel
open={localRulesOpen}
rules={localRulesDraft}