Strip UI to org + instances only, hide test controls

This commit is contained in:
2026-07-23 17:57:59 +04:00
parent c6563b3716
commit 8e2387f91c
+5 -113
View File
@@ -5,17 +5,15 @@
<title>Autotest</title> <title>Autotest</title>
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" /> <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='favicon.svg') }}" />
<link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" /> <link rel="stylesheet" href="{{ url_for('static', filename='style.css') }}" />
<script src="https://unpkg.com/lucide@latest"></script>
<style> <style>
.layout { display:flex; gap:16px; max-width:1200px; margin:16px auto; padding:0 16px; } .layout { display:flex; gap:16px; max-width:900px; margin:16px auto; padding:0 16px; }
.left { width:380px; flex-shrink:0; } .left { width:380px; flex-shrink:0; }
.right { flex:1; min-width:0; } .right { flex:1; min-width:0; display:flex; align-items:center; justify-content:center; color:var(--muted); }
.svc-row { cursor:pointer; }
</style> </style>
</head> </head>
<body> <body>
<form method="post" style="max-width:1200px;margin:16px auto 0;padding:0 16px;display:flex;align-items:center;gap:8px;"> <form method="post" style="max-width:900px;margin:16px auto 0;padding:0 16px;display:flex;align-items:center;gap:8px;">
<input type="password" name="token" placeholder="env: {{ env_token_masked }}" value="{{ '' if not has_user_token else '••••••••' }}" style="height:28px;width:200px;font-size:12px;border:1px solid var(--brand-gray);border-radius:4px;padding:0 6px;" /> <input type="password" name="token" placeholder="env: {{ env_token_masked }}" value="{{ '' if not has_user_token else '••••••••' }}" style="height:28px;width:200px;font-size:12px;border:1px solid var(--brand-gray);border-radius:4px;padding:0 6px;" />
<button type="submit" name="action" value="save" class="btn" style="height:28px;font-size:12px;padding:0 8px;">OK</button> <button type="submit" name="action" value="save" class="btn" style="height:28px;font-size:12px;padding:0 8px;">OK</button>
{% if has_user_token %} {% if has_user_token %}
@@ -41,7 +39,7 @@
{% if instances %} {% if instances %}
<div class="card" style="margin-top:8px;"> <div class="card" style="margin-top:8px;">
<div class="card-header" style="cursor:pointer;" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'block':'none'">Инстансы ({{ instances|length }})</div> <div class="card-header" style="cursor:pointer;" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'block':'none'">Инстансы ({{ instances|length }})</div>
<div class="card-body" style="padding:0;max-height:300px;overflow-y:auto;"> <div class="card-body" style="padding:0;max-height:60vh;overflow-y:auto;">
<table> <table>
{% for i in instances %} {% for i in instances %}
<tr><td style="font-size:12px;">{{ i.displayName }}</td><td style="font-size:11px;color:var(--muted);">{{ i.svc }}</td><td><span class="badge badge-success" style="font-size:10px;">{{ i.explainedStatus or "?" }}</span></td></tr> <tr><td style="font-size:12px;">{{ i.displayName }}</td><td style="font-size:11px;color:var(--muted);">{{ i.svc }}</td><td><span class="badge badge-success" style="font-size:10px;">{{ i.explainedStatus or "?" }}</span></td></tr>
@@ -51,116 +49,10 @@
</div> </div>
{% endif %} {% endif %}
</div> </div>
<div class="right"> <div class="right">
<div style="display:flex;gap:8px;margin-bottom:8px;"> <p>Скоро здесь будут тесты.</p>
<button class="btn btn-primary" onclick="runTests()" id="btn-run" style="height:36px;"><i data-lucide="play" style="width:14px;height:14px;"></i> Запустить тесты</button>
<button class="btn" onclick="saveConfig()" style="height:36px;"><i data-lucide="save" style="width:14px;height:14px;"></i> Сохранить конфиг</button>
</div>
{% if services %}
<div class="card">
<div class="card-header" style="cursor:pointer;" onclick="document.getElementById('svc-table').style.display=document.getElementById('svc-table').style.display==='none'?'':'none'">Сервисы ({{ services|length }})</div>
<div id="svc-table">
<div class="card-body" style="padding:0;max-height:45vh;overflow-y:auto;">
<table>
<thead><tr><th style="width:30px;"></th><th style="width:50px;">#</th><th>Сервис</th><th style="width:70px;">Опер.</th></tr></thead>
<tbody>
{% for s in services %}
<tr class="svc-row" data-svc-id="{{ s.svcId }}">
<td><input type="checkbox" class="svc-enabled" data-svc-id="{{ s.svcId }}"></td>
<td>{{ s.svcId }}</td>
<td style="font-size:12px;">{{ s.svc }}{% if s.svcExtendedName %} <span style="color:var(--muted);">— {{ s.svcExtendedName }}</span>{% endif %}</td>
<td style="text-align:center;"><span class="ops-count" id="ops-{{ s.svcId }}">?</span></td>
</tr>
<tr class="ops-detail" id="detail-{{ s.svcId }}" style="display:none;"><td colspan="4" style="padding:4px 10px;background:var(--brand-grey-light);"></td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
{% endif %}
<div class="card" id="results-card" style="margin-top:8px;display:none;">
<div class="card-header">Результаты <span id="results-status"></span></div>
<div class="card-body" style="padding:0;">
<table><thead><tr><th>Сервис</th><th>Операция</th><th style="width:70px;">Статус</th><th style="width:60px;">Время</th></tr></thead><tbody id="results-body"></tbody></table>
</div>
</div>
<div class="card" style="margin-top:16px;">
<div class="card-header" style="cursor:pointer;" onclick="this.nextElementSibling.style.display=this.nextElementSibling.style.display==='none'?'block':'none'">❓ Как это работает</div>
<div class="card-body" style="font-size:12px;color:var(--muted);line-height:1.6;">
<b>1. Выберите сервисы</b> — раскройте таблицу «Сервисы» и отметьте галочками те, которые хотите протестировать.<br>
<b>2. Сохраните конфиг</b> — нажмите «Сохранить конфиг». Настройки сохранятся в файл на сервере.<br>
<b>3. Запустите тесты</b> — нажмите «Запустить тесты». Для каждого отмеченного сервиса будут выполнены операции в порядке:<br>
&nbsp;&nbsp;&nbsp;<span class="badge" style="font-size:10px;">create</span> → создать тестовый инстанс<br>
&nbsp;&nbsp;&nbsp;<span class="badge" style="font-size:10px;">modify</span> → изменить параметры<br>
&nbsp;&nbsp;&nbsp;<span class="badge" style="font-size:10px;">delete</span> → удалить инстанс<br>
<b>4. Смотрите результаты</b> — статус каждой операции: <span class="badge badge-success">OK</span> успех, <span class="badge" style="background:#fecaca;color:#991b1b;">FAIL</span> ошибка, <span class="badge" style="background:var(--brand-grey-light);color:var(--muted);">SKIP</span> пропущена.<br>
<br>
<b>⚠️ Важно:</b> тесты создают реальные инстансы в организации. Убедитесь что операция <span class="badge" style="font-size:10px;">delete</span> включена для очистки. Сервисы с отложенным удалением (PostgreSQL и др.) — включайте удаление с осторожностью.<br>
<b>🔧 Настройка вручную:</b> файл <code>site/config.yaml</code> в репозитории. Можно задать <code>test_org_uid</code> конкретной организации или <code>"auto"</code> — найти автоматически.
</div>
</div>
</div> </div>
</div> </div>
<script>
const opsCache={},STATUS={OK:'<span class="badge badge-success">OK</span>',FAIL:'<span class="badge" style="background:#fecaca;color:#991b1b;">FAIL</span>',SKIP:'<span class="badge" style="background:var(--brand-grey-light);color:var(--muted);">SKIP</span>',RUNNING:'<span class="badge" style="background:#dbeafe;color:#1e40af;">⏳</span>',TIMEOUT:'<span class="badge" style="background:#fecaca;color:#991b1b;">TIMEOUT</span>'};
let pollTimer=null,configDirty=false;
(async function(){
try{const r=await fetch('/api/config');const d=await r.json();
(d.services||[]).forEach(s=>{const cb=document.querySelector('.svc-enabled[data-svc-id="'+s.service_id+'"]');if(cb)cb.checked=s.enabled;});}
catch(e){}
})();
function saveConfig(){
const cfg={test_org_uid:"auto",services:[]};
document.querySelectorAll('.svc-row').forEach(row=>{
const svcId=parseInt(row.dataset.svcId);
const name=row.cells[2].textContent.split(' — ')[0].trim();
cfg.services.push({service_id:svcId,name:name,display_name:"autotest-"+name,enabled:row.querySelector('.svc-enabled').checked,operations:[]});
});
fetch('/api/config',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(cfg)}).then(()=>{configDirty=false;}).catch(e=>alert('Ошибка сохранения'));
}
function runTests(){
if(configDirty){alert('Сначала сохраните конфиг');return;}
document.getElementById('btn-run').disabled=true;
document.getElementById('results-card').style.display='block';
document.getElementById('results-status').textContent=' запуск...';
fetch('/api/run',{method:'POST'}).then(()=>{pollTimer=setInterval(pollStatus,2000);}).catch(()=>{document.getElementById('btn-run').disabled=false;});
}
async function pollStatus(){
try{const r=await fetch('/api/status');const d=await r.json();if(!d.results)return;
document.getElementById('results-body').innerHTML=d.results.map(x=>'<tr><td style="font-size:12px;">'+x.service_name+'</td><td style="font-size:12px;">'+x.operation+'</td><td>'+STATUS[x.status]+(x.error?'<br><span style="font-size:10px;color:var(--destructive);">'+x.error+'</span>':'')+'</td><td style="font-size:12px;">'+(x.duration_sec||'')+'s</td></tr>').join('');
document.getElementById('results-status').textContent=d.status==='done'?' ✓ завершён':' выполняется...';
if(d.status==='done'){clearInterval(pollTimer);document.getElementById('btn-run').disabled=false;}}
catch(e){}
}
document.querySelectorAll('.svc-row').forEach(row=>{row.addEventListener('click',async e=>{
if(e.target.tagName==='INPUT'){configDirty=true;return;}
const svcId=row.dataset.svcId,detail=document.getElementById('detail-'+svcId);
if(detail.style.display==='table-row'){detail.style.display='none';row.style.background='';return;}
document.querySelectorAll('.ops-detail').forEach(d=>d.style.display='none');
document.querySelectorAll('.svc-row').forEach(r=>r.style.background='');
row.style.background='var(--brand-grey-light)';detail.style.display='table-row';
if(opsCache[svcId]){fillDetail(svcId,opsCache[svcId]);return;}
detail.cells[0].innerHTML='<span style="color:var(--muted);font-size:12px;">Загрузка...</span>';
try{const r=await fetch('/api/operations/'+svcId);const d=await r.json();opsCache[svcId]=d;fillDetail(svcId,d);}
catch(e){detail.cells[0].innerHTML='<span style="color:var(--destructive);font-size:12px;">Ошибка</span>';}
});});
function fillDetail(svcId,data){
const d=document.getElementById('detail-'+svcId);
if(data.error){d.cells[0].innerHTML='<span style="color:var(--destructive);font-size:12px;">'+data.error+'</span>';return;}
const ops=data.operations||[];
document.getElementById('ops-'+svcId).textContent=ops.length;
d.cells[0].innerHTML=ops.map(o=>'<span class="badge" style="margin:2px;font-size:11px;">'+o.operation+'</span>').join('')||'<span style="color:var(--muted);font-size:12px;">Нет операций</span>';
}
</script>
</body> </body>
</html> </html>