Add SSL certificate provisioning button for superadmin

- New provision_ssl API endpoint runs certbot for new domains
- SSL button appears next to domain textarea for superadmin
- Shell script on server handles Apache config + Let's Encrypt
- DNS check skips domains without resolution to avoid certbot errors

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 15:09:34 +02:00
parent d4e06fd586
commit a94d1edee0
3 changed files with 60 additions and 0 deletions

32
api.php
View File

@@ -4256,6 +4256,38 @@ switch ($action) {
echo json_encode(['success' => true]);
break;
case 'provision_ssl':
requireSuperAdmin();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$domains = $input['domains'] ?? [];
if (empty($domains)) {
http_response_code(400);
echo json_encode(['error' => 'Domainit puuttuvat']);
break;
}
// Validoi domainit
foreach ($domains as $d) {
if (!preg_match('/^[a-z0-9.-]+\.[a-z]{2,}$/', $d)) {
http_response_code(400);
echo json_encode(['error' => "Virheellinen domain: $d"]);
break 2;
}
}
// Suorita provisiointi shell-skriptinä
$escapedDomains = array_map('escapeshellarg', $domains);
$domainList = implode(' ', $escapedDomains);
$output = [];
$exitCode = 0;
exec("sudo /usr/local/bin/provision-ssl.sh $domainList 2>&1", $output, $exitCode);
if ($exitCode !== 0) {
http_response_code(500);
echo json_encode(['error' => 'SSL-provisiointi epäonnistui: ' . implode("\n", $output)]);
} else {
echo json_encode(['success' => true, 'message' => 'SSL-sertifikaatti päivitetty: ' . implode(', ', $domains)]);
}
break;
case 'company_switch':
requireAuth();
if ($method !== 'POST') break;

View File

@@ -1673,6 +1673,8 @@
<div class="form-group full-width">
<label>Domainit (yksi per rivi)</label>
<textarea id="company-edit-domains" rows="3" placeholder="intra.yritys.fi&#10;intra.toinen.fi" style="font-family:monospace;font-size:0.85rem;"></textarea>
<button class="btn-secondary" id="btn-provision-ssl" style="display:none;margin-top:0.5rem;font-size:0.82rem;" title="Lisää domainit Apache-configiin ja päivitä Let's Encrypt -sertifikaatti">🔒 Päivitä SSL-sertifikaatti</button>
<small id="ssl-provision-status" style="display:none;margin-top:0.25rem;color:#888;"></small>
</div>
</div>
<!-- Moduulit (vain superadmin) -->

View File

@@ -2908,6 +2908,8 @@ async function showCompanyDetail(id) {
if (modulesSection) modulesSection.style.display = isSA ? '' : 'none';
if (integrationsSection) integrationsSection.style.display = isSA ? '' : 'none';
if (ipsSection) ipsSection.style.display = isSA ? '' : 'none';
const sslBtn = document.getElementById('btn-provision-ssl');
if (sslBtn) sslBtn.style.display = isSA ? '' : 'none';
// Moduuli-checkboxit (yhteensopivuus: vanha 'devices' → 'tekniikka')
let enabledMods = comp?.enabled_modules || [];
@@ -3203,6 +3205,30 @@ document.getElementById('btn-save-company-settings').addEventListener('click', a
} catch (e) { alert(e.message); }
});
// SSL-sertifikaatin provisiointi (superadmin)
document.getElementById('btn-provision-ssl')?.addEventListener('click', async () => {
const domains = document.getElementById('company-edit-domains').value.trim().split('\n').map(d => d.trim()).filter(Boolean);
if (!domains.length) { alert('Lisää ensin domainit!'); return; }
if (!confirm('Päivitetäänkö Apache-config ja Let\'s Encrypt -sertifikaatti seuraavilla domaineilla?\n\n' + domains.join('\n'))) return;
const btn = document.getElementById('btn-provision-ssl');
const status = document.getElementById('ssl-provision-status');
btn.disabled = true;
btn.textContent = '⏳ Provisioidaan...';
status.style.display = 'block';
status.textContent = 'Päivitetään Apache-config ja Let\'s Encrypt...';
try {
const result = await apiCall('provision_ssl', 'POST', { domains });
status.style.color = '#27ae60';
status.textContent = '✅ ' + (result.message || 'SSL-sertifikaatti päivitetty!');
} catch (e) {
status.style.color = '#e74c3c';
status.textContent = '❌ ' + e.message;
} finally {
btn.disabled = false;
btn.textContent = '🔒 Päivitä SSL-sertifikaatti';
}
});
// ==================== POSTILAATIKOT ====================
let mailboxesData = [];