5df38bad2d
Closes TG-4
701 lines
51 KiB
HTML
701 lines
51 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="ru">
|
||
<head>
|
||
<meta charset="UTF-8"/>
|
||
<meta name="viewport" content="width=device-width,initial-scale=1.0,user-scalable=no"/>
|
||
<title>AIS Map v2</title>
|
||
<link rel="icon" href="/svg/icon.svg" type="image/svg+xml"/>
|
||
<link rel="shortcut icon" href="/svg/icon.svg"/>
|
||
<link rel="stylesheet" href="/static/leaflet/leaflet.css"/>
|
||
<link rel="stylesheet" href="/static/css/style.css"/>
|
||
</head>
|
||
<body>
|
||
|
||
<nav id="navbar">
|
||
<div class="nav-brand">AIS Map v2 <span class="nav-version">2026-04-21a</span></div>
|
||
<button class="hamburger" id="hamburger">☰</button>
|
||
<div class="nav-tabs" id="nav-tabs">
|
||
<a class="nav-tab active" data-tab="map">Карта</a>
|
||
<a class="nav-tab nav-tab--targets" data-tab="targets">Цели</a>
|
||
<a class="nav-tab" data-tab="stats">Статистика</a>
|
||
<a class="nav-tab" data-tab="settings">Настройки</a>
|
||
<a class="nav-tab" data-tab="transponder">Транспондер</a>
|
||
<a class="nav-tab" data-tab="logs">Логи</a>
|
||
<a class="nav-tab" data-tab="config">Конфиги</a>
|
||
<a class="nav-tab" data-tab="console">Консоль</a>
|
||
</div>
|
||
<div id="conn-banner" class="conn-banner" style="display:none" role="status" aria-live="polite"></div>
|
||
</nav>
|
||
|
||
<!-- Global banners (visible on all tabs/pages) -->
|
||
<div id="global-banners" class="global-banners" aria-live="polite">
|
||
<div id="danger-banner" class="danger-banner" style="display:none">ВНИМАНИЕ</div>
|
||
</div>
|
||
|
||
<!-- ==================== MAP PAGE ==================== -->
|
||
<div id="page-map" class="tab-page active">
|
||
<div id="status">Ожидание данных AIS...</div>
|
||
<div id="sidebar">
|
||
<h3 class="vessel-sidebar-heading">Цели <span class="vessel-count-paren">(<span id="vessel-count">0</span><span id="vessel-count-suffix"></span>)</span></h3>
|
||
<div id="vessel-list-toolbar" class="vessel-list-toolbar">
|
||
<input type="search" id="vessel-search" class="vessel-search-input" placeholder="Поиск: MMSI, имя, позывной…" autocomplete="off" spellcheck="false"/>
|
||
<div class="vessel-toolbar-row">
|
||
<label class="vessel-toolbar-label">Сортировка
|
||
<select id="vessel-sort" class="vessel-toolbar-select">
|
||
<option value="dist-asc">Расстояние ↑</option>
|
||
<option value="dist-desc">Расстояние ↓</option>
|
||
<option value="time-desc" selected>Обновление (новые)</option>
|
||
<option value="time-asc">Обновление (старые)</option>
|
||
<option value="name-asc">Название А–Я</option>
|
||
<option value="mmsi-asc">MMSI</option>
|
||
<option value="class-asc">Класс A/B</option>
|
||
<option value="speed-desc">Скорость</option>
|
||
</select>
|
||
</label>
|
||
<label class="vessel-toolbar-label">Класс
|
||
<select id="vessel-filter-class" class="vessel-toolbar-select">
|
||
<option value="all" selected>Все</option>
|
||
<option value="A">A</option>
|
||
<option value="B">B</option>
|
||
<option value="BS">База</option>
|
||
<option value="N">Буи</option>
|
||
</select>
|
||
</label>
|
||
<label class="vessel-toolbar-label">Тип
|
||
<select id="vessel-filter-shiptype" class="vessel-toolbar-select">
|
||
<option value="all" selected>Все</option>
|
||
<option value="unknown">Не указан / 0</option>
|
||
<option value="fishing">Рыболов (30)</option>
|
||
<option value="tug">Буксир (31–32)</option>
|
||
<option value="passenger">Пассажир (60–69)</option>
|
||
<option value="cargo">Грузовой (70–79)</option>
|
||
<option value="tanker">Танкер (80–89)</option>
|
||
<option value="other">Прочие</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div id="vessel-list"></div>
|
||
<div id="range-filter">
|
||
<div id="range-label">Радиус: <span id="range-value">∞</span></div>
|
||
<input type="range" id="range-slider" min="0" max="100" value="100"/>
|
||
</div>
|
||
</div>
|
||
<div id="cursor-coords">Координаты: -</div>
|
||
<div id="ownship-panel">
|
||
<h4>Своё судно</h4>
|
||
<div class="row"><span class="label">Источник:</span> <span id="os-source">-</span></div>
|
||
<div class="row"><span class="label">Координаты:</span> <span id="os-coords">-</span></div>
|
||
<div class="row"><span class="label">Курс:</span> <span id="os-course">-</span></div>
|
||
<div class="row"><span class="label">Скорость:</span> <span id="os-speed">-</span></div>
|
||
<div class="row"><span class="label">Спутники:</span> <span id="os-sats">-</span></div>
|
||
<div class="row os-compass-row">
|
||
<span class="label">Компас:</span>
|
||
<span class="os-compass">
|
||
<button class="compass-toggle" id="btn-rotate-map" type="button" title="Вращать карту по компасу">
|
||
<span class="compass-toggle-dot" aria-hidden="true"></span>
|
||
</button>
|
||
<span class="compass-dial" id="os-compass-dial" title="Север">
|
||
<span class="compass-arrow" id="os-compass-arrow" aria-hidden="true">
|
||
<svg viewBox="0 0 64 64" width="28" height="28" focusable="false" aria-hidden="true">
|
||
<path d="M32 4 L42 34 L32 28 L22 34 Z" fill="#ff4d4d"/>
|
||
<path d="M32 60 L42 30 L32 36 L22 30 Z" fill="#c9d1d9" opacity="0.9"/>
|
||
<circle cx="32" cy="32" r="3.2" fill="#0d1117" opacity="0.9"/>
|
||
</svg>
|
||
</span>
|
||
<span class="compass-n">N</span>
|
||
</span>
|
||
</span>
|
||
</div>
|
||
<div class="os-source-hint" id="os-source-hint" style="margin-top:5px;font-size:10px;color:#667">Источник GPS и «Следовать» — в панели управления картой</div>
|
||
</div>
|
||
<div id="map"></div>
|
||
|
||
<!-- Map control panel (centre, north-up, ruler, one-hand toggle) -->
|
||
<div id="map-controls" class="map-controls" role="toolbar" aria-label="Управление картой">
|
||
<button type="button" class="map-ctrl" id="mc-zoom-in" title="Приблизить" aria-label="Приблизить">+</button>
|
||
<button type="button" class="map-ctrl" id="mc-zoom-out" title="Отдалить" aria-label="Отдалить">−</button>
|
||
<button type="button" class="map-ctrl" id="mc-center" title="Центрировать на своём судне" aria-label="Центрировать на своём судне">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><circle cx="12" cy="12" r="3" fill="currentColor"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/><circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.5" fill="none"/></svg>
|
||
</button>
|
||
<button type="button" class="map-ctrl" id="mc-north-up" title="Сброс вращения (Север вверх)" aria-label="Север вверх">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M12 3 L17 20 L12 16 L7 20 Z" fill="#ff4d4d" stroke="#000" stroke-width=".5"/><text x="12" y="11" text-anchor="middle" font-size="7" font-weight="900" fill="#fff">N</text></svg>
|
||
</button>
|
||
<button type="button" class="map-ctrl" id="mc-compass" title="Heading-up: вращать карту по компасу" aria-label="Вращать карту по компасу">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||
<circle cx="12" cy="12" r="9" stroke="currentColor" stroke-width="1.4" fill="none"/>
|
||
<path d="M12 4 L14.5 12 L12 10 L9.5 12 Z" fill="#ff4d4d"/>
|
||
<path d="M12 20 L14.5 12 L12 14 L9.5 12 Z" fill="currentColor" opacity=".55"/>
|
||
<text x="12" y="3.6" text-anchor="middle" font-size="3.6" font-weight="900" fill="currentColor">N</text>
|
||
</svg>
|
||
</button>
|
||
<button type="button" class="map-ctrl" id="mc-ruler" title="Линейка (рулетка)" aria-label="Линейка">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M2 15 L15 2 L22 9 L9 22 Z M5 16 L8 13 M7 18 L10 15 M9 20 L12 17 M11 14 L14 11 M13 16 L16 13 M15 8 L18 5" stroke="currentColor" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>
|
||
</button>
|
||
<button type="button" class="map-ctrl" id="mc-follow" title="Следовать за судном (навигатор: авто-центрирование + авто-зум по скорости)" aria-label="Режим следования">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true">
|
||
<path d="M12 2 L18 20 L12 17 L6 20 Z" fill="currentColor" stroke="#000" stroke-width=".4"/>
|
||
<circle cx="12" cy="12" r="10" stroke="currentColor" stroke-width="1.4" fill="none" stroke-dasharray="2 3" opacity=".6"/>
|
||
</svg>
|
||
</button>
|
||
<button type="button" class="map-ctrl" id="mc-gps-src" title="Источник GPS: внутренний (NMEA) / телефон" aria-label="Источник GPS" data-src="nmea">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" class="mc-gps-icon-nmea"><rect x="3" y="4" width="18" height="12" rx="2" stroke="currentColor" stroke-width="1.6" fill="none"/><path d="M8 20h8M12 16v4" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><circle cx="9" cy="10" r="1.3" fill="currentColor"/><path d="M13 8h5M13 11h4M13 13h3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" class="mc-gps-icon-phone" style="display:none"><rect x="7" y="2" width="10" height="20" rx="2" stroke="currentColor" stroke-width="1.6" fill="none"/><circle cx="12" cy="19" r="1" fill="currentColor"/><path d="M10 6h4" stroke="currentColor" stroke-width="1.2" stroke-linecap="round" opacity=".7"/><circle cx="12" cy="12" r="1.6" fill="currentColor"/><path d="M9.5 12a2.5 2.5 0 0 1 5 0M8 12a4 4 0 0 1 8 0" stroke="currentColor" stroke-width="1.2" fill="none"/></svg>
|
||
</button>
|
||
<button type="button" class="map-ctrl" id="mc-onehand" title="Одноручный режим (крупные кнопки + тап-зум)" aria-label="Одноручный режим">
|
||
<svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true"><path d="M9 3v8H7c-1.1 0-2 .9-2 2v2l4 6h8c1.1 0 2-.9 2-2V9c0-1.1-.9-2-2-2h-5V3c0-1.1-.9-2-2-2S9 1.9 9 3z" stroke="currentColor" stroke-width="1.4" fill="none"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- One-hand side pad (hidden until toggled) -->
|
||
<div id="onehand-pad" class="onehand-pad" hidden aria-hidden="true">
|
||
<button type="button" class="onehand-btn" id="oh-zoom-in" aria-label="Приблизить">+</button>
|
||
<button type="button" class="onehand-btn" id="oh-zoom-out" aria-label="Отдалить">−</button>
|
||
<button type="button" class="onehand-btn" id="oh-center" aria-label="Центрировать">
|
||
<svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true"><circle cx="12" cy="12" r="3" fill="currentColor"/><path d="M12 2v4M12 18v4M2 12h4M18 12h4" stroke="currentColor" stroke-width="2" stroke-linecap="round" fill="none"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!-- Vessel infowindow (MarineTraffic-style: desktop floating + draggable, mobile bottom-sheet) -->
|
||
<div id="vessel-infowindow" class="vinf" hidden aria-hidden="true" role="dialog"></div>
|
||
|
||
<!-- Semi-transparent corner HUD:
|
||
- Own ship readout (SOG / coords / compass)
|
||
- Selected target (with X to clear) OR top-5 nearest targets (minimal info)
|
||
Replaces the mobile tab-bar for nearest vessels. -->
|
||
<div id="nearby-hud" class="nearby-hud" aria-label="Навигационный дисплей">
|
||
<div class="nearby-hud__own" id="nhud-own">
|
||
<div class="nhud-own__row">
|
||
<span class="nhud-compass" id="nhud-compass" title="Компас (GPS-курс или Android-компас)">
|
||
<svg viewBox="0 0 40 40" width="36" height="36" aria-hidden="true">
|
||
<circle cx="20" cy="20" r="18" fill="rgba(0,0,0,.35)" stroke="rgba(210,255,26,.35)" stroke-width="1"/>
|
||
<text x="20" y="7.8" text-anchor="middle" font-size="5" font-weight="700" fill="#d2ff1a">N</text>
|
||
<text x="20" y="36" text-anchor="middle" font-size="5" font-weight="500" fill="#8b949e">S</text>
|
||
<text x="35" y="22" text-anchor="middle" font-size="5" font-weight="500" fill="#8b949e">E</text>
|
||
<text x="5" y="22" text-anchor="middle" font-size="5" font-weight="500" fill="#8b949e">W</text>
|
||
<g class="nhud-compass-needle" id="nhud-needle" style="transform-origin:20px 20px">
|
||
<path d="M20 5 L23 20 L20 18 L17 20 Z" fill="#ff4d4d"/>
|
||
<path d="M20 35 L23 20 L20 22 L17 20 Z" fill="rgba(201,209,217,.85)"/>
|
||
</g>
|
||
</svg>
|
||
<span class="nhud-compass-val" id="nhud-compass-val">—</span>
|
||
</span>
|
||
<div class="nhud-own__info">
|
||
<div class="nhud-own__line"><span class="nhud-k">SOG</span><b id="nhud-own-sog">—</b></div>
|
||
<div class="nhud-own__line"><span class="nhud-k">COG</span><b id="nhud-own-cog">—</b></div>
|
||
<div class="nhud-own__coords" id="nhud-own-coords">—</div>
|
||
<div class="nhud-own__src" id="nhud-own-src"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="nearby-hud__list" id="nhud-list" aria-label="Ближайшие цели"></div>
|
||
<button type="button" class="nearby-hud__toggle" id="nhud-toggle" title="Свернуть/развернуть" aria-label="Свернуть/развернуть">
|
||
<svg viewBox="0 0 16 16" width="14" height="14" aria-hidden="true"><path d="M3 6 L8 11 L13 6" stroke="currentColor" stroke-width="1.8" fill="none" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
||
</button>
|
||
</div>
|
||
|
||
<!--
|
||
Mobile bottom tab bar — removed in favour of the always-on #nearby-hud corner overlay
|
||
and the full "Цели" tab in the navbar. Left commented-out in case we ever want to
|
||
re-introduce tab-style bottom navigation.
|
||
|
||
<div id="mob-panel-bar">
|
||
<button class="mob-panel-tab" data-panel="sidebar" id="mob-tab-targets">Ближайшие</button>
|
||
<button class="mob-panel-tab" data-panel="ownship" id="mob-tab-ownship">Судно</button>
|
||
</div>
|
||
-->
|
||
|
||
</div>
|
||
|
||
<!-- ==================== TARGETS PAGE (full list, primarily for mobile) ==================== -->
|
||
<div id="page-targets" class="tab-page">
|
||
<div id="targets-page-inner" class="targets-page-inner">
|
||
<h3 class="vessel-sidebar-heading">Цели <span class="vessel-count-paren">(<span id="targets-count">0</span><span id="targets-count-suffix"></span>)</span></h3>
|
||
<div id="targets-toolbar" class="vessel-list-toolbar">
|
||
<input type="search" id="targets-search" class="vessel-search-input" placeholder="Поиск: MMSI, имя, позывной…" autocomplete="off" spellcheck="false"/>
|
||
<div class="vessel-toolbar-row">
|
||
<label class="vessel-toolbar-label">Сортировка
|
||
<select id="targets-sort" class="vessel-toolbar-select">
|
||
<option value="dist-asc">Расстояние ↑</option>
|
||
<option value="dist-desc">Расстояние ↓</option>
|
||
<option value="time-desc" selected>Обновление (новые)</option>
|
||
<option value="time-asc">Обновление (старые)</option>
|
||
<option value="name-asc">Название А–Я</option>
|
||
<option value="mmsi-asc">MMSI</option>
|
||
<option value="class-asc">Класс A/B</option>
|
||
<option value="speed-desc">Скорость</option>
|
||
</select>
|
||
</label>
|
||
<label class="vessel-toolbar-label">Класс
|
||
<select id="targets-filter-class" class="vessel-toolbar-select">
|
||
<option value="all" selected>Все</option>
|
||
<option value="A">A</option>
|
||
<option value="B">B</option>
|
||
<option value="BS">База</option>
|
||
<option value="N">Буи</option>
|
||
</select>
|
||
</label>
|
||
<label class="vessel-toolbar-label">Тип
|
||
<select id="targets-filter-shiptype" class="vessel-toolbar-select">
|
||
<option value="all" selected>Все</option>
|
||
<option value="unknown">Не указан / 0</option>
|
||
<option value="fishing">Рыболов (30)</option>
|
||
<option value="tug">Буксир (31–32)</option>
|
||
<option value="passenger">Пассажир (60–69)</option>
|
||
<option value="cargo">Грузовой (70–79)</option>
|
||
<option value="tanker">Танкер (80–89)</option>
|
||
<option value="other">Прочие</option>
|
||
</select>
|
||
</label>
|
||
</div>
|
||
</div>
|
||
<div id="targets-list" class="targets-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== STATS PAGE ==================== -->
|
||
<div id="page-stats" class="tab-page">
|
||
<div class="stats-grid">
|
||
<div class="stat-card"><div class="val" id="st-active">0</div><div class="lbl">Активных целей</div></div>
|
||
<div class="stat-card"><div class="val" id="st-total">0</div><div class="lbl">Всего за сессию</div></div>
|
||
<div class="stat-card"><div class="val" id="st-class-a">0</div><div class="lbl">Класс A</div></div>
|
||
<div class="stat-card"><div class="val" id="st-class-b">0</div><div class="lbl">Класс B</div></div>
|
||
<div class="stat-card"><div class="val" id="st-ais-msg">0</div><div class="lbl">AIS сообщений</div></div>
|
||
<div class="stat-card"><div class="val" id="st-gps-msg">0</div><div class="lbl">GPS сообщений</div></div>
|
||
<div class="stat-card"><div class="val" id="st-gps-fix">-</div><div class="lbl">GPS фикс</div></div>
|
||
<div class="stat-card"><div class="val" id="st-uptime">-</div><div class="lbl">Аптайм приложения</div></div>
|
||
<div class="stat-card"><div class="val" id="st-sys-uptime">-</div><div class="lbl">Аптайм системы</div></div>
|
||
<div class="stat-card"><div class="val" id="st-rx-time">-</div><div class="lbl">Время приёмника</div></div>
|
||
<div class="stat-card"><div class="val" id="st-cpu-temp">-</div><div class="lbl">Температура CPU</div></div>
|
||
<div class="stat-card"><div class="val" id="st-cpu-load">-</div><div class="lbl">Загрузка CPU</div></div>
|
||
<div class="stat-card"><div class="val" id="st-mem">-</div><div class="lbl">Память</div></div>
|
||
<div class="stat-card"><div class="val" id="st-ais-rate">-</div><div class="lbl">AIS поток</div></div>
|
||
<div class="stat-card"><div class="val" id="st-nmea-rate">-</div><div class="lbl">Все NMEA</div></div>
|
||
<div class="stat-card stat-card-test"><div class="val" id="st-test-mmsi">0</div><div class="lbl">Тест (247320161)</div><div class="test-ch-detail"><span>A: <b id="st-test-mmsi-a">0</b></span> <span>B: <b id="st-test-mmsi-b">0</b></span></div></div>
|
||
</div>
|
||
<div class="stat-section">
|
||
<h3>AIS сообщения по типам</h3>
|
||
<table class="stat-table">
|
||
<thead><tr><th>Тип</th><th>Описание</th><th>Количество</th></tr></thead>
|
||
<tbody id="st-ais-types"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="stat-section">
|
||
<details class="stat-details" id="st-adv-details">
|
||
<summary>Расширенная статистика (ошибки/парсинг)</summary>
|
||
<div class="stat-adv-grid">
|
||
<div class="stat-adv-card">
|
||
<div class="stat-adv-title">Ошибки парсинга</div>
|
||
<table class="stat-table stat-table-compact">
|
||
<tbody id="st-parse-errors"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="stat-adv-card">
|
||
<div class="stat-adv-title">UDP / фрагменты</div>
|
||
<table class="stat-table stat-table-compact">
|
||
<tbody id="st-udp-errors"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="stat-adv-card">
|
||
<div class="stat-adv-title">Хранилище / отправка</div>
|
||
<table class="stat-table stat-table-compact">
|
||
<tbody id="st-storage-errors"></tbody>
|
||
</table>
|
||
</div>
|
||
<div class="stat-adv-card">
|
||
<div class="stat-adv-title">WS / прочее</div>
|
||
<table class="stat-table stat-table-compact">
|
||
<tbody id="st-misc-counters"></tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
</details>
|
||
</div>
|
||
<div class="slots-section">
|
||
<div class="slots-header" id="slots-toggle">
|
||
<span class="slots-arrow" id="slots-arrow">▶</span> Слоты TDMA
|
||
</div>
|
||
<div class="slots-content" id="slots-content">
|
||
<div class="slots-channels">
|
||
<div class="slots-channel">
|
||
<div class="slots-ch-title">Канал A</div>
|
||
<div class="slots-info" id="slots-info-a"><span class="slots-no-data">Нет данных</span></div>
|
||
<div class="slots-bar" id="slots-bar-a" style="display:none"><div class="slots-bar-fill" id="slots-bar-fill-a"></div></div>
|
||
<canvas id="slots-rssi-a" class="rssi-chart" width="482" height="90" style="display:none"></canvas>
|
||
<div class="slots-canvas-wrap" id="slots-wrap-a" style="display:none">
|
||
<canvas id="slots-canvas-a" width="482" height="180"></canvas>
|
||
</div>
|
||
</div>
|
||
<div class="slots-channel">
|
||
<div class="slots-ch-title">Канал B</div>
|
||
<div class="slots-info" id="slots-info-b"><span class="slots-no-data">Нет данных</span></div>
|
||
<div class="slots-bar" id="slots-bar-b" style="display:none"><div class="slots-bar-fill" id="slots-bar-fill-b"></div></div>
|
||
<canvas id="slots-rssi-b" class="rssi-chart" width="482" height="90" style="display:none"></canvas>
|
||
<div class="slots-canvas-wrap" id="slots-wrap-b" style="display:none">
|
||
<canvas id="slots-canvas-b" width="482" height="180"></canvas>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="slots-legend">
|
||
<span class="slots-legend-item"><span class="slots-legend-box free"></span> Свободен</span>
|
||
<span class="slots-legend-item"><span class="slots-legend-box occ"></span> Занят</span>
|
||
<span class="slots-legend-item" style="margin-left:12px"><span style="display:inline-block;width:16px;height:2px;background:#c678dd;vertical-align:middle;margin-right:4px"></span> RSSI</span>
|
||
<span class="slots-legend-item"><span style="display:inline-block;width:2px;height:12px;background:#ffd166;vertical-align:middle;margin-right:4px"></span> Event</span>
|
||
<span class="slots-legend-item"><span style="display:inline-block;width:2px;height:12px;background:#3fb950;vertical-align:middle;margin-right:4px;opacity:.9"></span> Кадр</span>
|
||
<span class="slots-legend-item" style="margin-left:12px"><span style="display:inline-block;width:16px;height:2px;background:#4fc3f7;vertical-align:middle;margin-right:4px"></span> Noise Floor</span>
|
||
<span class="slots-legend-item"><span style="display:inline-block;width:16px;height:2px;background:#f0883e;vertical-align:middle;margin-right:4px"></span> Threshold</span>
|
||
</div>
|
||
<div class="slots-test-send">
|
||
<span class="slots-test-label">Тест слота:</span>
|
||
<select id="test-slot-channel" class="slots-test-input" style="width:auto;min-width:80px">
|
||
<option value="A">Канал A</option>
|
||
<option value="B">Канал B</option>
|
||
</select>
|
||
<input type="number" id="test-slot-number" class="slots-test-input" min="0" max="2249" value="0" placeholder="Слот 0-2249" style="width:90px"/>
|
||
<button id="test-slot-send" class="slots-test-btn">Отправить</button>
|
||
<span id="test-slot-status" class="slots-test-status"></span>
|
||
</div>
|
||
<div id="slot-selected-info" class="slots-selected-info">Клик по слоту покажет детали.</div>
|
||
</div>
|
||
<div class="slots-tooltip" id="slots-tooltip"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== SETTINGS PAGE ==================== -->
|
||
<div id="page-settings" class="tab-page">
|
||
|
||
<!-- Network mode -->
|
||
<div class="settings-card">
|
||
<h3>Сеть / Режим работы</h3>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Текущий режим</span>
|
||
<span id="net-live-mode" class="net-status unknown">...</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">IP-адрес устройства</span>
|
||
<span class="settings-value" id="net-live-ip">-</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">SSID</span>
|
||
<span class="settings-value" id="net-live-ssid">-</span>
|
||
</div>
|
||
|
||
<div class="net-mode-btns">
|
||
<div class="net-mode-btn" id="net-btn-ap" data-mode="ap">Точка доступа (AP)</div>
|
||
<div class="net-mode-btn" id="net-btn-wifi" data-mode="wifi">WiFi-клиент</div>
|
||
</div>
|
||
|
||
<!-- AP settings -->
|
||
<div id="net-ap-fields" style="display:none">
|
||
<div class="settings-row"><span class="settings-label">SSID точки доступа</span><input class="net-input" id="net-ap-ssid" placeholder="AISMap"/></div>
|
||
<div class="settings-row"><span class="settings-label">Пароль AP</span><input class="net-input" id="net-ap-psk" type="text" placeholder="aismap1234"/></div>
|
||
<div class="settings-row"><span class="settings-label">IP точки доступа</span><input class="net-input" id="net-ap-ip" placeholder="192.168.4.1/24"/></div>
|
||
</div>
|
||
|
||
<!-- WiFi settings -->
|
||
<div id="net-wifi-fields" style="display:none">
|
||
<div class="settings-row"><span class="settings-label">SSID сети</span>
|
||
<span style="display:flex;gap:6px;align-items:center">
|
||
<input class="net-input" id="net-wifi-ssid" placeholder="MyNetwork"/>
|
||
<button class="net-btn net-btn-primary" id="net-scan-btn" style="padding:6px 12px;font-size:11px">Сканировать</button>
|
||
</span>
|
||
</div>
|
||
<div id="net-scan-list" class="net-scan-list" style="display:none"></div>
|
||
<div class="settings-row"><span class="settings-label">Пароль WiFi</span><input class="net-input" id="net-wifi-psk" type="password" placeholder="********"/></div>
|
||
<div class="settings-row"><span class="settings-label">Статический IP</span><input class="net-input" id="net-wifi-ip" placeholder="192.168.22.50/24"/></div>
|
||
<span class="net-toggle-adv" id="net-adv-toggle">Дополнительно ▾</span>
|
||
<div class="net-advanced" id="net-advanced">
|
||
<div class="settings-row"><span class="settings-label">Шлюз</span><input class="net-input" id="net-wifi-gw" placeholder="192.168.22.1"/></div>
|
||
<div class="settings-row"><span class="settings-label">DNS</span><input class="net-input" id="net-wifi-dns" placeholder="8.8.8.8"/></div>
|
||
<div class="settings-row"><span class="settings-label">Интерфейс</span><input class="net-input" id="net-iface" placeholder="wlan0"/></div>
|
||
</div>
|
||
</div>
|
||
|
||
<div style="margin-top:14px;display:flex;gap:8px">
|
||
<button class="net-btn net-btn-primary" id="net-save-btn">Сохранить настройки</button>
|
||
<button class="net-btn net-btn-danger" id="net-switch-btn">Применить и переключить</button>
|
||
</div>
|
||
<div id="net-msg" class="net-msg"></div>
|
||
</div>
|
||
|
||
<div class="settings-card">
|
||
<h3>Подключение</h3>
|
||
<div class="settings-row"><span class="settings-label">UDP порт (AIS + GPS)</span><span class="settings-value">5006</span></div>
|
||
<div class="settings-row"><span class="settings-label">Web-сервер</span><span class="settings-value" id="set-server">-</span></div>
|
||
<div class="settings-row"><span class="settings-label">HTTPS</span><span class="settings-value" id="set-https">-</span></div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Тайминги целей</h3>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Потеря цели (подсветка), мин</span>
|
||
<span style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:flex-end">
|
||
<input class="net-input" type="number" id="set-losing-target-min" min="1" step="1" style="width:120px"/>
|
||
<span class="settings-value" id="set-losing-target-preview">-</span>
|
||
</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Удаление цели, мин</span>
|
||
<span style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:flex-end">
|
||
<input class="net-input" type="number" id="set-remove-target-min" min="1" step="1" style="width:120px"/>
|
||
<span class="settings-value" id="set-remove-target-preview">-</span>
|
||
</span>
|
||
</div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Единицы измерения</h3>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Расстояние</span>
|
||
<select id="set-dist-unit" class="net-input" style="width:auto;min-width:100px">
|
||
<option value="nm">Морские мили (NM)</option>
|
||
<option value="km">Километры (км)</option>
|
||
<option value="au">Астрономические единицы (AU)</option>
|
||
</select>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Скорость</span>
|
||
<select id="set-speed-unit" class="net-input" style="width:auto;min-width:100px">
|
||
<option value="kn">Узлы (уз.)</option>
|
||
<option value="kmh">Километры в час (км/ч)</option>
|
||
</select>
|
||
</div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Радиусы пеленга</h3>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Радиус 1 (ВНИМАНИЕ), <span id="set-warn-radius-unit">NM</span></span>
|
||
<span style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:flex-end">
|
||
<input type="range" id="set-warn-radius-slider" min="0" max="50" step="0.1" value="0" style="width:220px"/>
|
||
<input class="net-input" type="number" id="set-warn-radius" min="0" step="0.1" placeholder="0 = выкл" style="width:120px"/>
|
||
</span>
|
||
</div>
|
||
<div class="settings-row">
|
||
<span class="settings-label">Радиус 2 (подсветка), <span id="set-near-radius-unit">NM</span></span>
|
||
<span style="display:flex;gap:10px;align-items:center;flex-wrap:wrap;justify-content:flex-end">
|
||
<input type="range" id="set-near-radius-slider" min="0" max="50" step="0.1" value="0" style="width:220px"/>
|
||
<input class="net-input" type="number" id="set-near-radius" min="0" step="0.1" placeholder="0 = выкл" style="width:120px"/>
|
||
</span>
|
||
</div>
|
||
<p class="transponder-hint" style="margin:8px 0 0">Окружности рисуются вокруг “Своё судно”. Радиус 1 включает баннер «ВНИМАНИЕ», радиус 2 подсвечивает близкие цели.</p>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Сертификат</h3>
|
||
<div class="settings-row"><span class="settings-label">Статус CA</span><span class="settings-value"><a href="/cert" style="color:#4fc3f7">Установить сертификат</a></span></div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>О системе</h3>
|
||
<div class="settings-row"><span class="settings-label">Версия</span><span class="settings-value">1.0</span></div>
|
||
<div class="settings-row"><span class="settings-label">Secure context</span><span class="settings-value" id="set-secure">-</span></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== TRANSPONDER (Class B) ==================== -->
|
||
<div id="page-transponder" class="tab-page">
|
||
<div class="settings-card">
|
||
<h3>Наше судно (Class B / B+)</h3>
|
||
<p class="transponder-hint">MMSI, имя, позывной, габариты и данные для сообщений 18, 19, 24. Движение из GPS при включённой опции ниже.</p>
|
||
<div class="settings-row"><span class="settings-label">MMSI</span><input class="net-input" type="number" id="tp-mmsi" min="0"/></div>
|
||
<div class="settings-row"><span class="settings-label">Имя судна (до 20)</span><input class="net-input" id="tp-shipname" maxlength="20"/></div>
|
||
<div class="settings-row"><span class="settings-label">Позывной</span><input class="net-input" id="tp-callsign" maxlength="7"/></div>
|
||
<div class="settings-row ship-type-row"><span class="settings-label">Тип судна</span>
|
||
<select class="net-input ship-type-select" id="tp-ship-type" title="ITU-R M.1371-6, табл. 51 (0–99)"></select>
|
||
</div>
|
||
<p class="transponder-hint ship-type-legend">Табл. 51: <abbr title="dangerous goods">ОПГ</abbr> — опасные грузы; <abbr title="materials hazardous only in bulk">ОН</abbr> — опасные только насыпью; <abbr title="harmful substances">ВВ</abbr> — вредные вещества; <abbr title="marine pollutants">МЗ</abbr> — морские загрязнители. Категории X, Y, Z, OS — по ИМО (ранее A, B, C, D).</p>
|
||
<div class="settings-row"><span class="settings-label">Габариты: A B C D (м)</span>
|
||
<span style="display:flex;flex-wrap:wrap;gap:6px;align-items:center">
|
||
<span class="settings-value" style="font-size:10px;color:#667;margin-right:4px">A нос</span>
|
||
<input class="net-input tp-dim" type="number" id="tp-to-bow" min="0" max="511" title="A: нос → GPS, м (≤511)"/>
|
||
<span class="settings-value" style="font-size:10px;color:#667">B корма</span>
|
||
<input class="net-input tp-dim" type="number" id="tp-to-stern" min="0" max="511" title="B: GPS → корма, м"/>
|
||
<span class="settings-value" style="font-size:10px;color:#667">C порт</span>
|
||
<input class="net-input tp-dim" type="number" id="tp-to-port" min="0" max="63" title="C: порт → GPS, м (≤63)"/>
|
||
<span class="settings-value" style="font-size:10px;color:#667">D штб</span>
|
||
<input class="net-input tp-dim" type="number" id="tp-to-starboard" min="0" max="63" title="D: GPS → штюрборд, м"/>
|
||
</span>
|
||
</div>
|
||
<div class="ship-editor-block">
|
||
<p class="transponder-hint" style="margin-top:0">Мышью: <strong>корма</strong> — L, <strong>правый борт</strong> — W (доли A/L и C/W как в начале жеста), <strong>GPS</strong> — сдвиг внутри L×W. Увеличивать W/L можно и за пределами контура — координаты не обрезаются по краю корпуса. Точные A–D — в полях выше.</p>
|
||
<div class="ship-editor-wrap">
|
||
<svg id="ship-editor-svg" class="ship-editor-svg" viewBox="0 0 320 320" xmlns="http://www.w3.org/2000/svg" aria-label="Редактор габаритов судна">
|
||
<defs>
|
||
<marker id="ship-dim-arrow" viewBox="0 0 10 10" refX="10" refY="5" markerWidth="5" markerHeight="5" orient="auto-start-reverse">
|
||
<path d="M10,5 L0,0 L0,10 Z" fill="#8b949e"/>
|
||
</marker>
|
||
</defs>
|
||
<g id="ship-editor-inner" transform="translate(0,0)">
|
||
<g id="ship-dim-beam" class="ship-dim-group" aria-hidden="true"></g>
|
||
<g id="ship-dim-length" class="ship-dim-group" aria-hidden="true"></g>
|
||
<path id="ship-hull" class="ship-hull" d=""/>
|
||
<line id="ship-grid-h" class="ship-grid" x1="0" y1="0" x2="0" y2="0"/>
|
||
<line id="ship-grid-v" class="ship-grid" x1="0" y1="0" x2="0" y2="0"/>
|
||
<text id="ship-lbl-bow" class="ship-axis-lbl ship-axis-lbl--dyn" x="0" y="0" text-anchor="middle">нос (курс)</text>
|
||
<text id="ship-lbl-stern" class="ship-axis-lbl ship-axis-lbl--dyn" x="0" y="0" text-anchor="middle">корма</text>
|
||
<g id="ship-gps-marker" class="ship-gps-group" transform="translate(0,0)">
|
||
<circle class="ship-gps-hit" r="16" cx="0" cy="0"/>
|
||
<circle class="ship-gps-dot" r="9" cx="0" cy="0"/>
|
||
<text class="ship-gps-lbl" x="0" y="4" text-anchor="middle">GPS</text>
|
||
</g>
|
||
<g id="ship-edge-handles" class="ship-edge-handles" aria-hidden="true">
|
||
<g class="ship-handle ship-handle--stern" data-ship-edge="stern"><circle r="11" cx="0" cy="0"/></g>
|
||
<g class="ship-handle ship-handle--starboard" data-ship-edge="starboard"><circle r="11" cx="0" cy="0"/></g>
|
||
</g>
|
||
</g>
|
||
</svg>
|
||
<ul class="ship-editor-legend">
|
||
<li><strong>Серые размеры</strong> — W сверху, L справа</li>
|
||
<li><strong>Жёлтые маркеры</strong> — корма = L, правый борт = W (A–D пересчитываются пропорционально)</li>
|
||
<li><strong>GPS</strong> — перетаскивание на схеме или спинбоксы A–D</li>
|
||
<li><strong>A</strong> — нос → GPS (бит 21–29)</li>
|
||
<li><strong>B</strong> — GPS → корма (12–20)</li>
|
||
<li><strong>C</strong> — порт → GPS (6–11)</li>
|
||
<li><strong>D</strong> — GPS → штюрборд (0–5)</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<div class="settings-row"><span class="settings-label">Vendor ID (6)</span><input class="net-input" id="tp-vendorid" maxlength="6" placeholder="1HZZZZ"/></div>
|
||
<div class="settings-row"><span class="settings-label">Модель / серийный №</span>
|
||
<span style="display:flex;gap:6px">
|
||
<input class="net-input" type="number" id="tp-model" min="0"/>
|
||
<input class="net-input" type="number" id="tp-serial" min="0"/>
|
||
</span>
|
||
</div>
|
||
<div class="settings-row"><span class="settings-label">Движение из GPS</span><label><input type="checkbox" id="tp-use-gps"/> курс/скорость/heading из ownship</label></div>
|
||
<div class="settings-row"><span class="settings-label">GPS (ownship)</span><span class="settings-value" id="tp-ownship-hint">—</span></div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Отправка NRZI</h3>
|
||
<p class="transponder-hint">В UDP <code>127.0.0.1:6010</code> уходит пакет как в «Тест слота»: <strong>канал</strong> (1 байт) + <strong>слот</strong> (uint16 LE) + сгенерированный NRZI.</p>
|
||
<div class="settings-row"><span class="settings-label">Канал / слот</span>
|
||
<span style="display:flex;flex-wrap:wrap;gap:8px;align-items:center">
|
||
<select id="tp-slot-channel" class="slots-test-input" style="width:auto;min-width:80px">
|
||
<option value="A">Канал A</option>
|
||
<option value="B">Канал B</option>
|
||
</select>
|
||
<input type="number" id="tp-slot-number" class="slots-test-input" min="0" max="2249" value="0" title="Слот 0–2249" style="width:90px"/>
|
||
</span>
|
||
</div>
|
||
<div class="settings-row"><span class="settings-label">Кодер NRZI</span>
|
||
<select id="tp-nrzi-encoder" class="net-input" style="width:auto;min-width:220px">
|
||
<option value="aistx" selected>ais_nrzi_pipeline/phy.py (gr-aistx) — по умолчанию</option>
|
||
<option value="ais_phy">ais_phy.py (pyais + CRC без reverse-byte)</option>
|
||
</select>
|
||
</div>
|
||
<div class="settings-row"><span class="settings-label">NRZI упаковка</span>
|
||
<select id="tp-nrzi-mode" class="net-input" style="width:auto">
|
||
<option value="packed">packed (8 бит/байт)</option>
|
||
<option value="expanded">expanded (1 бит = 0x00/0xFF)</option>
|
||
</select>
|
||
</div>
|
||
<div class="settings-row"><span class="settings-label">Преамбула NRZI</span><label><input type="checkbox" id="tp-preamble"/> чередование 10… перед флагом (как gr-aistx)</label></div>
|
||
<div class="settings-row"><span class="settings-label">Длина преамбулы (бит)</span><input class="net-input" type="number" id="tp-preamble-bits" min="0" max="128" value="24" title="ITU AIS training ~24 бит; 0 — без преамбулы при включённой галочке не рекомендуется"/></div>
|
||
<div class="settings-row"><span class="settings-label">Добор payload до октета</span><label><input type="checkbox" id="tp-pad-payload"/> нулями до кратности 8 бит перед CRC (как в GNU Radio)</label></div>
|
||
<div class="settings-row"><span class="settings-label">Добор NRZ до (бит)</span><input class="net-input" type="number" id="tp-pad-nrz-bits" min="0" max="4096" value="256" title="0=выкл. Нули в NRZ перед NRZI, если кадр короче (как padd_frame). В GR было 200; с преамбулой 24+HDLC часто >200 бит — тогда нужно ≥256, иначе добор не добавляет биты."/></div>
|
||
<div class="settings-row"><span class="settings-label">Импульс TX (GPIO)</span>
|
||
<span style="display:flex;flex-wrap:wrap;gap:10px;align-items:center;max-width:640px">
|
||
<label><input type="checkbox" id="tp-gpio-auto"/> после UDP: пауза 50–100 ms, затем скрипт (PTT)</label>
|
||
</span>
|
||
</div>
|
||
<div class="settings-row"><span class="settings-label">Пауза перед GPIO (мс)</span><input class="net-input" type="number" id="tp-gpio-delay-ms" min="50" max="100" value="75" title="После отправки UDP, перед запуском pulse-скрипта"/></div>
|
||
<div class="settings-row"><span class="settings-label">Скрипт импульса</span><input class="net-input" id="tp-gpio-script" style="max-width:480px" placeholder="/root/pulse_once.py или путь к scripts/pulse_once.py"/></div>
|
||
<div style="margin-top:12px;display:flex;flex-wrap:wrap;gap:8px;align-items:center">
|
||
<button type="button" class="net-btn net-btn-primary" id="tp-save">Сохранить</button>
|
||
<button type="button" class="net-btn" id="tp-preview">Обновить превью</button>
|
||
<button type="button" class="net-btn" id="tp-gpio-pulse-once" title="Только импульс, без UDP">Импульс TX</button>
|
||
<button type="button" class="net-btn" id="tp-send-18">Отпр. 18</button>
|
||
<button type="button" class="net-btn" id="tp-send-19">Отпр. 19</button>
|
||
<button type="button" class="net-btn" id="tp-send-24a">Отпр. 24 ч.1 (имя)</button>
|
||
<button type="button" class="net-btn" id="tp-send-24b">Отпр. 24 ч.2 (статика)</button>
|
||
<button type="button" class="net-btn net-btn-danger" id="tp-send-broadcast">Бродкаст 18+19+24A+24B</button>
|
||
</div>
|
||
<div id="tp-msg" class="net-msg"></div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Сырой NRZI (hex)</h3>
|
||
<p class="transponder-hint">Вставьте байты в hex (пробелы допускаются). Отправка: те же <strong>канал</strong> и <strong>слот</strong>, что выше → UDP <code>127.0.0.1:6010</code> как у теста слота. При включённой галочке «после UDP» к телу запроса подмешиваются настройки GPIO из формы.</p>
|
||
<textarea id="tp-raw-hex" class="transponder-raw-hex" rows="4" spellcheck="false" placeholder="66 66 66 fe … или cc cc cc fe …"></textarea>
|
||
<div style="margin-top:10px">
|
||
<button type="button" class="net-btn net-btn-primary" id="tp-send-raw">Отправить сырой NRZI</button>
|
||
</div>
|
||
</div>
|
||
<div class="settings-card">
|
||
<h3>Превью (чистый NRZI и полный UDP-кадр)</h3>
|
||
<pre class="transponder-preview" id="tp-preview-out">Нажмите «Обновить превью».</pre>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== LOGS PAGE ==================== -->
|
||
<div id="page-logs" class="tab-page">
|
||
<div class="logs-toolbar">
|
||
<label>Фильтр:</label>
|
||
<select id="log-filter">
|
||
<option value="all">Все</option>
|
||
<option value="ais">AIS</option>
|
||
<option value="gps">GPS</option>
|
||
<option value="unknown">Прочее</option>
|
||
</select>
|
||
<input type="text" id="log-search" placeholder="Поиск..." style="width:120px"/>
|
||
<span class="log-btn active" id="log-autoscroll">Автопрокрутка</span>
|
||
<span class="log-btn" id="log-clear">Очистить</span>
|
||
<span style="flex:1"></span>
|
||
<span id="log-count" style="font-size:11px;color:#667">0 строк</span>
|
||
</div>
|
||
<div id="log-output"></div>
|
||
</div>
|
||
|
||
<!-- ==================== CONFIG PAGE ==================== -->
|
||
<div id="page-config" class="tab-page">
|
||
<div class="config-toolbar">
|
||
<span id="cfg-title">Конфиги</span>
|
||
<span style="flex:1"></span>
|
||
<span class="config-svc-label">Сервис:</span>
|
||
<span id="cfg-svc-state" class="config-svc-badge unknown">...</span>
|
||
</div>
|
||
<div class="config-body">
|
||
<div class="config-tabs">
|
||
<button class="config-tab active" id="cfg-tab-mini" type="button">AIS-catcher Mini</button>
|
||
<button class="config-tab" id="cfg-tab-aishub" type="button">AisHub</button>
|
||
<span style="flex:1"></span>
|
||
<span id="cfg-file-hint" class="config-file-hint">Файл: /ais-mini.conf</span>
|
||
</div>
|
||
<textarea id="cfg-editor" class="config-editor" spellcheck="false" placeholder="Загрузка..."></textarea>
|
||
<div class="config-actions">
|
||
<button class="net-btn net-btn-primary" id="cfg-save-btn">Сохранить конфиг</button>
|
||
<button class="net-btn net-btn-danger" id="cfg-restart-btn">Перезапустить сервис</button>
|
||
<button class="net-btn" id="cfg-reload-btn" style="background:#30363d;color:#e0e0e0">Обновить</button>
|
||
<span style="flex:1"></span>
|
||
<span id="cfg-msg" class="config-msg"></span>
|
||
</div>
|
||
<div class="config-hint" id="cfg-bottom-hint">Файл: /ais-mini.conf | Сервис: aisMini.service</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- ==================== CONSOLE PAGE ==================== -->
|
||
<div id="page-console" class="tab-page">
|
||
<div class="console-toolbar">
|
||
<span>Локальная оболочка на устройстве</span>
|
||
<span style="flex:1"></span>
|
||
<span id="console-status">—</span>
|
||
</div>
|
||
<div id="terminal-unavailable"></div>
|
||
<div id="terminal-wrap"></div>
|
||
</div>
|
||
|
||
<script>
|
||
// Service Worker: кеш тайлов и ассетов. Регистрируем в корне, чтобы scope='/'.
|
||
if ('serviceWorker' in navigator && location.protocol !== 'file:') {
|
||
window.addEventListener('load', function () {
|
||
navigator.serviceWorker.register('/sw.js', { scope: '/' }).catch(function (e) {
|
||
try { console.warn('[AISMap] SW register failed:', e); } catch (_) {}
|
||
});
|
||
});
|
||
}
|
||
</script>
|
||
<script src="/static/leaflet/leaflet.js"></script>
|
||
<script src="/static/leaflet/leaflet-rotate.js"></script>
|
||
<script src="/static/leaflet/Leaflet.VectorGrid.bundled.min.js"></script>
|
||
<script src="/static/js/ship_dims_editor.js"></script>
|
||
<script src="/static/js/ship_types_table51.js"></script>
|
||
<!-- Cache-bust app.js so field deployments pick up the latest UI logic -->
|
||
<script src="/static/js/app.js?v=2026-04-30h"></script>
|
||
</body>
|
||
</html>
|