feat: unify Raspberry Pi disk presentation and manual discovery
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"id": "pigway-agent",
|
||||
"name": "PIGWay Raspberry Pi Agent",
|
||||
"version": "1.4.0",
|
||||
"version": "1.6.1",
|
||||
"protocol_version": 1,
|
||||
"entrypoint": "worker.py",
|
||||
"runtime": "python3",
|
||||
"capabilities": [
|
||||
"refresh",
|
||||
"status",
|
||||
"logs",
|
||||
"config",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { state as appState } from "/assets/js/state.js";
|
||||
import { addStorageSettings } from "./storage.js";
|
||||
import { renderDevice } from "./device.js";
|
||||
import { renderHosts } from "/assets/js/devices.js";
|
||||
import {
|
||||
@@ -345,6 +346,7 @@ export function renderConfig() {
|
||||
.forEach((b) => (b.onclick = () => deleteHost(b.dataset.deleteHost)));
|
||||
bindBulkSync();
|
||||
updateConditional();
|
||||
addStorageSettings();
|
||||
}
|
||||
export function markChanged() {
|
||||
let n = 0;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { state as appState } from "/assets/js/state.js";
|
||||
import { storageCards } from "./storage.js";
|
||||
import { esc } from "/assets/js/shared.js";
|
||||
import { tr } from "/assets/js/i18n.js";
|
||||
import { bytes, formatUptime, meter } from "/assets/js/overview.js";
|
||||
@@ -22,5 +23,5 @@ export function agentHostCard(entry) {
|
||||
diskTotal = y.disk_total_bytes;
|
||||
const percent = n => Number.isFinite(n) ? `${n.toFixed(1)}%` : "--";
|
||||
const progress = n => Number.isFinite(n) ? meter(n) : "";
|
||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(m.name || h.name || m.id)}</h2><span class="uptime-badge">${esc(formatUptime(s.uptime_seconds))}</span></div><div class="device-details"><span>ID ${esc(m.id || h.id)}</span><span>${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}</span><span>${esc(v.manufacturer || "")} ${esc(v.model || "--")}</span><span>${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}</span><span>${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}</span><span>${esc(v.os || "--")}</span><span>${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}</span><span>${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}</span><span>${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}</span></div></div><span class="badge good">${tr("在线")}</span></div><div class="host-grid"><div class="metric" data-refresh-group="system"><div class="muted">${tr("处理器负载")}</div><div class="value">${percent(y.cpu_percent)}</div>${progress(y.cpu_percent)}<div class="muted">${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}</div></div><div class="metric" data-refresh-group="system"><div class="muted">${tr("内存占用")}</div><div class="value">${percent(y.memory_percent)}</div>${progress(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}</div></div><div class="metric" data-refresh-group="storage"><div class="muted">${tr("磁盘占用")}</div><div class="value">${percent(y.disk_percent)}</div>${progress(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}</div></div><div class="metric" data-refresh-group="network"><div class="muted">${tr("主网络")}</div><div class="value">↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s</div><div class="muted">${esc(n.interface || "--")} · ${n.kind === "WIF" ? `${esc(n.metric)} dBm · ` : n.link_speed_mbps ? `${esc(n.link_speed_mbps)} Mbps · ` : ""}${esc(n.ip_label)} ${esc(n.ip)} · ${n.link === true ? (appState.LANG === "zh" ? "已连接" : "Connected") : n.link === false ? (appState.LANG === "zh" ? "已断开" : "Disconnected") : (appState.LANG === "zh" ? "状态未确认" : "Unconfirmed")}</div></div><div class="metric" data-refresh-group="display"><div class="muted">${tr("显示状态")}</div><div class="value">${esc(ownerName(d.owner))}</div><div class="muted">${appState.LANG === "zh" ? "告警状态独立于硬件插件" : "Alert state is independent of hardware plugins"}</div></div><div class="metric" data-refresh-group="alerts"><div class="muted">${tr("当前告警")}</div><div class="value">${(d.alerts || []).length}</div><div class="muted">${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}</div></div></div>${pluginSummary(s.plugins || {}, h.id)}<div class="group"><h3>${tr("服务与进程监控")}</h3><div class="rows">${(s.watches || []).map((w) => `<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span><span class="badge ${w.up ? "good" : "bad"}">${tr(w.up ? "正常" : "停止")}</span></div>`).join("") || `<div class="muted">${tr("暂无数据")}</div>`}</div></div></article>`;
|
||||
return `<article class="panel host-panel"><div class="host-head"><div><div class="host-title"><h2>${esc(m.name || h.name || m.id)}</h2><span class="uptime-badge">${esc(formatUptime(s.uptime_seconds))}</span></div><div class="device-details"><span>ID ${esc(m.id || h.id)}</span><span>${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}</span><span>${esc(v.manufacturer || "")} ${esc(v.model || "--")}</span><span>${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}</span><span>${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}</span><span>${esc(v.os || "--")}</span><span>${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}</span><span>${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}</span><span>${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}</span></div></div><span class="badge good">${tr("在线")}</span></div><div class="host-grid"><div class="metric" data-refresh-group="system"><div class="muted">${tr("处理器负载")}</div><div class="value">${percent(y.cpu_percent)}</div>${progress(y.cpu_percent)}<div class="muted">${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}</div></div><div class="metric" data-refresh-group="system"><div class="muted">${tr("内存占用")}</div><div class="value">${percent(y.memory_percent)}</div>${progress(y.memory_percent)}<div class="muted">${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}</div></div>${Array.isArray(y.storage_inventory) ? storageCards(y,entry.snapshot?.resources) : `<div class="metric" data-refresh-group="storage"><div class="muted">${tr("磁盘占用")}</div><div class="value">${percent(y.disk_percent)}</div>${progress(y.disk_percent)}<div class="muted">${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}</div></div>`}<div class="metric" data-refresh-group="network"><div class="muted">${tr("主网络")}</div><div class="value">↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s</div><div class="muted">${esc(n.interface || "--")} · ${n.kind === "WIF" ? `${esc(n.metric)} dBm · ` : n.link_speed_mbps ? `${esc(n.link_speed_mbps)} Mbps · ` : ""}${esc(n.ip_label)} ${esc(n.ip)} · ${n.link === true ? (appState.LANG === "zh" ? "已连接" : "Connected") : n.link === false ? (appState.LANG === "zh" ? "已断开" : "Disconnected") : (appState.LANG === "zh" ? "状态未确认" : "Unconfirmed")}</div></div><div class="metric" data-refresh-group="display"><div class="muted">${tr("显示状态")}</div><div class="value">${esc(ownerName(d.owner))}</div><div class="muted">${appState.LANG === "zh" ? "告警状态独立于硬件插件" : "Alert state is independent of hardware plugins"}</div></div><div class="metric" data-refresh-group="alerts"><div class="muted">${tr("当前告警")}</div><div class="value">${(d.alerts || []).length}</div><div class="muted">${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}</div></div></div>${pluginSummary(s.plugins || {}, h.id)}<div class="group"><h3>${tr("服务与进程监控")}</h3><div class="rows">${(s.watches || []).map((w) => `<div class="row"><span>${esc(w.name)} · ${esc(w.target)}</span><span class="badge ${w.up ? "good" : "bad"}">${tr(w.up ? "正常" : "停止")}</span></div>`).join("") || `<div class="muted">${tr("暂无数据")}</div>`}</div></div></article>`;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import { esc, pluginText as text } from "/assets/js/shared.js";
|
||||
import { bytes, meter } from "/assets/js/overview.js";
|
||||
const states = {mounted:["已挂载","Mounted"],unmounted:["未挂载","Unmounted"],missing:["设备未连接","Device disconnected"],unknown:["状态未确认","Unconfirmed"]};
|
||||
export function storageCards(system, resources) {
|
||||
if(!Array.isArray(system.storage_inventory))return "";
|
||||
const inventory=new Map(system.storage_inventory.map(d=>["storage-"+d.id,d]));
|
||||
const presented=resources?.filter(r=>inventory.has(r.id));
|
||||
const disks=presented?.length?presented.map(r=>({...inventory.get(r.id),name:r.name,visible:!r.hidden})):system.storage_inventory;
|
||||
return disks.filter(d=>d.visible!==false).map(d=>{
|
||||
const percent=Number.isFinite(d.used_percent)&&d.state==="mounted"?d.used_percent:null;
|
||||
const label=text(...(states[d.state]||states.unknown));
|
||||
const detail=percent===null?label:`${bytes(d.used_bytes)} / ${bytes(d.capacity_bytes)} · ${text("剩余","Free")} ${bytes(d.free_bytes)}`;
|
||||
const location=d.mountpoint?`${text("挂载点","Mount point")}: ${d.mountpoint}`:d.device?`${text("设备","Device")}: ${d.device}`:"";
|
||||
return `<div class="metric" data-refresh-group="storage_inventory" data-storage-id="${esc(d.id)}" data-resource-id="storage-${esc(d.id)}"><div class="muted" title="${esc(location)}">${esc(d.name)}</div><div class="value">${percent===null?esc(label):percent.toFixed(1)+"%"}</div>${percent===null?"":meter(percent)}<div class="muted">${esc(detail)}</div></div>`;
|
||||
}).join("");
|
||||
}
|
||||
export async function addStorageSettings() {
|
||||
const editor=await import("/assets/js/storage-presentation.js");
|
||||
return editor.installStoragePresentation();
|
||||
}
|
||||
+18
-2
@@ -96,14 +96,27 @@ def collect(device):
|
||||
at = source.get('storage_observed_at') if metric['key'].startswith('disk_') else source.get('observed_at') if metric['key'] != 'uptime_seconds' else value.get('timestamp')
|
||||
if isinstance(at, (int, float)) and not isinstance(at, bool) and math.isfinite(at) and at > 0:
|
||||
metric['observed_at'] = at
|
||||
resources = [{'id': 'host', 'type': 'host'}]
|
||||
for disk in value['system'].get('storage_inventory', []):
|
||||
rid = 'storage-' + disk['id']
|
||||
resources.append({'id': rid, 'type': 'filesystem' if disk.get('filesystem') else 'disk', 'parent_id': 'host',
|
||||
'name': disk['name'], 'state': disk['state'],
|
||||
'details': {'device': disk.get('device', ''), 'mountpoint': disk.get('mountpoint', ''),
|
||||
'visible': disk.get('visible', True)}})
|
||||
if disk['state'] == 'mounted' and not value['system'].get('storage_inventory_error'):
|
||||
for key, unit in [('capacity_bytes', 'bytes'), ('used_bytes', 'bytes'), ('free_bytes', 'bytes'), ('used_percent', 'percent')]:
|
||||
number = disk.get(key)
|
||||
if isinstance(number, (int, float)) and not isinstance(number, bool) and math.isfinite(number):
|
||||
metrics.append({'resource_id': rid, 'key': key, 'value': number, 'unit': unit,
|
||||
'observed_at': value['system']['storage_inventory_observed_at']})
|
||||
return {'schema_version': 1, 'observed_at': value.get('timestamp'),
|
||||
'identity': {'reported_name': value['machine'].get('name'), 'category': 'host',
|
||||
'details': value.get('device', {})},
|
||||
'collection': {**declaration, 'interval_seconds': collection_interval(value)},
|
||||
'capabilities': MANIFEST['capabilities'], 'metrics': metrics,
|
||||
'resources': [{'id': 'host', 'type': 'host'}],
|
||||
'resources': resources,
|
||||
'alerts': value.get('display', {}).get('alerts', []),
|
||||
'alerts_complete': isinstance(value.get('display', {}).get('alerts'), list) and not storage_plan.get('error',False),
|
||||
'alerts_complete': isinstance(value.get('display', {}).get('alerts'), list) and not storage_plan.get('error',False) and not value['system'].get('storage_inventory_error', False),
|
||||
'view': value}
|
||||
|
||||
|
||||
@@ -118,6 +131,9 @@ def dispatch(method, params):
|
||||
return request(device, '/health')
|
||||
if method == 'device.collect':
|
||||
return collect(device)
|
||||
if method == 'device.refresh':
|
||||
if params.get('group') != 'storage':raise AdapterError('UNSUPPORTED')
|
||||
return request(device, '/storage/scan', 'PUT', {})
|
||||
if method == 'device.logs.query':
|
||||
return request(device, '/logs', query=params.get('query'))
|
||||
if method == 'device.read':
|
||||
|
||||
Reference in New Issue
Block a user