SPB Git forge

spb/ka-guardian

Public
26commits 1branches 0releases
4.5 MBsize
maindefault branch
19 days agolast push
Python 57.1% Shell 13.8% CSS 12% JavaScript 10% HTML 7.2%
5.5 KB · 102 lines javascript
Raw Blame History
1/* KA Guardian — poste de commande (/commander) */2const $ = (s) => document.querySelector(s);3const esc = (s) => String(s ?? "").replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c]));4const STATUS_FR = { ok: "ok", degraded: "dégradé", broken: "cassé", stale: "endormi" };5const STATE_FR = { open: "en file", dispatched: "envoi…", fixing: "en cours", watching: "surveillance",6  cooldown: "attente", resolved: "livré", self_healed: "auto-guéri", abandoned: "terminé" };7const KIND_FR = { effort_new: "nouveau connecteur", effort_enrich: "enrichissement", effort_degrade: "inspection dégradés" };8const HINTS = {9  effort_new: "L'agent cartographie l'existant, découvre des sources québécoises non couvertes (Serper), choisit la meilleure et construit le connecteur complet — testé en volume réel.",10  effort_enrich: "L'agent maximise la valeur du connecteur choisi : pagination complète, champs des fiches détail, robustesse, détection des retraits — mesuré avant/après.",11  effort_degrade: "L'agent inspecte tous les connecteurs dégradés de la plateforme (volume < 50 % de la médiane), trie baisse réelle / légitime / blocage, et répare un par un — un commit par connecteur.",12};1314let STATE = null;1516async function load() {17  const r = await fetch("/api/state");18  STATE = await r.json();19  const id = STATE.identity, num = STATE.agent.replace("ka", "");20  const rs = document.documentElement.style;21  rs.setProperty("--agent", id.accent); rs.setProperty("--lime", id.accent);22  if (id.accent_soft) rs.setProperty("--lime-soft", id.accent_soft);23  if (id.accent2) rs.setProperty("--accent-deep", id.accent2);24  document.title = `Commander — ${STATE.agent.toUpperCase()} Guardian`;25  $("#wm-name").textContent = "ka·" + num;26  $("#foot-name").textContent = "ka·" + num;27  $("#hero-label").textContent = `${STATE.agent} — poste de commande · ${id.tagline}`;28  $("#siblings").innerHTML = Object.entries(STATE.siblings).map(([a, v]) =>29    `<a class="gk-badge" href="https://${v.domain}/commander"><span class="dot" style="background:${v.accent}"></span><b>${a}<span class="ka">·G</span></b></a>`).join(" ");30  const svcSel = $("#ef-service");31  if (!svcSel.options.length) {32    svcSel.innerHTML = Object.entries(STATE.services)33      .map(([k, s]) => `<option value="${k}">${esc(s.app)} — ${esc(s.node_alias)}</option>`).join("");34  }35  renderEfforts();36}3738function renderEfforts() {39  const efforts = STATE.incidents.filter((i) => (i.status_detected || "").startsWith("effort"));40  $("#efforts-list").innerHTML = efforts.slice(0, 20).map((i) => {41    const d = JSON.parse(i.detail || "{}");42    return `<div class="inc">43      <div><div class="src">${esc(KIND_FR[i.status_detected] || i.status_detected)} — ${esc((STATE.services[i.service] || {}).app || i.service)}</div>44      <div class="svc">${esc(i.source)}${d.note ? " · « " + esc(d.note.slice(0, 80)) + " »" : ""}${d.max_minutes ? " · ≤" + d.max_minutes + " min" : ""}${d.max_cost_usd ? " · ≤" + d.max_cost_usd + " $" : ""}</div></div>45      <span class="badge b-${esc(i.state)}">${STATE_FR[i.state] || esc(i.state)}</span></div>`;46  }).join("") || `<div class="empty">Aucun effort commandé encore — sois le premier à lui donner du travail.</div>`;47}4849function syncKind() {50  const kind = $("#ef-kind").value;51  $("#ef-source-wrap").hidden = kind !== "effort_enrich";52  $("#kind-hint").textContent = HINTS[kind] || "";53  if (kind === "effort_enrich") fillSources();54}5556async function fillSources() {57  const r = await fetch("/api/connectors/" + $("#ef-service").value);58  const d = await r.json();59  $("#ef-source").innerHTML = d.connectors60    .map((c) => `<option value="${esc(c.source)}">${esc(c.source)} (${STATUS_FR[c.status] || c.status})</option>`).join("")61    || `<option value="">— aucun connecteur connu —</option>`;62}6364async function submitEffort(e) {65  e.preventDefault();66  const msg = $("#ef-msg"), btn = e.target.querySelector("button");67  const token = $("#ef-token").value.trim();68  if (!token) { msg.className = "cmd-msg err"; msg.textContent = "jeton d'opérateur requis"; return; }69  localStorage.setItem("ka_token", token);70  btn.disabled = true; msg.className = "cmd-msg"; msg.textContent = "lancement…";71  try {72    const r = await fetch("/api/admin/effort", {73      method: "POST", headers: { "Content-Type": "application/json", "X-KA-Token": token },74      body: JSON.stringify({75        kind: $("#ef-kind").value, service: $("#ef-service").value,76        source: $("#ef-source").value || "", note: $("#ef-note").value,77        max_minutes: parseInt($("#ef-minutes").value, 10),78        max_cost_usd: parseFloat($("#ef-cost").value),79      }),80    });81    const d = await r.json();82    if (r.ok && d.ok) {83      msg.className = "cmd-msg ok";84      msg.textContent = `effort accepté (${d.incident_id}) — la session démarre d'ici ~5 min. Suis-la sur le flux ⚡`;85      $("#ef-note").value = "";86      load();87    } else {88      msg.className = "cmd-msg err";89      msg.textContent = "refusé: " + (d.detail || r.status);90    }91  } catch (err) {92    msg.className = "cmd-msg err"; msg.textContent = "erreur: " + err;93  } finally { btn.disabled = false; }94}9596$("#ef-kind").addEventListener("change", syncKind);97$("#ef-service").addEventListener("change", () => { if ($("#ef-kind").value === "effort_enrich") fillSources(); });98$("#effort-form").addEventListener("submit", submitEffort);99$("#ef-token").value = localStorage.getItem("ka_token") || "";100load().then(syncKind);101setInterval(load, 30000);102