feat: Servereinstellungen-Seite für Admins
- Neuer Endpoint GET/PUT /api/admin/server-settings - Liest appsettings.Production.json (Fallback: appsettings.json → Standardwert) - Schreibt immer in appsettings.Production.json - Definierte Settings: AnthropicToken, MaxIterations, PayloadLogging, PayloadLoggingGzip, KreaToken, KreaApiKey - Neue Frontend-Seite 'server-settings' (Admin-only) - Dynamisches Formular basierend auf Setting-Definitionen vom Backend - Password-Felder mit Eye-Toggle, Checkboxen für Booleans, Number-Inputs - Hinweis dass Neustart erforderlich ist - Dashboard: Neuer Link 'Servereinstellungen' für Admins - appsettings.Production.json vom Publish ausgeschlossen (CopyToPublishDirectory=Never)
This commit is contained in:
1 parent
af2f821c72
commit
bfd78278ab
8 files changed
+411
-3
No files matched your search
@@ -490,6 +490,27 @@ async function apiAdminGetLoginLink(userId) {
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// ── Server Settings API (Admin) ──
|
||||
|
||||
async function apiGetServerSettings() {
|
||||
const resp = await fetch('/api/admin/server-settings');
|
||||
if (!resp.ok) throw new Error('Fehler beim Laden der Servereinstellungen');
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
async function apiSaveServerSettings(data) {
|
||||
const resp = await fetch('/api/admin/server-settings', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
if (!resp.ok) {
|
||||
const err = await resp.json().catch(() => ({}));
|
||||
throw new Error(err.error || 'Fehler beim Speichern');
|
||||
}
|
||||
return resp.json();
|
||||
}
|
||||
|
||||
// ── Krea Image API ──
|
||||
|
||||
async function apiKreaStatus() {
|
||||
|
||||
@@ -14,14 +14,20 @@ registerPage('dashboard', {
|
||||
navigateTo('profile');
|
||||
});
|
||||
|
||||
// Admin-Link (nur für Admins sichtbar)
|
||||
// Admin-Links (nur für Admins sichtbar)
|
||||
var adminLink = document.getElementById('dashboardAdminLink');
|
||||
var serverSettingsLink = document.getElementById('dashboardServerSettingsLink');
|
||||
if (currentUser && currentUser.isAdmin) {
|
||||
adminLink.hidden = false;
|
||||
adminLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
navigateTo('admin');
|
||||
});
|
||||
serverSettingsLink.hidden = false;
|
||||
serverSettingsLink.addEventListener('click', (e) => {
|
||||
e.preventDefault();
|
||||
navigateTo('server-settings');
|
||||
});
|
||||
}
|
||||
|
||||
// Globaler Click-Handler zum Schließen aller Card-Menüs
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// ── Server Settings Page (Admin) ──
|
||||
|
||||
registerPage('server-settings', {
|
||||
async init() {
|
||||
if (!currentUser || !currentUser.isAdmin) {
|
||||
navigateTo('dashboard');
|
||||
return;
|
||||
}
|
||||
|
||||
var container = document.getElementById('serverSettingsFields');
|
||||
var saveBtn = document.getElementById('serverSettingsSaveBtn');
|
||||
var messageEl = document.getElementById('serverSettingsMessage');
|
||||
var inputs = {};
|
||||
|
||||
function showMessage(text, isError) {
|
||||
messageEl.textContent = text;
|
||||
messageEl.className = 'profile-message ' + (isError ? 'profile-message-error' : 'profile-message-success');
|
||||
messageEl.hidden = false;
|
||||
if (!isError) setTimeout(function() { messageEl.hidden = true; }, 4000);
|
||||
}
|
||||
|
||||
// Settings laden und Formular aufbauen
|
||||
try {
|
||||
var result = await apiGetServerSettings();
|
||||
var settings = result.settings;
|
||||
|
||||
for (var i = 0; i < settings.length; i++) {
|
||||
var s = settings[i];
|
||||
var field = document.createElement('div');
|
||||
field.className = 'form-field';
|
||||
|
||||
var label = document.createElement('label');
|
||||
label.textContent = s.label;
|
||||
label.setAttribute('for', 'ss-' + s.key);
|
||||
field.appendChild(label);
|
||||
|
||||
if (s.inputType === 'toggle') {
|
||||
var toggleLabel = document.createElement('label');
|
||||
toggleLabel.className = 'toggle-label';
|
||||
|
||||
var cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.id = 'ss-' + s.key;
|
||||
cb.checked = !!s.value;
|
||||
inputs[s.key] = cb;
|
||||
|
||||
var span = document.createElement('span');
|
||||
span.textContent = s.value ? 'Aktiviert' : 'Deaktiviert';
|
||||
|
||||
cb.addEventListener('change', (function(sp, checkbox) {
|
||||
return function() { sp.textContent = checkbox.checked ? 'Aktiviert' : 'Deaktiviert'; };
|
||||
})(span, cb));
|
||||
|
||||
toggleLabel.appendChild(cb);
|
||||
toggleLabel.appendChild(span);
|
||||
field.appendChild(toggleLabel);
|
||||
} else if (s.inputType === 'password') {
|
||||
var wrapper = document.createElement('div');
|
||||
wrapper.className = 'input-with-btn';
|
||||
|
||||
var inp = document.createElement('input');
|
||||
inp.type = 'password';
|
||||
inp.id = 'ss-' + s.key;
|
||||
inp.value = s.value || '';
|
||||
inp.placeholder = s.label;
|
||||
inp.autocomplete = 'off';
|
||||
inputs[s.key] = inp;
|
||||
|
||||
var toggleBtn = document.createElement('button');
|
||||
toggleBtn.type = 'button';
|
||||
toggleBtn.className = 'icon-btn';
|
||||
toggleBtn.innerHTML = '<i class="fa fa-eye"></i>';
|
||||
toggleBtn.addEventListener('click', (function(input, btn) {
|
||||
return function() {
|
||||
if (input.type === 'password') {
|
||||
input.type = 'text';
|
||||
btn.innerHTML = '<i class="fa fa-eye-slash"></i>';
|
||||
} else {
|
||||
input.type = 'password';
|
||||
btn.innerHTML = '<i class="fa fa-eye"></i>';
|
||||
}
|
||||
};
|
||||
})(inp, toggleBtn));
|
||||
|
||||
wrapper.appendChild(inp);
|
||||
wrapper.appendChild(toggleBtn);
|
||||
field.appendChild(wrapper);
|
||||
} else if (s.inputType === 'number') {
|
||||
var inp = document.createElement('input');
|
||||
inp.type = 'number';
|
||||
inp.id = 'ss-' + s.key;
|
||||
inp.value = s.value != null ? s.value : '';
|
||||
inp.placeholder = s.label;
|
||||
inputs[s.key] = inp;
|
||||
field.appendChild(inp);
|
||||
} else {
|
||||
var inp = document.createElement('input');
|
||||
inp.type = 'text';
|
||||
inp.id = 'ss-' + s.key;
|
||||
inp.value = s.value || '';
|
||||
inp.placeholder = s.label;
|
||||
inputs[s.key] = inp;
|
||||
field.appendChild(inp);
|
||||
}
|
||||
|
||||
container.appendChild(field);
|
||||
}
|
||||
|
||||
// Speichern
|
||||
saveBtn.addEventListener('click', async function() {
|
||||
saveBtn.disabled = true;
|
||||
messageEl.hidden = true;
|
||||
|
||||
try {
|
||||
var data = {};
|
||||
for (var j = 0; j < settings.length; j++) {
|
||||
var def = settings[j];
|
||||
var input = inputs[def.key];
|
||||
if (!input) continue;
|
||||
|
||||
if (def.type === 'bool') {
|
||||
data[def.key] = input.checked;
|
||||
} else if (def.type === 'int') {
|
||||
data[def.key] = parseInt(input.value, 10) || 0;
|
||||
} else {
|
||||
data[def.key] = input.value;
|
||||
}
|
||||
}
|
||||
|
||||
var result = await apiSaveServerSettings(data);
|
||||
showMessage(result.hint || 'Einstellungen gespeichert', false);
|
||||
} catch (err) {
|
||||
showMessage(err.message, true);
|
||||
} finally {
|
||||
saveBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
container.innerHTML = '<p class="form-error">Fehler beim Laden: ' + esc(err.message) + '</p>';
|
||||
}
|
||||
},
|
||||
|
||||
destroy() {}
|
||||
});
|
||||
Reference in new issue
Block a user