import React, { useEffect, useMemo, useState } from "react"; import { Alert, Box, Button, Chip, Paper, Stack, Tab, Tabs, TextField, Typography, Checkbox, FormControlLabel, } from "@mui/material"; import { api, getApiErrorMessage } from "../api"; import { useI18n } from "../i18n/I18nProvider"; type AiServiceMetrics = { healthy: boolean; model?: string | null; device?: string | null; gpuAvailable?: boolean; gpuName?: string | null; ocrAvailable?: boolean | null; ocrLanguages?: string | null; ollamaConfigured?: boolean | null; ollamaReachable?: boolean | null; ollamaModel?: string | null; ollamaModelAvailable?: boolean | null; ollamaVersion?: string | null; ollamaInstalledModels?: string[] | null; ollamaLoadedModels?: string[] | null; ollamaLoadedCount?: number | null; healthLatencyMs?: number | null; probeLatencyMs?: number | null; lastProbeAt?: string | null; lastProbeSuccessAt?: string | null; lastProbeFailureAt?: string | null; probeFailures: number; requests: number; cacheHits: number; cacheMisses: number; failures: number; averageLatencyMs?: number | null; ocrRequests: number; ocrFailures: number; averageOcrLatencyMs?: number | null; lastOcrSuccessAt?: string | null; lastOcrFailureAt?: string | null; lastSuccessAt?: string | null; lastFailureAt?: string | null; lastError?: string | null; }; type EditableEmailSettings = { enabled: boolean; host: string; port: number; user: string; from: string; fromName: string; enableSsl: boolean; timeoutMs: number; usesOverrides: boolean; hasPassword: boolean; }; type CvBenchmarkEntry = { FileName: string; Slug: string; Extension: string; Characters: number; OutputPath: string; ApprovedFixturePath?: string | null; CandidateFixturePath?: string | null; ContactLocation?: string | null; FirstJob?: string | null; FirstJobLocation?: string | null; FirstEducation?: string | null; FirstEducationLocation?: string | null; QualificationLevels: string[]; SuspiciousLocations: string[]; CoverageScore: number; ConfidenceScore: number; ConsistencyScore: number; DiffSummary?: string | null; }; type CvBenchmarkIndex = { CorpusRoot: string; OutputRoot: string; GeneratedAtUtc: string; TotalFiles: number; AverageCoverage: number; AverageConfidence: number; AverageConsistency: number; FilesWithSuspiciousLocations: number; MissingApprovedFixtures: number; Entries: CvBenchmarkEntry[]; }; type CvBenchmarkStatus = { indexJson?: string | null; reportMarkdown?: string | null; rootPath: string; lastUpdatedAtUtc?: string | null; }; type SystemStatus = { environment: string; contentRoot: string; version: string; commitSha?: string | null; buildStamp?: string | null; storage: { dataRoot: string; dbPath: string; dbExists: boolean; dbSizeBytes?: number | null; companyCount: number; jobCount: number; deletedCount: number; }; email: { enabled: boolean; host?: string | null; port: number; enableSsl: boolean; from?: string | null; fromName?: string | null; }; database: { provider: string; looksConfigured: boolean; canConnect: boolean; target?: string | null; usesFileStorage: boolean; warning?: string | null; }; runtime: { framework: string; osDescription: string; processArchitecture: string; machineName?: string | null; }; auth: { required: boolean; hasJwtKey: boolean; googleConfigured: boolean; gmailConfigured: boolean; }; ai: AiServiceMetrics; }; function formatBytes(bytes?: number | null) { if (bytes == null) return "-"; if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } function displayMetadata(value?: string | null) { return value && value.trim().length > 0 ? value : "-"; } function formatDate(value?: string | null) { return value ? new Date(value).toLocaleString() : "-"; } function formatPercent(value?: number | null) { return typeof value === "number" ? `${Math.round(value * 100)}%` : "-"; } function parseBenchmarkIndex(indexJson?: string | null): CvBenchmarkIndex | null { if (!indexJson?.trim()) return null; try { return JSON.parse(indexJson) as CvBenchmarkIndex; } catch { return null; } } function benchmarkTone(value?: number | null) { if (typeof value !== "number") return "default" as const; if (value >= 0.8) return "success" as const; if (value >= 0.6) return "warning" as const; return "error" as const; } function SummaryCard({ title, value, subtitle, tone = "default" }: { title: string; value: string; subtitle?: string; tone?: "default" | "success" | "warning" | "error" }) { const color = tone === "success" ? "success.main" : tone === "warning" ? "warning.main" : tone === "error" ? "error.main" : "text.primary"; return ( {title} {value} {subtitle || "-"} ); } function DetailRow({ label, value }: { label: string; value: React.ReactNode }) { return {label}: {value}; } export default function AdminSystemPage() { const { t } = useI18n(); const [tab, setTab] = useState(0); const [status, setStatus] = useState(null); const [emailSettings, setEmailSettings] = useState(null); const [benchmarkStatus, setBenchmarkStatus] = useState(null); const [smtpPassword, setSmtpPassword] = useState(""); const [clearPassword, setClearPassword] = useState(false); const [loading, setLoading] = useState(false); const [runningProbe, setRunningProbe] = useState(false); const [savingSettings, setSavingSettings] = useState(false); const [error, setError] = useState(null); const [testEmailTo, setTestEmailTo] = useState(""); const [testEmailSubject, setTestEmailSubject] = useState("Jobbjakt SMTP test"); const [testEmailMessage, setTestEmailMessage] = useState("This is a test email from the Jobbjakt system panel."); const [sendingTestEmail, setSendingTestEmail] = useState(false); const load = async () => { setLoading(true); setError(null); try { const [statusRes, emailRes, benchmarkRes] = await Promise.all([ api.get("/admin/system"), api.get("/admin/system/email-settings"), api.get("/admin/system/cv-benchmark").catch(() => ({ data: null } as any)), ]); setStatus(statusRes.data); setEmailSettings(emailRes.data); setBenchmarkStatus(benchmarkRes.data ?? null); setSmtpPassword(""); setClearPassword(false); } catch (e: any) { setError(getApiErrorMessage(e, "Failed to load system status.")); setStatus(null); setEmailSettings(null); setBenchmarkStatus(null); } finally { setLoading(false); } }; useEffect(() => { void load(); }, []); const dbTone = useMemo(() => { if (!status) return "default" as const; if (!status.database.looksConfigured || !status.database.canConnect) return "error" as const; if (status.database.warning) return "warning" as const; return "success" as const; }, [status]); const aiTone = useMemo(() => { if (!status) return "default" as const; if (!status.ai.healthy) return "error" as const; if (status.ai.probeFailures > 0 || status.ai.failures > 0 || (status.ai.ocrFailures ?? 0) > 0) return "warning" as const; return "success" as const; }, [status]); const benchmarkIndex = useMemo(() => parseBenchmarkIndex(benchmarkStatus?.indexJson), [benchmarkStatus?.indexJson]); const weakestEntries = useMemo(() => { if (!benchmarkIndex) return [] as CvBenchmarkEntry[]; return [...benchmarkIndex.Entries] .sort((a, b) => (a.CoverageScore + a.ConfidenceScore + a.ConsistencyScore) - (b.CoverageScore + b.ConfidenceScore + b.ConsistencyScore)) .slice(0, 6); }, [benchmarkIndex]); const benchmarkFindings = useMemo(() => { if (!benchmarkIndex) return [] as Array<{ file: string; issue: string }>; return benchmarkIndex.Entries.flatMap((entry) => { const findings: Array<{ file: string; issue: string }> = []; if (entry.ContactLocation && /(culture|education|arial|hobbies|cooperate|ag, ni|bold)/i.test(entry.ContactLocation)) { findings.push({ file: entry.FileName, issue: `Suspicious contact location: ${entry.ContactLocation}` }); } if (entry.FirstEducation && entry.FirstEducation.length > 120) { findings.push({ file: entry.FileName, issue: "Education qualification looks over-captured." }); } if ((entry.FirstJob ?? "").length > 120) { findings.push({ file: entry.FileName, issue: "Work title looks over-captured." }); } if ((entry.QualificationLevels ?? []).includes("Other")) { findings.push({ file: entry.FileName, issue: "Qualification level fell back to Other." }); } return findings; }).slice(0, 10); }, [benchmarkIndex]); const sendTestEmail = async () => { setSendingTestEmail(true); try { await api.post("/users/send-test-email", { toEmail: testEmailTo.trim() || null, subject: testEmailSubject.trim() || null, message: testEmailMessage.trim() || null, }); setError(null); } catch (e: any) { setError(getApiErrorMessage(e, "Failed to send test email.")); } finally { setSendingTestEmail(false); } }; const saveEmailSettings = async () => { if (!emailSettings) return; setSavingSettings(true); try { const res = await api.put("/admin/system/email-settings", { enabled: emailSettings.enabled, host: emailSettings.host, port: Number(emailSettings.port) || 587, user: emailSettings.user, password: smtpPassword.trim() || null, clearPassword, from: emailSettings.from, fromName: emailSettings.fromName, enableSsl: emailSettings.enableSsl, timeoutMs: Number(emailSettings.timeoutMs) || 15000, }); setEmailSettings(res.data); setSmtpPassword(""); setClearPassword(false); await load(); } catch (e: any) { setError(getApiErrorMessage(e, "Failed to save email settings.")); } finally { setSavingSettings(false); } }; return ( {t("adminSystemTitle")} {t("adminSystemSubtitle")} setTab(value)}> {error ? {error} : null} {status?.database.warning && tab === 0 ? {status.database.warning} : null} {status?.ai.lastError && tab === 0 ? {status.ai.lastError} : null} {tab === 0 ? ( <> {t("adminSystemDatabaseStorage")} {t("adminSystemRuntimeAuth")} {t("adminSystemEmailConfig")} {t("adminSystemSummarizerRuntime")} {t("adminSystemSummarizerTelemetry")} {t("adminSystemRequests")}{status?.ai.requests ?? 0} {t("adminSystemCacheHits")}{status?.ai.cacheHits ?? 0} {t("adminSystemCacheMisses")}{status?.ai.cacheMisses ?? 0} {t("adminSystemFailures")}{status?.ai.failures ?? 0} {t("adminSystemProbeFailures")}{status?.ai.probeFailures ?? 0} {t("adminSystemAvgLatency")}{status?.ai.averageLatencyMs != null ? `${status.ai.averageLatencyMs} ms` : "-"} {t("adminSystemOcrRequests")}{status?.ai.ocrRequests ?? 0} {t("adminSystemOcrAvgLatency")}{status?.ai.averageOcrLatencyMs != null ? `${status.ai.averageOcrLatencyMs} ms` : "-"} {(status?.ai.ollamaInstalledModels ?? []).slice(0, 4).map((model) => ( ))} {(status?.ai.ollamaLoadedModels ?? []).slice(0, 3).map((model) => ( ))} CV benchmark review {benchmarkIndex ? ( <> 0 ? "warning" : "success"} /> Top parser findings {benchmarkFindings.length > 0 ? benchmarkFindings.map((finding) => ( {finding.file} {finding.issue} )) : No standout benchmark anomalies in the current run.} Weakest files in current run {weakestEntries.map((entry) => ( {entry.FileName} {entry.DiffSummary || "-"} ))} Latest markdown summary {benchmarkStatus?.reportMarkdown || "-"} ) : ( {benchmarkStatus?.reportMarkdown || "Run scripts/run-cv-benchmark.sh to generate the latest corpus report and fixture candidates."} )} ) : ( {t("adminSystemEmailSettingsTitle")} {t("adminSystemEmailSettingsBody")} setEmailSettings((prev) => prev ? { ...prev, enabled: e.target.checked } : prev)} />} label={t("adminSystemEnabled")} sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} /> setEmailSettings((prev) => prev ? { ...prev, host: e.target.value } : prev)} fullWidth /> setEmailSettings((prev) => prev ? { ...prev, port: Number(e.target.value) } : prev)} fullWidth /> setEmailSettings((prev) => prev ? { ...prev, user: e.target.value } : prev)} fullWidth /> { setSmtpPassword(e.target.value); if (e.target.value.trim()) setClearPassword(false); }} helperText={emailSettings?.hasPassword ? t("adminSystemPasswordStored") : t("adminSystemPasswordMissing")} fullWidth /> setEmailSettings((prev) => prev ? { ...prev, from: e.target.value } : prev)} fullWidth /> setEmailSettings((prev) => prev ? { ...prev, fromName: e.target.value } : prev)} fullWidth /> setEmailSettings((prev) => prev ? { ...prev, timeoutMs: Number(e.target.value) } : prev)} fullWidth /> setEmailSettings((prev) => prev ? { ...prev, enableSsl: e.target.checked } : prev)} />} label={t("adminSystemSsl")} /> { setClearPassword(e.target.checked); if (e.target.checked) setSmtpPassword(""); }} />} label={t("adminSystemClearStoredPassword")} sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} /> {t("adminSystemSmtpTest")} {t("adminSystemSmtpTestBody")} setTestEmailTo(e.target.value)} placeholder={t("adminSystemRecipientPlaceholder")} /> setTestEmailSubject(e.target.value)} /> setTestEmailMessage(e.target.value)} sx={{ gridColumn: { xs: "1 / -1", md: "1 / -1" } }} /> )} ); }