UI: карточка установленных аддонов на странице кластера; API аддонов и правки стилей

- Отдельная section.card после сводки ресурсов узлов, выравнивание бейджа Helm с заголовком
- Расширение эндпоинтов аддонов, схемы, helm_addons, cluster_addons.js/html
- Обновление api_routes.md
This commit is contained in:
Sergey Antropoff
2026-04-04 14:13:01 +03:00
parent eb063aec20
commit d55e69c756
9 changed files with 1238 additions and 229 deletions
+181 -11
View File
@@ -358,18 +358,188 @@ def helm_list_all(cluster_name: str) -> list[dict[str, Any]]:
return []
def addon_status(cluster_name: str) -> dict[str, bool]:
"""Какие аддоны установлены (по имени релиза и namespace)."""
lst = helm_list_all(cluster_name)
names_ns = {(str(x.get("name", "")), str(x.get("namespace", ""))) for x in lst if isinstance(x, dict)}
return {
"ingress_nginx": ("ingress-nginx", "ingress-nginx") in names_ns,
"kube_prometheus_stack": ("kube-prometheus-stack", "monitoring") in names_ns,
"metrics_server": ("metrics-server", "kube-system") in names_ns,
"istio_base": ("istio-base", "istio-system") in names_ns,
"istiod": ("istiod", "istio-system") in names_ns,
"kiali": ("kiali-server", "istio-system") in names_ns,
def helm_release_entry(
lst: list[dict[str, Any]],
release: str,
namespace: str,
) -> dict[str, Any] | None:
"""Одна запись ``helm list -o json`` по имени релиза и namespace."""
for x in lst:
if not isinstance(x, dict):
continue
if str(x.get("name", "")) == release and str(x.get("namespace", "")) == namespace:
return x
return None
def _chart_version_from_list_field(chart: str, release_name: str) -> str | None:
"""
Поле ``chart`` в выводе ``helm list`` имеет вид ``<имя_чарта>-<версия>`` (например ``ingress-nginx-4.11.2``).
Для релиза Istio base в списке часто ``base-1.22.3``.
"""
chart = (chart or "").strip()
if not chart:
return None
prefixes: dict[str, str] = {
"ingress-nginx": "ingress-nginx-",
"kube-prometheus-stack": "kube-prometheus-stack-",
"metrics-server": "metrics-server-",
"istio-base": "base-",
"istiod": "istiod-",
"kiali-server": "kiali-server-",
}
p = prefixes.get(release_name)
if p and chart.startswith(p):
v = chart[len(p) :].strip()
return v or None
return chart
def helm_get_values_all_yaml(cluster_name: str, release: str, namespace: str) -> str:
"""
Эффективные values релиза: ``helm get values --all -o yaml`` (для правки при upgrade).
"""
kcfg = str(_kubeconfig_runtime_path(cluster_name))
rc, out, err = _run_cmd(
[
"helm",
"get",
"values",
release,
"-n",
namespace,
"--all",
"-o",
"yaml",
"--kubeconfig",
kcfg,
],
timeout=120,
)
if rc != 0:
raise HelmAddonError(
(err or out or f"helm get values {release}/{namespace} код {rc}")[:8000],
exit_code=rc,
)
return (out or "").strip()
_VALUES_YAML_API_MAX = 131_072
def trim_values_yaml_for_api(raw: str) -> str:
"""Ограничение размера YAML в JSON-ответе API."""
if len(raw) <= _VALUES_YAML_API_MAX:
return raw
return raw[: _VALUES_YAML_API_MAX - 120] + "\n\n# … YAML обрезан (лимит ответа API).\n"
def helm_addon_snapshot(cluster_name: str) -> dict[str, Any]:
"""
Флаги установки, версии чартов из ``helm list`` и признак полного mesh Istio+Kiali.
"""
lst = helm_list_all(cluster_name)
def pair(rel: str, ns: str) -> tuple[bool, str | None]:
e = helm_release_entry(lst, rel, ns)
if not e:
return False, None
ch = str(e.get("chart") or "")
ver = _chart_version_from_list_field(ch, rel)
return True, ver
ing_on, ing_v = pair("ingress-nginx", "ingress-nginx")
prom_on, prom_v = pair("kube-prometheus-stack", "monitoring")
ms_on, ms_v = pair("metrics-server", "kube-system")
base_on, base_v = pair("istio-base", "istio-system")
istiod_on, istiod_v = pair("istiod", "istio-system")
kiali_on, kiali_v = pair("kiali-server", "istio-system")
mesh = bool(base_on and istiod_on and kiali_on)
return {
"ingress_nginx": ing_on,
"ingress_nginx_chart_version": ing_v if ing_on else None,
"kube_prometheus_stack": prom_on,
"kube_prometheus_stack_chart_version": prom_v if prom_on else None,
"metrics_server": ms_on,
"metrics_server_chart_version": ms_v if ms_on else None,
"istio_base": base_on,
"istiod": istiod_on,
"kiali_server": kiali_on,
"istio_base_chart_version": base_v if base_on else None,
"istiod_chart_version": istiod_v if istiod_on else None,
"kiali_server_chart_version": kiali_v if kiali_on else None,
"istio_mesh_ready": mesh,
}
def addon_status(cluster_name: str) -> dict[str, bool]:
"""Какие аддоны установлены (по имени релиза и namespace). Совместимость со старым кодом."""
s = helm_addon_snapshot(cluster_name)
return {
"ingress_nginx": bool(s["ingress_nginx"]),
"kube_prometheus_stack": bool(s["kube_prometheus_stack"]),
"metrics_server": bool(s["metrics_server"]),
"istio_base": bool(s["istio_base"]),
"istiod": bool(s["istiod"]),
"kiali": bool(s["kiali_server"]),
}
def collect_installed_addons_values(cluster_name: str) -> dict[str, Any]:
"""
Для установленных релизов — версия чарта (как в snapshot) и YAML ``helm get values --all``.
Ключи совпадают с логическими аддонами страницы UI.
"""
s = helm_addon_snapshot(cluster_name)
out: dict[str, Any] = {}
if s["ingress_nginx"]:
try:
y = trim_values_yaml_for_api(
helm_get_values_all_yaml(cluster_name, "ingress-nginx", "ingress-nginx"),
)
except HelmAddonError as e:
y = f"# Не удалось прочитать values: {e}\n"
out["ingress_nginx"] = {"chart_version": s["ingress_nginx_chart_version"], "values_yaml": y}
if s["kube_prometheus_stack"]:
try:
y = trim_values_yaml_for_api(
helm_get_values_all_yaml(cluster_name, "kube-prometheus-stack", "monitoring"),
)
except HelmAddonError as e:
y = f"# Не удалось прочитать values: {e}\n"
out["kube_prometheus_stack"] = {"chart_version": s["kube_prometheus_stack_chart_version"], "values_yaml": y}
if s["metrics_server"]:
try:
y = trim_values_yaml_for_api(
helm_get_values_all_yaml(cluster_name, "metrics-server", "kube-system"),
)
except HelmAddonError as e:
y = f"# Не удалось прочитать values: {e}\n"
out["metrics_server"] = {"chart_version": s["metrics_server_chart_version"], "values_yaml": y}
if s["istio_mesh_ready"]:
try:
kb = trim_values_yaml_for_api(
helm_get_values_all_yaml(cluster_name, "istio-base", "istio-system"),
)
ki = trim_values_yaml_for_api(helm_get_values_all_yaml(cluster_name, "istiod", "istio-system"))
kk = trim_values_yaml_for_api(
helm_get_values_all_yaml(cluster_name, "kiali-server", "istio-system"),
)
except HelmAddonError as e:
kb = ki = kk = f"# Не удалось прочитать values: {e}\n"
out["istio_kiali"] = {
"istio_chart_version": s["istiod_chart_version"] or s["istio_base_chart_version"],
"kiali_chart_version": s["kiali_server_chart_version"],
"istio_base_values_yaml": kb,
"istiod_values_yaml": ki,
"kiali_values_yaml": kk,
}
return out
def _kubectl_apply_manifest(cluster_name: str, manifest: dict[str, Any]) -> tuple[int, str]: