Module im WebUI aktivieren und deaktivieren
Umsetzung von #560. Die Ansicht "Moduleinstellungen" zeigte bisher nur an, ob ein Modul laeuft. Jetzt laesst sich dort wie im Modul-Tab des Desktops festlegen, welche Module beim naechsten Start geladen werden. POST /api/modules/{name}/enabled nimmt den gewuenschten Zustand ausdruecklich entgegen ({"enabled": true|false}) und schaltet nicht einfach um. Zwei Klicks aus zwei Fenstern heben sich damit nicht auf, und ein wiederholter Aufruf aendert nichts mehr. Fehlerfaelle: - Unbekanntes Modul: 404. - Modul mit Aktivierungs-Blocker: 409. - Fehlender Zustand: 400. Die Pruefung war zuerst lueckenhaft, der Test hat es gefunden: Mit "bool Enabled" machte ein leerer Rumpf {} daraus false, und ein unvollstaendiger Aufruf haette das Modul deaktiviert. Gemeinsame Quelle statt Kopien: - KnownModules: Die Modulliste stand viermal im Code (Program, CoreViews, ModuleViews, hartkodiert im WebUI). Der Start registriert jetzt die eine Liste. - ModuleActivationView: Die Statusregeln des Desktops ("Aktiv - stoppt nach Neustart" usw.) liegen im Core. Desktop und WebUI zeigen dasselbe. ServerSettingsFile - gefunden, bevor das WebUI in die Datei schreiben durfte: ServerSettings.Load verschluckt jeden Lesefehler und liefert Vorgaben. "Frisch lesen, ein Feld aendern, speichern" haette eine gesperrte oder beschaedigte server_settings.xml durch Vorgaben ersetzt, und mit ihr Lizenzschluessel, Watchdog-Token, Mullvad-Account und Alchemy-Key. Genau so schrieb auch der Modul-Schalter des Desktops. ServerSettingsFile: - liest streng und schreibt bei unlesbarer Datei nichts, - schreibt atomar ueber eine Nachbardatei, - erhaelt unter Linux die Dateirechte, - serialisiert Aenderungen prozessweit. Der Desktop-Schalter benutzt ihn jetzt ebenfalls. Tests: Lizenzschluessel, Token und Alchemy-Key bleiben beim Umschalten in der Datei; eine kaputte Datei bleibt Byte fuer Byte unveraendert und die Anzeige bietet dann kein Umschalten an; Blocker, 404, 400, Idempotenz; die Statustabelle; strenges Lesen, atomares Schreiben und Rechte am Dateiobjekt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -531,41 +531,60 @@ async function triggerJob(jobName) {
|
||||
|
||||
// Modules View
|
||||
async function loadModules() {
|
||||
const container = document.getElementById('modulesContainer');
|
||||
try {
|
||||
const res = await fetch('/api/modules');
|
||||
if (!res.ok) return;
|
||||
if (!res.ok) throw new Error(await errorText(res));
|
||||
const modules = await res.json();
|
||||
refreshNavModules(modules);
|
||||
|
||||
const container = document.getElementById('modulesContainer');
|
||||
const views = {
|
||||
copytrading: ['copytrading', 'CopyTrading öffnen →'],
|
||||
resolutionfarming: ['resolutionfarming', 'Resolution Farming öffnen →'],
|
||||
supervisor: ['supervisor', 'AI Supervisor öffnen →'],
|
||||
accounting: ['accounting', 'Accounting & BWA öffnen →']
|
||||
};
|
||||
|
||||
container.innerHTML = modules.map(m => {
|
||||
let actionBtn = '';
|
||||
if (m.name.toLowerCase() === 'copytrading') {
|
||||
actionBtn = `<div style="margin-top: 14px;"><button class="btn btn-secondary btn-sm" onclick="switchView('copytrading')">CopyTrading öffnen →</button></div>`;
|
||||
} else if (m.name.toLowerCase() === 'resolutionfarming') {
|
||||
actionBtn = `<div style="margin-top: 14px;"><button class="btn btn-secondary btn-sm" onclick="switchView('resolutionfarming')">Resolution Farming öffnen →</button></div>`;
|
||||
} else if (m.name.toLowerCase() === 'supervisor') {
|
||||
actionBtn = `<div style="margin-top: 14px;"><button class="btn btn-secondary btn-sm" onclick="switchView('supervisor')">AI Supervisor öffnen →</button></div>`;
|
||||
} else if (m.name.toLowerCase() === 'accounting') {
|
||||
actionBtn = `<div style="margin-top: 14px;"><button class="btn btn-secondary btn-sm" onclick="switchView('accounting')">Accounting & BWA öffnen →</button></div>`;
|
||||
}
|
||||
const view = views[m.name.toLowerCase()];
|
||||
const openBtn = view && m.isRunning
|
||||
? `<button class="btn btn-secondary btn-sm" onclick="switchView(${jsArg(view[0])})">${escapeHtml(view[1])}</button>`
|
||||
: '';
|
||||
// #560: ausdruecklich der gewuenschte Zustand, nicht "umschalten".
|
||||
const toggleBtn = m.canToggle
|
||||
? `<button class="btn ${m.desiredEnabled ? 'btn-danger' : 'btn-primary'} btn-sm" onclick="setModuleEnabled(${jsArg(m.name)}, ${m.desiredEnabled ? 'false' : 'true'})">${escapeHtml(m.action)}</button>`
|
||||
: '';
|
||||
const statusColor = m.blocker ? 'var(--warning)' : (m.isRunning ? 'var(--positive)' : 'var(--muted)');
|
||||
|
||||
return `
|
||||
<div class="card">
|
||||
<div class="card-title">
|
||||
<span>${escapeHtml(m.name)}</span>
|
||||
<span style="font-size: 12px; color: ${m.isActive ? 'var(--positive)' : 'var(--muted)'}; font-weight: normal;">
|
||||
● ${m.isActive ? 'Aktiv im Betrieb' : 'Deaktiviert'}
|
||||
</span>
|
||||
<span style="font-size: 12px; color: ${statusColor}; font-weight: normal;">● ${escapeHtml(m.status)}</span>
|
||||
</div>
|
||||
<div style="font-size: 13px; color: var(--muted);">
|
||||
${m.isActive ? 'Modul läuft planmäßig im Hintergrund.' : 'Nicht geladen (in den Servereinstellungen oder über DisabledModules deaktiviert).'}
|
||||
</div>
|
||||
${actionBtn}
|
||||
<div style="font-size: 13px; color: var(--muted); min-height: 18px;">${escapeHtml(m.hint || (m.isRunning ? 'Läuft planmäßig im Hintergrund.' : 'In dieser Sitzung nicht geladen.'))}</div>
|
||||
<div style="margin-top: 14px; display: flex; gap: 8px; flex-wrap: wrap;">${toggleBtn}${openBtn}</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
} catch (e) {}
|
||||
} catch (e) {
|
||||
container.innerHTML = `<div class="card"><div class="card-title">Module nicht ladbar</div><div style="font-size: 13px; color: var(--negative);">${escapeHtml(e.message)}</div></div>`;
|
||||
}
|
||||
}
|
||||
|
||||
async function setModuleEnabled(name, enabled) {
|
||||
if (!enabled && !confirm(`Modul ${name} deaktivieren?\n\nEs läuft bis zum nächsten Neustart weiter und wird danach nicht mehr geladen.`)) return;
|
||||
try {
|
||||
const res = await fetch(`/api/modules/${encodeURIComponent(name)}/enabled`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ enabled })
|
||||
});
|
||||
if (!res.ok) throw new Error(await errorText(res));
|
||||
} catch (e) {
|
||||
alert(`Modul-Einstellung nicht gespeichert:\n\n${e.message}`);
|
||||
}
|
||||
loadModules();
|
||||
}
|
||||
|
||||
// Terminal & SSE Logs
|
||||
|
||||
Reference in New Issue
Block a user