slash-port 0.3.0 → 0.4.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 +37 -4
- package/dist/ui/App.js +60 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,16 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="https://github.com/Slash-ui">
|
|
3
|
+
<picture>
|
|
4
|
+
<source media="(prefers-color-scheme: dark)" srcset="docs/assets/logo-slashui-dark.svg" />
|
|
5
|
+
<img src="docs/assets/logo-slashui-light.svg" alt="Slash UI" width="159" height="65" />
|
|
6
|
+
</picture>
|
|
7
|
+
</a>
|
|
8
|
+
</p>
|
|
9
|
+
|
|
1
10
|
# slash-port
|
|
2
11
|
|
|
3
|
-
<!-- release:badge -->
|
|
12
|
+
<!-- release:badge -->
|
|
13
|
+
[](https://github.com/Slash-ui/slash-port/releases/tag/v0.4.0)<!-- /release:badge -->
|
|
4
14
|
[](https://www.npmjs.com/package/slash-port)
|
|
5
15
|
[](https://www.npmjs.com/package/slash-port)
|
|
6
16
|
[](https://github.com/Slash-ui/slash-port/actions/workflows/ci.yml)
|
|
@@ -91,9 +101,8 @@ Requires Node 22 or newer. Works on Linux, macOS, and Windows.
|
|
|
91
101
|
|
|
92
102
|
### Updating
|
|
93
103
|
|
|
94
|
-
The current release is
|
|
95
|
-
|
|
96
|
-
and what is published:
|
|
104
|
+
The current release is <!-- release:version -->0.4.0<!-- /release:version -->. To
|
|
105
|
+
see what you have, and what is published:
|
|
97
106
|
|
|
98
107
|
```sh
|
|
99
108
|
slash-port --version # the one you are running
|
|
@@ -162,6 +171,20 @@ at any time, and `d` hides the panel in either.
|
|
|
162
171
|
| Confirmation | Says what closing it costs and how to undo it | Names the signal |
|
|
163
172
|
| Cost | One scan | One scan, plus a lookup for the row under the cursor |
|
|
164
173
|
|
|
174
|
+
Both modes on a working machine, where the list is forty-two ports long
|
|
175
|
+
rather than the tidy eight above:
|
|
176
|
+
|
|
177
|
+

|
|
182
|
+
|
|
183
|
+

|
|
187
|
+
|
|
165
188
|
Advanced mode's extra facts are fetched for the selected row only. A machine
|
|
166
189
|
with four hundred listening sockets would otherwise pay four hundred times over
|
|
167
190
|
for facts you are reading one row at a time.
|
|
@@ -341,6 +364,16 @@ are fixed rather than configurable:
|
|
|
341
364
|
- **A process that has already exited is never signalled**, because by then its
|
|
342
365
|
pid may belong to something else.
|
|
343
366
|
|
|
367
|
+
The confirmation for a row that is safe to close names the process, says what
|
|
368
|
+
closing it costs, and keeps the polite close and the forced one on separate
|
|
369
|
+
keys:
|
|
370
|
+
|
|
371
|
+

|
|
376
|
+
|
|
344
377
|
On Windows there is no signal delivery: SIGTERM becomes `TerminateProcess`,
|
|
345
378
|
which a process cannot catch or ignore, so nothing there gets the chance to
|
|
346
379
|
shut down cleanly. The confirmation still applies - but "terminate" and "force"
|
package/dist/ui/App.js
CHANGED
|
@@ -7,7 +7,7 @@ import { ABSENT, formatAddresses, formatDescription, formatPid, formatPort, form
|
|
|
7
7
|
import { inspectProcess } from '../inspect.js';
|
|
8
8
|
import { killEntry } from '../kill.js';
|
|
9
9
|
import { scan } from '../scan/index.js';
|
|
10
|
-
import { cell, color, layout, truncate } from './theme.js';
|
|
10
|
+
import { cell, color, displayWidth, layout, truncate } from './theme.js';
|
|
11
11
|
/**
|
|
12
12
|
* Every row the interface spends on something other than the list, and what it
|
|
13
13
|
* spends them on. These have to be exact rather than generous: the viewport is
|
|
@@ -337,9 +337,65 @@ export function App({ initialEntries, initialFilter = '', udp: initialUdp = fals
|
|
|
337
337
|
address: formatAddresses(entry),
|
|
338
338
|
});
|
|
339
339
|
return (_jsxs(Box, { children: [_jsxs(Text, { inverse: isSelected, color: rowColor, children: [left, ' '] }), _jsx(Text, { inverse: isSelected, color: rowColor ?? color('accent'), children: cell(formatDescription(entry), columnLayout.description) }), columnLayout.url > 0 && (_jsxs(Text, { inverse: isSelected, color: rowColor ?? color('heading'), children: [' ', cell(entry.url ?? ABSENT, columnLayout.url)] })), columnLayout.risk > 0 && (_jsxs(Text, { inverse: isSelected, color: color(RISK_COLOR[entry.risk]), children: [' ', cell(riskWord(entry.risk), columnLayout.risk)] }))] }, entry.id));
|
|
340
|
-
}), !loading && entries.length === 0 && (_jsx(Text, { color: color('muted'), children: truncate(`Nothing is listening${udp ? '' : ' on TCP'}. Press u to include UDP, r to rescan.`, columnLayout.total) })), !loading && entries.length > 0 && visible.length === 0 && (_jsx(Text, { color: color('muted'), children: truncate(`No port matches “${filter}”. Press Esc to clear the filter.`, columnLayout.total) })), showPanel && current && !dialog && (_jsx(DetailPanel, { entry: current, mode: mode, detail: detail, docker: docker, width: columnLayout.total })), dialog && (_jsx(ConfirmDialog, { entry: dialog, mode: mode, compact: compact, width: columnLayout.total })), !cramped && filtering && (_jsxs(Text, { children: [_jsx(Text, { color: color('heading'), children: "filter " }), _jsx(Text, { children: truncate(filter, Math.max(8, columnLayout.total - 8)) }), _jsx(Text, { color: color('muted'), children: "\u258E" })] })), !cramped && !filtering && filter !== '' && (_jsxs(Text, { color: color('muted'), children: ["filter: ", truncate(filter, Math.max(8, columnLayout.total - 10))] })), !cramped && status && (_jsx(Text, { color: color(STATUS_COLOR[status.kind]), children: truncate(status.text, columnLayout.total) })), !dialog &&
|
|
341
|
-
|
|
342
|
-
|
|
340
|
+
}), !loading && entries.length === 0 && (_jsx(Text, { color: color('muted'), children: truncate(`Nothing is listening${udp ? '' : ' on TCP'}. Press u to include UDP, r to rescan.`, columnLayout.total) })), !loading && entries.length > 0 && visible.length === 0 && (_jsx(Text, { color: color('muted'), children: truncate(`No port matches “${filter}”. Press Esc to clear the filter.`, columnLayout.total) })), showPanel && current && !dialog && (_jsx(DetailPanel, { entry: current, mode: mode, detail: detail, docker: docker, width: columnLayout.total })), dialog && (_jsx(ConfirmDialog, { entry: dialog, mode: mode, compact: compact, width: columnLayout.total })), !cramped && filtering && (_jsxs(Text, { children: [_jsx(Text, { color: color('heading'), children: "filter " }), _jsx(Text, { children: truncate(filter, Math.max(8, columnLayout.total - 8)) }), _jsx(Text, { color: color('muted'), children: "\u258E" })] })), !cramped && !filtering && filter !== '' && (_jsxs(Text, { color: color('muted'), children: ["filter: ", truncate(filter, Math.max(8, columnLayout.total - 10))] })), !cramped && status && (_jsx(Text, { color: color(STATUS_COLOR[status.kind]), children: truncate(status.text, columnLayout.total) })), !dialog && _jsx(HelpLine, { mode: mode, width: columnLayout.total })] }));
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* The keys, kept apart from what they do so the key can be given a colour of
|
|
344
|
+
* its own. In one colour the line reads as a sentence and the reader has to
|
|
345
|
+
* pick the keys back out of it, which is the opposite of what a help line is
|
|
346
|
+
* for.
|
|
347
|
+
*/
|
|
348
|
+
const HELP = {
|
|
349
|
+
beginner: [
|
|
350
|
+
['↑↓', 'move'],
|
|
351
|
+
['/', 'find'],
|
|
352
|
+
['x', 'close it'],
|
|
353
|
+
['r', 'refresh'],
|
|
354
|
+
['u', 'udp'],
|
|
355
|
+
['d', 'details'],
|
|
356
|
+
['m', 'advanced'],
|
|
357
|
+
['q', 'quit'],
|
|
358
|
+
],
|
|
359
|
+
advanced: [
|
|
360
|
+
['↑↓/jk', 'move'],
|
|
361
|
+
['PgUp/PgDn/g/G', 'jump'],
|
|
362
|
+
['/', 'filter'],
|
|
363
|
+
['x', 'kill'],
|
|
364
|
+
['r', 'rescan'],
|
|
365
|
+
['u', 'udp'],
|
|
366
|
+
['d', 'detail'],
|
|
367
|
+
['m', 'beginner'],
|
|
368
|
+
['q', 'quit'],
|
|
369
|
+
],
|
|
370
|
+
};
|
|
371
|
+
const HELP_SEPARATOR = ' · ';
|
|
372
|
+
/**
|
|
373
|
+
* Measured in cells and trimmed by dropping whole hints, because half a hint
|
|
374
|
+
* helps nobody and an ellipsis costs about what a key does.
|
|
375
|
+
*
|
|
376
|
+
* `q quit` is kept and the hints before it give way, since the one line a
|
|
377
|
+
* reader goes looking for is the one that tells them how to get out - and it
|
|
378
|
+
* is last, which is exactly where trimming from the end would have taken it.
|
|
379
|
+
*
|
|
380
|
+
* Always renders, even with nothing left to show: the row is one the height
|
|
381
|
+
* arithmetic has already paid for, and giving it back would move the list.
|
|
382
|
+
*/
|
|
383
|
+
function HelpLine({ mode, width }) {
|
|
384
|
+
const hints = HELP[mode];
|
|
385
|
+
const quit = hints[hints.length - 1];
|
|
386
|
+
const gap = displayWidth(HELP_SEPARATOR);
|
|
387
|
+
const cost = ([key, label]) => displayWidth(key) + 1 + displayWidth(label);
|
|
388
|
+
const shown = [];
|
|
389
|
+
let used = cost(quit);
|
|
390
|
+
for (const hint of hints.slice(0, -1)) {
|
|
391
|
+
if (used + gap + cost(hint) > width)
|
|
392
|
+
break;
|
|
393
|
+
shown.push(hint);
|
|
394
|
+
used += gap + cost(hint);
|
|
395
|
+
}
|
|
396
|
+
if (used <= width)
|
|
397
|
+
shown.push(quit);
|
|
398
|
+
return (_jsx(Text, { color: color('muted'), children: shown.map(([key, label], index) => (_jsxs(Text, { children: [index === 0 ? '' : HELP_SEPARATOR, _jsx(Text, { color: color('heading'), children: key }), " ", label] }, key))) }));
|
|
343
399
|
}
|
|
344
400
|
/** A `label value` line, with the labels aligned down the panel. */
|
|
345
401
|
function FieldLines({ fields, width }) {
|