pyre-cli 10.1.0 → 10.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +211 -494
- package/dist/{checkCmd-RKW6QGDR.js → checkCmd-WQAHAUWO.js} +1 -1
- package/dist/{chunk-GHRDW646.js → chunk-23YWCI3O.js} +375 -135
- package/dist/{service-Q74BBILO.js → chunk-KFPTD266.js} +43 -3
- package/dist/{doctor-GYHFTNZQ.js → chunk-KVUMOA6C.js} +7 -6
- package/dist/{chunk-53O2RGSL.js → chunk-QM5L43EK.js} +19 -2
- package/dist/{chunk-6UHZP4QP.js → chunk-XTOOEKLM.js} +24 -12
- package/dist/{collectors-H43PN4PS.js → collectors-JDN5Y7EV.js} +1 -1
- package/dist/{completions-JKYEJNGR.js → completions-ZVTNE52B.js} +15 -11
- package/dist/{config-GYGJXU2Q.js → config-J4YMADEF.js} +3 -1
- package/dist/doctor-LHFW4UJP.js +10 -0
- package/dist/firebaseSync-ON3RZ6KX.js +91 -0
- package/dist/index.js +4245 -736
- package/dist/monitors/platform/windows-ui.ps1 +42 -0
- package/dist/{netUsageCmd-WKBTW2YG.js → netUsageCmd-67IBZZYN.js} +2 -2
- package/dist/{pipeCmd-7REDBLBU.js → pipeCmd-2N4JPKYE.js} +1 -1
- package/dist/{prometheusCmd-PL64BLN5.js → prometheusCmd-LEUM5WCZ.js} +1 -1
- package/dist/serveCmd-L7LVJOZN.js +353 -0
- package/dist/service-7GIX3YNC.js +16 -0
- package/dist/syncCmd-42IRF633.js +246 -0
- package/dist/{topoCmd-DU2HHZNH.js → topoCmd-DZ3YS5IL.js} +1 -1
- package/dist/{watchdogCmd-UF53JPZB.js → watchdogCmd-6GR4UN6B.js} +7 -7
- package/dist/{wizard-TD7BGX6O.js → wizard-JQMQWFAW.js} +1 -1
- package/package.json +4 -3
- package/parity.md +2 -0
- package/src/completions.ts +15 -11
- package/src/easy_features.test.ts +149 -0
- package/src/firebaseSync.ts +157 -0
- package/src/formatters/html_template.ts +3086 -598
- package/src/formatters/render.ts +25 -12
- package/src/index.ts +518 -24
- package/src/live/input.ts +501 -148
- package/src/live/render.ts +163 -18
- package/src/live/state.ts +52 -2
- package/src/live/types.ts +1 -1
- package/src/monitors/collectors.ts +3 -10
- package/src/monitors/platform/windows-ui.ps1 +42 -0
- package/src/monitors/platform/windows.ts +442 -147
- package/src/monitors/types.ts +8 -0
- package/src/serveCmd.ts +17 -2
- package/src/service.ts +49 -0
- package/src/state/config.ts +48 -0
- package/src/syncCmd.ts +159 -11
- package/src/web/dashboard.ts +205 -0
- package/dist/serveCmd-2H5VLF3P.js +0 -153
- package/dist/syncCmd-A2W3QB3S.js +0 -126
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<#
|
|
2
|
+
.SYNOPSIS
|
|
3
|
+
Launches a native Pyre UI window on Windows using WPF WebBrowser control.
|
|
4
|
+
#>
|
|
5
|
+
param(
|
|
6
|
+
[Parameter(Mandatory=$true)]
|
|
7
|
+
[int]$Port
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = 'Stop'
|
|
11
|
+
|
|
12
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
13
|
+
Add-Type -AssemblyName System.Drawing
|
|
14
|
+
Add-Type -AssemblyName PresentationFramework
|
|
15
|
+
|
|
16
|
+
$regPath = "HKCU:\Software\Microsoft\Internet Explorer\Main\FeatureControl\FEATURE_BROWSER_EMULATION"
|
|
17
|
+
if (-not (Test-Path $regPath)) {
|
|
18
|
+
New-Item -Path $regPath -Force | Out-Null
|
|
19
|
+
}
|
|
20
|
+
$scriptName = [System.IO.Path]::GetFileName($PSCommandPath)
|
|
21
|
+
Set-ItemProperty -Path $regPath -Name $scriptName -Value 11001 -Type DWord -ErrorAction SilentlyContinue
|
|
22
|
+
|
|
23
|
+
$form = New-Object System.Windows.Forms.Form
|
|
24
|
+
$form.Text = "Pyre Live Dashboard"
|
|
25
|
+
$form.Width = 1200
|
|
26
|
+
$form.Height = 800
|
|
27
|
+
$form.StartPosition = "CenterScreen"
|
|
28
|
+
$form.MinimumSize = New-Object System.Drawing.Size(860, 600)
|
|
29
|
+
$form.BackColor = [System.Drawing.Color]::FromArgb(10, 10, 15)
|
|
30
|
+
$form.ForeColor = [System.Drawing.Color]::FromArgb(245, 245, 247)
|
|
31
|
+
|
|
32
|
+
$browser = New-Object System.Windows.Forms.WebBrowser
|
|
33
|
+
$browser.Dock = "Fill"
|
|
34
|
+
$browser.ScriptErrorsSuppressed = $true
|
|
35
|
+
$browser.Navigate("http://localhost:$Port/")
|
|
36
|
+
$form.Controls.Add($browser)
|
|
37
|
+
|
|
38
|
+
$form.Add_Shown({
|
|
39
|
+
$form.Activate()
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
$form.ShowDialog()
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatBytes
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-XTOOEKLM.js";
|
|
4
4
|
import "./chunk-ZLVIVFSO.js";
|
|
5
5
|
import {
|
|
6
6
|
collectPackets
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-23YWCI3O.js";
|
|
8
8
|
import "./chunk-M73HCQD2.js";
|
|
9
9
|
import "./chunk-VIACN2RU.js";
|
|
10
10
|
import "./chunk-MZO5QKUR.js";
|
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
import {
|
|
2
|
+
collectAll,
|
|
3
|
+
collectBattery,
|
|
4
|
+
collectContainers,
|
|
5
|
+
collectCpu,
|
|
6
|
+
collectDisk,
|
|
7
|
+
collectGpu,
|
|
8
|
+
collectMemory,
|
|
9
|
+
collectNetwork,
|
|
10
|
+
collectProcesses,
|
|
11
|
+
collectSystem,
|
|
12
|
+
collectThermal
|
|
13
|
+
} from "./chunk-23YWCI3O.js";
|
|
14
|
+
import "./chunk-M73HCQD2.js";
|
|
15
|
+
import "./chunk-VIACN2RU.js";
|
|
16
|
+
import "./chunk-MZO5QKUR.js";
|
|
17
|
+
|
|
18
|
+
// src/serveCmd.ts
|
|
19
|
+
import http from "http";
|
|
20
|
+
import os from "os";
|
|
21
|
+
import chalk from "chalk";
|
|
22
|
+
|
|
23
|
+
// src/web/dashboard.ts
|
|
24
|
+
var DASHBOARD_HTML = (apiUrl, apiKey) => {
|
|
25
|
+
const authHeader = apiKey ? `"x-api-key": "${apiKey}",
|
|
26
|
+
` : "";
|
|
27
|
+
return `<!DOCTYPE html>
|
|
28
|
+
<html lang="en">
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="UTF-8"/>
|
|
31
|
+
<meta name="viewport" content="width=device-width,initial-scale=1.0"/>
|
|
32
|
+
<title>Pyre \u2014 Live Dashboard</title>
|
|
33
|
+
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.4/dist/chart.umd.min.js"></script>
|
|
34
|
+
<style>
|
|
35
|
+
*,*::before,*::after{box-sizing:border-box;margin:0;padding:0}
|
|
36
|
+
:root{
|
|
37
|
+
--bg:#0a0a0f;--surface:#141418;--border:rgba(255,255,255,.08);
|
|
38
|
+
--text:#f5f5f7;--muted:#a1a1a6;--accent:#ff5500;
|
|
39
|
+
--cpu:#00d4ff;--mem:#ff9f0a;--gpu:#af52de;--thm:#ff453a;
|
|
40
|
+
--net:#34c759;--pwr:#ffcc02;--bat:#64d2ff;
|
|
41
|
+
--ok:#34c759;--warn:#ff9f0a;--crit:#ff453a;
|
|
42
|
+
}
|
|
43
|
+
html,body{height:100%;background:var(--bg);color:var(--text);
|
|
44
|
+
font-family:-apple-system,BlinkMacSystemFont,"SF Pro Display","SF Pro Text","Helvetica Neue",Helvetica,Arial,sans-serif;
|
|
45
|
+
-webkit-font-smoothing:antialiased;overflow-x:hidden}
|
|
46
|
+
header{position:sticky;top:0;z-index:10;height:48px;background:rgba(10,10,15,.9);
|
|
47
|
+
backdrop-filter:saturate(180%) blur(20px);border-bottom:1px solid var(--border);
|
|
48
|
+
display:flex;align-items:center;padding:0 20px;gap:16px}
|
|
49
|
+
.logo{font-weight:700;font-size:15px;color:var(--accent);letter-spacing:-.01em}
|
|
50
|
+
.hostname{font-size:13px;color:var(--muted)}
|
|
51
|
+
.status{margin-left:auto;display:flex;gap:12px;align-items:center}
|
|
52
|
+
.badge{padding:3px 10px;border-radius:980px;font-size:11px;font-weight:600;font-family:ui-monospace,monospace}
|
|
53
|
+
.badge-live{background:rgba(52,199,89,.15);color:var(--ok);border:1px solid rgba(52,199,89,.3)}
|
|
54
|
+
.badge-err{background:rgba(255,69,58,.15);color:var(--crit);border:1px solid rgba(255,69,58,.3)}
|
|
55
|
+
.refresh{font-size:11px;color:var(--muted);font-family:ui-monospace,monospace}
|
|
56
|
+
main{padding:20px;display:grid;gap:16px;
|
|
57
|
+
grid-template-columns:repeat(auto-fit,minmax(340px,1fr));
|
|
58
|
+
max-width:1600px;margin:0 auto}
|
|
59
|
+
.card{background:var(--surface);border:1px solid var(--border);border-radius:16px;
|
|
60
|
+
padding:16px 18px;display:flex;flex-direction:column;min-height:200px}
|
|
61
|
+
.card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
|
|
62
|
+
.card-title{font-size:13px;font-weight:600;color:var(--muted);letter-spacing:-.01em;text-transform:uppercase}
|
|
63
|
+
.card-value{font-size:24px;font-weight:700;font-family:ui-monospace,monospace;letter-spacing:-.02em}
|
|
64
|
+
.card-sub{font-size:11px;color:var(--muted);margin-top:2px;font-family:ui-monospace,monospace}
|
|
65
|
+
.chart-wrap{flex:1;position:relative;min-height:80px;margin-top:8px}
|
|
66
|
+
canvas{width:100%!important;height:100%!important}
|
|
67
|
+
.summary{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin-bottom:16px}
|
|
68
|
+
.stat{background:var(--surface);border:1px solid var(--border);border-radius:12px;padding:12px 14px}
|
|
69
|
+
.stat-label{font-size:11px;color:var(--muted);text-transform:uppercase;font-weight:600;letter-spacing:-.01em}
|
|
70
|
+
.stat-val{font-size:20px;font-weight:700;font-family:ui-monospace,monospace;margin-top:2px}
|
|
71
|
+
.footer{text-align:center;padding:16px;font-size:11px;color:var(--muted)}
|
|
72
|
+
@media(max-width:768px){main{grid-template-columns:1fr}.summary{grid-template-columns:repeat(2,1fr)}}
|
|
73
|
+
</style>
|
|
74
|
+
</head>
|
|
75
|
+
<body>
|
|
76
|
+
<header>
|
|
77
|
+
<div class="logo">\u{1F525} Pyre</div>
|
|
78
|
+
<div class="hostname" id="hostname">loading\u2026</div>
|
|
79
|
+
<div class="status">
|
|
80
|
+
<div class="refresh" id="refresh">refreshing 2s</div>
|
|
81
|
+
<div class="badge badge-live" id="liveBadge">\u25CF LIVE</div>
|
|
82
|
+
</div>
|
|
83
|
+
</header>
|
|
84
|
+
<main>
|
|
85
|
+
<div class="summary" id="summary"></div>
|
|
86
|
+
<div class="card">
|
|
87
|
+
<div class="card-header">
|
|
88
|
+
<div class="card-title" style="color:var(--cpu)">CPU Usage %</div>
|
|
89
|
+
<div class="card-value" id="cpuVal" style="color:var(--cpu)">--</div>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="chart-wrap"><canvas id="cpuChart"></canvas></div>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="card">
|
|
94
|
+
<div class="card-header">
|
|
95
|
+
<div class="card-title" style="color:var(--mem)">Memory Usage %</div>
|
|
96
|
+
<div class="card-value" id="memVal" style="color:var(--mem)">--</div>
|
|
97
|
+
</div>
|
|
98
|
+
<div class="chart-wrap"><canvas id="memChart"></canvas></div>
|
|
99
|
+
</div>
|
|
100
|
+
<div class="card">
|
|
101
|
+
<div class="card-header">
|
|
102
|
+
<div class="card-title" style="color:var(--thm)">Temperature \xB0C</div>
|
|
103
|
+
<div class="card-value" id="tempVal" style="color:var(--thm)">--</div>
|
|
104
|
+
</div>
|
|
105
|
+
<div class="chart-wrap"><canvas id="tempChart"></canvas></div>
|
|
106
|
+
</div>
|
|
107
|
+
<div class="card">
|
|
108
|
+
<div class="card-header">
|
|
109
|
+
<div class="card-title" style="color:var(--net)">Network RX/TX MB/s</div>
|
|
110
|
+
<div class="card-value" id="netVal" style="color:var(--net)">--</div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="chart-wrap"><canvas id="netChart"></canvas></div>
|
|
113
|
+
</div>
|
|
114
|
+
<div class="card">
|
|
115
|
+
<div class="card-header">
|
|
116
|
+
<div class="card-title" style="color:var(--pwr)">Power Draw (W)</div>
|
|
117
|
+
<div class="card-value" id="pwrVal" style="color:var(--pwr)">--</div>
|
|
118
|
+
</div>
|
|
119
|
+
<div class="chart-wrap"><canvas id="pwrChart"></canvas></div>
|
|
120
|
+
</div>
|
|
121
|
+
</main>
|
|
122
|
+
<div class="footer">Pyre live dashboard \xB7 auto-refresh every 2s \xB7 <span id="lastUpdate">--</span></div>
|
|
123
|
+
<script>
|
|
124
|
+
const MAX_POINTS=60;
|
|
125
|
+
const history={cpu:[],mem:[],temp:[],netRx:[],netTx:[],pwr:[],labels:[]};
|
|
126
|
+
const chartOpts=(color,label,unit='')=>({
|
|
127
|
+
type:'line',data:{labels:history.labels,datasets:[{
|
|
128
|
+
label:label,data:[],borderColor:color,backgroundColor:color+'22',
|
|
129
|
+
borderWidth:2,pointRadius:0,tension:.4,fill:true
|
|
130
|
+
}]},
|
|
131
|
+
options:{responsive:true,maintainAspectRatio:false,animation:false,
|
|
132
|
+
scales:{x:{display:false},y:{beginAtZero:true,grid:{color:'rgba(255,255,255,.05)'},
|
|
133
|
+
ticks:{color:'#a1a1a6',font:{size:10},callback:v=>v+unit}}},
|
|
134
|
+
plugins:{legend:{display:false}},
|
|
135
|
+
interaction:{intersect:false,mode:'index'}}
|
|
136
|
+
});
|
|
137
|
+
const charts={
|
|
138
|
+
cpu:new Chart(document.getElementById('cpuChart'),chartOpts('#00d4ff','CPU %','%')),
|
|
139
|
+
mem:new Chart(document.getElementById('memChart'),chartOpts('#ff9f0a','Memory %','%')),
|
|
140
|
+
temp:new Chart(document.getElementById('tempChart'),chartOpts('#ff453a','Temp','\xB0C')),
|
|
141
|
+
net:new Chart(document.getElementById('netChart'),chartOpts('#34c759','Net','MB/s')),
|
|
142
|
+
pwr:new Chart(document.getElementById('pwrChart'),chartOpts('#ffcc02','Power','W')),
|
|
143
|
+
};
|
|
144
|
+
function fmtTime(d){return d.toLocaleTimeString([],{hour:'2-digit',minute:'2-digit',second:'2-digit'})}
|
|
145
|
+
function fmtBytes(b){if(!b)return'0';if(b<1024)return b+'B';if(b<1048576)return(b/1024).toFixed(1)+'KB';return(b/1048576).toFixed(1)+'MB'}
|
|
146
|
+
const AUTH_HEADERS = { ${authHeader} };
|
|
147
|
+
async function fetchData(){
|
|
148
|
+
try{
|
|
149
|
+
const r=await fetch('${apiUrl}',{headers:AUTH_HEADERS});
|
|
150
|
+
if(!r.ok)throw new Error(r.status);
|
|
151
|
+
const d=await r.json();
|
|
152
|
+
document.getElementById('hostname').textContent=d.header?.hostname||'pyre';
|
|
153
|
+
document.getElementById('lastUpdate').textContent='updated '+fmtTime(new Date());
|
|
154
|
+
const cpu=d.cpu?.usage??0,mem=d.memory?.usagePercent??0;
|
|
155
|
+
const temp=d.cpu?.temperature??d.thermal?.temperatures?.cpu_die??null;
|
|
156
|
+
const rxRate=d.network?.rxRate??(d.network?.rxBytes?0:0);
|
|
157
|
+
const txRate=d.network?.txRate??(d.network?.txBytes?0:0);
|
|
158
|
+
const pwr=d.power?.combinedWatts??d.power?.cpuWatts??0;
|
|
159
|
+
const now=fmtTime(new Date());
|
|
160
|
+
history.labels.push(now);
|
|
161
|
+
history.cpu.push(cpu);history.mem.push(mem);
|
|
162
|
+
history.temp.push(temp);history.netRx.push(rxRate/1048576);history.netTx.push(txRate/1048576);
|
|
163
|
+
history.pwr.push(pwr);
|
|
164
|
+
if(history.labels.length>MAX_POINTS){
|
|
165
|
+
history.labels.shift();history.cpu.shift();history.mem.shift();
|
|
166
|
+
history.temp.shift();history.netRx.shift();history.netTx.shift();history.pwr.shift();
|
|
167
|
+
}
|
|
168
|
+
function updChart(chart,data,color){
|
|
169
|
+
chart.data.labels=history.labels;
|
|
170
|
+
chart.data.datasets[0].data=data;
|
|
171
|
+
chart.update('none');
|
|
172
|
+
}
|
|
173
|
+
updChart(charts.cpu,history.cpu,'#00d4ff');
|
|
174
|
+
updChart(charts.mem,history.mem,'#ff9f0a');
|
|
175
|
+
const tempData=history.temp.map(v=>v===null?null:v);
|
|
176
|
+
updChart(charts.temp,tempData,'#ff453a');
|
|
177
|
+
const netData=history.netRx.map((r,i)=>r+history.netTx[i]);
|
|
178
|
+
updChart(charts.net,netData,'#34c759');
|
|
179
|
+
updChart(charts.pwr,history.pwr,'#ffcc02');
|
|
180
|
+
document.getElementById('cpuVal').textContent=cpu.toFixed(1)+'%';
|
|
181
|
+
document.getElementById('memVal').textContent=mem.toFixed(1)+'%';
|
|
182
|
+
document.getElementById('tempVal').textContent=temp!==null?temp.toFixed(1)+'\xB0C':'--';
|
|
183
|
+
document.getElementById('netVal').textContent=fmtBytes(rxRate+txRate)+'/s';
|
|
184
|
+
document.getElementById('pwrVal').textContent=pwr.toFixed(1)+'W';
|
|
185
|
+
const summary=document.getElementById('summary');
|
|
186
|
+
summary.innerHTML=
|
|
187
|
+
'<div class="stat"><div class="stat-label">CPU</div><div class="stat-val" style="color:var(--cpu)">'+cpu.toFixed(1)+'%</div></div>'+
|
|
188
|
+
'<div class="stat"><div class="stat-label">Memory</div><div class="stat-val" style="color:var(--mem)">'+mem.toFixed(1)+'%</div></div>'+
|
|
189
|
+
'<div class="stat"><div class="stat-label">Temp</div><div class="stat-val" style="color:var(--thm)">'+(temp!==null?temp.toFixed(1)+'\xB0C':'--')+'</div></div>'+
|
|
190
|
+
'<div class="stat"><div class="stat-label">Network</div><div class="stat-val" style="color:var(--net)">'+fmtBytes(d.network?.rxBytes??0)+' \u2193</div></div>'+
|
|
191
|
+
'<div class="stat"><div class="stat-label">Power</div><div class="stat-val" style="color:var(--pwr)">'+(pwr>0?pwr.toFixed(1)+'W':'--')+'</div></div>';
|
|
192
|
+
const badge=document.getElementById('liveBadge');
|
|
193
|
+
badge.className='badge badge-live';badge.textContent='\u25CF LIVE';
|
|
194
|
+
}catch(e){
|
|
195
|
+
const badge=document.getElementById('liveBadge');
|
|
196
|
+
badge.className='badge badge-err';badge.textContent='\u26A0 ERR';
|
|
197
|
+
console.error('pyre dashboard fetch error:',e);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
fetchData();setInterval(fetchData,2000);
|
|
201
|
+
</script>
|
|
202
|
+
</body>
|
|
203
|
+
</html>`;
|
|
204
|
+
};
|
|
205
|
+
function getWebDashboardHtml(apiUrl = "http://localhost:8080/api/all", apiKey) {
|
|
206
|
+
return DASHBOARD_HTML(apiUrl, apiKey);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// src/serveCmd.ts
|
|
210
|
+
async function runServeCommand(opts = {}) {
|
|
211
|
+
const port = opts.port || 8080;
|
|
212
|
+
const apiKey = opts.apiKey || process.env.PYRE_API_KEY || "";
|
|
213
|
+
const server = http.createServer(async (req, res) => {
|
|
214
|
+
res.setHeader("Access-Control-Allow-Origin", "*");
|
|
215
|
+
res.setHeader("Access-Control-Allow-Methods", "GET, OPTIONS");
|
|
216
|
+
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization, x-api-key");
|
|
217
|
+
if (req.method === "OPTIONS") {
|
|
218
|
+
res.writeHead(204);
|
|
219
|
+
res.end();
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
const urlObj = new URL(req.url || "/", `http://localhost:${port}`);
|
|
223
|
+
const pathname = urlObj.pathname;
|
|
224
|
+
if (apiKey) {
|
|
225
|
+
const authHeader = req.headers["authorization"] || "";
|
|
226
|
+
const xApiKey = req.headers["x-api-key"] || "";
|
|
227
|
+
const queryKey = urlObj.searchParams.get("key") || "";
|
|
228
|
+
const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7) : authHeader;
|
|
229
|
+
if (token !== apiKey && xApiKey !== apiKey && queryKey !== apiKey) {
|
|
230
|
+
res.writeHead(401, { "Content-Type": "application/json" });
|
|
231
|
+
res.end(JSON.stringify({ error: "Unauthorized: Invalid or missing API key" }));
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
let data;
|
|
237
|
+
switch (pathname) {
|
|
238
|
+
case "/":
|
|
239
|
+
case "/dashboard":
|
|
240
|
+
case "/web": {
|
|
241
|
+
const apiBase = `http://${req.headers.host || `localhost:${port}`}/api/all`;
|
|
242
|
+
const html = getWebDashboardHtml(apiBase, apiKey);
|
|
243
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
244
|
+
res.end(html);
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
case "/api/all":
|
|
248
|
+
case "/api/stats":
|
|
249
|
+
case "/api":
|
|
250
|
+
data = await collectAll({ detailed: opts.detailed });
|
|
251
|
+
break;
|
|
252
|
+
case "/api/cpu":
|
|
253
|
+
data = await collectCpu();
|
|
254
|
+
break;
|
|
255
|
+
case "/api/memory":
|
|
256
|
+
case "/api/mem":
|
|
257
|
+
data = await collectMemory();
|
|
258
|
+
break;
|
|
259
|
+
case "/api/gpu":
|
|
260
|
+
data = await collectGpu(opts.detailed);
|
|
261
|
+
break;
|
|
262
|
+
case "/api/battery":
|
|
263
|
+
case "/api/power":
|
|
264
|
+
data = await collectBattery();
|
|
265
|
+
break;
|
|
266
|
+
case "/api/thermal":
|
|
267
|
+
case "/api/temp":
|
|
268
|
+
data = await collectThermal(opts.detailed);
|
|
269
|
+
break;
|
|
270
|
+
case "/api/network":
|
|
271
|
+
case "/api/net":
|
|
272
|
+
data = await collectNetwork();
|
|
273
|
+
break;
|
|
274
|
+
case "/api/disk":
|
|
275
|
+
data = await collectDisk();
|
|
276
|
+
break;
|
|
277
|
+
case "/api/processes":
|
|
278
|
+
case "/api/proc":
|
|
279
|
+
data = await collectProcesses(50);
|
|
280
|
+
break;
|
|
281
|
+
case "/api/containers":
|
|
282
|
+
data = await collectContainers();
|
|
283
|
+
break;
|
|
284
|
+
case "/api/system":
|
|
285
|
+
case "/api/info":
|
|
286
|
+
data = await collectSystem();
|
|
287
|
+
break;
|
|
288
|
+
case "/health":
|
|
289
|
+
case "/api/health":
|
|
290
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
291
|
+
res.end(JSON.stringify({ status: "ok", uptime: os.uptime(), timestamp: (/* @__PURE__ */ new Date()).toISOString() }));
|
|
292
|
+
return;
|
|
293
|
+
default:
|
|
294
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
295
|
+
res.end(JSON.stringify({
|
|
296
|
+
error: "Not found",
|
|
297
|
+
availableEndpoints: [
|
|
298
|
+
"/",
|
|
299
|
+
"/dashboard",
|
|
300
|
+
"/api/all",
|
|
301
|
+
"/api/cpu",
|
|
302
|
+
"/api/memory",
|
|
303
|
+
"/api/gpu",
|
|
304
|
+
"/api/battery",
|
|
305
|
+
"/api/thermal",
|
|
306
|
+
"/api/network",
|
|
307
|
+
"/api/disk",
|
|
308
|
+
"/api/processes",
|
|
309
|
+
"/api/containers",
|
|
310
|
+
"/api/system",
|
|
311
|
+
"/api/health"
|
|
312
|
+
]
|
|
313
|
+
}));
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
317
|
+
res.end(JSON.stringify(data, null, 2));
|
|
318
|
+
} catch (err) {
|
|
319
|
+
res.writeHead(500, { "Content-Type": "application/json" });
|
|
320
|
+
res.end(JSON.stringify({ error: err.message || "Internal metric collection error" }));
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
server.on("error", (err) => {
|
|
324
|
+
if (err.code === "EADDRINUSE") {
|
|
325
|
+
console.error(chalk.red(`
|
|
326
|
+
\u2716 Error: Port ${port} is already in use. Use --port <port> to select an alternate port.
|
|
327
|
+
`));
|
|
328
|
+
} else {
|
|
329
|
+
console.error(chalk.red(`
|
|
330
|
+
\u2716 Server error: ${err.message}
|
|
331
|
+
`));
|
|
332
|
+
}
|
|
333
|
+
process.exit(1);
|
|
334
|
+
});
|
|
335
|
+
await new Promise((resolve) => server.listen(port, "0.0.0.0", resolve));
|
|
336
|
+
console.log(chalk.bold("\n \u{1F525} pyre serve \u2014 REST API + Web Dashboard\n"));
|
|
337
|
+
console.log(` ${chalk.green("\u2714")} API base URL: ${chalk.cyan(`http://0.0.0.0:${port}/api/all`)}`);
|
|
338
|
+
console.log(` ${chalk.green("\u2714")} Dashboard: ${chalk.cyan(`http://0.0.0.0:${port}/`)}`);
|
|
339
|
+
console.log(` ${chalk.green("\u2714")} Health check: ${chalk.cyan(`http://0.0.0.0:${port}/api/health`)}`);
|
|
340
|
+
if (apiKey) {
|
|
341
|
+
console.log(` ${chalk.yellow("\u{1F512}")} API Key protection: ${chalk.bold("ENABLED")} (Pass via header x-api-key or ?key=)`);
|
|
342
|
+
} else {
|
|
343
|
+
console.log(` ${chalk.dim("\u{1F513}")} API Key protection: ${chalk.dim("DISABLED (Public local network access)")}`);
|
|
344
|
+
}
|
|
345
|
+
console.log(`
|
|
346
|
+
${chalk.dim("Endpoints: /api/cpu, /api/memory, /api/gpu, /api/battery, /api/thermal, /api/network, /api/disk, /api/processes")}`);
|
|
347
|
+
console.log(chalk.dim("\n Press Ctrl+C to stop.\n"));
|
|
348
|
+
await new Promise(() => {
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
export {
|
|
352
|
+
runServeCommand
|
|
353
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getServiceStatus,
|
|
3
|
+
installLaunchdAgent,
|
|
4
|
+
installService,
|
|
5
|
+
isServiceInstalled,
|
|
6
|
+
uninstallLaunchdAgent,
|
|
7
|
+
uninstallService
|
|
8
|
+
} from "./chunk-KFPTD266.js";
|
|
9
|
+
export {
|
|
10
|
+
getServiceStatus,
|
|
11
|
+
installLaunchdAgent,
|
|
12
|
+
installService,
|
|
13
|
+
isServiceInstalled,
|
|
14
|
+
uninstallLaunchdAgent,
|
|
15
|
+
uninstallService
|
|
16
|
+
};
|