v1.0.62: immediate visual refresh after CREATE

This commit is contained in:
2026-07-27 11:19:26 +04:00
parent b0844114ba
commit 8531d5e15d
5 changed files with 78 additions and 28 deletions
+3 -1
View File
@@ -1,6 +1,6 @@
# Архитектура app-autotest — полный документ # Архитектура app-autotest — полный документ
v1.0.58, 27.07.2026 v1.0.62, 27.07.2026
--- ---
@@ -348,6 +348,8 @@ def detect_endpoint(token):
| 15 | v1.0.55 | F5 = предупреждение браузера | clear/stray POST → HTML без редиректа | `redirect("/")` для всех POST | | 15 | v1.0.55 | F5 = предупреждение браузера | clear/stray POST → HTML без редиректа | `redirect("/")` для всех POST |
| 16 | v1.0.56 | Кнопка «Готово» запускала повторный CREATE | `btn.onclick` оставался привязанным после завершения операции | `btn.onclick = null` после финального статуса | | 16 | v1.0.56 | Кнопка «Готово» запускала повторный CREATE | `btn.onclick` оставался привязанным после завершения операции | `btn.onclick = null` после финального статуса |
| 17 | v1.0.57 | Хрупкость финального состояния кнопки | Повторяемый код финализации статуса | `setFinishedState()` централизует финальный UI-состояние | | 17 | v1.0.57 | Хрупкость финального состояния кнопки | Повторяемый код финализации статуса | `setFinishedState()` централизует финальный UI-состояние |
| 18 | v1.0.59 | После F5 список терял tracked-инстанс | `api_operations()` не добирал tracked-сирот обратно из трекера | `tracked_by_uid` + добавление отсутствующих tracked-инстансов |
| 19 | v1.0.60 | CREATE не появлялся сразу в списке после OK | UI делал diff и мог не перерисовать список целиком | Полная перерисовка `inst-list` в `refreshInstances()` |
### ✅ Закрыто в v1.0.56 ### ✅ Закрыто в v1.0.56
+41
View File
@@ -1,5 +1,46 @@
# История разработки app-autotest # История разработки app-autotest
## v1.0.62 (27.07.2026) — immediate visual refresh after CREATE
### Что изменилось
- В `site/templates/index.html` после завершения CREATE список инстансов перерисовывается полностью.
- `refreshInstances()` больше не зависит от DOM-diff и сразу ставит новый инстанс в список.
- `DOCS/ARCHITECTURE.md` и `site/app.py` синхронизированы на `1.0.62`.
### Что это чинит
- После появления кнопки «Готово» созданный инстанс должен сразу быть виден в списке.
## v1.0.61 (27.07.2026) — immediate list rebuild after CREATE
### Что изменилось
- В `site/templates/index.html` `refreshInstances()` теперь всегда полностью перерисовывает список инстансов после завершения операции.
- После `OK` новый инстанс должен появляться в списке сразу, без зависимости от DOM-diff и без перезагрузки страницы.
- В `DOCS/ARCHITECTURE.md` и `site/app.py` синхронизирована версия `1.0.61`.
### Что это чинит
- Убрана ситуация, когда после CREATE статус уже `OK`, но в UI инстанс ещё не виден.
## v1.0.60 (27.07.2026) — full list rerender after CREATE
### Что изменилось
- В `site/templates/index.html` `refreshInstances()` теперь полностью перерисовывает список инстансов, а не пытается вставить только diff.
- После завершения CREATE новый инстанс должен появляться в списке сразу после `OK`.
- В `DOCS/ARCHITECTURE.md` добавлен отдельный шаг про полную перерисовку списка.
### Что это чинит
- Снята зависимость от частичного DOM-diff-а, из-за которого созданный инстанс мог не появляться сразу.
## v1.0.59 (27.07.2026) — restore tracked instance after reload
### Что изменилось
- В `site/routes/main.py` tracked-инстансы, которых Nubes уже не отдает в ответе после reload, снова добавляются в список из `/tmp/instances.json`.
- В `site/templates/index.html` статус CREATE теперь показывает имя инстанса, а не только `create`.
- В `DOCS/ARCHITECTURE.md` зафиксирован новый шаг восстановления списка после F5.
### Что это чинит
- После F5 инстанс не пропадает из UI, если он есть в локальном трекере.
- В статусе выполнения видно, по какому displayName идет CREATE.
## v1.0.58 (27.07.2026) — version bump ## v1.0.58 (27.07.2026) — version bump
- Поднята версия проекта до `1.0.58`. - Поднята версия проекта до `1.0.58`.
+1 -1
View File
@@ -6,7 +6,7 @@ from routes.main import bp as main_bp
from routes.api import bp as api_bp 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
VERSION = "1.0.58" VERSION = "1.0.62"
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-dev.ngcloud.ru/api/v1/svc") app.config["NUBES_API_ENDPOINT"] = os.getenv("NUBES_API_ENDPOINT", "https://lk-api-gateway-dev.ngcloud.ru/api/v1/svc")
+14 -2
View File
@@ -137,11 +137,23 @@ def api_operations(svc_id):
ops = detail.get("operations", []) ops = detail.get("operations", [])
# только отслеживаемые инстансы этого сервиса # только отслеживаемые инстансы этого сервиса
tracked = tracker_list() tracked = tracker_list()
tracked_uids = {t["instanceUid"] for t in tracked if t["svcId"] == svc_id} tracked_by_uid = {t["instanceUid"]: t for t in tracked if t["svcId"] == svc_id}
tracked_uids = set(tracked_by_uid.keys())
instances = get_instances(client) instances = get_instances(client)
nubes_uids = {i["instanceUid"] for i in instances}
svc_instances = [i for i in instances svc_instances = [i for i in instances
if i.get("instanceUid") in tracked_uids if i.get("instanceUid") in tracked_uids
and i.get("explainedStatus") not in ("deleted", "not created")] and i.get("explainedStatus") not in ("deleted",)]
# Если Nubes уже не отдаёт tracked-инстанс после reload, добавляем его из трекера.
for uid, t in tracked_by_uid.items():
if uid not in nubes_uids:
svc_instances.append({
"instanceUid": uid,
"displayName": t["displayName"],
"serviceId": svc_id,
"svc": detail.get("svc", ""),
"explainedStatus": "creating",
})
return jsonify({"svc": detail.get("svc", ""), "operations": ops, "instances": svc_instances}) return jsonify({"svc": detail.get("svc", ""), "operations": ops, "instances": svc_instances})
except Exception as e: except Exception as e:
return jsonify({"error": str(e)}), 500 return jsonify({"error": str(e)}), 500
+13 -18
View File
@@ -219,6 +219,13 @@ function setFinishedState(statusText, statusClass, statusError, statusDuration){
document.getElementById('test-status').innerHTML=` <span class="badge ${statusClass}">${statusText}</span> ${statusError||''} ${statusDuration||''}s`; document.getElementById('test-status').innerHTML=` <span class="badge ${statusClass}">${statusText}</span> ${statusError||''} ${statusDuration||''}s`;
} }
function setRunningState(opName, displayName){
const btn=document.getElementById('btn-test');
const title=displayName || opName;
btn.textContent='Готово';
document.getElementById('test-status').textContent=' ⏳ '+title+' выполняется...';
}
async function executeOp(params){ async function executeOp(params){
stopPoll(); stopPoll();
// Захватить displayName ДО очистки формы // Захватить displayName ДО очистки формы
@@ -229,7 +236,7 @@ async function executeOp(params){
btn.style.display='inline-flex'; btn.style.display='inline-flex';
btn.textContent=selectedOp.opName+'...'; btn.textContent=selectedOp.opName+'...';
btn.disabled=true; btn.disabled=true;
document.getElementById('test-status').textContent=' ⏳ '+selectedOp.opName+' выполняется...'; setRunningState(selectedOp.opName, displayName);
document.getElementById('stages-box').style.display='block'; document.getElementById('stages-box').style.display='block';
document.getElementById('stages-box').innerHTML=''; document.getElementById('stages-box').innerHTML='';
@@ -283,23 +290,9 @@ async function refreshInstances(){
const r=await fetch('/api/operations/'+SVC_ID); const r=await fetch('/api/operations/'+SVC_ID);
const d=await r.json(); const d=await r.json();
const newInsts=d.instances||[]; const newInsts=d.instances||[];
const oldUids=new Set(svcInstances.map(i=>i.instanceUid));
const added=newInsts.filter(i=>!oldUids.has(i.instanceUid));
svcInstances=newInsts; svcInstances=newInsts;
// обновить бейджи существующих
svcInstances.forEach(i=>{
const el=document.querySelector(`[data-iuid="${i.instanceUid}"] .badge`);
if(el){
el.textContent=i.explainedStatus||'?';
el.className='badge '+(i.explainedStatus==='running'?'badge-success':'');
}
});
// добавить новые инстансы в список (перед кнопкой "+ Создать")
if(added.length){
const list=document.getElementById('inst-list');
const addBtn=list.querySelector('[onclick="startCreate()"]');
let html=''; let html='';
added.forEach(i=>{ svcInstances.forEach(i=>{
const sc=i.explainedStatus==='running'?'badge-success':''; const sc=i.explainedStatus==='running'?'badge-success':'';
html+=`<div class="inst-item" onclick="toggleInstance('${i.instanceUid}')" data-iuid="${i.instanceUid}"> html+=`<div class="inst-item" onclick="toggleInstance('${i.instanceUid}')" data-iuid="${i.instanceUid}">
<span style="flex:1;">${i.displayName}</span> <span style="flex:1;">${i.displayName}</span>
@@ -307,8 +300,10 @@ async function refreshInstances(){
</div>`; </div>`;
html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`; html+=`<div class="inst-ops" id="ops-${i.instanceUid}"></div>`;
}); });
if(addBtn){addBtn.insertAdjacentHTML('beforebegin',html);} html+=`<div class="inst-item" style="margin-top:8px;border-top:1px solid var(--brand-gray);padding-top:8px;" onclick="startCreate()">
} <span style="flex:1;color:var(--brand-primary);">+ Создать новый инстанс</span>
</div>`;
document.getElementById('inst-list').innerHTML=html;
} }
</script> </script>
</body> </body>