@bluemanta/portdash 0.1.0 → 0.1.1
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/package.json +18 -3
- package/portdash.js +45 -14
package/package.json
CHANGED
|
@@ -1,8 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bluemanta/portdash",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A visual control panel for local dev servers — see every listening port, control it, and freeze (not kill) runaway memory before it takes the machine down.",
|
|
5
|
-
"keywords": [
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dev-server",
|
|
7
|
+
"localhost",
|
|
8
|
+
"ports",
|
|
9
|
+
"process-manager",
|
|
10
|
+
"dashboard",
|
|
11
|
+
"cli",
|
|
12
|
+
"memory",
|
|
13
|
+
"watchdog"
|
|
14
|
+
],
|
|
6
15
|
"homepage": "https://github.com/bluemanta/portdash",
|
|
7
16
|
"bugs": "https://github.com/bluemanta/portdash/issues",
|
|
8
17
|
"repository": {
|
|
@@ -15,6 +24,9 @@
|
|
|
15
24
|
"bin": {
|
|
16
25
|
"portdash": "portdash.js"
|
|
17
26
|
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"start": "node portdash.js"
|
|
29
|
+
},
|
|
18
30
|
"files": [
|
|
19
31
|
"portdash.js",
|
|
20
32
|
"README.md",
|
|
@@ -24,5 +36,8 @@
|
|
|
24
36
|
"engines": {
|
|
25
37
|
"node": ">=16"
|
|
26
38
|
},
|
|
27
|
-
"os": [
|
|
39
|
+
"os": [
|
|
40
|
+
"darwin",
|
|
41
|
+
"linux"
|
|
42
|
+
]
|
|
28
43
|
}
|
package/portdash.js
CHANGED
|
@@ -278,6 +278,19 @@ function sysMem() {
|
|
|
278
278
|
|
|
279
279
|
const alive = (pid) => { try { process.kill(pid, 0); return true; } catch (e) { return false; } };
|
|
280
280
|
|
|
281
|
+
// System daemons and sandboxed GUI apps report a cwd of "/" or somewhere under
|
|
282
|
+
// ~/Library — registering those would just put a junk entry in the registry, so
|
|
283
|
+
// don't offer it. Checked on the server too, not just hidden in the UI.
|
|
284
|
+
const SYS_PREFIXES = ['/System', '/Library', '/usr', '/bin', '/sbin', '/opt', '/Applications', '/private'];
|
|
285
|
+
function registrable(cwd) {
|
|
286
|
+
if (!cwd || cwd === '/' || cwd === HOME) return false;
|
|
287
|
+
if (!path.basename(cwd)) return false;
|
|
288
|
+
if (SYS_PREFIXES.some((p) => cwd === p || cwd.startsWith(p + '/'))) return false;
|
|
289
|
+
const lib = path.join(HOME, 'Library');
|
|
290
|
+
if (cwd === lib || cwd.startsWith(lib + '/')) return false;
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
281
294
|
// ---------------------------------------------------------------- state aggregation
|
|
282
295
|
|
|
283
296
|
function buildState() {
|
|
@@ -323,18 +336,34 @@ function buildState() {
|
|
|
323
336
|
});
|
|
324
337
|
});
|
|
325
338
|
|
|
326
|
-
|
|
339
|
+
// Group unclaimed listeners by process group, the same way projects are grouped —
|
|
340
|
+
// one row per process, ports collected, so a process on several ports isn't listed
|
|
341
|
+
// repeatedly with its group memory counted once per port.
|
|
342
|
+
const otherByPgid = new Map();
|
|
343
|
+
for (const r of L) {
|
|
344
|
+
if (claimed.has(r.pid) || r.pid === process.pid) continue;
|
|
327
345
|
const info = byPid[r.pid] || {};
|
|
328
|
-
const
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
346
|
+
const pgid = info.pgid || r.pid;
|
|
347
|
+
let row = otherByPgid.get(pgid);
|
|
348
|
+
if (!row) {
|
|
349
|
+
const exe = (info.command || '').trim().split(/\s+/)[0];
|
|
350
|
+
const cwd = C[r.pid] || '';
|
|
351
|
+
row = {
|
|
352
|
+
pid: r.pid, pgid, ports: [],
|
|
353
|
+
command: exe ? path.basename(exe) : r.command,
|
|
354
|
+
cmdline: info.command || '', etime: info.etime || '',
|
|
355
|
+
rssMB: Math.round(rssByPgid[pgid] || info.rssMB || 0),
|
|
356
|
+
cwd, cwdShort: cwd ? shorten(cwd) : '',
|
|
357
|
+
registrable: registrable(cwd),
|
|
358
|
+
paused: !!(info.stat && info.stat.startsWith('T'))
|
|
359
|
+
};
|
|
360
|
+
otherByPgid.set(pgid, row);
|
|
361
|
+
}
|
|
362
|
+
if (!row.ports.includes(r.port)) row.ports.push(r.port);
|
|
363
|
+
}
|
|
364
|
+
const others = [...otherByPgid.values()]
|
|
365
|
+
.map((o) => { o.ports.sort((a, b) => a - b); return o; })
|
|
366
|
+
.sort((a, b) => a.ports[0] - b.ports[0]);
|
|
338
367
|
|
|
339
368
|
return { projects, others, alerts, sys: sysMem(), limits: cfg.limits, now: Date.now() };
|
|
340
369
|
}
|
|
@@ -593,6 +622,7 @@ const server = http.createServer(async (req, res) => {
|
|
|
593
622
|
}
|
|
594
623
|
if (u.pathname === '/api/register') {
|
|
595
624
|
if (!body.cwd) throw new Error("Couldn't determine this process's working directory, can't register it");
|
|
625
|
+
if (!registrable(body.cwd)) throw new Error(`${body.cwd} doesn't look like a project directory — it's a system or sandboxed-app path`);
|
|
596
626
|
const reg = getReg();
|
|
597
627
|
if (reg.some((x) => x.cwd === body.cwd)) throw new Error('This directory is already registered');
|
|
598
628
|
const d = detectProject(body.cwd) || { name: path.basename(body.cwd), cmd: '', kind: 'unknown' };
|
|
@@ -789,12 +819,13 @@ function projectRow(p){
|
|
|
789
819
|
}
|
|
790
820
|
|
|
791
821
|
function otherRow(o){
|
|
792
|
-
|
|
822
|
+
const ports=o.ports.map(x=>'<span class="tag port">:'+x+'</span>').join('');
|
|
823
|
+
let acts='<button data-act="open" data-port="'+o.ports[0]+'">Open</button>'
|
|
793
824
|
+'<button data-act="'+(o.paused?'resume':'pause')+'" data-pid="'+o.pid+'">'+(o.paused?'Resume':'Pause')+'</button>'
|
|
794
825
|
+'<button class="d" data-act="stop" data-pid="'+o.pid+'">Stop</button>';
|
|
795
|
-
if(o.
|
|
826
|
+
if(o.registrable) acts+='<button data-act="register" data-cwd="'+esc(o.cwd)+'" data-port="'+o.ports[0]+'">Register</button>';
|
|
796
827
|
return '<div class="row"><span class="dot '+(o.paused?'paused':'running')+'"></span><div class="main">'
|
|
797
|
-
+'<div class="nm">'+esc(o.command)+
|
|
828
|
+
+'<div class="nm">'+esc(o.command)+ports
|
|
798
829
|
+'<span class="tag">pid '+o.pid+'</span>'+(o.rssMB?'<span class="tag mem">'+gb(o.rssMB)+'</span>':'')+'</div>'
|
|
799
830
|
+'<div class="meta">'+esc(o.cwdShort||o.cmdline||'')+'</div></div>'
|
|
800
831
|
+'<div class="acts">'+acts+'</div></div>';
|