cdd-cli 3.1.3 → 3.1.5

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 (138) hide show
  1. package/.eslintrc.json +30 -0
  2. package/.github/workflows/docs.yml +35 -0
  3. package/CHANGELOG.md +44 -4
  4. package/FIXES_APPLIED.md +145 -147
  5. package/README.md +26 -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 +17 -1
  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 +46 -8
  17. package/dist/components/UsageMenu.js +30 -1
  18. package/dist/helpers/actionHelpers.js +17 -6
  19. package/dist/helpers/constants.js +30 -0
  20. package/dist/helpers/dockerService/dockerService.js +19 -0
  21. package/dist/helpers/dockerService/serviceComponents/containerActions.js +194 -35
  22. package/dist/helpers/dockerService/serviceComponents/containerList.js +45 -8
  23. package/dist/helpers/dockerService/serviceComponents/containerLogs.js +16 -6
  24. package/dist/helpers/dockerService/serviceComponents/containerStats.js +28 -9
  25. package/dist/helpers/exitWithMessage.js +2 -1
  26. package/dist/helpers/logger.js +112 -0
  27. package/dist/helpers/safeCall.js +21 -0
  28. package/dist/helpers/validationHelpers.js +20 -1
  29. package/dist/hooks/creation/useContainerActions.js +2 -1
  30. package/dist/hooks/creation/useContainerCreation.js +5 -4
  31. package/dist/hooks/creation/useLogsViewer.js +3 -2
  32. package/dist/hooks/useContainers.js +6 -3
  33. package/dist/hooks/useControls.js +253 -122
  34. package/dist/hooks/useLogsStream.js +3 -2
  35. package/dist/index.js +1 -0
  36. package/docs/components_ContainerCreationPrompt.jsx.html +114 -0
  37. package/docs/components_ContainerRow.jsx.html +151 -0
  38. package/docs/components_ContainerSection.jsx.html +75 -0
  39. package/docs/components_Footer.jsx.html +66 -0
  40. package/docs/components_Header.jsx.html +84 -0
  41. package/docs/components_LogViewer.jsx.html +91 -0
  42. package/docs/components_MessageFeedback.jsx.html +76 -0
  43. package/docs/components_PromptField.jsx.html +99 -0
  44. package/docs/components_StatsBar.jsx.html +105 -0
  45. package/docs/components_UsageMenu.jsx.html +73 -0
  46. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  47. package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
  48. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  49. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  50. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
  51. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  52. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  53. package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
  54. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  55. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  56. package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
  57. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  58. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  59. package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
  60. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  61. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  62. package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
  63. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  64. package/docs/global.html +6145 -0
  65. package/docs/helpers_actionHelpers.js.html +94 -0
  66. package/docs/helpers_constants.js.html +81 -0
  67. package/docs/helpers_dockerService_dockerService.js.html +79 -0
  68. package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +153 -0
  69. package/docs/helpers_dockerService_serviceComponents_containerList.js.html +81 -0
  70. package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +85 -0
  71. package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +107 -0
  72. package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +82 -0
  73. package/docs/helpers_exitWithMessage.js.html +77 -0
  74. package/docs/helpers_safeCall.js.html +68 -0
  75. package/docs/helpers_validationHelpers.js.html +96 -0
  76. package/docs/hooks_creation_useContainerActions.js.html +120 -0
  77. package/docs/hooks_creation_useContainerCreation.js.html +152 -0
  78. package/docs/hooks_creation_useLogsViewer.js.html +102 -0
  79. package/docs/hooks_useControls.js.html +298 -0
  80. package/docs/hooks_useLogsStream.js.html +82 -0
  81. package/docs/index.html +65 -0
  82. package/docs/index.js.html +65 -0
  83. package/docs/module-index.html +171 -0
  84. package/docs/scripts/linenumber.js +25 -0
  85. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  86. package/docs/scripts/prettify/lang-css.js +2 -0
  87. package/docs/scripts/prettify/prettify.js +28 -0
  88. package/docs/styles/jsdoc-default.css +358 -0
  89. package/docs/styles/prettify-jsdoc.css +111 -0
  90. package/docs/styles/prettify-tomorrow.css +132 -0
  91. package/fix-imports.cjs +33 -6
  92. package/jest.config.cjs +2 -1
  93. package/package.json +19 -9
  94. package/src/App.jsx +16 -2
  95. package/src/components/ContainerCreationPrompt.jsx +26 -0
  96. package/src/components/ContainerList.jsx +12 -13
  97. package/src/components/ContainerRow.jsx +55 -20
  98. package/src/components/ContainerSection.jsx +15 -1
  99. package/src/components/Footer.jsx +6 -1
  100. package/src/components/Header.jsx +16 -0
  101. package/src/components/LogViewer.jsx +19 -2
  102. package/src/components/MessageFeedback.jsx +14 -0
  103. package/src/components/PromptField.jsx +30 -0
  104. package/src/components/StatsBar.jsx +47 -8
  105. package/src/components/UsageMenu.jsx +38 -8
  106. package/src/helpers/actionHelpers.js +16 -7
  107. package/src/helpers/constants.js +30 -0
  108. package/src/helpers/dockerService/dockerService.js +19 -0
  109. package/src/helpers/dockerService/serviceComponents/containerActions.js +123 -12
  110. package/src/helpers/dockerService/serviceComponents/containerList.js +61 -23
  111. package/src/helpers/dockerService/serviceComponents/containerLogs.js +20 -6
  112. package/src/helpers/dockerService/serviceComponents/containerStats.js +24 -5
  113. package/src/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
  114. package/src/helpers/exitWithMessage.js +2 -1
  115. package/src/helpers/logger.js +113 -0
  116. package/src/helpers/safeCall.js +18 -0
  117. package/src/helpers/validationHelpers.js +21 -3
  118. package/src/hooks/creation/useContainerActions.js +4 -3
  119. package/src/hooks/creation/useContainerCreation.js +5 -4
  120. package/src/hooks/creation/useLogsViewer.js +2 -1
  121. package/src/hooks/useContainers.js +6 -3
  122. package/src/hooks/useControls.js +244 -91
  123. package/src/hooks/useLogsStream.js +2 -1
  124. package/src/index.js +1 -0
  125. package/test/actionHelpers.test.js +106 -0
  126. package/test/containerActions.test.js +138 -0
  127. package/test/containerList.test.js +79 -0
  128. package/test/jest.setup.js +7 -0
  129. package/test/safeCall.test.js +25 -0
  130. package/test/useContainerCreation.dom.test.js +97 -0
  131. package/test/validationHelpers.test.js +2 -2
  132. package/AUDIT_REPORT.md +0 -1004
  133. package/dist/cdd.bundle.js +0 -8
  134. package/dist/components/ContainerCreator.js +0 -81
  135. package/dist/components/StatsViewer.js +0 -0
  136. package/dist/helpers/dockerActions.js +0 -64
  137. package/dist/helpers/dockerService.js +0 -284
  138. package/dist/hooks/useStatsPolling.js +0 -54
@@ -2,15 +2,16 @@
2
2
  * List component for Docker containers.
3
3
  *
4
4
  * @component
5
- * @param {Object} props - Component props / Props del componente
6
- * @param {Array} props.containers - Containers to display / Contenedores a mostrar
7
- * @returns {JSX.Element} Rendered list / Lista renderizada
5
+ * @param {Object} props - Component props
6
+ * @param {Array} props.containers - Containers to display
7
+ * @returns {JSX.Element} Rendered list
8
8
  * @example
9
9
  * // Render with containers
10
10
  * <ContainerList containers={containers} />
11
11
  */
12
12
  import React from "react";
13
- import { Box, Text } from "ink";
13
+ import PropTypes from 'prop-types';
14
+ import { Box } from "ink";
14
15
  import ContainerRow from "./ContainerRow.js";
15
16
  export default function ContainerList(_ref) {
16
17
  var containers = _ref.containers,
@@ -19,19 +20,14 @@ export default function ContainerList(_ref) {
19
20
  return /*#__PURE__*/React.createElement(Box, {
20
21
  key: container.id,
21
22
  flexDirection: "row",
22
- alignItems: "flex-start"
23
- }, /*#__PURE__*/React.createElement(Box, {
24
- width: 2,
25
- minWidth: 2,
26
- justifyContent: "flex-end"
27
- }, /*#__PURE__*/React.createElement(Text, {
28
- color: i === selected ? "green" : undefined
29
- }, i === selected ? "➤" : " ")), /*#__PURE__*/React.createElement(Box, {
30
- flexGrow: 1,
31
23
  paddingLeft: 1
32
24
  }, /*#__PURE__*/React.createElement(ContainerRow, {
33
25
  container: container,
34
26
  isSelected: i === selected
35
- })));
27
+ }));
36
28
  }));
37
- }
29
+ }
30
+ ContainerList.propTypes = {
31
+ containers: PropTypes.arrayOf(PropTypes.object).isRequired,
32
+ selected: PropTypes.number
33
+ };
@@ -11,7 +11,9 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
11
11
  import React, { useState, useEffect } from "react";
12
12
  import { Box, Text } from "ink";
13
13
  import { getStats } from "../helpers/dockerService/serviceComponents/containerStats.js";
14
+ import { REFRESH_INTERVALS } from "../helpers/constants.js";
14
15
  import StatsBar from "./StatsBar.js";
16
+ import PropTypes from 'prop-types';
15
17
  var stateText = function stateText(state) {
16
18
  if (state === "running") return {
17
19
  text: "🟢 RUNNING",
@@ -30,8 +32,18 @@ var stateText = function stateText(state) {
30
32
  color: "gray"
31
33
  };
32
34
  };
35
+
36
+ /**
37
+ * Row component that renders information and live stats for a container.
38
+ *
39
+ * @param {Object} props
40
+ * @param {Object} props.container - Container object with id, name, image, state and ports
41
+ * @returns {JSX.Element}
42
+ */
33
43
  export default function ContainerRow(_ref) {
34
- var container = _ref.container;
44
+ var container = _ref.container,
45
+ _ref$isSelected = _ref.isSelected,
46
+ isSelected = _ref$isSelected === void 0 ? false : _ref$isSelected;
35
47
  var id = container.id,
36
48
  name = container.name,
37
49
  image = container.image,
@@ -48,15 +60,22 @@ export default function ContainerRow(_ref) {
48
60
  stats = _useState2[0],
49
61
  setStats = _useState2[1];
50
62
 
51
- // Format ports for display
63
+ // Format ports for display (no leading space to avoid layout shifts)
52
64
  var formatPorts = function formatPorts(ports) {
53
65
  if (!ports || ports.length === 0) return "";
54
66
  if (Array.isArray(ports)) {
55
- return ports.map(function (p, i) {
56
- return " \uD83D\uDD17 ".concat(p);
67
+ return ports.map(function (p, _i) {
68
+ return "\uD83D\uDD17 ".concat(p);
57
69
  }).join(" ");
58
70
  }
59
- return " \uD83D\uDD17 ".concat(ports);
71
+ return "\uD83D\uDD17 ".concat(ports);
72
+ };
73
+
74
+ // Helper to truncate long strings to a max length without adding trailing spaces
75
+ var truncate = function truncate(s) {
76
+ var max = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 20;
77
+ if (!s) return "";
78
+ return s.length > max ? s.slice(0, max - 1) + "…" : s;
60
79
  };
61
80
  var _useState3 = useState(""),
62
81
  _useState4 = _slicedToArray(_useState3, 2),
@@ -106,7 +125,7 @@ export default function ContainerRow(_ref) {
106
125
  };
107
126
  }();
108
127
  fetchStats();
109
- var timer = setInterval(fetchStats, 1500);
128
+ var timer = setInterval(fetchStats, REFRESH_INTERVALS.CONTAINER_STATS);
110
129
  return function () {
111
130
  isMounted = false;
112
131
  clearInterval(timer);
@@ -116,18 +135,58 @@ export default function ContainerRow(_ref) {
116
135
  return /*#__PURE__*/React.createElement(Box, {
117
136
  flexDirection: "column",
118
137
  marginBottom: 1
119
- }, /*#__PURE__*/React.createElement(Box, null, /*#__PURE__*/React.createElement(Text, {
138
+ }, /*#__PURE__*/React.createElement(Box, {
139
+ flexDirection: "row",
140
+ alignItems: "center"
141
+ }, /*#__PURE__*/React.createElement(Box, {
142
+ width: 2,
143
+ minWidth: 2,
144
+ justifyContent: "flex-end",
145
+ paddingRight: 1
146
+ }, /*#__PURE__*/React.createElement(Text, {
147
+ color: isSelected ? "green" : undefined
148
+ }, isSelected ? "➤" : " ")), /*#__PURE__*/React.createElement(Box, {
149
+ width: 18,
150
+ flexShrink: 1,
151
+ paddingRight: 1
152
+ }, /*#__PURE__*/React.createElement(Text, {
120
153
  color: "cyan"
121
- }, name.padEnd(20)), /*#__PURE__*/React.createElement(Text, {
154
+ }, truncate(name, 18))), /*#__PURE__*/React.createElement(Box, {
155
+ width: 18,
156
+ flexShrink: 1,
157
+ paddingRight: 1
158
+ }, /*#__PURE__*/React.createElement(Text, {
122
159
  color: "gray"
123
- }, image.padEnd(20)), /*#__PURE__*/React.createElement(Text, {
160
+ }, truncate(image, 18))), /*#__PURE__*/React.createElement(Box, {
161
+ width: 14,
162
+ minWidth: 12,
163
+ paddingRight: 1
164
+ }, /*#__PURE__*/React.createElement(Text, {
124
165
  color: stateInfo.color
125
- }, stateInfo.text), /*#__PURE__*/React.createElement(Text, {
166
+ }, stateInfo.text)), /*#__PURE__*/React.createElement(Box, {
167
+ flexGrow: 1,
168
+ flexShrink: 1,
169
+ paddingLeft: 0,
170
+ paddingRight: 1
171
+ }, /*#__PURE__*/React.createElement(Text, {
126
172
  color: "yellow"
127
- }, formatPorts(container.ports)), state === "running" && /*#__PURE__*/React.createElement(Text, null, " "), state === "running" && /*#__PURE__*/React.createElement(StatsBar, {
173
+ }, formatPorts(container.ports))), /*#__PURE__*/React.createElement(Box, {
174
+ flexShrink: 0,
175
+ paddingLeft: 1
176
+ }, state === "running" ? /*#__PURE__*/React.createElement(StatsBar, {
128
177
  cpu: parseFloat(stats.cpuPercent),
129
178
  mem: parseFloat(stats.memPercent)
130
- }), state === "running" && statsError && /*#__PURE__*/React.createElement(Text, {
179
+ }) : null)), statsError && /*#__PURE__*/React.createElement(Text, {
131
180
  color: "red"
132
- }, statsError)));
133
- }
181
+ }, statsError));
182
+ }
183
+ ContainerRow.propTypes = {
184
+ container: PropTypes.shape({
185
+ id: PropTypes.string.isRequired,
186
+ name: PropTypes.string.isRequired,
187
+ image: PropTypes.string,
188
+ state: PropTypes.string,
189
+ ports: PropTypes.oneOfType([PropTypes.array, PropTypes.string])
190
+ }).isRequired,
191
+ isSelected: PropTypes.bool
192
+ };
@@ -1,14 +1,28 @@
1
1
  import React from "react";
2
2
  import { Text } from "ink";
3
3
  import ContainerList from "./ContainerList.js";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Section that displays the list of containers or a message when none exist.
8
+ *
9
+ * @param {Object} props
10
+ * @param {Array<Object>} props.containers - Array of container objects to display
11
+ * @param {number} [props.selected] - Index of the currently selected container
12
+ * @returns {JSX.Element}
13
+ */
4
14
  export default function ContainerSection(_ref) {
5
15
  var containers = _ref.containers,
6
16
  selected = _ref.selected;
7
- if (containers.length === 0) {
17
+ if (!containers || containers.length === 0) {
8
18
  return /*#__PURE__*/React.createElement(Text, null, "No containers found");
9
19
  }
10
20
  return /*#__PURE__*/React.createElement(ContainerList, {
11
21
  containers: containers,
12
22
  selected: selected
13
23
  });
14
- }
24
+ }
25
+ ContainerSection.propTypes = {
26
+ containers: PropTypes.array.isRequired,
27
+ selected: PropTypes.number
28
+ };
@@ -1,10 +1,16 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+
4
+ /**
5
+ * Simple footer displayed at the bottom of the interface.
6
+ *
7
+ * @returns {JSX.Element}
8
+ */
3
9
  export default function Footer() {
4
10
  return /*#__PURE__*/React.createElement(Box, {
5
11
  justifyContent: "flex-end",
6
12
  width: "100%"
7
13
  }, /*#__PURE__*/React.createElement(Text, {
8
14
  dimColor: true
9
- }, "Crafted by Carlos Cochero \u2022 2025"));
15
+ }, "Crafted by Carlos Cochero 2025"));
10
16
  }
@@ -1,6 +1,19 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
3
  import chalk from "chalk";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Header component displayed at the top of the CLI UI.
8
+ *
9
+ * @component
10
+ * @param {Object} props
11
+ * @param {number} props.count - Number of containers currently detected
12
+ * @returns {JSX.Element}
13
+ * @example
14
+ * <Header count={3} />
15
+ */
16
+
4
17
  export default function Header(_ref) {
5
18
  var count = _ref.count;
6
19
  return /*#__PURE__*/React.createElement(Box, {
@@ -12,4 +25,7 @@ export default function Header(_ref) {
12
25
  }, " \u2014 CLI Docker Dashboard")), /*#__PURE__*/React.createElement(Text, {
13
26
  color: "gray"
14
27
  }, count, " container", count === 1 ? "" : "s", " found"));
15
- }
28
+ }
29
+ Header.propTypes = {
30
+ count: PropTypes.number.isRequired
31
+ };
@@ -1,22 +1,40 @@
1
1
  import React from "react";
2
2
  import { Text, useInput } from "ink";
3
+ import { safeCall } from "../helpers/safeCall.js";
4
+ import PropTypes from 'prop-types';
5
+
6
+ /**
7
+ * Log viewer overlay component. Shows the most recent lines and closes on ESC.
8
+ *
9
+ * @param {Object} props
10
+ * @param {Array<string>} props.logs - Array of log lines
11
+ * @param {Function} [props.onExit] - Optional callback when the viewer closes
12
+ * @param {Object} [props.container] - Optional container metadata (used for title)
13
+ * @returns {JSX.Element}
14
+ */
3
15
  export default function LogViewer(_ref) {
16
+ var _container$name;
4
17
  var logs = _ref.logs,
5
18
  onExit = _ref.onExit,
6
19
  container = _ref.container;
7
20
  useInput(function (input, key) {
8
21
  if (key.escape) {
9
- onExit();
22
+ safeCall(onExit);
10
23
  }
11
24
  });
12
25
  var visibleLogs = logs.slice(-15);
13
26
  return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
14
27
  color: "green"
15
- }, (container === null || container === void 0 ? void 0 : container.name) || "Container", " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
28
+ }, (_container$name = container === null || container === void 0 ? void 0 : container.name) !== null && _container$name !== void 0 ? _container$name : "Container", " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
16
29
  dimColor: true
17
30
  }, "No logs...") : visibleLogs.map(function (line, idx) {
18
31
  return /*#__PURE__*/React.createElement(Text, {
19
32
  key: idx
20
33
  }, line);
21
34
  }));
22
- }
35
+ }
36
+ LogViewer.propTypes = {
37
+ logs: PropTypes.arrayOf(PropTypes.string).isRequired,
38
+ onExit: PropTypes.func,
39
+ container: PropTypes.object
40
+ };
@@ -1,5 +1,15 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * Small component that shows a feedback message in the UI.
7
+ *
8
+ * @param {Object} props
9
+ * @param {string} props.message - Message to display. If falsy, component returns null.
10
+ * @param {string} [props.color] - Optional color name for the message text.
11
+ * @returns {JSX.Element|null}
12
+ */
3
13
  export default function MessageFeedback(_ref) {
4
14
  var message = _ref.message,
5
15
  color = _ref.color;
@@ -9,4 +19,8 @@ export default function MessageFeedback(_ref) {
9
19
  }, /*#__PURE__*/React.createElement(Text, {
10
20
  color: color
11
21
  }, message));
12
- }
22
+ }
23
+ MessageFeedback.propTypes = {
24
+ message: PropTypes.string,
25
+ color: PropTypes.string
26
+ };
@@ -1,5 +1,17 @@
1
1
  import React from "react";
2
2
  import { Text } from "ink";
3
+ import PropTypes from 'prop-types';
4
+
5
+ /**
6
+ * PromptField shows a label and the current typed value for an input field.
7
+ * The cursor is represented with a trailing underscore.
8
+ *
9
+ * @param {Object} props
10
+ * @param {string} props.label - Label shown above the input value
11
+ * @param {string} props.value - Current value of the input
12
+ * @param {boolean} [props.required=false] - Whether the field is required
13
+ * @returns {JSX.Element}
14
+ */
3
15
  export function PromptField(_ref) {
4
16
  var label = _ref.label,
5
17
  value = _ref.value,
@@ -10,6 +22,15 @@ export function PromptField(_ref) {
10
22
  color: isEmpty ? "red" : "cyan"
11
23
  }, value, "_"));
12
24
  }
25
+
26
+ /**
27
+ * Small helper to show a message below prompts (errors, hints, etc.).
28
+ *
29
+ * @param {Object} props
30
+ * @param {string} props.message - Message text to display
31
+ * @param {string} [props.color] - Optional color for the message
32
+ * @returns {JSX.Element|null}
33
+ */
13
34
  export function PromptMessage(_ref2) {
14
35
  var message = _ref2.message,
15
36
  color = _ref2.color;
@@ -17,4 +38,13 @@ export function PromptMessage(_ref2) {
17
38
  return /*#__PURE__*/React.createElement(Text, {
18
39
  color: color || "yellow"
19
40
  }, message);
20
- }
41
+ }
42
+ PromptField.propTypes = {
43
+ label: PropTypes.string,
44
+ value: PropTypes.string,
45
+ required: PropTypes.bool
46
+ };
47
+ PromptMessage.propTypes = {
48
+ message: PropTypes.string,
49
+ color: PropTypes.string
50
+ };
@@ -1,19 +1,57 @@
1
1
  import React from "react";
2
2
  import { Text } from "ink";
3
3
  import chalk from "chalk";
4
+ import PropTypes from 'prop-types';
5
+ var BAR_WIDTH = 8;
6
+
7
+ /**
8
+ * Small visual bar that shows CPU and memory usage percentages.
9
+ *
10
+ * @param {Object} props
11
+ * @param {number} props.cpu - CPU percentage (0-100)
12
+ * @param {number} props.mem - Memory percentage (0-100)
13
+ * @returns {JSX.Element}
14
+ */
4
15
  export default function StatsBar(_ref) {
5
16
  var cpu = _ref.cpu,
6
17
  mem = _ref.mem;
7
- var cpuBar = makeBar(cpu);
8
- var memBar = makeBar(mem);
9
- return /*#__PURE__*/React.createElement(Text, null, "CPU: ", cpuBar, " ", colorize(cpu), "% MEM: ", memBar, " ", colorize(mem), "%");
18
+ var cpuBar = makeBar(cpu, BAR_WIDTH);
19
+ var memBar = makeBar(mem, BAR_WIDTH);
20
+ return /*#__PURE__*/React.createElement(Text, null, "CPU:", cpuBar, colorize(cpu, " ".concat(formatPercent(cpu), "%")), " MEM:", memBar, colorize(mem, " ".concat(formatPercent(mem), "%")));
10
21
  }
11
- function makeBar(value) {
12
- var filled = Math.round(value / 10);
13
- var empty = 10 - filled;
14
- var bar = "▓".repeat(filled) + "░".repeat(empty);
15
- return colorize(value, bar);
22
+ StatsBar.propTypes = {
23
+ cpu: PropTypes.number.isRequired,
24
+ mem: PropTypes.number.isRequired
25
+ };
26
+
27
+ /**
28
+ * Build a fixed-width bar representation from a numeric percentage.
29
+ *
30
+ * @param {number} value - Percentage value (0-100)
31
+ * @returns {string} Colored bar string
32
+ */
33
+ function makeBar(value, width) {
34
+ var safeWidth = Math.max(1, Math.round(width));
35
+ var normalized = Number.isFinite(value) ? Math.min(Math.max(value, 0), 100) : 0;
36
+ var filled = Math.round(normalized / 100 * safeWidth);
37
+ var empty = Math.max(0, safeWidth - filled);
38
+ var bar = "█".repeat(filled) + "░".repeat(empty);
39
+ return colorize(normalized, bar);
16
40
  }
41
+ function formatPercent(value) {
42
+ if (!Number.isFinite(value)) return 0;
43
+ var normalized = Math.min(Math.max(value, 0), 100);
44
+ if (normalized >= 10) return Math.round(normalized);
45
+ return normalized.toFixed(1).replace(/\.0$/, "");
46
+ }
47
+
48
+ /**
49
+ * Colorize a value or text depending on thresholds.
50
+ *
51
+ * @param {number} value - Numeric value used to pick color
52
+ * @param {string} [text] - Text to colorize, defaults to the numeric string
53
+ * @returns {string}
54
+ */
17
55
  function colorize(value) {
18
56
  var text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : value.toString();
19
57
  if (value < 50) return chalk.greenBright(text);
@@ -1,8 +1,37 @@
1
1
  import React from "react";
2
2
  import { Box, Text } from "ink";
3
+ var SHORTCUTS = ["Use ↑/↓ for navigation", "I to initiate selected container", "P to stop selected container", "R to restart selected container", "C to create a container", "L to view logs of selected container", "D to toggle debug log panel", "E to remove selected container", "Q to quit"];
4
+ var GRID_COLUMNS = 3;
5
+
6
+ /**
7
+ * Small usage menu that explains available keybindings to the user.
8
+ * Renders entries in a responsive grid to avoid an excessively long column.
9
+ *
10
+ * @returns {JSX.Element}
11
+ */
3
12
  export default function UsageMenu() {
13
+ var rowsPerColumn = Math.ceil(SHORTCUTS.length / GRID_COLUMNS);
14
+ var columns = Array.from({
15
+ length: GRID_COLUMNS
16
+ }, function (_, columnIndex) {
17
+ return SHORTCUTS.slice(columnIndex * rowsPerColumn, (columnIndex + 1) * rowsPerColumn);
18
+ }).filter(function (column) {
19
+ return column.length > 0;
20
+ });
4
21
  return /*#__PURE__*/React.createElement(Box, {
5
22
  flexDirection: "column",
6
23
  marginTop: 1
7
- }, /*#__PURE__*/React.createElement(Text, null, "Use \u2191/\u2193 for navigation"), /*#__PURE__*/React.createElement(Text, null, "\u2022I to initiate selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022P to stop selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022R to restart selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022C to create a container"), /*#__PURE__*/React.createElement(Text, null, "\u2022L to view logs of selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022E to remove selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022Q to quit"));
24
+ }, /*#__PURE__*/React.createElement(Box, {
25
+ flexDirection: "row"
26
+ }, columns.map(function (column, columnIndex) {
27
+ return /*#__PURE__*/React.createElement(Box, {
28
+ key: "shortcut-column-".concat(columnIndex),
29
+ flexDirection: "column",
30
+ marginRight: columnIndex === columns.length - 1 ? 0 : 3
31
+ }, column.map(function (shortcut, rowIndex) {
32
+ return /*#__PURE__*/React.createElement(Text, {
33
+ key: "shortcut-".concat(columnIndex, "-").concat(rowIndex)
34
+ }, shortcut);
35
+ }));
36
+ })));
8
37
  }
@@ -2,6 +2,8 @@ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present,
2
2
  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); }
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
+ import { MESSAGE_TIMEOUTS } from "../helpers/constants.js";
6
+ import { logger } from "./logger.js";
5
7
  /**
6
8
  * Generic helper to perform a container action with user feedback.
7
9
  *
@@ -20,7 +22,7 @@ export function handleAction(_x) {
20
22
  }
21
23
  function _handleAction() {
22
24
  _handleAction = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
23
- var containers, selected, actionFn, actionLabel, setMessage, setMessageColor, stateCheck, c, _t;
25
+ var containers, selected, actionFn, actionLabel, setMessage, setMessageColor, stateCheck, c, failureReason, _t;
24
26
  return _regenerator().w(function (_context) {
25
27
  while (1) switch (_context.p = _context.n) {
26
28
  case 0:
@@ -32,19 +34,26 @@ function _handleAction() {
32
34
  }
33
35
  return _context.a(2);
34
36
  case 1:
35
- if (!(stateCheck && stateCheck(c))) {
37
+ if (!stateCheck) {
36
38
  _context.n = 2;
37
39
  break;
38
40
  }
39
- setMessage(stateCheck(c));
41
+ failureReason = stateCheck(c);
42
+ if (!failureReason) {
43
+ _context.n = 2;
44
+ break;
45
+ }
46
+ logger.warn("Action %s blocked for container %s: %s", actionLabel, c.id, failureReason);
47
+ setMessage(failureReason);
40
48
  setMessageColor("red");
41
49
  setTimeout(function () {
42
50
  return setMessage("");
43
- }, 2000);
51
+ }, MESSAGE_TIMEOUTS.SHORT);
44
52
  return _context.a(2);
45
53
  case 2:
46
54
  setMessage("".concat(actionLabel, " container..."));
47
55
  setMessageColor("green");
56
+ logger.info("Action %s requested for container %s", actionLabel, c.id);
48
57
  _context.p = 3;
49
58
  _context.n = 4;
50
59
  return actionFn(c.id);
@@ -53,7 +62,8 @@ function _handleAction() {
53
62
  setMessageColor("green");
54
63
  setTimeout(function () {
55
64
  return setMessage("");
56
- }, 3000);
65
+ }, MESSAGE_TIMEOUTS.DEFAULT);
66
+ logger.info("Action %s completed for container %s", actionLabel, c.id);
57
67
  _context.n = 6;
58
68
  break;
59
69
  case 5:
@@ -63,7 +73,8 @@ function _handleAction() {
63
73
  setMessageColor("red");
64
74
  setTimeout(function () {
65
75
  return setMessage("");
66
- }, 3000);
76
+ }, MESSAGE_TIMEOUTS.DEFAULT);
77
+ logger.error("Action %s failed for container %s", actionLabel, c.id, _t);
67
78
  case 6:
68
79
  return _context.a(2);
69
80
  }
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Time intervals used across the app (milliseconds).
3
+ * - CONTAINER_LIST: how often to refresh the list of containers
4
+ * - CONTAINER_STATS: how often to fetch stats for running containers
5
+ */
6
+ export var REFRESH_INTERVALS = {
7
+ CONTAINER_LIST: 3000,
8
+ CONTAINER_STATS: 1500
9
+ };
10
+
11
+ /**
12
+ * Timeout values for UI messages before they disappear.
13
+ */
14
+ export var MESSAGE_TIMEOUTS = {
15
+ SHORT: 2000,
16
+ DEFAULT: 3000
17
+ };
18
+
19
+ /**
20
+ * Delay used before exiting the process when showing an exit message.
21
+ */
22
+ export var EXIT_DELAY = 500;
23
+
24
+ /**
25
+ * Operational timeouts for Docker operations and image pulls.
26
+ */
27
+ export var TIMEOUTS = {
28
+ CONTAINER_OP: 30000,
29
+ PULL_IMAGE: 300000
30
+ };
@@ -4,5 +4,24 @@ import Docker from "dockerode";
4
4
  // - /var/run/docker.sock on Linux/Mac
5
5
  // - //./pipe/docker_engine on Windows
6
6
  // - Environment variables DOCKER_HOST, DOCKER_CERT_PATH, etc.
7
+
8
+ /**
9
+ * Typedef for the docker client used in this project.
10
+ * Use a generic object shape so JSDoc can render a meaningful type.
11
+ *
12
+ * @typedef {Object} DockerClient
13
+ * @property {Function} listContainers
14
+ * @property {Function} getContainer
15
+ * @property {Function} listImages
16
+ * @property {Function} createContainer
17
+ * @property {Function} pull
18
+ */
19
+
20
+ /**
21
+ * Shared Docker client instance (dockerode). Consumers import { docker }
22
+ * and use it to list/create/start/stop containers and images.
23
+ *
24
+ * @type {DockerClient}
25
+ */
7
26
  var docker = new Docker();
8
27
  export { docker };