platform: modularize api/gui, add docs-tests-web foundation, and refresh root config
This commit is contained in:
158
selective-vpn-web/src/pages/overview/OverviewPage.tsx
Normal file
158
selective-vpn-web/src/pages/overview/OverviewPage.tsx
Normal file
@@ -0,0 +1,158 @@
|
||||
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>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user