|
|
|
|
@@ -65,10 +65,10 @@
|
|
|
|
|
.group-panel > summary::-webkit-details-marker { display:none; }
|
|
|
|
|
details.collapsible > summary::after,
|
|
|
|
|
.manual-context > summary::after,
|
|
|
|
|
.group-panel > summary::after { content:"aufklappen"; color:#9fb0be; font-weight:600; font-size:.86rem; }
|
|
|
|
|
.group-panel > summary::after { content:attr(data-closed-label); color:#9fb0be; font-weight:600; font-size:.86rem; }
|
|
|
|
|
details[open].collapsible > summary::after,
|
|
|
|
|
.manual-context[open] > summary::after,
|
|
|
|
|
.group-panel[open] > summary::after { content:"zuklappen"; }
|
|
|
|
|
.group-panel[open] > summary::after { content:attr(data-open-label); }
|
|
|
|
|
.steps { display:grid; grid-template-columns:repeat(auto-fit,minmax(190px,1fr)); gap:10px; margin-top:12px; }
|
|
|
|
|
.step { background:#121922; border:1px solid var(--border); border-radius:8px; padding:10px; }
|
|
|
|
|
.step-number { display:inline-grid; place-items:center; width:28px; height:28px; border-radius:8px; background:var(--complement); color:#03151d; font-weight:900; margin-bottom:8px; }
|
|
|
|
|
@@ -345,6 +345,8 @@ const I18N = {
|
|
|
|
|
unavailable_start: "Startdaten sind gerade nicht verfügbar.",
|
|
|
|
|
system_loading: "Systemübersicht lädt ...",
|
|
|
|
|
system_delayed: "Systemübersicht verzögert",
|
|
|
|
|
expand: "aufklappen",
|
|
|
|
|
collapse: "zuklappen",
|
|
|
|
|
context_detected: "Kontext erkannt",
|
|
|
|
|
active_approved: "aktiv freigegeben",
|
|
|
|
|
shadow_prediction: "Prüfmodus mit Vorhersage",
|
|
|
|
|
@@ -478,6 +480,8 @@ const I18N = {
|
|
|
|
|
unavailable_start: "Start data is currently unavailable.",
|
|
|
|
|
system_loading: "Loading system overview ...",
|
|
|
|
|
system_delayed: "System overview delayed",
|
|
|
|
|
expand: "expand",
|
|
|
|
|
collapse: "collapse",
|
|
|
|
|
context_detected: "Context detected",
|
|
|
|
|
active_approved: "actively approved",
|
|
|
|
|
shadow_prediction: "Review mode with prediction",
|
|
|
|
|
@@ -554,6 +558,182 @@ const I18N = {
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
const DE_TO_EN_TEXT = new Map(Object.entries({
|
|
|
|
|
"Aktiv / Prüfmodus": "Active / review mode",
|
|
|
|
|
"Aktive Gewichtung": "Active weight",
|
|
|
|
|
"Aktoren": "Actuators",
|
|
|
|
|
"Aktualisieren": "Refresh",
|
|
|
|
|
"Aktualisiert.": "Refreshed.",
|
|
|
|
|
"Aktuelle Blocker:": "Current blockers:",
|
|
|
|
|
"Aktuelle Situation auswerten": "Evaluate current situation",
|
|
|
|
|
"Aktuell ist kein gelerntes Handlungsmuster fällig.": "No learned action pattern is due right now.",
|
|
|
|
|
"Alle relevanten Vorschläge": "All relevant suggestions",
|
|
|
|
|
"Als Gruppe speichern": "Save as group",
|
|
|
|
|
"Anderes Gerät": "Another device",
|
|
|
|
|
"Annahmen": "Assumptions",
|
|
|
|
|
"Anomalien": "Anomalies",
|
|
|
|
|
"Anomalien offen": "anomalies open",
|
|
|
|
|
"Aktor-Simulation": "Actuator simulation",
|
|
|
|
|
"Aufgabenliste": "Task list",
|
|
|
|
|
"Automationen neu suchen": "Find automations again",
|
|
|
|
|
"Automatische Gewichtsanpassungen": "Automatic weight adjustments",
|
|
|
|
|
"Automatische Relevanz": "Automatic relevance",
|
|
|
|
|
"Bedienoberfläche.": "user interface.",
|
|
|
|
|
"Bereit.": "Ready.",
|
|
|
|
|
"Bestes Szenario": "Best scenario",
|
|
|
|
|
"Bestes Szenario berechnen": "Calculate best scenario",
|
|
|
|
|
"Betriebsart:": "Mode:",
|
|
|
|
|
"Beitragsfaktoren": "Contributing factors",
|
|
|
|
|
"Cache aktuell mit": "Cache current with",
|
|
|
|
|
"Cache wird nach Discovery aufgebaut": "Cache will be built after discovery",
|
|
|
|
|
"Cache-Zeitpunkt": "Cache time",
|
|
|
|
|
"Cooldown Sekunden": "Cooldown seconds",
|
|
|
|
|
"Dashboard bereit in": "Dashboard ready in",
|
|
|
|
|
"Dashboard bleibt bedienbar.": "The dashboard remains usable.",
|
|
|
|
|
"Dauer:": "Duration:",
|
|
|
|
|
"Davon eindeutig geregelt:": "Clearly regulated:",
|
|
|
|
|
"Details öffnen": "Open details",
|
|
|
|
|
"Diese Kontext-Auswahl speichern": "Save this context selection",
|
|
|
|
|
"Discovery-Gruppen": "Discovery groups",
|
|
|
|
|
"Entfernen": "Remove",
|
|
|
|
|
"Entscheidungsakte": "Decision record",
|
|
|
|
|
"Entity-IDs der Gruppe": "Group entity IDs",
|
|
|
|
|
"Entity-IDs manuell ergänzen": "Add entity IDs manually",
|
|
|
|
|
"Ergebnis:": "Result:",
|
|
|
|
|
"Erkannt:": "Detected:",
|
|
|
|
|
"Erkannte HA-Automationen:": "Detected HA automations:",
|
|
|
|
|
"Erneut versuchen: Aktion im Dashboard noch einmal starten; der nächste Lauf schreibt einen neuen Eintrag.": "Try again: start the dashboard action again; the next run will create a new entry.",
|
|
|
|
|
"Freigabe": "Approval",
|
|
|
|
|
"Freigabebereit": "Ready for approval",
|
|
|
|
|
"Freigabestatus:": "Approval status:",
|
|
|
|
|
"Freigabestufe": "Approval stage",
|
|
|
|
|
"Für die Simulation müssen zuerst Kontextsensoren ausgewählt sein.": "Select context sensors before running a simulation.",
|
|
|
|
|
"Geladene Startdaten": "Loaded start data",
|
|
|
|
|
"Gelernte Handlungen": "Learned actions",
|
|
|
|
|
"Gelernt / Wartet": "Learned / waiting",
|
|
|
|
|
"Gerät auswählen": "Select device",
|
|
|
|
|
"Geräteliste konnte nicht geladen werden:": "Device list could not be loaded:",
|
|
|
|
|
"Geräteliste lädt im Hintergrund ...": "Device list loading in the background ...",
|
|
|
|
|
"Geräteliste wird geladen ...": "Device list is loading ...",
|
|
|
|
|
"Gewichtung": "Weight",
|
|
|
|
|
"Gewichtung korrigieren": "Adjust weight",
|
|
|
|
|
"Gewichtung übernehmen": "Apply weight",
|
|
|
|
|
"Gewichtung speichern": "Save weight",
|
|
|
|
|
"Gewichtungen speichern": "Save weights",
|
|
|
|
|
"Gruppen-Gewicht in %": "Group weight in %",
|
|
|
|
|
"Gruppen-Gewichtung": "Group weighting",
|
|
|
|
|
"Gruppenname": "Group name",
|
|
|
|
|
"HA-Automationen pausieren": "pause HA automations",
|
|
|
|
|
"HA-Automationen pausiert lassen": "leave HA automations paused",
|
|
|
|
|
"HA-Automationen fortsetzen": "resume HA automations",
|
|
|
|
|
"Handlungen": "Actions",
|
|
|
|
|
"Job p95": "Job p95",
|
|
|
|
|
"Jobs aktiv": "Active jobs",
|
|
|
|
|
"Kategorie": "Category",
|
|
|
|
|
"Kategorien": "categories",
|
|
|
|
|
"Keine Annahmen gespeichert.": "No assumptions stored.",
|
|
|
|
|
"Keine aktiven Automation-Konflikte erkannt.": "No active automation conflicts detected.",
|
|
|
|
|
"Keine eindeutig passende HA-Automation gefunden.": "No clearly matching HA automation found.",
|
|
|
|
|
"Keine gesicherten Punkte gespeichert.": "No confirmed points stored.",
|
|
|
|
|
"Keine lokalen Sicherheitsblocker für die aktuelle Vorhersage.": "No local safety blockers for the current prediction.",
|
|
|
|
|
"Keine offenen Anomalien fuer diesen Aktor.": "No open anomalies for this actuator.",
|
|
|
|
|
"Keine passenden Geräte gefunden": "No matching devices found",
|
|
|
|
|
"Keine passenden Vorschläge": "No matching suggestions",
|
|
|
|
|
"Keine Simulationsergebnisse.": "No simulation results.",
|
|
|
|
|
"Keine Unsicherheit gespeichert.": "No uncertainty stored.",
|
|
|
|
|
"Kein Kommentar": "No comment",
|
|
|
|
|
"Kein numerischen Hauptsensor verwenden": "Do not use a numeric main sensor",
|
|
|
|
|
"Keinen numerischen Hauptsensor verwenden": "Do not use a numeric main sensor",
|
|
|
|
|
"Kontext": "Context",
|
|
|
|
|
"Kontext selbst festlegen": "Set context manually",
|
|
|
|
|
"Kontext wird automatisch analysiert ...": "Context is being analyzed automatically ...",
|
|
|
|
|
"Kontext-Entity entfernt.": "Context entity removed.",
|
|
|
|
|
"Kontext-Entity ausgewählt.": "context entity selected.",
|
|
|
|
|
"Kontextzuordnung:": "Context assignment:",
|
|
|
|
|
"Kritisch": "Critical",
|
|
|
|
|
"Langsame Jobs": "Slow jobs",
|
|
|
|
|
"Langsam:": "Slow:",
|
|
|
|
|
"Lädt ...": "Loading ...",
|
|
|
|
|
"Lernbereit": "Ready to learn",
|
|
|
|
|
"Lernbereite Geräte": "Devices ready to learn",
|
|
|
|
|
"Lernen": "Learning",
|
|
|
|
|
"Lernfortschritt": "Learning progress",
|
|
|
|
|
"Lernsystem": "Learning system",
|
|
|
|
|
"Letzte automatische Prüfung:": "Last automatic check:",
|
|
|
|
|
"Letztes Training:": "Last training:",
|
|
|
|
|
"Manuelle Kontext-Auswahl gespeichert.": "Manual context selection saved.",
|
|
|
|
|
"Manuelle Sicherheitssperre aktiv": "Manual safety block active",
|
|
|
|
|
"Mindest-Sicherheit in %": "Minimum confidence in %",
|
|
|
|
|
"Noch keine Aktoren ausgewählt.": "No actuators selected yet.",
|
|
|
|
|
"Noch keine aktuelle Entscheidungsfaktoren berechnet.": "No current decision factors calculated yet.",
|
|
|
|
|
"Noch keine automatische Gewichtsanpassung.": "No automatic weight adjustment yet.",
|
|
|
|
|
"Noch keine Daten": "No data yet",
|
|
|
|
|
"Noch keine Gruppe gespeichert.": "No group saved yet.",
|
|
|
|
|
"Noch keine Kontext-Entity ausgewählt.": "No context entity selected yet.",
|
|
|
|
|
"Noch keine verwendeten Sensoren oder Zustände für eine Gewichtung ausgewählt.": "No sensors or states selected for weighting yet.",
|
|
|
|
|
"Noch kein geeigneter Kontext erkannt. SillyHome prüft bei neuen HA-Daten erneut.": "No suitable context detected yet. SillyHome will check again when new HA data arrives.",
|
|
|
|
|
"Noch kein Modell-Snapshot gespeichert.": "No model snapshot saved yet.",
|
|
|
|
|
"Numerische Helper": "Numeric helpers",
|
|
|
|
|
"Optionaler Haupt-Messsensor": "Optional main measurement sensor",
|
|
|
|
|
"Passende Home-Assistant-Automationen": "Matching Home Assistant automations",
|
|
|
|
|
"Passende Home-Assistant-Automationen neu geprüft.": "Matching Home Assistant automations checked again.",
|
|
|
|
|
"Prüfen": "Review",
|
|
|
|
|
"Prüfung läuft ...": "Checking ...",
|
|
|
|
|
"Rollback möglich": "Rollback available",
|
|
|
|
|
"Sicherheit und manuelles Gegensteuern": "Safety and manual override",
|
|
|
|
|
"Sicherheitsprofil speichern": "Save safety profile",
|
|
|
|
|
"Sicherheitsprofil gespeichert.": "Safety profile saved.",
|
|
|
|
|
"SillyHome parallel aktivieren": "Activate SillyHome in parallel",
|
|
|
|
|
"SillyHome stoppen; HA-Automationen pausiert lassen": "Stop SillyHome; leave HA automations paused",
|
|
|
|
|
"SillyHome stoppen und pausierte HA-Automationen fortsetzen": "Stop SillyHome and resume paused HA automations",
|
|
|
|
|
"SillyHome übernehmen lassen": "Let SillyHome take over",
|
|
|
|
|
"SillyHome übernehmen lassen und passende HA-Automationen pausieren": "Let SillyHome take over and pause matching HA automations",
|
|
|
|
|
"Simulation läuft ...": "Simulation running ...",
|
|
|
|
|
"Simulation übernommen.": "Simulation applied.",
|
|
|
|
|
"Simulation übernommen und Dry-run gestartet.": "Simulation applied and dry-run started.",
|
|
|
|
|
"Simulationsergebnis ist nicht mehr verfügbar. Bitte neu simulieren.": "Simulation result is no longer available. Please simulate again.",
|
|
|
|
|
"Simulierte Gewichtung in %": "Simulated weight in %",
|
|
|
|
|
"Simulierter Zustand": "Simulated state",
|
|
|
|
|
"Sensor-Gewichtung": "Sensor weighting",
|
|
|
|
|
"Sensor-Gewichtung gespeichert.": "Sensor weighting saved.",
|
|
|
|
|
"Start:": "Start:",
|
|
|
|
|
"Status teilweise verfügbar. Das Dashboard bleibt bedienbar.": "Status partially available. The dashboard remains usable.",
|
|
|
|
|
"Status wird geprüft ...": "Checking status ...",
|
|
|
|
|
"Statusgrund:": "Status reason:",
|
|
|
|
|
"System bereit": "System ready",
|
|
|
|
|
"Systemübersicht bereit in": "System overview ready in",
|
|
|
|
|
"Systemübersicht langsam:": "System overview slow:",
|
|
|
|
|
"Systemübersicht verzögert:": "System overview delayed:",
|
|
|
|
|
"Übernehmen + Dry-run starten": "Apply + start dry-run",
|
|
|
|
|
"Unsicherheit": "Uncertainty",
|
|
|
|
|
"Verwendete Sensoren/Zustände ändern": "Change used sensors/states",
|
|
|
|
|
"Vorhersage": "Prediction",
|
|
|
|
|
"Vorhersage korrekt": "Prediction correct",
|
|
|
|
|
"Vorhersage falsch": "Prediction wrong",
|
|
|
|
|
"Was SillyHome aktuell vorhersagt": "What SillyHome currently predicts",
|
|
|
|
|
"Wissen": "Knowledge",
|
|
|
|
|
"Wähle ein Gerät aus der geladenen Liste oder trage eine Entity-ID ein.": "Select a device from the loaded list or enter an entity ID.",
|
|
|
|
|
"Würde nach Sicherheitsprüfung schalten.": "Would switch after safety check.",
|
|
|
|
|
"Zeitprofile": "Time profiles",
|
|
|
|
|
"Zuordnung": "Assignment",
|
|
|
|
|
"Zuordnungssicherheit:": "Assignment confidence:",
|
|
|
|
|
"Zurück zur Übersicht": "Back to overview",
|
|
|
|
|
"Zustände": "States",
|
|
|
|
|
"Zustände vergleichen": "Compare states",
|
|
|
|
|
"aktiv": "active",
|
|
|
|
|
"aktiv freigegeben": "actively approved",
|
|
|
|
|
"automatisch erledigt": "automatic",
|
|
|
|
|
"bereit": "ready",
|
|
|
|
|
"keine": "none",
|
|
|
|
|
"keine Vorhersage": "no prediction",
|
|
|
|
|
"läuft/offen": "running/open",
|
|
|
|
|
"noch offen": "pending",
|
|
|
|
|
"offen": "open",
|
|
|
|
|
"pausiert": "paused",
|
|
|
|
|
"relevant": "relevant",
|
|
|
|
|
"statistisch relevanter Kandidat": "statistically relevant candidate",
|
|
|
|
|
"unbekannt": "unknown",
|
|
|
|
|
}));
|
|
|
|
|
let uiLang = localStorage.getItem("sillyhome.ui.language") || "de";
|
|
|
|
|
|
|
|
|
|
function jumpToSection(target) {
|
|
|
|
|
@@ -595,6 +775,7 @@ function setLanguage(language) {
|
|
|
|
|
uiLang = I18N[language] ? language : "de";
|
|
|
|
|
localStorage.setItem("sillyhome.ui.language", uiLang);
|
|
|
|
|
document.documentElement.lang = uiLang;
|
|
|
|
|
cachedDetailHtml.clear();
|
|
|
|
|
applyStaticTranslations();
|
|
|
|
|
syncSettingsView();
|
|
|
|
|
renderActuatorSelect();
|
|
|
|
|
@@ -630,7 +811,72 @@ function setPlaceholder(selector, key) {
|
|
|
|
|
if (element) element.placeholder = ui(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function translateText(value) {
|
|
|
|
|
if (uiLang !== "en") return value;
|
|
|
|
|
const text = String(value ?? "");
|
|
|
|
|
const direct = DE_TO_EN_TEXT.get(text.trim());
|
|
|
|
|
if (direct) {
|
|
|
|
|
return text.replace(text.trim(), direct);
|
|
|
|
|
}
|
|
|
|
|
let translated = text;
|
|
|
|
|
const replacements = [...DE_TO_EN_TEXT.entries()]
|
|
|
|
|
.filter(([source]) => source.length > 3)
|
|
|
|
|
.sort(([left], [right]) => right.length - left.length);
|
|
|
|
|
for (const [source, target] of replacements) {
|
|
|
|
|
translated = translated.replaceAll(source, target);
|
|
|
|
|
}
|
|
|
|
|
translated = translated
|
|
|
|
|
.replace(/Bereit in (\d+) ms/g, "Ready in $1 ms")
|
|
|
|
|
.replace(/Weitere (\d+) Geräte anzeigen/g, "Show $1 more devices")
|
|
|
|
|
.replace(/(\d+) von (\d+); Suche oder Typ weiter eingrenzen/g, "$1 of $2; narrow search or type")
|
|
|
|
|
.replace(/Szenario (\d+)/g, "Scenario $1")
|
|
|
|
|
.replace(/Ø (\d+) %/g, "avg. $1 %")
|
|
|
|
|
.replace(/(\d+) Beispiele/g, "$1 examples")
|
|
|
|
|
.replace(/(\d+) eindeutig/g, "$1 clear")
|
|
|
|
|
.replace(/(\d+) % Beitrag/g, "$1 % contribution")
|
|
|
|
|
.replace(/(\d+) % Profilklarheit/g, "$1 % profile clarity")
|
|
|
|
|
.replace(/mit (\d+) % Sicherheit/g, "with $1 % confidence")
|
|
|
|
|
.replace(/Prüfung ([^:]+):/g, "Check $1:")
|
|
|
|
|
.replace(/Keine Zusammenfassung/g, "No summary")
|
|
|
|
|
.replace(/Dauer: läuft\/offen/g, "Duration: running/open");
|
|
|
|
|
return translated;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function localizeFragment(root = document) {
|
|
|
|
|
for (const summary of root.querySelectorAll?.("details.collapsible > summary, .manual-context > summary, .group-panel > summary") || []) {
|
|
|
|
|
summary.dataset.closedLabel = ui("expand");
|
|
|
|
|
summary.dataset.openLabel = ui("collapse");
|
|
|
|
|
}
|
|
|
|
|
if (uiLang !== "en") return;
|
|
|
|
|
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
|
|
|
acceptNode(node) {
|
|
|
|
|
const parent = node.parentElement;
|
|
|
|
|
if (!parent || ["SCRIPT", "STYLE", "TEXTAREA", "INPUT"].includes(parent.tagName)) {
|
|
|
|
|
return NodeFilter.FILTER_REJECT;
|
|
|
|
|
}
|
|
|
|
|
return node.nodeValue.trim() ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
|
|
|
|
|
},
|
|
|
|
|
});
|
|
|
|
|
const textNodes = [];
|
|
|
|
|
while (walker.nextNode()) textNodes.push(walker.currentNode);
|
|
|
|
|
for (const node of textNodes) {
|
|
|
|
|
node.nodeValue = translateText(node.nodeValue);
|
|
|
|
|
}
|
|
|
|
|
for (const input of root.querySelectorAll?.("input[placeholder], textarea[placeholder]") || []) {
|
|
|
|
|
input.placeholder = translateText(input.placeholder);
|
|
|
|
|
}
|
|
|
|
|
for (const optgroup of root.querySelectorAll?.("optgroup[label]") || []) {
|
|
|
|
|
optgroup.label = translateText(optgroup.label);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function applyStaticTranslations() {
|
|
|
|
|
document.body.dataset.closedLabel = ui("expand");
|
|
|
|
|
document.body.dataset.openLabel = ui("collapse");
|
|
|
|
|
for (const summary of document.querySelectorAll("details.collapsible > summary, .manual-context > summary, .group-panel > summary")) {
|
|
|
|
|
summary.dataset.closedLabel = ui("expand");
|
|
|
|
|
summary.dataset.openLabel = ui("collapse");
|
|
|
|
|
}
|
|
|
|
|
setText("header .brand p", "tagline");
|
|
|
|
|
setText("label[for='section-jump']", "menu");
|
|
|
|
|
const navOptions = document.querySelectorAll("#section-jump option");
|
|
|
|
|
@@ -693,6 +939,7 @@ function applyStaticTranslations() {
|
|
|
|
|
setText("#settings h2", "settings");
|
|
|
|
|
setText("#settings .muted", "settings_hint");
|
|
|
|
|
setText("label[for='language-select']", "language");
|
|
|
|
|
localizeFragment(document);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function formatDateTime(value) {
|
|
|
|
|
@@ -881,6 +1128,7 @@ async function doLoadOverview() {
|
|
|
|
|
budget.textContent = loadMs <= DASHBOARD_TIMEOUT_MS
|
|
|
|
|
? `Bereit in ${loadMs} ms`
|
|
|
|
|
: `Langsam: ${loadMs} ms`;
|
|
|
|
|
budget.textContent = translateText(budget.textContent);
|
|
|
|
|
}
|
|
|
|
|
} catch (error) {
|
|
|
|
|
document.getElementById("configured-actuators").innerHTML = `<p class="bad">${escapeHtml(error.message)}</p>`;
|
|
|
|
|
@@ -918,10 +1166,11 @@ async function doLoadSystemOverview() {
|
|
|
|
|
budget.textContent = loadMs <= DASHBOARD_TIMEOUT_MS
|
|
|
|
|
? `Systemübersicht bereit in ${loadMs} ms`
|
|
|
|
|
: `Systemübersicht langsam: ${loadMs} ms`;
|
|
|
|
|
budget.textContent = translateText(budget.textContent);
|
|
|
|
|
}
|
|
|
|
|
scheduleDashboardExtras();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
document.getElementById("status").innerHTML = `<p class="warn">Systemübersicht verzögert: ${escapeHtml(error.message)}</p>`;
|
|
|
|
|
document.getElementById("status").innerHTML = `<p class="warn">${escapeHtml(translateText(`Systemübersicht verzögert: ${error.message}`))}</p>`;
|
|
|
|
|
if (budget) budget.textContent = ui("system_delayed");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
@@ -965,7 +1214,7 @@ async function loadDashboardExtras() {
|
|
|
|
|
if (reconciliation.status === "fulfilled") {
|
|
|
|
|
const text = document.getElementById("reconciliation-status");
|
|
|
|
|
if (text) {
|
|
|
|
|
text.textContent = `Letzte automatische Prüfung: ${formatDateTime(reconciliation.value.last_completed_at)}`;
|
|
|
|
|
text.textContent = translateText(`Letzte automatische Prüfung: ${formatDateTime(reconciliation.value.last_completed_at)}`);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} catch (_) {
|
|
|
|
|
@@ -1002,6 +1251,8 @@ async function loadStatus() {
|
|
|
|
|
status.innerHTML = `<p class="bad">${escapeHtml(error.message)}</p>`;
|
|
|
|
|
chips.innerHTML = "";
|
|
|
|
|
}
|
|
|
|
|
localizeFragment(status);
|
|
|
|
|
localizeFragment(chips);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderDashboardStatus(dashboard) {
|
|
|
|
|
@@ -1066,6 +1317,9 @@ function renderDashboardStatus(dashboard) {
|
|
|
|
|
`<div class="metric"><strong>Cache-Zeitpunkt</strong>${escapeHtml(cache.updated_at || "noch offen")}</div>`,
|
|
|
|
|
].join("");
|
|
|
|
|
renderJobQueue(jobs);
|
|
|
|
|
localizeFragment(status);
|
|
|
|
|
localizeFragment(chips);
|
|
|
|
|
localizeFragment(stats);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderJobQueue(jobs) {
|
|
|
|
|
@@ -1086,6 +1340,7 @@ function renderJobQueue(jobs) {
|
|
|
|
|
</div>
|
|
|
|
|
`).join("")}
|
|
|
|
|
` : "";
|
|
|
|
|
localizeFragment(jobsBox);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadSummaryData() {
|
|
|
|
|
@@ -1159,6 +1414,7 @@ function renderActuatorDiscovery() {
|
|
|
|
|
options.innerHTML = "";
|
|
|
|
|
select.innerHTML = `<option value="">Geräteliste konnte nicht geladen werden: ${escapeHtml(error.message)}</option>`;
|
|
|
|
|
}
|
|
|
|
|
localizeFragment(document.getElementById("choose"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadActuatorSuggestions() {
|
|
|
|
|
@@ -1185,6 +1441,7 @@ async function loadActuatorSuggestions() {
|
|
|
|
|
} catch (_) {
|
|
|
|
|
box.innerHTML = "";
|
|
|
|
|
}
|
|
|
|
|
localizeFragment(box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function actuatorGroupLabel(domain) {
|
|
|
|
|
@@ -1232,6 +1489,7 @@ function renderActuatorSelect() {
|
|
|
|
|
</optgroup>
|
|
|
|
|
`),
|
|
|
|
|
].join("");
|
|
|
|
|
localizeFragment(select);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function loadContextOptions(actuatorId) {
|
|
|
|
|
@@ -1277,6 +1535,7 @@ function renderManualContextSelect() {
|
|
|
|
|
select.innerHTML = filtered.length
|
|
|
|
|
? optionGroups(filtered, selectedNow)
|
|
|
|
|
: `<option value="">Keine passenden Vorschläge</option>`;
|
|
|
|
|
localizeFragment(select);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function parseEntityIds(value) {
|
|
|
|
|
@@ -1374,6 +1633,7 @@ function renderConfiguredActuators() {
|
|
|
|
|
} catch (error) {
|
|
|
|
|
box.textContent = error.message;
|
|
|
|
|
}
|
|
|
|
|
localizeFragment(box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
@@ -1386,6 +1646,7 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
const box = document.getElementById("actuator-detail");
|
|
|
|
|
if (!evaluationMessage && cachedDetailHtml.has(actuatorId)) {
|
|
|
|
|
box.innerHTML = cachedDetailHtml.get(actuatorId);
|
|
|
|
|
localizeFragment(box);
|
|
|
|
|
document.getElementById("detail").scrollIntoView({behavior: "smooth", block: "start"});
|
|
|
|
|
renderConfiguredActuators();
|
|
|
|
|
return;
|
|
|
|
|
@@ -1762,12 +2023,14 @@ async function showActuator(actuatorId, evaluationMessage = "") {
|
|
|
|
|
${manualAssignment}
|
|
|
|
|
`;
|
|
|
|
|
box.innerHTML = detailHtml;
|
|
|
|
|
cachedDetailHtml.set(actuatorId, detailHtml);
|
|
|
|
|
localizeFragment(box);
|
|
|
|
|
cachedDetailHtml.set(actuatorId, box.innerHTML);
|
|
|
|
|
document.getElementById("detail").scrollIntoView({behavior: "smooth", block: "start"});
|
|
|
|
|
renderConfiguredActuators();
|
|
|
|
|
} catch (error) {
|
|
|
|
|
box.textContent = error.message;
|
|
|
|
|
}
|
|
|
|
|
localizeFragment(box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function renderActuatorDetailShell(actuatorId) {
|
|
|
|
|
@@ -1784,6 +2047,7 @@ function renderActuatorDetailShell(actuatorId) {
|
|
|
|
|
<div class="metric"><strong>Kontext</strong>...</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
localizeFragment(document.getElementById("actuator-detail"));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrateContextOptions(record) {
|
|
|
|
|
@@ -1811,6 +2075,8 @@ async function hydrateContextOptions(record) {
|
|
|
|
|
${contextCategories.map(category => `<option value="${escapeHtml(category)}">${escapeHtml(category)}</option>`).join("")}
|
|
|
|
|
`;
|
|
|
|
|
renderManualContextSelect();
|
|
|
|
|
localizeFragment(numericSelect);
|
|
|
|
|
localizeFragment(categorySelect);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function hydrateCurrentContextOptions(actuatorId) {
|
|
|
|
|
@@ -1912,6 +2178,7 @@ async function simulateActuator(actuatorId) {
|
|
|
|
|
} catch (error) {
|
|
|
|
|
box.innerHTML = `<p class="bad">${escapeHtml(error.message)}</p>`;
|
|
|
|
|
}
|
|
|
|
|
localizeFragment(box);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
async function applySimulationWeights(actuatorId, scenarioId, startDryRun) {
|
|
|
|
|
|