v1.1.44: 5 fixes — null-guard + try/catch in toggleInstance/selectService/refreshInstances/CMDB

This commit is contained in:
2026-07-30 09:49:08 +04:00
parent b7f7bba22c
commit b5d139a04a
3 changed files with 29 additions and 4 deletions
+19
View File
@@ -0,0 +1,19 @@
# Sonnet 4.6 — Финальный аудит
Дата: 2026-07-30
## 5 багов в app.js
| # | Баг | Строка |
|---|-----|--------|
| 🔴1 | `d.operations.filter` без null-guard | toggleInstance |
| 🔴2 | toggleInstance без try/catch | toggleInstance |
| 🔴3 | selectService без try/catch | selectService |
| 🔴4 | refreshInstances без try/catch | refreshInstances |
| 🟡5 | CMDB OK + refreshInstances → ошибка перезаписывает успех | executeOp |
## redeploy без _send_params_terraform
Намеренно — redeploy переиспользует текущие params из state.
## Бэкенд чист
api_test.py, main.py, get_params.py, http_client.py — багов нет.
+1 -1
View File
@@ -18,7 +18,7 @@ from routes.api import bp as api_bp
from routes.api_test import bp as api_test_bp from routes.api_test import bp as api_test_bp
# Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI. # Версия — меняется при КАЖДОМ изменении кода. Показывается в топбаре UI.
VERSION = "1.1.43" VERSION = "1.1.44"
app = Flask(__name__, template_folder="templates", static_folder="static") app = Flask(__name__, template_folder="templates", static_folder="static")
app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-test.ngcloud.ru/api/v1/svc")
+9 -3
View File
@@ -46,6 +46,7 @@ async function selectService(svcId){
const svcEl=document.querySelector(`.svc-item[onclick*="${svcId}"]`); const svcEl=document.querySelector(`.svc-item[onclick*="${svcId}"]`);
if(svcEl) svcEl.classList.add('active'); if(svcEl) svcEl.classList.add('active');
try{
const r=await fetch('/api/operations/'+svcId); const r=await fetch('/api/operations/'+svcId);
const d=await r.json(); const d=await r.json();
svcInstances=d.instances||[]; svcInstances=d.instances||[];
@@ -67,6 +68,7 @@ async function selectService(svcId){
html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`; html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`;
}); });
document.getElementById('inst-list').innerHTML=html; document.getElementById('inst-list').innerHTML=html;
}catch(e){document.getElementById('inst-list').innerHTML='<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';}
} }
async function toggleInstance(iuid){ async function toggleInstance(iuid){
@@ -81,9 +83,10 @@ async function toggleInstance(iuid){
// Операции для ИНСТАНСА, не для выбранного сервиса // Операции для ИНСТАНСА, не для выбранного сервиса
const inst=svcInstances.find(x=>x.instanceUid===iuid); const inst=svcInstances.find(x=>x.instanceUid===iuid);
const svcId=inst?inst.serviceId:currentSvcId; const svcId=inst?inst.serviceId:currentSvcId;
try{
const r=await fetch('/api/operations/'+svcId); const r=await fetch('/api/operations/'+svcId);
const d=await r.json(); const d=await r.json();
let ops=d.operations.filter(o=>o.operation!=='reconcile'&&o.operation!=='create'); let ops=(d.operations||[]).filter(o=>o.operation!=='reconcile'&&o.operation!=='create');
if(inst&&(inst.status||inst.explainedStatus)==='not created'){ if(inst&&(inst.status||inst.explainedStatus)==='not created'){
ops=ops.filter(o=>o.operation==='delete'); ops=ops.filter(o=>o.operation==='delete');
} }
@@ -91,6 +94,7 @@ async function toggleInstance(iuid){
`<button class="btn btn-sm op-btn ${opClass(o.operation)}" onclick="runOp('${o.operation}',${o.svcOperationId})">${o.operation}</button>` `<button class="btn btn-sm op-btn ${opClass(o.operation)}" onclick="runOp('${o.operation}',${o.svcOperationId})">${o.operation}</button>`
).join(''); ).join('');
opsEl.classList.add('open'); opsEl.classList.add('open');
}catch(e){opsEl.innerHTML='<span style="color:var(--destructive);font-size:11px;">Ошибка загрузки</span>';opsEl.classList.add('open');}
} }
function opClass(opName){ function opClass(opName){
@@ -338,8 +342,8 @@ async function executeOp(params){
if(d.status==='OK'&&(d.opUid||'').startsWith('cmdb-')){ if(d.status==='OK'&&(d.opUid||'').startsWith('cmdb-')){
busy=false; busy=false;
setFinishedState('OK','badge-success','',0,d.displayName); setFinishedState('OK','badge-success','',0,d.displayName);
await refreshInstances(); try{await refreshInstances();}catch(e){}
if(historyOpen) await loadHistory(); if(historyOpen) try{await loadHistory();}catch(e){}
return; return;
} }
// start polling // start polling
@@ -390,6 +394,7 @@ function stopPoll(){
} }
async function refreshInstances(){ async function refreshInstances(){
try{
const r=await fetch('/api/operations/'+currentSvcId); const r=await fetch('/api/operations/'+currentSvcId);
const d=await r.json(); const d=await r.json();
const newInsts=d.instances||[]; const newInsts=d.instances||[];
@@ -406,6 +411,7 @@ async function refreshInstances(){
html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`; html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`;
}); });
document.getElementById('inst-list').innerHTML=html; document.getElementById('inst-list').innerHTML=html;
}catch(e){}
} }
// === История тестов === // === История тестов ===