@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.
Files changed (2) hide show
  1. package/package.json +18 -3
  2. package/portdash.js +45 -14
package/package.json CHANGED
@@ -1,8 +1,17 @@
1
1
  {
2
2
  "name": "@bluemanta/portdash",
3
- "version": "0.1.0",
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": ["dev-server", "localhost", "ports", "process-manager", "dashboard", "cli", "memory", "watchdog"],
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": ["darwin", "linux"]
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
- const others = L.filter((r) => !claimed.has(r.pid) && r.pid !== process.pid).map((r) => {
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 exe = (info.command || '').trim().split(/\s+/)[0];
329
- return {
330
- pid: r.pid, pgid: info.pgid || r.pid, port: r.port,
331
- command: exe ? path.basename(exe) : r.command,
332
- cmdline: info.command || '', etime: info.etime || '',
333
- rssMB: Math.round(rssByPgid[info.pgid] || info.rssMB || 0),
334
- cwd: C[r.pid] || '', cwdShort: C[r.pid] ? shorten(C[r.pid]) : '',
335
- paused: !!(info.stat && info.stat.startsWith('T'))
336
- };
337
- }).sort((a, b) => a.port - b.port);
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
- let acts='<button data-act="open" data-port="'+o.port+'">Open</button>'
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.cwd) acts+='<button data-act="register" data-cwd="'+esc(o.cwd)+'" data-port="'+o.port+'">Register</button>';
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)+'<span class="tag port">:'+o.port+'</span>'
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>';