Backend:
- Neuer Database Explorer Endpoint (GET /api/admin/db/tables, GET /api/admin/db/tables/{name})
- DatabaseService: public GetConnection() Methode
- StripeKeyChecker: 2-Schritt-Check (/v1/balance + /v1/account), erkennt Live/Test, Balance, Account-Details
- AnthropicKeyChecker: Auth-Header fix (x-api-key für API-Keys, Bearer für OAuth), 400-Handler für credit balance
- KeyPatterns: Stripe einkommentiert, Krea AI Pattern, AllTypes-Array, MinLength 4
- ServerSettingsService: string_array Typ (Get/SetStringArray), ScannerKeyTypes Setting mit Checkbox-Optionen
- ServerSettingsEndpoint: string_array Speichern (Array/CSV)
- KeyScannerService: GetActiveSearchPatterns() filtert nach ScannerKeyTypes, Progress-Tracking
- GitHubEventScannerService: CheckNewKeys nur für aktuelle Poll-Keys (statt alle unchecked), klarere Log-Meldung
Frontend:
- Database Explorer Page: Tabellenliste mit Klick -> Tabelleninhalt, Pagination, sticky Header
- Server Settings: Checkbox-Gruppe für string_array (Alle/Keine Buttons)
- API Keys: adoptedApiKeyId Fix (leerer String == null)
- Dashboard: DB-Explorer Link für Admins
- Scan-Progress: Live-Fortschritt mit Phase/Detail/Counter
186 lines
6.7 KiB
JavaScript
186 lines
6.7 KiB
JavaScript
// ── 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 inputs = {};
|
|
|
|
// 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 === 'checkboxes' && s.options) {
|
|
var cbGroup = document.createElement('div');
|
|
cbGroup.className = 'checkbox-group';
|
|
cbGroup.id = 'ss-' + s.key;
|
|
var selected = Array.isArray(s.value) ? s.value : [];
|
|
|
|
// Select All / Deselect All Buttons
|
|
var cbActions = document.createElement('div');
|
|
cbActions.className = 'checkbox-group-actions';
|
|
var selectAllBtn = document.createElement('button');
|
|
selectAllBtn.type = 'button';
|
|
selectAllBtn.className = 'btn btn-xs';
|
|
selectAllBtn.textContent = 'Alle';
|
|
var deselectAllBtn = document.createElement('button');
|
|
deselectAllBtn.type = 'button';
|
|
deselectAllBtn.className = 'btn btn-xs';
|
|
deselectAllBtn.textContent = 'Keine';
|
|
cbActions.appendChild(selectAllBtn);
|
|
cbActions.appendChild(deselectAllBtn);
|
|
cbGroup.appendChild(cbActions);
|
|
|
|
var checkboxes = [];
|
|
for (var oi = 0; oi < s.options.length; oi++) {
|
|
var opt = s.options[oi];
|
|
var cbItem = document.createElement('label');
|
|
cbItem.className = 'checkbox-item';
|
|
var cb2 = document.createElement('input');
|
|
cb2.type = 'checkbox';
|
|
cb2.value = opt.id;
|
|
cb2.checked = selected.indexOf(opt.id) !== -1;
|
|
checkboxes.push(cb2);
|
|
var cbText = document.createElement('span');
|
|
cbText.textContent = opt.name;
|
|
cbItem.appendChild(cb2);
|
|
cbItem.appendChild(cbText);
|
|
cbGroup.appendChild(cbItem);
|
|
}
|
|
|
|
selectAllBtn.addEventListener('click', function() {
|
|
for (var ci = 0; ci < checkboxes.length; ci++) checkboxes[ci].checked = true;
|
|
});
|
|
deselectAllBtn.addEventListener('click', function() {
|
|
for (var ci = 0; ci < checkboxes.length; ci++) checkboxes[ci].checked = false;
|
|
});
|
|
|
|
inputs[s.key] = { _checkboxes: checkboxes };
|
|
field.appendChild(cbGroup);
|
|
} else 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', () => withButtonLoading(saveBtn, 'Speichern...', async () => {
|
|
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 if (def.type === 'string_array' && input._checkboxes) {
|
|
var vals = [];
|
|
for (var ci = 0; ci < input._checkboxes.length; ci++) {
|
|
if (input._checkboxes[ci].checked) vals.push(input._checkboxes[ci].value);
|
|
}
|
|
data[def.key] = vals;
|
|
} else {
|
|
data[def.key] = input.value;
|
|
}
|
|
}
|
|
|
|
var result = await apiSaveServerSettings(data);
|
|
showToast(result.hint || 'Einstellungen gespeichert');
|
|
} catch (err) {
|
|
showToast(err.message, 'error');
|
|
}
|
|
}));
|
|
|
|
} catch (err) {
|
|
container.innerHTML = '<p class="form-error">Fehler beim Laden: ' + esc(err.message) + '</p>';
|
|
}
|
|
},
|
|
|
|
destroy() {}
|
|
});
|