feat(admin): очистка всех сканов — POST /api/admin/purge-scans + UI (NETTOPO_RESET_DB_SECRET)

Made-with: Cursor
This commit is contained in:
Луценко Андрей Анатольевич
2026-04-10 16:08:35 +10:00
parent a7f80eac9a
commit a782d36af9
7 changed files with 117 additions and 12 deletions
+52
View File
@@ -55,6 +55,14 @@
<button id="diffBtn">Сравнить</button>
<button id="exportDiffBtn">Экспорт diff JSON</button>
</div>
<details class="admin-purge" style="margin:10px 0;padding:8px 12px;background:#fff7ed;border:1px solid #fed7aa;border-radius:8px;max-width:720px;">
<summary style="cursor:pointer;font-weight:600;color:#9a3412;">Админ: начать всё заново — очистить данные сканов в БД</summary>
<p class="hint" style="margin:8px 0;">На сервере должен быть задан <code>NETTOPO_RESET_DB_SECRET</code>. Ключ ниже передаётся только в заголовке запроса и нигде не сохраняется.</p>
<div class="row" style="flex-wrap:wrap;gap:8px;align-items:center;">
<input type="password" id="purgeSecretInput" placeholder="секрет (X-Nettopo-Reset-Key)" style="min-width:260px;" autocomplete="off" />
<button type="button" id="purgeDbBtn" style="background:#b91c1c;color:#fff;border:none;padding:6px 12px;border-radius:6px;cursor:pointer;">Удалить все сканы и результаты</button>
</div>
</details>
<div id="status">Ожидание действия. Строка статуса показывает текущую операцию и прогресс скана.</div>
<div class="panel">
@@ -1097,6 +1105,50 @@
});
refreshDevicesBtn.addEventListener("click", () => loadDeviceTable(scanIdInput.value.trim()));
createScanBtn.addEventListener("click", createScan);
document.getElementById("purgeDbBtn")?.addEventListener("click", async () => {
const inp = document.getElementById("purgeSecretInput");
const secret = (inp && inp.value.trim()) || "";
if (!secret) {
setStatus("Введите секрет (тот же, что NETTOPO_RESET_DB_SECRET на сервере).");
return;
}
if (
!confirm(
"Удалить ВСЕ задачи скана и связанные данные (хосты, порты, SNMP, LLDP, интерфейсы)? Это необратимо."
)
) {
return;
}
setStatus("Очистка данных сканов…");
try {
const res = await fetch("/api/admin/purge-scans", {
method: "POST",
headers: { "X-Nettopo-Reset-Key": secret },
});
const text = await res.text();
if (!res.ok) {
setStatus(`Ошибка очистки: ${res.status} ${text}`);
return;
}
scanIdInput.value = "";
fromScanIdInput.value = "";
toScanIdInput.value = "";
clearDeviceDetailPanel();
deviceRowsCache = [];
deviceTableBody.innerHTML =
'<tr><td colspan="5" style="padding:10px;color:#64748b;">Нет scan_id. Загрузите после нового скана.</td></tr>';
lastTopology = null;
lastDiff = null;
lastHighlight = null;
clearSVG();
edgeListPanel.style.display = "none";
diffBox.textContent = "";
await loadScanOptions();
setStatus("Данные сканов удалены. Создайте новый scan или выберите из списка.");
} catch (e) {
setStatus(`Ошибка: ${e.message}`);
}
});
watchScanBtn.addEventListener("click", watchScan);
scanSelect.addEventListener("change", () => {
if (scanSelect.value) scanIdInput.value = scanSelect.value;