import React, { useEffect, useState } from 'react'; import { formatRelative, formatBytes } from '../utils/format.js'; import { flagFor } from '../utils/country.js'; import { api } from '../api.js'; function StatusHero({ state, status }) { const text = { running: { title: '🟢 VPN-шлюз работает', kind: 'success' }, applying: { title: '🟠 Применяем изменения…', kind: 'warning' }, error: { title: '🔴 Ошибка', kind: 'danger' }, stopped: { title: '⚫ Шлюз остановлен', kind: 'neutral' }, no_config: { title: '⚪ Шлюз не настроен', kind: 'neutral' }, }[status]; const userInfo = state?.userInfo; const traffic = userInfo ? `${formatBytes((userInfo.upload || 0) + (userInfo.download || 0))} / ${userInfo.total ? formatBytes(userInfo.total) : 'без лимита'}` : 'нет данных'; return (

{text.title}

{state?.appliedAt ? `Последнее применение: ${formatRelative(state.appliedAt)}` : 'Конфиг ещё не применялся'}
{state?.singboxRunning ? 'sing-box online' : 'sing-box offline'}
Активный сервер
{state?.selectedTag ? ( <> {flagFor({ tag: state.selectedTag })} {state.selectedTag} ) : Не выбран}
Трафик
{traffic}
Правил маршрутизации
{(state?.customRules || []).filter(r => r.enabled).length} активных
); } function QuickActions({ state, busy, onRestart, onStop, onShowConfig, onNav, onBypassToggle }) { return (

Быстрые действия

); } function RecentEvents({ onNav }) { const [entries, setEntries] = useState([]); useEffect(() => { let cancelled = false; fetch('/api/logs') .then((r) => r.json()) .then((data) => { if (cancelled) return; const list = (data.logs || []).slice(-15).reverse(); setEntries(list); }) .catch(() => {}); return () => { cancelled = true; }; }, []); return (

Последние события

{entries.length === 0 ? ( Пока ничего нет. ) : (
{entries.slice(0, 8).map((e, i) => { const dot = e.level === 'error' ? 'danger' : e.level === 'warning' ? 'warning' : 'success'; const time = new Date(e.ts).toLocaleTimeString('ru-RU', { hour12: false }); return (
{time} {e.line}
); })}
)}
); } function RoutingSummary({ state, onNav, onFlushDirectCache }) { const rules = state?.customRules || []; const enabled = rules.filter((r) => r.enabled).length; const cacheCount = state?.directBypassCount || 0; const cacheAvailable = state?.directBypassAvailable && state?.directBypassEnabled; const transparentDefault = state?.devicesConfig?.defaultTransparentMode || 'direct'; const proxyDefault = state?.devicesConfig?.proxyDefaultMode || 'vpn'; return (

Маршрутизация

Private IP→ direct
{state?.routingRuDirect && (
RU (geoip/geosite)→ direct
)}
Global custom правил{enabled} из {rules.length}
Transparent fallback→ {transparentDefault}
Proxy fallback→ {proxyDefault}
{cacheAvailable && (
Direct bypass cache {cacheCount} IP
)}
); } export function OverviewPage({ state, status, busy, onRestart, onStop, onShowConfig, onNav, onBypassToggle, onFlushDirectCache }) { return (
{state?.bypassMode && (
⚠ Режим обхода правил активен — весь трафик идёт напрямую, VPN-правила не применяются.
)}
); }