GuestOps/web/src/OperationsPage.tsx
wolf-demon 92f875621d
Some checks are pending
Build and verify web migration / verify (push) Waiting to run
milestone 16 complete
2026-09-29 20:40:18 +01:00

30 lines
4.1 KiB
TypeScript

import {useEffect,useState} from 'react';
import {api} from './api';
import {hotelTime} from './time';
type Health={
checkedAt:string;timeZone:string;preview:boolean;database:string;
worker:{state:string;lastSeenAt:string|null};
mailboxes:{total:number;connected:number;attention:number};
replies:{sampleSize:number;sampleLimit:number;pending:number;uncertain:number;rejected:number};
};
export function OperationsPage({owner,go}:{owner:boolean;go:(path:string)=>void}){
const [data,setData]=useState<Health|null>(null),[error,setError]=useState(''),[busy,setBusy]=useState(false);
async function refresh(){setBusy(true);setError('');try{setData(await api<Health>('/operations'));}catch(e){setError(e instanceof Error?e.message:'Unable to check workspace health.');}finally{setBusy(false);}}
useEffect(()=>{if(owner)void refresh();},[owner]);
if(!owner)return <div className="page"><h1>Workspace health</h1><p>Your hotel owner can review operational health.</p></div>;
const when=(value:string|null)=>value?hotelTime(value,data?.timeZone||'UTC'):'Not yet';
return <div className="page settings-page">
<div className="heading-row"><div className="page-heading"><span className="eyebrow">Keep your workspace running</span><h1>Workspace health</h1><p>Spot connection and delivery issues before they affect your team.</p></div><button className="button secondary" disabled={busy} onClick={refresh}>{busy?'Checking…':'Refresh health'}</button></div>
{error&&<div className="alert" role="alert">{error} The information below may be out of date.</div>}
{data&&<>
<p className="small muted">Checked {when(data.checkedAt)}{data.preview?' · Sample workspace':''}</p>
<section className="settings-card"><h2>Application and worker</h2><div className="mailbox-health"><div><span>Database connection</span><strong>{data.preview?'Temporary preview storage':data.database}</strong></div><div><span>Background worker</span><strong>{data.worker.state==='Reporting'?'Reporting normally':data.worker.state==='Preview'?'Unavailable in preview':data.worker.state==='Stale'?'Heartbeat overdue':'No heartbeat received'}</strong></div><div><span>Last worker heartbeat</span><strong>{when(data.worker.lastSeenAt)}</strong></div></div>{['Stale','NotSeen'].includes(data.worker.state)&&<p className="mailbox-explanation">Ask your server administrator to check the worker container and its database connection. Imports and queued replies may be delayed.</p>}<p className="small muted">A heartbeat confirms the worker process can reach storage. It does not prove that Google, payment or PMS requests are succeeding.</p></section>
<section className="settings-card"><h2>Mailbox connections</h2><div className="mailbox-health"><div><span>Total</span><strong>{data.mailboxes.total}</strong></div><div><span>Connected</span><strong>{data.mailboxes.connected}</strong></div><div><span>Need attention</span><strong>{data.mailboxes.attention}</strong></div></div><button className="button secondary" onClick={()=>go('/settings')}>Review mailbox status</button></section>
<section className="settings-card"><h2>Reply delivery</h2><div className="mailbox-health"><div><span>Queued or submitting</span><strong>{data.replies.pending}</strong></div><div><span>Need verification</span><strong>{data.replies.uncertain}</strong></div><div><span>Stopped before sending</span><strong>{data.replies.rejected}</strong></div></div><p className="small muted">Based on {data.replies.sampleSize} recent conversations, up to {data.replies.sampleLimit}. Older deliveries may exist. Verify uncertain results in Gmail before taking further action.</p><div className="form-actions"><button className="button secondary" onClick={()=>go('/inbox')}>Review the inbox</button></div></section>
<section className="settings-card"><h2>Backups and recovery</h2><p className="muted">Your server administrator runs encrypted backups and isolated restore drills. Ask them to confirm the latest off-server backup and successful restore test.</p><p className="small muted">This page does not claim a backup exists or that the server is ready for production. Provider acceptance and recovery checks are separate.</p></section>
</>}
</div>;
}