From 721cec18e711e1a1ce3f02e98be8a33e368bb46a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=B8=A1=E5=8F=A3=E6=B5=AA=E4=BA=BA?= Date: Thu, 15 Oct 2026 09:13:50 +0800 Subject: [PATCH] feat: follow Agent object plans and daily storage observations --- plugin/manifest.json | 4 ++-- plugin/ui/overview.js | 2 +- plugin/worker.py | 9 ++++++--- tests/test_metrics.py | 6 +++--- 4 files changed, 12 insertions(+), 9 deletions(-) diff --git a/plugin/manifest.json b/plugin/manifest.json index b553f53..7bc023d 100644 --- a/plugin/manifest.json +++ b/plugin/manifest.json @@ -1,7 +1,7 @@ { "id": "pigway-agent", "name": "PIGWay Raspberry Pi Agent", - "version": "1.3.3", + "version": "1.4.0", "protocol_version": 1, "entrypoint": "worker.py", "runtime": "python3", @@ -40,6 +40,6 @@ }, "legacy_connection_fields": true, "collection": { - "interval_seconds": 4 + "interval_seconds": 1 } } diff --git a/plugin/ui/overview.js b/plugin/ui/overview.js index 7f9590b..1e71e86 100644 --- a/plugin/ui/overview.js +++ b/plugin/ui/overview.js @@ -22,5 +22,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 `

${esc(m.name || h.name || m.id)}

${esc(formatUptime(s.uptime_seconds))}
ID ${esc(m.id || h.id)}${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}${esc(v.manufacturer || "")} ${esc(v.model || "--")}${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}${esc(v.os || "--")}${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}
${tr("在线")}
${tr("处理器负载")}
${percent(y.cpu_percent)}
${progress(y.cpu_percent)}
${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}
${tr("内存占用")}
${percent(y.memory_percent)}
${progress(y.memory_percent)}
${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}
${tr("磁盘占用")}
${percent(y.disk_percent)}
${progress(y.disk_percent)}
${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}
${tr("主网络")}
↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s
${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")}
${tr("显示状态")}
${esc(ownerName(d.owner))}
${appState.LANG === "zh" ? "告警状态独立于硬件插件" : "Alert state is independent of hardware plugins"}
${tr("当前告警")}
${(d.alerts || []).length}
${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}
${pluginSummary(s.plugins || {}, h.id)}

${tr("服务与进程监控")}

${(s.watches || []).map((w) => `
${esc(w.name)} · ${esc(w.target)}${tr(w.up ? "正常" : "停止")}
`).join("") || `
${tr("暂无数据")}
`}
`; + return `

${esc(m.name || h.name || m.id)}

${esc(formatUptime(s.uptime_seconds))}
ID ${esc(m.id || h.id)}${appState.LANG === "zh" ? "主机名" : "Hostname"} ${esc(m.hostname || v.hostname || "--")}${esc(v.manufacturer || "")} ${esc(v.model || "--")}${appState.LANG === "zh" ? "序列号" : "Serial"} ${esc(v.serial || "--")}${appState.LANG === "zh" ? "硬件版本" : "Revision"} ${esc(v.revision || "--")}${esc(v.os || "--")}${appState.LANG === "zh" ? "内核" : "Kernel"} ${esc(v.kernel || "--")}${esc(v.architecture || "--")} · CPU ${esc(v.cpu_count || "--")}${appState.LANG === "zh" ? "程序版本" : "Agent"} ${esc(s.version || "--")}
${tr("在线")}
${tr("处理器负载")}
${percent(y.cpu_percent)}
${progress(y.cpu_percent)}
${appState.LANG === "zh" ? "频率" : "Frequency"} ${esc(y.cpu_freq_mhz)} MHz · ${esc(y.temperature_c)}°C${fanMetric(s.plugins || {})}
${tr("内存占用")}
${percent(y.memory_percent)}
${progress(y.memory_percent)}
${bytes(memUsed)} / ${bytes(memTotal)} · ${appState.LANG === "zh" ? "可用" : "Available"} ${bytes(y.memory_available_bytes)}
${tr("磁盘占用")}
${percent(y.disk_percent)}
${progress(y.disk_percent)}
${bytes(diskUsed)} / ${bytes(diskTotal)} · ${appState.LANG === "zh" ? "剩余" : "Free"} ${bytes(y.disk_free_bytes)}
${tr("主网络")}
↓ ${bytes(n.rx_bytes_per_second)}/s · ↑ ${bytes(n.tx_bytes_per_second)}/s
${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")}
${tr("显示状态")}
${esc(ownerName(d.owner))}
${appState.LANG === "zh" ? "告警状态独立于硬件插件" : "Alert state is independent of hardware plugins"}
${tr("当前告警")}
${(d.alerts || []).length}
${(d.alerts || []).map((a) => esc(ownerName(a.id))).join(" · ") || tr("无告警")}
${pluginSummary(s.plugins || {}, h.id)}

${tr("服务与进程监控")}

${(s.watches || []).map((w) => `
${esc(w.name)} · ${esc(w.target)}${tr(w.up ? "正常" : "停止")}
`).join("") || `
${tr("暂无数据")}
`}
`; } diff --git a/plugin/worker.py b/plugin/worker.py index a3dc85f..298e35b 100644 --- a/plugin/worker.py +++ b/plugin/worker.py @@ -68,6 +68,9 @@ def collect(device): value = request(device, '/status') if not isinstance(value.get('system'), dict) or not isinstance(value.get('machine'), dict): raise AdapterError('INVALID_RESPONSE') + declaration = value.get('collection') if isinstance(value.get('collection'),dict) else {} + plans = declaration.get('refresh') if isinstance(declaration.get('refresh'),dict) else {} + storage_plan = plans.get('storage') if isinstance(plans.get('storage'),dict) else {} metrics = [] units = {'cpu_percent': 'percent', 'memory_percent': 'percent', 'disk_percent': 'percent', 'temperature_c': 'celsius', 'cpu_freq_mhz': 'megahertz', @@ -90,17 +93,17 @@ def collect(device): metrics.append({'resource_id': 'host', 'key': key, 'value': rate, 'unit': 'bytes_per_second'}) for metric in metrics: source = value.get('network', {}) if metric['key'].startswith('network_') else value.get('system', {}) - at = source.get('observed_at') if metric['key'] != 'uptime_seconds' else value.get('timestamp') + 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 return {'schema_version': 1, 'observed_at': value.get('timestamp'), 'identity': {'reported_name': value['machine'].get('name'), 'category': 'host', 'details': value.get('device', {})}, - 'collection': {'interval_seconds': collection_interval(value)}, + 'collection': {**declaration, 'interval_seconds': collection_interval(value)}, 'capabilities': MANIFEST['capabilities'], 'metrics': metrics, 'resources': [{'id': 'host', 'type': 'host'}], 'alerts': value.get('display', {}).get('alerts', []), - 'alerts_complete': isinstance(value.get('display', {}).get('alerts'), list), + 'alerts_complete': isinstance(value.get('display', {}).get('alerts'), list) and not storage_plan.get('error',False), 'view': value} diff --git a/tests/test_metrics.py b/tests/test_metrics.py index db7dd64..5d3818f 100644 --- a/tests/test_metrics.py +++ b/tests/test_metrics.py @@ -21,13 +21,13 @@ class MetricsTests(unittest.TestCase): self.assertEqual(values['network_rx_bytes_per_second'],0) self.assertEqual(values['network_tx_bytes_per_second'],1234) self.assertEqual(next(m['observed_at'] for m in result['metrics'] if m['key']=='network_tx_bytes_per_second'),120) - self.assertEqual(result['collection']['interval_seconds'],4) + self.assertEqual(result['collection']['interval_seconds'],1) self.assertTrue(result['alerts_complete']) def test_collection_minimum_and_slower_device(self): - self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':1}}),4) + self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':1}}),1) self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':5}}),5) - self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':True}}),4) + self.assertEqual(worker.collection_interval({'collection':{'interval_seconds':True}}),1) def test_absent_or_boolean_fields_not_invented(self): value={'system': {'cpu_percent': True}, 'machine':{}, 'network': {'link_speed_mbps': None, 'rx_bytes_per_second':float('nan'), 'tx_bytes_per_second':True}}