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.
Files changed (33) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/FIXES_APPLIED.md +419 -0
  3. package/dist/App.js +7 -9
  4. package/dist/components/ContainerCreationPrompt.js +1 -1
  5. package/dist/components/ContainerList.js +2 -4
  6. package/dist/components/ContainerRow.js +20 -14
  7. package/dist/components/ContainerSection.js +1 -1
  8. package/dist/helpers/actionHelpers.js +14 -1
  9. package/dist/helpers/dockerService/dockerService.js +6 -3
  10. package/dist/helpers/dockerService/serviceComponents/containerActions.js +60 -10
  11. package/dist/helpers/dockerService/serviceComponents/containerList.js +7 -2
  12. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +33 -20
  13. package/dist/helpers/dockerService/serviceComponents/containerStats.js +12 -3
  14. package/dist/helpers/dockerService/serviceComponents/imageUtils.js +12 -0
  15. package/dist/helpers/exitWithMessage.js +11 -0
  16. package/dist/helpers/validationHelpers.js +15 -2
  17. package/dist/hooks/creation/useContainerCreation.js +2 -6
  18. package/dist/hooks/useContainers.js +1 -3
  19. package/dist/hooks/useControls.js +27 -2
  20. package/dist/hooks/useLogsStream.js +6 -0
  21. package/dist/index.js +1 -4
  22. package/package.json +1 -1
  23. package/src/components/ContainerRow.jsx +17 -5
  24. package/src/helpers/actionHelpers.js +1 -1
  25. package/src/helpers/dockerService/dockerService.js +7 -1
  26. package/src/helpers/dockerService/serviceComponents/containerActions.js +25 -9
  27. package/src/helpers/dockerService/serviceComponents/containerList.js +1 -1
  28. package/src/helpers/dockerService/serviceComponents/containerLogs.js +19 -15
  29. package/src/helpers/dockerService/serviceComponents/containerStats.js +9 -1
  30. package/src/helpers/validationHelpers.js +14 -2
  31. package/src/hooks/creation/useContainerCreation.js +2 -6
  32. package/src/hooks/useControls.js +5 -1
  33. 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
- // Si no hay puertos públicos, mostrar los privados expuestos
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
- var container = docker.getContainer(containerId);
4
- container.logs({
5
- follow: true,
6
- stdout: true,
7
- stderr: true,
8
- tail: 100
9
- }, function (err, stream) {
10
- if (err) {
11
- onError === null || onError === void 0 || onError(err);
12
- return;
13
- }
14
- stream.on('data', function (chunk) {
15
- return onData === null || onData === void 0 ? void 0 : onData(chunk.toString());
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
- stream.on('end', function () {
18
- return onEnd === null || onEnd === void 0 ? void 0 : onEnd();
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 container, stream, cpuDelta, systemDelta, cpuPercent, memUsage, memLimit, memPercent, rx, tx;
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
- cpuPercent = systemDelta > 0 ? cpuDelta / systemDelta * 100 : 0;
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
- // Future specific validations
25
- return true;
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 (!portInput.trim()) {
77
- setMessage("You must specify at least one port to expose (e.g. 8080:80)");
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
- * // EN: Use in a component
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
- // Confirmación de borrado
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
- return [].concat(_toConsumableArray(prev), _toConsumableArray(data.split("\n").filter(Boolean)));
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
- * // EN: Run the CLI
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdd-cli",
3
- "version": "3.1.2",
3
+ "version": "3.1.4",
4
4
  "description": "CLI Docker Dashboard",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -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
- setStats(s);
37
- setStatsError("");
39
+ if (isMounted) {
40
+ setStats(s);
41
+ setStatsError("");
42
+ }
38
43
  } catch (err) {
39
- setStats({ cpuPercent: 0, memPercent: 0, netIO: { rx: 0, tx: 0 } });
40
- setStatsError("Error fetching stats");
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
- return () => clearInterval(timer);
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
- const docker = new Docker({ socketPath: "/var/run/docker.sock" });
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
- const container = docker.getContainer(containerId);
13
- container.logs({
14
- follow: true,
15
- stdout: true,
16
- stderr: true,
17
- tail: 100
18
- }, (err, stream) => {
19
- if (err) {
20
- onError?.(err);
21
- return;
22
- }
23
- stream.on('data', chunk => onData?.(chunk.toString()));
24
- stream.on('end', () => onEnd?.());
25
- stream.on('error', err => onError?.(err));
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
  }