Add SSL certificate mode: Let's Encrypt or Wildcard per NPM
Settings > NPM Integration now allows choosing between per-customer Let's Encrypt certificates (default) or a shared wildcard certificate already uploaded in NPM. Includes backend, frontend UI, and i18n support. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -381,6 +381,32 @@
|
||||
<button class="btn btn-outline-secondary" type="button" onclick="togglePasswordVisibility('cfg-npm-api-password')"><i class="bi bi-eye"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
<!-- SSL Certificate Mode -->
|
||||
<div class="col-12 mt-3">
|
||||
<hr class="my-2">
|
||||
<h6 class="mb-2" data-i18n="settings.sslModeTitle">SSL Certificate Mode</h6>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<label class="form-label" data-i18n="settings.sslMode">SSL Mode</label>
|
||||
<select class="form-select" id="cfg-ssl-mode" onchange="onSslModeChange()">
|
||||
<option value="letsencrypt" data-i18n="settings.sslModeLetsencrypt">Per-Customer Let's Encrypt Certificate</option>
|
||||
<option value="wildcard" data-i18n="settings.sslModeWildcard">Wildcard Certificate (pre-configured in NPM)</option>
|
||||
</select>
|
||||
<div class="form-text" data-i18n="settings.sslModeHint">Choose how SSL certificates are assigned to customer proxy hosts.</div>
|
||||
</div>
|
||||
<div class="col-md-8" id="wildcard-cert-section" style="display:none;">
|
||||
<label class="form-label" data-i18n="settings.wildcardCertificate">Wildcard Certificate</label>
|
||||
<div class="input-group">
|
||||
<select class="form-select" id="cfg-wildcard-cert-id">
|
||||
<option value="" data-i18n="settings.selectCertificate">-- Select a certificate --</option>
|
||||
</select>
|
||||
<button type="button" class="btn btn-outline-secondary" onclick="loadNpmCertificates()" title="Refresh">
|
||||
<i class="bi bi-arrow-clockwise"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="form-text" data-i18n="settings.wildcardCertHint">Select the wildcard certificate (e.g. *.example.com) already uploaded in NPM.</div>
|
||||
<div id="wildcard-cert-status" class="mt-1"></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.saveNpmSettings">Save NPM Settings</span></button>
|
||||
|
||||
@@ -816,6 +816,14 @@ async function loadSettings() {
|
||||
document.getElementById('cfg-dashboard-base-port').value = cfg.dashboard_base_port || 9000;
|
||||
document.getElementById('cfg-npm-api-url').value = cfg.npm_api_url || '';
|
||||
document.getElementById('npm-credentials-status').textContent = cfg.npm_credentials_set ? t('settings.credentialsSet') : t('settings.noCredentials');
|
||||
|
||||
// SSL mode
|
||||
document.getElementById('cfg-ssl-mode').value = cfg.ssl_mode || 'letsencrypt';
|
||||
onSslModeChange();
|
||||
if (cfg.ssl_mode === 'wildcard') {
|
||||
loadNpmCertificates(cfg.wildcard_cert_id);
|
||||
}
|
||||
|
||||
document.getElementById('cfg-mgmt-image').value = cfg.netbird_management_image || '';
|
||||
document.getElementById('cfg-signal-image').value = cfg.netbird_signal_image || '';
|
||||
document.getElementById('cfg-relay-image').value = cfg.netbird_relay_image || '';
|
||||
@@ -876,6 +884,14 @@ document.getElementById('settings-npm-form').addEventListener('submit', async (e
|
||||
const password = document.getElementById('cfg-npm-api-password').value;
|
||||
if (email) payload.npm_api_email = email;
|
||||
if (password) payload.npm_api_password = password;
|
||||
|
||||
// SSL mode
|
||||
const sslMode = document.getElementById('cfg-ssl-mode').value;
|
||||
payload.ssl_mode = sslMode;
|
||||
if (sslMode === 'wildcard') {
|
||||
const certId = document.getElementById('cfg-wildcard-cert-id').value;
|
||||
if (certId) payload.wildcard_cert_id = parseInt(certId);
|
||||
}
|
||||
try {
|
||||
await api('PUT', '/settings/system', payload);
|
||||
showSettingsAlert('success', t('messages.npmSettingsSaved'));
|
||||
@@ -924,6 +940,42 @@ async function testNpmConnection() {
|
||||
}
|
||||
}
|
||||
|
||||
// SSL mode toggle
|
||||
function onSslModeChange() {
|
||||
const mode = document.getElementById('cfg-ssl-mode').value;
|
||||
const section = document.getElementById('wildcard-cert-section');
|
||||
section.style.display = mode === 'wildcard' ? '' : 'none';
|
||||
}
|
||||
|
||||
// Load NPM wildcard certificates into dropdown
|
||||
async function loadNpmCertificates(preselectId) {
|
||||
const select = document.getElementById('cfg-wildcard-cert-id');
|
||||
const statusEl = document.getElementById('wildcard-cert-status');
|
||||
select.innerHTML = `<option value="">${t('settings.selectCertificate')}</option>`;
|
||||
statusEl.textContent = t('common.loading');
|
||||
statusEl.className = 'mt-1 text-muted';
|
||||
|
||||
try {
|
||||
const certs = await api('GET', '/settings/npm-certificates');
|
||||
const wildcards = certs.filter(c => c.is_wildcard || (c.domain_names && c.domain_names.some(d => d.startsWith('*.'))));
|
||||
wildcards.forEach(c => {
|
||||
const domains = (c.domain_names || []).join(', ');
|
||||
const expires = c.expires_on ? ` (${t('settings.expiresOn')}: ${new Date(c.expires_on).toLocaleDateString()})` : '';
|
||||
const opt = document.createElement('option');
|
||||
opt.value = c.id;
|
||||
opt.textContent = `${domains}${expires}`;
|
||||
select.appendChild(opt);
|
||||
});
|
||||
if (preselectId) select.value = preselectId;
|
||||
statusEl.textContent = t('settings.certsLoaded', { count: wildcards.length });
|
||||
statusEl.className = wildcards.length > 0 ? 'mt-1 text-success small' : 'mt-1 text-warning small';
|
||||
if (wildcards.length === 0) statusEl.textContent = t('settings.noWildcardCerts');
|
||||
} catch (err) {
|
||||
statusEl.textContent = t('errors.failed', { error: err.message });
|
||||
statusEl.className = 'mt-1 text-danger small';
|
||||
}
|
||||
}
|
||||
|
||||
// Change password form
|
||||
document.getElementById('change-password-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -147,6 +147,17 @@
|
||||
"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": "Waehlen Sie, ob jeder Kunde ein eigenes Let's Encrypt Zertifikat erhaelt oder ein gemeinsames Wildcard-Zertifikat verwendet wird.",
|
||||
"wildcardCertificate": "Wildcard-Zertifikat",
|
||||
"selectCertificate": "-- Zertifikat waehlen --",
|
||||
"wildcardCertHint": "Waehlen Sie das Wildcard-Zertifikat (z.B. *.example.com), das bereits in NPM hochgeladen ist.",
|
||||
"noWildcardCerts": "Keine Wildcard-Zertifikate in NPM gefunden.",
|
||||
"certsLoaded": "{count} Wildcard-Zertifikat(e) gefunden.",
|
||||
"expiresOn": "Ablaufdatum",
|
||||
"managementImage": "Management Image",
|
||||
"managementImagePlaceholder": "netbirdio/management:latest",
|
||||
"signalImage": "Signal Image",
|
||||
|
||||
@@ -147,6 +147,17 @@
|
||||
"noCredentials": "No NPM credentials configured",
|
||||
"saveNpmSettings": "Save NPM Settings",
|
||||
"testConnection": "Test Connection",
|
||||
"sslModeTitle": "SSL Certificate Mode",
|
||||
"sslMode": "SSL Mode",
|
||||
"sslModeLetsencrypt": "Let's Encrypt (per customer)",
|
||||
"sslModeWildcard": "Wildcard Certificate",
|
||||
"sslModeHint": "Choose whether each customer gets an individual Let's Encrypt certificate or uses a shared wildcard certificate.",
|
||||
"wildcardCertificate": "Wildcard Certificate",
|
||||
"selectCertificate": "-- Select certificate --",
|
||||
"wildcardCertHint": "Select the wildcard certificate (e.g. *.example.com) already uploaded in NPM.",
|
||||
"noWildcardCerts": "No wildcard certificates found in NPM.",
|
||||
"certsLoaded": "{count} wildcard certificate(s) found.",
|
||||
"expiresOn": "Expires",
|
||||
"managementImage": "Management Image",
|
||||
"managementImagePlaceholder": "netbirdio/management:latest",
|
||||
"signalImage": "Signal Image",
|
||||
|
||||
Reference in New Issue
Block a user