30 lines
4.1 KiB
TypeScript
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>;
|
|
}
|