cdd-cli 3.1.2 → 3.1.4
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/CHANGELOG.md +29 -0
- package/FIXES_APPLIED.md +419 -0
- package/dist/App.js +7 -9
- package/dist/components/ContainerCreationPrompt.js +1 -1
- package/dist/components/ContainerList.js +2 -4
- package/dist/components/ContainerRow.js +20 -14
- package/dist/components/ContainerSection.js +1 -1
- package/dist/helpers/actionHelpers.js +14 -1
- package/dist/helpers/dockerService/dockerService.js +6 -3
- package/dist/helpers/dockerService/serviceComponents/containerActions.js +60 -10
- package/dist/helpers/dockerService/serviceComponents/containerList.js +7 -2
- package/dist/helpers/dockerService/serviceComponents/containerLogs.js +33 -20
- package/dist/helpers/dockerService/serviceComponents/containerStats.js +12 -3
- package/dist/helpers/dockerService/serviceComponents/imageUtils.js +12 -0
- package/dist/helpers/exitWithMessage.js +11 -0
- package/dist/helpers/validationHelpers.js +15 -2
- package/dist/hooks/creation/useContainerCreation.js +2 -6
- package/dist/hooks/useContainers.js +1 -3
- package/dist/hooks/useControls.js +27 -2
- package/dist/hooks/useLogsStream.js +6 -0
- package/dist/index.js +1 -4
- package/package.json +1 -1
- package/src/components/ContainerRow.jsx +17 -5
- package/src/helpers/actionHelpers.js +1 -1
- package/src/helpers/dockerService/dockerService.js +7 -1
- package/src/helpers/dockerService/serviceComponents/containerActions.js +25 -9
- package/src/helpers/dockerService/serviceComponents/containerList.js +1 -1
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +19 -15
- package/src/helpers/dockerService/serviceComponents/containerStats.js +9 -1
- package/src/helpers/validationHelpers.js +14 -2
- package/src/hooks/creation/useContainerCreation.js +2 -6
- package/src/hooks/useControls.js +5 -1
- package/test/validationHelpers.test.js +32 -0
|
@@ -8,9 +8,43 @@ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present,
|
|
|
8
8
|
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
9
9
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
10
10
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
11
|
+
import { docker } from "../dockerService.js";
|
|
12
|
+
import { imageExists, pullImage } from "./imageUtils.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Helper to add timeout to promises
|
|
16
|
+
* @param {Promise} promise - Promise to wrap
|
|
17
|
+
* @param {number} ms - Timeout in milliseconds
|
|
18
|
+
* @returns {Promise}
|
|
19
|
+
*/
|
|
20
|
+
function withTimeout(promise) {
|
|
21
|
+
var ms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 30000;
|
|
22
|
+
return Promise.race([promise, new Promise(function (_, reject) {
|
|
23
|
+
return setTimeout(function () {
|
|
24
|
+
return reject(new Error('Operation timed out'));
|
|
25
|
+
}, ms);
|
|
26
|
+
})]);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Remove (delete) a container by id. Force removal so running containers are stopped first.
|
|
31
|
+
*
|
|
32
|
+
* @param {string} containerId - Docker container id
|
|
33
|
+
* @returns {Promise<void>} Resolves when removal completes
|
|
34
|
+
* @throws {Error} If Docker reports an error
|
|
35
|
+
*/
|
|
11
36
|
export function removeContainer(_x) {
|
|
12
37
|
return _removeContainer.apply(this, arguments);
|
|
13
38
|
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a new container from an image. If the image is missing locally, it will be pulled.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} imageName - Image name (e.g. 'nginx:alpine')
|
|
44
|
+
* @param {Object} [options] - Docker create options (Env, ExposedPorts, HostConfig, name, etc.)
|
|
45
|
+
* @returns {Promise<string>} The created container id
|
|
46
|
+
* @throws {Error} If image listing/pull or creation fails
|
|
47
|
+
*/
|
|
14
48
|
function _removeContainer() {
|
|
15
49
|
_removeContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(containerId) {
|
|
16
50
|
var container, _t;
|
|
@@ -20,9 +54,9 @@ function _removeContainer() {
|
|
|
20
54
|
container = docker.getContainer(containerId);
|
|
21
55
|
_context.p = 1;
|
|
22
56
|
_context.n = 2;
|
|
23
|
-
return container.remove({
|
|
57
|
+
return withTimeout(container.remove({
|
|
24
58
|
force: true
|
|
25
|
-
});
|
|
59
|
+
}), 30000);
|
|
26
60
|
case 2:
|
|
27
61
|
_context.n = 4;
|
|
28
62
|
break;
|
|
@@ -37,11 +71,15 @@ function _removeContainer() {
|
|
|
37
71
|
}));
|
|
38
72
|
return _removeContainer.apply(this, arguments);
|
|
39
73
|
}
|
|
40
|
-
import { docker } from "../dockerService.js";
|
|
41
|
-
import { imageExists, pullImage } from "./imageUtils.js";
|
|
42
74
|
export function createContainer(_x2) {
|
|
43
75
|
return _createContainer.apply(this, arguments);
|
|
44
76
|
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Start a container by id.
|
|
80
|
+
* @param {string} containerId - Docker container id
|
|
81
|
+
* @returns {Promise<void>}
|
|
82
|
+
*/
|
|
45
83
|
function _createContainer() {
|
|
46
84
|
_createContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(imageName) {
|
|
47
85
|
var options,
|
|
@@ -58,7 +96,7 @@ function _createContainer() {
|
|
|
58
96
|
options = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
|
|
59
97
|
_context2.p = 1;
|
|
60
98
|
_context2.n = 2;
|
|
61
|
-
return imageExists(imageName);
|
|
99
|
+
return withTimeout(imageExists(imageName), 10000);
|
|
62
100
|
case 2:
|
|
63
101
|
exists = _context2.v;
|
|
64
102
|
_context2.n = 4;
|
|
@@ -74,7 +112,7 @@ function _createContainer() {
|
|
|
74
112
|
}
|
|
75
113
|
_context2.p = 5;
|
|
76
114
|
_context2.n = 6;
|
|
77
|
-
return pullImage(imageName);
|
|
115
|
+
return withTimeout(pullImage(imageName), 300000);
|
|
78
116
|
case 6:
|
|
79
117
|
_context2.n = 8;
|
|
80
118
|
break;
|
|
@@ -89,7 +127,7 @@ function _createContainer() {
|
|
|
89
127
|
}, options);
|
|
90
128
|
_context2.p = 9;
|
|
91
129
|
_context2.n = 10;
|
|
92
|
-
return docker.createContainer(createOpts);
|
|
130
|
+
return withTimeout(docker.createContainer(createOpts), 30000);
|
|
93
131
|
case 10:
|
|
94
132
|
container = _context2.v;
|
|
95
133
|
return _context2.a(2, container.id || container.Id);
|
|
@@ -107,6 +145,12 @@ function _createContainer() {
|
|
|
107
145
|
export function startContainer(_x3) {
|
|
108
146
|
return _startContainer.apply(this, arguments);
|
|
109
147
|
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Stop a container by id.
|
|
151
|
+
* @param {string} containerId - Docker container id
|
|
152
|
+
* @returns {Promise<void>}
|
|
153
|
+
*/
|
|
110
154
|
function _startContainer() {
|
|
111
155
|
_startContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(containerId) {
|
|
112
156
|
var container;
|
|
@@ -115,7 +159,7 @@ function _startContainer() {
|
|
|
115
159
|
case 0:
|
|
116
160
|
container = docker.getContainer(containerId);
|
|
117
161
|
_context3.n = 1;
|
|
118
|
-
return container.start();
|
|
162
|
+
return withTimeout(container.start(), 30000);
|
|
119
163
|
case 1:
|
|
120
164
|
return _context3.a(2);
|
|
121
165
|
}
|
|
@@ -126,6 +170,12 @@ function _startContainer() {
|
|
|
126
170
|
export function stopContainer(_x4) {
|
|
127
171
|
return _stopContainer.apply(this, arguments);
|
|
128
172
|
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Restart a container by id.
|
|
176
|
+
* @param {string} containerId - Docker container id
|
|
177
|
+
* @returns {Promise<void>}
|
|
178
|
+
*/
|
|
129
179
|
function _stopContainer() {
|
|
130
180
|
_stopContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(containerId) {
|
|
131
181
|
var container;
|
|
@@ -134,7 +184,7 @@ function _stopContainer() {
|
|
|
134
184
|
case 0:
|
|
135
185
|
container = docker.getContainer(containerId);
|
|
136
186
|
_context4.n = 1;
|
|
137
|
-
return container.stop();
|
|
187
|
+
return withTimeout(container.stop(), 30000);
|
|
138
188
|
case 1:
|
|
139
189
|
return _context4.a(2);
|
|
140
190
|
}
|
|
@@ -153,7 +203,7 @@ function _restartContainer() {
|
|
|
153
203
|
case 0:
|
|
154
204
|
container = docker.getContainer(containerId);
|
|
155
205
|
_context5.n = 1;
|
|
156
|
-
return container.restart();
|
|
206
|
+
return withTimeout(container.restart(), 30000);
|
|
157
207
|
case 1:
|
|
158
208
|
return _context5.a(2);
|
|
159
209
|
}
|
|
@@ -9,6 +9,11 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
9
9
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
10
10
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
11
11
|
import { docker } from "../dockerService.js";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Return a list of containers with normalized fields for the UI.
|
|
15
|
+
* @returns {Promise<Array<Object>>}
|
|
16
|
+
*/
|
|
12
17
|
export function getContainers() {
|
|
13
18
|
return _getContainers.apply(this, arguments);
|
|
14
19
|
}
|
|
@@ -27,7 +32,7 @@ function _getContainers() {
|
|
|
27
32
|
return _context.a(2, containers.map(function (container) {
|
|
28
33
|
return {
|
|
29
34
|
id: container.Id,
|
|
30
|
-
name: container.Names[0].replace("/", ""),
|
|
35
|
+
name: (container.Names && container.Names[0] || 'Unknown').replace("/", ""),
|
|
31
36
|
image: container.Image,
|
|
32
37
|
state: container.State,
|
|
33
38
|
status: container.Status,
|
|
@@ -40,7 +45,7 @@ function _getContainers() {
|
|
|
40
45
|
if (publicPorts.length > 0) {
|
|
41
46
|
return _toConsumableArray(new Set(publicPorts));
|
|
42
47
|
}
|
|
43
|
-
//
|
|
48
|
+
// If there are no public ports, show private exposed ports
|
|
44
49
|
var privatePorts = container.Ports.filter(function (port) {
|
|
45
50
|
return port.PrivatePort;
|
|
46
51
|
}).map(function (port) {
|
|
@@ -1,24 +1,37 @@
|
|
|
1
1
|
import { docker } from "../dockerService.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Return a stream of logs from a container and call callbacks for events.
|
|
5
|
+
*
|
|
6
|
+
* @param {string} containerId - Docker container id
|
|
7
|
+
* @param {Function} onData - Called with chunk string when data arrives
|
|
8
|
+
* @param {Function} onEnd - Called when stream ends
|
|
9
|
+
* @param {Function} onError - Called on error
|
|
10
|
+
*/
|
|
2
11
|
export function getLogsStream(containerId, onData, onEnd, onError) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
try {
|
|
13
|
+
var container = docker.getContainer(containerId);
|
|
14
|
+
container.logs({
|
|
15
|
+
follow: true,
|
|
16
|
+
stdout: true,
|
|
17
|
+
stderr: true,
|
|
18
|
+
tail: 100
|
|
19
|
+
}, function (err, stream) {
|
|
20
|
+
if (err) {
|
|
21
|
+
onError === null || onError === void 0 || onError(err);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
stream.on('data', function (chunk) {
|
|
25
|
+
return onData === null || onData === void 0 ? void 0 : onData(chunk.toString());
|
|
26
|
+
});
|
|
27
|
+
stream.on('end', function () {
|
|
28
|
+
return onEnd === null || onEnd === void 0 ? void 0 : onEnd();
|
|
29
|
+
});
|
|
30
|
+
stream.on('error', function (err) {
|
|
31
|
+
return onError === null || onError === void 0 ? void 0 : onError(err);
|
|
32
|
+
});
|
|
16
33
|
});
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
stream.on('error', function (err) {
|
|
21
|
-
return onError === null || onError === void 0 ? void 0 : onError(err);
|
|
22
|
-
});
|
|
23
|
-
});
|
|
34
|
+
} catch (err) {
|
|
35
|
+
onError === null || onError === void 0 || onError(err);
|
|
36
|
+
}
|
|
24
37
|
}
|
|
@@ -3,12 +3,20 @@ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try {
|
|
|
3
3
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
4
4
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
5
5
|
import { docker } from "../dockerService.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Retrieve a snapshot of container resource usage (CPU, memory, network).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} containerId - Docker container id
|
|
11
|
+
* @returns {Promise<Object>} Object with cpuPercent, memPercent and netIO {rx,tx}
|
|
12
|
+
*/
|
|
6
13
|
export function getStats(_x) {
|
|
7
14
|
return _getStats.apply(this, arguments);
|
|
8
15
|
}
|
|
9
16
|
function _getStats() {
|
|
10
17
|
_getStats = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(containerId) {
|
|
11
|
-
var
|
|
18
|
+
var _stream$cpu_stats$cpu;
|
|
19
|
+
var container, stream, cpuDelta, systemDelta, numCpus, cpuPercent, memUsage, memLimit, memPercent, rx, tx;
|
|
12
20
|
return _regenerator().w(function (_context) {
|
|
13
21
|
while (1) switch (_context.n) {
|
|
14
22
|
case 0:
|
|
@@ -20,8 +28,9 @@ function _getStats() {
|
|
|
20
28
|
case 1:
|
|
21
29
|
stream = _context.v;
|
|
22
30
|
cpuDelta = stream.cpu_stats.cpu_usage.total_usage - stream.precpu_stats.cpu_usage.total_usage;
|
|
23
|
-
systemDelta = stream.cpu_stats.system_cpu_usage - stream.precpu_stats.system_cpu_usage;
|
|
24
|
-
|
|
31
|
+
systemDelta = stream.cpu_stats.system_cpu_usage - stream.precpu_stats.system_cpu_usage; // Get number of CPUs for proper normalization
|
|
32
|
+
numCpus = stream.cpu_stats.online_cpus || ((_stream$cpu_stats$cpu = stream.cpu_stats.cpu_usage.percpu_usage) === null || _stream$cpu_stats$cpu === void 0 ? void 0 : _stream$cpu_stats$cpu.length) || 1; // Calculate normalized CPU percentage
|
|
33
|
+
cpuPercent = systemDelta > 0 ? cpuDelta / systemDelta * numCpus * 100 : 0;
|
|
25
34
|
memUsage = stream.memory_stats.usage || 0;
|
|
26
35
|
memLimit = stream.memory_stats.limit || 1;
|
|
27
36
|
memPercent = memUsage / memLimit * 100;
|
|
@@ -3,9 +3,21 @@ function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try {
|
|
|
3
3
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
4
4
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
5
5
|
import { docker } from "../dockerService.js";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check whether an image exists locally.
|
|
9
|
+
* @param {string} imageName - Image name or tag
|
|
10
|
+
* @returns {Promise<boolean>}
|
|
11
|
+
*/
|
|
6
12
|
export function imageExists(_x) {
|
|
7
13
|
return _imageExists.apply(this, arguments);
|
|
8
14
|
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Pull an image from the registry.
|
|
18
|
+
* @param {string} imageName - Image name to pull
|
|
19
|
+
* @returns {Promise<void>}
|
|
20
|
+
*/
|
|
9
21
|
function _imageExists() {
|
|
10
22
|
_imageExists = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(imageName) {
|
|
11
23
|
var images;
|
|
@@ -1,4 +1,15 @@
|
|
|
1
1
|
import { spawn } from "child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Show an exit message using provided setters, clear the terminal and exit after a delay.
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} params
|
|
7
|
+
* @param {Function} params.setMessage - Setter for message text
|
|
8
|
+
* @param {Function} params.setMessageColor - Setter for message color
|
|
9
|
+
* @param {string} [params.message] - Message to display
|
|
10
|
+
* @param {string} [params.color] - Color for the message
|
|
11
|
+
* @param {number} [params.delay] - Delay in milliseconds before exiting
|
|
12
|
+
*/
|
|
2
13
|
export function exitWithMessage(_ref) {
|
|
3
14
|
var setMessage = _ref.setMessage,
|
|
4
15
|
setMessageColor = _ref.setMessageColor,
|
|
@@ -21,6 +21,19 @@ export function validatePorts(portInput) {
|
|
|
21
21
|
return !invalid;
|
|
22
22
|
}
|
|
23
23
|
export function validateEnvVars(envInput) {
|
|
24
|
-
//
|
|
25
|
-
|
|
24
|
+
if (!envInput || !envInput.trim()) return true; // Empty is valid
|
|
25
|
+
|
|
26
|
+
var vars = envInput.split(",").map(function (v) {
|
|
27
|
+
return v.trim();
|
|
28
|
+
}).filter(Boolean);
|
|
29
|
+
var invalid = vars.find(function (v) {
|
|
30
|
+
var parts = v.split("=");
|
|
31
|
+
// Must have at least VAR=value format
|
|
32
|
+
if (parts.length < 2) return true;
|
|
33
|
+
var varName = parts[0].trim();
|
|
34
|
+
// Variable names should be alphanumeric with underscores
|
|
35
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(varName)) return true;
|
|
36
|
+
return false;
|
|
37
|
+
});
|
|
38
|
+
return !invalid;
|
|
26
39
|
}
|
|
@@ -73,12 +73,8 @@ export function useContainerCreation(_ref) {
|
|
|
73
73
|
return;
|
|
74
74
|
}
|
|
75
75
|
if (step === 2) {
|
|
76
|
-
if
|
|
77
|
-
|
|
78
|
-
setMessageColor("red");
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
81
|
-
if (!validatePorts(portInput)) {
|
|
76
|
+
// Ports are now optional - only validate if provided
|
|
77
|
+
if (portInput.trim() && !validatePorts(portInput)) {
|
|
82
78
|
setMessage("Port format must be host:container and both must be numbers (e.g. 8080:80)");
|
|
83
79
|
setMessageColor("red");
|
|
84
80
|
return;
|
|
@@ -10,12 +10,10 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
|
|
|
10
10
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
11
11
|
/**
|
|
12
12
|
* React hook to manage Docker containers state.
|
|
13
|
-
* Hook de React para gestionar el estado de contenedores Docker.
|
|
14
13
|
*
|
|
15
14
|
* @returns {[Array, Function]} [containers, refresh] / [contenedores, refrescar]
|
|
16
15
|
* @example
|
|
17
|
-
* //
|
|
18
|
-
* // ES: Usar en un componente
|
|
16
|
+
* // Use in a component
|
|
19
17
|
* const [containers, refresh] = useContainers();
|
|
20
18
|
*/
|
|
21
19
|
import React, { useState, useEffect } from "react";
|
|
@@ -21,6 +21,29 @@ import { getLogsStream } from "../helpers/dockerService/serviceComponents/contai
|
|
|
21
21
|
import { createContainer as svcCreateContainer } from "../helpers/dockerService/serviceComponents/containerActions.js";
|
|
22
22
|
|
|
23
23
|
// Principal hook to manage user inputs and control the app state
|
|
24
|
+
/**
|
|
25
|
+
* Main hook that wires user input, creation, actions and logs viewing.
|
|
26
|
+
* It coordinates the modular hooks and exposes a compact API consumed by the App.
|
|
27
|
+
*
|
|
28
|
+
* @param {Array<Object>} containers - Current list of Docker containers
|
|
29
|
+
* @returns {Object} controls - API for the App component
|
|
30
|
+
* @property {number} selected - Index of the currently selected container
|
|
31
|
+
* @property {function} setSelected - Setter for selected index
|
|
32
|
+
* @property {string} message - Current feedback message (creation or actions)
|
|
33
|
+
* @property {string} messageColor - Color to show for the feedback message
|
|
34
|
+
* @property {boolean} showLogs - Whether the logs viewer is active
|
|
35
|
+
* @property {Array<string>} logs - Array of log lines currently collected
|
|
36
|
+
* @property {function} exitLogs - Helper to close the logs viewer
|
|
37
|
+
* @property {boolean} creatingContainer - Whether the create-container prompt is open
|
|
38
|
+
* @property {number} creationStep - Current step in the creation flow
|
|
39
|
+
* @property {string} imageNameInput - Current value of the image name field
|
|
40
|
+
* @property {string} containerNameInput - Current value of the container name field
|
|
41
|
+
* @property {string} portInput - Current value of the port input field
|
|
42
|
+
* @property {string} envInput - Current value of the env input field
|
|
43
|
+
* @property {Object} creation - The creation hook API (setters and helpers)
|
|
44
|
+
* @property {Object} actions - The actions hook API (helpers to start/stop/remove)
|
|
45
|
+
* @property {Object} logsViewer - The logs viewer hook API
|
|
46
|
+
*/
|
|
24
47
|
export function useControls() {
|
|
25
48
|
var containers = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];
|
|
26
49
|
var _React$useState = React.useState(0),
|
|
@@ -120,7 +143,7 @@ export function useControls() {
|
|
|
120
143
|
// Handler to exit logs (delegated to logsViewer)
|
|
121
144
|
var exitLogs = logsViewer.closeLogs;
|
|
122
145
|
useInput(function (input, key) {
|
|
123
|
-
//
|
|
146
|
+
// Erase confirmation
|
|
124
147
|
if (confirmErase) {
|
|
125
148
|
if (input === "y" || input === "Y") {
|
|
126
149
|
actions.handleAction({
|
|
@@ -318,7 +341,9 @@ export function useControls() {
|
|
|
318
341
|
logsViewer.openLogs();
|
|
319
342
|
getLogsStream(containers[selected].id, function (data) {
|
|
320
343
|
return logsViewer.setLogs(function (prev) {
|
|
321
|
-
|
|
344
|
+
var newLogs = [].concat(_toConsumableArray(prev), _toConsumableArray(data.split("\n").filter(Boolean)));
|
|
345
|
+
// Limit to last 1000 lines to prevent memory leak
|
|
346
|
+
return newLogs.slice(-1000);
|
|
322
347
|
});
|
|
323
348
|
}, function () {}, function (err) {
|
|
324
349
|
return logsViewer.setLogs(function (prev) {
|
|
@@ -6,6 +6,12 @@ function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(
|
|
|
6
6
|
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
7
7
|
import React, { useRef, useCallback } from "react";
|
|
8
8
|
import { getLogsStream } from "../helpers/dockerService/serviceComponents/containerLogs.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Hook that manages opening and closing a logs stream for a container.
|
|
12
|
+
*
|
|
13
|
+
* @returns {Object} { openLogs, closeLogs }
|
|
14
|
+
*/
|
|
9
15
|
export function useLogsStream() {
|
|
10
16
|
var logsStreamRef = useRef(null);
|
|
11
17
|
var openLogs = useCallback(function (containerId, setLogs) {
|
package/dist/index.js
CHANGED
|
@@ -1,13 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
|
|
3
2
|
/**
|
|
4
3
|
* Entry point for the CDD CLI application.
|
|
5
|
-
* Punto de entrada para la aplicación CLI de CDD.
|
|
6
4
|
*
|
|
7
5
|
* @module index
|
|
8
6
|
* @example
|
|
9
|
-
* //
|
|
10
|
-
* // ES: Ejecutar el CLI
|
|
7
|
+
* // Run the CLI
|
|
11
8
|
* node index.js
|
|
12
9
|
*/
|
|
13
10
|
import React from "react";
|
package/package.json
CHANGED
|
@@ -30,19 +30,31 @@ export default function ContainerRow({ container }) {
|
|
|
30
30
|
const [statsError, setStatsError] = useState("");
|
|
31
31
|
useEffect(() => {
|
|
32
32
|
if (state !== "running") return;
|
|
33
|
+
|
|
34
|
+
let isMounted = true;
|
|
35
|
+
|
|
33
36
|
const fetchStats = async () => {
|
|
34
37
|
try {
|
|
35
38
|
const s = await getStats(id);
|
|
36
|
-
|
|
37
|
-
|
|
39
|
+
if (isMounted) {
|
|
40
|
+
setStats(s);
|
|
41
|
+
setStatsError("");
|
|
42
|
+
}
|
|
38
43
|
} catch (err) {
|
|
39
|
-
|
|
40
|
-
|
|
44
|
+
if (isMounted) {
|
|
45
|
+
setStats({ cpuPercent: 0, memPercent: 0, netIO: { rx: 0, tx: 0 } });
|
|
46
|
+
setStatsError("Error fetching stats");
|
|
47
|
+
}
|
|
41
48
|
}
|
|
42
49
|
};
|
|
50
|
+
|
|
43
51
|
fetchStats();
|
|
44
52
|
const timer = setInterval(fetchStats, 1500);
|
|
45
|
-
|
|
53
|
+
|
|
54
|
+
return () => {
|
|
55
|
+
isMounted = false;
|
|
56
|
+
clearInterval(timer);
|
|
57
|
+
};
|
|
46
58
|
}, [id, state]);
|
|
47
59
|
|
|
48
60
|
const stateInfo = stateText(state);
|
|
@@ -32,7 +32,7 @@ export async function handleAction({
|
|
|
32
32
|
setMessageColor("green");
|
|
33
33
|
try {
|
|
34
34
|
await actionFn(c.id);
|
|
35
|
-
setMessage(`${actionLabel} container
|
|
35
|
+
setMessage(`${actionLabel} container completed successfully`);
|
|
36
36
|
setMessageColor("green");
|
|
37
37
|
setTimeout(() => setMessage(""), 3000);
|
|
38
38
|
} catch (err) {
|
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import Docker from "dockerode";
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
// Use default dockerode configuration which automatically handles:
|
|
4
|
+
// - /var/run/docker.sock on Linux/Mac
|
|
5
|
+
// - //./pipe/docker_engine on Windows
|
|
6
|
+
// - Environment variables DOCKER_HOST, DOCKER_CERT_PATH, etc.
|
|
7
|
+
const docker = new Docker();
|
|
8
|
+
|
|
3
9
|
export { docker };
|
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
import { docker } from "../dockerService";
|
|
2
|
+
import { imageExists, pullImage } from "./imageUtils.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Helper to add timeout to promises
|
|
6
|
+
* @param {Promise} promise - Promise to wrap
|
|
7
|
+
* @param {number} ms - Timeout in milliseconds
|
|
8
|
+
* @returns {Promise}
|
|
9
|
+
*/
|
|
10
|
+
function withTimeout(promise, ms = 30000) {
|
|
11
|
+
return Promise.race([
|
|
12
|
+
promise,
|
|
13
|
+
new Promise((_, reject) =>
|
|
14
|
+
setTimeout(() => reject(new Error('Operation timed out')), ms)
|
|
15
|
+
)
|
|
16
|
+
]);
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
/**
|
|
2
20
|
* Remove (delete) a container by id. Force removal so running containers are stopped first.
|
|
3
21
|
*
|
|
@@ -8,13 +26,11 @@
|
|
|
8
26
|
export async function removeContainer(containerId) {
|
|
9
27
|
const container = docker.getContainer(containerId);
|
|
10
28
|
try {
|
|
11
|
-
await container.remove({ force: true });
|
|
29
|
+
await withTimeout(container.remove({ force: true }), 30000);
|
|
12
30
|
} catch (err) {
|
|
13
31
|
throw new Error('Error removing container: ' + err.message);
|
|
14
32
|
}
|
|
15
33
|
}
|
|
16
|
-
import { docker } from "../dockerService";
|
|
17
|
-
import { imageExists, pullImage } from "./imageUtils.js";
|
|
18
34
|
|
|
19
35
|
/**
|
|
20
36
|
* Create a new container from an image. If the image is missing locally, it will be pulled.
|
|
@@ -27,13 +43,13 @@ import { imageExists, pullImage } from "./imageUtils.js";
|
|
|
27
43
|
export async function createContainer(imageName, options = {}) {
|
|
28
44
|
let exists;
|
|
29
45
|
try {
|
|
30
|
-
exists = await imageExists(imageName);
|
|
46
|
+
exists = await withTimeout(imageExists(imageName), 10000);
|
|
31
47
|
} catch (err) {
|
|
32
48
|
throw new Error('Error listing local images: ' + err.message);
|
|
33
49
|
}
|
|
34
50
|
if (!exists) {
|
|
35
51
|
try {
|
|
36
|
-
await pullImage(imageName);
|
|
52
|
+
await withTimeout(pullImage(imageName), 300000); // 5 minutes for pull
|
|
37
53
|
} catch (err) {
|
|
38
54
|
throw new Error('Could not pull image: ' + err.message);
|
|
39
55
|
}
|
|
@@ -44,7 +60,7 @@ export async function createContainer(imageName, options = {}) {
|
|
|
44
60
|
...options,
|
|
45
61
|
};
|
|
46
62
|
try {
|
|
47
|
-
const container = await docker.createContainer(createOpts);
|
|
63
|
+
const container = await withTimeout(docker.createContainer(createOpts), 30000);
|
|
48
64
|
return container.id || container.Id;
|
|
49
65
|
} catch (err) {
|
|
50
66
|
throw new Error('Error creating container: ' + err.message);
|
|
@@ -58,7 +74,7 @@ export async function createContainer(imageName, options = {}) {
|
|
|
58
74
|
*/
|
|
59
75
|
export async function startContainer(containerId) {
|
|
60
76
|
const container = docker.getContainer(containerId);
|
|
61
|
-
await container.start();
|
|
77
|
+
await withTimeout(container.start(), 30000);
|
|
62
78
|
}
|
|
63
79
|
|
|
64
80
|
/**
|
|
@@ -68,7 +84,7 @@ export async function startContainer(containerId) {
|
|
|
68
84
|
*/
|
|
69
85
|
export async function stopContainer(containerId) {
|
|
70
86
|
const container = docker.getContainer(containerId);
|
|
71
|
-
await container.stop();
|
|
87
|
+
await withTimeout(container.stop(), 30000);
|
|
72
88
|
}
|
|
73
89
|
|
|
74
90
|
/**
|
|
@@ -78,5 +94,5 @@ export async function stopContainer(containerId) {
|
|
|
78
94
|
*/
|
|
79
95
|
export async function restartContainer(containerId) {
|
|
80
96
|
const container = docker.getContainer(containerId);
|
|
81
|
-
await container.restart();
|
|
97
|
+
await withTimeout(container.restart(), 30000);
|
|
82
98
|
}
|
|
@@ -8,7 +8,7 @@ export async function getContainers() {
|
|
|
8
8
|
const containers = await docker.listContainers({ all: true });
|
|
9
9
|
return containers.map((container) => ({
|
|
10
10
|
id: container.Id,
|
|
11
|
-
name: container.Names[0].replace("/", ""),
|
|
11
|
+
name: (container.Names && container.Names[0] || 'Unknown').replace("/", ""),
|
|
12
12
|
image: container.Image,
|
|
13
13
|
state: container.State,
|
|
14
14
|
status: container.Status,
|
|
@@ -9,19 +9,23 @@ import { docker } from "../dockerService";
|
|
|
9
9
|
* @param {Function} onError - Called on error
|
|
10
10
|
*/
|
|
11
11
|
export function getLogsStream(containerId, onData, onEnd, onError) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
12
|
+
try {
|
|
13
|
+
const container = docker.getContainer(containerId);
|
|
14
|
+
container.logs({
|
|
15
|
+
follow: true,
|
|
16
|
+
stdout: true,
|
|
17
|
+
stderr: true,
|
|
18
|
+
tail: 100
|
|
19
|
+
}, (err, stream) => {
|
|
20
|
+
if (err) {
|
|
21
|
+
onError?.(err);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
stream.on('data', chunk => onData?.(chunk.toString()));
|
|
25
|
+
stream.on('end', () => onEnd?.());
|
|
26
|
+
stream.on('error', err => onError?.(err));
|
|
27
|
+
});
|
|
28
|
+
} catch (err) {
|
|
29
|
+
onError?.(err);
|
|
30
|
+
}
|
|
27
31
|
}
|