cdd-cli 3.1.9 → 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 +46 -0
- package/SECURITY.md +5 -0
- package/__mocks__/ink.cjs +17 -0
- package/coverage/clover.xml +694 -0
- package/coverage/coverage-final.json +21 -0
- package/coverage/lcov-report/base.css +224 -0
- package/coverage/lcov-report/block-navigation.js +87 -0
- 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/favicon.png +0 -0
- package/coverage/lcov-report/helpers/constants.js.html +298 -0
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +244 -0
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +931 -0
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +307 -0
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +131 -0
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +181 -0
- package/coverage/lcov-report/helpers/index.html +191 -0
- package/coverage/lcov-report/helpers/logger.js.html +424 -0
- package/coverage/lcov-report/helpers/safeCall.js.html +139 -0
- package/coverage/lcov-report/helpers/validationHelpers.js.html +346 -0
- package/coverage/lcov-report/hooks/creation/index.html +146 -0
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +313 -0
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +805 -0
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +238 -0
- package/coverage/lcov-report/hooks/debug/index.html +116 -0
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +187 -0
- package/coverage/lcov-report/hooks/index.html +161 -0
- package/coverage/lcov-report/hooks/navigation/index.html +116 -0
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +169 -0
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +454 -0
- package/coverage/lcov-report/hooks/useControls.js.html +826 -0
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +202 -0
- package/coverage/lcov-report/hooks/useExitHandler.js.html +292 -0
- package/coverage/lcov-report/index.html +206 -0
- package/coverage/lcov-report/prettify.css +1 -0
- package/coverage/lcov-report/prettify.js +2 -0
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +210 -0
- package/coverage/lcov.info +1402 -0
- 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 +41 -0
- package/src/helpers/containerOptionsBuilder.js +16 -5
- package/src/helpers/dockerService/dockerService.js +1 -1
- package/src/helpers/dockerService/serviceComponents/containerActions.js +116 -43
- 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 +62 -21
- package/src/hooks/creation/useContainerActions.js +19 -12
- package/src/hooks/creation/useContainerCreation.js +161 -37
- 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 +120 -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/containerActions.test.js +104 -13
- package/test/containerOptionsBuilder.test.js +36 -0
- package/test/useContainerCreation.dom.test.js +166 -2
- package/test/useControls.dom.test.js +313 -0
- package/test/validationHelpers.test.js +51 -0
|
@@ -1,6 +1,12 @@
|
|
|
1
|
-
import { useState } from
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
validatePorts,
|
|
4
|
+
validateEnvVars,
|
|
5
|
+
} from '../../helpers/validationHelpers.js';
|
|
6
|
+
import { IMAGE_PROFILES } from '../../helpers/constants.js';
|
|
7
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
8
|
+
|
|
9
|
+
const MAX_VISIBLE = 6;
|
|
4
10
|
|
|
5
11
|
/**
|
|
6
12
|
* Custom hook to manage the container creation flow, step by step.
|
|
@@ -9,17 +15,82 @@ import { safeCall } from "../../helpers/safeCall.js";
|
|
|
9
15
|
* @param {Object} params
|
|
10
16
|
* @param {Function} params.onCreate - Callback when creation is confirmed
|
|
11
17
|
* @param {Function} params.onCancel - Callback when creation is cancelled
|
|
12
|
-
* @param {Array<string>} params.dbImages - List of DB image names for env var warning
|
|
18
|
+
* @param {Array<string>} params.dbImages - List of DB image names for env var warning (legacy)
|
|
19
|
+
* @param {Object} [params.imageProfiles] - Image profiles map for contextual validation
|
|
13
20
|
* @returns {Object} Creation state, setters, and helpers
|
|
14
21
|
*/
|
|
15
|
-
export function useContainerCreation({
|
|
22
|
+
export function useContainerCreation({
|
|
23
|
+
onCreate,
|
|
24
|
+
onCancel,
|
|
25
|
+
dbImages = [],
|
|
26
|
+
imageProfiles = IMAGE_PROFILES,
|
|
27
|
+
}) {
|
|
16
28
|
const [step, setStep] = useState(0); // 0: image, 1: name, 2: ports, 3: env
|
|
17
|
-
const [imageName, setImageName] = useState(
|
|
18
|
-
const [containerName, setContainerName] = useState(
|
|
19
|
-
const [portInput, setPortInput] = useState(
|
|
20
|
-
const [envInput, setEnvInput] = useState(
|
|
21
|
-
const [message, setMessage] = useState(
|
|
22
|
-
const [messageColor, setMessageColor] = useState(
|
|
29
|
+
const [imageName, setImageName] = useState('');
|
|
30
|
+
const [containerName, setContainerName] = useState('');
|
|
31
|
+
const [portInput, setPortInput] = useState('');
|
|
32
|
+
const [envInput, setEnvInput] = useState('');
|
|
33
|
+
const [message, setMessage] = useState('');
|
|
34
|
+
const [messageColor, setMessageColor] = useState('yellow');
|
|
35
|
+
|
|
36
|
+
// Autocomplete state
|
|
37
|
+
const [suggestions, setSuggestions] = useState([]);
|
|
38
|
+
const [selectedSuggestionIndex, setSelectedSuggestionIndex] = useState(-1);
|
|
39
|
+
const [visibleOffset, setVisibleOffset] = useState(0);
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Updates the image name input and recalculates autocomplete suggestions.
|
|
43
|
+
* Resets selectedSuggestionIndex to -1 on every keystroke.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} value - New raw image name typed by user
|
|
46
|
+
*/
|
|
47
|
+
function updateImageInput(value) {
|
|
48
|
+
setImageName(value);
|
|
49
|
+
setSelectedSuggestionIndex(-1);
|
|
50
|
+
setVisibleOffset(0);
|
|
51
|
+
if (!value.trim()) {
|
|
52
|
+
setSuggestions([]);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const lower = value.toLowerCase();
|
|
56
|
+
const matches = Object.keys(imageProfiles).filter((key) =>
|
|
57
|
+
key.includes(lower)
|
|
58
|
+
);
|
|
59
|
+
setSuggestions(matches);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Moves the suggestion selection up (-1) or down (+1).
|
|
64
|
+
* Clamps index to [-1, suggestions.length - 1].
|
|
65
|
+
* Adjusts visibleOffset to keep selected item in the visible window.
|
|
66
|
+
*
|
|
67
|
+
* @param {number} direction - -1 (up) or 1 (down)
|
|
68
|
+
*/
|
|
69
|
+
function moveSuggestionSelection(direction) {
|
|
70
|
+
setSelectedSuggestionIndex((prev) => {
|
|
71
|
+
const next = Math.max(-1, Math.min(suggestions.length - 1, prev + direction));
|
|
72
|
+
// Adjust visibleOffset to keep selected in view
|
|
73
|
+
setVisibleOffset((offset) => {
|
|
74
|
+
if (next < offset) return Math.max(0, next);
|
|
75
|
+
if (next >= offset + MAX_VISIBLE) return next - MAX_VISIBLE + 1;
|
|
76
|
+
return offset;
|
|
77
|
+
});
|
|
78
|
+
return next;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Applies the currently focused suggestion to imageName.
|
|
84
|
+
* Does NOT advance the step. Clears suggestions after applying.
|
|
85
|
+
*/
|
|
86
|
+
function applyFocusedSuggestion() {
|
|
87
|
+
if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
|
|
88
|
+
const chosen = suggestions[selectedSuggestionIndex];
|
|
89
|
+
setImageName(chosen);
|
|
90
|
+
setSuggestions([]);
|
|
91
|
+
setSelectedSuggestionIndex(-1);
|
|
92
|
+
setVisibleOffset(0);
|
|
93
|
+
}
|
|
23
94
|
|
|
24
95
|
/**
|
|
25
96
|
* Advances to the next step, with validation and feedback.
|
|
@@ -27,40 +98,81 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
|
|
|
27
98
|
function nextStep() {
|
|
28
99
|
if (step === 0) {
|
|
29
100
|
if (!imageName.trim()) {
|
|
30
|
-
setMessage(
|
|
31
|
-
setMessageColor(
|
|
101
|
+
setMessage('Image name cannot be empty.');
|
|
102
|
+
setMessageColor('red');
|
|
32
103
|
return;
|
|
33
104
|
}
|
|
34
105
|
setStep(1);
|
|
35
|
-
setMessage(
|
|
36
|
-
|
|
106
|
+
setMessage(
|
|
107
|
+
'Optional: Enter container name or leave empty and press Enter'
|
|
108
|
+
);
|
|
109
|
+
setMessageColor('yellow');
|
|
37
110
|
return;
|
|
38
111
|
}
|
|
39
112
|
if (step === 1) {
|
|
40
113
|
setStep(2);
|
|
41
|
-
setMessage(
|
|
42
|
-
|
|
114
|
+
setMessage(
|
|
115
|
+
'Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter'
|
|
116
|
+
);
|
|
117
|
+
setMessageColor('yellow');
|
|
43
118
|
return;
|
|
44
119
|
}
|
|
45
120
|
if (step === 2) {
|
|
46
121
|
// Ports are now optional - only validate if provided
|
|
47
122
|
if (portInput.trim() && !validatePorts(portInput)) {
|
|
48
|
-
setMessage(
|
|
49
|
-
|
|
123
|
+
setMessage(
|
|
124
|
+
'Port format must be host:container and both must be numbers (e.g. 8080:80)'
|
|
125
|
+
);
|
|
126
|
+
setMessageColor('red');
|
|
50
127
|
return;
|
|
51
128
|
}
|
|
52
129
|
setStep(3);
|
|
53
|
-
const isDb = dbImages.some(db =>
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
130
|
+
const isDb = dbImages.some((db) =>
|
|
131
|
+
imageName.trim().toLowerCase().includes(db)
|
|
132
|
+
);
|
|
133
|
+
// Check if the image has a profile with required env vars
|
|
134
|
+
const baseName = imageName
|
|
135
|
+
.trim()
|
|
136
|
+
.toLowerCase()
|
|
137
|
+
.split(':')[0]
|
|
138
|
+
.split('/')
|
|
139
|
+
.pop();
|
|
140
|
+
const profile = imageProfiles[baseName];
|
|
141
|
+
if (profile && profile.requiredEnv && profile.requiredEnv.length) {
|
|
142
|
+
const suggestedPart =
|
|
143
|
+
profile.suggestedEnv && profile.suggestedEnv.length
|
|
144
|
+
? ` | Suggested: ${profile.suggestedEnv.join(', ')}`
|
|
145
|
+
: '';
|
|
146
|
+
setMessage(
|
|
147
|
+
`Required env vars for ${baseName}: ${profile.requiredEnv.join(', ')}. Enter as VAR=val,VAR2=val2${suggestedPart}`
|
|
148
|
+
);
|
|
149
|
+
setMessageColor('yellow');
|
|
150
|
+
} else if (profile && profile.suggestedEnv && profile.suggestedEnv.length) {
|
|
151
|
+
setMessage(
|
|
152
|
+
`Suggested env vars for ${baseName}: ${profile.suggestedEnv.join(', ')}. Enter as VAR=val,VAR2=val2 or leave empty.`
|
|
153
|
+
);
|
|
154
|
+
setMessageColor('yellow');
|
|
155
|
+
} else if (isDb) {
|
|
156
|
+
setMessage(
|
|
157
|
+
'Warning: This image usually requires environment variables (e.g. MYSQL_ROOT_PASSWORD=my-secret-pw for MySQL, POSTGRES_PASSWORD=yourpassword for Postgres). Enter them as VAR=val,VAR2=val2 or leave empty and press Enter.'
|
|
158
|
+
);
|
|
159
|
+
setMessageColor('yellow');
|
|
57
160
|
} else {
|
|
58
|
-
setMessage(
|
|
59
|
-
|
|
161
|
+
setMessage(
|
|
162
|
+
'Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter'
|
|
163
|
+
);
|
|
164
|
+
setMessageColor('yellow');
|
|
60
165
|
}
|
|
61
166
|
return;
|
|
62
167
|
}
|
|
63
168
|
if (step === 3) {
|
|
169
|
+
// Contextual env validation using image profiles
|
|
170
|
+
const result = validateEnvVars(envInput, imageName, imageProfiles);
|
|
171
|
+
if (!result.valid) {
|
|
172
|
+
setMessage(result.errors.join(' | '));
|
|
173
|
+
setMessageColor('red');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
64
176
|
// Final step: call onCreate with all data (safely)
|
|
65
177
|
safeCall(onCreate, { imageName, containerName, portInput, envInput });
|
|
66
178
|
}
|
|
@@ -71,12 +183,15 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
|
|
|
71
183
|
*/
|
|
72
184
|
function cancelCreation() {
|
|
73
185
|
setStep(0);
|
|
74
|
-
setImageName(
|
|
75
|
-
setContainerName(
|
|
76
|
-
setPortInput(
|
|
77
|
-
setEnvInput(
|
|
78
|
-
setMessage(
|
|
79
|
-
setMessageColor(
|
|
186
|
+
setImageName('');
|
|
187
|
+
setContainerName('');
|
|
188
|
+
setPortInput('');
|
|
189
|
+
setEnvInput('');
|
|
190
|
+
setMessage('');
|
|
191
|
+
setMessageColor('yellow');
|
|
192
|
+
setSuggestions([]);
|
|
193
|
+
setSelectedSuggestionIndex(-1);
|
|
194
|
+
setVisibleOffset(0);
|
|
80
195
|
safeCall(onCancel);
|
|
81
196
|
}
|
|
82
197
|
|
|
@@ -86,12 +201,15 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
|
|
|
86
201
|
*/
|
|
87
202
|
function resetCreation() {
|
|
88
203
|
setStep(0);
|
|
89
|
-
setImageName(
|
|
90
|
-
setContainerName(
|
|
91
|
-
setPortInput(
|
|
92
|
-
setEnvInput(
|
|
93
|
-
setMessage(
|
|
94
|
-
setMessageColor(
|
|
204
|
+
setImageName('');
|
|
205
|
+
setContainerName('');
|
|
206
|
+
setPortInput('');
|
|
207
|
+
setEnvInput('');
|
|
208
|
+
setMessage('Insert the name of the image to create: ');
|
|
209
|
+
setMessageColor('yellow');
|
|
210
|
+
setSuggestions([]);
|
|
211
|
+
setSelectedSuggestionIndex(-1);
|
|
212
|
+
setVisibleOffset(0);
|
|
95
213
|
}
|
|
96
214
|
|
|
97
215
|
return {
|
|
@@ -109,6 +227,12 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
|
|
|
109
227
|
setMessage,
|
|
110
228
|
messageColor,
|
|
111
229
|
setMessageColor,
|
|
230
|
+
suggestions,
|
|
231
|
+
selectedSuggestionIndex,
|
|
232
|
+
visibleOffset,
|
|
233
|
+
updateImageInput,
|
|
234
|
+
moveSuggestionSelection,
|
|
235
|
+
applyFocusedSuggestion,
|
|
112
236
|
nextStep,
|
|
113
237
|
cancelCreation,
|
|
114
238
|
resetCreation,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useRef } from
|
|
2
|
-
import { safeCall } from
|
|
1
|
+
import { useState, useRef } from 'react';
|
|
2
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Custom hook to manage the logs viewer state and stream reference.
|
|
@@ -8,16 +8,21 @@ export function useDebugLogs() {
|
|
|
8
8
|
useEffect(() => {
|
|
9
9
|
const unsubscribe = logger.subscribe((entry) => {
|
|
10
10
|
setDebugLogs((prev) => {
|
|
11
|
-
const extras = (entry.args || [])
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
11
|
+
const extras = (entry.args || [])
|
|
12
|
+
.map((arg) => {
|
|
13
|
+
if (typeof arg === 'string') return arg;
|
|
14
|
+
if (typeof arg === 'number' || typeof arg === 'boolean')
|
|
15
|
+
return String(arg);
|
|
16
|
+
try {
|
|
17
|
+
return JSON.stringify(arg);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
return '[unserializable]';
|
|
20
|
+
}
|
|
21
|
+
})
|
|
22
|
+
.filter(Boolean);
|
|
23
|
+
const line = extras.length
|
|
24
|
+
? `${entry.formatted} ${extras.join(' ')}`
|
|
25
|
+
: entry.formatted;
|
|
21
26
|
const next = [...prev, line];
|
|
22
27
|
return next.slice(-200);
|
|
23
28
|
});
|
|
@@ -3,23 +3,26 @@ import { useState, useCallback } from 'react';
|
|
|
3
3
|
export function useContainerSelection(total) {
|
|
4
4
|
const [selected, setSelected] = useState(0);
|
|
5
5
|
|
|
6
|
-
const handleNavigation = useCallback(
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const handleNavigation = useCallback(
|
|
7
|
+
(input, key) => {
|
|
8
|
+
if (total === 0) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
if (key.upArrow) {
|
|
13
|
+
setSelected((i) => (i === 0 ? total - 1 : i - 1));
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
15
16
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
if (key.downArrow) {
|
|
18
|
+
setSelected((i) => (i === total - 1 ? 0 : i + 1));
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
return false;
|
|
23
|
+
},
|
|
24
|
+
[total]
|
|
25
|
+
);
|
|
23
26
|
|
|
24
27
|
return { selected, setSelected, handleNavigation };
|
|
25
28
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { useCallback } from
|
|
1
|
+
import { useCallback } from 'react';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Hook responsible solely for routing Docker action keystrokes to the correct handler.
|
|
@@ -36,75 +36,88 @@ export function useContainerCommandRouter({
|
|
|
36
36
|
onToggleDebug,
|
|
37
37
|
onStartCreate,
|
|
38
38
|
}) {
|
|
39
|
-
const handleDockerCommands = useCallback(
|
|
40
|
-
|
|
39
|
+
const handleDockerCommands = useCallback(
|
|
40
|
+
(input) => {
|
|
41
|
+
const container = containers[selected];
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
43
|
+
if (input === 'i') {
|
|
44
|
+
actions.handleAction({
|
|
45
|
+
actionFn: async (id) => await actions.startContainer(id),
|
|
46
|
+
actionLabel: 'Starting',
|
|
47
|
+
selected,
|
|
48
|
+
stateCheck: (c) =>
|
|
49
|
+
(c.state === 'running' || c.status === 'running') &&
|
|
50
|
+
'Container is already running.',
|
|
51
|
+
});
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
53
54
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
55
|
+
if (input === 'p') {
|
|
56
|
+
actions.handleAction({
|
|
57
|
+
actionFn: async (id) => await actions.stopContainer(id),
|
|
58
|
+
actionLabel: 'Stopping',
|
|
59
|
+
selected,
|
|
60
|
+
stateCheck: (c) =>
|
|
61
|
+
(c.state === 'exited' ||
|
|
62
|
+
c.status === 'exited' ||
|
|
63
|
+
c.state === 'stopped' ||
|
|
64
|
+
c.status === 'stopped') &&
|
|
65
|
+
'Container is already stopped.',
|
|
66
|
+
});
|
|
67
|
+
return true;
|
|
68
|
+
}
|
|
68
69
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
70
|
+
if (input === 'r') {
|
|
71
|
+
actions.handleAction({
|
|
72
|
+
actionFn: async (id) => await actions.restartContainer(id),
|
|
73
|
+
actionLabel: 'Restarting',
|
|
74
|
+
selected,
|
|
75
|
+
});
|
|
76
|
+
return true;
|
|
77
|
+
}
|
|
77
78
|
|
|
78
|
-
|
|
79
|
-
|
|
79
|
+
if (input === 'e') {
|
|
80
|
+
if (!container) {
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
onStartErase();
|
|
80
84
|
return true;
|
|
81
85
|
}
|
|
82
|
-
onStartErase();
|
|
83
|
-
return true;
|
|
84
|
-
}
|
|
85
86
|
|
|
86
|
-
|
|
87
|
-
|
|
87
|
+
if (input === 'l') {
|
|
88
|
+
if (!container) {
|
|
89
|
+
return true;
|
|
90
|
+
}
|
|
91
|
+
logsViewer.openLogs();
|
|
92
|
+
startLogsStream(container.id);
|
|
88
93
|
return true;
|
|
89
94
|
}
|
|
90
|
-
logsViewer.openLogs();
|
|
91
|
-
startLogsStream(container.id);
|
|
92
|
-
return true;
|
|
93
|
-
}
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
96
|
+
if (input === 'c') {
|
|
97
|
+
onStartCreate();
|
|
98
|
+
creation.resetCreation();
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
100
101
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
102
|
+
if (input === 'd') {
|
|
103
|
+
onToggleDebug();
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
105
106
|
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
return false;
|
|
108
|
+
},
|
|
109
|
+
[
|
|
110
|
+
actions,
|
|
111
|
+
containers,
|
|
112
|
+
creation,
|
|
113
|
+
logsViewer,
|
|
114
|
+
onStartCreate,
|
|
115
|
+
onStartErase,
|
|
116
|
+
onToggleDebug,
|
|
117
|
+
selected,
|
|
118
|
+
startLogsStream,
|
|
119
|
+
]
|
|
120
|
+
);
|
|
108
121
|
|
|
109
122
|
return { handleDockerCommands };
|
|
110
123
|
}
|
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* // Use in a component
|
|
9
9
|
* const { containers } = useContainers();
|
|
10
10
|
*/
|
|
11
|
-
import React, { useState, useEffect } from
|
|
12
|
-
import { getContainers } from
|
|
13
|
-
import { REFRESH_INTERVALS } from
|
|
11
|
+
import React, { useState, useEffect } from 'react';
|
|
12
|
+
import { getContainers } from '../helpers/dockerService/serviceComponents/containerList';
|
|
13
|
+
import { REFRESH_INTERVALS } from '../helpers/constants.js';
|
|
14
14
|
|
|
15
15
|
export function useContainers() {
|
|
16
16
|
const [containers, setContainers] = useState([]);
|
|
@@ -18,7 +18,7 @@ export function useContainers() {
|
|
|
18
18
|
useEffect(() => {
|
|
19
19
|
const fetch = async () => setContainers(await getContainers());
|
|
20
20
|
fetch();
|
|
21
|
-
|
|
21
|
+
const timer = setInterval(fetch, REFRESH_INTERVALS.CONTAINER_LIST);
|
|
22
22
|
return () => clearInterval(timer);
|
|
23
23
|
}, []);
|
|
24
24
|
|