diff --git a/static/index.html b/static/index.html
index d733a09..40675d7 100644
--- a/static/index.html
+++ b/static/index.html
@@ -311,6 +311,9 @@
diff --git a/static/js/app.js b/static/js/app.js
index d671a9e..2510f94 100644
--- a/static/js/app.js
+++ b/static/js/app.js
@@ -844,6 +844,31 @@ async function loadSettings() {
document.getElementById('cfg-azure-tenant').value = cfg.azure_tenant_id || '';
document.getElementById('cfg-azure-client-id').value = cfg.azure_client_id || '';
document.getElementById('azure-secret-status').textContent = cfg.azure_client_secret_set ? t('settings.secretSet') : t('settings.noSecret');
+ document.getElementById('cfg-azure-group-id').value = cfg.azure_allowed_group_id || '';
+
+ // DNS tab
+ document.getElementById('cfg-dns-enabled').checked = cfg.dns_enabled || false;
+ document.getElementById('cfg-dns-server').value = cfg.dns_server || '';
+ document.getElementById('cfg-dns-zone').value = cfg.dns_zone || '';
+ document.getElementById('cfg-dns-username').value = cfg.dns_username || '';
+ document.getElementById('cfg-dns-record-ip').value = cfg.dns_record_ip || '';
+ document.getElementById('dns-password-status').textContent = cfg.dns_password_set ? t('settings.passwordSet') : t('settings.noPasswordSet');
+
+ // LDAP tab
+ document.getElementById('cfg-ldap-enabled').checked = cfg.ldap_enabled || false;
+ document.getElementById('cfg-ldap-server').value = cfg.ldap_server || '';
+ document.getElementById('cfg-ldap-port').value = cfg.ldap_port || 389;
+ document.getElementById('cfg-ldap-use-ssl').checked = cfg.ldap_use_ssl || false;
+ document.getElementById('cfg-ldap-bind-dn').value = cfg.ldap_bind_dn || '';
+ document.getElementById('cfg-ldap-base-dn').value = cfg.ldap_base_dn || '';
+ document.getElementById('cfg-ldap-user-filter').value = cfg.ldap_user_filter || '(sAMAccountName={username})';
+ document.getElementById('cfg-ldap-group-dn').value = cfg.ldap_group_dn || '';
+ document.getElementById('ldap-password-status').textContent = cfg.ldap_bind_password_set ? t('settings.passwordSet') : t('settings.noPasswordSet');
+
+ // Git/Update tab
+ document.getElementById('cfg-git-repo-url').value = cfg.git_repo_url || '';
+ document.getElementById('cfg-git-branch').value = cfg.git_branch || 'main';
+ document.getElementById('git-token-status').textContent = cfg.git_token_set ? t('settings.tokenSet') : t('settings.noToken');
} catch (err) {
showSettingsAlert('danger', t('errors.failedToLoadSettings', { error: err.message }));
}
@@ -1069,6 +1094,182 @@ async function deleteLogo() {
}
}
+// ---------------------------------------------------------------------------
+// DNS Settings
+// ---------------------------------------------------------------------------
+document.getElementById('settings-dns-form').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const payload = {
+ dns_enabled: document.getElementById('cfg-dns-enabled').checked,
+ dns_server: document.getElementById('cfg-dns-server').value,
+ dns_zone: document.getElementById('cfg-dns-zone').value,
+ dns_username: document.getElementById('cfg-dns-username').value,
+ dns_record_ip: document.getElementById('cfg-dns-record-ip').value,
+ };
+ const pw = document.getElementById('cfg-dns-password').value;
+ if (pw) payload.dns_password = pw;
+ try {
+ await api('PUT', '/settings/system', payload);
+ showSettingsAlert('success', t('messages.dnsSettingsSaved'));
+ document.getElementById('cfg-dns-password').value = '';
+ loadSettings();
+ } catch (err) {
+ showSettingsAlert('danger', t('errors.failed', { error: err.message }));
+ }
+});
+
+async function testDnsConnection() {
+ const spinner = document.getElementById('dns-test-spinner');
+ const resultEl = document.getElementById('dns-test-result');
+ spinner.classList.remove('d-none');
+ resultEl.classList.add('d-none');
+ try {
+ const data = await api('GET', '/settings/test-dns');
+ resultEl.className = `mt-3 alert alert-${data.ok ? 'success' : 'danger'}`;
+ resultEl.textContent = data.message;
+ resultEl.classList.remove('d-none');
+ } catch (err) {
+ resultEl.className = 'mt-3 alert alert-danger';
+ resultEl.textContent = err.message;
+ resultEl.classList.remove('d-none');
+ } finally {
+ spinner.classList.add('d-none');
+ }
+}
+
+// ---------------------------------------------------------------------------
+// LDAP Settings
+// ---------------------------------------------------------------------------
+document.getElementById('settings-ldap-form').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const payload = {
+ ldap_enabled: document.getElementById('cfg-ldap-enabled').checked,
+ ldap_server: document.getElementById('cfg-ldap-server').value,
+ ldap_port: parseInt(document.getElementById('cfg-ldap-port').value) || 389,
+ ldap_use_ssl: document.getElementById('cfg-ldap-use-ssl').checked,
+ ldap_bind_dn: document.getElementById('cfg-ldap-bind-dn').value,
+ ldap_base_dn: document.getElementById('cfg-ldap-base-dn').value,
+ ldap_user_filter: document.getElementById('cfg-ldap-user-filter').value,
+ ldap_group_dn: document.getElementById('cfg-ldap-group-dn').value,
+ };
+ const pw = document.getElementById('cfg-ldap-bind-password').value;
+ if (pw) payload.ldap_bind_password = pw;
+ try {
+ await api('PUT', '/settings/system', payload);
+ showSettingsAlert('success', t('messages.ldapSettingsSaved'));
+ document.getElementById('cfg-ldap-bind-password').value = '';
+ loadSettings();
+ } catch (err) {
+ showSettingsAlert('danger', t('errors.failed', { error: err.message }));
+ }
+});
+
+async function testLdapConnection() {
+ const spinner = document.getElementById('ldap-test-spinner');
+ const resultEl = document.getElementById('ldap-test-result');
+ spinner.classList.remove('d-none');
+ resultEl.classList.add('d-none');
+ try {
+ const data = await api('GET', '/settings/test-ldap');
+ resultEl.className = `mt-3 alert alert-${data.ok ? 'success' : 'danger'}`;
+ resultEl.textContent = data.message;
+ resultEl.classList.remove('d-none');
+ } catch (err) {
+ resultEl.className = 'mt-3 alert alert-danger';
+ resultEl.textContent = err.message;
+ resultEl.classList.remove('d-none');
+ } finally {
+ spinner.classList.add('d-none');
+ }
+}
+
+// ---------------------------------------------------------------------------
+// Update / Version Management
+// ---------------------------------------------------------------------------
+document.getElementById('settings-git-form').addEventListener('submit', async (e) => {
+ e.preventDefault();
+ const payload = {
+ git_repo_url: document.getElementById('cfg-git-repo-url').value,
+ git_branch: document.getElementById('cfg-git-branch').value || 'main',
+ };
+ const token = document.getElementById('cfg-git-token').value;
+ if (token) payload.git_token = token;
+ try {
+ await api('PUT', '/settings/system', payload);
+ showSettingsAlert('success', t('messages.gitSettingsSaved'));
+ document.getElementById('cfg-git-token').value = '';
+ loadSettings();
+ } catch (err) {
+ showSettingsAlert('danger', t('errors.failed', { error: err.message }));
+ }
+});
+
+async function loadVersionInfo() {
+ const el = document.getElementById('version-info-content');
+ if (!el) return;
+ el.innerHTML = `
${t('common.loading')}
`;
+ try {
+ const data = await api('GET', '/settings/version');
+ const current = data.current || {};
+ const latest = data.latest;
+ const needsUpdate = data.needs_update;
+
+ let html = `
+
+
+
${t('settings.currentVersion')}
+
${esc(current.commit || 'unknown')}
+
${t('settings.branch')}: ${esc(current.branch || 'unknown')}
+
${esc(current.date || '')}
+
+
`;
+
+ if (latest) {
+ const badge = needsUpdate
+ ? `
${t('settings.updateAvailable')}`
+ : `
${t('settings.upToDate')}`;
+ html += `
+
+
${t('settings.latestVersion')} ${badge}
+
${esc(latest.commit || 'unknown')}
+
${t('settings.branch')}: ${esc(latest.branch || 'unknown')}
+
${esc(latest.message || '')}
+
${esc(latest.date || '')}
+
+
`;
+ } else if (data.error) {
+ html += `
`;
+ }
+ html += '
';
+
+ if (needsUpdate) {
+ html += `
+
+
${t('settings.updateWarning')}
+
`;
+ }
+ el.innerHTML = html;
+ } catch (err) {
+ el.innerHTML = `
${esc(err.message)}
`;
+ }
+}
+
+async function triggerUpdate() {
+ if (!confirm(t('settings.confirmUpdate'))) return;
+ const spinner = document.getElementById('update-spinner');
+ if (spinner) spinner.classList.remove('d-none');
+ try {
+ const data = await api('POST', '/settings/update');
+ showSettingsAlert('success', data.message || t('messages.updateStarted'));
+ } catch (err) {
+ showSettingsAlert('danger', t('errors.failed', { error: err.message }));
+ if (spinner) spinner.classList.add('d-none');
+ }
+}
+
// ---------------------------------------------------------------------------
// User Management
// ---------------------------------------------------------------------------
@@ -1181,6 +1382,7 @@ document.getElementById('settings-azure-form').addEventListener('submit', async
azure_enabled: document.getElementById('cfg-azure-enabled').checked,
azure_tenant_id: document.getElementById('cfg-azure-tenant').value || null,
azure_client_id: document.getElementById('cfg-azure-client-id').value || null,
+ azure_allowed_group_id: document.getElementById('cfg-azure-group-id').value || null,
};
const secret = document.getElementById('cfg-azure-client-secret').value;
if (secret) payload.azure_client_secret = secret;
diff --git a/static/lang/de.json b/static/lang/de.json
index 034a881..383a0fc 100644
--- a/static/lang/de.json
+++ b/static/lang/de.json
@@ -90,5 +90,283 @@
"thImage": "Image",
"lastCheck": "Letzte Prüfung: {time}",
"openDashboard": "Dashboard öffnen"
+ },
+ "settings": {
+ "title": "Systemeinstellungen",
+ "tabSystem": "Systemkonfiguration",
+ "tabNpm": "NPM Integration",
+ "tabImages": "Docker Images",
+ "tabBranding": "Branding",
+ "tabUsers": "Benutzer",
+ "tabAzure": "Azure AD",
+ "tabDns": "Windows DNS",
+ "tabLdap": "LDAP / AD",
+ "tabUpdate": "Updates",
+ "tabSecurity": "Sicherheit",
+ "baseDomain": "Basis-Domain",
+ "baseDomainPlaceholder": "ihredomain.com",
+ "baseDomainHint": "Kunden erhalten Subdomains: kunde.ihredomain.com",
+ "adminEmail": "Admin E-Mail",
+ "adminEmailPlaceholder": "admin@ihredomain.com",
+ "dataDir": "Datenverzeichnis",
+ "dataDirPlaceholder": "/opt/netbird-instances",
+ "dockerNetwork": "Docker-Netzwerk",
+ "dockerNetworkPlaceholder": "npm-network",
+ "relayBasePort": "Relay-Basisport",
+ "relayBasePortHint": "Erster UDP-Port für Relay. Bereich: Basis bis Basis+99",
+ "dashboardBasePort": "Dashboard-Basisport",
+ "dashboardBasePortHint": "Basisport für Kunden-Dashboards. Kunde N erhält Basis+N",
+ "saveSystemSettings": "Systemeinstellungen speichern",
+ "npmDescription": "NPM verwendet JWT-Authentifizierung. Geben Sie Ihre NPM-Zugangsdaten ein. Das System meldet sich automatisch an.",
+ "npmApiUrl": "NPM API URL",
+ "npmApiUrlPlaceholder": "http://nginx-proxy-manager:81/api",
+ "npmApiUrlHint": "http:// oder https:// - muss /api am Ende enthalten",
+ "npmLoginEmail": "NPM Login E-Mail",
+ "npmLoginEmailPlaceholder": "Leer lassen zum Beibehalten",
+ "npmLoginPassword": "NPM Login Passwort",
+ "npmLoginPasswordPlaceholder": "Leer lassen zum Beibehalten",
+ "credentialsSet": "Zugangsdaten gesetzt (leer lassen zum Beibehalten)",
+ "noCredentials": "Keine NPM-Zugangsdaten konfiguriert",
+ "saveNpmSettings": "NPM-Einstellungen speichern",
+ "testConnection": "Verbindung testen",
+ "sslModeTitle": "SSL-Zertifikat Modus",
+ "sslMode": "SSL-Modus",
+ "sslModeLetsencrypt": "Let's Encrypt (pro Kunde)",
+ "sslModeWildcard": "Wildcard-Zertifikat",
+ "sslModeHint": "Wählen Sie ob jeder Kunde ein eigenes Let's Encrypt Zertifikat oder ein geteiltes Wildcard-Zertifikat erhält.",
+ "wildcardCertificate": "Wildcard-Zertifikat",
+ "selectCertificate": "-- Zertifikat auswählen --",
+ "wildcardCertHint": "Wählen Sie das Wildcard-Zertifikat (z.B. *.example.com) das in NPM hochgeladen ist.",
+ "noWildcardCerts": "Keine Wildcard-Zertifikate in NPM gefunden.",
+ "certsLoaded": "{count} Wildcard-Zertifikat(e) gefunden.",
+ "expiresOn": "Läuft ab",
+ "managementImage": "Management Image",
+ "managementImagePlaceholder": "netbirdio/management:latest",
+ "signalImage": "Signal Image",
+ "signalImagePlaceholder": "netbirdio/signal:latest",
+ "relayImage": "Relay Image",
+ "relayImagePlaceholder": "netbirdio/relay:latest",
+ "dashboardImage": "Dashboard Image",
+ "dashboardImagePlaceholder": "netbirdio/dashboard:latest",
+ "saveImageSettings": "Image-Einstellungen speichern",
+ "brandingTitle": "Branding-Einstellungen",
+ "companyName": "Firmen- / Anwendungsname",
+ "companyNamePlaceholder": "NetBird MSP Appliance",
+ "companyNameHint": "Wird auf der Anmeldeseite und in der Navigationsleiste angezeigt",
+ "logoPreview": "Logo-Vorschau",
+ "defaultIcon": "Standardsymbol (kein Logo hochgeladen)",
+ "uploadLogo": "Logo hochladen (PNG, JPG, SVG, max. 500 KB)",
+ "uploadBtn": "Hochladen",
+ "removeLogo": "Logo entfernen",
+ "brandingSubtitle": "Untertitel",
+ "brandingSubtitlePlaceholder": "Multi-Tenant Management Plattform",
+ "brandingSubtitleHint": "Wird unter dem Titel auf der Anmeldeseite angezeigt",
+ "defaultLanguage": "Standardsprache",
+ "defaultLanguageHint": "Standardsprache für Benutzer ohne Präferenz",
+ "systemDefault": "Systemstandard",
+ "saveBranding": "Branding speichern",
+ "userManagement": "Benutzerverwaltung",
+ "newUser": "Neuer Benutzer",
+ "thId": "ID",
+ "thUsername": "Benutzername",
+ "thEmail": "E-Mail",
+ "thRole": "Rolle",
+ "thAuth": "Auth",
+ "thLanguage": "Sprache",
+ "thStatus": "Status",
+ "thActions": "Aktionen",
+ "azureTitle": "Azure AD / Entra ID Integration",
+ "enableAzureSso": "Azure AD SSO aktivieren",
+ "tenantId": "Tenant ID",
+ "clientId": "Client ID (Anwendungs-ID)",
+ "clientSecret": "Client Secret",
+ "clientSecretPlaceholder": "Leer lassen zum Beibehalten",
+ "secretSet": "Secret gesetzt (leer lassen zum Beibehalten)",
+ "noSecret": "Kein Client-Secret konfiguriert",
+ "saveAzureSettings": "Azure AD-Einstellungen speichern",
+ "azureGroupId": "Erlaubte Gruppen-Objekt-ID (optional)",
+ "azureGroupIdHint": "Falls gesetzt, können sich nur Azure AD-Mitglieder dieser Gruppe anmelden.",
+ "dnsTitle": "Windows DNS Integration",
+ "enableDns": "Windows DNS Integration aktivieren",
+ "dnsDescription": "Automatisch DNS A-Records erstellen/löschen beim Bereitstellen von Kunden.",
+ "dnsServer": "DNS-Serveradresse",
+ "dnsZone": "DNS-Zone",
+ "dnsUsername": "Benutzername (NTLM)",
+ "dnsPassword": "Passwort",
+ "dnsRecordIp": "A-Record Ziel-IP",
+ "dnsRecordIpHint": "IP-Adresse, auf die Kunden-A-Records zeigen (normalerweise die NPM-Server-IP).",
+ "saveDnsSettings": "DNS-Einstellungen speichern",
+ "ldapTitle": "LDAP / Active Directory Authentifizierung",
+ "enableLdap": "LDAP / AD Authentifizierung aktivieren",
+ "ldapDescription": "Active Directory Benutzern die Anmeldung erlauben. Lokale Admin-Konten funktionieren immer als Fallback.",
+ "ldapServer": "LDAP-Server",
+ "ldapPort": "Port",
+ "ldapUseSsl": "SSL/TLS verwenden (LDAPS)",
+ "ldapBindDn": "Bind DN (Dienstkonto)",
+ "ldapBindPassword": "Bind-Passwort",
+ "ldapBaseDn": "Basis-DN",
+ "ldapUserFilter": "Benutzerfilter",
+ "ldapUserFilterHint": "Verwenden Sie {username} als Platzhalter für den Anmeldenamen.",
+ "ldapGroupDn": "Gruppen-DN (optional, zur Einschränkung)",
+ "ldapGroupDnHint": "Falls gesetzt, können sich nur Mitglieder dieser Gruppe per LDAP anmelden.",
+ "saveLdapSettings": "LDAP-Einstellungen speichern",
+ "versionTitle": "Version & Updates",
+ "currentVersion": "Installierte Version",
+ "latestVersion": "Neueste verfügbare Version",
+ "branch": "Branch",
+ "updateAvailable": "Update verfügbar",
+ "upToDate": "Aktuell",
+ "triggerUpdate": "Update starten",
+ "updateWarning": "Die App ist während des Rebuilds ca. 60 Sekunden nicht verfügbar.",
+ "confirmUpdate": "Update jetzt starten? Die Datenbank wird zuerst gesichert. Die App startet neu (~60 Sekunden Ausfallzeit).",
+ "gitTitle": "Git-Repository Einstellungen",
+ "gitRepoUrl": "Repository URL",
+ "gitRepoUrlHint": "Wird für Versionsprüfungen und One-Click-Updates via Gitea API verwendet.",
+ "gitBranch": "Branch",
+ "gitToken": "Zugriffstoken (optional)",
+ "saveGitSettings": "Git-Einstellungen speichern",
+ "leaveEmptyToKeep": "Leer lassen zum Beibehalten",
+ "passwordSet": "Passwort gesetzt (leer lassen zum Beibehalten)",
+ "noPasswordSet": "Kein Passwort konfiguriert",
+ "tokenSet": "Token gesetzt (leer lassen zum Beibehalten)",
+ "noToken": "Kein Zugriffstoken konfiguriert",
+ "securityTitle": "Admin-Passwort ändern",
+ "currentPassword": "Aktuelles Passwort",
+ "newPassword": "Neues Passwort (min. 12 Zeichen)",
+ "confirmPassword": "Neues Passwort bestätigen",
+ "changePassword": "Passwort ändern"
+ },
+ "mfa": {
+ "title": "Zwei-Faktor-Authentifizierung (MFA)",
+ "enableMfa": "MFA für alle lokalen Benutzer aktivieren",
+ "mfaDescription": "Bei Aktivierung müssen lokale Benutzer sich nach der Passworteingabe mit einer TOTP-Authentifikator-App verifizieren. Azure AD-Benutzer sind nicht betroffen.",
+ "saveMfaSettings": "MFA-Einstellungen speichern",
+ "yourTotpStatus": "Ihr TOTP-Status",
+ "totpActive": "Aktiv",
+ "totpNotSetUp": "Nicht eingerichtet",
+ "disableMyTotp": "Mein TOTP deaktivieren",
+ "enterCode": "Geben Sie Ihren 6-stelligen Authentifikator-Code ein",
+ "verify": "Bestätigen",
+ "backToLogin": "Zurück zur Anmeldung",
+ "scanQrCode": "Scannen Sie diesen QR-Code mit Ihrer Authentifikator-App",
+ "orEnterManually": "Oder geben Sie diesen Schlüssel manuell ein:",
+ "verifyAndActivate": "Bestätigen & Aktivieren",
+ "resetMfa": "MFA zurücksetzen",
+ "confirmResetMfa": "MFA für '{username}' zurücksetzen? Sie müssen bei der nächsten Anmeldung ihren Authentifikator neu einrichten.",
+ "mfaResetSuccess": "MFA für '{username}' zurückgesetzt.",
+ "mfaDisabled": "Ihr TOTP wurde deaktiviert.",
+ "mfaSaved": "MFA-Einstellungen gespeichert.",
+ "invalidCode": "Ungültiger Code. Bitte versuchen Sie es erneut.",
+ "codeExpired": "Verifizierung abgelaufen. Bitte melden Sie sich erneut an."
+ },
+ "common": {
+ "loading": "Laden...",
+ "back": "Zurück",
+ "save": "Speichern",
+ "cancel": "Abbrechen",
+ "delete": "Löschen",
+ "edit": "Bearbeiten",
+ "view": "Ansehen",
+ "start": "Starten",
+ "stop": "Stoppen",
+ "restart": "Neustarten",
+ "disable": "Deaktivieren",
+ "enable": "Aktivieren",
+ "resetPassword": "Passwort zurücksetzen",
+ "open": "Öffnen",
+ "active": "Aktiv",
+ "disabled": "Deaktiviert"
+ },
+ "errors": {
+ "networkError": "Netzwerkfehler — Server nicht erreichbar.",
+ "sessionExpired": "Sitzung abgelaufen.",
+ "requestFailed": "Anfrage fehlgeschlagen.",
+ "serverError": "Serverfehler (HTTP {status}).",
+ "unknownError": "Ein unbekannter Fehler ist aufgetreten.",
+ "uploadFailed": "Upload fehlgeschlagen.",
+ "deleteFailed": "Löschen fehlgeschlagen: {error}",
+ "failedToLoadSettings": "Einstellungen konnten nicht geladen werden: {error}",
+ "failed": "Fehlgeschlagen: {error}",
+ "logoUploadFailed": "Logo-Upload fehlgeschlagen: {error}",
+ "failedToRemoveLogo": "Logo konnte nicht entfernt werden: {error}",
+ "updateFailed": "Aktualisierung fehlgeschlagen: {error}",
+ "passwordResetFailed": "Passwort zurücksetzen fehlgeschlagen: {error}",
+ "selectFileFirst": "Bitte wählen Sie zuerst eine Datei aus.",
+ "passwordsDoNotMatch": "Passwörter stimmen nicht überein.",
+ "failedToLoadCredentials": "Zugangsdaten konnten nicht geladen werden: {error}",
+ "azureNotConfigured": "Azure AD ist nicht konfiguriert.",
+ "azureLoginFailed": "Azure AD Anmeldung fehlgeschlagen: {error}",
+ "actionFailed": "{action} fehlgeschlagen: {error}"
+ },
+ "messages": {
+ "systemSettingsSaved": "Systemeinstellungen gespeichert.",
+ "npmSettingsSaved": "NPM-Einstellungen gespeichert.",
+ "imageSettingsSaved": "Image-Einstellungen gespeichert.",
+ "brandingNameSaved": "Branding-Einstellungen gespeichert.",
+ "logoUploaded": "Logo erfolgreich hochgeladen.",
+ "logoRemoved": "Logo entfernt.",
+ "azureSettingsSaved": "Azure AD-Einstellungen gespeichert.",
+ "dnsSettingsSaved": "DNS-Einstellungen gespeichert.",
+ "ldapSettingsSaved": "LDAP-Einstellungen gespeichert.",
+ "gitSettingsSaved": "Git-Einstellungen gespeichert.",
+ "updateStarted": "Update gestartet. Die App wird in Kürze neu starten.",
+ "passwordChanged": "Passwort erfolgreich geändert.",
+ "setupUrlCopied": "Setup-URL in Zwischenablage kopiert.",
+ "copiedToClipboard": "In Zwischenablage kopiert.",
+ "userCreated": "Benutzer '{username}' erstellt.",
+ "userDeleted": "Benutzer '{username}' gelöscht.",
+ "passwordResetFor": "Passwort zurückgesetzt für '{username}'.",
+ "newPasswordAlert": "Neues Passwort für '{username}':\n\n{password}\n\nBitte speichern Sie dieses Passwort jetzt. Es wird nicht erneut angezeigt.",
+ "confirmDeleteUser": "Benutzer '{username}' löschen? Dies kann nicht rückgängig gemacht werden.",
+ "confirmResetPassword": "Passwort für '{username}' zurücksetzen? Ein neues zufälliges Passwort wird generiert."
+ },
+ "userModal": {
+ "title": "Neuer Benutzer",
+ "usernameLabel": "Benutzername *",
+ "passwordLabel": "Passwort * (min. 8 Zeichen)",
+ "emailLabel": "E-Mail",
+ "languageLabel": "Standardsprache",
+ "cancel": "Abbrechen",
+ "createUser": "Benutzer erstellen"
+ },
+ "customerModal": {
+ "newCustomer": "Neuer Kunde",
+ "editCustomer": "Kunde bearbeiten",
+ "nameLabel": "Name *",
+ "companyLabel": "Firma",
+ "subdomainLabel": "Subdomain *",
+ "subdomainHint": "Kleinbuchstaben, alphanumerisch + Bindestriche",
+ "emailLabel": "E-Mail *",
+ "maxDevicesLabel": "Max. Geräte",
+ "notesLabel": "Notizen",
+ "cancel": "Abbrechen",
+ "saveAndDeploy": "Speichern & Bereitstellen",
+ "saveChanges": "Änderungen speichern"
+ },
+ "deleteModal": {
+ "title": "Löschen bestätigen",
+ "confirmText": "Möchten Sie den Kunden wirklich löschen:",
+ "warning": "Alle Container, NPM-Einträge und Daten werden entfernt. Diese Aktion kann nicht rückgängig gemacht werden.",
+ "cancel": "Abbrechen",
+ "delete": "Löschen"
+ },
+ "monitoring": {
+ "title": "System-Monitoring",
+ "refresh": "Aktualisieren",
+ "hostResources": "Host-Ressourcen",
+ "hostname": "Hostname",
+ "cpu": "CPU ({count} Kerne)",
+ "memory": "Arbeitsspeicher ({used}/{total} GB)",
+ "disk": "Festplatte ({used}/{total} GB)",
+ "allCustomerDeployments": "Alle Kunden-Deployments",
+ "thId": "ID",
+ "thName": "Name",
+ "thSubdomain": "Subdomain",
+ "thStatus": "Status",
+ "thDeployment": "Deployment",
+ "thDashboard": "Dashboard",
+ "thRelayPort": "Relay-Port",
+ "thContainers": "Container",
+ "noCustomers": "Keine Kunden."
}
}
diff --git a/static/lang/en.json b/static/lang/en.json
index 72b1965..0dd40f4 100644
--- a/static/lang/en.json
+++ b/static/lang/en.json
@@ -120,6 +120,9 @@
"tabBranding": "Branding",
"tabUsers": "Users",
"tabAzure": "Azure AD",
+ "tabDns": "Windows DNS",
+ "tabLdap": "LDAP / AD",
+ "tabUpdate": "Updates",
"tabSecurity": "Security",
"baseDomain": "Base Domain",
"baseDomainPlaceholder": "yourdomain.com",
@@ -202,6 +205,52 @@
"secretSet": "Secret is set (leave empty to keep current)",
"noSecret": "No client secret configured",
"saveAzureSettings": "Save Azure AD Settings",
+ "azureGroupId": "Allowed Group Object ID (optional)",
+ "azureGroupIdHint": "If set, only Azure AD members of this group can log in.",
+ "dnsTitle": "Windows DNS Integration",
+ "enableDns": "Enable Windows DNS Integration",
+ "dnsDescription": "Automatically create/delete DNS A-records when deploying customers.",
+ "dnsServer": "DNS Server Address",
+ "dnsZone": "DNS Zone",
+ "dnsUsername": "Username (NTLM)",
+ "dnsPassword": "Password",
+ "dnsRecordIp": "A-Record Target IP",
+ "dnsRecordIpHint": "IP address that customer A-records will point to (usually your NPM server IP).",
+ "saveDnsSettings": "Save DNS Settings",
+ "ldapTitle": "LDAP / Active Directory Authentication",
+ "enableLdap": "Enable LDAP / AD Authentication",
+ "ldapDescription": "Allow Active Directory users to log in. Local admin accounts always work as fallback.",
+ "ldapServer": "LDAP Server",
+ "ldapPort": "Port",
+ "ldapUseSsl": "Use SSL/TLS (LDAPS)",
+ "ldapBindDn": "Bind DN (Service Account)",
+ "ldapBindPassword": "Bind Password",
+ "ldapBaseDn": "Base DN",
+ "ldapUserFilter": "User Filter",
+ "ldapUserFilterHint": "Use {username} as placeholder for the login name.",
+ "ldapGroupDn": "Group Restriction DN (optional)",
+ "ldapGroupDnHint": "If set, only members of this group can log in via LDAP.",
+ "saveLdapSettings": "Save LDAP Settings",
+ "versionTitle": "Version & Updates",
+ "currentVersion": "Installed Version",
+ "latestVersion": "Latest Available",
+ "branch": "Branch",
+ "updateAvailable": "Update Available",
+ "upToDate": "Up to date",
+ "triggerUpdate": "Start Update",
+ "updateWarning": "The app will be unavailable for ~60 seconds during rebuild.",
+ "confirmUpdate": "Start the update now? The database will be backed up first. The app will restart (~60 seconds downtime).",
+ "gitTitle": "Git Repository Settings",
+ "gitRepoUrl": "Repository URL",
+ "gitRepoUrlHint": "Used for version checks and one-click updates via Gitea API.",
+ "gitBranch": "Branch",
+ "gitToken": "Access Token (optional)",
+ "saveGitSettings": "Save Git Settings",
+ "leaveEmptyToKeep": "Leave empty to keep current",
+ "passwordSet": "Password is set (leave empty to keep current)",
+ "noPasswordSet": "No password configured",
+ "tokenSet": "Token is set (leave empty to keep current)",
+ "noToken": "No access token configured",
"securityTitle": "Change Admin Password",
"currentPassword": "Current Password",
"newPassword": "New Password (min 12 chars)",
@@ -306,6 +355,10 @@
"logoUploaded": "Logo uploaded successfully.",
"logoRemoved": "Logo removed.",
"azureSettingsSaved": "Azure AD settings saved.",
+ "dnsSettingsSaved": "DNS settings saved.",
+ "ldapSettingsSaved": "LDAP settings saved.",
+ "gitSettingsSaved": "Git settings saved.",
+ "updateStarted": "Update started. The app will restart shortly.",
"passwordChanged": "Password changed successfully.",
"setupUrlCopied": "Setup URL copied to clipboard.",
"copiedToClipboard": "Copied to clipboard.",