Files
peardata/index.html
T
Raven Scott 6ef3afe821
CI / test (push) Successful in 1m4s
Release rolling / release (push) Successful in 6m57s
System Log
2026-07-18 23:05:44 -04:00

481 lines
23 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>
<button type="button" class="nav-link" data-view="logs">
<span class="nav-ico"></span><span class="nav-label">Logs</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="84"></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="84"></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="84"></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="84"></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="84"></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="84"></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-correlate" class="btn btn-ghost" aria-pressed="false" title="Metric Correlations — brush a highlight, then find changed metrics">Correlate</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">
<button type="button" id="metrics-filters-btn" class="btn btn-ghost" aria-expanded="false" aria-controls="metrics-filters-panel" title="Search, sections, group, sort">Filters</button>
<span id="metrics-filter-chip" class="metrics-filter-chip hidden" role="status"></span>
<span id="metrics-meta" class="muted metrics-meta"></span>
<span id="metrics-hover" class="muted metrics-hover"></span>
</div>
</div>
<div id="metrics-mc-bar" class="metrics-mc-bar hidden" aria-label="Metric Correlations">
<label class="metrics-group-label muted">
Method
<select id="metrics-mc-method" aria-label="Correlation method">
<option value="volume">volume</option>
<option value="ks2">ks2</option>
<option value="anomaly-rate">anomaly-rate</option>
<option value="value">value (cv)</option>
</select>
</label>
<span id="metrics-mc-hint" class="muted metrics-mc-hint">Brush a chart (≥15s), then Find Correlations</span>
<button type="button" id="metrics-mc-run" class="btn btn-primary" disabled>Find Correlations</button>
<button type="button" id="metrics-mc-clear" class="btn btn-ghost">Clear</button>
</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 id="metrics-mc-results" class="related-panel mc-results-panel hidden"></div>
<div class="metrics-shell">
<aside id="metrics-filters-panel" class="metrics-toc metrics-filters-panel" hidden>
<div class="metrics-filters-controls">
<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>
</div>
<input id="chart-search" type="search" placeholder="Filter charts…" autocomplete="off" />
<nav id="metrics-toc" class="metrics-toc-nav" aria-label="Chart sections"></nav>
</aside>
<div id="metrics-wall" class="metrics-wall" tabindex="0"></div>
</div>
</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>
<!-- Logs -->
<section id="logs-view" class="view hidden">
<header class="page-header">
<div>
<p class="dash-kicker">Investigate</p>
<h1 class="dash-title">Logs</h1>
<p class="page-subtitle">Anomalies, agent audit trail, and optional host journal</p>
</div>
</header>
<div class="logs-toolbar">
<div id="logs-sources" class="logs-sources" role="tablist" aria-label="Log source">
<button type="button" class="metrics-tf active" data-log-source="anomaly" role="tab" aria-selected="true">Anomalies</button>
<button type="button" class="metrics-tf" data-log-source="audit" role="tab" aria-selected="false" title="Admin only">Audit</button>
<button type="button" class="metrics-tf" data-log-source="journal" role="tab" aria-selected="false" title="Admin only · PEARDATA_JOURNAL=1">Journal</button>
</div>
<div class="logs-search-row">
<input id="logs-q" type="search" placeholder="Search message…" autocomplete="off" />
<div id="logs-presets" class="metrics-presets" role="group" aria-label="Time range">
<button type="button" class="metrics-tf" data-log-range="15m">15m</button>
<button type="button" class="metrics-tf active" data-log-range="1h">1h</button>
<button type="button" class="metrics-tf" data-log-range="6h">6h</button>
<button type="button" class="metrics-tf" data-log-range="24h">24h</button>
</div>
<label class="metrics-group-label muted logs-journal-only">
Priority
<select id="logs-priority" aria-label="Journal priority">
<option value="">any</option>
<option value="0">0 emerg</option>
<option value="1">1 alert</option>
<option value="2">2 crit</option>
<option value="3">3 err</option>
<option value="4">4 warning</option>
<option value="5">5 notice</option>
<option value="6">6 info</option>
<option value="7">7 debug</option>
</select>
</label>
<label class="metrics-group-label muted logs-journal-only">
Unit
<input id="logs-unit" type="text" placeholder="e.g. peardata" autocomplete="off" />
</label>
<button type="button" id="logs-search-btn" class="btn btn-primary">Search</button>
</div>
</div>
<p id="logs-status" class="muted logs-status" role="status"></p>
<div class="dash-card logs-card">
<ul id="logs-list" class="event-list logs-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>
</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>
Overview spark depth
<input id="setting-chart-points" type="number" min="30" max="300" step="10" value="90" />
</label>
<p class="muted settings-hint">Fallback ring size when Charts has no active window. While Charts is open, Overview follows the Charts timeframe (capped at 300).</p>
<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>