From 11eb3d3c45004722bce24fc535b2e11ce395dec0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BD=D0=B4=D1=80=D0=B5=D0=B9=20=D0=91=D0=BE=D0=B1?= =?UTF-8?q?=D1=8B=D1=80=D0=B5=D0=B2?= Date: Fri, 29 May 2026 19:38:43 +0300 Subject: [PATCH] fix(keenetic): safe delete URLs and not-found handling Encode router names in Keenetic API calls and return an error when delete targets a missing device. Co-authored-by: Cursor --- server/api/keenetic.py | 2 ++ server/static/js/app.js | 15 ++++++++++----- server/templates/dashboard.html | 2 +- 3 files changed, 13 insertions(+), 6 deletions(-) diff --git a/server/api/keenetic.py b/server/api/keenetic.py index a710065..bbe2a5b 100644 --- a/server/api/keenetic.py +++ b/server/api/keenetic.py @@ -320,6 +320,8 @@ async def keenetic_update(name: str, request: Request, user: str = Depends(requi @router.delete("/{name}") async def keenetic_delete(name: str, request: Request, user: str = Depends(require_auth)): devices = _load_keenetic() + if not any(d["name"] == name for d in devices): + return {"status": "error", "detail": "router not found"} devices = [d for d in devices if d["name"] != name] _save_keenetic(devices) keenetic_metrics.pop(name, None) diff --git a/server/static/js/app.js b/server/static/js/app.js index e581352..c826aaf 100644 --- a/server/static/js/app.js +++ b/server/static/js/app.js @@ -1378,7 +1378,7 @@ async function showKeeneticDetail(name) { // Fetch clients + interfaces lazily try { - const resp = await fetch(`/api/keenetic/detail/${name}`, {credentials: 'include'}); + const resp = await fetch(`/api/keenetic/detail/${encodeURIComponent(name)}`, {credentials: 'include'}); const data = await resp.json(); const extra = document.getElementById('keeneticDetailExtra'); if (!extra) return; @@ -1558,7 +1558,7 @@ async function refreshKeenetic(name) { const card = document.querySelector(`[data-keen="${name}"]`); if (card) card.classList.add('loading'); try { - const resp = await fetch(`/api/keenetic/refresh/${name}`, { + const resp = await fetch(`/api/keenetic/refresh/${encodeURIComponent(name)}`, { method: 'POST', credentials: 'include', }); const resp2 = await fetch('/api/keenetic/list', {credentials: 'include'}); @@ -1593,7 +1593,7 @@ async function refreshAllKeenetic() { async function rebootKeenetic(name) { if (!confirm(`Перезагрузить роутер "${name}"?`)) return; try { - const resp = await fetch(`/api/keenetic/reboot/${name}`, { + const resp = await fetch(`/api/keenetic/reboot/${encodeURIComponent(name)}`, { method: 'POST', credentials: 'include', }); const data = await resp.json(); @@ -1682,11 +1682,16 @@ async function saveKeeneticEdit(oldName) { } async function deleteKeenetic(name) { - if (!confirm(`Удалить роутер "${name}"?`)) return; + if (!confirm(`Удалить роутер "${name}" из мониторинга?`)) return; try { - await fetch(`/api/keenetic/${name}`, { + const resp = await fetch(`/api/keenetic/${encodeURIComponent(name)}`, { method: 'DELETE', credentials: 'include', }); + const data = await resp.json(); + if (!resp.ok || data.status === 'error') { + alert(`❌ Не удалось удалить: ${data.detail || resp.statusText}`); + return; + } await loadKeenetic(); } catch (e) { alert('Error: ' + e.message); } } diff --git a/server/templates/dashboard.html b/server/templates/dashboard.html index 2f1abf4..c4a3011 100644 --- a/server/templates/dashboard.html +++ b/server/templates/dashboard.html @@ -601,6 +601,6 @@ loftliliana https://loftliliana.netcraze.pro 1020687391" style="width:100%;font- - +