Kiali anonymous, журнал Helm, kubeconfig для контейнеров, UI аддонов

- Kiali: убран login, anonymous по умолчанию; удалены поля логина/пароля из UI и API
- Журнал Helm: install/upgrade/delete, message и колонка в journal.js
- Аддоны: values свёрнуты при подгрузке для установленных
- GET …/kubeconfig/docker: host.docker.internal:порт + tls-server-name; кнопка в UI
- apply_apiserver_endpoint_to_kubeconfig_file; KIND_K8S_APISERVER_GATEWAY_HOST в compose/env.example
- README и api_routes.md обновлены
This commit is contained in:
Sergey Antropoff
2026-04-04 18:54:10 +03:00
parent 349b78d24d
commit 4b703801e1
13 changed files with 415 additions and 123 deletions
+17 -15
View File
@@ -464,7 +464,13 @@
if (ta) ta.value = "";
}
function showAddonValuesPanel(addon) {
/**
* Показать панель YAML values. Для установленного аддона при подгрузке values — блок <details> свёрнут.
* @param {string} addon
* @param {{ collapseDetails?: boolean } | undefined} opts
*/
function showAddonValuesPanel(addon, opts) {
opts = opts || {};
var pid = ADDON_VALUES_PANEL_ID[addon];
if (!pid) return;
var panel = document.getElementById(pid);
@@ -472,6 +478,10 @@
panel.classList.remove("hidden");
panel.removeAttribute("hidden");
panel.setAttribute("aria-hidden", "false");
var det = panel.querySelector("details.cluster-addon-card__values-details");
if (det) {
det.open = opts.collapseDetails ? false : true;
}
}
function resetAllAddonValuesPanels() {
@@ -532,7 +542,7 @@
var t = document.getElementById("addons-values-yaml-" + addon);
if (t) t.value = r.values_yaml || "";
}
showAddonValuesPanel(addon);
showAddonValuesPanel(addon, { collapseDetails: addonEndpointInstalled(addon) });
} catch (e) {
setBootstrapMsg("Не удалось загрузить values (helm show values): " + e.message);
} finally {
@@ -674,7 +684,7 @@
} else if (iv.ingress_nginx && iv.ingress_nginx.values_yaml) {
var ti = document.getElementById("addons-values-yaml-ingress-nginx");
if (ti) ti.value = iv.ingress_nginx.values_yaml;
showAddonValuesPanel("ingress-nginx");
showAddonValuesPanel("ingress-nginx", { collapseDetails: true });
}
if (!st.kube_prometheus_stack) {
@@ -682,7 +692,7 @@
} else if (iv.kube_prometheus_stack && iv.kube_prometheus_stack.values_yaml) {
var tp = document.getElementById("addons-values-yaml-kube-prometheus-stack");
if (tp) tp.value = iv.kube_prometheus_stack.values_yaml;
showAddonValuesPanel("kube-prometheus-stack");
showAddonValuesPanel("kube-prometheus-stack", { collapseDetails: true });
}
if (!st.metrics_server) {
@@ -690,7 +700,7 @@
} else if (iv.metrics_server && iv.metrics_server.values_yaml) {
var tm = document.getElementById("addons-values-yaml-metrics-server");
if (tm) tm.value = iv.metrics_server.values_yaml;
showAddonValuesPanel("metrics-server");
showAddonValuesPanel("metrics-server", { collapseDetails: true });
}
if (!st.istio_mesh_ready) {
@@ -710,7 +720,7 @@
if (tiod) tiod.value = ik.istiod_values_yaml;
}
if (ik.kiali_values_yaml || ik.istio_base_values_yaml || ik.istiod_values_yaml) {
showAddonValuesPanel("istio-kiali");
showAddonValuesPanel("istio-kiali", { collapseDetails: true });
}
}
}
@@ -1002,17 +1012,9 @@
return;
}
if (ep === "istio-kiali") {
const u = document.getElementById("addons-kiali-user");
const p = document.getElementById("addons-kiali-pass");
const isel = document.getElementById("addons-istio-version-select");
const ksel = document.getElementById("addons-kiali-version-select");
const user = u ? u.value.trim() : "kiali-admin";
const pw = p ? p.value : "";
if (pw.length < 8) {
alert("Задайте пароль Kiali не короче 8 символов.");
return;
}
const payload = { kiali_username: user, kiali_password: pw };
const payload = {};
if (isel && isel.value.trim()) payload.istio_chart_version = isel.value.trim();
if (ksel && ksel.value.trim()) payload.kiali_chart_version = ksel.value.trim();
if (!attachAddonYamlToPayload(ep, payload)) return;
+33 -1
View File
@@ -250,6 +250,20 @@
document.body.removeChild(a);
}
/**
* Скачать kubeconfig для kubectl из другого контейнера: host.docker.internal:порт + tls-server-name (см. GET …/kubeconfig/docker).
* @param {string} clusterName
*/
function downloadKubeconfigDocker(clusterName) {
const url = API + "/clusters/" + encodeURIComponent(clusterName) + "/kubeconfig/docker";
const a = document.createElement("a");
a.href = url;
a.download = "kubeconfig-docker-" + clusterName + ".yaml";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
/** SVG-иконки действий (stroke, currentColor). */
var ICONS = {
state:
@@ -260,6 +274,9 @@
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="5" y="5" width="14" height="14" rx="2"/></svg>',
download:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg>',
/** kubeconfig для контейнера: apiserver по имени control-plane в сети Docker */
kubeDocker:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/><polyline points="3.27 6.96 12 12.01 20.73 6.96"/><line x1="12" y1="22.08" x2="12" y2="12"/></svg>',
trash:
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/><line x1="10" y1="11" x2="10" y2="17"/><line x1="14" y1="11" x2="14" y2="17"/></svg>',
logFile:
@@ -1519,7 +1536,7 @@
iconActionButton(
ICONS.download,
c.has_local_kubeconfig
? "Скачать kubeconfig для kubectl на хосте"
? "Скачать kubeconfig для kubectl на хосте (localhost:порт)"
: "Kubeconfig ещё нет — появится после создания или подъёма кластера",
"icon-btn--secondary",
c.has_local_kubeconfig
@@ -1530,6 +1547,21 @@
!c.has_local_kubeconfig,
),
);
host.appendChild(
iconActionButton(
ICONS.kubeDocker,
c.has_local_kubeconfig
? "Скачать kubeconfig для kubectl из другого контейнера: host.docker.internal:порт + tls-server-name (как в веб-UI; на Linux — extra_hosts host-gateway)"
: "Kubeconfig ещё нет — появится после создания или подъёма кластера",
"icon-btn--secondary",
c.has_local_kubeconfig
? function () {
downloadKubeconfigDocker(name);
}
: null,
!c.has_local_kubeconfig,
),
);
if (c.has_provision_log) {
host.appendChild(
iconActionButton(
+56 -1
View File
@@ -88,6 +88,61 @@
return "journal-status-pill";
}
/** Суффикс job_kind (после helm_addon_install_|upgrade_|delete_) → id аддона в API. */
function helmJobSlugToAddonId(slug) {
const m = {
ingress_nginx: "ingress-nginx",
kube_prometheus_stack: "kube-prometheus-stack",
metrics_server: "metrics-server",
istio_kiali: "istio-kiali",
};
return m[slug] || String(slug || "").replace(/_/g, "-") || "—";
}
function helmAddonTitle(id) {
const m = {
"ingress-nginx": "ingress-nginx",
"kube-prometheus-stack": "kube-prometheus-stack",
"metrics-server": "metrics-server",
"istio-kiali": "Istio + Kiali",
};
return m[id] || id || "—";
}
function helmOperationTitle(op) {
const o = (op || "").toLowerCase();
if (o === "install") return "Установка";
if (o === "upgrade") return "Обновление";
if (o === "delete") return "Удаление";
return op || "—";
}
/**
* Колонка «Вид операции» для режима Helm: установка / обновление / удаление и имя аддона.
* Старые записи без result.action разбираются по префиксу kind.
*/
function formatHelmJournalKind(e) {
const r = e.result;
if (r && typeof r === "object") {
const verb = helmOperationTitle(r.action);
const ad = r.addon != null ? helmAddonTitle(String(r.addon)) : "—";
if (r.action && r.addon != null && String(r.addon).length) {
return verb + " · " + ad;
}
}
const k = e.kind || "";
if (k.indexOf("helm_addon_delete_") === 0) {
return "Удаление · " + helmAddonTitle(helmJobSlugToAddonId(k.replace(/^helm_addon_delete_/, "")));
}
if (k.indexOf("helm_addon_upgrade_") === 0) {
return "Обновление · " + helmAddonTitle(helmJobSlugToAddonId(k.replace(/^helm_addon_upgrade_/, "")));
}
if (k.indexOf("helm_addon_install_") === 0) {
return "Установка · " + helmAddonTitle(helmJobSlugToAddonId(k.replace(/^helm_addon_install_/, "")));
}
return k || "—";
}
function visiblePageItems(current, total) {
const delta = 2;
const range = [];
@@ -200,7 +255,7 @@
const finished = formatUtc(
e.finished_at_utc || e.created_at_utc,
);
const kind = e.kind || "";
const kind = mode === "helm_addons" ? formatHelmJournalKind(e) : (e.kind || "");
const msg = (e.message || "").trim();
let lines = [];
if (mode === "per_cluster") {