cdd-cli 3.1.4 → 3.1.6

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 (140) hide show
  1. package/.eslintrc.json +30 -0
  2. package/.github/workflows/docs.yml +35 -0
  3. package/CHANGELOG.md +37 -4
  4. package/README.md +26 -0
  5. package/_config.yml +13 -0
  6. package/dist/App.js +20 -3
  7. package/dist/components/ContainerCreationPrompt.js +27 -1
  8. package/dist/components/ContainerList.js +11 -15
  9. package/dist/components/ContainerRow.js +73 -14
  10. package/dist/components/ContainerSection.js +16 -2
  11. package/dist/components/Footer.js +7 -1
  12. package/dist/components/Header.js +27 -3
  13. package/dist/components/LogViewer.js +21 -3
  14. package/dist/components/MessageFeedback.js +15 -1
  15. package/dist/components/PromptField.js +31 -1
  16. package/dist/components/StatsBar.js +47 -8
  17. package/dist/components/UsageMenu.js +30 -1
  18. package/dist/helpers/actionHelpers.js +17 -6
  19. package/dist/helpers/appInfo.js +21 -0
  20. package/dist/helpers/constants.js +30 -0
  21. package/dist/helpers/dockerService/dockerService.js +19 -0
  22. package/dist/helpers/dockerService/serviceComponents/containerActions.js +194 -35
  23. package/dist/helpers/dockerService/serviceComponents/containerList.js +45 -8
  24. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +16 -6
  25. package/dist/helpers/dockerService/serviceComponents/containerStats.js +28 -9
  26. package/dist/helpers/exitWithMessage.js +2 -1
  27. package/dist/helpers/logger.js +126 -0
  28. package/dist/helpers/safeCall.js +21 -0
  29. package/dist/helpers/validationHelpers.js +20 -1
  30. package/dist/hooks/creation/useContainerActions.js +2 -1
  31. package/dist/hooks/creation/useContainerCreation.js +5 -4
  32. package/dist/hooks/creation/useLogsViewer.js +3 -2
  33. package/dist/hooks/useContainers.js +6 -3
  34. package/dist/hooks/useControls.js +253 -122
  35. package/dist/hooks/useLogsStream.js +3 -2
  36. package/dist/index.js +1 -0
  37. package/docs/_config.yml +13 -0
  38. package/docs/components_ContainerCreationPrompt.jsx.html +114 -0
  39. package/docs/components_ContainerRow.jsx.html +151 -0
  40. package/docs/components_ContainerSection.jsx.html +75 -0
  41. package/docs/components_Footer.jsx.html +66 -0
  42. package/docs/components_Header.jsx.html +84 -0
  43. package/docs/components_LogViewer.jsx.html +91 -0
  44. package/docs/components_MessageFeedback.jsx.html +76 -0
  45. package/docs/components_PromptField.jsx.html +99 -0
  46. package/docs/components_StatsBar.jsx.html +105 -0
  47. package/docs/components_UsageMenu.jsx.html +73 -0
  48. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  49. package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
  50. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  51. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  52. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  53. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  54. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  55. package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
  56. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  57. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  58. package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
  59. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  60. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  61. package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  62. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  63. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  64. package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
  65. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  66. package/docs/global.html +6145 -0
  67. package/docs/helpers_actionHelpers.js.html +94 -0
  68. package/docs/helpers_constants.js.html +81 -0
  69. package/docs/helpers_dockerService_dockerService.js.html +79 -0
  70. package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +153 -0
  71. package/docs/helpers_dockerService_serviceComponents_containerList.js.html +81 -0
  72. package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +85 -0
  73. package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +107 -0
  74. package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +82 -0
  75. package/docs/helpers_exitWithMessage.js.html +77 -0
  76. package/docs/helpers_safeCall.js.html +68 -0
  77. package/docs/helpers_validationHelpers.js.html +96 -0
  78. package/docs/hooks_creation_useContainerActions.js.html +120 -0
  79. package/docs/hooks_creation_useContainerCreation.js.html +152 -0
  80. package/docs/hooks_creation_useLogsViewer.js.html +102 -0
  81. package/docs/hooks_useControls.js.html +298 -0
  82. package/docs/hooks_useLogsStream.js.html +82 -0
  83. package/docs/index.html +65 -0
  84. package/docs/index.js.html +65 -0
  85. package/docs/module-index.html +171 -0
  86. package/docs/scripts/linenumber.js +25 -0
  87. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  88. package/docs/scripts/prettify/lang-css.js +2 -0
  89. package/docs/scripts/prettify/prettify.js +28 -0
  90. package/docs/styles/jsdoc-default.css +358 -0
  91. package/docs/styles/prettify-jsdoc.css +111 -0
  92. package/docs/styles/prettify-tomorrow.css +132 -0
  93. package/fix-imports.cjs +33 -6
  94. package/jest.config.cjs +2 -1
  95. package/package.json +19 -9
  96. package/src/App.jsx +16 -2
  97. package/src/components/ContainerCreationPrompt.jsx +26 -0
  98. package/src/components/ContainerList.jsx +12 -13
  99. package/src/components/ContainerRow.jsx +55 -20
  100. package/src/components/ContainerSection.jsx +15 -1
  101. package/src/components/Footer.jsx +6 -1
  102. package/src/components/Header.jsx +27 -3
  103. package/src/components/LogViewer.jsx +19 -2
  104. package/src/components/MessageFeedback.jsx +14 -0
  105. package/src/components/PromptField.jsx +30 -0
  106. package/src/components/StatsBar.jsx +47 -8
  107. package/src/components/UsageMenu.jsx +38 -8
  108. package/src/helpers/actionHelpers.js +16 -7
  109. package/src/helpers/appInfo.js +23 -0
  110. package/src/helpers/constants.js +30 -0
  111. package/src/helpers/dockerService/dockerService.js +19 -0
  112. package/src/helpers/dockerService/serviceComponents/containerActions.js +123 -12
  113. package/src/helpers/dockerService/serviceComponents/containerList.js +61 -23
  114. package/src/helpers/dockerService/serviceComponents/containerLogs.js +20 -6
  115. package/src/helpers/dockerService/serviceComponents/containerStats.js +24 -5
  116. package/src/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  117. package/src/helpers/exitWithMessage.js +2 -1
  118. package/src/helpers/logger.js +113 -0
  119. package/src/helpers/safeCall.js +18 -0
  120. package/src/helpers/validationHelpers.js +21 -3
  121. package/src/hooks/creation/useContainerActions.js +4 -3
  122. package/src/hooks/creation/useContainerCreation.js +5 -4
  123. package/src/hooks/creation/useLogsViewer.js +2 -1
  124. package/src/hooks/useContainers.js +6 -3
  125. package/src/hooks/useControls.js +244 -91
  126. package/src/hooks/useLogsStream.js +2 -1
  127. package/src/index.js +1 -0
  128. package/test/actionHelpers.test.js +106 -0
  129. package/test/containerActions.test.js +138 -0
  130. package/test/containerList.test.js +79 -0
  131. package/test/jest.setup.js +7 -0
  132. package/test/safeCall.test.js +25 -0
  133. package/test/useContainerCreation.dom.test.js +97 -0
  134. package/test/validationHelpers.test.js +2 -2
  135. package/dist/cdd.bundle.js +0 -8
  136. package/dist/components/ContainerCreator.js +0 -81
  137. package/dist/components/StatsViewer.js +0 -0
  138. package/dist/helpers/dockerActions.js +0 -64
  139. package/dist/helpers/dockerService.js +0 -284
  140. package/dist/hooks/useStatsPolling.js +0 -54
@@ -9,6 +9,33 @@ 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
+ import { logger } from "../../logger.js";
13
+ var DEFAULT_CONTAINER_NAME = "Unknown";
14
+
15
+ /**
16
+ * Produce a safe, display-friendly container name from the raw Docker payload.
17
+ *
18
+ * @param {Object} container Docker container summary as returned by dockerode.
19
+ * @returns {string} Normalized name without leading slashes and with a fallback applied.
20
+ */
21
+ function normalizeContainerName(container) {
22
+ var names = container === null || container === void 0 ? void 0 : container.Names;
23
+ var rawName;
24
+ if (Array.isArray(names) && names.length > 0 && typeof names[0] === "string") {
25
+ rawName = names[0];
26
+ } else if (typeof names === "string" && names.trim().length > 0) {
27
+ rawName = names;
28
+ } else if (typeof (container === null || container === void 0 ? void 0 : container.Name) === "string" && container.Name.trim().length > 0) {
29
+ rawName = container.Name;
30
+ } else {
31
+ rawName = DEFAULT_CONTAINER_NAME;
32
+ }
33
+ if (typeof rawName !== "string") {
34
+ return DEFAULT_CONTAINER_NAME;
35
+ }
36
+ var cleanedName = rawName.replace(/^\/+/, "").trim();
37
+ return cleanedName.length > 0 ? cleanedName : DEFAULT_CONTAINER_NAME;
38
+ }
12
39
 
13
40
  /**
14
41
  * Return a list of containers with normalized fields for the UI.
@@ -19,20 +46,23 @@ export function getContainers() {
19
46
  }
20
47
  function _getContainers() {
21
48
  _getContainers = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
22
- var containers;
49
+ var containers, _t;
23
50
  return _regenerator().w(function (_context) {
24
- while (1) switch (_context.n) {
51
+ while (1) switch (_context.p = _context.n) {
25
52
  case 0:
26
- _context.n = 1;
53
+ logger.debug("Listing containers");
54
+ _context.p = 1;
55
+ _context.n = 2;
27
56
  return docker.listContainers({
28
57
  all: true
29
58
  });
30
- case 1:
59
+ case 2:
31
60
  containers = _context.v;
61
+ logger.debug("Retrieved %d containers", containers.length);
32
62
  return _context.a(2, containers.map(function (container) {
33
63
  return {
34
64
  id: container.Id,
35
- name: (container.Names && container.Names[0] || 'Unknown').replace("/", ""),
65
+ name: normalizeContainerName(container),
36
66
  image: container.Image,
37
67
  state: container.State,
38
68
  status: container.Status,
@@ -45,18 +75,25 @@ function _getContainers() {
45
75
  if (publicPorts.length > 0) {
46
76
  return _toConsumableArray(new Set(publicPorts));
47
77
  }
48
- // If there are no public ports, show private exposed ports
78
+ // Fall back to exposed private ports when there are no bindings.
49
79
  var privatePorts = container.Ports.filter(function (port) {
50
80
  return port.PrivatePort;
51
81
  }).map(function (port) {
52
- return ":".concat(port.PrivatePort);
82
+ return "".concat(port.PrivatePort);
53
83
  });
54
84
  return _toConsumableArray(new Set(privatePorts));
55
85
  }()
56
86
  };
57
87
  }));
88
+ case 3:
89
+ _context.p = 3;
90
+ _t = _context.v;
91
+ logger.error("Failed to list containers", _t);
92
+ throw _t;
93
+ case 4:
94
+ return _context.a(2);
58
95
  }
59
- }, _callee);
96
+ }, _callee, null, [[1, 3]]);
60
97
  }));
61
98
  return _getContainers.apply(this, arguments);
62
99
  }
@@ -1,4 +1,6 @@
1
1
  import { docker } from "../dockerService.js";
2
+ import { safeCall } from "../../safeCall.js";
3
+ import { logger } from "../../logger.js";
2
4
 
3
5
  /**
4
6
  * Return a stream of logs from a container and call callbacks for events.
@@ -9,8 +11,11 @@ import { docker } from "../dockerService.js";
9
11
  * @param {Function} onError - Called on error
10
12
  */
11
13
  export function getLogsStream(containerId, onData, onEnd, onError) {
14
+ // Use shared safeCall util to call optional callbacks safely
15
+
12
16
  try {
13
17
  var container = docker.getContainer(containerId);
18
+ logger.debug("Opening logs stream for container %s", containerId);
14
19
  container.logs({
15
20
  follow: true,
16
21
  stdout: true,
@@ -18,20 +23,25 @@ export function getLogsStream(containerId, onData, onEnd, onError) {
18
23
  tail: 100
19
24
  }, function (err, stream) {
20
25
  if (err) {
21
- onError === null || onError === void 0 || onError(err);
26
+ logger.error("Failed to open logs stream for container %s", containerId, err);
27
+ safeCall(onError, err);
22
28
  return;
23
29
  }
30
+ logger.info("Logs stream established for container %s", containerId);
24
31
  stream.on('data', function (chunk) {
25
- return onData === null || onData === void 0 ? void 0 : onData(chunk.toString());
32
+ return safeCall(onData, chunk.toString());
26
33
  });
27
34
  stream.on('end', function () {
28
- return onEnd === null || onEnd === void 0 ? void 0 : onEnd();
35
+ logger.debug("Logs stream ended for container %s", containerId);
36
+ safeCall(onEnd);
29
37
  });
30
- stream.on('error', function (err) {
31
- return onError === null || onError === void 0 ? void 0 : onError(err);
38
+ stream.on('error', function (streamErr) {
39
+ logger.error("Logs stream error for container %s", containerId, streamErr);
40
+ safeCall(onError, streamErr);
32
41
  });
33
42
  });
34
43
  } catch (err) {
35
- onError === null || onError === void 0 || onError(err);
44
+ logger.error("Unexpected error while opening logs stream for container %s", containerId, err);
45
+ safeCall(onError, err);
36
46
  }
37
47
  }
@@ -3,6 +3,7 @@ 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
+ import { logger } from "../../logger.js";
6
7
 
7
8
  /**
8
9
  * Retrieve a snapshot of container resource usage (CPU, memory, network).
@@ -15,21 +16,37 @@ export function getStats(_x) {
15
16
  }
16
17
  function _getStats() {
17
18
  _getStats = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(containerId) {
18
- var _stream$cpu_stats$cpu;
19
- var container, stream, cpuDelta, systemDelta, numCpus, cpuPercent, memUsage, memLimit, memPercent, rx, tx;
19
+ var container, stream, cpuDelta, systemDelta, numCpus, cpuPercent, memUsage, memLimit, memPercent, rx, tx, snapshot, _t;
20
20
  return _regenerator().w(function (_context) {
21
- while (1) switch (_context.n) {
21
+ while (1) switch (_context.p = _context.n) {
22
22
  case 0:
23
23
  container = docker.getContainer(containerId);
24
- _context.n = 1;
24
+ logger.debug("Fetching stats for container %s", containerId);
25
+ _context.p = 1;
26
+ _context.n = 2;
25
27
  return container.stats({
26
28
  stream: false
27
29
  });
28
- case 1:
30
+ case 2:
29
31
  stream = _context.v;
32
+ _context.n = 4;
33
+ break;
34
+ case 3:
35
+ _context.p = 3;
36
+ _t = _context.v;
37
+ logger.error("Failed to fetch stats for container %s", containerId, _t);
38
+ throw _t;
39
+ case 4:
30
40
  cpuDelta = stream.cpu_stats.cpu_usage.total_usage - stream.precpu_stats.cpu_usage.total_usage;
31
41
  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
42
+ numCpus = 1;
43
+ if (typeof stream.cpu_stats.online_cpus === 'number' && stream.cpu_stats.online_cpus > 0) {
44
+ numCpus = stream.cpu_stats.online_cpus;
45
+ } else if (stream.cpu_stats.cpu_usage && Array.isArray(stream.cpu_stats.cpu_usage.percpu_usage) && stream.cpu_stats.cpu_usage.percpu_usage.length > 0) {
46
+ numCpus = stream.cpu_stats.cpu_usage.percpu_usage.length;
47
+ }
48
+
49
+ // Calculate normalized CPU percentage
33
50
  cpuPercent = systemDelta > 0 ? cpuDelta / systemDelta * numCpus * 100 : 0;
34
51
  memUsage = stream.memory_stats.usage || 0;
35
52
  memLimit = stream.memory_stats.limit || 1;
@@ -44,16 +61,18 @@ function _getStats() {
44
61
  }).reduce(function (a, b) {
45
62
  return a + b;
46
63
  }, 0) : 0;
47
- return _context.a(2, {
64
+ snapshot = {
48
65
  cpuPercent: cpuPercent.toFixed(1),
49
66
  memPercent: memPercent.toFixed(1),
50
67
  netIO: {
51
68
  rx: rx,
52
69
  tx: tx
53
70
  }
54
- });
71
+ };
72
+ logger.debug("Stats fetched for container %s", containerId, snapshot);
73
+ return _context.a(2, snapshot);
55
74
  }
56
- }, _callee);
75
+ }, _callee, null, [[1, 3]]);
57
76
  }));
58
77
  return _getStats.apply(this, arguments);
59
78
  }
@@ -1,4 +1,5 @@
1
1
  import { spawn } from "child_process";
2
+ import { EXIT_DELAY } from "./constants.js";
2
3
 
3
4
  /**
4
5
  * Show an exit message using provided setters, clear the terminal and exit after a delay.
@@ -18,7 +19,7 @@ export function exitWithMessage(_ref) {
18
19
  _ref$color = _ref.color,
19
20
  color = _ref$color === void 0 ? "yellow" : _ref$color,
20
21
  _ref$delay = _ref.delay,
21
- delay = _ref$delay === void 0 ? 1500 : _ref$delay;
22
+ delay = _ref$delay === void 0 ? EXIT_DELAY : _ref$delay;
22
23
  setMessage(message);
23
24
  setMessageColor(color);
24
25
  setTimeout(function () {
@@ -0,0 +1,126 @@
1
+ var _ref, _parseLevel;
2
+ /**
3
+ * Supported log levels in ascending verbosity.
4
+ * Lower numeric values represent higher severity.
5
+ */
6
+ var LOG_LEVELS = {
7
+ error: 0,
8
+ warn: 1,
9
+ info: 2,
10
+ debug: 3
11
+ };
12
+
13
+ /**
14
+ * @typedef {Object} LogEntry
15
+ * @property {string} level Severity string (error, warn, info, debug)
16
+ * @property {string} message Primary log message
17
+ * @property {any[]} args Additional payload values forwarded to listeners
18
+ * @property {string} timestamp ISO timestamp when the entry was created
19
+ * @property {string} formatted Preformatted message used for console output
20
+ */
21
+
22
+ /**
23
+ * @callback LogListener
24
+ * @param {LogEntry} entry Log entry data emitted by the logger
25
+ */
26
+
27
+ function parseLevel(value) {
28
+ if (typeof value !== "string") {
29
+ return null;
30
+ }
31
+ var normalized = value.trim().toLowerCase();
32
+ if (normalized in LOG_LEVELS) {
33
+ return LOG_LEVELS[normalized];
34
+ }
35
+ return null;
36
+ }
37
+ var envLevel = (_ref = (_parseLevel = parseLevel(process.env.CDD_LOG_LEVEL)) !== null && _parseLevel !== void 0 ? _parseLevel : parseLevel(process.env.LOG_LEVEL)) !== null && _ref !== void 0 ? _ref : LOG_LEVELS.info;
38
+ var currentLevel = typeof envLevel === "number" ? envLevel : LOG_LEVELS.info;
39
+ var listeners = new Set();
40
+ function shouldLog(level) {
41
+ var target = LOG_LEVELS[level];
42
+ return typeof target === "number" && target <= currentLevel;
43
+ }
44
+ function log(level, message) {
45
+ if (!shouldLog(level)) {
46
+ return;
47
+ }
48
+ var timestamp = new Date().toISOString();
49
+ var upperLevel = level.toUpperCase();
50
+ var formatted = "[".concat(timestamp, "] [").concat(upperLevel, "] ").concat(message);
51
+ for (var _len = arguments.length, args = new Array(_len > 2 ? _len - 2 : 0), _key = 2; _key < _len; _key++) {
52
+ args[_key - 2] = arguments[_key];
53
+ }
54
+ var entry = {
55
+ level: level,
56
+ message: message,
57
+ args: args,
58
+ timestamp: timestamp,
59
+ formatted: formatted
60
+ };
61
+ var shouldWriteToConsole = listeners.size === 0 || level === "error" || level === "warn";
62
+ if (shouldWriteToConsole) {
63
+ var method = level === "debug" ? "log" : level;
64
+ if (typeof console[method] === "function") {
65
+ var _console;
66
+ (_console = console)[method].apply(_console, [formatted].concat(args));
67
+ } else {
68
+ var _console2;
69
+ (_console2 = console).log.apply(_console2, [formatted].concat(args));
70
+ }
71
+ }
72
+ listeners.forEach(function (listener) {
73
+ try {
74
+ listener(entry);
75
+ } catch (err) {
76
+ // Ignore listener errors so logging remains resilient.
77
+ }
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Shared logger instance with leveled output and in-memory subscriptions.
83
+ * Consumers can import `{ logger }` to emit messages or to listen for
84
+ * log entries (used by the CLI debug panel).
85
+ */
86
+ export var logger = {
87
+ debug: function debug(message) {
88
+ for (var _len2 = arguments.length, args = new Array(_len2 > 1 ? _len2 - 1 : 0), _key2 = 1; _key2 < _len2; _key2++) {
89
+ args[_key2 - 1] = arguments[_key2];
90
+ }
91
+ log.apply(void 0, ["debug", message].concat(args));
92
+ },
93
+ info: function info(message) {
94
+ for (var _len3 = arguments.length, args = new Array(_len3 > 1 ? _len3 - 1 : 0), _key3 = 1; _key3 < _len3; _key3++) {
95
+ args[_key3 - 1] = arguments[_key3];
96
+ }
97
+ log.apply(void 0, ["info", message].concat(args));
98
+ },
99
+ warn: function warn(message) {
100
+ for (var _len4 = arguments.length, args = new Array(_len4 > 1 ? _len4 - 1 : 0), _key4 = 1; _key4 < _len4; _key4++) {
101
+ args[_key4 - 1] = arguments[_key4];
102
+ }
103
+ log.apply(void 0, ["warn", message].concat(args));
104
+ },
105
+ error: function error(message) {
106
+ for (var _len5 = arguments.length, args = new Array(_len5 > 1 ? _len5 - 1 : 0), _key5 = 1; _key5 < _len5; _key5++) {
107
+ args[_key5 - 1] = arguments[_key5];
108
+ }
109
+ log.apply(void 0, ["error", message].concat(args));
110
+ },
111
+ /**
112
+ * Subscribe to log events.
113
+ *
114
+ * @param {LogListener} listener Listener invoked for every log entry that passes the current level filter.
115
+ * @returns {function(): void} Cleanup function that removes the listener when called.
116
+ */
117
+ subscribe: function subscribe(listener) {
118
+ if (typeof listener !== "function") {
119
+ return function () {};
120
+ }
121
+ listeners.add(listener);
122
+ return function () {
123
+ return listeners["delete"](listener);
124
+ };
125
+ }
126
+ };
@@ -0,0 +1,21 @@
1
+ import { logger } from "./logger.js";
2
+
3
+ /**
4
+ * Call a function safely: if it's a function call it with provided args and
5
+ * catch any errors so they don't bubble to the host application.
6
+ *
7
+ * @param {Function} fn
8
+ * @param {...any} args
9
+ */
10
+ export function safeCall(fn) {
11
+ if (typeof fn === 'function') {
12
+ try {
13
+ for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
14
+ args[_key - 1] = arguments[_key];
15
+ }
16
+ return fn.apply(void 0, args);
17
+ } catch (e) {
18
+ logger.error("safeCall caught error", e);
19
+ }
20
+ }
21
+ }
@@ -6,11 +6,20 @@ function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" !=
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
7
  // Reusable validations for ports and environment variables
8
8
 
9
+ /**
10
+ * Validate a comma-separated list of port mappings in the form "host:container".
11
+ * Examples of valid input: "8080:80, 3000:3000"
12
+ *
13
+ * @param {string} portInput - Comma-separated port mappings
14
+ * @returns {boolean} True when all mappings are valid, false otherwise
15
+ */
9
16
  export function validatePorts(portInput) {
17
+ // Empty or whitespace-only input is considered valid (ports are optional)
18
+ if (!portInput || !portInput.trim()) return true;
10
19
  var ports = portInput.split(",").map(function (p) {
11
20
  return p.trim();
12
21
  }).filter(Boolean);
13
- if (ports.length === 0) return false;
22
+ if (ports.length === 0) return true;
14
23
  var invalid = ports.find(function (pair) {
15
24
  var _pair$split = pair.split(":"),
16
25
  _pair$split2 = _slicedToArray(_pair$split, 2),
@@ -20,6 +29,16 @@ export function validatePorts(portInput) {
20
29
  });
21
30
  return !invalid;
22
31
  }
32
+
33
+ /**
34
+ * Validate environment variable input as comma-separated VAR=value pairs.
35
+ * Empty or whitespace-only input is considered valid (no env vars).
36
+ * Variable names must start with a letter or underscore and contain only
37
+ * alphanumeric characters and underscores.
38
+ *
39
+ * @param {string} envInput - Comma-separated environment variable assignments
40
+ * @returns {boolean} True when all env var assignments are valid, false otherwise
41
+ */
23
42
  export function validateEnvVars(envInput) {
24
43
  if (!envInput || !envInput.trim()) return true; // Empty is valid
25
44
 
@@ -9,6 +9,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
9
9
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
10
10
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
11
  import { useState } from "react";
12
+ import { safeCall } from "../../helpers/safeCall.js";
12
13
  import { startContainer as svcStartContainer, stopContainer as svcStopContainer, restartContainer as svcRestartContainer, removeContainer as svcRemoveContainer } from "../../helpers/dockerService/serviceComponents/containerActions.js";
13
14
 
14
15
  /**
@@ -77,7 +78,7 @@ export function useContainerActions(_ref) {
77
78
  case 4:
78
79
  setMessage("".concat(actionLabel, " container successful."));
79
80
  setMessageColor("green");
80
- if (onAction) onAction();
81
+ safeCall(onAction);
81
82
  _context5.n = 6;
82
83
  break;
83
84
  case 5:
@@ -5,7 +5,8 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
7
  import { useState } from "react";
8
- import { validatePorts, validateEnvVars } from "../../helpers/validationHelpers.js";
8
+ import { validatePorts } from "../../helpers/validationHelpers.js";
9
+ import { safeCall } from "../../helpers/safeCall.js";
9
10
 
10
11
  /**
11
12
  * Custom hook to manage the container creation flow, step by step.
@@ -93,8 +94,8 @@ export function useContainerCreation(_ref) {
93
94
  return;
94
95
  }
95
96
  if (step === 3) {
96
- // Final step: call onCreate with all data
97
- onCreate({
97
+ // Final step: call onCreate with all data (safely)
98
+ safeCall(onCreate, {
98
99
  imageName: imageName,
99
100
  containerName: containerName,
100
101
  portInput: portInput,
@@ -114,7 +115,7 @@ export function useContainerCreation(_ref) {
114
115
  setEnvInput("");
115
116
  setMessage("");
116
117
  setMessageColor("yellow");
117
- if (onCancel) onCancel();
118
+ safeCall(onCancel);
118
119
  }
119
120
  return {
120
121
  step: step,
@@ -5,6 +5,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
5
5
  function _iterableToArrayLimit(r, l) { var t = null == r ? null : "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (null != t) { var e, n, i, u, a = [], f = !0, o = !1; try { if (i = (t = t.call(r)).next, 0 === l) { if (Object(t) !== t) return; f = !1; } else for (; !(f = (e = i.call(t)).done) && (a.push(e.value), a.length !== l); f = !0); } catch (r) { o = !0, n = r; } finally { try { if (!f && null != t["return"] && (u = t["return"](), Object(u) !== u)) return; } finally { if (o) throw n; } } return a; } }
6
6
  function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
7
7
  import { useState, useRef } from "react";
8
+ import { safeCall } from "../../helpers/safeCall.js";
8
9
 
9
10
  /**
10
11
  * Custom hook to manage the logs viewer state and stream reference.
@@ -45,8 +46,8 @@ export function useLogsViewer() {
45
46
  setShowLogs(false);
46
47
  setLogs([]);
47
48
  if (logsStreamRef.current) {
48
- var _logsStreamRef$curren, _logsStreamRef$curren2;
49
- (_logsStreamRef$curren = (_logsStreamRef$curren2 = logsStreamRef.current).destroy) === null || _logsStreamRef$curren === void 0 || _logsStreamRef$curren.call(_logsStreamRef$curren2);
49
+ var _logsStreamRef$curren;
50
+ safeCall((_logsStreamRef$curren = logsStreamRef.current.destroy) === null || _logsStreamRef$curren === void 0 ? void 0 : _logsStreamRef$curren.bind(logsStreamRef.current));
50
51
  logsStreamRef.current = null;
51
52
  }
52
53
  }
@@ -11,13 +11,16 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
11
  /**
12
12
  * React hook to manage Docker containers state.
13
13
  *
14
- * @returns {[Array, Function]} [containers, refresh] / [contenedores, refrescar]
14
+ * @returns {{containers: Array<Object>}} An object containing the current
15
+ * containers array. The hook starts a periodic refresh and fetches the
16
+ * container list immediately on mount.
15
17
  * @example
16
18
  * // Use in a component
17
- * const [containers, refresh] = useContainers();
19
+ * const { containers } = useContainers();
18
20
  */
19
21
  import React, { useState, useEffect } from "react";
20
22
  import { getContainers } from "../helpers/dockerService/serviceComponents/containerList.js";
23
+ import { REFRESH_INTERVALS } from "../helpers/constants.js";
21
24
  export function useContainers() {
22
25
  var _useState = useState([]),
23
26
  _useState2 = _slicedToArray(_useState, 2),
@@ -43,7 +46,7 @@ export function useContainers() {
43
46
  };
44
47
  }();
45
48
  fetch();
46
- var timer = setInterval(fetch, 3000);
49
+ var timer = setInterval(fetch, REFRESH_INTERVALS.CONTAINER_LIST);
47
50
  return function () {
48
51
  return clearInterval(timer);
49
52
  };