dockza.app 0.2.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.
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ConfirmDialog = void 0;
7
+ const neo_blessed_1 = __importDefault(require("neo-blessed"));
8
+ const _theme_1 = require("../../theme");
9
+ class ConfirmDialog {
10
+ screen;
11
+ box;
12
+ visible = false;
13
+ onConfirm = null;
14
+ onCancel = null;
15
+ handleConfirm = () => {
16
+ if (!this.visible)
17
+ return;
18
+ this.hide();
19
+ this.onConfirm?.();
20
+ };
21
+ handleCancel = () => {
22
+ if (!this.visible)
23
+ return;
24
+ this.hide();
25
+ this.onCancel?.();
26
+ };
27
+ constructor(screen) {
28
+ this.screen = screen;
29
+ this.box = neo_blessed_1.default.box({
30
+ parent: screen,
31
+ width: 52,
32
+ height: 9,
33
+ top: 'center',
34
+ left: 'center',
35
+ tags: true,
36
+ border: { type: 'line' },
37
+ style: {
38
+ bg: _theme_1.C.selection,
39
+ border: { fg: _theme_1.C.purple },
40
+ },
41
+ hidden: true,
42
+ });
43
+ screen.append(this.box);
44
+ }
45
+ show(options) {
46
+ this.onConfirm = options.onConfirm;
47
+ this.onCancel = options.onCancel;
48
+ const danger = options.danger ?? false;
49
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
50
+ this.box.style.border.fg = danger ? _theme_1.C.red : _theme_1.C.purple;
51
+ const confirmBtn = danger
52
+ ? `${_theme_1.t.red('[y]')} ${_theme_1.t.fg('Yes, proceed')}`
53
+ : `${_theme_1.t.green('[y]')} ${_theme_1.t.fg('Yes')}`;
54
+ const lines = [
55
+ '',
56
+ ` {bold}${danger ? _theme_1.t.red(options.title) : _theme_1.t.fg(options.title)}{/bold}`,
57
+ '',
58
+ ` ${_theme_1.t.comment(options.message)}`,
59
+ '',
60
+ ` ${confirmBtn} ${_theme_1.t.comment('[n] Cancel')}`,
61
+ ` ${_theme_1.t.comment('y:confirm n / Esc:cancel')}`,
62
+ ];
63
+ this.box.setContent(lines.join('\n'));
64
+ this.visible = true;
65
+ this.box.show();
66
+ this.box.focus();
67
+ this.screen.key(['y', 'S-y'], this.handleConfirm);
68
+ this.screen.key(['n', 'S-n', 'escape'], this.handleCancel);
69
+ this.screen.render();
70
+ }
71
+ hide() {
72
+ this.visible = false;
73
+ this.box.hide();
74
+ for (const k of ['y', 'S-y'])
75
+ this.screen.removeKey(k, this.handleConfirm);
76
+ for (const k of ['n', 'S-n', 'escape'])
77
+ this.screen.removeKey(k, this.handleCancel);
78
+ this.screen.render();
79
+ }
80
+ isVisible() {
81
+ return this.visible;
82
+ }
83
+ }
84
+ exports.ConfirmDialog = ConfirmDialog;
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ContainerDetail = void 0;
7
+ const neo_blessed_1 = __importDefault(require("neo-blessed"));
8
+ const _theme_1 = require("../../theme");
9
+ const format_1 = require("../../utils/format");
10
+ const status_1 = require("../../utils/status");
11
+ const widgets_1 = require("../widgets");
12
+ const BAR_WIDTH = 20;
13
+ class ContainerDetail {
14
+ screen;
15
+ wrapper;
16
+ headerBox;
17
+ box;
18
+ container = null;
19
+ lastStats;
20
+ envExpanded = false;
21
+ visible = false;
22
+ closeHandlers = [];
23
+ handleClose = () => {
24
+ if (!this.visible)
25
+ return;
26
+ this.hide();
27
+ this.closeHandlers.forEach((h) => h());
28
+ };
29
+ handleE = () => {
30
+ if (!this.visible || !this.container)
31
+ return;
32
+ this.envExpanded = !this.envExpanded;
33
+ this.render();
34
+ this.screen.render();
35
+ };
36
+ constructor(screen, dims) {
37
+ this.screen = screen;
38
+ this.wrapper = neo_blessed_1.default.box({
39
+ parent: screen,
40
+ top: dims.top,
41
+ left: dims.left,
42
+ width: dims.width,
43
+ height: dims.height,
44
+ style: { bg: _theme_1.C.bg },
45
+ hidden: true,
46
+ });
47
+ this.headerBox = (0, widgets_1.createHeaderBar)(this.wrapper);
48
+ this.box = neo_blessed_1.default.box({
49
+ parent: this.wrapper,
50
+ top: 1,
51
+ left: 0,
52
+ height: '100%-1',
53
+ width: '100%',
54
+ scrollable: true,
55
+ keys: true,
56
+ mouse: true,
57
+ tags: true,
58
+ alwaysScroll: true,
59
+ scrollbar: { ch: '│', style: { fg: _theme_1.C.comment } },
60
+ style: { fg: _theme_1.C.fg, bg: _theme_1.C.bg },
61
+ padding: { left: 2, right: 2 },
62
+ });
63
+ }
64
+ on(event, handler) {
65
+ if (event === 'close')
66
+ this.closeHandlers.push(handler);
67
+ }
68
+ show(container, stats) {
69
+ this.container = container;
70
+ if (stats !== undefined)
71
+ this.lastStats = stats;
72
+ this.envExpanded = false;
73
+ this.visible = true;
74
+ this.render();
75
+ this.updateHeader();
76
+ this.wrapper.show();
77
+ this.box.focus();
78
+ this.screen.key(['e'], this.handleE);
79
+ this.screen.key(['escape'], this.handleClose);
80
+ this.screen.render();
81
+ }
82
+ hide() {
83
+ this.visible = false;
84
+ this.wrapper.hide();
85
+ this.screen.removeKey('e', this.handleE);
86
+ this.screen.removeKey('escape', this.handleClose);
87
+ this.screen.render();
88
+ }
89
+ update(container, stats) {
90
+ this.container = container;
91
+ if (stats !== undefined)
92
+ this.lastStats = stats;
93
+ if (this.visible) {
94
+ this.render();
95
+ this.updateHeader();
96
+ }
97
+ }
98
+ isVisible() {
99
+ return this.visible;
100
+ }
101
+ getContainerId() {
102
+ return this.container?.id ?? null;
103
+ }
104
+ updateHeader() {
105
+ if (!this.container)
106
+ return;
107
+ this.headerBox.setContent(` ${_theme_1.t.purple('DETAIL')} — ${_theme_1.t.fg(this.container.name)}`);
108
+ }
109
+ render() {
110
+ if (!this.container)
111
+ return;
112
+ this.box.setContent(this.buildContent(this.container, this.lastStats));
113
+ }
114
+ buildContent(c, stats) {
115
+ const lines = [];
116
+ lines.push(`{bold}${_theme_1.t.purple(c.name)}{/bold} ${_theme_1.t.comment(`${c.image} · ${c.id.slice(0, 12)}`)}`);
117
+ lines.push((0, status_1.colorByStatus)(c, `● ${(0, status_1.statusLabel)(c)}`));
118
+ lines.push('');
119
+ lines.push(`${_theme_1.t.comment('Uptime:')} ${(0, status_1.colorByStatus)(c, c.uptime)}`);
120
+ lines.push(`${_theme_1.t.comment('Restart:')} ${c.restartPolicy}`);
121
+ lines.push(`${_theme_1.t.comment('PIDs:')} ${c.status === 'running' ? String(c.pids) : _theme_1.t.comment('—')}`);
122
+ if (c.ports.length > 0) {
123
+ lines.push('');
124
+ lines.push(_theme_1.t.comment('Ports:'));
125
+ for (const p of c.ports)
126
+ lines.push(` ${_theme_1.t.pink(p)}`);
127
+ }
128
+ if (c.networks.length > 0) {
129
+ lines.push('');
130
+ const info = c.ip ? `${c.networks[0]} · ${c.ip}` : c.networks[0];
131
+ lines.push(`${_theme_1.t.comment('Network:')} ${_theme_1.t.cyan(info)}`);
132
+ for (const n of c.networks.slice(1))
133
+ lines.push(` ${_theme_1.t.cyan(n)}`);
134
+ }
135
+ if (stats && c.status === 'running') {
136
+ lines.push('');
137
+ lines.push(this.cpuBar(stats.cpuPercent));
138
+ lines.push(this.memBar(stats.memPercent, stats.memUsageMB, stats.memLimitMB));
139
+ lines.push(this.diskLine(stats.diskReadMB, stats.diskWriteMB));
140
+ }
141
+ if (c.mounts.length > 0) {
142
+ lines.push('');
143
+ lines.push(_theme_1.t.comment('MOUNTS'));
144
+ for (const m of c.mounts) {
145
+ lines.push(_theme_1.t.comment(`${m.source} → ${m.destination} (${m.rw ? 'rw' : 'ro'})`));
146
+ }
147
+ }
148
+ lines.push('');
149
+ const arrow = this.envExpanded ? '▼' : '▶';
150
+ lines.push(_theme_1.t.comment(`ENV (${c.env.length}) ${arrow} — press e to toggle`));
151
+ if (this.envExpanded) {
152
+ for (const entry of c.env) {
153
+ const eq = entry.indexOf('=');
154
+ if (eq >= 0) {
155
+ lines.push(_theme_1.t.yellow(`${entry.slice(0, eq)}=${(0, format_1.truncate)(entry.slice(eq + 1), 55)}`));
156
+ }
157
+ else {
158
+ lines.push(_theme_1.t.yellow(entry));
159
+ }
160
+ }
161
+ }
162
+ lines.push('');
163
+ lines.push(this.actionsLine(c));
164
+ return lines.join('\n');
165
+ }
166
+ cpuBar(cpu) {
167
+ const filled = Math.min(BAR_WIDTH, Math.round((cpu / 100) * BAR_WIDTH));
168
+ const color = (0, status_1.cpuColor)(cpu);
169
+ const bar = `[${color('█'.repeat(filled))}${_theme_1.t.comment('░'.repeat(BAR_WIDTH - filled))}]`;
170
+ return `${_theme_1.t.comment('CPU')} ${bar} ${color(`${cpu.toFixed(1)}%`)}`;
171
+ }
172
+ memBar(mem, usageMB, limitMB) {
173
+ const filled = Math.min(BAR_WIDTH, Math.round((mem / 100) * BAR_WIDTH));
174
+ const color = (0, status_1.memColor)(mem);
175
+ const bar = `[${color('█'.repeat(filled))}${_theme_1.t.comment('░'.repeat(BAR_WIDTH - filled))}]`;
176
+ const label = `(${(0, format_1.humanSizeMB)(usageMB)} / ${(0, format_1.humanSizeMB)(limitMB)})`;
177
+ return `${_theme_1.t.comment('MEM')} ${bar} ${color(`${mem.toFixed(1)}%`)} ${_theme_1.t.comment(label)}`;
178
+ }
179
+ diskLine(readMB, writeMB) {
180
+ return `${_theme_1.t.comment('DISK')} ${_theme_1.t.comment('R:')}${_theme_1.t.cyan((0, format_1.humanSizeMB)(readMB))} ${_theme_1.t.comment('W:')}${_theme_1.t.orange((0, format_1.humanSizeMB)(writeMB))}`;
181
+ }
182
+ actionsLine(c) {
183
+ if ((0, status_1.isActive)(c.status)) {
184
+ return ` ${_theme_1.t.red('[s] Stop')} ${_theme_1.t.green('[r] Restart')} ${_theme_1.t.orange('[k] Kill')} ${_theme_1.t.purple('[x] Shell')} ${_theme_1.t.cyan('[l] Logs')} ${_theme_1.t.yellow('[e] Env')} ${_theme_1.t.comment('Esc close')}`;
185
+ }
186
+ return ` ${_theme_1.t.green('[S] Start')} ${_theme_1.t.red('[d] Remove')} ${_theme_1.t.cyan('[l] Logs')} ${_theme_1.t.yellow('[e] Env')} ${_theme_1.t.comment('Esc close')}`;
187
+ }
188
+ }
189
+ exports.ContainerDetail = ContainerDetail;
@@ -0,0 +1,120 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ContainerList = void 0;
7
+ const neo_blessed_1 = __importDefault(require("neo-blessed"));
8
+ const _theme_1 = require("../../theme");
9
+ const format_1 = require("../../utils/format");
10
+ const status_1 = require("../../utils/status");
11
+ const widgets_1 = require("../widgets");
12
+ class ContainerList {
13
+ wrapper;
14
+ header;
15
+ list;
16
+ messageBox;
17
+ containers = [];
18
+ selectHandlers = [];
19
+ navigateHandlers = [];
20
+ constructor(parent, dims) {
21
+ this.wrapper = neo_blessed_1.default.box({
22
+ parent,
23
+ top: dims.top,
24
+ left: dims.left,
25
+ width: dims.width,
26
+ height: dims.height,
27
+ });
28
+ this.header = (0, widgets_1.createHeaderBar)(this.wrapper);
29
+ this.list = (0, widgets_1.createListWidget)(this.wrapper);
30
+ this.messageBox = (0, widgets_1.createCenteredMessage)(this.wrapper);
31
+ this.list.on('select', (_item, index) => {
32
+ const container = this.containers[index];
33
+ if (container)
34
+ this.selectHandlers.forEach((h) => h(container));
35
+ });
36
+ this.list.on('select item', (_item, index) => {
37
+ const container = this.containers[index] ?? null;
38
+ this.navigateHandlers.forEach((h) => h(container));
39
+ });
40
+ this.list.on('click', () => {
41
+ const container = this.containers[(0, widgets_1.listSelected)(this.list)];
42
+ if (container)
43
+ this.selectHandlers.forEach((h) => h(container));
44
+ });
45
+ }
46
+ on(event, handler) {
47
+ if (event === 'select')
48
+ this.selectHandlers.push(handler);
49
+ if (event === 'navigate')
50
+ this.navigateHandlers.push(handler);
51
+ }
52
+ showLoading() {
53
+ this.messageBox.setContent(_theme_1.t.comment(' Loading...'));
54
+ this.messageBox.show();
55
+ }
56
+ setData(containers, stats) {
57
+ this.containers = containers;
58
+ this.messageBox.hide();
59
+ const innerWidth = Math.max(10, this.list.width - 2);
60
+ const cols = this.calcColWidths(innerWidth);
61
+ this.renderHeader(cols);
62
+ const items = containers.map((c) => this.buildRow(c, cols, stats?.get(c.id)));
63
+ this.list.setItems(items);
64
+ if (containers.length === 0) {
65
+ this.messageBox.setContent(_theme_1.t.comment(' No containers'));
66
+ this.messageBox.show();
67
+ }
68
+ }
69
+ getSelected() {
70
+ return this.containers[(0, widgets_1.listSelected)(this.list)] ?? null;
71
+ }
72
+ getSelectedIndex() {
73
+ return (0, widgets_1.listSelected)(this.list);
74
+ }
75
+ focus() {
76
+ this.list.focus();
77
+ }
78
+ show() {
79
+ this.wrapper.show();
80
+ }
81
+ hide() {
82
+ this.wrapper.hide();
83
+ }
84
+ calcColWidths(inner) {
85
+ const c1 = Math.floor(inner * 0.21);
86
+ const c2 = Math.floor(inner * 0.17);
87
+ const c3 = Math.floor(inner * 0.22);
88
+ const c4 = Math.floor(inner * 0.1);
89
+ const c5 = Math.floor(inner * 0.08);
90
+ const c6 = Math.floor(inner * 0.08);
91
+ const c7 = Math.max(1, inner - c1 - c2 - c3 - c4 - c5 - c6);
92
+ return [c1, c2, c3, c4, c5, c6, c7];
93
+ }
94
+ renderHeader([c1, c2, c3, c4, c5, c6]) {
95
+ const h1 = (0, format_1.padEnd)(_theme_1.t.comment(' NAME'), c1 + 1);
96
+ const h2 = (0, format_1.padEnd)(_theme_1.t.comment('IMAGE'), c2);
97
+ const h3 = (0, format_1.padEnd)(_theme_1.t.comment('STATUS & UPTIME'), c3);
98
+ const h4 = (0, format_1.padEnd)(_theme_1.t.comment('NET'), c4);
99
+ const h5 = (0, format_1.padEnd)(_theme_1.t.comment('CPU'), c5);
100
+ const h6 = (0, format_1.padEnd)(_theme_1.t.comment('MEM'), c6);
101
+ const h7 = _theme_1.t.comment('PORTS');
102
+ this.header.setContent(`${h1}${h2}${h3}${h4}${h5}${h6}${h7}`);
103
+ }
104
+ buildRow(c, [c1, c2, c3, c4, c5, c6, c7], stats) {
105
+ const nameRaw = (0, format_1.truncate)(c.name, Math.max(1, c1 - 2));
106
+ const col1 = (0, format_1.padEnd)(`${(0, status_1.statusDot)(c)} ${nameRaw}`, c1);
107
+ const imageRaw = (0, format_1.truncate)(c.image, Math.max(1, c2 - 1));
108
+ const col2 = (0, format_1.padEnd)(_theme_1.t.comment(imageRaw), c2);
109
+ const stateRaw = `${(0, status_1.statusLabel)(c)} · ${c.uptime}`;
110
+ const col3 = (0, format_1.padEnd)((0, status_1.colorByStatus)(c, (0, format_1.truncate)(stateRaw, c3 - 1)), c3);
111
+ const netRaw = c.networks.length > 0 ? (0, format_1.truncate)(c.networks[0], c4 - 1) : '—';
112
+ const col4 = (0, format_1.padEnd)(c.networks.length > 0 ? _theme_1.t.cyan(netRaw) : _theme_1.t.comment(netRaw), c4);
113
+ const col5 = (0, format_1.padEnd)((0, status_1.formatCpuCell)(c, stats?.cpuPercent), c5);
114
+ const col6 = (0, format_1.padEnd)((0, status_1.formatMemCell)(c, stats?.memPercent), c6);
115
+ const rawPort = c.ports.length > 0 ? (0, format_1.truncate)(c.ports[0], c7 - 1) : '';
116
+ const col7 = rawPort ? _theme_1.t.pink(rawPort) : _theme_1.t.comment('—');
117
+ return `${col1}${col2}${col3}${col4}${col5}${col6}${col7}`;
118
+ }
119
+ }
120
+ exports.ContainerList = ContainerList;