108 lines
3.9 KiB
HTML
108 lines
3.9 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>boehmitools · Einstellungen</title>
|
|
<link rel="stylesheet" href="/shared/boehmi.css">
|
|
<style>
|
|
.field { margin-bottom: 12px; }
|
|
.field .help { font-size: 11.5px; color: var(--bt-muted); margin-top: 4px; }
|
|
.sticky-actions {
|
|
position: sticky; bottom: 0; margin-top: 18px; padding: 12px 0;
|
|
background: linear-gradient(transparent, var(--bt-bg) 40%);
|
|
display: flex; gap: 8px; align-items: center;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body data-bt-title="Einstellungen" data-bt-icon="⚙">
|
|
|
|
<main class="bt-main narrow">
|
|
<div class="bt-pagehead">
|
|
<h1>Einstellungen</h1>
|
|
<p>Diese Zugänge gelten für alle Tools der Suite. Sie liegen in
|
|
<code>data/settings.json</code> und werden den Tools als Umgebungsvariablen
|
|
übergeben — genau so, wie sie es als Einzeltool erwarten.</p>
|
|
</div>
|
|
|
|
<div class="bt-notice info">
|
|
Tokens und Schlüssel bleiben im lokalen Backend. Bereits gespeicherte
|
|
Geheimnisse werden hier nur maskiert angezeigt; ein leeres Feld lässt den
|
|
bestehenden Wert unverändert.
|
|
</div>
|
|
|
|
<div id="groups"></div>
|
|
|
|
<div class="sticky-actions">
|
|
<button class="primary" id="save">Speichern</button>
|
|
<span id="state" class="bt-muted"></span>
|
|
</div>
|
|
</main>
|
|
|
|
<script src="/shared/boehmi.js"></script>
|
|
<script>
|
|
const groupsEl = document.getElementById("groups");
|
|
const stateEl = document.getElementById("state");
|
|
let schema = [];
|
|
|
|
function fieldHtml(f, value) {
|
|
const id = "f_" + f.key;
|
|
let control;
|
|
if (f.kind === "bool") {
|
|
control = `<label class="bt-check"><input type="checkbox" id="${id}"
|
|
${String(value).toLowerCase() === "true" ? "checked" : ""}> aktiv</label>`;
|
|
} else if (f.kind === "select") {
|
|
control = `<select id="${id}">${f.options.map((o) =>
|
|
`<option ${o === value ? "selected" : ""}>${BT.escape(o)}</option>`).join("")}</select>`;
|
|
} else if (f.kind === "secret") {
|
|
control = `<input type="password" id="${id}" value="${BT.escape(value)}"
|
|
placeholder="${value ? "unverändert" : "nicht gesetzt"}" autocomplete="new-password">`;
|
|
} else {
|
|
control = `<input type="${f.kind === "number" ? "number" : "text"}" id="${id}"
|
|
value="${BT.escape(value)}" placeholder="${BT.escape(f.placeholder || f.default || "")}">`;
|
|
}
|
|
return `<div class="field">
|
|
<label for="${id}">${BT.escape(f.label)}</label>
|
|
${control}
|
|
${f.help ? `<div class="help">${BT.escape(f.help)}</div>` : ""}
|
|
</div>`;
|
|
}
|
|
|
|
async function load() {
|
|
const data = await fetch("/api/settings").then((r) => r.json());
|
|
schema = data.schema;
|
|
groupsEl.innerHTML = schema.map((g) => `
|
|
<section class="bt-card">
|
|
<h2>${BT.escape(g.group)}</h2>
|
|
<p class="hint">${g.group === "Tandoor"
|
|
? "Wird vom AI-Webimport, vom Rezeptimport und vom Vereinheitlichen genutzt."
|
|
: g.group === "OpenAI" ? "Nur für den AI-Webimport nötig."
|
|
: "Schutz beim Abruf externer Rezeptseiten."}</p>
|
|
${g.fields.map((f) => fieldHtml(f, data.values[f.key] ?? "")).join("")}
|
|
</section>`).join("");
|
|
}
|
|
|
|
document.getElementById("save").addEventListener("click", async () => {
|
|
const payload = {};
|
|
schema.forEach((g) => g.fields.forEach((f) => {
|
|
const el = document.getElementById("f_" + f.key);
|
|
if (!el) return;
|
|
payload[f.key] = f.kind === "bool" ? String(el.checked) : el.value;
|
|
}));
|
|
try {
|
|
stateEl.textContent = "Speichern …";
|
|
await BT.api("/api/settings", { method: "POST", body: JSON.stringify(payload) });
|
|
stateEl.textContent = "";
|
|
BT.toast("Einstellungen gespeichert.", "ok");
|
|
load();
|
|
} catch (e) {
|
|
stateEl.textContent = "";
|
|
BT.toast(e.message, "err");
|
|
}
|
|
});
|
|
|
|
load();
|
|
</script>
|
|
</body>
|
|
</html>
|