feat: Dark Mode + Zertifikat-Verwaltung im Superadmin

- Dark Mode: ThemeProvider (next-themes), ThemeToggle in Navbar (Hell/Dunkel/System)
- Zertifikat-Tab (superadmin only): aktuelles Zertifikat anzeigen, Upload (cert+key),
  Self-Signed ausstellen, Let's Encrypt/ACME
- Backend: /api/admin/cert/* Endpunkte (info, upload, self-signed, acme), nginx reload
- HTTPS bereits live auf Server (self-signed RSA-4096, Port 443)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-20 00:14:43 +01:00
parent 0e62b10bd4
commit 9e71af104f
8 changed files with 708 additions and 3 deletions
+61
View File
@@ -935,3 +935,64 @@ export async function createLabelRule(
export async function deleteLabelRule(id: number): Promise<void> {
return request<void>(`/api/admin/label-rules/${id}`, { method: "DELETE" });
}
// ── Certificate Management ────────────────────────────────────────────────
export interface CertInfo {
exists: boolean;
subject?: string;
issuer?: string;
not_before?: string;
not_after?: string;
dns_names?: string[];
ip_addresses?: string[];
fingerprint_sha256?: string;
is_self_signed?: boolean;
days_remaining?: number;
}
export interface SelfSignedRequest {
common_name: string;
dns_names: string[];
ip_addresses: string[];
validity_years: number;
}
export interface ACMERequest {
domain: string;
email: string;
}
export async function getCertInfo(): Promise<CertInfo> {
return request<CertInfo>("/api/admin/cert/info");
}
export async function uploadCert(cert: File, key: File): Promise<{ ok: boolean; message: string }> {
const form = new FormData();
form.append("cert", cert);
form.append("key", key);
const res = await fetch(`${API_BASE}/api/admin/cert/upload`, {
method: "POST",
credentials: "include",
body: form,
});
if (!res.ok) {
const body = await res.text();
throw new Error(body || `Upload failed: ${res.status}`);
}
return res.json();
}
export async function generateSelfSignedCert(req: SelfSignedRequest): Promise<CertInfo & { ok: boolean }> {
return request<CertInfo & { ok: boolean }>("/api/admin/cert/self-signed", {
method: "POST",
body: JSON.stringify(req),
});
}
export async function requestACMECert(req: ACMERequest): Promise<{ ok: boolean; output: string }> {
return request<{ ok: boolean; output: string }>("/api/admin/cert/acme", {
method: "POST",
body: JSON.stringify(req),
});
}