feat(MikrotikTools): add popular targets for ping/traceroute with icons for enhanced user selection
Publish Fast Tabler Docker image / build-and-push-fast (push) Failing after 3m17s

This commit is contained in:
2026-02-12 23:52:42 +07:00
parent caa566aa8f
commit c7f55dfee9
+59 -30
View File
@@ -12,8 +12,18 @@ import {
IconAlertTriangle, IconAlertTriangle,
IconRefresh, IconRefresh,
IconSearch, IconSearch,
IconBrandGoogle,
IconBrandYandex,
IconBrandCloudflare,
} from '@tabler/icons-react'; } from '@tabler/icons-react';
/** Популярные цели для ping/traceroute — карточки с логотипами */
const POPULAR_TARGETS = [
{ id: 'yandex', name: 'Yandex', target: 'yandex.ru', Icon: IconBrandYandex, color: 'red' },
{ id: 'google', name: 'Google', target: '8.8.8.8', Icon: IconBrandGoogle, color: 'blue' },
{ id: 'cloudflare', name: 'Cloudflare DNS', target: '1.1.1.1', Icon: IconBrandCloudflare, color: 'orange' },
];
/** /**
* Раздел "Инструменты → MikroTik": проверка маршрута (traceroute) * Раздел "Инструменты → MikroTik": проверка маршрута (traceroute)
* с выбором сервера, gateway и домена/IP. * с выбором сервера, gateway и домена/IP.
@@ -222,22 +232,20 @@ function MikrotikTools() {
meta={`Текущий сервер: ${currentServerLabel}`} meta={`Текущий сервер: ${currentServerLabel}`}
/> />
{/* Выбор сервера для трассировки (в стиле MikrotikBackups / filters) */} {/* Объединённый блок: сервер + шлюз + популярные цели */}
<div className="card mb-3"> <div className="card mb-3">
<div className="card-header"> <div className="card-header">
<div className="row align-items-center g-2"> <div className="row align-items-center g-2">
<div className="col"> <div className="col">
<h3 className="card-title mb-1">Сервер для проверки</h3> <h3 className="card-title mb-1">Сервер и шлюз для проверки</h3>
<div className="text-muted small"> <div className="text-muted small">
Доступно Jumphost-серверов: {jumphostServers.length}.{' '} Доступно Jumphost-серверов: {jumphostServers.length}.{' '}
{serverId {serverId
? 'Кликните по другой карточке, чтобы сменить сервер.' ? 'Кликните по другой карточке, чтобы сменить сервер. Ниже — выбор шлюза и быстрые цели.'
: 'Выберите сервер, чтобы выполнять проверки через него.'} : 'Выберите сервер, чтобы выполнять проверки через него.'}
</div> </div>
</div> </div>
<div className="col-auto ms-auto d-print-none" style={{ minWidth: 260 }}> <div className="col-auto ms-auto d-print-none" style={{ minWidth: 260 }}>
<div className="d-flex">
<div className="me-0 flex-grow-1">
<div className="input-icon"> <div className="input-icon">
<input <input
type="text" type="text"
@@ -253,8 +261,6 @@ function MikrotikTools() {
</div> </div>
</div> </div>
</div> </div>
</div>
</div>
<div className="card-body"> <div className="card-body">
{isInitialLoading ? ( {isInitialLoading ? (
<TableSkeleton rows={2} columns={2} /> <TableSkeleton rows={2} columns={2} />
@@ -286,11 +292,9 @@ function MikrotikTools() {
{flag || s.ip?.slice(0, 2) || '?'} {flag || s.ip?.slice(0, 2) || '?'}
</span> </span>
<div className="flex-grow-1 min-w-0"> <div className="flex-grow-1 min-w-0">
<div className="d-flex align-items-center gap-2">
<div className="fw-bold text-truncate" title={s.dns || s.ip || id}> <div className="fw-bold text-truncate" title={s.dns || s.ip || id}>
{s.dns || s.ip || id} {s.dns || s.ip || id}
</div> </div>
</div>
{line && ( {line && (
<div className="text-muted small text-truncate" title={line}> <div className="text-muted small text-truncate" title={line}>
{line} {line}
@@ -311,30 +315,24 @@ function MikrotikTools() {
Текущий сервер:&nbsp; Текущий сервер:&nbsp;
<span className="fw-semibold">{currentServerLabel}</span> <span className="fw-semibold">{currentServerLabel}</span>
</div> </div>
</div>
</div>
{/* Шлюз и параметры проверки — только когда выбран сервер, в том же стиле что и блок серверов */} {/* Шлюз (gateway) — только при выбранном сервере */}
{serverId && ( {serverId && (
<> <>
{/* Выбор gateway в стиле карточек серверов */} <hr className="my-3" />
<div className="card mb-3"> <h5 className="mb-2">Шлюз (gateway)</h5>
<div className="card-header"> <div className="text-muted small mb-2">
<h3 className="card-title mb-1">Шлюз (gateway)</h3>
<div className="text-muted small">
{gatewaysForServer.length > 0 {gatewaysForServer.length > 0
? 'Выберите шлюз для привязки проверки (опционально). Можно искать по IP или интерфейсу ниже.' ? 'Выберите шлюз для привязки проверки (опционально). Можно искать по IP или интерфейсу ниже.'
: 'Нет настроенных шлюзов для этого сервера. Проверка пойдёт без привязки к gateway.'} : 'Нет настроенных шлюзов для этого сервера. Проверка пойдёт без привязки к gateway.'}
</div> </div>
</div>
<div className="card-body">
{loading && !networkConfig ? ( {loading && !networkConfig ? (
<div className="text-muted small">Загрузка сетевых настроек…</div> <div className="text-muted small">Загрузка сетевых настроек…</div>
) : ( ) : (
<> <>
{gatewaysForServer.length > 0 && ( {gatewaysForServer.length > 0 && (
<div className="row row-cards g-2 mb-3"> <div className="row row-cards g-2 mb-2">
{gatewaysForServer .map((g) => { {gatewaysForServer.map((g) => {
const isSelected = gatewayRef === g.ip || gatewayRef === g.id; const isSelected = gatewayRef === g.ip || gatewayRef === g.id;
const flag = g.country ? countryToFlag(g.country) : ''; const flag = g.country ? countryToFlag(g.country) : '';
const subtitle = [g.country, g.provider].filter(Boolean).join(' · '); const subtitle = [g.country, g.provider].filter(Boolean).join(' · ');
@@ -347,9 +345,8 @@ function MikrotikTools() {
onClick={() => { onClick={() => {
const next = isSelected ? '' : (g.ip || g.id); const next = isSelected ? '' : (g.ip || g.id);
setGatewayRef(next); setGatewayRef(next);
if (!next) { if (!next) setGatewayMeta(null);
setGatewayMeta(null); else {
} else {
setGatewayMeta({ ip: g.ip, gateway: g }); setGatewayMeta({ ip: g.ip, gateway: g });
if (!target && g.ip) setTarget(g.ip); if (!target && g.ip) setTarget(g.ip);
} }
@@ -368,9 +365,7 @@ function MikrotikTools() {
{g.description || g.ip} {g.description || g.ip}
</div> </div>
)} )}
<div className="text-muted small mt-1"> <div className="text-muted small mt-1">{subtitle || '—'}</div>
{subtitle || '—'}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -388,16 +383,51 @@ function MikrotikTools() {
placeholder="IP gateway или remote IP туннеля…" placeholder="IP gateway или remote IP туннеля…"
onSelectMeta={handleSelectGatewayMeta} onSelectMeta={handleSelectGatewayMeta}
/> />
<div className="text-muted small mt-2"> <div className="text-muted small mt-2 mb-0">
Текущий gateway:&nbsp; Текущий gateway:&nbsp;
<span className="fw-semibold">{gatewayRef || gatewayMeta?.ip || 'не выбран'}</span> <span className="fw-semibold">{gatewayRef || gatewayMeta?.ip || 'не выбран'}</span>
</div> </div>
</> </>
)} )}
{/* Популярные цели — быстрый выбор таргета по логотипу */}
<hr className="my-3" />
<h5 className="mb-2">Популярные цели</h5>
<div className="text-muted small mb-2">
Клик по карточке подставит цель в поле «Домен или IP назначения».
</div>
<div className="row row-cards g-2">
{POPULAR_TARGETS.map(({ id: tid, name, target: tgt, Icon, color }) => {
const isSelected = target === tgt;
const bgClass = color === 'red' ? 'bg-red-lt' : color === 'blue' ? 'bg-blue-lt' : 'bg-orange-lt';
const textClass = color === 'red' ? 'text-red' : color === 'blue' ? 'text-blue' : 'text-orange';
return (
<div className="col-12 col-sm-6 col-md-4" key={tid}>
<div
className={`card card-sm h-100 cursor-pointer ${isSelected ? 'border-primary card-hover' : 'card-hover'}`}
onClick={() => setTarget(tgt)}
>
<div className="card-body d-flex align-items-center gap-2">
<span className={`avatar ${bgClass} ${textClass}`}>
<Icon size={24} stroke={1.5} />
</span>
<div className="flex-grow-1 min-w-0">
<div className="fw-bold text-truncate">{name}</div>
<div className="text-muted small text-truncate">{tgt}</div>
</div>
</div>
</div>
</div>
);
})}
</div>
</>
)}
</div> </div>
</div> </div>
{/* Режим и кнопки запуска */} {/* Режим и кнопки запуска — только при выбранном сервере */}
{serverId && (
<div className="card mb-3"> <div className="card mb-3">
<div className="card-body py-2"> <div className="card-body py-2">
<div className="row align-items-center g-2"> <div className="row align-items-center g-2">
@@ -449,7 +479,6 @@ function MikrotikTools() {
</div> </div>
</div> </div>
</div> </div>
</>
)} )}
{/* Параметры проверки + результаты */} {/* Параметры проверки + результаты */}