domain_health_status

This commit is contained in:
2026-04-28 20:38:50 -05:00
parent f097f96d06
commit 62219a372a
8 changed files with 743 additions and 4 deletions

View File

@@ -1,7 +1,7 @@
import React, { useCallback, useEffect, useState } from 'react';
import {
FiRefreshCw, FiList, FiLogOut, FiSettings, FiKey, FiTrash2, FiPlus, FiInbox,
FiUsers, FiUser, FiHardDrive, FiDollarSign,
FiUsers, FiUser, FiHardDrive, FiDollarSign, FiActivity,
} from 'react-icons/fi';
import Login from './components/Login';
@@ -17,8 +17,10 @@ import AdminUsersModal from './components/AdminUsersModal';
import ChangeMyPasswordModal from './components/ChangeMyPasswordModal';
import DomainQuotaModal from './components/DomainQuotaModal';
import BillingModal from './components/BillingModal';
import HealthModal from './components/HealthModal';
import HealthBanner from './components/HealthBanner';
import { authAPI, domainsAPI, mailboxesAPI } from './services/api';
import { authAPI, domainsAPI, mailboxesAPI, healthAPI } from './services/api';
function App() {
const [user, setUser] = useState(null);
@@ -40,6 +42,10 @@ function App() {
const [showChangePw, setShowChangePw] = useState(false);
const [showDomainQuota, setShowDomainQuota] = useState(false);
const [showBilling, setShowBilling] = useState(false);
const [showHealth, setShowHealth] = useState(false);
// Persisted health status for the currently selected domain (drives the banner).
const [healthStatus, setHealthStatus] = useState(null);
const showToast = useCallback((message, type = 'success') => {
setToast({ message, type });
@@ -60,6 +66,20 @@ function App() {
setMailboxes(list);
}, []);
// Load (or re-load) the persisted health status for a domain.
// Cheap call — just reads from PostgreSQL, no checks are run.
const loadHealthStatus = useCallback(async (domain) => {
if (!domain) { setHealthStatus(null); return; }
try {
const status = await healthAPI.getStatus(domain);
setHealthStatus(status); // null if never checked, that's fine
} catch (err) {
// Silent: don't block the UI just because health status load failed.
console.warn('Failed to load health status:', err);
setHealthStatus(null);
}
}, []);
useEffect(() => {
(async () => {
try {
@@ -84,6 +104,7 @@ function App() {
if (first) {
setBusyMessage('Refreshing quotas...');
await loadMailboxes(first, true);
await loadHealthStatus(first);
}
} catch (err) {
showToast(`Failed to load: ${err.message}`, 'error');
@@ -100,6 +121,7 @@ function App() {
setBusyMessage('Loading mailboxes...');
try {
await loadMailboxes(domain, true);
await loadHealthStatus(domain);
} catch (err) {
showToast(`Failed to load mailboxes: ${err.message}`, 'error');
} finally {
@@ -115,7 +137,10 @@ function App() {
if (selectedDomain && !list.find((d) => d.domain === selectedDomain)) {
const first = list[0]?.domain || null;
setSelectedDomain(first);
if (first) await loadMailboxes(first, false);
if (first) {
await loadMailboxes(first, false);
await loadHealthStatus(first);
}
} else if (selectedDomain) {
await loadMailboxes(selectedDomain, false);
}
@@ -133,6 +158,7 @@ function App() {
setDomains([]);
setMailboxes([]);
setSelectedDomain(null);
setHealthStatus(null);
};
const handleDelete = async () => {
@@ -167,6 +193,12 @@ function App() {
}
};
// Called by HealthModal after a fresh check completes — re-load the
// persisted summary so the banner reflects the new state.
const handleHealthChecked = async () => {
if (selectedDomain) await loadHealthStatus(selectedDomain);
};
if (!bootChecked) {
return <div className="min-h-screen flex items-center justify-center text-gray-400">Loading...</div>;
}
@@ -269,6 +301,9 @@ function App() {
)}
<section className="card relative">
{/* Health banner (shows only when last check found problems) */}
<HealthBanner status={healthStatus} onOpen={() => setShowHealth(true)} />
<div className="flex items-start justify-between gap-4 mb-5 flex-wrap">
<div>
<h2 className="text-base font-semibold text-gray-900">
@@ -278,7 +313,16 @@ function App() {
Create/delete mailboxes, reset passwords, edit rules. Quotas are refreshed when you open a domain.
</p>
</div>
<div className="flex flex-row flex-nowrap gap-2">
<div className="flex flex-row flex-wrap gap-2">
<button
onClick={() => setShowHealth(true)}
disabled={!selectedDomain}
className="btn-secondary"
title="Run DNS, DMS and TLS cert checks for this domain"
>
<FiActivity className="w-4 h-4 mr-2" />
Check health
</button>
{isSuperAdmin && (
<button
onClick={() => setShowDomainQuota(true)}
@@ -441,6 +485,13 @@ function App() {
onToast={showToast}
/>
)}
<HealthModal
open={showHealth}
domain={selectedDomain}
onClose={() => setShowHealth(false)}
onCheckedReport={handleHealthChecked}
onToast={showToast}
/>
<ChangeMyPasswordModal
open={showChangePw}
onClose={() => setShowChangePw(false)}