All checks were successful
Build and Deploy Gateway / build-and-deploy (push) Successful in 25s
- Создан компонент RuleEditorDrawer для редактирования правил с поддержкой JSON. - Добавлен компонент ServersPage для отображения и управления серверами. - Реализован компонент SettingsPage для управления подписками и конфигурациями. - Создан компонент Sidebar для навигации по приложению. - Добавлен компонент StatusPane для отображения статуса сервера. - Реализован компонент Toasts для отображения уведомлений. - Создан компонент Topbar для отображения информации о текущем состоянии. - Добавлен модуль country.js для определения страны по тегу сервера. Refs: None
75 lines
2.6 KiB
JavaScript
75 lines
2.6 KiB
JavaScript
import React, { useState } from 'react';
|
|
import { api } from '../api.js';
|
|
|
|
export function RouteChecker() {
|
|
const [host, setHost] = useState('');
|
|
const [port, setPort] = useState('443');
|
|
const [network, setNetwork] = useState('tcp');
|
|
const [busy, setBusy] = useState(false);
|
|
const [result, setResult] = useState(null);
|
|
const [error, setError] = useState('');
|
|
|
|
async function check() {
|
|
setBusy(true);
|
|
setError('');
|
|
setResult(null);
|
|
try {
|
|
const data = await api.route.check({ host, port: port || undefined, network });
|
|
setResult(data);
|
|
} catch (err) {
|
|
setError(err.message);
|
|
} finally {
|
|
setBusy(false);
|
|
}
|
|
}
|
|
|
|
const r = result?.result;
|
|
const kind = r?.outbound?.startsWith('direct') ? 'success'
|
|
: r?.outbound === 'block' ? 'danger'
|
|
: r?.outbound?.includes('VPN') || r?.outbound?.includes('vpn') ? 'info'
|
|
: 'warning';
|
|
|
|
return (
|
|
<div className="card flat compact">
|
|
<div className="card-header no-margin"><h3>Проверить маршрут</h3></div>
|
|
<div className="filter-bar" style={{ marginTop: 12 }}>
|
|
<input
|
|
className="input"
|
|
placeholder="домен или IP (riotgames.com)"
|
|
value={host}
|
|
onChange={(e) => setHost(e.target.value)}
|
|
onKeyDown={(e) => e.key === 'Enter' && check()}
|
|
style={{ minWidth: 220, flex: 1 }}
|
|
/>
|
|
<input
|
|
className="input"
|
|
placeholder="port"
|
|
value={port}
|
|
onChange={(e) => setPort(e.target.value)}
|
|
style={{ width: 90 }}
|
|
/>
|
|
<select className="select" value={network} onChange={(e) => setNetwork(e.target.value)} style={{ width: 90 }}>
|
|
<option value="tcp">tcp</option>
|
|
<option value="udp">udp</option>
|
|
</select>
|
|
<button className="btn btn-primary" onClick={check} disabled={busy || !host}>Проверить</button>
|
|
</div>
|
|
|
|
{error && <div className="field-error" style={{ marginTop: 10 }}>{error}</div>}
|
|
|
|
{r && (
|
|
<div className="route-result" style={{ marginTop: 12 }}>
|
|
<div className="flex-between">
|
|
<strong>{r.ruleIndex >= 0 ? `Правило #${r.ruleIndex + 1}: ${r.ruleName}` : r.ruleName}</strong>
|
|
<span className={`badge ${kind}`}>→ {r.outbound}</span>
|
|
</div>
|
|
{result.resolvedIp && result.resolvedFrom && (
|
|
<small className="muted text-mono">DNS: {result.resolvedFrom} → {result.resolvedIp}</small>
|
|
)}
|
|
<small className="muted">{r.reason}</small>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|