Files
archivmail/src/lib/api/system.ts
T
sysops 8564d7c11f feat: Versions-Spalte im Dienste-Tab für Superadmin
Bislang war die einzige Möglichkeit, die laufende Manticore-/PostgreSQL-/
Postfix-/nginx-Version zu sehen, SSH + <binary> --version. serviceVersion()
löst das best-effort pro Dienst auf (archivmail/-web: appVersion-Konstante,
manticore: searchd --version, postgresql: psql --version, postfix: postconf
mail_version, nginx: nginx -v). Fehler werden verschluckt (leerer String),
eine unbekannte Version darf den Dienst-Status nicht auf "Fehler" kippen.

manticore war bisher gar nicht in der Dienste-Whitelist (allowedServices) —
jetzt ergänzt, damit es überhaupt in der Liste auftaucht und
start/stop/restart wie die anderen Dienste möglich ist.
2026-07-05 19:12:38 +02:00

267 lines
7.3 KiB
TypeScript

import { API_BASE, request } from "./core";
// ── Types ────────────────────────────────────────────────────────────────────
export interface HealthResponse {
status: string;
}
export interface SystemInfo {
fqdn: string;
imap_port: number;
imap_port_alt: number;
}
export interface SMTPStatus {
// global daemon fields (superadmin)
running?: boolean;
enabled?: boolean;
bind?: string;
domain?: string;
tls?: boolean;
max_size_mb?: number;
allowed_ips?: string[];
received?: number;
rejected?: number;
last_mail_at?: string;
// tenant-scoped fields (domain_admin)
tenant_only?: boolean;
domains?: string[];
total_mails?: number;
total_bytes?: number;
}
export interface StorageStats {
total_mails: number;
total_bytes: number;
}
export interface ServiceStatus {
name: string;
display_name: string;
active: string;
sub: string;
enabled: string;
description: string;
version?: string;
external_blocked?: boolean;
}
export interface AuditEntry {
id: string;
timestamp: string;
event_type: string;
username: string;
detail: string;
}
export interface AuditResponse {
total: number;
entries: AuditEntry[];
}
export interface SystemStatsCPU {
load1: number;
load5: number;
load15: number;
num_cpu: number;
}
export interface SystemStatsRAM {
total_bytes: number;
used_bytes: number;
free_bytes: number;
used_pct: number;
}
export interface SystemStatsDisk {
mount: string;
total_bytes: number;
used_bytes: number;
free_bytes: number;
used_pct: number;
fstype: string;
}
export interface SystemStatsMailInfo {
id: string;
date: string;
from: string;
subject: string;
}
export interface SystemStatsActivity {
last_60_min: number;
last_24h: number;
last_7d: number;
last_30d: number;
}
export interface SystemStatsEstimate {
avg_mails_per_day: number;
avg_mail_bytes: number;
days_until_full: number; // -1 = unknown
archive_age_days: number;
}
export interface SystemStats {
cpu: SystemStatsCPU;
ram: SystemStatsRAM;
disks: SystemStatsDisk[];
uptime: { seconds: number };
archive: {
first_mail: SystemStatsMailInfo | null;
last_mail: SystemStatsMailInfo | null;
};
activity: SystemStatsActivity;
estimate: SystemStatsEstimate;
}
export interface TimeseriesPoint {
day: string; // "2026-04-05"
count: number;
}
export interface SecurityCheck {
name: string;
status: "ok" | "warning" | "error";
message: string;
}
export interface SecurityAuditResult {
checks: SecurityCheck[];
run_at: string;
}
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;
}
// ── Health & Stats ────────────────────────────────────────────────────────────
export async function getHealth(): Promise<HealthResponse> {
return request<HealthResponse>("/api/health");
}
export async function getSystemInfo(): Promise<SystemInfo> {
return request<SystemInfo>("/api/system/info");
}
export async function getSMTPStatus(): Promise<SMTPStatus> {
return request<SMTPStatus>("/api/admin/smtp/status");
}
export async function getStorageStats(): Promise<StorageStats> {
return request<StorageStats>("/api/admin/storage/stats");
}
export async function getSystemStats(): Promise<SystemStats> {
return request<SystemStats>("/api/admin/system/stats");
}
export async function getMailTimeseries(days = 30): Promise<{ days: number; points: TimeseriesPoint[] }> {
return request<{ days: number; points: TimeseriesPoint[] }>(`/api/admin/stats/timeseries?days=${days}`);
}
// ── Services ──────────────────────────────────────────────────────────────────
export async function getServices(): Promise<ServiceStatus[]> {
return request<ServiceStatus[]>("/api/admin/services");
}
export async function serviceAction(
name: string,
action: "start" | "stop" | "restart" | "enable" | "disable" | "block_external" | "allow_external"
): Promise<ServiceStatus> {
return request<ServiceStatus>(`/api/admin/services/${encodeURIComponent(name)}/action`, {
method: "POST",
body: JSON.stringify({ action }),
});
}
// ── Audit ─────────────────────────────────────────────────────────────────────
export async function getAuditLog(params: {
page?: number;
page_size?: number;
username?: string;
event_type?: string;
}): Promise<AuditResponse> {
const sp = new URLSearchParams();
if (params.page) sp.set("page", String(params.page));
if (params.page_size) sp.set("page_size", String(params.page_size));
if (params.username) sp.set("username", params.username);
if (params.event_type) sp.set("event_type", params.event_type);
return request<AuditResponse>(`/api/audit?${sp.toString()}`);
}
// ── Security ──────────────────────────────────────────────────────────────────
export async function getSecurityAudit(): Promise<SecurityAuditResult> {
return request<SecurityAuditResult>("/api/admin/security/audit");
}
export async function fixSecurityIssue(action: string): Promise<{ message: string }> {
return request<{ message: string }>("/api/admin/security/fix", {
method: "POST",
body: JSON.stringify({ action }),
});
}
// ── Certificates ──────────────────────────────────────────────────────────────
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),
});
}