174 lines
6.2 KiB
TypeScript
174 lines
6.2 KiB
TypeScript
import { html } from "lit";
|
|
|
|
import type { GatewayHelloOk } from "../gateway";
|
|
import { formatAgo, formatDurationMs } from "../format";
|
|
import { formatNextRun } from "../presenter";
|
|
import type { UiSettings } from "../storage";
|
|
|
|
export type OverviewProps = {
|
|
connected: boolean;
|
|
hello: GatewayHelloOk | null;
|
|
settings: UiSettings;
|
|
password: string;
|
|
lastError: string | null;
|
|
presenceCount: number;
|
|
sessionsCount: number | null;
|
|
cronEnabled: boolean | null;
|
|
cronNext: number | null;
|
|
lastProvidersRefresh: number | null;
|
|
onSettingsChange: (next: UiSettings) => void;
|
|
onPasswordChange: (next: string) => void;
|
|
onSessionKeyChange: (next: string) => void;
|
|
onRefresh: () => void;
|
|
};
|
|
|
|
export function renderOverview(props: OverviewProps) {
|
|
const snapshot = props.hello?.snapshot as
|
|
| { uptimeMs?: number; policy?: { tickIntervalMs?: number } }
|
|
| undefined;
|
|
const uptime = snapshot?.uptimeMs ? formatDurationMs(snapshot.uptimeMs) : "n/a";
|
|
const tick = snapshot?.policy?.tickIntervalMs
|
|
? `${snapshot.policy.tickIntervalMs}ms`
|
|
: "n/a";
|
|
|
|
return html`
|
|
<section class="grid grid-cols-2">
|
|
<div class="card">
|
|
<div class="card-title">Gateway Access</div>
|
|
<div class="card-sub">Where the dashboard connects and how it authenticates.</div>
|
|
<div class="form-grid" style="margin-top: 16px;">
|
|
<label class="field">
|
|
<span>WebSocket URL</span>
|
|
<input
|
|
.value=${props.settings.gatewayUrl}
|
|
@input=${(e: Event) => {
|
|
const v = (e.target as HTMLInputElement).value;
|
|
props.onSettingsChange({ ...props.settings, gatewayUrl: v });
|
|
}}
|
|
placeholder="ws://100.x.y.z:18789"
|
|
/>
|
|
</label>
|
|
<label class="field">
|
|
<span>Gateway Token</span>
|
|
<input
|
|
.value=${props.settings.token}
|
|
@input=${(e: Event) => {
|
|
const v = (e.target as HTMLInputElement).value;
|
|
props.onSettingsChange({ ...props.settings, token: v });
|
|
}}
|
|
placeholder="CLAWDIS_GATEWAY_TOKEN"
|
|
/>
|
|
</label>
|
|
<label class="field">
|
|
<span>Password (not stored)</span>
|
|
<input
|
|
type="password"
|
|
.value=${props.password}
|
|
@input=${(e: Event) => {
|
|
const v = (e.target as HTMLInputElement).value;
|
|
props.onPasswordChange(v);
|
|
}}
|
|
placeholder="system or shared password"
|
|
/>
|
|
</label>
|
|
<label class="field">
|
|
<span>Default Session Key</span>
|
|
<input
|
|
.value=${props.settings.sessionKey}
|
|
@input=${(e: Event) => {
|
|
const v = (e.target as HTMLInputElement).value;
|
|
props.onSessionKeyChange(v);
|
|
}}
|
|
/>
|
|
</label>
|
|
</div>
|
|
<div class="row" style="margin-top: 14px;">
|
|
<button class="btn" @click=${() => props.onRefresh()}>Refresh</button>
|
|
<span class="muted">Reconnect to apply changes.</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="card">
|
|
<div class="card-title">Snapshot</div>
|
|
<div class="card-sub">Latest gateway handshake information.</div>
|
|
<div class="stat-grid" style="margin-top: 16px;">
|
|
<div class="stat">
|
|
<div class="stat-label">Status</div>
|
|
<div class="stat-value ${props.connected ? "ok" : "warn"}">
|
|
${props.connected ? "Connected" : "Disconnected"}
|
|
</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Uptime</div>
|
|
<div class="stat-value">${uptime}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Tick Interval</div>
|
|
<div class="stat-value">${tick}</div>
|
|
</div>
|
|
<div class="stat">
|
|
<div class="stat-label">Last Providers Refresh</div>
|
|
<div class="stat-value">
|
|
${props.lastProvidersRefresh
|
|
? formatAgo(props.lastProvidersRefresh)
|
|
: "n/a"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
${props.lastError
|
|
? html`<div class="callout danger" style="margin-top: 14px;">
|
|
${props.lastError}
|
|
</div>`
|
|
: html`<div class="callout" style="margin-top: 14px;">
|
|
Use Connections to link WhatsApp, Telegram, Discord, Signal, or iMessage.
|
|
</div>`}
|
|
</div>
|
|
</section>
|
|
|
|
<section class="grid grid-cols-3" style="margin-top: 18px;">
|
|
<div class="card stat-card">
|
|
<div class="stat-label">Instances</div>
|
|
<div class="stat-value">${props.presenceCount}</div>
|
|
<div class="muted">Presence beacons in the last 5 minutes.</div>
|
|
</div>
|
|
<div class="card stat-card">
|
|
<div class="stat-label">Sessions</div>
|
|
<div class="stat-value">${props.sessionsCount ?? "n/a"}</div>
|
|
<div class="muted">Recent session keys tracked by the gateway.</div>
|
|
</div>
|
|
<div class="card stat-card">
|
|
<div class="stat-label">Cron</div>
|
|
<div class="stat-value">
|
|
${props.cronEnabled == null
|
|
? "n/a"
|
|
: props.cronEnabled
|
|
? "Enabled"
|
|
: "Disabled"}
|
|
</div>
|
|
<div class="muted">Next wake ${formatNextRun(props.cronNext)}</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="card" style="margin-top: 18px;">
|
|
<div class="card-title">Notes</div>
|
|
<div class="card-sub">Quick reminders for remote control setups.</div>
|
|
<div class="note-grid" style="margin-top: 14px;">
|
|
<div>
|
|
<div class="note-title">Tailscale serve</div>
|
|
<div class="muted">
|
|
Prefer serve mode to keep the gateway on loopback with tailnet auth.
|
|
</div>
|
|
</div>
|
|
<div>
|
|
<div class="note-title">Session hygiene</div>
|
|
<div class="muted">Use /new or sessions.patch to reset context.</div>
|
|
</div>
|
|
<div>
|
|
<div class="note-title">Cron reminders</div>
|
|
<div class="muted">Use isolated sessions for recurring runs.</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
`;
|
|
}
|