159 lines
4.9 KiB
TypeScript
159 lines
4.9 KiB
TypeScript
import { useQuery } from '@tanstack/react-query'
|
|
|
|
import { api } from '../../shared/api/endpoints'
|
|
|
|
function boolView(value: boolean | undefined): string {
|
|
if (value === undefined) {
|
|
return 'n/a'
|
|
}
|
|
return value ? 'ok' : 'missing'
|
|
}
|
|
|
|
function queryErrorMessage(error: unknown): string {
|
|
if (error instanceof Error) {
|
|
return error.message
|
|
}
|
|
return 'unknown error'
|
|
}
|
|
|
|
export function OverviewPage() {
|
|
const healthQuery = useQuery({
|
|
queryKey: ['healthz'],
|
|
queryFn: api.healthz,
|
|
refetchInterval: 5000,
|
|
})
|
|
|
|
const statusQuery = useQuery({
|
|
queryKey: ['status'],
|
|
queryFn: api.status,
|
|
refetchInterval: 5000,
|
|
})
|
|
|
|
const vpnQuery = useQuery({
|
|
queryKey: ['vpn-status'],
|
|
queryFn: api.vpnStatus,
|
|
refetchInterval: 5000,
|
|
})
|
|
|
|
const loginQuery = useQuery({
|
|
queryKey: ['vpn-login-state'],
|
|
queryFn: api.vpnLoginState,
|
|
refetchInterval: 5000,
|
|
})
|
|
|
|
return (
|
|
<section>
|
|
<h1 className="page-title">Overview</h1>
|
|
<p className="page-subtitle">
|
|
Foundation mode: read-only checks and realtime connectivity indicators.
|
|
</p>
|
|
|
|
<div className="panel-grid">
|
|
<article className="panel">
|
|
<h2>Core Health</h2>
|
|
{healthQuery.isLoading ? <p>Loading...</p> : null}
|
|
{healthQuery.error ? (
|
|
<p className="text-bad">Error: {queryErrorMessage(healthQuery.error)}</p>
|
|
) : null}
|
|
{healthQuery.data ? (
|
|
<div className="kv-list">
|
|
<div className="kv-row">
|
|
<span>Status</span>
|
|
<span>{healthQuery.data.status}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>Time</span>
|
|
<span>{healthQuery.data.time}</span>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</article>
|
|
|
|
<article className="panel">
|
|
<h2>Routes Snapshot</h2>
|
|
{statusQuery.isLoading ? <p>Loading...</p> : null}
|
|
{statusQuery.error ? (
|
|
<p className="text-bad">Error: {queryErrorMessage(statusQuery.error)}</p>
|
|
) : null}
|
|
{statusQuery.data ? (
|
|
<div className="kv-list">
|
|
<div className="kv-row">
|
|
<span>iface</span>
|
|
<span>{statusQuery.data.iface || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>table</span>
|
|
<span>{statusQuery.data.table || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>mark</span>
|
|
<span>{statusQuery.data.mark || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>ip_count</span>
|
|
<span>{statusQuery.data.ip_count}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>domain_count</span>
|
|
<span>{statusQuery.data.domain_count}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>policy_route</span>
|
|
<span>{boolView(statusQuery.data.policy_route_ok)}</span>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</article>
|
|
|
|
<article className="panel">
|
|
<h2>VPN Snapshot</h2>
|
|
{vpnQuery.isLoading ? <p>Loading...</p> : null}
|
|
{vpnQuery.error ? (
|
|
<p className="text-bad">Error: {queryErrorMessage(vpnQuery.error)}</p>
|
|
) : null}
|
|
{vpnQuery.data ? (
|
|
<div className="kv-list">
|
|
<div className="kv-row">
|
|
<span>desired_location</span>
|
|
<span>{vpnQuery.data.desired_location || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>status_word</span>
|
|
<span>{vpnQuery.data.status_word || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>unit_state</span>
|
|
<span>{vpnQuery.data.unit_state || '—'}</span>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</article>
|
|
|
|
<article className="panel">
|
|
<h2>Login Snapshot</h2>
|
|
{loginQuery.isLoading ? <p>Loading...</p> : null}
|
|
{loginQuery.error ? (
|
|
<p className="text-bad">Error: {queryErrorMessage(loginQuery.error)}</p>
|
|
) : null}
|
|
{loginQuery.data ? (
|
|
<div className="kv-list">
|
|
<div className="kv-row">
|
|
<span>state</span>
|
|
<span>{loginQuery.data.state || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>email</span>
|
|
<span>{loginQuery.data.email || '—'}</span>
|
|
</div>
|
|
<div className="kv-row">
|
|
<span>message</span>
|
|
<span>{loginQuery.data.msg || '—'}</span>
|
|
</div>
|
|
</div>
|
|
) : null}
|
|
</article>
|
|
</div>
|
|
</section>
|
|
)
|
|
}
|