diff --git a/DOCS/ARCHITECTURE.md b/DOCS/ARCHITECTURE.md index f52d016..7efbfbc 100644 --- a/DOCS/ARCHITECTURE.md +++ b/DOCS/ARCHITECTURE.md @@ -1,6 +1,6 @@ # Архитектура app-autotest — полный документ -v1.0.62, 27.07.2026 +v1.0.63, 27.07.2026 --- @@ -350,6 +350,7 @@ def detect_endpoint(token): | 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()` | +| 20 | v1.0.63 | Дубли и чужие инстансы | Принадлежность определялась не по namespace | `autotest-` prefix + уникализация `displayName` при CREATE | ### ✅ Закрыто в v1.0.56 diff --git a/DOCS/HISTORY.md b/DOCS/HISTORY.md index a866c92..cd0beea 100644 --- a/DOCS/HISTORY.md +++ b/DOCS/HISTORY.md @@ -1,5 +1,18 @@ # История разработки app-autotest +## v1.0.63 (27.07.2026) — autotest namespace for instances + +### Что изменилось +- В `site/templates/index.html` CREATE теперь генерирует `displayName` с префиксом `autotest-` и проверяет его на уникальность среди текущих инстансов. +- В `site/routes/api_test.py` backend тоже нормализует `displayName` с этим префиксом и, если нужно, добавляет суффикс для уникальности перед созданием в облаке. +- В `site/routes/main.py` список инстансов фильтруется по namespace `autotest-`, чтобы в UI попадали только тестовые инстансы приложения. +- В `DOCS/ARCHITECTURE.md` зафиксирован переход на namespace-based фильтрацию. + +### Что это чинит +- Дубли `displayName` при CREATE. +- Чужие инстансы в списке. +- Зависимость от локального трекера как от основного признака принадлежности. + ## v1.0.62 (27.07.2026) — immediate visual refresh after CREATE ### Что изменилось diff --git a/site/app.py b/site/app.py index 033801e..7e76a32 100644 --- a/site/app.py +++ b/site/app.py @@ -6,7 +6,7 @@ from routes.main import bp as main_bp from routes.api import bp as api_bp from routes.api_test import bp as api_test_bp -VERSION = "1.0.62" +VERSION = "1.0.63" 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") diff --git a/site/routes/api_test.py b/site/routes/api_test.py index c0822d7..5a9599c 100644 --- a/site/routes/api_test.py +++ b/site/routes/api_test.py @@ -1,4 +1,5 @@ from flask import Blueprint, current_app, jsonify, request +import uuid from api.http_client import HttpClient, detect_endpoint from operations.get_services import get_services, get_service_detail @@ -8,6 +9,33 @@ from operations.tracker import remove as tracker_remove bp = Blueprint("api_test", __name__) +AUTOTEST_PREFIX = "autotest-" + + +def _with_prefix(name): + name = (name or "").strip() or "instance" + return name if name.startswith(AUTOTEST_PREFIX) else f"{AUTOTEST_PREFIX}{name}" + + +def _unique_display_name(client, requested_name): + base_name = _with_prefix(requested_name) + try: + existing = { + i.get("displayName", "") + for i in get_instances(client) + if str(i.get("displayName", "")).startswith(AUTOTEST_PREFIX) + } + except Exception: + existing = set() + + if base_name not in existing: + return base_name + + while True: + candidate = f"{base_name}-{uuid.uuid4().hex[:6]}" + if candidate not in existing: + return candidate + def _client(): token = request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"] @@ -107,6 +135,7 @@ def api_test(): try: if op_name == "create": + display_name = _unique_display_name(client, display_name) payload = {"serviceId": svc_id, "displayName": display_name, "descr": ""} resp = client.post("/instances", payload) instance_uid = resp.get("instanceUid") or _find_uid(resp) or _uid_from_location(resp.get("_location", "")) @@ -135,7 +164,7 @@ def api_test(): # фоном ждать завершения threading.Thread(target=_finish_op, args=(client, op_uid, instance_uid, svc_id, display_name, op_name, svc_op_id, True), daemon=True).start() - return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid}) + return jsonify({"status": "RUNNING", "opUid": op_uid, "instanceUid": instance_uid, "displayName": display_name}) else: if not instance_uid: diff --git a/site/routes/main.py b/site/routes/main.py index 460f733..ca52096 100644 --- a/site/routes/main.py +++ b/site/routes/main.py @@ -7,6 +7,7 @@ from operations.tracker import list_all as tracker_list from runner import load_config bp = Blueprint("main", __name__) +AUTOTEST_PREFIX = "autotest-" def _mask(s): @@ -143,6 +144,7 @@ def api_operations(svc_id): nubes_uids = {i["instanceUid"] for i in instances} svc_instances = [i for i in instances if i.get("instanceUid") in tracked_uids + and str(i.get("displayName", "")).startswith(AUTOTEST_PREFIX) and i.get("explainedStatus") not in ("deleted",)] # Если Nubes уже не отдаёт tracked-инстанс после reload, добавляем его из трекера. for uid, t in tracked_by_uid.items(): diff --git a/site/templates/index.html b/site/templates/index.html index fd28f8f..298bc45 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -99,6 +99,7 @@