411 lines
18 KiB
HTML
411 lines
18 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en" data-theme="dark">
|
||
<head>
|
||
<meta charset="UTF-8" />
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||
<title>PearData — P2P Fleet Monitoring</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||
<link
|
||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap"
|
||
rel="stylesheet"
|
||
/>
|
||
<script>
|
||
try {
|
||
var raw = localStorage.getItem('peardata.settings.v1') || localStorage.getItem('peardata-ui-boot') || '{}'
|
||
var s = JSON.parse(raw)
|
||
var theme = s.theme || (s.settings && s.settings.theme)
|
||
if (theme) document.documentElement.dataset.theme = theme
|
||
} catch (e) {}
|
||
</script>
|
||
<link rel="stylesheet" href="./ui/styles.css" />
|
||
</head>
|
||
<body data-accent="teal" data-density="comfortable" data-reduce-motion="0">
|
||
<div id="titlebar" role="banner">
|
||
<div class="titlebar-left">
|
||
<pear-ctrl></pear-ctrl>
|
||
<div class="app-brand" aria-hidden="true">
|
||
<div class="app-brand-mark">◈</div>
|
||
<div class="app-brand-text">
|
||
PearData <span>P2P observability</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div class="titlebar-right">
|
||
<span id="active-peer-chip" class="chip chip-muted">no agent</span>
|
||
<span id="role-badge" class="badge">—</span>
|
||
<div id="status-chip" class="chip offline">offline</div>
|
||
</div>
|
||
</div>
|
||
|
||
<aside id="sidebar">
|
||
<nav id="main-nav" aria-label="Main">
|
||
<div class="nav-group-label">Monitor</div>
|
||
<button type="button" class="nav-link active" data-view="overview">
|
||
<span class="nav-ico">◉</span><span class="nav-label">Overview</span>
|
||
</button>
|
||
<button type="button" class="nav-link" data-view="charts">
|
||
<span class="nav-ico">▦</span><span class="nav-label">Charts</span>
|
||
</button>
|
||
<button type="button" class="nav-link" data-view="alerts">
|
||
<span class="nav-ico">⚑</span><span class="nav-label">Alerts</span>
|
||
</button>
|
||
<div class="nav-group-label">Fleet</div>
|
||
<button type="button" class="nav-link" data-view="fleet">
|
||
<span class="nav-ico">◎</span><span class="nav-label">Agents</span>
|
||
</button>
|
||
<button type="button" class="nav-link" data-view="connect">
|
||
<span class="nav-ico">﹢</span><span class="nav-label">Connect</span>
|
||
</button>
|
||
<div class="nav-group-label">App</div>
|
||
<button type="button" class="nav-link" data-view="settings">
|
||
<span class="nav-ico">⚙</span><span class="nav-label">Settings</span>
|
||
</button>
|
||
</nav>
|
||
<div class="sidebar-footer">
|
||
<button type="button" id="collapse-sidebar-btn" class="ghost compact" title="Collapse sidebar">
|
||
‹
|
||
</button>
|
||
<div id="conn-meta" class="meta muted">Ready</div>
|
||
</div>
|
||
</aside>
|
||
|
||
<div id="content">
|
||
<div id="offline-banner" class="offline-banner hidden" role="status">
|
||
Agent offline — showing last-known samples. Reconnecting…
|
||
</div>
|
||
<div id="restoring-banner" class="offline-banner restoring-banner hidden" role="status">
|
||
Restoring saved agents…
|
||
</div>
|
||
|
||
<!-- Overview -->
|
||
<section id="overview-view" class="view">
|
||
<header class="page-header">
|
||
<div>
|
||
<p class="dash-kicker">Live</p>
|
||
<h1 class="dash-title">Overview</h1>
|
||
<p class="page-subtitle">Real-time host metrics from the active agent</p>
|
||
</div>
|
||
<div class="page-actions">
|
||
<button type="button" id="btn-refresh-meta" class="ghost">Refresh</button>
|
||
</div>
|
||
</header>
|
||
|
||
<section class="dash-kpis">
|
||
<div class="dash-kpi" data-stat="cpu">
|
||
<span class="stat-label">CPU</span>
|
||
<strong id="stat-cpu">—</strong>
|
||
</div>
|
||
<div class="dash-kpi" data-stat="ram">
|
||
<span class="stat-label">RAM used</span>
|
||
<strong id="stat-ram">—</strong>
|
||
</div>
|
||
<div class="dash-kpi" data-stat="load">
|
||
<span class="stat-label">Load 1m</span>
|
||
<strong id="stat-load">—</strong>
|
||
</div>
|
||
<div class="dash-kpi" data-stat="net">
|
||
<span class="stat-label">Net RX</span>
|
||
<strong id="stat-net">—</strong>
|
||
</div>
|
||
<div class="dash-kpi" data-stat="health" id="kpi-health">
|
||
<span class="stat-label">Health</span>
|
||
<strong id="stat-health">—</strong>
|
||
</div>
|
||
</section>
|
||
|
||
<section id="fleet-strip" class="fleet-strip dash-card hidden" aria-live="polite">
|
||
<header>
|
||
<h3>Fleet</h3>
|
||
<span id="fleet-summary" class="muted"></span>
|
||
</header>
|
||
<ul id="fleet-children" class="fleet-children"></ul>
|
||
</section>
|
||
|
||
<section class="charts-grid">
|
||
<article class="dash-card chart-panel" data-chart="system.cpu" id="panel-cpu">
|
||
<header>
|
||
<h3>CPU</h3>
|
||
<span class="muted" id="chart-cpu-label">system.cpu</span>
|
||
</header>
|
||
<canvas id="chart-cpu" height="160"></canvas>
|
||
<div class="chart-legend" id="legend-cpu"></div>
|
||
</article>
|
||
<article class="dash-card chart-panel" data-chart="mem.available" id="panel-ram">
|
||
<header>
|
||
<h3>Memory</h3>
|
||
<span class="muted">system.ram</span>
|
||
</header>
|
||
<canvas id="chart-ram" height="160"></canvas>
|
||
</article>
|
||
<article class="dash-card chart-panel" data-chart="system.net" id="panel-net">
|
||
<header>
|
||
<h3>Network</h3>
|
||
<span class="muted">system.net</span>
|
||
</header>
|
||
<canvas id="chart-net" height="160"></canvas>
|
||
</article>
|
||
<article class="dash-card chart-panel" data-chart="system.io" id="panel-io">
|
||
<header>
|
||
<h3>Disk I/O</h3>
|
||
<span class="muted">system.io</span>
|
||
</header>
|
||
<canvas id="chart-io" height="160"></canvas>
|
||
</article>
|
||
<article class="dash-card chart-panel" data-chart="system.load" id="panel-load">
|
||
<header>
|
||
<h3>Load</h3>
|
||
<span class="muted">system.load</span>
|
||
</header>
|
||
<canvas id="chart-load" height="160"></canvas>
|
||
</article>
|
||
<article class="dash-card chart-panel" data-chart="explore" id="panel-explore">
|
||
<header>
|
||
<h3>Spotlight</h3>
|
||
<label class="chart-picker">
|
||
<select id="explore-chart" aria-label="Spotlight chart">
|
||
<option value="system.io">system.io</option>
|
||
</select>
|
||
</label>
|
||
</header>
|
||
<canvas id="chart-explore" height="160"></canvas>
|
||
</article>
|
||
</section>
|
||
</section>
|
||
|
||
<!-- Master metrics wall -->
|
||
<section id="charts-view" class="view hidden">
|
||
<header class="page-header metrics-header">
|
||
<div>
|
||
<p class="dash-kicker">Metrics</p>
|
||
<h1 class="dash-title">Charts</h1>
|
||
<p class="page-subtitle">Every collected context, sectioned and live · <kbd>Space</kbd> play · <kbd>/</kbd> search · <kbd>1–5</kbd> window</p>
|
||
</div>
|
||
<div class="metrics-timebar" id="metrics-timebar">
|
||
<button type="button" id="metrics-play" class="ghost" aria-pressed="true">Pause</button>
|
||
<button type="button" id="metrics-force-play" class="ghost" aria-pressed="false" title="Keep live updating (wallboard)">Force</button>
|
||
<button type="button" id="metrics-board" class="ghost" aria-pressed="false" title="Show only pinned charts">Board</button>
|
||
<button type="button" id="metrics-reset" class="ghost" title="Reset to live 5m">Reset</button>
|
||
<div id="metrics-presets" class="metrics-presets" role="group" aria-label="Time window">
|
||
<button type="button" class="ghost" data-preset="1m">1m</button>
|
||
<button type="button" class="ghost active" data-preset="5m">5m</button>
|
||
<button type="button" class="ghost" data-preset="15m">15m</button>
|
||
<button type="button" class="ghost" data-preset="1h">1h</button>
|
||
<button type="button" class="ghost" data-preset="6h">6h</button>
|
||
</div>
|
||
<label class="metrics-group-label muted">
|
||
Group
|
||
<select id="metrics-group" aria-label="Downsample aggregation">
|
||
<option value="average">average</option>
|
||
<option value="min">min</option>
|
||
<option value="max">max</option>
|
||
<option value="sum">sum</option>
|
||
</select>
|
||
</label>
|
||
<button type="button" id="metrics-dim-sort" class="ghost">Sort: name</button>
|
||
<span id="metrics-meta" class="muted metrics-meta"></span>
|
||
<span id="metrics-hover" class="muted metrics-hover"></span>
|
||
</div>
|
||
</header>
|
||
<div id="metrics-related" class="dash-card related-panel hidden"></div>
|
||
<div class="metrics-shell">
|
||
<aside class="metrics-toc dash-card">
|
||
<input id="chart-search" type="search" placeholder="Filter by id, title, family…" autocomplete="off" />
|
||
<nav id="metrics-toc" class="metrics-toc-nav" aria-label="Chart sections"></nav>
|
||
<!-- legacy list kept hidden for compatibility -->
|
||
<ul id="chart-catalog-list" class="catalog-list hidden" hidden></ul>
|
||
</aside>
|
||
<div id="metrics-wall" class="metrics-wall" tabindex="0"></div>
|
||
</div>
|
||
<!-- Detail strip for deep-dive (dblclick a card) -->
|
||
<article id="chart-detail-panel" class="dash-card chart-detail metrics-detail hidden">
|
||
<header>
|
||
<h3 id="chart-detail-title">Select a chart</h3>
|
||
<span id="chart-detail-meta" class="muted"></span>
|
||
</header>
|
||
<canvas id="chart-detail" height="220"></canvas>
|
||
<pre id="chart-detail-dims" class="dim-list muted"></pre>
|
||
</article>
|
||
</section>
|
||
|
||
<!-- Alerts -->
|
||
<section id="alerts-view" class="view hidden">
|
||
<header class="page-header">
|
||
<div>
|
||
<p class="dash-kicker">Signals</p>
|
||
<h1 class="dash-title">Alerts</h1>
|
||
<p class="page-subtitle">Anomalies and threshold events from the agent</p>
|
||
</div>
|
||
<label class="check-row">
|
||
<input type="checkbox" id="notify-desktop" checked />
|
||
Desktop notifications
|
||
</label>
|
||
</header>
|
||
<div class="dash-card">
|
||
<ul id="anomaly-list" class="event-list"></ul>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Fleet / agents -->
|
||
<section id="fleet-view" class="view hidden">
|
||
<header class="page-header">
|
||
<div>
|
||
<p class="dash-kicker">Peers</p>
|
||
<h1 class="dash-title">Agents</h1>
|
||
<p class="page-subtitle">Saved bookmarks and live connections</p>
|
||
</div>
|
||
<label class="check-row">
|
||
<input type="checkbox" id="compare-toggle" />
|
||
Compare CPU overlay
|
||
</label>
|
||
</header>
|
||
<div class="fleet-layout">
|
||
<div class="dash-card">
|
||
<h3 class="rail-sub">Saved</h3>
|
||
<ul id="bookmark-list" class="peer-list bookmark-list"></ul>
|
||
</div>
|
||
<div class="dash-card">
|
||
<h3 class="rail-sub">Connected</h3>
|
||
<ul id="peer-list" class="peer-list"></ul>
|
||
<div class="admin-block">
|
||
<button id="btn-invite" class="ghost" disabled>Mint invite</button>
|
||
<pre id="invite-out" class="invite-out hidden"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Connect -->
|
||
<section id="connect-view" class="view hidden">
|
||
<header class="page-header">
|
||
<div>
|
||
<p class="dash-kicker">Dial</p>
|
||
<h1 class="dash-title">Connect</h1>
|
||
<p class="page-subtitle">Public key or <code>pd1.</code> invite</p>
|
||
</div>
|
||
</header>
|
||
<div class="dash-card connect-card">
|
||
<label>
|
||
Agent / invite
|
||
<textarea id="connect-input" rows="3" placeholder="64-hex key or pd1.…"></textarea>
|
||
</label>
|
||
<label>
|
||
Alias (optional)
|
||
<input id="peer-alias" type="text" maxlength="64" placeholder="homelab-1" autocomplete="off" />
|
||
</label>
|
||
<label>
|
||
Admin seed (optional)
|
||
<input id="admin-seed" type="password" autocomplete="off" placeholder="SERVER_SEED" />
|
||
</label>
|
||
<div class="row">
|
||
<button id="btn-connect" class="primary">Connect</button>
|
||
<button id="btn-disconnect" class="ghost" disabled>Disconnect</button>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- Settings -->
|
||
<section id="settings-view" class="view hidden">
|
||
<header class="page-header">
|
||
<div>
|
||
<p class="dash-kicker">Preferences</p>
|
||
<h1 class="dash-title">Settings</h1>
|
||
<p class="page-subtitle">Appearance, charts, and desktop behaviour</p>
|
||
</div>
|
||
</header>
|
||
<div class="settings-subtabs" id="settings-tabs" role="tablist">
|
||
<button type="button" class="settings-tab active" data-settings-tab="appearance">Appearance</button>
|
||
<button type="button" class="settings-tab" data-settings-tab="charts">Charts</button>
|
||
<button type="button" class="settings-tab" data-settings-tab="connections">Connections</button>
|
||
<button type="button" class="settings-tab" data-settings-tab="notifications">Notifications</button>
|
||
<button type="button" class="settings-tab" data-settings-tab="about">About</button>
|
||
</div>
|
||
|
||
<div class="settings-panel active" data-settings-panel="appearance">
|
||
<div class="dash-card settings-section">
|
||
<h3>Theme</h3>
|
||
<div class="seg">
|
||
<button type="button" class="seg-btn" data-theme-set="dark">Dark</button>
|
||
<button type="button" class="seg-btn" data-theme-set="light">Light</button>
|
||
</div>
|
||
<h3>Accent</h3>
|
||
<div class="accent-swatches" id="accent-swatches">
|
||
<button type="button" data-accent="teal" class="swatch" title="Teal"></button>
|
||
<button type="button" data-accent="emerald" class="swatch" title="Emerald"></button>
|
||
<button type="button" data-accent="sky" class="swatch" title="Sky"></button>
|
||
<button type="button" data-accent="amber" class="swatch" title="Amber"></button>
|
||
<button type="button" data-accent="rose" class="swatch" title="Rose"></button>
|
||
</div>
|
||
<h3>Density</h3>
|
||
<div class="seg">
|
||
<button type="button" class="seg-btn" data-density-set="compact">Compact</button>
|
||
<button type="button" class="seg-btn" data-density-set="comfortable">Comfortable</button>
|
||
<button type="button" class="seg-btn" data-density-set="spacious">Spacious</button>
|
||
</div>
|
||
<label class="check-row">
|
||
<input type="checkbox" id="setting-reduce-motion" />
|
||
Reduce motion
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-panel" data-settings-panel="charts">
|
||
<div class="dash-card settings-section">
|
||
<h3>History window</h3>
|
||
<label>
|
||
Points per chart
|
||
<input id="setting-chart-points" type="number" min="30" max="300" step="10" value="90" />
|
||
</label>
|
||
<label>
|
||
Default spotlight chart
|
||
<input id="setting-default-explore" type="text" value="system.io" />
|
||
</label>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-panel" data-settings-panel="connections">
|
||
<div class="dash-card settings-section">
|
||
<h3>Multi-agent</h3>
|
||
<label class="check-row">
|
||
<input type="checkbox" id="setting-auto-restore" checked />
|
||
Restore saved agents on launch
|
||
</label>
|
||
<label>
|
||
Max reconnect attempts
|
||
<input id="setting-reconnect-max" type="number" min="0" max="100" step="1" value="20" />
|
||
</label>
|
||
<p class="hint">
|
||
Saved agents live in <code>~/.config/peardata/cache/peers.json</code>.
|
||
Last active agent is restored when online. Reconnect never steals the active host.
|
||
</p>
|
||
<button type="button" id="btn-reset-peers" class="ghost">Forget all saved agents</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-panel" data-settings-panel="notifications">
|
||
<div class="dash-card settings-section">
|
||
<h3>Desktop</h3>
|
||
<label class="check-row">
|
||
<input type="checkbox" id="setting-notify" checked />
|
||
Enable desktop notifications for alerts
|
||
</label>
|
||
<p class="hint">Agent webhooks are configured on the server via environment variables.</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="settings-panel" data-settings-panel="about">
|
||
<div class="dash-card settings-section">
|
||
<h3>PearData</h3>
|
||
<p class="hint">Decentralized P2P real-time host monitoring over HyperDHT.</p>
|
||
<pre id="server-info" class="server-info muted">Not connected</pre>
|
||
<h3>Log</h3>
|
||
<pre id="log" class="log"></pre>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
</div>
|
||
|
||
<script type="module" src="./app.js"></script>
|
||
</body>
|
||
</html>
|