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
package/dist/helpers/safeCall.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { logger } from
|
|
1
|
+
import { logger } from './logger.js';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Call a function safely: if it's a function call it with provided args and
|
|
@@ -15,7 +15,7 @@ export function safeCall(fn) {
|
|
|
15
15
|
}
|
|
16
16
|
return fn.apply(void 0, args);
|
|
17
17
|
} catch (e) {
|
|
18
|
-
logger.error(
|
|
18
|
+
logger.error('safeCall caught error', e);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
|
|
1
2
|
function _slicedToArray(r, e) { return _arrayWithHoles(r) || _iterableToArrayLimit(r, e) || _unsupportedIterableToArray(r, e) || _nonIterableRest(); }
|
|
2
3
|
function _nonIterableRest() { throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); }
|
|
3
4
|
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
@@ -5,6 +6,7 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
5
6
|
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
7
|
function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
7
8
|
// Reusable validations for ports and environment variables
|
|
9
|
+
import { normalizeImageName } from './imageNameUtils.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Validate a comma-separated list of port mappings in the form "host:container".
|
|
@@ -16,12 +18,12 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
|
16
18
|
export function validatePorts(portInput) {
|
|
17
19
|
// Empty or whitespace-only input is considered valid (ports are optional)
|
|
18
20
|
if (!portInput || !portInput.trim()) return true;
|
|
19
|
-
var ports = portInput.split(
|
|
21
|
+
var ports = portInput.split(',').map(function (p) {
|
|
20
22
|
return p.trim();
|
|
21
23
|
}).filter(Boolean);
|
|
22
24
|
if (ports.length === 0) return true;
|
|
23
25
|
var invalid = ports.find(function (pair) {
|
|
24
|
-
var _pair$split = pair.split(
|
|
26
|
+
var _pair$split = pair.split(':'),
|
|
25
27
|
_pair$split2 = _slicedToArray(_pair$split, 2),
|
|
26
28
|
host = _pair$split2[0],
|
|
27
29
|
cont = _pair$split2[1];
|
|
@@ -32,27 +34,80 @@ export function validatePorts(portInput) {
|
|
|
32
34
|
|
|
33
35
|
/**
|
|
34
36
|
* Validate environment variable input as comma-separated VAR=value pairs.
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
37
|
+
*
|
|
38
|
+
* When called with 3 arguments (envInput, imageName, imageProfiles), performs
|
|
39
|
+
* contextual validation: checks that all required env vars for the given image
|
|
40
|
+
* are present and non-empty. Returns an object { valid, errors, parsedEnv }.
|
|
41
|
+
*
|
|
42
|
+
* When called with 1 argument (legacy), returns a boolean for backward compatibility.
|
|
38
43
|
*
|
|
39
44
|
* @param {string} envInput - Comma-separated environment variable assignments
|
|
40
|
-
* @
|
|
45
|
+
* @param {string} [imageName] - Optional Docker image name for contextual validation
|
|
46
|
+
* @param {Object} [imageProfiles] - Optional map of image profiles (from constants.js)
|
|
47
|
+
* @returns {boolean|{valid: boolean, errors: string[], parsedEnv: Record<string,string>}}
|
|
41
48
|
*/
|
|
42
|
-
export function validateEnvVars(envInput) {
|
|
43
|
-
|
|
49
|
+
export function validateEnvVars(envInput, imageName, imageProfiles) {
|
|
50
|
+
var contextual = imageName !== undefined && imageProfiles !== undefined;
|
|
44
51
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
var
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
var
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
52
|
+
// Parse: split on FIRST '=' only
|
|
53
|
+
var parsedEnv = {};
|
|
54
|
+
var syntaxErrors = [];
|
|
55
|
+
if (envInput && envInput.trim()) {
|
|
56
|
+
var vars = envInput.split(',').map(function (v) {
|
|
57
|
+
return v.trim();
|
|
58
|
+
}).filter(Boolean);
|
|
59
|
+
var _iterator = _createForOfIteratorHelper(vars),
|
|
60
|
+
_step;
|
|
61
|
+
try {
|
|
62
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
63
|
+
var v = _step.value;
|
|
64
|
+
var eqIdx = v.indexOf('=');
|
|
65
|
+
if (eqIdx === -1) {
|
|
66
|
+
syntaxErrors.push("\"".concat(v, "\" is missing an '=' sign"));
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
var varName = v.slice(0, eqIdx).trim();
|
|
70
|
+
var varValue = v.slice(eqIdx + 1);
|
|
71
|
+
if (!/^[A-Z_][A-Z0-9_]*$/i.test(varName)) {
|
|
72
|
+
syntaxErrors.push("\"".concat(varName, "\" is not a valid variable name"));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
parsedEnv[varName] = varValue;
|
|
76
|
+
}
|
|
77
|
+
} catch (err) {
|
|
78
|
+
_iterator.e(err);
|
|
79
|
+
} finally {
|
|
80
|
+
_iterator.f();
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (!contextual) {
|
|
84
|
+
// Legacy: return boolean
|
|
85
|
+
return syntaxErrors.length === 0;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// Contextual: also check required env vars from the profile
|
|
89
|
+
var errors = [].concat(syntaxErrors);
|
|
90
|
+
var baseName = normalizeImageName(imageName);
|
|
91
|
+
var profile = imageProfiles[baseName];
|
|
92
|
+
if (profile && profile.requiredEnv && profile.requiredEnv.length) {
|
|
93
|
+
var _iterator2 = _createForOfIteratorHelper(profile.requiredEnv),
|
|
94
|
+
_step2;
|
|
95
|
+
try {
|
|
96
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
97
|
+
var required = _step2.value;
|
|
98
|
+
if (!parsedEnv[required] || parsedEnv[required].trim() === '') {
|
|
99
|
+
errors.push("Missing required env var: ".concat(required));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
} catch (err) {
|
|
103
|
+
_iterator2.e(err);
|
|
104
|
+
} finally {
|
|
105
|
+
_iterator2.f();
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
valid: errors.length === 0,
|
|
110
|
+
errors: errors,
|
|
111
|
+
parsedEnv: parsedEnv
|
|
112
|
+
};
|
|
58
113
|
}
|
|
@@ -8,9 +8,9 @@ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r)
|
|
|
8
8
|
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; }
|
|
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
|
-
import { useState } from
|
|
12
|
-
import { safeCall } from
|
|
13
|
-
import { startContainer as svcStartContainer, stopContainer as svcStopContainer, restartContainer as svcRestartContainer, removeContainer as svcRemoveContainer } from
|
|
11
|
+
import { useState } from 'react';
|
|
12
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
13
|
+
import { startContainer as svcStartContainer, stopContainer as svcStopContainer, restartContainer as svcRestartContainer, removeContainer as svcRemoveContainer } from '../../helpers/dockerService/serviceComponents/containerActions.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Custom hook to manage container actions (start, stop, restart, remove).
|
|
@@ -24,11 +24,11 @@ import { startContainer as svcStartContainer, stopContainer as svcStopContainer,
|
|
|
24
24
|
export function useContainerActions(_ref) {
|
|
25
25
|
var containers = _ref.containers,
|
|
26
26
|
onAction = _ref.onAction;
|
|
27
|
-
var _useState = useState(
|
|
27
|
+
var _useState = useState(''),
|
|
28
28
|
_useState2 = _slicedToArray(_useState, 2),
|
|
29
29
|
message = _useState2[0],
|
|
30
30
|
setMessage = _useState2[1];
|
|
31
|
-
var _useState3 = useState(
|
|
31
|
+
var _useState3 = useState('yellow'),
|
|
32
32
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
33
33
|
messageColor = _useState4[0],
|
|
34
34
|
setMessageColor = _useState4[1];
|
|
@@ -67,17 +67,17 @@ export function useContainerActions(_ref) {
|
|
|
67
67
|
break;
|
|
68
68
|
}
|
|
69
69
|
setMessage(checkMsg);
|
|
70
|
-
setMessageColor(
|
|
70
|
+
setMessageColor('yellow');
|
|
71
71
|
return _context5.a(2);
|
|
72
72
|
case 2:
|
|
73
73
|
setMessage("".concat(actionLabel, " container..."));
|
|
74
|
-
setMessageColor(
|
|
74
|
+
setMessageColor('yellow');
|
|
75
75
|
_context5.p = 3;
|
|
76
76
|
_context5.n = 4;
|
|
77
77
|
return actionFn(container.id);
|
|
78
78
|
case 4:
|
|
79
79
|
setMessage("".concat(actionLabel, " container successful."));
|
|
80
|
-
setMessageColor(
|
|
80
|
+
setMessageColor('green');
|
|
81
81
|
safeCall(onAction);
|
|
82
82
|
_context5.n = 6;
|
|
83
83
|
break;
|
|
@@ -85,7 +85,7 @@ export function useContainerActions(_ref) {
|
|
|
85
85
|
_context5.p = 5;
|
|
86
86
|
_t = _context5.v;
|
|
87
87
|
setMessage("Failed to ".concat(actionLabel.toLowerCase(), " container: ").concat(_t.message));
|
|
88
|
-
setMessageColor(
|
|
88
|
+
setMessageColor('red');
|
|
89
89
|
case 6:
|
|
90
90
|
return _context5.a(2);
|
|
91
91
|
}
|
|
@@ -4,9 +4,11 @@ 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 } from
|
|
8
|
-
import { validatePorts } from
|
|
9
|
-
import {
|
|
7
|
+
import { useState } from 'react';
|
|
8
|
+
import { validatePorts, validateEnvVars } from '../../helpers/validationHelpers.js';
|
|
9
|
+
import { IMAGE_PROFILES } from '../../helpers/constants.js';
|
|
10
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
11
|
+
var MAX_VISIBLE = 6;
|
|
10
12
|
|
|
11
13
|
/**
|
|
12
14
|
* Custom hook to manage the container creation flow, step by step.
|
|
@@ -15,85 +17,173 @@ import { safeCall } from "../../helpers/safeCall.js";
|
|
|
15
17
|
* @param {Object} params
|
|
16
18
|
* @param {Function} params.onCreate - Callback when creation is confirmed
|
|
17
19
|
* @param {Function} params.onCancel - Callback when creation is cancelled
|
|
18
|
-
* @param {Array<string>} params.dbImages - List of DB image names for env var warning
|
|
20
|
+
* @param {Array<string>} params.dbImages - List of DB image names for env var warning (legacy)
|
|
21
|
+
* @param {Object} [params.imageProfiles] - Image profiles map for contextual validation
|
|
19
22
|
* @returns {Object} Creation state, setters, and helpers
|
|
20
23
|
*/
|
|
21
24
|
export function useContainerCreation(_ref) {
|
|
22
25
|
var onCreate = _ref.onCreate,
|
|
23
26
|
onCancel = _ref.onCancel,
|
|
24
27
|
_ref$dbImages = _ref.dbImages,
|
|
25
|
-
dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages
|
|
28
|
+
dbImages = _ref$dbImages === void 0 ? [] : _ref$dbImages,
|
|
29
|
+
_ref$imageProfiles = _ref.imageProfiles,
|
|
30
|
+
imageProfiles = _ref$imageProfiles === void 0 ? IMAGE_PROFILES : _ref$imageProfiles;
|
|
26
31
|
var _useState = useState(0),
|
|
27
32
|
_useState2 = _slicedToArray(_useState, 2),
|
|
28
33
|
step = _useState2[0],
|
|
29
34
|
setStep = _useState2[1]; // 0: image, 1: name, 2: ports, 3: env
|
|
30
|
-
var _useState3 = useState(
|
|
35
|
+
var _useState3 = useState(''),
|
|
31
36
|
_useState4 = _slicedToArray(_useState3, 2),
|
|
32
37
|
imageName = _useState4[0],
|
|
33
38
|
setImageName = _useState4[1];
|
|
34
|
-
var _useState5 = useState(
|
|
39
|
+
var _useState5 = useState(''),
|
|
35
40
|
_useState6 = _slicedToArray(_useState5, 2),
|
|
36
41
|
containerName = _useState6[0],
|
|
37
42
|
setContainerName = _useState6[1];
|
|
38
|
-
var _useState7 = useState(
|
|
43
|
+
var _useState7 = useState(''),
|
|
39
44
|
_useState8 = _slicedToArray(_useState7, 2),
|
|
40
45
|
portInput = _useState8[0],
|
|
41
46
|
setPortInput = _useState8[1];
|
|
42
|
-
var _useState9 = useState(
|
|
47
|
+
var _useState9 = useState(''),
|
|
43
48
|
_useState0 = _slicedToArray(_useState9, 2),
|
|
44
49
|
envInput = _useState0[0],
|
|
45
50
|
setEnvInput = _useState0[1];
|
|
46
|
-
var _useState1 = useState(
|
|
51
|
+
var _useState1 = useState(''),
|
|
47
52
|
_useState10 = _slicedToArray(_useState1, 2),
|
|
48
53
|
message = _useState10[0],
|
|
49
54
|
setMessage = _useState10[1];
|
|
50
|
-
var _useState11 = useState(
|
|
55
|
+
var _useState11 = useState('yellow'),
|
|
51
56
|
_useState12 = _slicedToArray(_useState11, 2),
|
|
52
57
|
messageColor = _useState12[0],
|
|
53
58
|
setMessageColor = _useState12[1];
|
|
54
59
|
|
|
60
|
+
// Autocomplete state
|
|
61
|
+
var _useState13 = useState([]),
|
|
62
|
+
_useState14 = _slicedToArray(_useState13, 2),
|
|
63
|
+
suggestions = _useState14[0],
|
|
64
|
+
setSuggestions = _useState14[1];
|
|
65
|
+
var _useState15 = useState(-1),
|
|
66
|
+
_useState16 = _slicedToArray(_useState15, 2),
|
|
67
|
+
selectedSuggestionIndex = _useState16[0],
|
|
68
|
+
setSelectedSuggestionIndex = _useState16[1];
|
|
69
|
+
var _useState17 = useState(0),
|
|
70
|
+
_useState18 = _slicedToArray(_useState17, 2),
|
|
71
|
+
visibleOffset = _useState18[0],
|
|
72
|
+
setVisibleOffset = _useState18[1];
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Updates the image name input and recalculates autocomplete suggestions.
|
|
76
|
+
* Resets selectedSuggestionIndex to -1 on every keystroke.
|
|
77
|
+
*
|
|
78
|
+
* @param {string} value - New raw image name typed by user
|
|
79
|
+
*/
|
|
80
|
+
function updateImageInput(value) {
|
|
81
|
+
setImageName(value);
|
|
82
|
+
setSelectedSuggestionIndex(-1);
|
|
83
|
+
setVisibleOffset(0);
|
|
84
|
+
if (!value.trim()) {
|
|
85
|
+
setSuggestions([]);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
var lower = value.toLowerCase();
|
|
89
|
+
var matches = Object.keys(imageProfiles).filter(function (key) {
|
|
90
|
+
return key.includes(lower);
|
|
91
|
+
});
|
|
92
|
+
setSuggestions(matches);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Moves the suggestion selection up (-1) or down (+1).
|
|
97
|
+
* Clamps index to [-1, suggestions.length - 1].
|
|
98
|
+
* Adjusts visibleOffset to keep selected item in the visible window.
|
|
99
|
+
*
|
|
100
|
+
* @param {number} direction - -1 (up) or 1 (down)
|
|
101
|
+
*/
|
|
102
|
+
function moveSuggestionSelection(direction) {
|
|
103
|
+
setSelectedSuggestionIndex(function (prev) {
|
|
104
|
+
var next = Math.max(-1, Math.min(suggestions.length - 1, prev + direction));
|
|
105
|
+
// Adjust visibleOffset to keep selected in view
|
|
106
|
+
setVisibleOffset(function (offset) {
|
|
107
|
+
if (next < offset) return Math.max(0, next);
|
|
108
|
+
if (next >= offset + MAX_VISIBLE) return next - MAX_VISIBLE + 1;
|
|
109
|
+
return offset;
|
|
110
|
+
});
|
|
111
|
+
return next;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Applies the currently focused suggestion to imageName.
|
|
117
|
+
* Does NOT advance the step. Clears suggestions after applying.
|
|
118
|
+
*/
|
|
119
|
+
function applyFocusedSuggestion() {
|
|
120
|
+
if (selectedSuggestionIndex < 0 || selectedSuggestionIndex >= suggestions.length) return;
|
|
121
|
+
var chosen = suggestions[selectedSuggestionIndex];
|
|
122
|
+
setImageName(chosen);
|
|
123
|
+
setSuggestions([]);
|
|
124
|
+
setSelectedSuggestionIndex(-1);
|
|
125
|
+
setVisibleOffset(0);
|
|
126
|
+
}
|
|
127
|
+
|
|
55
128
|
/**
|
|
56
129
|
* Advances to the next step, with validation and feedback.
|
|
57
130
|
*/
|
|
58
131
|
function nextStep() {
|
|
59
132
|
if (step === 0) {
|
|
60
133
|
if (!imageName.trim()) {
|
|
61
|
-
setMessage(
|
|
62
|
-
setMessageColor(
|
|
134
|
+
setMessage('Image name cannot be empty.');
|
|
135
|
+
setMessageColor('red');
|
|
63
136
|
return;
|
|
64
137
|
}
|
|
65
138
|
setStep(1);
|
|
66
|
-
setMessage(
|
|
67
|
-
setMessageColor(
|
|
139
|
+
setMessage('Optional: Enter container name or leave empty and press Enter');
|
|
140
|
+
setMessageColor('yellow');
|
|
68
141
|
return;
|
|
69
142
|
}
|
|
70
143
|
if (step === 1) {
|
|
71
144
|
setStep(2);
|
|
72
|
-
setMessage(
|
|
73
|
-
setMessageColor(
|
|
145
|
+
setMessage('Optional: Enter ports (format 8080:80,443:443) or leave empty and press Enter');
|
|
146
|
+
setMessageColor('yellow');
|
|
74
147
|
return;
|
|
75
148
|
}
|
|
76
149
|
if (step === 2) {
|
|
77
150
|
// Ports are now optional - only validate if provided
|
|
78
151
|
if (portInput.trim() && !validatePorts(portInput)) {
|
|
79
|
-
setMessage(
|
|
80
|
-
setMessageColor(
|
|
152
|
+
setMessage('Port format must be host:container and both must be numbers (e.g. 8080:80)');
|
|
153
|
+
setMessageColor('red');
|
|
81
154
|
return;
|
|
82
155
|
}
|
|
83
156
|
setStep(3);
|
|
84
157
|
var isDb = dbImages.some(function (db) {
|
|
85
158
|
return imageName.trim().toLowerCase().includes(db);
|
|
86
159
|
});
|
|
87
|
-
if
|
|
88
|
-
|
|
89
|
-
|
|
160
|
+
// Check if the image has a profile with required env vars
|
|
161
|
+
var baseName = imageName.trim().toLowerCase().split(':')[0].split('/').pop();
|
|
162
|
+
var profile = imageProfiles[baseName];
|
|
163
|
+
if (profile && profile.requiredEnv && profile.requiredEnv.length) {
|
|
164
|
+
var suggestedPart = profile.suggestedEnv && profile.suggestedEnv.length ? " | Suggested: ".concat(profile.suggestedEnv.join(', ')) : '';
|
|
165
|
+
setMessage("Required env vars for ".concat(baseName, ": ").concat(profile.requiredEnv.join(', '), ". Enter as VAR=val,VAR2=val2").concat(suggestedPart));
|
|
166
|
+
setMessageColor('yellow');
|
|
167
|
+
} else if (profile && profile.suggestedEnv && profile.suggestedEnv.length) {
|
|
168
|
+
setMessage("Suggested env vars for ".concat(baseName, ": ").concat(profile.suggestedEnv.join(', '), ". Enter as VAR=val,VAR2=val2 or leave empty."));
|
|
169
|
+
setMessageColor('yellow');
|
|
170
|
+
} else if (isDb) {
|
|
171
|
+
setMessage('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.');
|
|
172
|
+
setMessageColor('yellow');
|
|
90
173
|
} else {
|
|
91
|
-
setMessage(
|
|
92
|
-
setMessageColor(
|
|
174
|
+
setMessage('Optional: Enter environment variables (format VAR1=val1,VAR2=val2) or leave empty and press Enter');
|
|
175
|
+
setMessageColor('yellow');
|
|
93
176
|
}
|
|
94
177
|
return;
|
|
95
178
|
}
|
|
96
179
|
if (step === 3) {
|
|
180
|
+
// Contextual env validation using image profiles
|
|
181
|
+
var result = validateEnvVars(envInput, imageName, imageProfiles);
|
|
182
|
+
if (!result.valid) {
|
|
183
|
+
setMessage(result.errors.join(' | '));
|
|
184
|
+
setMessageColor('red');
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
97
187
|
// Final step: call onCreate with all data (safely)
|
|
98
188
|
safeCall(onCreate, {
|
|
99
189
|
imageName: imageName,
|
|
@@ -109,12 +199,15 @@ export function useContainerCreation(_ref) {
|
|
|
109
199
|
*/
|
|
110
200
|
function cancelCreation() {
|
|
111
201
|
setStep(0);
|
|
112
|
-
setImageName(
|
|
113
|
-
setContainerName(
|
|
114
|
-
setPortInput(
|
|
115
|
-
setEnvInput(
|
|
116
|
-
setMessage(
|
|
117
|
-
setMessageColor(
|
|
202
|
+
setImageName('');
|
|
203
|
+
setContainerName('');
|
|
204
|
+
setPortInput('');
|
|
205
|
+
setEnvInput('');
|
|
206
|
+
setMessage('');
|
|
207
|
+
setMessageColor('yellow');
|
|
208
|
+
setSuggestions([]);
|
|
209
|
+
setSelectedSuggestionIndex(-1);
|
|
210
|
+
setVisibleOffset(0);
|
|
118
211
|
safeCall(onCancel);
|
|
119
212
|
}
|
|
120
213
|
|
|
@@ -124,12 +217,15 @@ export function useContainerCreation(_ref) {
|
|
|
124
217
|
*/
|
|
125
218
|
function resetCreation() {
|
|
126
219
|
setStep(0);
|
|
127
|
-
setImageName(
|
|
128
|
-
setContainerName(
|
|
129
|
-
setPortInput(
|
|
130
|
-
setEnvInput(
|
|
131
|
-
setMessage(
|
|
132
|
-
setMessageColor(
|
|
220
|
+
setImageName('');
|
|
221
|
+
setContainerName('');
|
|
222
|
+
setPortInput('');
|
|
223
|
+
setEnvInput('');
|
|
224
|
+
setMessage('Insert the name of the image to create: ');
|
|
225
|
+
setMessageColor('yellow');
|
|
226
|
+
setSuggestions([]);
|
|
227
|
+
setSelectedSuggestionIndex(-1);
|
|
228
|
+
setVisibleOffset(0);
|
|
133
229
|
}
|
|
134
230
|
return {
|
|
135
231
|
step: step,
|
|
@@ -146,6 +242,12 @@ export function useContainerCreation(_ref) {
|
|
|
146
242
|
setMessage: setMessage,
|
|
147
243
|
messageColor: messageColor,
|
|
148
244
|
setMessageColor: setMessageColor,
|
|
245
|
+
suggestions: suggestions,
|
|
246
|
+
selectedSuggestionIndex: selectedSuggestionIndex,
|
|
247
|
+
visibleOffset: visibleOffset,
|
|
248
|
+
updateImageInput: updateImageInput,
|
|
249
|
+
moveSuggestionSelection: moveSuggestionSelection,
|
|
250
|
+
applyFocusedSuggestion: applyFocusedSuggestion,
|
|
149
251
|
nextStep: nextStep,
|
|
150
252
|
cancelCreation: cancelCreation,
|
|
151
253
|
resetCreation: resetCreation
|
|
@@ -4,8 +4,8 @@ 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, useRef } from
|
|
8
|
-
import { safeCall } from
|
|
7
|
+
import { useState, useRef } from 'react';
|
|
8
|
+
import { safeCall } from '../../helpers/safeCall.js';
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Custom hook to manage the logs viewer state and stream reference.
|
|
@@ -23,15 +23,15 @@ export function useDebugLogs() {
|
|
|
23
23
|
var unsubscribe = logger.subscribe(function (entry) {
|
|
24
24
|
setDebugLogs(function (prev) {
|
|
25
25
|
var extras = (entry.args || []).map(function (arg) {
|
|
26
|
-
if (typeof arg ===
|
|
27
|
-
if (typeof arg ===
|
|
26
|
+
if (typeof arg === 'string') return arg;
|
|
27
|
+
if (typeof arg === 'number' || typeof arg === 'boolean') return String(arg);
|
|
28
28
|
try {
|
|
29
29
|
return JSON.stringify(arg);
|
|
30
30
|
} catch (err) {
|
|
31
|
-
return
|
|
31
|
+
return '[unserializable]';
|
|
32
32
|
}
|
|
33
33
|
}).filter(Boolean);
|
|
34
|
-
var line = extras.length ? "".concat(entry.formatted, " ").concat(extras.join(
|
|
34
|
+
var line = extras.length ? "".concat(entry.formatted, " ").concat(extras.join(' ')) : entry.formatted;
|
|
35
35
|
var next = [].concat(_toConsumableArray(prev), [line]);
|
|
36
36
|
return next.slice(-200);
|
|
37
37
|
});
|
|
@@ -2,7 +2,7 @@ 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 { useCallback } from
|
|
5
|
+
import { useCallback } from 'react';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Hook responsible solely for routing Docker action keystrokes to the correct handler.
|
|
@@ -41,7 +41,7 @@ export function useContainerCommandRouter(_ref) {
|
|
|
41
41
|
onStartCreate = _ref.onStartCreate;
|
|
42
42
|
var handleDockerCommands = useCallback(function (input) {
|
|
43
43
|
var container = containers[selected];
|
|
44
|
-
if (input ===
|
|
44
|
+
if (input === 'i') {
|
|
45
45
|
actions.handleAction({
|
|
46
46
|
actionFn: function () {
|
|
47
47
|
var _actionFn = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(id) {
|
|
@@ -60,15 +60,15 @@ export function useContainerCommandRouter(_ref) {
|
|
|
60
60
|
}
|
|
61
61
|
return actionFn;
|
|
62
62
|
}(),
|
|
63
|
-
actionLabel:
|
|
63
|
+
actionLabel: 'Starting',
|
|
64
64
|
selected: selected,
|
|
65
65
|
stateCheck: function stateCheck(c) {
|
|
66
|
-
return (c.state ===
|
|
66
|
+
return (c.state === 'running' || c.status === 'running') && 'Container is already running.';
|
|
67
67
|
}
|
|
68
68
|
});
|
|
69
69
|
return true;
|
|
70
70
|
}
|
|
71
|
-
if (input ===
|
|
71
|
+
if (input === 'p') {
|
|
72
72
|
actions.handleAction({
|
|
73
73
|
actionFn: function () {
|
|
74
74
|
var _actionFn2 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee2(id) {
|
|
@@ -87,15 +87,15 @@ export function useContainerCommandRouter(_ref) {
|
|
|
87
87
|
}
|
|
88
88
|
return actionFn;
|
|
89
89
|
}(),
|
|
90
|
-
actionLabel:
|
|
90
|
+
actionLabel: 'Stopping',
|
|
91
91
|
selected: selected,
|
|
92
92
|
stateCheck: function stateCheck(c) {
|
|
93
|
-
return (c.state ===
|
|
93
|
+
return (c.state === 'exited' || c.status === 'exited' || c.state === 'stopped' || c.status === 'stopped') && 'Container is already stopped.';
|
|
94
94
|
}
|
|
95
95
|
});
|
|
96
96
|
return true;
|
|
97
97
|
}
|
|
98
|
-
if (input ===
|
|
98
|
+
if (input === 'r') {
|
|
99
99
|
actions.handleAction({
|
|
100
100
|
actionFn: function () {
|
|
101
101
|
var _actionFn3 = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(id) {
|
|
@@ -114,19 +114,19 @@ export function useContainerCommandRouter(_ref) {
|
|
|
114
114
|
}
|
|
115
115
|
return actionFn;
|
|
116
116
|
}(),
|
|
117
|
-
actionLabel:
|
|
117
|
+
actionLabel: 'Restarting',
|
|
118
118
|
selected: selected
|
|
119
119
|
});
|
|
120
120
|
return true;
|
|
121
121
|
}
|
|
122
|
-
if (input ===
|
|
122
|
+
if (input === 'e') {
|
|
123
123
|
if (!container) {
|
|
124
124
|
return true;
|
|
125
125
|
}
|
|
126
126
|
onStartErase();
|
|
127
127
|
return true;
|
|
128
128
|
}
|
|
129
|
-
if (input ===
|
|
129
|
+
if (input === 'l') {
|
|
130
130
|
if (!container) {
|
|
131
131
|
return true;
|
|
132
132
|
}
|
|
@@ -134,12 +134,12 @@ export function useContainerCommandRouter(_ref) {
|
|
|
134
134
|
startLogsStream(container.id);
|
|
135
135
|
return true;
|
|
136
136
|
}
|
|
137
|
-
if (input ===
|
|
137
|
+
if (input === 'c') {
|
|
138
138
|
onStartCreate();
|
|
139
139
|
creation.resetCreation();
|
|
140
140
|
return true;
|
|
141
141
|
}
|
|
142
|
-
if (input ===
|
|
142
|
+
if (input === 'd') {
|
|
143
143
|
onToggleDebug();
|
|
144
144
|
return true;
|
|
145
145
|
}
|
|
@@ -18,9 +18,9 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
|
18
18
|
* // Use in a component
|
|
19
19
|
* const { containers } = useContainers();
|
|
20
20
|
*/
|
|
21
|
-
import React, { useState, useEffect } from
|
|
22
|
-
import { getContainers } from
|
|
23
|
-
import { REFRESH_INTERVALS } from
|
|
21
|
+
import React, { useState, useEffect } from 'react';
|
|
22
|
+
import { getContainers } from '../helpers/dockerService/serviceComponents/containerList.js';
|
|
23
|
+
import { REFRESH_INTERVALS } from '../helpers/constants.js';
|
|
24
24
|
export function useContainers() {
|
|
25
25
|
var _useState = useState([]),
|
|
26
26
|
_useState2 = _slicedToArray(_useState, 2),
|