mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
Fix multiple user panel 500 errors
- domains: VhostManager::create() called with array instead of 4 params - PHPManager: VhostManager not required; pool writes use sudo tee (permission); updateConfig creates pool if missing instead of throwing - DatabaseManager: MySQL ops used SQLite panel PDO; add dedicated mysqlPdo() using MariaDB socket auth - BackupManager: column name is size_mb not size; diskUsage returns float - DB.php: add LAST_INSERT_ID() → last_insert_rowid() translation - user.js: SSL issue/submit used Nova.api (JSON) but endpoint streams SSE; add _sslStream() helper matching admin panel behavior - schema/migration: add enc_password column to email_accounts Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE email_accounts ADD COLUMN enc_password TEXT;
|
||||||
+4
-3
@@ -183,9 +183,10 @@ CREATE INDEX IF NOT EXISTS idx_dns_records_type ON dns_records (type);
|
|||||||
CREATE TABLE IF NOT EXISTS email_accounts (
|
CREATE TABLE IF NOT EXISTS email_accounts (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
account_id INTEGER NOT NULL,
|
account_id INTEGER NOT NULL,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
password TEXT NOT NULL,
|
password TEXT NOT NULL,
|
||||||
quota_mb INTEGER DEFAULT 500,
|
enc_password TEXT,
|
||||||
|
quota_mb INTEGER DEFAULT 500,
|
||||||
used_mb INTEGER DEFAULT 0,
|
used_mb INTEGER DEFAULT 0,
|
||||||
status TEXT DEFAULT 'active' CHECK(status IN ('active','suspended')),
|
status TEXT DEFAULT 'active' CHECK(status IN ('active','suspended')),
|
||||||
created_at TEXT DEFAULT (datetime('now')),
|
created_at TEXT DEFAULT (datetime('now')),
|
||||||
|
|||||||
@@ -41,13 +41,7 @@ match ($action) {
|
|||||||
@mkdir($docRoot, 0755, true);
|
@mkdir($docRoot, 0755, true);
|
||||||
file_put_contents("$docRoot/index.html", "<html><body><h1>$domain is ready!</h1><p>Upload your website files.</p></body></html>");
|
file_put_contents("$docRoot/index.html", "<html><body><h1>$domain is ready!</h1><p>Upload your website files.</p></body></html>");
|
||||||
|
|
||||||
VhostManager::create([
|
VhostManager::create($acct['username'], $domain, $docRoot, $acct['php_version'] ?? PHP_DEFAULT);
|
||||||
'domain' => $domain,
|
|
||||||
'username' => $acct['username'],
|
|
||||||
'home_dir' => $acct['home_dir'],
|
|
||||||
'doc_root' => $docRoot,
|
|
||||||
'php_ver' => $acct['php_version'] ?? PHP_DEFAULT,
|
|
||||||
]);
|
|
||||||
DNSManager::createZone($accountId, $domain);
|
DNSManager::createZone($accountId, $domain);
|
||||||
audit('domains.add-addon', $domain);
|
audit('domains.add-addon', $domain);
|
||||||
Response::success(null, "Addon domain $domain added");
|
Response::success(null, "Addon domain $domain added");
|
||||||
@@ -67,13 +61,7 @@ match ($action) {
|
|||||||
"INSERT INTO domains (account_id, domain, type, document_root, created_at) VALUES (?,?,?,?,NOW())",
|
"INSERT INTO domains (account_id, domain, type, document_root, created_at) VALUES (?,?,?,?,NOW())",
|
||||||
[$accountId, $full, 'subdomain', $docRoot]
|
[$accountId, $full, 'subdomain', $docRoot]
|
||||||
);
|
);
|
||||||
VhostManager::create([
|
VhostManager::create($acct['username'], $full, $docRoot, $acct['php_version'] ?? PHP_DEFAULT);
|
||||||
'domain' => $full,
|
|
||||||
'username' => $acct['username'],
|
|
||||||
'home_dir' => $acct['home_dir'],
|
|
||||||
'doc_root' => $docRoot,
|
|
||||||
'php_ver' => $acct['php_version'] ?? PHP_DEFAULT,
|
|
||||||
]);
|
|
||||||
$zone = DB::getInstance()->fetchOne("SELECT id FROM dns_zones WHERE domain = ? AND account_id = ?", [$parent, $accountId]);
|
$zone = DB::getInstance()->fetchOne("SELECT id FROM dns_zones WHERE domain = ? AND account_id = ?", [$parent, $accountId]);
|
||||||
if ($zone) DNSManager::addRecord((int)$zone['id'], $sub, 'A', gethostbyname(gethostname()));
|
if ($zone) DNSManager::addRecord((int)$zone['id'], $sub, 'A', gethostbyname(gethostname()));
|
||||||
audit('domains.add-subdomain', $full);
|
audit('domains.add-subdomain', $full);
|
||||||
@@ -89,13 +77,7 @@ match ($action) {
|
|||||||
"INSERT INTO domains (account_id, domain, type, document_root, created_at) VALUES (?,?,?,?,NOW())",
|
"INSERT INTO domains (account_id, domain, type, document_root, created_at) VALUES (?,?,?,?,NOW())",
|
||||||
[$accountId, $alias, 'alias', $acct['home_dir'] . '/public_html']
|
[$accountId, $alias, 'alias', $acct['home_dir'] . '/public_html']
|
||||||
);
|
);
|
||||||
VhostManager::create([
|
VhostManager::create($acct['username'], $alias, $acct['home_dir'] . '/public_html', $acct['php_version'] ?? PHP_DEFAULT);
|
||||||
'domain' => $alias,
|
|
||||||
'username' => $acct['username'],
|
|
||||||
'home_dir' => $acct['home_dir'],
|
|
||||||
'doc_root' => $acct['home_dir'] . '/public_html',
|
|
||||||
'php_ver' => $acct['php_version'] ?? PHP_DEFAULT,
|
|
||||||
]);
|
|
||||||
DNSManager::createZone($accountId, $alias);
|
DNSManager::createZone($accountId, $alias);
|
||||||
audit('domains.add-alias', $alias);
|
audit('domains.add-alias', $alias);
|
||||||
Response::success(null, "Domain alias $alias added");
|
Response::success(null, "Domain alias $alias added");
|
||||||
|
|||||||
@@ -46,9 +46,10 @@ class BackupManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$size = file_exists($filepath) ? filesize($filepath) : 0;
|
$bytes = file_exists($filepath) ? filesize($filepath) : 0;
|
||||||
$this->db->prepare("UPDATE backups SET status='complete', size=? WHERE id=?")->execute([$size, $backupId]);
|
$sizeMb = round($bytes / 1048576, 2);
|
||||||
return ['id' => $backupId, 'filename' => $filename, 'size' => $size];
|
$this->db->prepare("UPDATE backups SET status='complete', size_mb=? WHERE id=?")->execute([$sizeMb, $backupId]);
|
||||||
|
return ['id' => $backupId, 'filename' => $filename, 'size_mb' => $sizeMb];
|
||||||
|
|
||||||
} catch (RuntimeException $e) {
|
} catch (RuntimeException $e) {
|
||||||
$this->db->prepare("UPDATE backups SET status='failed' WHERE id=?")->execute([$backupId]);
|
$this->db->prepare("UPDATE backups SET status='failed' WHERE id=?")->execute([$backupId]);
|
||||||
@@ -147,14 +148,14 @@ class BackupManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Disk usage ────────────────────────────────────────────────────────────
|
// ── Disk usage ────────────────────────────────────────────────────────────
|
||||||
public function diskUsage(int $accountId = 0): int {
|
public function diskUsage(int $accountId = 0): float {
|
||||||
if ($accountId) {
|
if ($accountId) {
|
||||||
$stmt = $this->db->prepare("SELECT COALESCE(SUM(size),0) FROM backups WHERE account_id=? AND status='complete'");
|
$stmt = $this->db->prepare("SELECT COALESCE(SUM(size_mb),0) FROM backups WHERE account_id=? AND status='complete'");
|
||||||
$stmt->execute([$accountId]);
|
$stmt->execute([$accountId]);
|
||||||
} else {
|
} else {
|
||||||
$stmt = $this->db->query("SELECT COALESCE(SUM(size),0) FROM backups WHERE status='complete'");
|
$stmt = $this->db->query("SELECT COALESCE(SUM(size_mb),0) FROM backups WHERE status='complete'");
|
||||||
}
|
}
|
||||||
return (int)$stmt->fetchColumn();
|
return (float)$stmt->fetchColumn();
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getAccount(int $id): array {
|
private function getAccount(int $id): array {
|
||||||
|
|||||||
@@ -93,6 +93,9 @@ class DB {
|
|||||||
$sql
|
$sql
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// LAST_INSERT_ID() → last_insert_rowid()
|
||||||
|
$sql = preg_replace('/\bLAST_INSERT_ID\(\)/i', 'last_insert_rowid()', $sql);
|
||||||
|
|
||||||
// IFNULL → COALESCE (SQLite supports both but be safe)
|
// IFNULL → COALESCE (SQLite supports both but be safe)
|
||||||
$sql = preg_replace('/\bIFNULL\s*\(/i', 'COALESCE(', $sql);
|
$sql = preg_replace('/\bIFNULL\s*\(/i', 'COALESCE(', $sql);
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,26 @@
|
|||||||
*/
|
*/
|
||||||
class DatabaseManager {
|
class DatabaseManager {
|
||||||
|
|
||||||
|
private static function mysqlPdo(): PDO {
|
||||||
|
$cfg = @parse_ini_file('/etc/novacpx/config.ini', true) ?: [];
|
||||||
|
$host = $cfg['mysql']['host'] ?? '127.0.0.1';
|
||||||
|
$port = $cfg['mysql']['port'] ?? '3306';
|
||||||
|
$user = $cfg['mysql']['root_user'] ?? 'root';
|
||||||
|
$pass = $cfg['mysql']['root_pass'] ?? '';
|
||||||
|
$opts = [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION];
|
||||||
|
if ($pass === '' && $host === '127.0.0.1') {
|
||||||
|
// Try socket auth (MariaDB/MySQL root with no password)
|
||||||
|
try {
|
||||||
|
return new PDO("mysql:unix_socket=/var/run/mysqld/mysqld.sock", $user, '', $opts);
|
||||||
|
} catch (PDOException $e) { /* fall through to TCP */ }
|
||||||
|
}
|
||||||
|
return new PDO("mysql:host={$host};port={$port}", $user, $pass, $opts);
|
||||||
|
}
|
||||||
|
|
||||||
public static function createMySQL(int $accountId, string $dbName, string $dbUser, string $dbPass): int {
|
public static function createMySQL(int $accountId, string $dbName, string $dbUser, string $dbPass): int {
|
||||||
self::validateName($dbName); self::validateName($dbUser);
|
self::validateName($dbName); self::validateName($dbUser);
|
||||||
$db = DB::getInstance();
|
$db = DB::getInstance();
|
||||||
$pdo = $db->pdo();
|
$pdo = self::mysqlPdo();
|
||||||
|
|
||||||
$pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
$pdo->exec("CREATE DATABASE IF NOT EXISTS `{$dbName}` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci");
|
||||||
$pdo->exec("CREATE USER IF NOT EXISTS '{$dbUser}'@'localhost' IDENTIFIED BY " . $pdo->quote($dbPass));
|
$pdo->exec("CREATE USER IF NOT EXISTS '{$dbUser}'@'localhost' IDENTIFIED BY " . $pdo->quote($dbPass));
|
||||||
@@ -35,7 +51,7 @@ class DatabaseManager {
|
|||||||
|
|
||||||
public static function drop(string $dbName, string $dbUser, string $type = 'mysql'): void {
|
public static function drop(string $dbName, string $dbUser, string $type = 'mysql'): void {
|
||||||
if ($type === 'mysql') {
|
if ($type === 'mysql') {
|
||||||
$pdo = DB::getInstance()->pdo();
|
$pdo = self::mysqlPdo();
|
||||||
$pdo->exec("DROP DATABASE IF EXISTS `{$dbName}`");
|
$pdo->exec("DROP DATABASE IF EXISTS `{$dbName}`");
|
||||||
$pdo->exec("DROP USER IF EXISTS '{$dbUser}'@'localhost'");
|
$pdo->exec("DROP USER IF EXISTS '{$dbUser}'@'localhost'");
|
||||||
$pdo->exec("FLUSH PRIVILEGES");
|
$pdo->exec("FLUSH PRIVILEGES");
|
||||||
@@ -51,7 +67,7 @@ class DatabaseManager {
|
|||||||
$dbe = $db->fetchOne("SELECT * FROM `databases` WHERE id = ?", [$id]);
|
$dbe = $db->fetchOne("SELECT * FROM `databases` WHERE id = ?", [$id]);
|
||||||
if (!$dbe) throw new RuntimeException("Database not found");
|
if (!$dbe) throw new RuntimeException("Database not found");
|
||||||
if ($dbe['db_type'] === 'mysql') {
|
if ($dbe['db_type'] === 'mysql') {
|
||||||
$pdo = $db->pdo();
|
$pdo = self::mysqlPdo();
|
||||||
$pdo->exec("ALTER USER '{$dbe['db_user']}'@'localhost' IDENTIFIED BY " . $pdo->quote($newPass));
|
$pdo->exec("ALTER USER '{$dbe['db_user']}'@'localhost' IDENTIFIED BY " . $pdo->quote($newPass));
|
||||||
} else {
|
} else {
|
||||||
shell_exec("sudo -u postgres psql -c \"ALTER USER {$dbe['db_user']} WITH PASSWORD " . escapeshellarg($newPass) . "\" 2>/dev/null");
|
shell_exec("sudo -u postgres psql -c \"ALTER USER {$dbe['db_user']} WITH PASSWORD " . escapeshellarg($newPass) . "\" 2>/dev/null");
|
||||||
@@ -61,11 +77,12 @@ class DatabaseManager {
|
|||||||
|
|
||||||
public static function getSize(string $dbName, string $type = 'mysql'): float {
|
public static function getSize(string $dbName, string $type = 'mysql'): float {
|
||||||
if ($type === 'mysql') {
|
if ($type === 'mysql') {
|
||||||
$row = DB::getInstance()->fetchOne(
|
$stmt = self::mysqlPdo()->prepare(
|
||||||
"SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size
|
"SELECT ROUND(SUM(data_length + index_length) / 1024 / 1024, 2) AS size
|
||||||
FROM information_schema.tables WHERE table_schema = ?",
|
FROM information_schema.tables WHERE table_schema = ?"
|
||||||
[$dbName]
|
|
||||||
);
|
);
|
||||||
|
$stmt->execute([$dbName]);
|
||||||
|
$row = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
return (float)($row['size'] ?? 0);
|
return (float)($row['size'] ?? 0);
|
||||||
}
|
}
|
||||||
$out = shell_exec("sudo -u postgres psql -t -c \"SELECT pg_size_pretty(pg_database_size('{$dbName}'))\" 2>/dev/null");
|
$out = shell_exec("sudo -u postgres psql -t -c \"SELECT pg_size_pretty(pg_database_size('{$dbName}'))\" 2>/dev/null");
|
||||||
|
|||||||
@@ -2,16 +2,25 @@
|
|||||||
/**
|
/**
|
||||||
* PHPManager — per-account PHP-FPM pools + version switching
|
* PHPManager — per-account PHP-FPM pools + version switching
|
||||||
*/
|
*/
|
||||||
|
if (!class_exists('VhostManager')) require_once __DIR__ . '/VhostManager.php';
|
||||||
|
|
||||||
class PHPManager {
|
class PHPManager {
|
||||||
|
|
||||||
private static string $poolDir = '/etc/php/{ver}/fpm/pool.d';
|
private static string $poolDir = '/etc/php/{ver}/fpm/pool.d';
|
||||||
|
|
||||||
|
private static function writeFile(string $path, string $content): void {
|
||||||
|
$tmp = tempnam('/tmp', 'ncpx_pool_');
|
||||||
|
file_put_contents($tmp, $content);
|
||||||
|
shell_exec("sudo tee " . escapeshellarg($path) . " > /dev/null < " . escapeshellarg($tmp));
|
||||||
|
@unlink($tmp);
|
||||||
|
}
|
||||||
|
|
||||||
public static function createPool(string $username, string $phpVer): void {
|
public static function createPool(string $username, string $phpVer): void {
|
||||||
$poolFile = str_replace('{ver}', $phpVer, self::$poolDir) . "/{$username}.conf";
|
$poolFile = str_replace('{ver}', $phpVer, self::$poolDir) . "/{$username}.conf";
|
||||||
$homeDir = "/home/{$username}";
|
$homeDir = "/home/{$username}";
|
||||||
$sock = "/run/php/php{$phpVer}-fpm-{$username}.sock";
|
$sock = "/run/php/php{$phpVer}-fpm-{$username}.sock";
|
||||||
|
|
||||||
file_put_contents($poolFile, "[{$username}]
|
self::writeFile($poolFile, "[{$username}]
|
||||||
user = {$username}
|
user = {$username}
|
||||||
group = www-data
|
group = www-data
|
||||||
listen = {$sock}
|
listen = {$sock}
|
||||||
@@ -39,7 +48,7 @@ php_value[max_execution_time] = 30
|
|||||||
public static function removePool(string $username): void {
|
public static function removePool(string $username): void {
|
||||||
foreach (['7.4','8.1','8.2','8.3'] as $ver) {
|
foreach (['7.4','8.1','8.2','8.3'] as $ver) {
|
||||||
$file = str_replace('{ver}', $ver, self::$poolDir) . "/{$username}.conf";
|
$file = str_replace('{ver}', $ver, self::$poolDir) . "/{$username}.conf";
|
||||||
if (file_exists($file)) { unlink($file); self::reloadFPM($ver); }
|
if (file_exists($file)) { shell_exec("sudo rm -f " . escapeshellarg($file)); self::reloadFPM($ver); }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,9 +79,9 @@ php_value[max_execution_time] = 30
|
|||||||
if (!$acct) throw new RuntimeException("Account not found");
|
if (!$acct) throw new RuntimeException("Account not found");
|
||||||
|
|
||||||
$poolFile = str_replace('{ver}', $acct['php_version'], self::$poolDir) . "/{$acct['username']}.conf";
|
$poolFile = str_replace('{ver}', $acct['php_version'], self::$poolDir) . "/{$acct['username']}.conf";
|
||||||
if (!file_exists($poolFile)) throw new RuntimeException("PHP-FPM pool not found");
|
if (!file_exists($poolFile)) self::createPool($acct['username'], $acct['php_version']);
|
||||||
|
|
||||||
$content = file_get_contents($poolFile);
|
$content = file_get_contents($poolFile) ?: '';
|
||||||
$map = [
|
$map = [
|
||||||
'memory_limit' => 'php_value[memory_limit]',
|
'memory_limit' => 'php_value[memory_limit]',
|
||||||
'max_execution_time' => 'php_value[max_execution_time]',
|
'max_execution_time' => 'php_value[max_execution_time]',
|
||||||
@@ -84,7 +93,7 @@ php_value[max_execution_time] = 30
|
|||||||
$content = preg_replace("/{$iniKey}\s*=.*/", "{$iniKey} = {$cfg[$key]}", $content);
|
$content = preg_replace("/{$iniKey}\s*=.*/", "{$iniKey} = {$cfg[$key]}", $content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
file_put_contents($poolFile, $content);
|
self::writeFile($poolFile, $content);
|
||||||
self::reloadFPM($acct['php_version']);
|
self::reloadFPM($acct['php_version']);
|
||||||
|
|
||||||
$db->execute(
|
$db->execute(
|
||||||
|
|||||||
@@ -240,13 +240,54 @@ window.removeDomain = (id, domain) => {
|
|||||||
}, true);
|
}, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
window.issueSSL = async (domainId, domain) => {
|
function _sslStream(params, onSuccess) {
|
||||||
Nova.loading(`Issuing SSL for ${domain}…`);
|
const termId = 'ssl-term-' + Date.now();
|
||||||
const res = await Nova.api('ssl', 'issue', { method: 'POST', body: { domain } });
|
Nova.modal(`SSL: ${params.domain}`, `
|
||||||
Nova.loadingDone();
|
<div id="${termId}" style="background:#1a1a2e;color:#e0e0e0;font-family:monospace;font-size:.82rem;
|
||||||
if (res?.success) { Nova.toast('SSL issued successfully','success'); loadDomainsList(); }
|
padding:1rem;border-radius:6px;height:260px;overflow-y:auto;white-space:pre-wrap;line-height:1.5">
|
||||||
else Nova.toast(res?.message || 'SSL failed — check domain DNS','error',6000);
|
<span style="color:#7ec8e3">Requesting certificate…</span>\n
|
||||||
};
|
</div>`,
|
||||||
|
`<button class="btn btn-ghost" id="ssl-term-close" onclick="this.closest('.modal-overlay').remove()">Close</button>`);
|
||||||
|
const term = document.getElementById(termId);
|
||||||
|
const append = t => { term.textContent += t; term.scrollTop = term.scrollHeight; };
|
||||||
|
fetch('/api/ssl/issue', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(params),
|
||||||
|
credentials: 'same-origin',
|
||||||
|
}).then(resp => {
|
||||||
|
if (!resp.ok) { append(`\nHTTP error ${resp.status}`); return; }
|
||||||
|
const reader = resp.body.getReader();
|
||||||
|
const dec = new TextDecoder();
|
||||||
|
let buf = '';
|
||||||
|
const read = () => reader.read().then(({ done, value }) => {
|
||||||
|
if (done) { append('\n[done]'); return; }
|
||||||
|
buf += dec.decode(value, { stream: true });
|
||||||
|
const parts = buf.split('\n\n');
|
||||||
|
buf = parts.pop();
|
||||||
|
for (const part of parts) {
|
||||||
|
const m = part.match(/^data: (.+)$/m);
|
||||||
|
if (!m) continue;
|
||||||
|
try {
|
||||||
|
const obj = JSON.parse(m[1]);
|
||||||
|
if (obj.line) { append(obj.line); }
|
||||||
|
else if (obj.done) {
|
||||||
|
const btn = document.getElementById('ssl-term-close');
|
||||||
|
if (btn) {
|
||||||
|
btn.textContent = obj.success ? 'Done ✓' : 'Close';
|
||||||
|
btn.className = obj.success ? 'btn btn-primary' : 'btn btn-ghost';
|
||||||
|
if (obj.success) btn.onclick = () => { document.querySelector('.modal-overlay')?.remove(); if (onSuccess) onSuccess(); };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
}
|
||||||
|
read();
|
||||||
|
}).catch(err => append(`\n[error: ${err.message}]`));
|
||||||
|
read();
|
||||||
|
}).catch(err => append(`\n[error: ${err.message}]`));
|
||||||
|
}
|
||||||
|
|
||||||
|
window.issueSSL = (domainId, domain) => _sslStream({ domain }, () => loadDomainsList());
|
||||||
window.issueSSL = window.issueSSL;
|
window.issueSSL = window.issueSSL;
|
||||||
|
|
||||||
/* ── Email ──────────────────────────────────────────────────────────────── */
|
/* ── Email ──────────────────────────────────────────────────────────────── */
|
||||||
@@ -514,15 +555,11 @@ window.issueNewSSL = () => {
|
|||||||
`<button class="btn btn-primary" onclick="submitIssueSSL()">Issue SSL</button>`);
|
`<button class="btn btn-primary" onclick="submitIssueSSL()">Issue SSL</button>`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
window.submitIssueSSL = async () => {
|
window.submitIssueSSL = () => {
|
||||||
const domain = document.getElementById('ssl-dom')?.value;
|
const domain = document.getElementById('ssl-dom')?.value;
|
||||||
const email = document.getElementById('ssl-email')?.value;
|
const email = document.getElementById('ssl-email')?.value;
|
||||||
document.querySelector('.modal-overlay')?.remove();
|
document.querySelector('.modal-overlay')?.remove();
|
||||||
Nova.loading(`Issuing SSL for ${domain}…`);
|
_sslStream({ domain, email }, () => loadSSLList());
|
||||||
const res = await Nova.api('ssl', 'issue', { method:'POST', body:{ domain, email }});
|
|
||||||
Nova.loadingDone();
|
|
||||||
if (res?.success) { Nova.toast('SSL issued!','success'); loadSSLList(); }
|
|
||||||
else Nova.toast(res?.message || 'SSL issue failed','error',8000);
|
|
||||||
};
|
};
|
||||||
window.renewCert = async (id) => {
|
window.renewCert = async (id) => {
|
||||||
Nova.toast('Renewing…','info');
|
Nova.toast('Renewing…','info');
|
||||||
|
|||||||
Reference in New Issue
Block a user