From c9654c31f32ffd353224878629d16ae3480230f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9CNaeel=E2=80=9D?= Date: Fri, 24 Jul 2026 07:37:19 +0400 Subject: [PATCH] =?UTF-8?q?Three-column=20cascade=20UI:=20services=20?= =?UTF-8?q?=E2=86=92=20operations=20=E2=86=92=20params=20=E2=86=92=20test,?= =?UTF-8?q?=20v1.1.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- site/app.py | 4 +- site/routes/api_test.py | 146 ++++++++++++++++++++++++++++++ site/templates/index.html | 183 +++++++++++++++++++++++++++++++++----- 3 files changed, 311 insertions(+), 22 deletions(-) create mode 100644 site/routes/api_test.py diff --git a/site/app.py b/site/app.py index 1f445ff..0453e55 100644 --- a/site/app.py +++ b/site/app.py @@ -4,8 +4,9 @@ from flask import Flask 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.5" +VERSION = "1.1.0" 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") @@ -13,6 +14,7 @@ app.config["NUBES_API_TOKEN"] = os.getenv("NUBES_API_TOKEN", "") app.config["VERSION"] = VERSION app.register_blueprint(main_bp) app.register_blueprint(api_bp) +app.register_blueprint(api_test_bp) @app.route("/health") diff --git a/site/routes/api_test.py b/site/routes/api_test.py new file mode 100644 index 0000000..f9d8682 --- /dev/null +++ b/site/routes/api_test.py @@ -0,0 +1,146 @@ +from flask import Blueprint, current_app, jsonify, request + +from api.http_client import HttpClient +from operations.get_services import get_services, get_service_detail +from operations.get_instances import get_instances + +bp = Blueprint("api_test", __name__) + + +def _client(): + token = request.cookies.get("token") or current_app.config["NUBES_API_TOKEN"] + return HttpClient(current_app.config["NUBES_API_ENDPOINT"], token) + + +@bp.route("/api/services") +def api_services(): + try: + raw = get_services(_client()) + svc_list = [{"svcId": s["svcId"], "svc": s["svc"], "svcExtendedName": s.get("svcExtendedName", "")} for s in raw] + svc_list.sort(key=lambda s: s["svcId"]) + return jsonify(svc_list) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@bp.route("/api/instances/list") +def api_instances_list(): + try: + raw = get_instances(_client()) + inst = [{"instanceUid": i["instanceUid"], "displayName": i["displayName"], "serviceId": i["serviceId"], "svc": i["svc"], "explainedStatus": i.get("explainedStatus", "?")} for i in raw] + return jsonify(inst) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@bp.route("/api/operations/") +def api_operations(svc_id): + try: + detail = get_service_detail(_client(), svc_id) + ops = detail.get("operations", []) + instances = get_instances(_client()) + svc_instances = [i for i in instances if i.get("serviceId") == svc_id and i.get("explainedStatus") not in ("deleted", "not created")] + return jsonify({ + "svc": detail.get("svc", ""), + "operations": [{"svcOperationId": o["svcOperationId"], "operation": o["operation"]} for o in ops], + "instances": svc_instances, + }) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@bp.route("/api/params/") +def api_params(op_id): + try: + data = _client().get(f"/instanceOperations/default/{op_id}") + params = data["svcOperation"]["cfsParams"] + result = [] + for p in params: + result.append({ + "svcOperationCfsParamId": p["svcOperationCfsParamId"], + "name": p.get("svcOperationCfsParam", ""), + "dataType": p.get("dataType", ""), + "isRequired": p.get("isRequired", False), + "defaultValue": p.get("defaultValue"), + "valueList": p.get("valueList"), + "dataDescriptor": {k: {"dataType": v.get("dataType",""), "valueList": v.get("valueList",""), "isRequired": v.get("isRequired", False)} for k, v in p.get("dataDescriptor", {}).items()} if p.get("dataDescriptor") else None, + }) + return jsonify(result) + except Exception as e: + return jsonify({"error": str(e)}), 500 + + +@bp.route("/api/test", methods=["POST"]) +def api_test(): + """Запустить одну операцию.""" + import threading, time + + data = request.get_json() + svc_id = data["serviceId"] + op_name = data["operation"] + svc_op_id = data["svcOperationId"] + params = data.get("params", {}) + instance_uid = data.get("instanceUid") + display_name = data.get("displayName", f"autotest-{svc_id}") + + client = _client() + + result = {"status": "RUNNING", "error": None, "instanceUid": None, "duration": 0} + t0 = time.time() + + try: + if op_name == "create": + payload = {"serviceId": svc_id, "displayName": display_name} + if params: + payload["cfsParams"] = [{"svcOperationCfsParamId": int(k), "paramValue": str(v)} for k, v in params.items()] + resp = client.post("/instances", payload) + instance_uid = resp.get("instanceUid") or _find_uid(resp) + + # wait + deadline = time.time() + 300 + while time.time() < deadline: + insts = get_instances(client) + for i in insts: + if i.get("instanceUid") == instance_uid: + if not i.get("operationIsInProgress") and i.get("explainedStatus") not in ("creating",): + result["status"] = "OK" + result["instanceUid"] = instance_uid + result["duration"] = round(time.time() - t0, 1) + return jsonify(result) + time.sleep(5) + result["status"] = "TIMEOUT" + else: + if not instance_uid: + return jsonify({"status": "FAIL", "error": "Нет instanceUid"}), 400 + payload = {"instanceUid": instance_uid, "svcOperationId": svc_op_id} + if params: + payload["cfsParams"] = [{"svcOperationCfsParamId": int(k), "paramValue": str(v)} for k, v in params.items()] + client.post("/instanceOperations", payload) + + deadline = time.time() + 300 + while time.time() < deadline: + insts = get_instances(client) + for i in insts: + if i.get("instanceUid") == instance_uid: + if not i.get("operationIsInProgress"): + result["status"] = "OK" + result["instanceUid"] = instance_uid + result["duration"] = round(time.time() - t0, 1) + return jsonify(result) + time.sleep(5) + result["status"] = "TIMEOUT" + except Exception as e: + result["status"] = "FAIL" + result["error"] = str(e) + + result["instanceUid"] = instance_uid + result["duration"] = round(time.time() - t0, 1) + return jsonify(result) + + +def _find_uid(resp): + if isinstance(resp, dict): + for v in resp.values(): + if isinstance(v, str) and len(v) == 36 and v.count("-") == 4: + return v + return None diff --git a/site/templates/index.html b/site/templates/index.html index 64e5dcd..0683038 100644 --- a/site/templates/index.html +++ b/site/templates/index.html @@ -6,54 +6,195 @@ -
- Nubes - v{{ config.VERSION if config else '?' }} +
+ Nubes + v{{ config.VERSION }} {% if token_info.email %}{{ token_info.email }} | {{ token_info.company }} | test{% endif %}
- - - {% if has_user_token %} - - {% endif %} + + + {% if has_user_token %}{% endif %} +
{% if error %}{{ error }}{% endif %} - +
-
+ +
{% if organization %}
Организация
-
- {{ organization.displayName }}{% if client_id %} ({{ client_id }}){% endif %} — {{ organization.explainedStatus or "OK" }} +
+ {{ organization.displayName }}{% if client_id %} ({{ client_id }}){% endif %}
+ {{ organization.explainedStatus or "OK" }}
{% endif %} {% for svc_name, items in instance_groups.items() %} -
-
{{ svc_name }} ({{ items|length }})
-
+
+
{{ svc_name }} ({{ items|length }})
+
{% for i in items %} - + {% endfor %}
{{ i.displayName }}{{ i.explainedStatus or "?" }}
{{ i.displayName }}{{ i.explainedStatus or '?' }}
{% endfor %}
-
-

Скоро здесь будут тесты.

+ + +
+
+
Сервисы
+
+ Загрузка... +
+
+
+ + +
+ +
+ \ No newline at end of file