From 320bf6d1c7503e3bad0478866c3293cbe1e14bd2 Mon Sep 17 00:00:00 2001 From: cesnimda Date: Tue, 10 Nov 2026 12:37:34 +0100 Subject: [PATCH] feat(admin): show when system status last refreshed --- job-tracker-ui/src/admin-system-page.test.tsx | 10 ++++++++++ job-tracker-ui/src/i18n/translations.ts | 2 ++ job-tracker-ui/src/views/AdminSystemPage.tsx | 8 +++++--- 3 files changed, 17 insertions(+), 3 deletions(-) diff --git a/job-tracker-ui/src/admin-system-page.test.tsx b/job-tracker-ui/src/admin-system-page.test.tsx index 1be9f864..05ff8066 100644 --- a/job-tracker-ui/src/admin-system-page.test.tsx +++ b/job-tracker-ui/src/admin-system-page.test.tsx @@ -371,6 +371,7 @@ describe('AdminSystemPage', () => { mockedApi.get.mockRejectedValue({ response: { status: 403 } }); fireEvent.click(screen.getByRole('button', { name: 'Refresh' })); await waitFor(() => expect(screen.queryByText('Production')).not.toBeInTheDocument()); + expect(screen.queryByText('Last successful status refresh:')).not.toBeInTheDocument(); expect(screen.queryByRole('button', { name: 'Retry' })).not.toBeInTheDocument(); expect(screen.getByRole('button', { name: 'Run probe now' })).toBeDisabled(); fireEvent.click(screen.getByRole('tab', { name: 'Settings' })); @@ -381,11 +382,20 @@ describe('AdminSystemPage', () => { it('keeps the last diagnostic values under a qualified refresh error', async () => { render(); await screen.findByText('Production'); + const readTime = screen.getByText('Last successful status refresh:').parentElement?.querySelector('time'); + const original = readTime?.dateTime; + expect(original).toMatch(/^\d{4}-\d{2}-\d{2}T.*Z$/); const get = mockedApi.get.getMockImplementation()!; mockedApi.get.mockImplementation((url: string, config?: any) => url === '/admin/system' ? Promise.reject({ response: { status: 503, data: 'Temporary failure' } }) : get(url, config)); fireEvent.click(screen.getByRole('button', { name: 'Refresh' })); expect(await screen.findByText('System status could not be refreshed. Previously loaded values remain.')).toBeVisible(); expect(screen.getByText('Production')).toBeVisible(); + expect(readTime).toBeInTheDocument(); + expect(readTime?.dateTime).toBe(original); + mockedApi.get.mockImplementation(get); + fireEvent.click(screen.getByRole('button', { name: 'Retry' })); + await waitFor(() => expect(readTime?.dateTime).not.toBe(original)); + expect(screen.queryByRole('button', { name: 'Retry' })).not.toBeInTheDocument(); expect(mockedApi.post).not.toHaveBeenCalled(); }); diff --git a/job-tracker-ui/src/i18n/translations.ts b/job-tracker-ui/src/i18n/translations.ts index 2a3f8df8..493c9a28 100644 --- a/job-tracker-ui/src/i18n/translations.ts +++ b/job-tracker-ui/src/i18n/translations.ts @@ -2285,6 +2285,7 @@ export const translations = { adminSystemTestEmailSent: "Test email sent.", adminSystemProbeCompleted: "AI probe completed.", adminSystemRefreshFailed: "System status could not be refreshed. Previously loaded values remain.", + adminSystemStatusReceivedAt: "Last successful status refresh", adminSystemTestEmailFailed: "Failed to send test email.", adminSystemDefaultTestMessage: "This is a test email from the Jobbjakt system panel.", adminSystemEmailSaveFailed: "Failed to save email settings.", @@ -5510,6 +5511,7 @@ export const translations = { adminSystemTestEmailSent: "Test-e-posten er sendt.", adminSystemProbeCompleted: "KI-kontrollen er fullført.", adminSystemRefreshFailed: "Kunne ikke oppdatere systemstatus. Tidligere innlastede verdier beholdes.", + adminSystemStatusReceivedAt: "Siste vellykkede statusoppdatering", adminSystemTestEmailFailed: "Kunne ikke sende test-e-post.", adminSystemDefaultTestMessage: "Dette er en test-e-post fra systempanelet i Jobbjakt.", adminSystemEmailSaveFailed: "Kunne ikke lagre e-postinnstillingene.", diff --git a/job-tracker-ui/src/views/AdminSystemPage.tsx b/job-tracker-ui/src/views/AdminSystemPage.tsx index 24275e6b..05fdd796 100644 --- a/job-tracker-ui/src/views/AdminSystemPage.tsx +++ b/job-tracker-ui/src/views/AdminSystemPage.tsx @@ -225,7 +225,8 @@ const actionErrorKeys: Record = { save: "adminSystem export default function AdminSystemPage() { const { language, t } = useI18n(); const [tab, setTab] = useState(0); - const [status, setStatus] = useState(null); + const [statusSnapshot, setStatusSnapshot] = useState<{ data: SystemStatus; receivedAt: string } | null>(null); + const status = statusSnapshot?.data; const [emailSettings, setEmailSettings] = useState(null); const [benchmarkStatus, setBenchmarkStatus] = useState(null); const [maintenanceJobs, setMaintenanceJobs] = useState([]); @@ -268,7 +269,7 @@ export default function AdminSystemPage() { try { const response = await api.get(readPaths[part], { signal: abort.signal }); if (!active() || abort.signal.aborted || denied.current) return; - if (part === "status") setStatus(response.data); + if (part === "status") setStatusSnapshot({ data: response.data, receivedAt: new Date().toISOString() }); if (part === "email" && revision === draftRevision.current && revision === savedRevision.current) setEmailSettings(response.data); if (part === "benchmark") setBenchmarkStatus(response.data ?? null); if (part === "maintenance") setMaintenanceJobs(response.data ?? []); @@ -278,7 +279,7 @@ export default function AdminSystemPage() { const responseStatus = (cause as { response?: { status?: number } })?.response?.status; if (responseStatus === 401 || responseStatus === 403) { denied.current = true; setAccessDenied(true); - setStatus(null); setEmailSettings(null); setBenchmarkStatus(null); setMaintenanceJobs([]); + setStatusSnapshot(null); setEmailSettings(null); setBenchmarkStatus(null); setMaintenanceJobs([]); setSmtpPassword(""); setClearPassword(false); } setReadErrors(errors => ({ ...errors, [part]: cause })); @@ -450,6 +451,7 @@ export default function AdminSystemPage() {