feat: add Windows DNS, LDAP, and Update settings tabs to UI
- Settings page: 3 new tabs (Windows DNS, LDAP / AD, Updates) - Windows DNS tab: enable toggle, server/zone/username/password/record-IP, save + test connection button - LDAP tab: enable toggle, server/port/SSL/bind-DN/password/base-DN/ user-filter/group-DN, save + test connection button - Updates tab: current + latest version info card with update-available badge, one-click update button (git pull + rebuild), git repo/branch/ token settings form - Azure AD tab: added Allowed Group Object ID field - app.js: settings-dns-form, settings-ldap-form, settings-git-form submit handlers; testDnsConnection(), testLdapConnection(), loadVersionInfo(), triggerUpdate() functions; loadSettings() extended for all new fields - en.json: all new translation keys - de.json: complete German translation (was mostly empty before) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -311,6 +311,9 @@
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-branding" data-i18n="settings.tabBranding">Branding</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-users" onclick="loadUsers()" data-i18n="settings.tabUsers">Users</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-azure" data-i18n="settings.tabAzure">Azure AD</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-dns" data-i18n="settings.tabDns">Windows DNS</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-ldap" data-i18n="settings.tabLdap">LDAP / AD</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-update" onclick="loadVersionInfo()" data-i18n="settings.tabUpdate">Updates</a></li>
|
||||
<li class="nav-item"><a class="nav-link" data-bs-toggle="tab" href="#settings-security" data-i18n="settings.tabSecurity">Security</a></li>
|
||||
</ul>
|
||||
|
||||
@@ -562,6 +565,11 @@
|
||||
</div>
|
||||
<div class="form-text" id="azure-secret-status"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.azureGroupId">Allowed Group Object ID (optional)</label>
|
||||
<input type="text" class="form-control" id="cfg-azure-group-id" placeholder="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx">
|
||||
<div class="form-text" data-i18n="settings.azureGroupIdHint">If set, only Azure AD members of this group can log in.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-save me-1"></i><span data-i18n="settings.saveAzureSettings">Save Azure AD Settings</span></button>
|
||||
@@ -571,6 +579,171 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Windows DNS -->
|
||||
<div class="tab-pane fade" id="settings-dns">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3" data-i18n="settings.dnsTitle">Windows DNS Integration</h5>
|
||||
<form id="settings-dns-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="cfg-dns-enabled">
|
||||
<label class="form-check-label" for="cfg-dns-enabled" data-i18n="settings.enableDns">Enable Windows DNS Integration</label>
|
||||
</div>
|
||||
<div class="form-text" data-i18n="settings.dnsDescription">Automatically create/delete DNS A-records when deploying customers.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.dnsServer">DNS Server Address</label>
|
||||
<input type="text" class="form-control" id="cfg-dns-server" placeholder="192.168.1.10">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.dnsZone">DNS Zone</label>
|
||||
<input type="text" class="form-control" id="cfg-dns-zone" placeholder="example.com">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.dnsUsername">Username (NTLM)</label>
|
||||
<input type="text" class="form-control" id="cfg-dns-username" placeholder="DOMAIN\svcuser">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.dnsPassword">Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="cfg-dns-password" data-i18n-placeholder="settings.leaveEmptyToKeep" placeholder="Leave empty to keep current">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="togglePasswordVisibility('cfg-dns-password')"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
<div class="form-text" id="dns-password-status"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.dnsRecordIp">A-Record Target IP</label>
|
||||
<input type="text" class="form-control" id="cfg-dns-record-ip" placeholder="1.2.3.4">
|
||||
<div class="form-text" data-i18n="settings.dnsRecordIpHint">IP address that customer A-records will point to (usually your NPM server IP).</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary me-2"><i class="bi bi-save me-1"></i><span data-i18n="settings.saveDnsSettings">Save DNS Settings</span></button>
|
||||
<button type="button" class="btn btn-outline-info" onclick="testDnsConnection()">
|
||||
<span class="spinner-border spinner-border-sm d-none me-1" id="dns-test-spinner"></span>
|
||||
<i class="bi bi-plug me-1"></i><span data-i18n="settings.testConnection">Test Connection</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="dns-test-result" class="mt-3 d-none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- LDAP / Active Directory -->
|
||||
<div class="tab-pane fade" id="settings-ldap">
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3" data-i18n="settings.ldapTitle">LDAP / Active Directory Authentication</h5>
|
||||
<form id="settings-ldap-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-12">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="cfg-ldap-enabled">
|
||||
<label class="form-check-label" for="cfg-ldap-enabled" data-i18n="settings.enableLdap">Enable LDAP / AD Authentication</label>
|
||||
</div>
|
||||
<div class="form-text" data-i18n="settings.ldapDescription">Allow Active Directory users to log in. Local admin accounts always work as fallback.</div>
|
||||
</div>
|
||||
<div class="col-md-5">
|
||||
<label class="form-label" data-i18n="settings.ldapServer">LDAP Server</label>
|
||||
<input type="text" class="form-control" id="cfg-ldap-server" placeholder="192.168.1.10 or dc.example.com">
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<label class="form-label" data-i18n="settings.ldapPort">Port</label>
|
||||
<input type="number" class="form-control" id="cfg-ldap-port" value="389" min="1" max="65535">
|
||||
</div>
|
||||
<div class="col-md-4 d-flex align-items-end pb-1">
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="cfg-ldap-use-ssl">
|
||||
<label class="form-check-label" for="cfg-ldap-use-ssl" data-i18n="settings.ldapUseSsl">Use SSL/TLS (LDAPS)</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.ldapBindDn">Bind DN (Service Account)</label>
|
||||
<input type="text" class="form-control" id="cfg-ldap-bind-dn" placeholder="CN=svcUser,OU=Service,DC=example,DC=com">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.ldapBindPassword">Bind Password</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="cfg-ldap-bind-password" data-i18n-placeholder="settings.leaveEmptyToKeep" placeholder="Leave empty to keep current">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="togglePasswordVisibility('cfg-ldap-bind-password')"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
<div class="form-text" id="ldap-password-status"></div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.ldapBaseDn">Base DN</label>
|
||||
<input type="text" class="form-control" id="cfg-ldap-base-dn" placeholder="DC=example,DC=com">
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.ldapUserFilter">User Filter</label>
|
||||
<input type="text" class="form-control" id="cfg-ldap-user-filter" placeholder="(sAMAccountName={username})">
|
||||
<div class="form-text" data-i18n="settings.ldapUserFilterHint">Use {username} as placeholder for the login name.</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<label class="form-label" data-i18n="settings.ldapGroupDn">Group Restriction DN (optional)</label>
|
||||
<input type="text" class="form-control" id="cfg-ldap-group-dn" placeholder="CN=NetBirdAdmins,OU=Groups,DC=example,DC=com">
|
||||
<div class="form-text" data-i18n="settings.ldapGroupDnHint">If set, only members of this group can log in via LDAP.</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary me-2"><i class="bi bi-save me-1"></i><span data-i18n="settings.saveLdapSettings">Save LDAP Settings</span></button>
|
||||
<button type="button" class="btn btn-outline-info" onclick="testLdapConnection()">
|
||||
<span class="spinner-border spinner-border-sm d-none me-1" id="ldap-test-spinner"></span>
|
||||
<i class="bi bi-plug me-1"></i><span data-i18n="settings.testConnection">Test Connection</span>
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
<div id="ldap-test-result" class="mt-3 d-none"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Updates -->
|
||||
<div class="tab-pane fade" id="settings-update">
|
||||
<div class="card shadow-sm mb-4">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<span data-i18n="settings.versionTitle">Version & Updates</span>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="loadVersionInfo()">
|
||||
<i class="bi bi-arrow-clockwise me-1"></i><span data-i18n="dashboard.refresh">Refresh</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body" id="version-info-content">
|
||||
<div class="text-muted" data-i18n="common.loading">Loading...</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card shadow-sm">
|
||||
<div class="card-body">
|
||||
<h5 class="mb-3" data-i18n="settings.gitTitle">Git Repository Settings</h5>
|
||||
<form id="settings-git-form">
|
||||
<div class="row g-3">
|
||||
<div class="col-md-8">
|
||||
<label class="form-label" data-i18n="settings.gitRepoUrl">Repository URL</label>
|
||||
<input type="text" class="form-control" id="cfg-git-repo-url" placeholder="https://git.example.com/owner/repo">
|
||||
<div class="form-text" data-i18n="settings.gitRepoUrlHint">Used for version checks and one-click updates via Gitea API.</div>
|
||||
</div>
|
||||
<div class="col-md-4">
|
||||
<label class="form-label" data-i18n="settings.gitBranch">Branch</label>
|
||||
<input type="text" class="form-control" id="cfg-git-branch" placeholder="main">
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label" data-i18n="settings.gitToken">Access Token (optional)</label>
|
||||
<div class="input-group">
|
||||
<input type="password" class="form-control" id="cfg-git-token" data-i18n-placeholder="settings.leaveEmptyToKeep" placeholder="Leave empty to keep current">
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="togglePasswordVisibility('cfg-git-token')"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
<div class="form-text" id="git-token-status"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-4">
|
||||
<button type="submit" class="btn btn-primary"><i class="bi bi-save me-1"></i><span data-i18n="settings.saveGitSettings">Save Git Settings</span></button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Security -->
|
||||
<div class="tab-pane fade" id="settings-security">
|
||||
<!-- MFA Settings -->
|
||||
|
||||
202
static/js/app.js
202
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 = `<div class="text-muted">${t('common.loading')}</div>`;
|
||||
try {
|
||||
const data = await api('GET', '/settings/version');
|
||||
const current = data.current || {};
|
||||
const latest = data.latest;
|
||||
const needsUpdate = data.needs_update;
|
||||
|
||||
let html = `<div class="row g-3">
|
||||
<div class="col-md-6">
|
||||
<div class="border rounded p-3">
|
||||
<div class="text-muted small mb-1">${t('settings.currentVersion')}</div>
|
||||
<div class="fw-bold font-monospace">${esc(current.commit || 'unknown')}</div>
|
||||
<div class="text-muted small">${t('settings.branch')}: <strong>${esc(current.branch || 'unknown')}</strong></div>
|
||||
<div class="text-muted small">${esc(current.date || '')}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
if (latest) {
|
||||
const badge = needsUpdate
|
||||
? `<span class="badge bg-warning text-dark ms-1">${t('settings.updateAvailable')}</span>`
|
||||
: `<span class="badge bg-success ms-1">${t('settings.upToDate')}</span>`;
|
||||
html += `<div class="col-md-6">
|
||||
<div class="border rounded p-3 ${needsUpdate ? 'border-warning' : ''}">
|
||||
<div class="text-muted small mb-1">${t('settings.latestVersion')} ${badge}</div>
|
||||
<div class="fw-bold font-monospace">${esc(latest.commit || 'unknown')}</div>
|
||||
<div class="text-muted small">${t('settings.branch')}: <strong>${esc(latest.branch || 'unknown')}</strong></div>
|
||||
<div class="text-muted small">${esc(latest.message || '')}</div>
|
||||
<div class="text-muted small">${esc(latest.date || '')}</div>
|
||||
</div>
|
||||
</div>`;
|
||||
} else if (data.error) {
|
||||
html += `<div class="col-md-6"><div class="alert alert-warning mb-0">${esc(data.error)}</div></div>`;
|
||||
}
|
||||
html += '</div>';
|
||||
|
||||
if (needsUpdate) {
|
||||
html += `<div class="mt-3">
|
||||
<button class="btn btn-warning" onclick="triggerUpdate()">
|
||||
<span class="spinner-border spinner-border-sm d-none me-1" id="update-spinner"></span>
|
||||
<i class="bi bi-arrow-repeat me-1"></i>${t('settings.triggerUpdate')}
|
||||
</button>
|
||||
<div class="text-muted small mt-1">${t('settings.updateWarning')}</div>
|
||||
</div>`;
|
||||
}
|
||||
el.innerHTML = html;
|
||||
} catch (err) {
|
||||
el.innerHTML = `<div class="text-danger">${esc(err.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
@@ -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."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.",
|
||||
|
||||
Reference in New Issue
Block a user