Files
peardata/index.html
T
Raven Scott 41623bae68
CI / test (push) Successful in 1m6s
Release rolling / release (push) Successful in 7m30s
Updates
2026-07-18 21:15:49 -04:00

419 lines
19 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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=Outfit:wght@400;500;600;700;800&family=IBM+Plex+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">Fleet</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 overview-header">
<div>
<p class="dash-kicker">Live</p>
<h1 class="dash-title">Overview</h1>
<p class="page-subtitle">Active agent at a glance</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 overview-charts">
<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="110"></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="110"></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="110"></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="110"></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="110"></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="110"></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">Scroll freely · <kbd></kbd>/<kbd></kbd>+scroll zooms · <kbd>Space</kbd> · <kbd>/</kbd> · <kbd>15</kbd></p>
</div>
</header>
<div class="metrics-toolbar" id="metrics-timebar">
<div class="metrics-toolbar-left">
<span id="metrics-live" class="metrics-live is-live" aria-live="polite">
<i class="metrics-live-dot" aria-hidden="true"></i>
<span id="metrics-live-label">Live</span>
</span>
<button type="button" id="metrics-play" class="btn btn-ghost" aria-pressed="true">Pause</button>
<button type="button" id="metrics-force-play" class="btn btn-ghost" aria-pressed="false" title="Keep live (wallboard)">Force</button>
<button type="button" id="metrics-board" class="btn btn-ghost" aria-pressed="false" title="Pinned board only">Board</button>
<button type="button" id="metrics-reset" class="btn btn-ghost" title="Reset to live 5m">Reset</button>
</div>
<div id="metrics-presets" class="metrics-presets" role="group" aria-label="Time window">
<button type="button" class="metrics-tf" data-preset="1m" title="Last 1 minute">1m</button>
<button type="button" class="metrics-tf active" data-preset="5m" title="Last 5 minutes">5m</button>
<button type="button" class="metrics-tf" data-preset="15m" title="Last 15 minutes">15m</button>
<button type="button" class="metrics-tf" data-preset="1h" title="Last hour">1h</button>
<button type="button" class="metrics-tf" data-preset="6h" title="Last 6 hours">6h</button>
</div>
<div class="metrics-toolbar-right">
<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="btn btn-ghost">Sort: name</button>
<span id="metrics-meta" class="muted metrics-meta"></span>
<span id="metrics-hover" class="muted metrics-hover"></span>
</div>
</div>
<p id="metrics-retention-hint" class="metrics-retention-hint muted hidden" role="status"></p>
<div id="metrics-related" class="related-panel hidden"></div>
<div class="metrics-shell">
<aside class="metrics-toc">
<input id="chart-search" type="search" placeholder="Filter charts…" autocomplete="off" />
<nav id="metrics-toc" class="metrics-toc-nav" aria-label="Chart sections"></nav>
<ul id="chart-catalog-list" class="catalog-list hidden" hidden></ul>
</aside>
<div id="metrics-wall" class="metrics-wall" tabindex="0"></div>
</div>
<article id="chart-detail-panel" class="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 -->
<section id="fleet-view" class="view hidden">
<header class="page-header">
<div>
<p class="dash-kicker">Multi-host</p>
<h1 class="dash-title">Fleet</h1>
<p class="page-subtitle">Concurrent agents — set active, reconnect, open charts</p>
</div>
<div class="page-actions">
<label class="check-row">
<input type="checkbox" id="compare-toggle" />
Compare CPU overlay
</label>
<button type="button" id="fleet-refresh-btn" class="btn btn-ghost">Refresh</button>
<button type="button" id="btn-invite" class="btn btn-ghost" disabled>Mint invite</button>
</div>
</header>
<section id="fleet-summary-bar" class="fleet-summary-bar" aria-live="polite">
<div class="fleet-stat" data-fleet-stat="live"><span class="fleet-stat-val" id="fleet-stat-live">0</span><span class="muted">Live</span></div>
<div class="fleet-stat" data-fleet-stat="retry"><span class="fleet-stat-val" id="fleet-stat-retry">0</span><span class="muted">Retrying</span></div>
<div class="fleet-stat" data-fleet-stat="offline"><span class="fleet-stat-val" id="fleet-stat-offline">0</span><span class="muted">Offline</span></div>
<div class="fleet-stat" data-fleet-stat="failed"><span class="fleet-stat-val" id="fleet-stat-failed">0</span><span class="muted">Failed</span></div>
</section>
<div id="fleet-cards" class="fleet-cards"></div>
<pre id="invite-out" class="invite-out hidden"></pre>
<!-- Compatibility hooks for legacy list renderers -->
<ul id="bookmark-list" class="hidden" hidden></ul>
<ul id="peer-list" class="hidden" hidden></ul>
</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>