Merge Stufe 4a (#764): WebUI-Texte ueber Sprachschluessel

This commit is contained in:
Richard
2026-10-03 19:21:56 +02:00
15 changed files with 182 additions and 116 deletions
+7 -5
View File
@@ -233,16 +233,16 @@ function aktualisiereInstanzPlakette() {
const res = await api(`/verwaltung/instanzen/${encodeURIComponent(aktive.id)}/start`, { const res = await api(`/verwaltung/instanzen/${encodeURIComponent(aktive.id)}/start`, {
method: "POST" method: "POST"
}); });
zeigeMeldung(res?.meldung || `Instanz '${aktive.name}' gestartet.`, "erfolg"); zeigeMeldung(res?.meldung || t("header.instanceStarted", { name: aktive.name }), "erfolg");
await aktualisiereInstanzenListe(); await aktualisiereInstanzenListe();
if (window.location.hash) { if (window.location.hash) {
window.dispatchEvent(new HashChangeEvent("hashchange")); window.dispatchEvent(new HashChangeEvent("hashchange"));
} }
} catch (err) { } catch (err) {
if (err.lizenzFehlt) { if (err.lizenzFehlt) {
zeigeMeldung("Start abgelehnt: Ohne gültige Lizenz startet keine Instanz (HTTP 402).", "gefahr", 6000); zeigeMeldung(t("header.startNoLicense"), "gefahr", 6000);
} else { } else {
zeigeMeldung(`Start fehlgeschlagen: ${err.message}`, "gefahr"); zeigeMeldung(t("header.startFailed", { error: err.message }), "gefahr");
} }
startBtn.disabled = false; startBtn.disabled = false;
startBtn.textContent = t("header.startInstance"); startBtn.textContent = t("header.startInstance");
@@ -319,9 +319,11 @@ async function aktualisiereKopfStatus() {
try { try {
const status = await api("/status"); const status = await api("/status");
if (kopfOpenRouter && status) { if (kopfOpenRouter && status) {
// #764: The field is apiReachable (StatusDto); isApiReachable never existed, so without
// credits text this always said "offline".
kopfOpenRouter.textContent = status.creditsText kopfOpenRouter.textContent = status.creditsText
? `OpenRouter: ${status.creditsText}` ? t("header.openRouterCredits", { credits: status.creditsText })
: (status.isApiReachable ? "OpenRouter: OK" : "OpenRouter: offline"); : (status.apiReachable ? t("header.openRouterOk") : t("header.openRouterOffline"));
} }
} catch (err) { } catch (err) {
if (kopfOpenRouter) { if (kopfOpenRouter) {
@@ -669,6 +669,59 @@
"logs.empty": "Noch keine Protokolleinträge vorhanden.", "logs.empty": "Noch keine Protokolleinträge vorhanden.",
"settings.title": "Einstellungen & Systemarchitektur", "settings.title": "Einstellungen & Systemarchitektur",
"common.error": "Fehler",
"common.errorWith": "Fehler: {error}",
"header.instanceStarted": "Instanz '{name}' gestartet.",
"header.startNoLicense": "Start abgelehnt: Ohne gültige Lizenz startet keine Instanz (HTTP 402).",
"header.startFailed": "Start fehlgeschlagen: {error}",
"header.openRouterCredits": "OpenRouter: {credits}",
"header.openRouterOk": "OpenRouter: OK",
"header.openRouterOffline": "OpenRouter: offline",
"login.tokenRejected": "Kein gültiges Token. Bitte neu anmelden.",
"backup.loadFailed": "Sicherungen konnten nicht geladen werden: {error}",
"backup.ftpLoadFailed": "Einstellungen nicht ladbar: {error}",
"info.agentEngine": "Agent-Engine",
"info.scheduler": "Scanner / Planer",
"info.apiOk": "OK",
"info.apiOffline": "Offline",
"info.credits": "Guthaben",
"instances.starting": "'{name}' wird gestartet…",
"instances.stopTitle": "Instanz anhalten",
"instances.stopExplanation": "Das Anhalten von '{name}' fährt alle laufenden Agenten und Prozesse geordnet herunter.",
"instances.instance": "Instanz",
"instances.confirmLocked": "Bestätigung gesperrt: {seconds}s…",
"instances.confirmUnlocked": "Entsperrt — bereit zur Bestätigung.",
"instances.stopping": "Wird angehalten…",
"instances.stopped": "'{name}' angehalten.",
"instances.stopFailed": "Anhalten fehlgeschlagen: {error}",
"logs.level.debug": "Debug",
"logs.level.info": "Info",
"logs.level.warning": "Warnung",
"logs.level.error": "Fehler",
"logs.level.critical": "Kritisch",
"logs.live": "Live",
"services.enabledMsg": "'{name}' aktiviert.",
"services.disabledMsg": "'{name}' deaktiviert.",
"services.deleteConfirm": "Dienst '{name}' ({id}) löschen?",
"services.deleted": "Dienst '{name}' gelöscht.",
"services.created": "Dienst '{name}' angelegt.",
"services.namePlaceholder": "z. B. WebHookService",
"services.typePlaceholder": "z. B. HTTP, TCP, Socket",
"services.portPlaceholder": "z. B. 8081 (optional)",
"tasks.loadError": "Fehler im Taskboard",
"tasks.untitled": "Aufgabe {id}",
"tasks.assignNewRun": "{name} (neuer Lauf)",
"tasks.assignChatHistory": "{name} (Chatverlauf)",
"tasks.assignHuman": "Mensch (kein Agent)",
"tasks.chatHistoryNotice": "⚠ Wiederkehrende Aufgaben im Chatverlauf lassen Kontext und Kosten wachsen.",
"tasks.scheduleValue": "Wert:",
"tasks.timezone": "Zeitzone:",
"tasks.acceptance": "Abnahmekriterien:",
"agents.modelPlaceholder": "z. B. anthropic/claude-sonnet-4-5",
"org.unitNamePlaceholder": "z. B. Marketing",
"org.workspaceNamePlaceholder": "z. B. Artikelstamm",
"org.protectedPathsPlaceholder": "z. B. prices, archive",
"org.votersPlaceholder": "buchhaltung\nlogistik\nmarketing",
"settings.updateCard": "Update", "settings.updateCard": "Update",
"settings.updateInstalled": "Installiert", "settings.updateInstalled": "Installiert",
"settings.updateLatest": "Neueste", "settings.updateLatest": "Neueste",
@@ -669,6 +669,59 @@
"logs.empty": "No log messages yet.", "logs.empty": "No log messages yet.",
"settings.title": "Settings & System Architecture", "settings.title": "Settings & System Architecture",
"common.error": "Error",
"common.errorWith": "Error: {error}",
"header.instanceStarted": "Instance '{name}' started.",
"header.startNoLicense": "Start refused: without a valid license no instance starts (HTTP 402).",
"header.startFailed": "Start failed: {error}",
"header.openRouterCredits": "OpenRouter: {credits}",
"header.openRouterOk": "OpenRouter: OK",
"header.openRouterOffline": "OpenRouter: offline",
"login.tokenRejected": "No valid token. Please sign in again.",
"backup.loadFailed": "Backups could not be loaded: {error}",
"backup.ftpLoadFailed": "Settings could not be loaded: {error}",
"info.agentEngine": "Agent engine",
"info.scheduler": "Scanner / scheduler",
"info.apiOk": "OK",
"info.apiOffline": "Offline",
"info.credits": "Credits",
"instances.starting": "Starting '{name}'…",
"instances.stopTitle": "Stop instance",
"instances.stopExplanation": "Stopping instance '{name}' winds down all running agents and processes in order.",
"instances.instance": "Instance",
"instances.confirmLocked": "Confirmation locked: {seconds}s…",
"instances.confirmUnlocked": "Unlocked — ready to confirm.",
"instances.stopping": "Stopping…",
"instances.stopped": "'{name}' stopped.",
"instances.stopFailed": "Stop failed: {error}",
"logs.level.debug": "Debug",
"logs.level.info": "Info",
"logs.level.warning": "Warning",
"logs.level.error": "Error",
"logs.level.critical": "Critical",
"logs.live": "Live",
"services.enabledMsg": "'{name}' enabled.",
"services.disabledMsg": "'{name}' disabled.",
"services.deleteConfirm": "Delete service '{name}' ({id})?",
"services.deleted": "Service '{name}' deleted.",
"services.created": "Service '{name}' created.",
"services.namePlaceholder": "e.g. WebHookService",
"services.typePlaceholder": "e.g. HTTP, TCP, Socket",
"services.portPlaceholder": "e.g. 8081 (optional)",
"tasks.loadError": "Taskboard error",
"tasks.untitled": "Task {id}",
"tasks.assignNewRun": "{name} (new run)",
"tasks.assignChatHistory": "{name} (chat history)",
"tasks.assignHuman": "Human (no agent)",
"tasks.chatHistoryNotice": "⚠ Recurring tasks in the chat history make the context and the costs grow.",
"tasks.scheduleValue": "Value:",
"tasks.timezone": "Time zone:",
"tasks.acceptance": "Acceptance criteria:",
"agents.modelPlaceholder": "e.g. anthropic/claude-sonnet-4-5",
"org.unitNamePlaceholder": "e.g. Marketing",
"org.workspaceNamePlaceholder": "e.g. Article master",
"org.protectedPathsPlaceholder": "e.g. prices, archive",
"org.votersPlaceholder": "accounting\nlogistics\nmarketing",
"settings.updateCard": "Update", "settings.updateCard": "Update",
"settings.updateInstalled": "Installed", "settings.updateInstalled": "Installed",
"settings.updateLatest": "Latest", "settings.updateLatest": "Latest",
@@ -2,6 +2,8 @@
// //
// Verwaltet das Zugangstoken im sessionStorage und kapselt fetch-Aufrufe. // Verwaltet das Zugangstoken im sessionStorage und kapselt fetch-Aufrufe.
import { t } from "./i18n.js";
export const TOKEN_SCHLUESSEL = "clawd.token"; export const TOKEN_SCHLUESSEL = "clawd.token";
export const INSTANZ_SCHLUESSEL = "clawd.instanz"; export const INSTANZ_SCHLUESSEL = "clawd.instanz";
@@ -145,7 +147,7 @@ export async function api(pfad, optionen = {}) {
if (antwort.status === 401) { if (antwort.status === 401) {
meldeUnberechtigt(); meldeUnberechtigt();
const fehler = new Error("Kein gültiges Token. Bitte neu anmelden."); const fehler = new Error(t("login.tokenRejected"));
fehler.abgelehnt = true; fehler.abgelehnt = true;
fehler.status = 401; fehler.status = 401;
throw fehler; throw fehler;
@@ -2,7 +2,7 @@
// //
// Guide rule 3.1: foreign text is never HTML. No innerHTML anywhere in the tree. // Guide rule 3.1: foreign text is never HTML. No innerHTML anywhere in the tree.
import { t } from "./i18n.js"; import { t, currentLanguage } from "./i18n.js";
const SVG_NS = "http://www.w3.org/2000/svg"; const SVG_NS = "http://www.w3.org/2000/svg";
@@ -256,11 +256,19 @@ export function erstelleModal(titel, inhalt, aktionen = [], optionen = {}) {
* @param {string|Date|number|null} wert * @param {string|Date|number|null} wert
* @returns {string} * @returns {string}
*/ */
/**
* #764: The locale of the active language. Dates were always formatted "de-DE", also in the
* English interface. en-GB rather than en-US: day before month, like the German format.
*/
function locale() {
return currentLanguage() === "de" ? "de-DE" : "en-GB";
}
export function formatiereZeit(wert) { export function formatiereZeit(wert) {
if (!wert) return "—"; if (!wert) return "—";
const d = wert instanceof Date ? wert : new Date(wert); const d = wert instanceof Date ? wert : new Date(wert);
if (isNaN(d.getTime())) return String(wert); if (isNaN(d.getTime())) return String(wert);
return d.toLocaleTimeString("de-DE", { hour: "2-digit", minute: "2-digit", second: "2-digit" }); return d.toLocaleTimeString(locale(), { hour: "2-digit", minute: "2-digit", second: "2-digit" });
} }
/** /**
@@ -272,7 +280,7 @@ export function formatiereDatumZeit(wert) {
if (!wert) return "—"; if (!wert) return "—";
const d = wert instanceof Date ? wert : new Date(wert); const d = wert instanceof Date ? wert : new Date(wert);
if (isNaN(d.getTime())) return String(wert); if (isNaN(d.getTime())) return String(wert);
return d.toLocaleString("de-DE", { return d.toLocaleString(locale(), {
day: "2-digit", day: "2-digit",
month: "2-digit", month: "2-digit",
year: "numeric", year: "numeric",
@@ -389,7 +389,7 @@ function oeffneNeuerAgentDialog() {
const modellFeld = erstelleElement("input", { const modellFeld = erstelleElement("input", {
type: "text", type: "text",
required: true, required: true,
placeholder: "z.B. anthropic/claude-3.5-sonnet", placeholder: t("agents.modelPlaceholder"),
value: "anthropic/claude-3.5-sonnet", value: "anthropic/claude-3.5-sonnet",
id: "neu-agent-modell" id: "neu-agent-modell"
}); });
@@ -187,8 +187,8 @@ export async function rendereSicherung() {
inhaltBereich.replaceChildren(erstelleKarte(t("backup.existingBackups", { count: backups.length }), [tabelle])); inhaltBereich.replaceChildren(erstelleKarte(t("backup.existingBackups", { count: backups.length }), [tabelle]));
} catch (err) { } catch (err) {
inhaltBereich.replaceChildren( inhaltBereich.replaceChildren(
erstelleKarte("Fehler", [ erstelleKarte(t("common.error"), [
erstelleElement("p", { klasse: "fehler-meldung", text: `Sicherungen konnten nicht geladen werden: ${err.message}` }) erstelleElement("p", { klasse: "fehler-meldung", text: t("backup.loadFailed", { error: err.message }) })
]) ])
); );
} }
@@ -426,7 +426,7 @@ async function rendereFtp(bereich) {
stand = await api("/verwaltung/sicherung/ftp"); stand = await api("/verwaltung/sicherung/ftp");
} catch (err) { } catch (err) {
bereich.replaceChildren(erstelleKarte(t("backup.ftpTitle"), [ bereich.replaceChildren(erstelleKarte(t("backup.ftpTitle"), [
erstelleElement("p", { klasse: "fehler-meldung", text: `Einstellungen nicht ladbar: ${err.message}` }) erstelleElement("p", { klasse: "fehler-meldung", text: t("backup.ftpLoadFailed", { error: err.message }) })
])); ]));
return; return;
} }
@@ -212,7 +212,7 @@ async function ladeAgenten(gewuenschteId = null) {
agentenSelector.value = zielId; agentenSelector.value = zielId;
await waehleAgent(zielId); await waehleAgent(zielId);
} catch (err) { } catch (err) {
zeigeMeldung(`Fehler: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} }
} }
@@ -64,9 +64,9 @@ export async function rendereInfo() {
// 2. Teilsysteme // 2. Teilsysteme
const dlSysteme = erstelleElement("dl", { klasse: "datenliste" }); const dlSysteme = erstelleElement("dl", { klasse: "datenliste" });
dlSysteme.append( dlSysteme.append(
erstelleElement("dt", { text: "Agent Engine" }), erstelleElement("dt", { text: t("info.agentEngine") }),
erstelleElement("dd", {}, erstellePlakette(info.engineActive ? (t("status.running")) : (t("status.stopped")), info.engineActive ? "erfolg" : "neutral")), erstelleElement("dd", {}, erstellePlakette(info.engineActive ? (t("status.running")) : (t("status.stopped")), info.engineActive ? "erfolg" : "neutral")),
erstelleElement("dt", { text: "Scanner / Scheduler" }), erstelleElement("dt", { text: t("info.scheduler") }),
erstelleElement("dd", {}, erstellePlakette(info.schedulerActive ? (t("status.running")) : (t("status.stopped")), info.schedulerActive ? "erfolg" : "neutral")), erstelleElement("dd", {}, erstellePlakette(info.schedulerActive ? (t("status.running")) : (t("status.stopped")), info.schedulerActive ? "erfolg" : "neutral")),
erstelleElement("dt", { text: t("nav.staging") }), erstelleElement("dt", { text: t("nav.staging") }),
erstelleElement("dd", {}, erstellePlakette(info.stagingActive ? (t("status.running")) : (t("status.stopped")), info.stagingActive ? "erfolg" : "neutral")), erstelleElement("dd", {}, erstellePlakette(info.stagingActive ? (t("status.running")) : (t("status.stopped")), info.stagingActive ? "erfolg" : "neutral")),
@@ -81,10 +81,10 @@ export async function rendereInfo() {
if (status) { if (status) {
dlStatus.append( dlStatus.append(
erstelleElement("dt", { text: "API" }), erstelleElement("dt", { text: "API" }),
erstelleElement("dd", {}, erstellePlakette(status.isApiReachable ? "OK" : "Offline", status.isApiReachable ? "erfolg" : "gefahr")), erstelleElement("dd", {}, erstellePlakette(status.apiReachable ? t("info.apiOk") : t("info.apiOffline"), status.apiReachable ? "erfolg" : "gefahr")),
erstelleElement("dt", { text: t("common.status") }), erstelleElement("dt", { text: t("common.status") }),
erstelleElement("dd", { text: status.statusText || "—" }), erstelleElement("dd", { text: status.statusText || "—" }),
erstelleElement("dt", { text: "Credits" }), erstelleElement("dt", { text: t("info.credits") }),
erstelleElement("dd", {}, erstelleElement("strong", { text: status.creditsText || "—" })) erstelleElement("dd", {}, erstelleElement("strong", { text: status.creditsText || "—" }))
); );
} else { } else {
@@ -188,7 +188,7 @@ async function ladeInstanzen(istHintergrund = false) {
} catch (err) { } catch (err) {
if (!istHintergrund) { if (!istHintergrund) {
raster.replaceChildren( raster.replaceChildren(
erstelleElement("div", { klasse: "fehler-zentriert", text: `Error: ${err.message}` }) erstelleElement("div", { klasse: "fehler-zentriert", text: t("common.errorWith", { error: err.message }) })
); );
} }
} }
@@ -199,16 +199,16 @@ async function starteInstanz(instanz) {
const antwort = await api(`/verwaltung/instanzen/${encodeURIComponent(instanz.id)}/start`, { const antwort = await api(`/verwaltung/instanzen/${encodeURIComponent(instanz.id)}/start`, {
method: "POST" method: "POST"
}); });
zeigeMeldung(antwort?.meldung || `Starting '${instanz.name}'…`, "erfolg"); zeigeMeldung(antwort?.meldung || t("instances.starting", { name: instanz.name }), "erfolg");
if (!holeAktiveInstanz()) { if (!holeAktiveInstanz()) {
setzeAktiveInstanz(instanz.id); setzeAktiveInstanz(instanz.id);
} }
await ladeInstanzen(); await ladeInstanzen();
} catch (err) { } catch (err) {
if (err.lizenzFehlt) { if (err.lizenzFehlt) {
zeigeMeldung("License required (HTTP 402).", "gefahr", 6000); zeigeMeldung(t("header.startNoLicense"), "gefahr", 6000);
} else { } else {
zeigeMeldung(`Failed to start: ${err.message}`, "gefahr"); zeigeMeldung(t("header.startFailed", { error: err.message }), "gefahr");
} }
} }
} }
@@ -228,16 +228,16 @@ function oeffneSicherheitsStoppDialog(instanz) {
"h3", "h3",
{}, {},
erstelleIcon(PFADE.ausrufezeichen, 18), erstelleIcon(PFADE.ausrufezeichen, 18),
erstelleElement("span", { text: " Stop Instance" }) erstelleElement("span", { text: ` ${t("instances.stopTitle")}` })
); );
const boxText1 = erstelleElement("p", { const boxText1 = erstelleElement("p", {
text: `Stopping instance '${instanz.name}' will gracefully shut down all active agents and processes.` text: t("instances.stopExplanation", { name: instanz.name })
}); });
sicherheitsBox.append(boxTitel, boxText1); sicherheitsBox.append(boxTitel, boxText1);
const dl = erstelleElement("dl", { klasse: "datenliste" }); const dl = erstelleElement("dl", { klasse: "datenliste" });
dl.append( dl.append(
erstelleElement("dt", { text: "Instance" }), erstelleElement("dt", { text: t("instances.instance") }),
erstelleElement("dd", {}, erstelleElement("strong", { text: instanz.name })), erstelleElement("dd", {}, erstelleElement("strong", { text: instanz.name })),
erstelleElement("dt", { text: "ID" }), erstelleElement("dt", { text: "ID" }),
erstelleElement("dd", {}, erstelleElement("code", { text: instanz.id })), erstelleElement("dd", {}, erstelleElement("code", { text: instanz.id })),
@@ -247,7 +247,7 @@ function oeffneSicherheitsStoppDialog(instanz) {
const countdownBox = erstelleElement("div", { const countdownBox = erstelleElement("div", {
klasse: "sicherheits-countdown", klasse: "sicherheits-countdown",
text: `Confirmation locked: ${verbleibend}s…` text: t("instances.confirmLocked", { seconds: verbleibend })
}); });
inhalt.append(sicherheitsBox, dl, countdownBox); inhalt.append(sicherheitsBox, dl, countdownBox);
@@ -287,7 +287,7 @@ function oeffneSicherheitsStoppDialog(instanz) {
timerId = setInterval(() => { timerId = setInterval(() => {
verbleibend--; verbleibend--;
if (verbleibend > 0) { if (verbleibend > 0) {
countdownBox.textContent = `Confirmation locked: ${verbleibend}s…`; countdownBox.textContent = t("instances.confirmLocked", { seconds: verbleibend });
const textSpan = anhaltenBtn.querySelector("span"); const textSpan = anhaltenBtn.querySelector("span");
if (textSpan) textSpan.textContent = ` ${t("instances.stop")} (${verbleibend}s)`; if (textSpan) textSpan.textContent = ` ${t("instances.stop")} (${verbleibend}s)`;
} else { } else {
@@ -297,7 +297,7 @@ function oeffneSicherheitsStoppDialog(instanz) {
const textSpan = anhaltenBtn.querySelector("span"); const textSpan = anhaltenBtn.querySelector("span");
if (textSpan) textSpan.textContent = ` ${t("instances.stop")}`; if (textSpan) textSpan.textContent = ` ${t("instances.stop")}`;
countdownBox.className = "sicherheits-countdown abgelaufen"; countdownBox.className = "sicherheits-countdown abgelaufen";
countdownBox.textContent = "Unlocked — confirmation ready."; countdownBox.textContent = t("instances.confirmUnlocked");
} }
}, 1000); }, 1000);
@@ -305,17 +305,17 @@ function oeffneSicherheitsStoppDialog(instanz) {
if (verbleibend > 0) return; if (verbleibend > 0) return;
anhaltenBtn.disabled = true; anhaltenBtn.disabled = true;
const textSpan = anhaltenBtn.querySelector("span"); const textSpan = anhaltenBtn.querySelector("span");
if (textSpan) textSpan.textContent = " Stopping…"; if (textSpan) textSpan.textContent = ` ${t("instances.stopping")}`;
try { try {
const antwort = await api(`/verwaltung/instanzen/${encodeURIComponent(instanz.id)}/stop`, { const antwort = await api(`/verwaltung/instanzen/${encodeURIComponent(instanz.id)}/stop`, {
method: "POST" method: "POST"
}); });
modal.schliessen(); modal.schliessen();
zeigeMeldung(antwort?.meldung || `'${instanz.name}' stopped.`, "info"); zeigeMeldung(antwort?.meldung || t("instances.stopped", { name: instanz.name }), "info");
await ladeInstanzen(); await ladeInstanzen();
} catch (err) { } catch (err) {
zeigeMeldung(`Stop failed: ${err.message}`, "gefahr"); zeigeMeldung(t("instances.stopFailed", { error: err.message }), "gefahr");
anhaltenBtn.disabled = false; anhaltenBtn.disabled = false;
if (textSpan) textSpan.textContent = ` ${t("instances.stop")}`; if (textSpan) textSpan.textContent = ` ${t("instances.stop")}`;
} }
@@ -70,11 +70,11 @@ export async function rendereProtokoll() {
}); });
const levelOptionen = [ const levelOptionen = [
["All", t("common.all")], ["All", t("common.all")],
["Debug", "Debug"], ["Debug", t("logs.level.debug")],
["Information", "Info"], ["Information", t("logs.level.info")],
["Warning", "Warning"], ["Warning", t("logs.level.warning")],
["Error", "Error"], ["Error", t("logs.level.error")],
["Critical", "Critical"] ["Critical", t("logs.level.critical")]
]; ];
for (const [val, label] of levelOptionen) { for (const [val, label] of levelOptionen) {
levelSelect.append(erstelleElement("option", { value: val, text: label })); levelSelect.append(erstelleElement("option", { value: val, text: label }));
@@ -252,7 +252,7 @@ function togglePause() {
function aktualisiereStatus(offen, text = null) { function aktualisiereStatus(offen, text = null) {
if (!streamStatusPlakette) return; if (!streamStatusPlakette) return;
const statusT = text || (offen ? "Live" : "—"); const statusT = text || (offen ? t("logs.live") : "—");
const art = offen ? "erfolg" : "neutral"; const art = offen ? "erfolg" : "neutral";
const neuePlakette = erstellePlakette(statusT, art); const neuePlakette = erstellePlakette(statusT, art);
streamStatusPlakette.replaceWith(neuePlakette); streamStatusPlakette.replaceWith(neuePlakette);
@@ -270,7 +270,7 @@ function select(sel) {
function openCreateUnit(parentId = null) { function openCreateUnit(parentId = null) {
const id = erstelleElement("input", { type: "text", placeholder: "marketing", required: true }); const id = erstelleElement("input", { type: "text", placeholder: "marketing", required: true });
const name = erstelleElement("input", { type: "text", placeholder: "Marketing", required: true }); const name = erstelleElement("input", { type: "text", placeholder: t("org.unitNamePlaceholder"), required: true });
const parent = unitOptions(erstelleElement("select"), parentId); const parent = unitOptions(erstelleElement("select"), parentId);
const goal = erstelleElement("textarea", { rows: 2 }); const goal = erstelleElement("textarea", { rows: 2 });
@@ -668,7 +668,7 @@ function votesCard(unit) {
const current = unit.votes; const current = unit.votes;
const ops = erstelleElement("textarea", { rows: 2, placeholder: "Database.insert" }); const ops = erstelleElement("textarea", { rows: 2, placeholder: "Database.insert" });
ops.value = (current?.operations ?? []).join("\n"); ops.value = (current?.operations ?? []).join("\n");
const voters = erstelleElement("textarea", { rows: 3, placeholder: "accounting\nlogistics\nmarketing" }); const voters = erstelleElement("textarea", { rows: 3, placeholder: t("org.votersPlaceholder") });
voters.value = (current?.voters ?? []).join("\n"); voters.value = (current?.voters ?? []).join("\n");
const number = (value, min) => erstelleElement("input", { type: "number", min: String(min), step: "1", value: String(value) }); const number = (value, min) => erstelleElement("input", { type: "number", min: String(min), step: "1", value: String(value) });
const approveAt = number(current?.approveAt ?? 3, 1); const approveAt = number(current?.approveAt ?? 3, 1);
@@ -944,7 +944,7 @@ function unitWorkspaceCard(unit) {
function openCreateWorkspace() { function openCreateWorkspace() {
const id = erstelleElement("input", { type: "text", placeholder: "articles", required: true }); const id = erstelleElement("input", { type: "text", placeholder: "articles", required: true });
const name = erstelleElement("input", { type: "text", placeholder: "Article master", required: true }); const name = erstelleElement("input", { type: "text", placeholder: t("org.workspaceNamePlaceholder"), required: true });
const purpose = erstelleElement("textarea", { rows: 2 }); const purpose = erstelleElement("textarea", { rows: 2 });
const owner = unitOptions(erstelleElement("select"), null, new Set(), t("org.noOwner")); const owner = unitOptions(erstelleElement("select"), null, new Set(), t("org.noOwner"));
@@ -982,7 +982,7 @@ function workspacePanel(workspace) {
const name = erstelleElement("input", { type: "text", value: workspace.name }); const name = erstelleElement("input", { type: "text", value: workspace.name });
const purpose = erstelleElement("textarea", { rows: 2 }); const purpose = erstelleElement("textarea", { rows: 2 });
purpose.value = workspace.purpose ?? ""; purpose.value = workspace.purpose ?? "";
const protectedPaths = erstelleElement("input", { type: "text", value: workspace.protectedPaths.join(", "), placeholder: "prices, archive" }); const protectedPaths = erstelleElement("input", { type: "text", value: workspace.protectedPaths.join(", "), placeholder: t("org.protectedPathsPlaceholder") });
const general = erstelleKarte(workspace.name, [ const general = erstelleKarte(workspace.name, [
erstelleElement("p", { klasse: "gedaempft text-klein", erstelleElement("p", { klasse: "gedaempft text-klein",
@@ -1,52 +0,0 @@
// ClawdDotNet — Platzhalter-Ansichten für noch nicht freigegebene Kern-Endpunkte
//
// Leitfaden Abschnitt 7: Sicherung, Einstellungen und Agenten-Chats (Analyse).
import { erstelleElement, erstelleKarte, erstelleIcon, PFADE } from "../modules/dom.js";
export function rendereSicherung() {
const container = document.getElementById("ansicht-inhalt");
if (!container) return;
container.replaceChildren();
const layout = erstelleElement("div", { klasse: "platzhalter-ansicht" });
layout.append(erstelleElement("h1", { text: "Datensicherung & Wiederherstellung" }));
const karte = erstelleKarte("In Vorbereitung", [
erstelleElement("p", {
klasse: "gedaempft",
text: "Die API-Endpunkte für die automatisierte Sicherung und Rücksicherung über das WebUI befinden sich in Arbeit."
}),
erstelleElement("p", {
klasse: "hinweis",
text: "Bitte verwende für die Sicherung von Instanzen aktuell die Desktop-Oberfläche von ClawdDotNet."
})
]);
layout.append(karte);
container.append(layout);
}
export function rendereAgentenChats() {
const container = document.getElementById("ansicht-inhalt");
if (!container) return;
container.replaceChildren();
const layout = erstelleElement("div", { klasse: "platzhalter-ansicht" });
layout.append(erstelleElement("h1", { text: "Agenten-Kommunikation" }));
const karte = erstelleKarte("Analyse: Interne Agenten-Chats", [
erstelleElement("p", {
klasse: "gedaempft",
text: "Die Protokollierung und Auswertung der direkten Kommunikation zwischen zwei Agenten (A2A) wird mit Vorhaben #208 bereitgestellt."
}),
erstelleElement("p", {
klasse: "hinweis",
text: "Direkte Chats mit Agenten können im Bereich 'Chat' geführt werden."
})
]);
layout.append(karte);
container.append(layout);
}
@@ -111,10 +111,10 @@ async function ladeDienste() {
method: "PUT" method: "PUT"
}); });
s.enabled = aktiv; s.enabled = aktiv;
zeigeMeldung(`'${s.name}' ${aktiv ? "enabled" : "disabled"}.`, "info"); zeigeMeldung(t(aktiv ? "services.enabledMsg" : "services.disabledMsg", { name: s.name }), "info");
} catch (err) { } catch (err) {
e.target.checked = !aktiv; e.target.checked = !aktiv;
zeigeMeldung(`Error: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} }
} }
}); });
@@ -152,14 +152,14 @@ async function ladeDienste() {
} }
async function loescheDienst(s) { async function loescheDienst(s) {
if (!confirm(`Delete service '${s.name}' (${s.id})?`)) return; if (!confirm(t("services.deleteConfirm", { name: s.name, id: s.id }))) return;
try { try {
await api(`/services/${encodeURIComponent(s.id)}`, { method: "DELETE" }); await api(`/services/${encodeURIComponent(s.id)}`, { method: "DELETE" });
zeigeMeldung(`Service '${s.name}' deleted.`, "info"); zeigeMeldung(t("services.deleted", { name: s.name }), "info");
await ladeDienste(); await ladeDienste();
} catch (err) { } catch (err) {
zeigeMeldung(`Failed: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} }
} }
@@ -169,20 +169,20 @@ function oeffneNeuerDienstDialog() {
const nameFeld = erstelleElement("input", { const nameFeld = erstelleElement("input", {
type: "text", type: "text",
required: true, required: true,
placeholder: "e.g. WebHookService" placeholder: t("services.namePlaceholder")
}); });
const typFeld = erstelleElement("input", { const typFeld = erstelleElement("input", {
type: "text", type: "text",
required: true, required: true,
placeholder: "e.g. HTTP, TCP, Socket" placeholder: t("services.typePlaceholder")
}); });
const portFeld = erstelleElement("input", { const portFeld = erstelleElement("input", {
type: "number", type: "number",
min: "1", min: "1",
max: "65535", max: "65535",
placeholder: "e.g. 8081 (optional)" placeholder: t("services.portPlaceholder")
}); });
const descFeld = erstelleElement("textarea", { const descFeld = erstelleElement("textarea", {
@@ -231,10 +231,10 @@ function oeffneNeuerDienstDialog() {
} }
}); });
modal.schliessen(); modal.schliessen();
zeigeMeldung(`Service '${nameFeld.value.trim()}' created.`, "erfolg"); zeigeMeldung(t("services.created", { name: nameFeld.value.trim() }), "erfolg");
await ladeDienste(); await ladeDienste();
} catch (err) { } catch (err) {
zeigeMeldung(`Error: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} finally { } finally {
anlegenBtn.disabled = false; anlegenBtn.disabled = false;
} }
@@ -155,7 +155,7 @@ async function ladeTaskboard() {
container.append(raster); container.append(raster);
} catch (err) { } catch (err) {
container.replaceChildren( container.replaceChildren(
erstelleKarte("Taskboard Error", [ erstelleKarte(t("tasks.loadError"), [
erstelleElement("p", { klasse: "gedaempft", text: err.message }) erstelleElement("p", { klasse: "gedaempft", text: err.message })
]) ])
); );
@@ -343,7 +343,7 @@ function erstelleAufgabenKarte(task) {
); );
zeilen.push(aktionen); zeilen.push(aktionen);
return erstelleKarte(task.title || `Task ${task.id}`, zeilen); return erstelleKarte(task.title || t("tasks.untitled", { id: task.id }), zeilen);
} }
async function fuehreAus(task) { async function fuehreAus(task) {
@@ -352,7 +352,7 @@ async function fuehreAus(task) {
zeigeMeldung(res?.message || t("tasks.runTriggered"), "erfolg"); zeigeMeldung(res?.message || t("tasks.runTriggered"), "erfolg");
setTimeout(ladeTaskboard, 1500); setTimeout(ladeTaskboard, 1500);
} catch (err) { } catch (err) {
zeigeMeldung(`Error: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} }
} }
@@ -362,7 +362,7 @@ async function setzeAktiv(task, aktiv) {
zeigeMeldung(res?.message || (aktiv ? t("tasks.resume") : t("tasks.pause")), "info"); zeigeMeldung(res?.message || (aktiv ? t("tasks.resume") : t("tasks.pause")), "info");
await ladeTaskboard(); await ladeTaskboard();
} catch (err) { } catch (err) {
zeigeMeldung(`Error: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} }
} }
@@ -384,11 +384,11 @@ async function oeffneAufgabenDialog(task) {
const zuordnung = erstelleElement("select", { required: true }); const zuordnung = erstelleElement("select", { required: true });
for (const a of agenten) { for (const a of agenten) {
zuordnung.append( zuordnung.append(
erstelleElement("option", { value: `@new:${a.agentId}`, text: `${a.displayName} (New Run)` }), erstelleElement("option", { value: `@new:${a.agentId}`, text: t("tasks.assignNewRun", { name: a.displayName }) }),
erstelleElement("option", { value: `@${a.agentId}`, text: `${a.displayName} (Chat History)` }) erstelleElement("option", { value: `@${a.agentId}`, text: t("tasks.assignChatHistory", { name: a.displayName }) })
); );
} }
zuordnung.append(erstelleElement("option", { value: "@human", text: "Human (No Agent)" })); zuordnung.append(erstelleElement("option", { value: "@human", text: t("tasks.assignHuman") }));
if (task?.assignee && ![...zuordnung.options].some((o) => o.value === task.assignee)) { if (task?.assignee && ![...zuordnung.options].some((o) => o.value === task.assignee)) {
zuordnung.append(erstelleElement("option", { value: task.assignee, text: task.assignee })); zuordnung.append(erstelleElement("option", { value: task.assignee, text: task.assignee }));
} }
@@ -397,7 +397,7 @@ async function oeffneAufgabenDialog(task) {
const terminArt = erstelleElement("select"); const terminArt = erstelleElement("select");
terminArt.append( terminArt.append(
erstelleElement("option", { value: "", text: t("tasks.scheduleNone") }), erstelleElement("option", { value: "", text: t("tasks.scheduleNone") }),
erstelleElement("option", { value: "cron", text: "Cron" }), erstelleElement("option", { value: "cron", text: t("tasks.scheduleCron") }),
erstelleElement("option", { value: "every", text: t("tasks.scheduleInterval") }), erstelleElement("option", { value: "every", text: t("tasks.scheduleInterval") }),
erstelleElement("option", { value: "at", text: t("tasks.scheduleAt") }) erstelleElement("option", { value: "at", text: t("tasks.scheduleAt") })
); );
@@ -439,7 +439,7 @@ async function oeffneAufgabenDialog(task) {
const wiederkehrend = terminArt.value === "cron" || terminArt.value === "every"; const wiederkehrend = terminArt.value === "cron" || terminArt.value === "every";
const imVerlauf = zuordnung.value.startsWith("@") && !zuordnung.value.startsWith("@new") && zuordnung.value !== "@human"; const imVerlauf = zuordnung.value.startsWith("@") && !zuordnung.value.startsWith("@new") && zuordnung.value !== "@human";
hinweis.hidden = !(wiederkehrend && imVerlauf); hinweis.hidden = !(wiederkehrend && imVerlauf);
hinweis.textContent = "⚠ Notice: Running recurring tasks in chat history increases context and costs."; hinweis.textContent = t("tasks.chatHistoryNotice");
const ohneTermin = terminArt.value === ""; const ohneTermin = terminArt.value === "";
terminWert.disabled = ohneTermin; terminWert.disabled = ohneTermin;
@@ -459,9 +459,9 @@ async function oeffneAufgabenDialog(task) {
erstelleElement("label", { text: t("tasks.taskMessage") + ":" }), beschreibung, erstelleElement("label", { text: t("tasks.taskMessage") + ":" }), beschreibung,
erstelleElement("label", { text: t("tasks.agent") + ":" }), zuordnung, erstelleElement("label", { text: t("tasks.agent") + ":" }), zuordnung,
erstelleElement("label", { text: t("tasks.scheduleType") + ":" }), terminArt, erstelleElement("label", { text: t("tasks.scheduleType") + ":" }), terminArt,
erstelleElement("label", { text: "Value:" }), terminWert, erstelleElement("label", { text: t("tasks.scheduleValue") }), terminWert,
erstelleElement("label", { text: "Timezone:" }), zeitzone, erstelleElement("label", { text: t("tasks.timezone") }), zeitzone,
erstelleElement("label", { text: "Acceptance Criteria:" }), abnahme, erstelleElement("label", { text: t("tasks.acceptance") }), abnahme,
erstelleElement("label", { text: t("tasks.parent") + ":" }), eltern, erstelleElement("label", { text: t("tasks.parent") + ":" }), eltern,
erstelleElement("label", { text: t("tasks.fillBy") + ":", title: t("tasks.fillByHint") }), fuellBis, erstelleElement("label", { text: t("tasks.fillBy") + ":", title: t("tasks.fillByHint") }), fuellBis,
erstelleElement("label", { text: t("tasks.estimateUsd") + ":" }), schaetzung, erstelleElement("label", { text: t("tasks.estimateUsd") + ":" }), schaetzung,
@@ -513,7 +513,7 @@ async function oeffneAufgabenDialog(task) {
zeigeMeldung(text, "erfolg"); zeigeMeldung(text, "erfolg");
await ladeTaskboard(); await ladeTaskboard();
} catch (err) { } catch (err) {
zeigeMeldung(`Error: ${err.message}`, "gefahr"); zeigeMeldung(t("common.errorWith", { error: err.message }), "gefahr");
} finally { } finally {
speichern.disabled = false; speichern.disabled = false;
} }