cdd-cli 3.2.0 → 3.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.atl/skill-registry.md +30 -0
- package/.editorconfig +12 -0
- package/.eslintrc.json +1 -1
- package/.prettierignore +4 -0
- package/.prettierrc +8 -0
- package/CHANGELOG.md +29 -0
- package/SECURITY.md +5 -0
- package/__mocks__/ink.cjs +17 -0
- package/coverage/clover.xml +526 -436
- package/coverage/coverage-final.json +19 -15
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +376 -0
- package/coverage/lcov-report/components/PromptField.jsx.html +229 -0
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +244 -0
- package/coverage/lcov-report/components/index.html +146 -0
- package/coverage/lcov-report/helpers/constants.js.html +80 -17
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +41 -8
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +162 -84
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +50 -32
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +21 -21
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
- package/coverage/lcov-report/helpers/index.html +27 -12
- package/coverage/lcov-report/helpers/logger.js.html +19 -19
- package/coverage/lcov-report/helpers/safeCall.js.html +3 -3
- package/coverage/lcov-report/helpers/validationHelpers.js.html +28 -76
- package/coverage/lcov-report/hooks/creation/index.html +20 -20
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +38 -17
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +416 -89
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +7 -7
- package/coverage/lcov-report/hooks/debug/index.html +5 -5
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +35 -20
- package/coverage/lcov-report/hooks/index.html +40 -40
- package/coverage/lcov-report/hooks/navigation/index.html +19 -19
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +38 -29
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +119 -80
- package/coverage/lcov-report/hooks/useControls.js.html +263 -122
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +8 -8
- package/coverage/lcov-report/hooks/useExitHandler.js.html +50 -41
- package/coverage/lcov-report/index.html +67 -52
- package/coverage/lcov.info +991 -805
- package/dist/App.js +11 -11
- package/dist/components/ContainerCreationPrompt.js +31 -10
- package/dist/components/ContainerList.js +3 -3
- package/dist/components/ContainerRow.js +20 -20
- package/dist/components/ContainerSection.js +3 -3
- package/dist/components/Footer.js +2 -2
- package/dist/components/Header.js +7 -7
- package/dist/components/LogViewer.js +3 -3
- package/dist/components/MessageFeedback.js +2 -2
- package/dist/components/PromptField.js +4 -4
- package/dist/components/StatsBar.js +5 -5
- package/dist/components/SuggestionPanel.js +44 -0
- package/dist/components/UsageMenu.js +3 -3
- package/dist/helpers/appInfo.js +7 -7
- package/dist/helpers/constants.js +114 -0
- package/dist/helpers/containerOptionsBuilder.js +3 -3
- package/dist/helpers/dockerService/dockerService.js +1 -1
- package/dist/helpers/dockerService/serviceComponents/containerActions.js +99 -40
- package/dist/helpers/dockerService/serviceComponents/containerList.js +11 -11
- package/dist/helpers/dockerService/serviceComponents/containerLogs.js +9 -9
- package/dist/helpers/dockerService/serviceComponents/containerStats.js +5 -5
- package/dist/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
- package/dist/helpers/exitWithMessage.js +10 -10
- package/dist/helpers/imageNameUtils.js +32 -0
- package/dist/helpers/logger.js +13 -13
- package/dist/helpers/safeCall.js +2 -2
- package/dist/helpers/validationHelpers.js +76 -21
- package/dist/hooks/creation/useContainerActions.js +9 -9
- package/dist/hooks/creation/useContainerCreation.js +138 -36
- package/dist/hooks/creation/useLogsViewer.js +2 -2
- package/dist/hooks/debug/useDebugLogs.js +4 -4
- package/dist/hooks/useContainerCommandRouter.js +13 -13
- package/dist/hooks/useContainers.js +3 -3
- package/dist/hooks/useControls.js +66 -35
- package/dist/hooks/useEraseConfirmation.js +3 -3
- package/dist/index.js +2 -2
- package/jest.config.cjs +7 -0
- package/package.json +7 -3
- package/src/App.jsx +33 -16
- package/src/components/ContainerCreationPrompt.jsx +43 -9
- package/src/components/ContainerList.jsx +3 -3
- package/src/components/ContainerRow.jsx +19 -16
- package/src/components/ContainerSection.jsx +3 -3
- package/src/components/Footer.jsx +2 -2
- package/src/components/Header.jsx +7 -7
- package/src/components/LogViewer.jsx +5 -3
- package/src/components/MessageFeedback.jsx +2 -2
- package/src/components/PromptField.jsx +4 -4
- package/src/components/StatsBar.jsx +11 -7
- package/src/components/SuggestionPanel.jsx +53 -0
- package/src/components/UsageMenu.jsx +18 -13
- package/src/helpers/appInfo.js +7 -7
- package/src/helpers/constants.js +30 -9
- package/src/helpers/containerOptionsBuilder.js +16 -5
- package/src/helpers/dockerService/dockerService.js +1 -1
- package/src/helpers/dockerService/serviceComponents/containerActions.js +87 -61
- package/src/helpers/dockerService/serviceComponents/containerList.js +31 -25
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +41 -26
- package/src/helpers/dockerService/serviceComponents/containerStats.js +13 -11
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +5 -4
- package/src/helpers/exitWithMessage.js +13 -7
- package/src/helpers/imageNameUtils.js +32 -0
- package/src/helpers/logger.js +15 -15
- package/src/helpers/safeCall.js +2 -2
- package/src/helpers/validationHelpers.js +13 -29
- package/src/hooks/creation/useContainerActions.js +19 -12
- package/src/hooks/creation/useContainerCreation.js +150 -41
- package/src/hooks/creation/useLogsViewer.js +2 -2
- package/src/hooks/debug/useDebugLogs.js +15 -10
- package/src/hooks/navigation/useContainerSelection.js +17 -14
- package/src/hooks/useContainerCommandRouter.js +71 -58
- package/src/hooks/useContainers.js +4 -4
- package/src/hooks/useControls.js +115 -68
- package/src/hooks/useEraseConfirmation.js +3 -3
- package/src/hooks/useExitHandler.js +29 -26
- package/src/index.js +4 -4
- package/test/ContainerCreationPrompt.dom.test.js +68 -0
- package/test/SuggestionPanel.dom.test.js +53 -0
- package/test/constants.test.js +34 -0
- package/test/useContainerCreation.dom.test.js +123 -0
- package/test/useControls.dom.test.js +198 -2
|
@@ -12,19 +12,20 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
|
|
|
12
12
|
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; }
|
|
13
13
|
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; } }
|
|
14
14
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
15
|
-
import React from
|
|
16
|
-
import { useInput } from
|
|
17
|
-
import { useContainerActions } from
|
|
18
|
-
import { useContainerCreation } from
|
|
19
|
-
import { useLogsViewer } from
|
|
20
|
-
import { useContainerSelection } from
|
|
21
|
-
import { useDebugLogs } from
|
|
22
|
-
import { useEraseConfirmation } from
|
|
23
|
-
import { useExitHandler } from
|
|
24
|
-
import { useContainerCommandRouter } from
|
|
25
|
-
import { getLogsStream } from
|
|
26
|
-
import { createContainer as svcCreateContainer } from
|
|
27
|
-
import { buildContainerOptions } from
|
|
15
|
+
import React from 'react';
|
|
16
|
+
import { useInput } from 'ink';
|
|
17
|
+
import { useContainerActions } from './creation/useContainerActions.js';
|
|
18
|
+
import { useContainerCreation } from './creation/useContainerCreation.js';
|
|
19
|
+
import { useLogsViewer } from './creation/useLogsViewer.js';
|
|
20
|
+
import { useContainerSelection } from './navigation/useContainerSelection.js';
|
|
21
|
+
import { useDebugLogs } from './debug/useDebugLogs.js';
|
|
22
|
+
import { useEraseConfirmation } from './useEraseConfirmation.js';
|
|
23
|
+
import { useExitHandler } from './useExitHandler.js';
|
|
24
|
+
import { useContainerCommandRouter } from './useContainerCommandRouter.js';
|
|
25
|
+
import { getLogsStream } from '../helpers/dockerService/serviceComponents/containerLogs.js';
|
|
26
|
+
import { createContainer as svcCreateContainer } from '../helpers/dockerService/serviceComponents/containerActions.js';
|
|
27
|
+
import { buildContainerOptions } from '../helpers/containerOptionsBuilder.js';
|
|
28
|
+
import { DB_IMAGES } from '../helpers/constants.js';
|
|
28
29
|
|
|
29
30
|
// Principal hook to manage user inputs and control the app state
|
|
30
31
|
/**
|
|
@@ -48,7 +49,7 @@ export function useControls() {
|
|
|
48
49
|
var creation = useContainerCreation({
|
|
49
50
|
onCreate: function () {
|
|
50
51
|
var _onCreate = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
|
|
51
|
-
var imageName, containerName, portInput, envInput, options, id, _t;
|
|
52
|
+
var imageName, containerName, portInput, envInput, options, _yield$svcCreateConta, id, ports, portMsg, _t;
|
|
52
53
|
return _regenerator().w(function (_context) {
|
|
53
54
|
while (1) switch (_context.p = _context.n) {
|
|
54
55
|
case 0:
|
|
@@ -60,21 +61,29 @@ export function useControls() {
|
|
|
60
61
|
envInput: envInput
|
|
61
62
|
});
|
|
62
63
|
actions.setMessage("Creating container ".concat(imageName, "..."));
|
|
63
|
-
actions.setMessageColor(
|
|
64
|
+
actions.setMessageColor('yellow');
|
|
64
65
|
_context.p = 1;
|
|
65
66
|
_context.n = 2;
|
|
66
67
|
return svcCreateContainer(imageName, options);
|
|
67
68
|
case 2:
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
_yield$svcCreateConta = _context.v;
|
|
70
|
+
id = _yield$svcCreateConta.id;
|
|
71
|
+
ports = _yield$svcCreateConta.ports;
|
|
72
|
+
portMsg = '';
|
|
73
|
+
if (ports && ports.length) {
|
|
74
|
+
portMsg = ' | Ports: ' + ports.map(function (p) {
|
|
75
|
+
return "".concat(p.hostPort, "\u2192").concat(p.containerPort, "/").concat(p.protocol);
|
|
76
|
+
}).join(', ');
|
|
77
|
+
}
|
|
78
|
+
actions.setMessage("Created container ".concat(id).concat(portMsg));
|
|
79
|
+
actions.setMessageColor('green');
|
|
71
80
|
_context.n = 4;
|
|
72
81
|
break;
|
|
73
82
|
case 3:
|
|
74
83
|
_context.p = 3;
|
|
75
84
|
_t = _context.v;
|
|
76
85
|
actions.setMessage("Error creating container: ".concat(_t.message));
|
|
77
|
-
actions.setMessageColor(
|
|
86
|
+
actions.setMessageColor('red');
|
|
78
87
|
case 4:
|
|
79
88
|
_context.p = 4;
|
|
80
89
|
setCreatingContainer(false);
|
|
@@ -92,7 +101,7 @@ export function useControls() {
|
|
|
92
101
|
onCancel: function onCancel() {
|
|
93
102
|
return setCreatingContainer(false);
|
|
94
103
|
},
|
|
95
|
-
dbImages:
|
|
104
|
+
dbImages: DB_IMAGES
|
|
96
105
|
});
|
|
97
106
|
var logsViewer = useLogsViewer();
|
|
98
107
|
var selection = useContainerSelection(containers.length);
|
|
@@ -117,14 +126,14 @@ export function useControls() {
|
|
|
117
126
|
}
|
|
118
127
|
return actionFn;
|
|
119
128
|
}(),
|
|
120
|
-
actionLabel:
|
|
129
|
+
actionLabel: 'Erasing',
|
|
121
130
|
selected: selection.selected
|
|
122
131
|
});
|
|
123
|
-
actions.setMessageColor(
|
|
132
|
+
actions.setMessageColor('yellow');
|
|
124
133
|
},
|
|
125
134
|
onCancel: function onCancel() {
|
|
126
|
-
actions.setMessage(
|
|
127
|
-
actions.setMessageColor(
|
|
135
|
+
actions.setMessage('');
|
|
136
|
+
actions.setMessageColor('');
|
|
128
137
|
}
|
|
129
138
|
});
|
|
130
139
|
|
|
@@ -135,7 +144,7 @@ export function useControls() {
|
|
|
135
144
|
var startLogsStream = React.useCallback(function (containerId) {
|
|
136
145
|
getLogsStream(containerId, function (data) {
|
|
137
146
|
return logsViewer.setLogs(function (prev) {
|
|
138
|
-
var newLogs = [].concat(_toConsumableArray(prev), _toConsumableArray(data.split(
|
|
147
|
+
var newLogs = [].concat(_toConsumableArray(prev), _toConsumableArray(data.split('\n').filter(Boolean)));
|
|
139
148
|
return newLogs.slice(-1000);
|
|
140
149
|
});
|
|
141
150
|
}, function () {}, function (err) {
|
|
@@ -153,8 +162,8 @@ export function useControls() {
|
|
|
153
162
|
startLogsStream: startLogsStream,
|
|
154
163
|
onStartErase: function onStartErase() {
|
|
155
164
|
eraseConfirmation.startErase();
|
|
156
|
-
actions.setMessage(
|
|
157
|
-
actions.setMessageColor(
|
|
165
|
+
actions.setMessage('Are you sure you want to erase this container? (y/n)');
|
|
166
|
+
actions.setMessageColor('yellow');
|
|
158
167
|
},
|
|
159
168
|
onToggleDebug: function onToggleDebug() {
|
|
160
169
|
return debugLogs.setShowDebugLogs(function (prev) {
|
|
@@ -167,8 +176,8 @@ export function useControls() {
|
|
|
167
176
|
});
|
|
168
177
|
var exitHandler = useExitHandler({
|
|
169
178
|
onBeforeExit: function onBeforeExit() {
|
|
170
|
-
actions.setMessage(
|
|
171
|
-
actions.setMessageColor(
|
|
179
|
+
actions.setMessage('Exiting...');
|
|
180
|
+
actions.setMessageColor('yellow');
|
|
172
181
|
logsViewer.closeLogs();
|
|
173
182
|
debugLogs.setShowDebugLogs(false);
|
|
174
183
|
}
|
|
@@ -181,30 +190,52 @@ export function useControls() {
|
|
|
181
190
|
var step = creation.step;
|
|
182
191
|
var removeLastChar = function removeLastChar(setter) {
|
|
183
192
|
return setter(function (value) {
|
|
184
|
-
return (value ||
|
|
193
|
+
return (value || '').slice(0, -1);
|
|
185
194
|
});
|
|
186
195
|
};
|
|
187
196
|
var appendChar = function appendChar(setter, current, ch) {
|
|
188
|
-
return setter((current ||
|
|
197
|
+
return setter((current || '') + ch);
|
|
189
198
|
};
|
|
190
199
|
if (key.escape) {
|
|
191
200
|
creation.cancelCreation();
|
|
192
201
|
setCreatingContainer(false);
|
|
193
202
|
return;
|
|
194
203
|
}
|
|
195
|
-
if (input ===
|
|
204
|
+
if (input === '\r' || input === '\n') {
|
|
205
|
+
// If on step 0 with a focused suggestion, apply it instead of advancing
|
|
206
|
+
if (step === 0 && creation.selectedSuggestionIndex >= 0) {
|
|
207
|
+
creation.applyFocusedSuggestion();
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
196
210
|
creation.nextStep();
|
|
197
211
|
return;
|
|
198
212
|
}
|
|
213
|
+
|
|
214
|
+
// Arrow keys on step 0: navigate suggestion list
|
|
215
|
+
if (step === 0 && creation.suggestions.length > 0) {
|
|
216
|
+
if (key.upArrow) {
|
|
217
|
+
creation.moveSuggestionSelection(-1);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (key.downArrow) {
|
|
221
|
+
creation.moveSuggestionSelection(1);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
199
225
|
if (key.backspace || key["delete"]) {
|
|
200
|
-
if (step === 0)
|
|
226
|
+
if (step === 0) {
|
|
227
|
+
var newValue = (creation.imageName || '').slice(0, -1);
|
|
228
|
+
creation.updateImageInput(newValue);
|
|
229
|
+
}
|
|
201
230
|
if (step === 1) removeLastChar(creation.setContainerName);
|
|
202
231
|
if (step === 2) removeLastChar(creation.setPortInput);
|
|
203
232
|
if (step === 3) removeLastChar(creation.setEnvInput);
|
|
204
233
|
return;
|
|
205
234
|
}
|
|
206
235
|
if (input && input.length === 1 && !key.ctrl && !key.meta) {
|
|
207
|
-
if (step === 0)
|
|
236
|
+
if (step === 0) {
|
|
237
|
+
creation.updateImageInput((creation.imageName || '') + input);
|
|
238
|
+
}
|
|
208
239
|
if (step === 1) appendChar(creation.setContainerName, creation.containerName, input);
|
|
209
240
|
if (step === 2) appendChar(creation.setPortInput, creation.portInput, input);
|
|
210
241
|
if (step === 3) appendChar(creation.setEnvInput, creation.envInput, input);
|
|
@@ -218,7 +249,7 @@ export function useControls() {
|
|
|
218
249
|
return;
|
|
219
250
|
}
|
|
220
251
|
if (logsViewer.showLogs) {
|
|
221
|
-
if (input ===
|
|
252
|
+
if (input === 'q' || key.escape) {
|
|
222
253
|
logsViewer.closeLogs();
|
|
223
254
|
}
|
|
224
255
|
return;
|
|
@@ -4,7 +4,7 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
|
|
|
4
4
|
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; }
|
|
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
|
-
import { useState, useCallback } from
|
|
7
|
+
import { useState, useCallback } from 'react';
|
|
8
8
|
|
|
9
9
|
/**
|
|
10
10
|
* Hook to manage the erase confirmation dialog flow.
|
|
@@ -28,12 +28,12 @@ export function useEraseConfirmation(_ref) {
|
|
|
28
28
|
setConfirmErase(true);
|
|
29
29
|
}, []);
|
|
30
30
|
var processEraseConfirmation = useCallback(function (input, key) {
|
|
31
|
-
if (input ===
|
|
31
|
+
if (input === 'y' || input === 'Y') {
|
|
32
32
|
onConfirm();
|
|
33
33
|
setConfirmErase(false);
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
|
-
if (input ===
|
|
36
|
+
if (input === 'n' || input === 'N' || key.escape) {
|
|
37
37
|
setConfirmErase(false);
|
|
38
38
|
onCancel();
|
|
39
39
|
return;
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* // Run the CLI
|
|
8
8
|
* node index.js
|
|
9
9
|
*/
|
|
10
|
-
import React from
|
|
11
|
-
import { render } from
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { render } from 'ink';
|
|
12
12
|
import App from './App.js';
|
|
13
13
|
console.clear();
|
|
14
14
|
render(/*#__PURE__*/React.createElement(App, null));
|
package/jest.config.cjs
CHANGED
|
@@ -3,5 +3,12 @@ module.exports = {
|
|
|
3
3
|
setupFilesAfterEnv: ['./test/jest.setup.js'],
|
|
4
4
|
transform: {
|
|
5
5
|
'^.+\\.[tj]sx?$': 'babel-jest'
|
|
6
|
+
},
|
|
7
|
+
// Allow babel-jest to transform ESM-only packages used by components
|
|
8
|
+
transformIgnorePatterns: [
|
|
9
|
+
'/node_modules/(?!(ink|ink-testing-library|ansi-styles|chalk|cli-cursor|cli-spinners|is-unicode-supported|restore-cursor|signal-exit|slice-ansi|strip-ansi|wrap-ansi|yoga-layout-prebuilt)/)'
|
|
10
|
+
],
|
|
11
|
+
moduleNameMapper: {
|
|
12
|
+
'^ink$': '<rootDir>/__mocks__/ink.cjs'
|
|
6
13
|
}
|
|
7
14
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cdd-cli",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.2",
|
|
4
4
|
"description": "CLI Docker Dashboard",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
"build": "NODE_ENV=production babel src --out-dir dist --extensions .js,.jsx && node fix-imports.cjs",
|
|
9
9
|
"changes": "npm run build && node dist/index.js",
|
|
10
10
|
"lint": "eslint src --ext .js,.jsx",
|
|
11
|
-
"docs": "jsdoc -r -d docs src"
|
|
11
|
+
"docs": "jsdoc -r -d docs src",
|
|
12
|
+
"format": "prettier --write \"src/**/*.{js,jsx}\"",
|
|
13
|
+
"format:check": "prettier --check \"src/**/*.{js,jsx}\""
|
|
12
14
|
},
|
|
13
15
|
"keywords": [],
|
|
14
16
|
"author": "",
|
|
@@ -35,9 +37,11 @@
|
|
|
35
37
|
"babel-jest": "^29.6.1",
|
|
36
38
|
"babel-plugin-module-resolver": "^5.0.2",
|
|
37
39
|
"eslint": "^8.50.0",
|
|
40
|
+
"eslint-config-prettier": "^10.1.8",
|
|
38
41
|
"eslint-plugin-react": "^7.37.5",
|
|
39
42
|
"jest": "^29.6.1",
|
|
40
43
|
"jest-environment-jsdom": "^29.7.0",
|
|
41
|
-
"jsdoc": "^4.0.5"
|
|
44
|
+
"jsdoc": "^4.0.5",
|
|
45
|
+
"prettier": "^3.8.3"
|
|
42
46
|
}
|
|
43
47
|
}
|
package/src/App.jsx
CHANGED
|
@@ -9,17 +9,17 @@
|
|
|
9
9
|
* // Render the app
|
|
10
10
|
* <App />
|
|
11
11
|
*/
|
|
12
|
-
import React from
|
|
13
|
-
import { Box, Text, Spacer } from
|
|
14
|
-
import { useContainers } from
|
|
15
|
-
import { useControls } from
|
|
16
|
-
import ContainerSection from
|
|
17
|
-
import MessageFeedback from
|
|
18
|
-
import Header from
|
|
19
|
-
import LogViewer from
|
|
20
|
-
import ContainerCreationPrompt from
|
|
21
|
-
import UsageMenu from
|
|
22
|
-
import Footer from
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { Box, Text, Spacer } from 'ink';
|
|
14
|
+
import { useContainers } from './hooks/useContainers.js';
|
|
15
|
+
import { useControls } from './hooks/useControls.js';
|
|
16
|
+
import ContainerSection from './components/ContainerSection.jsx';
|
|
17
|
+
import MessageFeedback from './components/MessageFeedback.jsx';
|
|
18
|
+
import Header from './components/Header.jsx';
|
|
19
|
+
import LogViewer from './components/LogViewer.jsx';
|
|
20
|
+
import ContainerCreationPrompt from './components/ContainerCreationPrompt.jsx';
|
|
21
|
+
import UsageMenu from './components/UsageMenu.jsx';
|
|
22
|
+
import Footer from './components/Footer.jsx';
|
|
23
23
|
|
|
24
24
|
export default function App() {
|
|
25
25
|
const { containers } = useContainers();
|
|
@@ -49,18 +49,35 @@ export default function App() {
|
|
|
49
49
|
>
|
|
50
50
|
<Header count={containers.length} />
|
|
51
51
|
<Text> </Text>
|
|
52
|
-
<ContainerSection
|
|
52
|
+
<ContainerSection
|
|
53
|
+
containers={containers}
|
|
54
|
+
selected={controls.selected}
|
|
55
|
+
/>
|
|
53
56
|
<Spacer />
|
|
54
|
-
<MessageFeedback
|
|
57
|
+
<MessageFeedback
|
|
58
|
+
message={controls.message}
|
|
59
|
+
color={controls.messageColor}
|
|
60
|
+
/>
|
|
55
61
|
<UsageMenu />
|
|
56
62
|
{controls.showDebugLogs && (
|
|
57
|
-
<Box
|
|
63
|
+
<Box
|
|
64
|
+
marginTop={1}
|
|
65
|
+
flexDirection="column"
|
|
66
|
+
borderStyle="round"
|
|
67
|
+
borderColor="gray"
|
|
68
|
+
padding={1}
|
|
69
|
+
>
|
|
58
70
|
<Text color="cyan">Debug log — press D or ESC to close</Text>
|
|
59
71
|
{controls.debugLogs.length === 0 ? (
|
|
60
|
-
<Text dimColor>
|
|
72
|
+
<Text dimColor>
|
|
73
|
+
No debug entries yet. Run with CDD_LOG_LEVEL=debug for verbose
|
|
74
|
+
output.
|
|
75
|
+
</Text>
|
|
61
76
|
) : (
|
|
62
77
|
controls.debugLogs.slice(-15).map((line, idx) => (
|
|
63
|
-
<Text key={idx} color="gray">
|
|
78
|
+
<Text key={idx} color="gray">
|
|
79
|
+
{line}
|
|
80
|
+
</Text>
|
|
64
81
|
))
|
|
65
82
|
)}
|
|
66
83
|
</Box>
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import { PromptField, PromptMessage } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import { PromptField, PromptMessage } from './PromptField.jsx';
|
|
4
|
+
import { SuggestionPanel } from './SuggestionPanel.jsx';
|
|
4
5
|
import PropTypes from 'prop-types';
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -16,36 +17,63 @@ import PropTypes from 'prop-types';
|
|
|
16
17
|
* @param {string} props.envInput - Value for the environment variables field
|
|
17
18
|
* @param {string} props.message - Contextual message to display (errors/hints)
|
|
18
19
|
* @param {string} props.messageColor - Color for the contextual message
|
|
20
|
+
* @param {string[]} [props.suggestions] - Autocomplete suggestions (shown only on step 0)
|
|
21
|
+
* @param {number} [props.selectedSuggestionIndex] - Currently focused suggestion index
|
|
22
|
+
* @param {number} [props.visibleOffset] - First visible suggestion offset
|
|
19
23
|
* @returns {JSX.Element}
|
|
20
24
|
*/
|
|
21
25
|
export default function ContainerCreationPrompt(props) {
|
|
22
|
-
const {
|
|
26
|
+
const {
|
|
27
|
+
step,
|
|
28
|
+
imageName,
|
|
29
|
+
containerName,
|
|
30
|
+
portInput,
|
|
31
|
+
envInput,
|
|
32
|
+
message,
|
|
33
|
+
messageColor,
|
|
34
|
+
suggestions = [],
|
|
35
|
+
selectedSuggestionIndex = -1,
|
|
36
|
+
visibleOffset = 0,
|
|
37
|
+
} = props;
|
|
23
38
|
const prompts = [
|
|
24
39
|
{
|
|
25
|
-
label:
|
|
40
|
+
label: 'Name of the image to create (e.g., nginx:latest):',
|
|
26
41
|
value: imageName,
|
|
27
42
|
required: true,
|
|
28
43
|
},
|
|
29
44
|
{
|
|
30
|
-
label:
|
|
45
|
+
label: 'Name of the container (optional):',
|
|
31
46
|
value: containerName,
|
|
32
47
|
required: false,
|
|
33
48
|
},
|
|
34
49
|
{
|
|
35
|
-
label:
|
|
50
|
+
label: 'Ports (optional, format 8080:80,443:443):',
|
|
36
51
|
value: portInput,
|
|
37
52
|
required: false,
|
|
38
53
|
},
|
|
39
54
|
{
|
|
40
|
-
label:
|
|
55
|
+
label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
|
|
41
56
|
value: envInput,
|
|
42
57
|
required: false,
|
|
43
58
|
},
|
|
44
59
|
];
|
|
45
60
|
const { label, value, required } = prompts[step] || {};
|
|
61
|
+
const showSuggestions = step === 0 && suggestions.length > 0;
|
|
46
62
|
return (
|
|
47
|
-
<Box
|
|
63
|
+
<Box
|
|
64
|
+
flexDirection="column"
|
|
65
|
+
borderStyle="round"
|
|
66
|
+
borderColor="yellow"
|
|
67
|
+
padding={1}
|
|
68
|
+
>
|
|
48
69
|
<PromptField label={label} value={value} required={required} />
|
|
70
|
+
{showSuggestions && (
|
|
71
|
+
<SuggestionPanel
|
|
72
|
+
items={suggestions}
|
|
73
|
+
selectedIndex={selectedSuggestionIndex}
|
|
74
|
+
visibleOffset={visibleOffset}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
49
77
|
<PromptMessage message={message} color={messageColor} />
|
|
50
78
|
<Text dimColor>Press Enter to continue, Escape to cancel</Text>
|
|
51
79
|
</Box>
|
|
@@ -60,4 +88,10 @@ ContainerCreationPrompt.propTypes = {
|
|
|
60
88
|
envInput: PropTypes.string,
|
|
61
89
|
message: PropTypes.string,
|
|
62
90
|
messageColor: PropTypes.string,
|
|
91
|
+
suggestions: PropTypes.arrayOf(PropTypes.string),
|
|
92
|
+
selectedSuggestionIndex: PropTypes.number,
|
|
93
|
+
visibleOffset: PropTypes.number,
|
|
63
94
|
};
|
|
95
|
+
|
|
96
|
+
// Named export for test compatibility with jest ESM interop
|
|
97
|
+
export { ContainerCreationPrompt };
|
|
@@ -9,10 +9,10 @@
|
|
|
9
9
|
* // Render with containers
|
|
10
10
|
* <ContainerList containers={containers} />
|
|
11
11
|
*/
|
|
12
|
-
import React from
|
|
12
|
+
import React from 'react';
|
|
13
13
|
import PropTypes from 'prop-types';
|
|
14
|
-
import { Box } from
|
|
15
|
-
import ContainerRow from
|
|
14
|
+
import { Box } from 'ink';
|
|
15
|
+
import ContainerRow from './ContainerRow.jsx';
|
|
16
16
|
|
|
17
17
|
export default function ContainerList({ containers, selected }) {
|
|
18
18
|
return (
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import StatsBar from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import StatsBar from './StatsBar.jsx';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
5
|
import { useContainerStats } from '../hooks/useContainerStats.js';
|
|
6
6
|
|
|
7
7
|
const stateText = (state) => {
|
|
8
|
-
if (state ===
|
|
9
|
-
if (state ===
|
|
10
|
-
if (state ===
|
|
11
|
-
return { text: state.toUpperCase(), color:
|
|
8
|
+
if (state === 'running') return { text: '🟢 RUNNING', color: 'green' };
|
|
9
|
+
if (state === 'exited') return { text: '🔴 EXITED', color: 'red' };
|
|
10
|
+
if (state === 'paused') return { text: '🟠 PAUSED', color: 'yellow' };
|
|
11
|
+
return { text: state.toUpperCase(), color: 'gray' };
|
|
12
12
|
};
|
|
13
13
|
|
|
14
14
|
/**
|
|
@@ -24,17 +24,17 @@ export default function ContainerRow({ container, isSelected = false }) {
|
|
|
24
24
|
|
|
25
25
|
// Format ports for display (no leading space to avoid layout shifts)
|
|
26
26
|
const formatPorts = (ports) => {
|
|
27
|
-
if (!ports || ports.length === 0) return
|
|
27
|
+
if (!ports || ports.length === 0) return '';
|
|
28
28
|
if (Array.isArray(ports)) {
|
|
29
|
-
return ports.map((p, _i) => `🔗 ${p}`).join(
|
|
29
|
+
return ports.map((p, _i) => `🔗 ${p}`).join(' ');
|
|
30
30
|
}
|
|
31
31
|
return `🔗 ${ports}`;
|
|
32
32
|
};
|
|
33
33
|
|
|
34
34
|
// Helper to truncate long strings to a max length without adding trailing spaces
|
|
35
35
|
const truncate = (s, max = 20) => {
|
|
36
|
-
if (!s) return
|
|
37
|
-
return s.length > max ? s.slice(0, max - 1) +
|
|
36
|
+
if (!s) return '';
|
|
37
|
+
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
38
38
|
};
|
|
39
39
|
|
|
40
40
|
const stateInfo = stateText(state);
|
|
@@ -42,8 +42,8 @@ export default function ContainerRow({ container, isSelected = false }) {
|
|
|
42
42
|
<Box flexDirection="column" marginBottom={1}>
|
|
43
43
|
<Box flexDirection="row" alignItems="center">
|
|
44
44
|
<Box width={2} minWidth={2} justifyContent="flex-end" paddingRight={1}>
|
|
45
|
-
<Text color={isSelected ?
|
|
46
|
-
{isSelected ?
|
|
45
|
+
<Text color={isSelected ? 'green' : undefined}>
|
|
46
|
+
{isSelected ? '➤' : ' '}
|
|
47
47
|
</Text>
|
|
48
48
|
</Box>
|
|
49
49
|
<Box width={18} flexShrink={1} paddingRight={1}>
|
|
@@ -59,8 +59,11 @@ export default function ContainerRow({ container, isSelected = false }) {
|
|
|
59
59
|
<Text color="yellow">{formatPorts(container.ports)}</Text>
|
|
60
60
|
</Box>
|
|
61
61
|
<Box flexShrink={0} paddingLeft={1}>
|
|
62
|
-
{state ===
|
|
63
|
-
<StatsBar
|
|
62
|
+
{state === 'running' ? (
|
|
63
|
+
<StatsBar
|
|
64
|
+
cpu={parseFloat(stats.cpuPercent)}
|
|
65
|
+
mem={parseFloat(stats.memPercent)}
|
|
66
|
+
/>
|
|
64
67
|
) : null}
|
|
65
68
|
</Box>
|
|
66
69
|
</Box>
|
|
@@ -75,7 +78,7 @@ ContainerRow.propTypes = {
|
|
|
75
78
|
name: PropTypes.string.isRequired,
|
|
76
79
|
image: PropTypes.string,
|
|
77
80
|
state: PropTypes.string,
|
|
78
|
-
ports: PropTypes.oneOfType([PropTypes.array, PropTypes.string])
|
|
81
|
+
ports: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
|
79
82
|
}).isRequired,
|
|
80
83
|
isSelected: PropTypes.bool,
|
|
81
84
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Box, Text } from
|
|
3
|
-
import chalk from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
import chalk from 'chalk';
|
|
4
4
|
import PropTypes from 'prop-types';
|
|
5
|
-
import { getAppVersion } from
|
|
5
|
+
import { getAppVersion } from '../helpers/appInfo.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Header component displayed at the top of the CLI UI.
|
|
@@ -18,18 +18,18 @@ import { getAppVersion } from "../helpers/appInfo.js";
|
|
|
18
18
|
const VERSION = getAppVersion();
|
|
19
19
|
|
|
20
20
|
export default function Header({ count }) {
|
|
21
|
-
const formattedVersion = VERSION ===
|
|
21
|
+
const formattedVersion = VERSION === 'unknown' ? 'unknown' : `v${VERSION}`;
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
24
|
<Box justifyContent="space-between">
|
|
25
25
|
<Text color="cyanBright">
|
|
26
|
-
🐳 {chalk.bold(
|
|
26
|
+
🐳 {chalk.bold('CDD')}
|
|
27
27
|
<Text color="gray"> — CLI Docker Dashboard</Text>
|
|
28
28
|
</Text>
|
|
29
29
|
<Box flexDirection="column" alignItems="flex-end">
|
|
30
30
|
<Text color="gray">{formattedVersion}</Text>
|
|
31
31
|
<Text color="gray">
|
|
32
|
-
{count} container{count === 1 ?
|
|
32
|
+
{count} container{count === 1 ? '' : 's'} found
|
|
33
33
|
</Text>
|
|
34
34
|
</Box>
|
|
35
35
|
</Box>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Text } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'ink';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -17,7 +17,9 @@ export default function LogViewer({ logs, onExit: _onExit, container }) {
|
|
|
17
17
|
|
|
18
18
|
return (
|
|
19
19
|
<>
|
|
20
|
-
<Text color="green">
|
|
20
|
+
<Text color="green">
|
|
21
|
+
{container?.name ?? 'Container'} logs, press ESC to exit
|
|
22
|
+
</Text>
|
|
21
23
|
{visibleLogs.length === 0 ? (
|
|
22
24
|
<Text dimColor>No logs...</Text>
|
|
23
25
|
) : (
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import React from
|
|
2
|
-
import { Text } from
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Text } from 'ink';
|
|
3
3
|
import PropTypes from 'prop-types';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -18,7 +18,7 @@ export function PromptField({ label, value, required }) {
|
|
|
18
18
|
return (
|
|
19
19
|
<>
|
|
20
20
|
<Text>{label}</Text>
|
|
21
|
-
<Text color={isEmpty ?
|
|
21
|
+
<Text color={isEmpty ? 'red' : 'cyan'}>{value}_</Text>
|
|
22
22
|
</>
|
|
23
23
|
);
|
|
24
24
|
}
|
|
@@ -33,7 +33,7 @@ export function PromptField({ label, value, required }) {
|
|
|
33
33
|
*/
|
|
34
34
|
export function PromptMessage({ message, color }) {
|
|
35
35
|
if (!message) return null;
|
|
36
|
-
return <Text color={color ||
|
|
36
|
+
return <Text color={color || 'yellow'}>{message}</Text>;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
PromptField.propTypes = {
|