763 lines
37 KiB
HTML
763 lines
37 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=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>1–5</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 A–Z</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>
|