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.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Achraf El Fadili
4
+ Copyright (c) 2026 Dmytro Shandyba
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/NOTICE ADDED
@@ -0,0 +1,69 @@
1
+ dockza
2
+ Copyright (c) 2026 Dmytro Shandyba
3
+
4
+ This product includes software originally developed as "docktui"
5
+ Copyright (c) 2026 Achraf El Fadili
6
+
7
+ ================================================================================
8
+ Origin and history
9
+ ================================================================================
10
+
11
+ dockza is a fork of docktui, a terminal UI for Docker originally written by
12
+ Achraf El Fadili and released under the MIT License.
13
+
14
+ Original project : docktui
15
+ Original author : Achraf El Fadili <contact@elfadiliachraf.tech>
16
+ Original source : https://github.com/0xShady/docktui
17
+ Original license : MIT (see LICENSE)
18
+
19
+ The fork was made from docktui v0.1.1. Upstream development appeared to have
20
+ stopped, and this fork continues the work independently. It is not affiliated
21
+ with, endorsed by, or supported by the original author or the original project.
22
+
23
+ Former names of this software:
24
+
25
+ docktui (upstream project and npm package, through v0.1.1)
26
+ dockza (this fork, from v0.2.0 onward)
27
+
28
+ The complete authorship record is preserved in this repository's git history,
29
+ which retains every original commit and its author attribution unmodified.
30
+
31
+ Both the original code and the modifications made in this fork are distributed
32
+ under the MIT License. The LICENSE file carries the copyright notices of the
33
+ original author and of the fork maintainer, as the MIT License requires.
34
+
35
+ Names, logos, domains and published packages belonging to the original project
36
+ (including the docktui name, the docktui logo, the docktui.com domain and the
37
+ "docktui" npm package) remain the property of the original author and are not
38
+ used by this fork. dockza carries its own name and its own logo.
39
+
40
+ ================================================================================
41
+ Third-party software
42
+ ================================================================================
43
+
44
+ This software depends on the following third-party packages. They are declared
45
+ as runtime dependencies and are not bundled into this distribution; each is
46
+ retrieved and installed by the package manager under its own license.
47
+
48
+ dayjs MIT https://github.com/iamkun/dayjs
49
+ dockerode Apache-2.0 https://github.com/apocas/dockerode
50
+ neo-blessed MIT https://github.com/embarklabs/neo-blessed
51
+
52
+ dockerode is licensed under the Apache License, Version 2.0. A copy of that
53
+ license is available at https://www.apache.org/licenses/LICENSE-2.0. If this
54
+ software is ever redistributed in a form that bundles or vendors dockerode's
55
+ source or object code, the Apache-2.0 attribution requirements (Section 4)
56
+ apply and this NOTICE must be extended accordingly.
57
+
58
+ ================================================================================
59
+ Trademarks
60
+ ================================================================================
61
+
62
+ Docker and the Docker logo are trademarks or registered trademarks of Docker,
63
+ Inc. dockza is an independent, unofficial tool. It is not produced, endorsed,
64
+ sponsored, or supported by Docker, Inc., and the "dockza" name is not a Docker,
65
+ Inc. product name.
66
+
67
+ Podman is a trademark of Red Hat, Inc. Any other product names, logos and brands
68
+ referenced in this software or its documentation are the property of their
69
+ respective owners and are used for identification purposes only.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/shandyba/dockza/main/assets/icon.png" alt="dockza" width="180">
3
+ </p>
4
+
5
+ <h1 align="center">dockza</h1>
6
+
7
+ <p align="center">
8
+ A lightweight terminal UI for Docker. Manage containers, images, volumes, and networks from your terminal — Compose-aware, no mouse required.
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://www.npmjs.com/package/dockza"><img src="https://img.shields.io/npm/v/dockza.svg" alt="npm version"></a>
13
+ <a href="https://github.com/shandyba/dockza/actions/workflows/ci.yml"><img src="https://github.com/shandyba/dockza/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
14
+ <a href="https://nodejs.org/"><img src="https://img.shields.io/node/v/dockza.svg" alt="Node"></a>
15
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/License-MIT-blue.svg" alt="License: MIT"></a>
16
+ </p>
17
+
18
+ <!-- TODO: record a new asciicast under the dockza name and link it here. -->
19
+
20
+ ## Install
21
+
22
+ Run it without installing:
23
+
24
+ ```bash
25
+ npx dockza
26
+ ```
27
+
28
+ Or install globally:
29
+
30
+ ```bash
31
+ npm install -g dockza
32
+ dockza
33
+ ```
34
+
35
+ ## Features
36
+
37
+ - **Compose-aware Stacks view** — containers grouped by project with running / errored / stopped counts, collapsible per stack.
38
+ - **Containers / Images / Volumes** — live CPU and memory stats, start / stop / restart / kill / remove with confirm dialogs.
39
+ - **Networks** — list Docker networks with driver, scope, attached-container count, creation time, and in-use status; remove unused networks (built-in networks and any with attached containers are protected).
40
+ - **Detail panel** — full metadata, mounts, environment, live CPU/MEM bars.
41
+ - **Streaming log viewer** with follow mode, scrollback cap, and color-coded stdout/stderr.
42
+ - **Filter** — press `/` from Stacks to filter by stack, service, or image name.
43
+ - **Side rail navigation** — `1`–`5` or `Tab` cycles views; click a live stack to jump to it.
44
+ - **Help overlay** — press `h` anywhere (except confirm dialogs / the stack filter) to toggle the key-binding reference.
45
+ - **Shell into containers** — `x` opens an external terminal `exec`'d into the selected container (macOS Terminal, Windows Terminal, common Linux emulators).
46
+ - **Honors `DOCKER_HOST`** — works with rootless Docker, podman, and remote daemons.
47
+
48
+ ## Requirements
49
+
50
+ - Node.js **20** or later
51
+ - A running Docker daemon (Docker Desktop on macOS/Windows, Docker Engine on Linux, or any podman/rootless setup)
52
+ - A **256-color** or **truecolor** terminal (`TERM=xterm-256color` or `COLORTERM=truecolor`)
53
+
54
+ ## Configuration
55
+
56
+ ### Custom Docker host
57
+
58
+ dockza auto-detects the daemon socket. To point it elsewhere, set `DOCKER_HOST`:
59
+
60
+ ```bash
61
+ DOCKER_HOST=unix:///run/user/1000/docker.sock dockza # rootless docker
62
+ DOCKER_HOST=unix:///run/user/1000/podman/podman.sock dockza # podman
63
+ DOCKER_HOST=tcp://10.0.0.5:2375 dockza # remote daemon
64
+ DOCKER_HOST=npipe:////./pipe/docker_engine dockza # Windows named pipe
65
+ ```
66
+
67
+ ### Shell-into-container (`x` key)
68
+
69
+ Requires the `docker` CLI to be on your `PATH`. dockza launches your platform's native terminal with `docker exec -it <id> sh` (falls back to `bash` if available inside the container).
70
+
71
+ ## Key bindings
72
+
73
+ ### Global
74
+
75
+ | Key | Action |
76
+ |-----|--------|
77
+ | `1` – `5` | Jump to Stacks / Containers / Images / Volumes / Networks |
78
+ | `Tab` / `Shift+Tab` | Cycle views |
79
+ | `h` | Toggle help overlay |
80
+ | `q` / `Ctrl+C` | Quit |
81
+
82
+ ### Stacks
83
+
84
+ | Key | Action |
85
+ |-----|--------|
86
+ | `↑ ↓` / `j k` | Navigate tree |
87
+ | `→` / `←` | Expand / collapse stack |
88
+ | `Enter` | On a stack: toggle expand. On a service: open detail panel |
89
+ | `/` | Open filter (`Esc` to clear) |
90
+ | `l` | Open log viewer |
91
+ | `x` | Shell into selected running container |
92
+ | `s` / `r` / `k` | Stop / restart / kill running container (confirm) |
93
+ | `S` | Start stopped container |
94
+ | `d` | Remove stopped container (confirm) |
95
+
96
+ ### Containers
97
+
98
+ Same per-container actions as Stacks. `Enter` always opens the detail panel.
99
+
100
+ ### Detail panel
101
+
102
+ Same container actions as the list/tree (`l`, `s`, `r`, `k`, `S`, `d`, `x`) plus:
103
+
104
+ | Key | Action |
105
+ |-----|--------|
106
+ | `e` | Toggle environment variables (expanded / collapsed) |
107
+ | `↑ ↓` | Scroll |
108
+ | `Esc` | Close |
109
+
110
+ ### Log viewer
111
+
112
+ | Key | Action |
113
+ |-----|--------|
114
+ | `f` | Toggle follow mode |
115
+ | `g` | Scroll to top |
116
+ | `G` | Scroll to bottom, re-enable follow |
117
+ | `↑` / `k` | Scroll up (disables follow) |
118
+ | `Esc` | Close |
119
+
120
+ ### Images, Volumes & Networks
121
+
122
+ | Key | Action |
123
+ |-----|--------|
124
+ | `↑ ↓` / `j k` | Navigate list |
125
+ | `d` | Delete unused item (confirm) |
126
+
127
+ Networks additionally protect the built-in `bridge` / `host` / `none` networks and any network with attached containers — `d` on those shows a message instead of a confirm dialog. In-use detection is consistent with Images and Volumes: a network counts as in use whenever a container is attached, running or stopped.
128
+
129
+ ### Confirm dialog
130
+
131
+ | `y` / `Y` to confirm · `n` / `N` / `Esc` to cancel |
132
+ |-----------------------------------------------------|
133
+
134
+ ## Troubleshooting
135
+
136
+ ### `permission denied` on the Docker socket (Linux)
137
+
138
+ Add your user to the `docker` group, then log out and back in:
139
+
140
+ ```bash
141
+ sudo usermod -aG docker $USER
142
+ ```
143
+
144
+ ### Windows: cannot connect to the daemon
145
+
146
+ By default dockza tries the named pipe `//./pipe/docker_engine`. If that fails, enable TCP in Docker Desktop (**Settings → General → Expose daemon on `tcp://localhost:2375` without TLS**) and run:
147
+
148
+ ```bash
149
+ DOCKER_HOST=tcp://localhost:2375 dockza
150
+ ```
151
+
152
+ ### `dockza requires a 256-color (or truecolor) terminal`
153
+
154
+ Your `$TERM` is too narrow. Set it explicitly or use a modern terminal:
155
+
156
+ ```bash
157
+ TERM=xterm-256color dockza
158
+ ```
159
+
160
+ ### `The 'docker' CLI is not on your PATH`
161
+
162
+ You hit `x` (shell into container) but the `docker` binary isn't installed or isn't on `PATH`. Install Docker CLI or skip the `x` action — everything else works without it.
163
+
164
+ ## Development
165
+
166
+ ```bash
167
+ git clone https://github.com/shandyba/dockza.git
168
+ cd dockza
169
+ npm install
170
+
171
+ npm run dev # run from source with ts-node
172
+ npm run build # compile to dist/
173
+ npm start # run compiled output
174
+
175
+ npm test # vitest, one-shot
176
+ npm run test:coverage # generate ./coverage/index.html
177
+ npm run lint
178
+ npm run format
179
+ ```
180
+
181
+ See [CONTRIBUTING.md](./CONTRIBUTING.md) for the pre-PR checklist and [CHANGELOG.md](./CHANGELOG.md) for release history.
182
+
183
+ ## Credits
184
+
185
+ dockza is a fork of [**docktui**](https://github.com/0xShady/docktui) by
186
+ [Achraf El Fadili](https://github.com/0xShady), released under the MIT License.
187
+ Upstream development appeared to have stopped, so this fork continues the work
188
+ independently under a new name. Every original commit and its authorship is
189
+ preserved in this repository's git history.
190
+
191
+ This project is not affiliated with, endorsed by, or supported by the original
192
+ author or the original project. Bug reports and feature requests belong
193
+ [here](https://github.com/shandyba/dockza/issues), not upstream.
194
+
195
+ Docker and the Docker logo are trademarks or registered trademarks of Docker,
196
+ Inc. dockza is an independent, unofficial tool and is not a Docker, Inc.
197
+ product.
198
+
199
+ ## License
200
+
201
+ [MIT](./LICENSE) © Achraf El Fadili (original work) · © Dmytro Shandyba (fork and subsequent work)
202
+
203
+ See [NOTICE](./NOTICE) for attribution details and third-party licenses.
package/dist/cli.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ const client_1 = require("./docker/client");
5
+ const app_1 = require("./ui/app");
6
+ const term_caps_1 = require("./utils/term-caps");
7
+ async function main() {
8
+ const colors = (0, term_caps_1.detectColors)();
9
+ if (colors === 'low') {
10
+ process.stderr.write('dockza requires a 256-color (or truecolor) terminal. Set TERM=xterm-256color or use a modern terminal emulator.\n');
11
+ process.exit(1);
12
+ }
13
+ try {
14
+ await (0, client_1.pingDocker)();
15
+ }
16
+ catch (err) {
17
+ process.stderr.write(`${err instanceof Error ? err.message : String(err)}\n`);
18
+ process.exit(1);
19
+ }
20
+ try {
21
+ const app = new app_1.App();
22
+ await app.start();
23
+ process.exit(0);
24
+ }
25
+ catch (err) {
26
+ process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
27
+ process.exit(1);
28
+ }
29
+ }
30
+ main().catch((err) => {
31
+ process.stderr.write(`Fatal error: ${err instanceof Error ? err.message : String(err)}\n`);
32
+ process.exit(1);
33
+ });
@@ -0,0 +1,76 @@
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.dockerode = void 0;
7
+ exports.getDockerSocketLabel = getDockerSocketLabel;
8
+ exports.pingDocker = pingDocker;
9
+ exports.getDockerVersion = getDockerVersion;
10
+ const fs_1 = require("fs");
11
+ const dockerode_1 = __importDefault(require("dockerode"));
12
+ let socketLabel = '';
13
+ function createDockerode() {
14
+ // Honor DOCKER_HOST when set (rootless docker, podman, remote daemons).
15
+ // Supported forms: unix:///path/to/sock, npipe:////./pipe/name, tcp://host:port
16
+ const host = process.env.DOCKER_HOST;
17
+ if (host) {
18
+ if (host.startsWith('unix://')) {
19
+ const sock = host.slice('unix://'.length);
20
+ socketLabel = `unix://${sock}`;
21
+ return new dockerode_1.default({ socketPath: sock });
22
+ }
23
+ if (host.startsWith('npipe://')) {
24
+ const sock = host.slice('npipe://'.length);
25
+ socketLabel = `npipe://${sock}`;
26
+ return new dockerode_1.default({ socketPath: sock });
27
+ }
28
+ if (host.startsWith('tcp://') || host.startsWith('http://') || host.startsWith('https://')) {
29
+ const url = new URL(host);
30
+ socketLabel = host;
31
+ return new dockerode_1.default({
32
+ host: url.hostname,
33
+ port: Number(url.port) || (url.protocol === 'https:' ? 2376 : 2375),
34
+ protocol: url.protocol === 'https:' ? 'https' : 'http',
35
+ });
36
+ }
37
+ throw new Error(`Unrecognized DOCKER_HOST scheme: '${host}'. Expected unix://…, npipe://…, tcp://…, http://…, or https://…`);
38
+ }
39
+ if (process.platform === 'win32') {
40
+ socketLabel = 'npipe:////./pipe/docker_engine';
41
+ return new dockerode_1.default({ socketPath: '//./pipe/docker_engine' });
42
+ }
43
+ // macOS Docker Desktop sometimes only exposes the per-user socket; prefer it when present.
44
+ const userSock = `${process.env.HOME ?? ''}/.docker/run/docker.sock`;
45
+ if (process.platform === 'darwin' && process.env.HOME && (0, fs_1.existsSync)(userSock)) {
46
+ socketLabel = `unix://${userSock}`;
47
+ return new dockerode_1.default({ socketPath: userSock });
48
+ }
49
+ socketLabel = 'unix:///var/run/docker.sock';
50
+ return new dockerode_1.default({ socketPath: '/var/run/docker.sock' });
51
+ }
52
+ exports.dockerode = createDockerode();
53
+ function getDockerSocketLabel() {
54
+ return socketLabel;
55
+ }
56
+ async function pingDocker() {
57
+ try {
58
+ await exports.dockerode.ping();
59
+ }
60
+ catch (err) {
61
+ const raw = err instanceof Error ? err.message : String(err);
62
+ if (/EACCES|permission denied/i.test(raw) && process.platform === 'linux') {
63
+ throw new Error(`Cannot access the Docker socket (permission denied). Add your user to the 'docker' group: sudo usermod -aG docker $USER (then log out and back in). (${raw})`);
64
+ }
65
+ throw new Error(`Docker is not running or not accessible. Make sure the Docker daemon is started. (${raw})`);
66
+ }
67
+ }
68
+ async function getDockerVersion() {
69
+ try {
70
+ const info = (await exports.dockerode.version());
71
+ return String(info?.Version ?? '');
72
+ }
73
+ catch {
74
+ return 'unknown';
75
+ }
76
+ }
@@ -0,0 +1,180 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toContainerInfo = toContainerInfo;
4
+ exports.listContainers = listContainers;
5
+ exports.toContainerStats = toContainerStats;
6
+ exports.fetchStats = fetchStats;
7
+ exports.startContainer = startContainer;
8
+ exports.stopContainer = stopContainer;
9
+ exports.restartContainer = restartContainer;
10
+ exports.killContainer = killContainer;
11
+ exports.removeContainer = removeContainer;
12
+ exports.streamLogs = streamLogs;
13
+ const client_1 = require("./client");
14
+ const format_1 = require("../utils/format");
15
+ const stats_1 = require("../utils/stats");
16
+ const VALID_STATUSES = new Set([
17
+ 'running',
18
+ 'paused',
19
+ 'exited',
20
+ 'restarting',
21
+ 'dead',
22
+ 'created',
23
+ 'removing',
24
+ ]);
25
+ function toContainerInfo(raw, inspected) {
26
+ const name = (raw.Names?.[0] ?? raw.Id).replace(/^\//, '');
27
+ const status = VALID_STATUSES.has(raw.State)
28
+ ? raw.State
29
+ : 'exited';
30
+ let uptime = raw.Status;
31
+ if (inspected) {
32
+ if (status === 'running' || status === 'paused' || status === 'restarting') {
33
+ const startedAt = new Date(inspected.State.StartedAt);
34
+ if (startedAt.getTime() > 0)
35
+ uptime = (0, format_1.humanUptime)(startedAt);
36
+ }
37
+ else if (status === 'exited' || status === 'dead') {
38
+ const finishedAt = new Date(inspected.State.FinishedAt);
39
+ if (finishedAt.getTime() > 0)
40
+ uptime = (0, format_1.relativeTime)(finishedAt);
41
+ }
42
+ }
43
+ // The daemon sends null (not []) for these when a container publishes no ports
44
+ // or is attached to no networks, so every access here has to tolerate null.
45
+ const ports = (raw.Ports ?? []).map((p) => p.PublicPort
46
+ ? `${p.IP ? `${p.IP}:` : ''}${p.PublicPort}->${p.PrivatePort}/${p.Type}`
47
+ : `${p.PrivatePort}/${p.Type}`);
48
+ const rawNetworks = raw.NetworkSettings?.Networks ?? {};
49
+ const networks = Object.keys(rawNetworks);
50
+ const ip = networks.length > 0 ? (rawNetworks[networks[0]]?.IPAddress ?? '') : '';
51
+ const mounts = (raw.Mounts ?? []).map((m) => ({
52
+ source: m.Source ?? '',
53
+ destination: m.Destination ?? '',
54
+ mode: m.Mode ?? '',
55
+ rw: m.RW ?? false,
56
+ type: m.Type ?? 'bind',
57
+ }));
58
+ return {
59
+ id: raw.Id,
60
+ name,
61
+ image: raw.Image,
62
+ status,
63
+ exitCode: inspected?.State?.ExitCode ?? 0,
64
+ uptime,
65
+ ports,
66
+ networks,
67
+ ip,
68
+ mounts,
69
+ env: inspected?.Config?.Env ?? [],
70
+ restartPolicy: inspected?.HostConfig?.RestartPolicy?.Name ?? 'no',
71
+ pids: inspected?.State?.Pid ?? 0,
72
+ labels: raw.Labels ?? {},
73
+ };
74
+ }
75
+ async function listContainers() {
76
+ try {
77
+ const rawList = await client_1.dockerode.listContainers({ all: true });
78
+ return await Promise.all(rawList.map(async (raw) => {
79
+ let inspected = null;
80
+ try {
81
+ inspected = await client_1.dockerode.getContainer(raw.Id).inspect();
82
+ }
83
+ catch {
84
+ // Fall back to list data if inspect fails
85
+ }
86
+ return toContainerInfo(raw, inspected);
87
+ }));
88
+ }
89
+ catch (err) {
90
+ throw new Error(`Failed to list containers: ${err instanceof Error ? err.message : String(err)}`);
91
+ }
92
+ }
93
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
94
+ function toContainerStats(stats) {
95
+ const usage = stats.memory_stats.usage;
96
+ const limit = stats.memory_stats.limit;
97
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
98
+ const blkio = stats.blkio_stats?.io_service_bytes_recursive ?? [];
99
+ const readBytes = blkio
100
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
101
+ .filter((b) => b.op === 'Read' || b.op === 'read')
102
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
103
+ .reduce((sum, b) => sum + (b.value ?? 0), 0);
104
+ const writeBytes = blkio
105
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
106
+ .filter((b) => b.op === 'Write' || b.op === 'write')
107
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
108
+ .reduce((sum, b) => sum + (b.value ?? 0), 0);
109
+ return {
110
+ cpuPercent: (0, stats_1.calcCPUPercent)(stats),
111
+ memUsageMB: usage / 1024 / 1024,
112
+ memLimitMB: limit / 1024 / 1024,
113
+ memPercent: limit > 0 ? (usage / limit) * 100 : 0,
114
+ diskReadMB: readBytes / 1024 / 1024,
115
+ diskWriteMB: writeBytes / 1024 / 1024,
116
+ };
117
+ }
118
+ async function fetchStats(id) {
119
+ try {
120
+ const stats = await client_1.dockerode.getContainer(id).stats({ stream: false });
121
+ return toContainerStats(stats);
122
+ }
123
+ catch (err) {
124
+ throw new Error(`Failed to fetch stats for container ${id}: ${err instanceof Error ? err.message : String(err)}`);
125
+ }
126
+ }
127
+ async function startContainer(id) {
128
+ try {
129
+ await client_1.dockerode.getContainer(id).start();
130
+ }
131
+ catch (err) {
132
+ throw new Error(`Failed to start container ${id}: ${err instanceof Error ? err.message : String(err)}`);
133
+ }
134
+ }
135
+ async function stopContainer(id) {
136
+ try {
137
+ await client_1.dockerode.getContainer(id).stop();
138
+ }
139
+ catch (err) {
140
+ throw new Error(`Failed to stop container ${id}: ${err instanceof Error ? err.message : String(err)}`);
141
+ }
142
+ }
143
+ async function restartContainer(id) {
144
+ try {
145
+ await client_1.dockerode.getContainer(id).restart();
146
+ }
147
+ catch (err) {
148
+ throw new Error(`Failed to restart container ${id}: ${err instanceof Error ? err.message : String(err)}`);
149
+ }
150
+ }
151
+ async function killContainer(id) {
152
+ try {
153
+ await client_1.dockerode.getContainer(id).kill();
154
+ }
155
+ catch (err) {
156
+ throw new Error(`Failed to kill container ${id}: ${err instanceof Error ? err.message : String(err)}`);
157
+ }
158
+ }
159
+ async function removeContainer(id) {
160
+ try {
161
+ await client_1.dockerode.getContainer(id).remove({ force: true });
162
+ }
163
+ catch (err) {
164
+ throw new Error(`Failed to remove container ${id}: ${err instanceof Error ? err.message : String(err)}`);
165
+ }
166
+ }
167
+ async function streamLogs(id, tail) {
168
+ try {
169
+ return await client_1.dockerode.getContainer(id).logs({
170
+ follow: true,
171
+ stdout: true,
172
+ stderr: true,
173
+ tail,
174
+ timestamps: false,
175
+ });
176
+ }
177
+ catch (err) {
178
+ throw new Error(`Failed to stream logs for container ${id}: ${err instanceof Error ? err.message : String(err)}`);
179
+ }
180
+ }
@@ -0,0 +1,41 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listImages = listImages;
4
+ exports.removeImage = removeImage;
5
+ const client_1 = require("./client");
6
+ async function listImages() {
7
+ try {
8
+ const [rawImages, rawContainers] = await Promise.all([
9
+ client_1.dockerode.listImages({ all: false }),
10
+ client_1.dockerode.listContainers({ all: true }),
11
+ ]);
12
+ const usedImageIds = new Set(rawContainers.map((c) => c.ImageID));
13
+ return rawImages
14
+ .sort((a, b) => (a.RepoTags?.[0] ?? '').localeCompare(b.RepoTags?.[0] ?? ''))
15
+ .map((img) => {
16
+ const repoTag = img.RepoTags?.[0] ?? '<none>:<none>';
17
+ const colonIdx = repoTag.lastIndexOf(':');
18
+ const repository = colonIdx >= 0 ? repoTag.slice(0, colonIdx) || '<none>' : '<none>';
19
+ const tag = colonIdx >= 0 ? repoTag.slice(colonIdx + 1) || '<none>' : '<none>';
20
+ return {
21
+ id: img.Id,
22
+ repository,
23
+ tag,
24
+ sizeMB: img.Size / 1024 / 1024,
25
+ created: new Date(img.Created * 1000),
26
+ inUse: usedImageIds.has(img.Id),
27
+ };
28
+ });
29
+ }
30
+ catch (err) {
31
+ throw new Error(`Failed to list images: ${err instanceof Error ? err.message : String(err)}`);
32
+ }
33
+ }
34
+ async function removeImage(id) {
35
+ try {
36
+ await client_1.dockerode.getImage(id).remove();
37
+ }
38
+ catch (err) {
39
+ throw new Error(`Failed to remove image ${id}: ${err instanceof Error ? err.message : String(err)}`);
40
+ }
41
+ }