""" Операция: получить параметры операции с ТЕКУЩИМИ значениями инстанса. Зачем: когда пользователь выбирает modify/suspend/resume, мы показываем ТЕКУЩИЕ значения параметров (из state.params инстанса), а не шаблонные defaults. Это позволяет видеть что реально настроено, а не то что было при создании. Источник правды — Nubes API: 1. GET /instances/{instanceUid} → state.params (текущие значения, ключ = код параметра) 2. GET /instanceOperations/default/{opId} (шаблон: код, тип, valueList, dataDescriptor) 3. Смержить: defaultValue = state.params["код"] ?? template.defaultValue Также заполняет пустые valueList из state.out (users/databases для PG и др.). """ def get_params_with_current_values(client, op_id, instance_uid): """ Возвращает список параметров (dict) для заполнения формы операции. Каждый элемент результирующего списка: svcOperationCfsParamId — числовой ID параметра (напр. 198) name — код параметра (напр. "durationMs") dataType — тип (напр. "integer >= 0", "boolean", "string") isRequired — обязательный? defaultValue — ТЕКУЩЕЕ значение из state.params или шаблонный default valueList — список допустимых значений (если есть) refSvcId — ID сервиса для refSvc-параметров dataDescriptor — вложенная структура для map-параметров (если есть) Args: client: HttpClient op_id: int — svcOperationId (напр. 18 для modify Болванки) instance_uid: str — UUID инстанса (для получения state.params) Returns: list[dict] — параметры со смерженными текущими значениями """ # --- Шаг 1: текущие значения из инстанса --- # GET /instances/{uid} возвращает полную информацию об инстансе # Нас интересует instance.state.params — dict вида: # {"whereFail": "1", "durationMs": "0", "diskSizeGb": "10"} # Ключи — СИМВОЛИЧЕСКИЕ имена параметров (не числовые ID!) inst_data = client.get(f"/instances/{instance_uid}") state_params = inst_data.get("instance", {}).get("state", {}).get("params", {}) or {} # state.out — дополнительные данные (users, databases для PG-сервисов) # Используется для заполнения пустых valueList (см. ниже) state_out = inst_data.get("instance", {}).get("state", {}).get("out", {}) or {} # --- Шаг 2: шаблон параметров операции --- # GET /instanceOperations/default/{opId} → svcOperation.cfsParams[] # Это полный список параметров с типами, default-значениями, valueList и т.д. tmpl_data = client.get(f"/instanceOperations/default/{op_id}") tmpl_params = tmpl_data.get("svcOperation", {}).get("cfsParams", []) or [] # --- Шаг 3: слияние шаблона с текущими значениями --- result = [] for p in tmpl_params: pid = p["svcOperationCfsParamId"] # числовой ID (напр. 198) code = p.get("svcOperationCfsParam", "") # символическое имя (напр. "durationMs") data_type = p.get("dataType", "") required = p.get("isRequired", False) tmpl_default = p.get("defaultValue") # шаблонный default value_list = p.get("valueList") # Заполнить ПУСТЫЕ valueList из state.out. # Некоторые сервисы (PG) возвращают valueList=[] в шаблоне, # но реальный список значений (пользователи/базы) лежит в state.out. # Пример: state.out.users = {"pgadmin": {...}, "appuser": {...}} # → valueList = ["pgadmin", "appuser"] if value_list is not None and not value_list: code_lower = code.lower() # Параметр типа "пользователь" / "владелец" → state.out.users if ("user" in code_lower or "owner" in code_lower) and isinstance(state_out.get("users"), dict): value_list = sorted(state_out["users"].keys()) # Параметр типа "база данных" → state.out.databases elif "db" in code_lower and isinstance(state_out.get("databases"), dict): value_list = sorted(state_out["databases"].keys()) dd = p.get("dataDescriptor") # вложенная структура (для map-параметров) # Выбираем defaultValue: # Если в state.params есть значение для этого кода → используем его # Иначе → шаблонный defaultValue current = state_params.get(code) if current is not None: # Приводим к строке — фронтенд ожидает строки в полях ввода. # bool → "true"/"false" (не "True"/"False" — JS не поймёт) # dict → JSON-строка # остальное → str() if isinstance(current, bool): current = "true" if current else "false" elif isinstance(current, dict): import json current = json.dumps(current) else: current = str(current) merged_value = current else: merged_value = tmpl_default result.append({ "svcOperationCfsParamId": pid, "name": code, "dataType": data_type, "isRequired": required, "defaultValue": merged_value, "valueList": _normalize_value_list(value_list), "refSvcId": p.get("refSvcId"), # если параметр ссылается на другой сервис "dataDescriptor": ( # Для map-параметров: рекурсивно нормализуем вложенные поля { k: { "dataType": v.get("dataType", ""), "valueList": _normalize_value_list(v.get("valueList", "")), "defaultValue": v.get("defaultValue", ""), "isRequired": v.get("isRequired", False), } for k, v in dd.items() } if isinstance(dd, dict) else None ), }) return result def _normalize_value_list(vl): """Нормализовать valueList: API может вернуть CSV-строку или массив. Приводим к list для фронтенда (всегда массив строк). Args: vl: None | str | list — valueList из API. Returns: list или None — нормализованный список или None если пусто.""" if vl is None: return None # Уже массив — возвращаем как есть if isinstance(vl, list): return vl # CSV-строка: "val1,val2,val3" → ["val1", "val2", "val3"] if isinstance(vl, str): return [x.strip() for x in vl.split(',') if x.strip()] or None return None