Files
peardata/index.html
T
2026-07-30 16:16:25 -04:00

763 lines
37 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="processes">
<span class="nav-ico"></span><span class="nav-label">Processes</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="qvac">
<span class="nav-ico"></span><span class="nav-label">QVAC</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">Double-click or <span class="page-hint-ico"></span> for fullscreen · <kbd>Space</kbd> · <kbd>/</kbd> · <kbd>15</kbd> · <kbd>Esc</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 logs-header">
<div>
<p class="dash-kicker">Investigate</p>
<h1 class="dash-title">Logs</h1>
<p class="page-subtitle">Host journal · anomalies · audit · <kbd>/</kbd> search · Follow for live</p>
</div>
<div class="page-actions logs-header-actions">
<button type="button" id="logs-follow" class="btn btn-ghost" aria-pressed="false" title="Auto-refresh every 5s">Follow</button>
<button type="button" id="logs-refresh" class="btn btn-ghost" title="Refresh now">Refresh</button>
</div>
</header>
<div class="logs-toolbar" id="logs-toolbar">
<div class="logs-toolbar-top">
<div id="logs-sources" class="logs-sources" role="tablist" aria-label="Log source">
<button type="button" class="metrics-tf active" data-log-source="journal" role="tab" aria-selected="true" title="Admin · host journal">Journal</button>
<button type="button" class="metrics-tf" data-log-source="anomaly" role="tab" aria-selected="false">Anomalies</button>
<button type="button" class="metrics-tf" data-log-source="audit" role="tab" aria-selected="false" title="Admin · agent audit trail">Audit</button>
</div>
<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>
</div>
<div class="logs-search-row">
<input id="logs-q" type="search" placeholder="Search messages…" autocomplete="off" spellcheck="false" />
<label class="metrics-group-label muted logs-journal-only">
Max priority
<select id="logs-priority" aria-label="Journal max priority (and above)">
<option value="">any</option>
<option value="3">err+</option>
<option value="4">warning+</option>
<option value="5">notice+</option>
<option value="6">info+</option>
<option value="2">crit+</option>
<option value="0">emerg only</option>
</select>
</label>
<label class="metrics-group-label muted logs-journal-only">
Unit
<input id="logs-unit" type="text" placeholder="peardata.service" autocomplete="off" spellcheck="false" />
</label>
<button type="button" id="logs-search-btn" class="btn btn-primary">Search</button>
<button type="button" id="logs-clear-btn" class="btn btn-ghost" title="Clear search filters">Clear</button>
</div>
<div class="logs-status-row">
<p id="logs-status" class="muted logs-status" role="status"></p>
<button type="button" id="logs-copy" class="btn btn-ghost logs-copy-btn" hidden title="Copy visible lines">Copy</button>
</div>
</div>
<div class="logs-stream" id="logs-stream">
<div class="logs-cols" aria-hidden="true">
<span>Time</span>
<span>Sev</span>
<span>Unit</span>
<span>Message</span>
<span></span>
</div>
<ul id="logs-list" class="logs-list" aria-live="polite"></ul>
<div id="logs-empty" class="logs-empty hidden" role="status"></div>
<div class="logs-footer">
<button type="button" id="logs-more" class="btn btn-ghost hidden">Load more</button>
</div>
</div>
</section>
<!-- QVAC local AI -->
<section id="qvac-view" class="view hidden">
<header class="page-header qvac-header">
<div>
<p class="dash-kicker">Local AI</p>
<h1 class="dash-title">QVAC</h1>
<p class="page-subtitle">On-device SRE copilot · tools → agent RPC · no cloud</p>
</div>
<div class="page-actions qvac-header-actions">
<span id="qvac-model-chip" class="qvac-model-chip" title="Model status">idle</span>
<span id="qvac-status" class="muted" data-kind=""></span>
<button type="button" id="qvac-new-chat" class="btn btn-ghost">New chat</button>
<button type="button" id="qvac-unload" class="btn btn-ghost" title="Unload model from memory">Unload</button>
<button type="button" id="qvac-reset" class="btn btn-ghost" title="Re-run onboarding">Setup</button>
</div>
</header>
<div id="qvac-setup" class="qvac-setup">
<div id="qvac-setup-steps" class="qvac-setup-steps"></div>
</div>
<div id="qvac-chat" class="qvac-chat hidden">
<div id="qvac-samples" class="qvac-samples" aria-label="Sample prompts"></div>
<div id="qvac-messages" class="qvac-messages" aria-live="polite"></div>
<form id="qvac-form" class="qvac-composer" onsubmit="return false">
<textarea id="qvac-input" rows="2" placeholder="Ask about host health, metrics, anomalies, processes…" autocomplete="off"></textarea>
<button type="button" id="qvac-send" class="btn btn-primary">Send</button>
</form>
</div>
</section>
<!-- Processes -->
<section id="processes-view" class="view hidden">
<header class="page-header proc-header">
<div>
<p class="dash-kicker">Host</p>
<h1 class="dash-title">Processes</h1>
<p class="page-subtitle">Live /proc · rates · tree · detail · <kbd>/</kbd> search · <kbd>F</kbd> follow · <kbd>T</kbd> tree</p>
</div>
<div class="page-actions proc-header-actions">
<button type="button" id="proc-follow" class="btn btn-ghost active" aria-pressed="true" title="Auto-refresh">Follow</button>
<button type="button" id="proc-refresh" class="btn btn-ghost" title="Refresh now">Refresh</button>
<button type="button" id="proc-tree" class="btn btn-ghost" aria-pressed="false" title="Parent/child tree">Tree</button>
<button type="button" id="proc-export" class="btn btn-ghost" title="Export CSV">CSV</button>
<button type="button" id="proc-charts" class="btn btn-primary" title="Open top process charts">Charts</button>
</div>
</header>
<div id="proc-kpis" class="proc-kpis" aria-label="Process summary"></div>
<div class="proc-toolbar">
<input id="proc-q" type="search" placeholder="Filter pid, name, user, cmdline, cgroup…" autocomplete="off" spellcheck="false" />
<div id="proc-filters" class="proc-filters" role="group" aria-label="Process filters">
<button type="button" class="metrics-tf active" data-proc-filter="all">All</button>
<button type="button" class="metrics-tf" data-proc-filter="running">Running</button>
<button type="button" class="metrics-tf" data-proc-filter="highcpu">Hot CPU</button>
<button type="button" class="metrics-tf" data-proc-filter="highmem">Hot mem</button>
<button type="button" class="metrics-tf" data-proc-filter="hasio">I/O</button>
<button type="button" class="metrics-tf" data-proc-filter="zombie">Zombie</button>
<button type="button" class="metrics-tf" data-proc-filter="user">User</button>
<button type="button" class="metrics-tf" data-proc-filter="kernel">Kernel</button>
</div>
<label class="metrics-group-label muted">
Sort
<select id="proc-sort" aria-label="Sort processes">
<option value="cpu:desc">CPU ↓</option>
<option value="cpu:asc">CPU ↑</option>
<option value="rss:desc">RSS ↓</option>
<option value="rss:asc">RSS ↑</option>
<option value="io:desc">I/O ↓</option>
<option value="threads:desc">Threads ↓</option>
<option value="pid:asc">PID ↑</option>
<option value="name:asc">Name AZ</option>
<option value="age:desc">Age ↓</option>
</select>
</label>
<label class="metrics-group-label muted">
Refresh
<select id="proc-refresh-ms" aria-label="Refresh interval">
<option value="1s">1s</option>
<option value="2s" selected>2s</option>
<option value="5s">5s</option>
<option value="off">Manual</option>
</select>
</label>
</div>
<p id="proc-status" class="muted proc-status" role="status"></p>
<div class="proc-shell">
<div class="proc-main">
<div class="proc-table-wrap" id="proc-table-wrap">
<table class="proc-table" id="proc-table">
<thead>
<tr>
<th data-sort="pid">PID</th>
<th>User</th>
<th data-sort="name">Process</th>
<th data-sort="state">State</th>
<th data-sort="cpu">CPU</th>
<th data-sort="rss">RSS</th>
<th data-sort="io">I/O KiB/s</th>
<th data-sort="threads">Thr</th>
<th data-sort="age">Age</th>
</tr>
</thead>
<tbody id="proc-tbody"></tbody>
</table>
<div id="proc-empty" class="proc-empty hidden" role="status"></div>
</div>
</div>
<aside class="proc-side" aria-label="Process insights">
<section class="proc-side-card">
<h3>Top CPU</h3>
<div id="proc-top-cpu" class="proc-top-list"></div>
</section>
<section class="proc-side-card">
<h3>Top memory</h3>
<div id="proc-top-mem" class="proc-top-list"></div>
</section>
<section class="proc-side-card proc-detail-card" id="proc-detail">
<p class="muted proc-detail-empty">Select a process to inspect</p>
</section>
</aside>
</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, data retention, 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="data">Data</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="qvac">QVAC</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="data">
<div class="dash-card settings-section settings-section-wide">
<h3>Data Manager</h3>
<p class="hint">
Configure how much history the connected agent keeps in memory and on disk.
Changes apply live on the agent and persist under its data directory.
</p>
<p id="dm-admin-gate" class="dm-gate hint" role="status"></p>
<div class="dm-usage" aria-live="polite">
<div class="dm-usage-card">
<span class="stat-label">Total data</span>
<strong id="dm-usage-total"></strong>
</div>
<div class="dm-usage-card">
<span class="stat-label">Corestore</span>
<strong id="dm-usage-corestore"></strong>
</div>
<div class="dm-usage-card">
<span class="stat-label">Memory rings</span>
<strong id="dm-usage-memory"></strong>
</div>
<div class="dm-usage-card">
<span class="stat-label">Warm history</span>
<strong id="dm-usage-warm"></strong>
</div>
</div>
<p id="dm-usage-meta" class="muted settings-hint"></p>
<h3>Hot memory (1s)</h3>
<label>
Retention window
<select id="dm-hot-preset"></select>
</label>
<label>
Points (custom)
<input id="dm-hot-points" type="number" min="60" max="604800" step="60" value="3600" />
</label>
<p class="muted settings-hint">High-resolution samples kept in RAM. Larger windows use more memory.</p>
<h3>Warm memory (downsampled)</h3>
<label>
In-memory warm ring
<select id="dm-warm-mem-preset"></select>
</label>
<label>
Points (custom)
<input id="dm-warm-mem-points" type="number" min="10" max="525600" step="10" value="1440" />
</label>
<label>
Samples per warm bucket
<input id="dm-tier1-every" type="number" min="5" max="3600" step="1" value="60" />
</label>
<p class="muted settings-hint">Every N hot samples are averaged into one warm point (~60s at default).</p>
<h3>Disk history (HyperDB)</h3>
<label class="check-row">
<input type="checkbox" id="dm-warm-enabled" checked />
Auto-rotate warm history by age
</label>
<label>
Keep history for
<select id="dm-warm-preset"></select>
</label>
<label id="dm-warm-custom-wrap" class="hidden">
Custom days
<input id="dm-warm-custom-days" type="number" min="1" max="3650" step="1" value="365" />
</label>
<label>
Soft disk budget
<select id="dm-disk-preset"></select>
</label>
<label id="dm-disk-custom-wrap" class="hidden">
Custom budget (MB)
<input id="dm-disk-custom-mb" type="number" min="64" max="1048576" step="64" value="1024" />
</label>
<label>
Max warm points (0 = unlimited)
<input id="dm-warm-max-points" type="number" min="0" max="100000000" step="1000" value="0" />
</label>
<h3>Auto prune</h3>
<label class="check-row">
<input type="checkbox" id="dm-auto-prune" checked />
Run automatic prune on a schedule
</label>
<label>
Prune interval (hours)
<input id="dm-prune-interval-hours" type="number" min="1" max="168" step="1" value="1" />
</label>
<p id="dm-last-prune" class="muted settings-hint">Last prune: —</p>
<div class="dm-actions">
<button type="button" id="dm-refresh" class="ghost">Refresh</button>
<button type="button" id="dm-prune-dry" class="ghost">Dry-run prune</button>
<button type="button" id="dm-prune-now" class="ghost danger-btn">Prune now</button>
<button type="button" id="dm-save" class="primary">Save to agent</button>
</div>
<p id="dm-status" class="dm-status muted" role="status"></p>
</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="qvac">
<div class="dash-card settings-section">
<h3>Local AI (QVAC)</h3>
<p class="hint">
Models run on this desktop. The agent only answers metrics tools.
Open the <strong>QVAC</strong> tab for chat and first-time setup.
</p>
<label>
Default model profile
<select id="setting-qvac-profile">
<option value="lite">Lite (Qwen3 0.6B)</option>
<option value="recommended" selected>Recommended (Qwen3 1.7B + tools)</option>
<option value="strong">Strong (Qwen3 4B)</option>
<option value="tool-tiny">Tool-tiny (Llama 1B tools)</option>
</select>
</label>
<label class="check-row">
<input type="checkbox" id="setting-qvac-rag" checked />
Inject local knowledge (guide + catalog) into prompts
</label>
<label>
Idle unload (minutes, 0 = never)
<input id="setting-qvac-idle" type="number" min="0" max="240" step="5" value="30" />
</label>
<p class="muted settings-hint">
Install <code>@qvac/sdk</code> for full local LLM. Without it, QVAC stays in tools-only mode.
See <code>docs/QVAC.md</code>.
</p>
<button type="button" id="btn-qvac-open" class="btn btn-ghost">Open QVAC tab</button>
</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>