From 0387491ada174e9731798ee355f13528a99a60c3 Mon Sep 17 00:00:00 2001 From: shats Date: Thu, 12 Feb 2026 23:09:57 +0700 Subject: [PATCH] feat(MikrotikTools): add traceroute tab state management for improved user navigation between results and analysis --- frontend/src/MikrotikTools.jsx | 328 +++++++++++++++++---------------- 1 file changed, 171 insertions(+), 157 deletions(-) diff --git a/frontend/src/MikrotikTools.jsx b/frontend/src/MikrotikTools.jsx index 39bf290..ef356e4 100644 --- a/frontend/src/MikrotikTools.jsx +++ b/frontend/src/MikrotikTools.jsx @@ -36,6 +36,7 @@ function MikrotikTools() { const [running, setRunning] = useState(false); const [hops, setHops] = useState([]); const [analysis, setAnalysis] = useState([]); + const [tracerouteTab, setTracerouteTab] = useState('table'); // 'table' | 'analysis' const [pingResult, setPingResult] = useState(null); const [useDns, setUseDns] = useState(true); @@ -508,144 +509,6 @@ function MikrotikTools() {
- {mode === 'traceroute' && ( -
-
-

Результаты трассировки

-
-
- {hops.length === 0 ? ( -
- Результаты трассировки появятся здесь после запуска проверки. -
- ) : ( -
- - - - - - - - - - - - - {hops.map((h) => { - const ip = h.ip || ''; - const dns = h.dns || ''; - - let mainLabel; - if (useDns) { - if (dns && ip) mainLabel = `${dns} (${ip})`; - else mainLabel = dns || ip || h.host || '—'; - } else { - mainLabel = ip || dns || h.host || '—'; - } - - const avg = typeof h.avgMs === 'number' ? h.avgMs : null; - const loss = typeof h.loss === 'number' ? h.loss : null; - const statusRaw = (h.status || '').toString(); - const statusLower = statusRaw.toLowerCase(); - const hasTimeout = - statusLower.includes('timeout') || - statusLower.includes('no reply') || - statusLower.includes('unreachable'); - - let severity = 'ok'; - if (hasTimeout || (loss != null && loss >= 50)) { - severity = 'danger'; - } else if ((loss != null && loss > 0) || (avg != null && avg > 200)) { - severity = 'warn'; - } - - const rowClass = - severity === 'danger' - ? 'table-danger' - : severity === 'warn' - ? 'table-warning' - : ''; - - return ( - - - - - - - - - ); - })} - -
ХопАдрес / узелСреднее, мсМин / Макс, мсПотериСтатус
- - {h.hop} - - -
- - {mainLabel} - - {ip && dns && ( - - IP: {ip} - - )} -
-
- {avg != null ? ( - 200 - ? 'text-danger fw-semibold' - : avg > 120 - ? 'text-warning fw-semibold' - : '' - } - > - {avg.toFixed(1)} - - ) : ( - '—' - )} - - {h.bestMs != null ? h.bestMs.toFixed(1) : '—'} /{' '} - {h.worstMs != null ? h.worstMs.toFixed(1) : '—'} - - {loss != null ? ( - = 50 - ? 'badge bg-red-lt text-red' - : loss > 0 - ? 'badge bg-yellow-lt text-yellow' - : 'badge bg-green-lt text-green' - } - > - {loss}% - - ) : ( - '—' - )} - - {severity === 'danger' && ( - Проблемный хоп - )} - {severity === 'warn' && ( - Возможная проблема - )} - - {statusRaw || (loss != null || avg != null ? 'OK' : '—')} - -
-
- )} -
-
- )} - {mode === 'ping' && (
@@ -767,26 +630,177 @@ function MikrotikTools() { {mode === 'traceroute' && (
-

- - Расшифровка трассировки -

-
-
- {analysis.length === 0 ? ( -
- После выполнения трассировки здесь появится человекочитаемое описание пути и возможных проблем. -
- ) : ( -
    - {analysis.map((line, idx) => ( -
  • - {line} -
  • - ))} -
- )} +
    +
  • + +
  • +
  • + +
  • +
+ {tracerouteTab === 'table' ? ( +
+ {hops.length === 0 ? ( +
+ Результаты трассировки появятся здесь после запуска проверки. +
+ ) : ( +
+ + + + + + + + + + + + + {hops.map((h) => { + const ip = h.ip || ''; + const dns = h.dns || ''; + + let mainLabel; + if (useDns) { + if (dns && ip) mainLabel = `${dns} (${ip})`; + else mainLabel = dns || ip || h.host || '—'; + } else { + mainLabel = ip || dns || h.host || '—'; + } + + const avg = typeof h.avgMs === 'number' ? h.avgMs : null; + const loss = typeof h.loss === 'number' ? h.loss : null; + const statusRaw = (h.status || '').toString(); + const statusLower = statusRaw.toLowerCase(); + const hasTimeout = + statusLower.includes('timeout') || + statusLower.includes('no reply') || + statusLower.includes('unreachable'); + + let severity = 'ok'; + if (hasTimeout || (loss != null && loss >= 50)) { + severity = 'danger'; + } else if ((loss != null && loss > 0) || (avg != null && avg > 200)) { + severity = 'warn'; + } + + const rowClass = + severity === 'danger' + ? 'table-danger' + : severity === 'warn' + ? 'table-warning' + : ''; + + return ( + + + + + + + + + ); + })} + +
ХопАдрес / узелСреднее, мсМин / Макс, мсПотериСтатус
+ + {h.hop} + + +
+ + {mainLabel} + + {ip && dns && ( + + IP: {ip} + + )} +
+
+ {avg != null ? ( + 200 + ? 'text-danger fw-semibold' + : avg > 120 + ? 'text-warning fw-semibold' + : '' + } + > + {avg.toFixed(1)} + + ) : ( + '—' + )} + + {h.bestMs != null ? h.bestMs.toFixed(1) : '—'} /{' '} + {h.worstMs != null ? h.worstMs.toFixed(1) : '—'} + + {loss != null ? ( + = 50 + ? 'badge bg-red-lt text-red' + : loss > 0 + ? 'badge bg-yellow-lt text-yellow' + : 'badge bg-green-lt text-green' + } + > + {loss}% + + ) : ( + '—' + )} + + {severity === 'danger' && ( + Проблемный хоп + )} + {severity === 'warn' && ( + Возможная проблема + )} + + {statusRaw || (loss != null || avg != null ? 'OK' : '—')} + +
+
+ )} +
+ ) : ( +
+ {analysis.length === 0 ? ( +
+ После выполнения трассировки здесь появится человекочитаемое описание пути и возможных проблем. +
+ ) : ( +
    + {analysis.map((line, idx) => ( +
  • + {line} +
  • + ))} +
+ )} +
+ )}
)}