cdd-cli 3.1.4 → 3.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +30 -0
- package/.github/workflows/docs.yml +35 -0
- package/CHANGELOG.md +37 -4
- package/README.md +26 -0
- package/dist/App.js +20 -3
- package/dist/components/ContainerCreationPrompt.js +27 -1
- package/dist/components/ContainerList.js +11 -15
- package/dist/components/ContainerRow.js +73 -14
- package/dist/components/ContainerSection.js +16 -2
- package/dist/components/Footer.js +7 -1
- package/dist/components/Header.js +17 -1
- package/dist/components/LogViewer.js +21 -3
- package/dist/components/MessageFeedback.js +15 -1
- package/dist/components/PromptField.js +31 -1
- package/dist/components/StatsBar.js +46 -8
- package/dist/components/UsageMenu.js +30 -1
- package/dist/helpers/actionHelpers.js +17 -6
- package/dist/helpers/constants.js +30 -0
- package/dist/helpers/dockerService/dockerService.js +19 -0
- package/dist/helpers/dockerService/serviceComponents/containerActions.js +194 -35
- package/dist/helpers/dockerService/serviceComponents/containerList.js +45 -8
- package/dist/helpers/dockerService/serviceComponents/containerLogs.js +16 -6
- package/dist/helpers/dockerService/serviceComponents/containerStats.js +28 -9
- package/dist/helpers/exitWithMessage.js +2 -1
- package/dist/helpers/logger.js +112 -0
- package/dist/helpers/safeCall.js +21 -0
- package/dist/helpers/validationHelpers.js +20 -1
- package/dist/hooks/creation/useContainerActions.js +2 -1
- package/dist/hooks/creation/useContainerCreation.js +5 -4
- package/dist/hooks/creation/useLogsViewer.js +3 -2
- package/dist/hooks/useContainers.js +6 -3
- package/dist/hooks/useControls.js +253 -122
- package/dist/hooks/useLogsStream.js +3 -2
- package/dist/index.js +1 -0
- package/docs/components_ContainerCreationPrompt.jsx.html +114 -0
- package/docs/components_ContainerRow.jsx.html +151 -0
- package/docs/components_ContainerSection.jsx.html +75 -0
- package/docs/components_Footer.jsx.html +66 -0
- package/docs/components_Header.jsx.html +84 -0
- package/docs/components_LogViewer.jsx.html +91 -0
- package/docs/components_MessageFeedback.jsx.html +76 -0
- package/docs/components_PromptField.jsx.html +99 -0
- package/docs/components_StatsBar.jsx.html +105 -0
- package/docs/components_UsageMenu.jsx.html +73 -0
- package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Bold-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.svg +1830 -0
- package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Light-webfont.svg +1831 -0
- package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.svg +1835 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.svg +1831 -0
- package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/global.html +6145 -0
- package/docs/helpers_actionHelpers.js.html +94 -0
- package/docs/helpers_constants.js.html +81 -0
- package/docs/helpers_dockerService_dockerService.js.html +79 -0
- package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +153 -0
- package/docs/helpers_dockerService_serviceComponents_containerList.js.html +81 -0
- package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +85 -0
- package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +107 -0
- package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +82 -0
- package/docs/helpers_exitWithMessage.js.html +77 -0
- package/docs/helpers_safeCall.js.html +68 -0
- package/docs/helpers_validationHelpers.js.html +96 -0
- package/docs/hooks_creation_useContainerActions.js.html +120 -0
- package/docs/hooks_creation_useContainerCreation.js.html +152 -0
- package/docs/hooks_creation_useLogsViewer.js.html +102 -0
- package/docs/hooks_useControls.js.html +298 -0
- package/docs/hooks_useLogsStream.js.html +82 -0
- package/docs/index.html +65 -0
- package/docs/index.js.html +65 -0
- package/docs/module-index.html +171 -0
- package/docs/scripts/linenumber.js +25 -0
- package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
- package/docs/scripts/prettify/lang-css.js +2 -0
- package/docs/scripts/prettify/prettify.js +28 -0
- package/docs/styles/jsdoc-default.css +358 -0
- package/docs/styles/prettify-jsdoc.css +111 -0
- package/docs/styles/prettify-tomorrow.css +132 -0
- package/fix-imports.cjs +33 -6
- package/jest.config.cjs +2 -1
- package/package.json +19 -9
- package/src/App.jsx +16 -2
- package/src/components/ContainerCreationPrompt.jsx +26 -0
- package/src/components/ContainerList.jsx +12 -13
- package/src/components/ContainerRow.jsx +55 -20
- package/src/components/ContainerSection.jsx +15 -1
- package/src/components/Footer.jsx +6 -1
- package/src/components/Header.jsx +16 -0
- package/src/components/LogViewer.jsx +19 -2
- package/src/components/MessageFeedback.jsx +14 -0
- package/src/components/PromptField.jsx +30 -0
- package/src/components/StatsBar.jsx +47 -8
- package/src/components/UsageMenu.jsx +38 -8
- package/src/helpers/actionHelpers.js +16 -7
- package/src/helpers/constants.js +30 -0
- package/src/helpers/dockerService/dockerService.js +19 -0
- package/src/helpers/dockerService/serviceComponents/containerActions.js +123 -12
- package/src/helpers/dockerService/serviceComponents/containerList.js +61 -23
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +20 -6
- package/src/helpers/dockerService/serviceComponents/containerStats.js +24 -5
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +1 -1
- package/src/helpers/exitWithMessage.js +2 -1
- package/src/helpers/logger.js +113 -0
- package/src/helpers/safeCall.js +18 -0
- package/src/helpers/validationHelpers.js +21 -3
- package/src/hooks/creation/useContainerActions.js +4 -3
- package/src/hooks/creation/useContainerCreation.js +5 -4
- package/src/hooks/creation/useLogsViewer.js +2 -1
- package/src/hooks/useContainers.js +6 -3
- package/src/hooks/useControls.js +244 -91
- package/src/hooks/useLogsStream.js +2 -1
- package/src/index.js +1 -0
- package/test/actionHelpers.test.js +106 -0
- package/test/containerActions.test.js +138 -0
- package/test/containerList.test.js +79 -0
- package/test/jest.setup.js +7 -0
- package/test/safeCall.test.js +25 -0
- package/test/useContainerCreation.dom.test.js +97 -0
- package/test/validationHelpers.test.js +2 -2
- package/dist/cdd.bundle.js +0 -8
- package/dist/components/ContainerCreator.js +0 -81
- package/dist/components/StatsViewer.js +0 -0
- package/dist/helpers/dockerActions.js +0 -64
- package/dist/helpers/dockerService.js +0 -284
- package/dist/hooks/useStatsPolling.js +0 -54
|
@@ -1,19 +1,57 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Text } from "ink";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
+
import PropTypes from 'prop-types';
|
|
5
|
+
var BAR_WIDTH = 8;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Small visual bar that shows CPU and memory usage percentages.
|
|
9
|
+
*
|
|
10
|
+
* @param {Object} props
|
|
11
|
+
* @param {number} props.cpu - CPU percentage (0-100)
|
|
12
|
+
* @param {number} props.mem - Memory percentage (0-100)
|
|
13
|
+
* @returns {JSX.Element}
|
|
14
|
+
*/
|
|
4
15
|
export default function StatsBar(_ref) {
|
|
5
16
|
var cpu = _ref.cpu,
|
|
6
17
|
mem = _ref.mem;
|
|
7
|
-
var cpuBar = makeBar(cpu);
|
|
8
|
-
var memBar = makeBar(mem);
|
|
9
|
-
return /*#__PURE__*/React.createElement(Text, null, "CPU:
|
|
18
|
+
var cpuBar = makeBar(cpu, BAR_WIDTH);
|
|
19
|
+
var memBar = makeBar(mem, BAR_WIDTH);
|
|
20
|
+
return /*#__PURE__*/React.createElement(Text, null, "CPU:", cpuBar, colorize(cpu, " ".concat(formatPercent(cpu), "%")), " MEM:", memBar, colorize(mem, " ".concat(formatPercent(mem), "%")));
|
|
10
21
|
}
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
StatsBar.propTypes = {
|
|
23
|
+
cpu: PropTypes.number.isRequired,
|
|
24
|
+
mem: PropTypes.number.isRequired
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Build a fixed-width bar representation from a numeric percentage.
|
|
29
|
+
*
|
|
30
|
+
* @param {number} value - Percentage value (0-100)
|
|
31
|
+
* @returns {string} Colored bar string
|
|
32
|
+
*/
|
|
33
|
+
function makeBar(value, width) {
|
|
34
|
+
var safeWidth = Math.max(1, Math.round(width));
|
|
35
|
+
var normalized = Number.isFinite(value) ? Math.min(Math.max(value, 0), 100) : 0;
|
|
36
|
+
var filled = Math.round(normalized / 100 * safeWidth);
|
|
37
|
+
var empty = Math.max(0, safeWidth - filled);
|
|
38
|
+
var bar = "█".repeat(filled) + "░".repeat(empty);
|
|
39
|
+
return colorize(normalized, bar);
|
|
16
40
|
}
|
|
41
|
+
function formatPercent(value) {
|
|
42
|
+
if (!Number.isFinite(value)) return 0;
|
|
43
|
+
var normalized = Math.min(Math.max(value, 0), 100);
|
|
44
|
+
if (normalized >= 10) return Math.round(normalized);
|
|
45
|
+
return normalized.toFixed(1).replace(/\.0$/, "");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Colorize a value or text depending on thresholds.
|
|
50
|
+
*
|
|
51
|
+
* @param {number} value - Numeric value used to pick color
|
|
52
|
+
* @param {string} [text] - Text to colorize, defaults to the numeric string
|
|
53
|
+
* @returns {string}
|
|
54
|
+
*/
|
|
17
55
|
function colorize(value) {
|
|
18
56
|
var text = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : value.toString();
|
|
19
57
|
if (value < 50) return chalk.greenBright(text);
|
|
@@ -1,8 +1,37 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
+
var SHORTCUTS = ["Use ↑/↓ for navigation", "I to initiate selected container", "P to stop selected container", "R to restart selected container", "C to create a container", "L to view logs of selected container", "D to toggle debug log panel", "E to remove selected container", "Q to quit"];
|
|
4
|
+
var GRID_COLUMNS = 3;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Small usage menu that explains available keybindings to the user.
|
|
8
|
+
* Renders entries in a responsive grid to avoid an excessively long column.
|
|
9
|
+
*
|
|
10
|
+
* @returns {JSX.Element}
|
|
11
|
+
*/
|
|
3
12
|
export default function UsageMenu() {
|
|
13
|
+
var rowsPerColumn = Math.ceil(SHORTCUTS.length / GRID_COLUMNS);
|
|
14
|
+
var columns = Array.from({
|
|
15
|
+
length: GRID_COLUMNS
|
|
16
|
+
}, function (_, columnIndex) {
|
|
17
|
+
return SHORTCUTS.slice(columnIndex * rowsPerColumn, (columnIndex + 1) * rowsPerColumn);
|
|
18
|
+
}).filter(function (column) {
|
|
19
|
+
return column.length > 0;
|
|
20
|
+
});
|
|
4
21
|
return /*#__PURE__*/React.createElement(Box, {
|
|
5
22
|
flexDirection: "column",
|
|
6
23
|
marginTop: 1
|
|
7
|
-
}, /*#__PURE__*/React.createElement(
|
|
24
|
+
}, /*#__PURE__*/React.createElement(Box, {
|
|
25
|
+
flexDirection: "row"
|
|
26
|
+
}, columns.map(function (column, columnIndex) {
|
|
27
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
28
|
+
key: "shortcut-column-".concat(columnIndex),
|
|
29
|
+
flexDirection: "column",
|
|
30
|
+
marginRight: columnIndex === columns.length - 1 ? 0 : 3
|
|
31
|
+
}, column.map(function (shortcut, rowIndex) {
|
|
32
|
+
return /*#__PURE__*/React.createElement(Text, {
|
|
33
|
+
key: "shortcut-".concat(columnIndex, "-").concat(rowIndex)
|
|
34
|
+
}, shortcut);
|
|
35
|
+
}));
|
|
36
|
+
})));
|
|
8
37
|
}
|
|
@@ -2,6 +2,8 @@ function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present,
|
|
|
2
2
|
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
3
3
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
4
4
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
5
|
+
import { MESSAGE_TIMEOUTS } from "../helpers/constants.js";
|
|
6
|
+
import { logger } from "./logger.js";
|
|
5
7
|
/**
|
|
6
8
|
* Generic helper to perform a container action with user feedback.
|
|
7
9
|
*
|
|
@@ -20,7 +22,7 @@ export function handleAction(_x) {
|
|
|
20
22
|
}
|
|
21
23
|
function _handleAction() {
|
|
22
24
|
_handleAction = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
|
|
23
|
-
var containers, selected, actionFn, actionLabel, setMessage, setMessageColor, stateCheck, c, _t;
|
|
25
|
+
var containers, selected, actionFn, actionLabel, setMessage, setMessageColor, stateCheck, c, failureReason, _t;
|
|
24
26
|
return _regenerator().w(function (_context) {
|
|
25
27
|
while (1) switch (_context.p = _context.n) {
|
|
26
28
|
case 0:
|
|
@@ -32,19 +34,26 @@ function _handleAction() {
|
|
|
32
34
|
}
|
|
33
35
|
return _context.a(2);
|
|
34
36
|
case 1:
|
|
35
|
-
if (!
|
|
37
|
+
if (!stateCheck) {
|
|
36
38
|
_context.n = 2;
|
|
37
39
|
break;
|
|
38
40
|
}
|
|
39
|
-
|
|
41
|
+
failureReason = stateCheck(c);
|
|
42
|
+
if (!failureReason) {
|
|
43
|
+
_context.n = 2;
|
|
44
|
+
break;
|
|
45
|
+
}
|
|
46
|
+
logger.warn("Action %s blocked for container %s: %s", actionLabel, c.id, failureReason);
|
|
47
|
+
setMessage(failureReason);
|
|
40
48
|
setMessageColor("red");
|
|
41
49
|
setTimeout(function () {
|
|
42
50
|
return setMessage("");
|
|
43
|
-
},
|
|
51
|
+
}, MESSAGE_TIMEOUTS.SHORT);
|
|
44
52
|
return _context.a(2);
|
|
45
53
|
case 2:
|
|
46
54
|
setMessage("".concat(actionLabel, " container..."));
|
|
47
55
|
setMessageColor("green");
|
|
56
|
+
logger.info("Action %s requested for container %s", actionLabel, c.id);
|
|
48
57
|
_context.p = 3;
|
|
49
58
|
_context.n = 4;
|
|
50
59
|
return actionFn(c.id);
|
|
@@ -53,7 +62,8 @@ function _handleAction() {
|
|
|
53
62
|
setMessageColor("green");
|
|
54
63
|
setTimeout(function () {
|
|
55
64
|
return setMessage("");
|
|
56
|
-
},
|
|
65
|
+
}, MESSAGE_TIMEOUTS.DEFAULT);
|
|
66
|
+
logger.info("Action %s completed for container %s", actionLabel, c.id);
|
|
57
67
|
_context.n = 6;
|
|
58
68
|
break;
|
|
59
69
|
case 5:
|
|
@@ -63,7 +73,8 @@ function _handleAction() {
|
|
|
63
73
|
setMessageColor("red");
|
|
64
74
|
setTimeout(function () {
|
|
65
75
|
return setMessage("");
|
|
66
|
-
},
|
|
76
|
+
}, MESSAGE_TIMEOUTS.DEFAULT);
|
|
77
|
+
logger.error("Action %s failed for container %s", actionLabel, c.id, _t);
|
|
67
78
|
case 6:
|
|
68
79
|
return _context.a(2);
|
|
69
80
|
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Time intervals used across the app (milliseconds).
|
|
3
|
+
* - CONTAINER_LIST: how often to refresh the list of containers
|
|
4
|
+
* - CONTAINER_STATS: how often to fetch stats for running containers
|
|
5
|
+
*/
|
|
6
|
+
export var REFRESH_INTERVALS = {
|
|
7
|
+
CONTAINER_LIST: 3000,
|
|
8
|
+
CONTAINER_STATS: 1500
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Timeout values for UI messages before they disappear.
|
|
13
|
+
*/
|
|
14
|
+
export var MESSAGE_TIMEOUTS = {
|
|
15
|
+
SHORT: 2000,
|
|
16
|
+
DEFAULT: 3000
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Delay used before exiting the process when showing an exit message.
|
|
21
|
+
*/
|
|
22
|
+
export var EXIT_DELAY = 500;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Operational timeouts for Docker operations and image pulls.
|
|
26
|
+
*/
|
|
27
|
+
export var TIMEOUTS = {
|
|
28
|
+
CONTAINER_OP: 30000,
|
|
29
|
+
PULL_IMAGE: 300000
|
|
30
|
+
};
|
|
@@ -4,5 +4,24 @@ import Docker from "dockerode";
|
|
|
4
4
|
// - /var/run/docker.sock on Linux/Mac
|
|
5
5
|
// - //./pipe/docker_engine on Windows
|
|
6
6
|
// - Environment variables DOCKER_HOST, DOCKER_CERT_PATH, etc.
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Typedef for the docker client used in this project.
|
|
10
|
+
* Use a generic object shape so JSDoc can render a meaningful type.
|
|
11
|
+
*
|
|
12
|
+
* @typedef {Object} DockerClient
|
|
13
|
+
* @property {Function} listContainers
|
|
14
|
+
* @property {Function} getContainer
|
|
15
|
+
* @property {Function} listImages
|
|
16
|
+
* @property {Function} createContainer
|
|
17
|
+
* @property {Function} pull
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Shared Docker client instance (dockerode). Consumers import { docker }
|
|
22
|
+
* and use it to list/create/start/stop containers and images.
|
|
23
|
+
*
|
|
24
|
+
* @type {DockerClient}
|
|
25
|
+
*/
|
|
7
26
|
var docker = new Docker();
|
|
8
27
|
export { docker };
|
|
@@ -10,15 +10,20 @@ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.
|
|
|
10
10
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
11
11
|
import { docker } from "../dockerService.js";
|
|
12
12
|
import { imageExists, pullImage } from "./imageUtils.js";
|
|
13
|
+
import { TIMEOUTS } from "../../constants.js";
|
|
14
|
+
import { logger } from "../../logger.js";
|
|
13
15
|
|
|
14
16
|
/**
|
|
15
|
-
* Helper to add timeout to promises
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* @
|
|
17
|
+
* Helper to add timeout to promises. If the provided promise does not settle
|
|
18
|
+
* within `ms` milliseconds it will reject with an Error.
|
|
19
|
+
*
|
|
20
|
+
* @template T
|
|
21
|
+
* @param {Promise<T>} promise - Promise to wrap
|
|
22
|
+
* @param {number} [ms=TIMEOUTS.CONTAINER_OP] - Timeout in milliseconds
|
|
23
|
+
* @returns {Promise<T>} The original promise result or a rejection on timeout
|
|
19
24
|
*/
|
|
20
25
|
function withTimeout(promise) {
|
|
21
|
-
var ms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] :
|
|
26
|
+
var ms = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : TIMEOUTS.CONTAINER_OP;
|
|
22
27
|
return Promise.race([promise, new Promise(function (_, reject) {
|
|
23
28
|
return setTimeout(function () {
|
|
24
29
|
return reject(new Error('Operation timed out'));
|
|
@@ -51,18 +56,21 @@ function _removeContainer() {
|
|
|
51
56
|
return _regenerator().w(function (_context) {
|
|
52
57
|
while (1) switch (_context.p = _context.n) {
|
|
53
58
|
case 0:
|
|
59
|
+
logger.debug("Removing container %s", containerId);
|
|
54
60
|
container = docker.getContainer(containerId);
|
|
55
61
|
_context.p = 1;
|
|
56
62
|
_context.n = 2;
|
|
57
63
|
return withTimeout(container.remove({
|
|
58
64
|
force: true
|
|
59
|
-
}),
|
|
65
|
+
}), TIMEOUTS.CONTAINER_OP);
|
|
60
66
|
case 2:
|
|
67
|
+
logger.info("Removed container %s", containerId);
|
|
61
68
|
_context.n = 4;
|
|
62
69
|
break;
|
|
63
70
|
case 3:
|
|
64
71
|
_context.p = 3;
|
|
65
72
|
_t = _context.v;
|
|
73
|
+
logger.error("Failed to remove container %s", containerId, _t);
|
|
66
74
|
throw new Error('Error removing container: ' + _t.message);
|
|
67
75
|
case 4:
|
|
68
76
|
return _context.a(2);
|
|
@@ -85,15 +93,30 @@ function _createContainer() {
|
|
|
85
93
|
var options,
|
|
86
94
|
exists,
|
|
87
95
|
createOpts,
|
|
96
|
+
hasUserDefinedPorts,
|
|
97
|
+
image,
|
|
98
|
+
_inspectData$Config,
|
|
99
|
+
_inspectData$Containe,
|
|
100
|
+
usedHostPorts,
|
|
101
|
+
containers,
|
|
102
|
+
reservePort,
|
|
103
|
+
pickNextAvailablePort,
|
|
104
|
+
inspectData,
|
|
105
|
+
exposed,
|
|
106
|
+
exposedKeys,
|
|
88
107
|
container,
|
|
108
|
+
containerId,
|
|
89
109
|
_args2 = arguments,
|
|
90
110
|
_t2,
|
|
91
111
|
_t3,
|
|
92
|
-
_t4
|
|
112
|
+
_t4,
|
|
113
|
+
_t5,
|
|
114
|
+
_t6;
|
|
93
115
|
return _regenerator().w(function (_context2) {
|
|
94
116
|
while (1) switch (_context2.p = _context2.n) {
|
|
95
117
|
case 0:
|
|
96
118
|
options = _args2.length > 1 && _args2[1] !== undefined ? _args2[1] : {};
|
|
119
|
+
logger.info("Creating container from image %s", imageName);
|
|
97
120
|
_context2.p = 1;
|
|
98
121
|
_context2.n = 2;
|
|
99
122
|
return withTimeout(imageExists(imageName), 10000);
|
|
@@ -104,41 +127,144 @@ function _createContainer() {
|
|
|
104
127
|
case 3:
|
|
105
128
|
_context2.p = 3;
|
|
106
129
|
_t2 = _context2.v;
|
|
130
|
+
logger.error("Could not check local images for %s", imageName, _t2);
|
|
107
131
|
throw new Error('Error listing local images: ' + _t2.message);
|
|
108
132
|
case 4:
|
|
109
133
|
if (exists) {
|
|
110
134
|
_context2.n = 8;
|
|
111
135
|
break;
|
|
112
136
|
}
|
|
137
|
+
logger.info("Image %s not found locally, pulling", imageName);
|
|
113
138
|
_context2.p = 5;
|
|
114
139
|
_context2.n = 6;
|
|
115
|
-
return withTimeout(pullImage(imageName),
|
|
140
|
+
return withTimeout(pullImage(imageName), TIMEOUTS.PULL_IMAGE);
|
|
116
141
|
case 6:
|
|
142
|
+
logger.info("Pulled image %s", imageName);
|
|
117
143
|
_context2.n = 8;
|
|
118
144
|
break;
|
|
119
145
|
case 7:
|
|
120
146
|
_context2.p = 7;
|
|
121
147
|
_t3 = _context2.v;
|
|
148
|
+
logger.error("Failed to pull image %s", imageName, _t3);
|
|
122
149
|
throw new Error('Could not pull image: ' + _t3.message);
|
|
123
150
|
case 8:
|
|
124
151
|
createOpts = _objectSpread({
|
|
125
152
|
Image: imageName,
|
|
126
153
|
Tty: true
|
|
127
154
|
}, options);
|
|
155
|
+
hasUserDefinedPorts = Boolean(createOpts.ExposedPorts && Object.keys(createOpts.ExposedPorts).length);
|
|
156
|
+
if (hasUserDefinedPorts) {
|
|
157
|
+
_context2.n = 17;
|
|
158
|
+
break;
|
|
159
|
+
}
|
|
128
160
|
_context2.p = 9;
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
161
|
+
image = docker.getImage(imageName);
|
|
162
|
+
if (!(image && typeof image.inspect === "function")) {
|
|
163
|
+
_context2.n = 15;
|
|
164
|
+
break;
|
|
165
|
+
}
|
|
166
|
+
usedHostPorts = new Set();
|
|
167
|
+
if (!(typeof docker.listContainers === "function")) {
|
|
168
|
+
_context2.n = 13;
|
|
169
|
+
break;
|
|
170
|
+
}
|
|
171
|
+
_context2.p = 10;
|
|
172
|
+
_context2.n = 11;
|
|
173
|
+
return withTimeout(docker.listContainers({
|
|
174
|
+
all: true
|
|
175
|
+
}), TIMEOUTS.CONTAINER_OP);
|
|
134
176
|
case 11:
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
177
|
+
containers = _context2.v;
|
|
178
|
+
containers.forEach(function (container) {
|
|
179
|
+
(container.Ports || []).forEach(function (portInfo) {
|
|
180
|
+
if (portInfo && portInfo.PublicPort) {
|
|
181
|
+
usedHostPorts.add(String(portInfo.PublicPort));
|
|
182
|
+
}
|
|
183
|
+
});
|
|
184
|
+
});
|
|
185
|
+
_context2.n = 13;
|
|
186
|
+
break;
|
|
138
187
|
case 12:
|
|
188
|
+
_context2.p = 12;
|
|
189
|
+
_t4 = _context2.v;
|
|
190
|
+
logger.warn("Could not inspect existing containers to auto-map ports", _t4);
|
|
191
|
+
case 13:
|
|
192
|
+
reservePort = function reservePort(port) {
|
|
193
|
+
var portStr = String(port);
|
|
194
|
+
usedHostPorts.add(portStr);
|
|
195
|
+
};
|
|
196
|
+
pickNextAvailablePort = function pickNextAvailablePort(base) {
|
|
197
|
+
var numericBase = Number.parseInt(base, 10);
|
|
198
|
+
if (Number.isNaN(numericBase)) {
|
|
199
|
+
// If the base port is non-numeric, just return the base.
|
|
200
|
+
if (!usedHostPorts.has(base)) {
|
|
201
|
+
usedHostPorts.add(base);
|
|
202
|
+
return base;
|
|
203
|
+
}
|
|
204
|
+
var counter = 1;
|
|
205
|
+
var _candidate = "".concat(base, "-").concat(counter);
|
|
206
|
+
while (usedHostPorts.has(_candidate)) {
|
|
207
|
+
counter += 1;
|
|
208
|
+
_candidate = "".concat(base, "-").concat(counter);
|
|
209
|
+
}
|
|
210
|
+
usedHostPorts.add(_candidate);
|
|
211
|
+
return _candidate;
|
|
212
|
+
}
|
|
213
|
+
var candidate = numericBase;
|
|
214
|
+
while (usedHostPorts.has(String(candidate))) {
|
|
215
|
+
candidate += 1;
|
|
216
|
+
}
|
|
217
|
+
reservePort(candidate);
|
|
218
|
+
return String(candidate);
|
|
219
|
+
};
|
|
220
|
+
_context2.n = 14;
|
|
221
|
+
return withTimeout(image.inspect(), TIMEOUTS.CONTAINER_OP);
|
|
222
|
+
case 14:
|
|
223
|
+
inspectData = _context2.v;
|
|
224
|
+
exposed = (inspectData === null || inspectData === void 0 || (_inspectData$Config = inspectData.Config) === null || _inspectData$Config === void 0 ? void 0 : _inspectData$Config.ExposedPorts) || (inspectData === null || inspectData === void 0 || (_inspectData$Containe = inspectData.ContainerConfig) === null || _inspectData$Containe === void 0 ? void 0 : _inspectData$Containe.ExposedPorts) || {};
|
|
225
|
+
exposedKeys = Object.keys(exposed || {});
|
|
226
|
+
if (exposedKeys.length) {
|
|
227
|
+
createOpts.ExposedPorts = createOpts.ExposedPorts || {};
|
|
228
|
+
createOpts.HostConfig = _objectSpread({}, createOpts.HostConfig || {});
|
|
229
|
+
createOpts.HostConfig.PortBindings = _objectSpread({}, createOpts.HostConfig.PortBindings || {});
|
|
230
|
+
exposedKeys.forEach(function (portKey) {
|
|
231
|
+
if (!createOpts.ExposedPorts[portKey]) {
|
|
232
|
+
createOpts.ExposedPorts[portKey] = exposed[portKey] || {};
|
|
233
|
+
}
|
|
234
|
+
if (!createOpts.HostConfig.PortBindings[portKey] || !createOpts.HostConfig.PortBindings[portKey].length) {
|
|
235
|
+
var containerPort = portKey.split("/")[0];
|
|
236
|
+
var hostPort = pickNextAvailablePort(containerPort);
|
|
237
|
+
createOpts.HostConfig.PortBindings[portKey] = [{
|
|
238
|
+
HostPort: hostPort
|
|
239
|
+
}];
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
case 15:
|
|
244
|
+
_context2.n = 17;
|
|
245
|
+
break;
|
|
246
|
+
case 16:
|
|
247
|
+
_context2.p = 16;
|
|
248
|
+
_t5 = _context2.v;
|
|
249
|
+
logger.warn("Could not inspect image %s for default ports", imageName, _t5);
|
|
250
|
+
case 17:
|
|
251
|
+
_context2.p = 17;
|
|
252
|
+
_context2.n = 18;
|
|
253
|
+
return withTimeout(docker.createContainer(createOpts), TIMEOUTS.CONTAINER_OP);
|
|
254
|
+
case 18:
|
|
255
|
+
container = _context2.v;
|
|
256
|
+
containerId = container.id || container.Id;
|
|
257
|
+
logger.info("Created container %s from image %s", containerId, imageName);
|
|
258
|
+
return _context2.a(2, container.id || container.Id);
|
|
259
|
+
case 19:
|
|
260
|
+
_context2.p = 19;
|
|
261
|
+
_t6 = _context2.v;
|
|
262
|
+
logger.error("Failed to create container from image %s", imageName, _t6);
|
|
263
|
+
throw new Error('Error creating container: ' + _t6.message);
|
|
264
|
+
case 20:
|
|
139
265
|
return _context2.a(2);
|
|
140
266
|
}
|
|
141
|
-
}, _callee2, null, [[9,
|
|
267
|
+
}, _callee2, null, [[17, 19], [10, 12], [9, 16], [5, 7], [1, 3]]);
|
|
142
268
|
}));
|
|
143
269
|
return _createContainer.apply(this, arguments);
|
|
144
270
|
}
|
|
@@ -153,17 +279,28 @@ export function startContainer(_x3) {
|
|
|
153
279
|
*/
|
|
154
280
|
function _startContainer() {
|
|
155
281
|
_startContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee3(containerId) {
|
|
156
|
-
var container;
|
|
282
|
+
var container, _t7;
|
|
157
283
|
return _regenerator().w(function (_context3) {
|
|
158
|
-
while (1) switch (_context3.n) {
|
|
284
|
+
while (1) switch (_context3.p = _context3.n) {
|
|
159
285
|
case 0:
|
|
160
286
|
container = docker.getContainer(containerId);
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
287
|
+
logger.debug("Starting container %s", containerId);
|
|
288
|
+
_context3.p = 1;
|
|
289
|
+
_context3.n = 2;
|
|
290
|
+
return withTimeout(container.start(), TIMEOUTS.CONTAINER_OP);
|
|
291
|
+
case 2:
|
|
292
|
+
logger.info("Started container %s", containerId);
|
|
293
|
+
_context3.n = 4;
|
|
294
|
+
break;
|
|
295
|
+
case 3:
|
|
296
|
+
_context3.p = 3;
|
|
297
|
+
_t7 = _context3.v;
|
|
298
|
+
logger.error("Failed to start container %s", containerId, _t7);
|
|
299
|
+
throw _t7;
|
|
300
|
+
case 4:
|
|
164
301
|
return _context3.a(2);
|
|
165
302
|
}
|
|
166
|
-
}, _callee3);
|
|
303
|
+
}, _callee3, null, [[1, 3]]);
|
|
167
304
|
}));
|
|
168
305
|
return _startContainer.apply(this, arguments);
|
|
169
306
|
}
|
|
@@ -178,17 +315,28 @@ export function stopContainer(_x4) {
|
|
|
178
315
|
*/
|
|
179
316
|
function _stopContainer() {
|
|
180
317
|
_stopContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee4(containerId) {
|
|
181
|
-
var container;
|
|
318
|
+
var container, _t8;
|
|
182
319
|
return _regenerator().w(function (_context4) {
|
|
183
|
-
while (1) switch (_context4.n) {
|
|
320
|
+
while (1) switch (_context4.p = _context4.n) {
|
|
184
321
|
case 0:
|
|
185
322
|
container = docker.getContainer(containerId);
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
323
|
+
logger.debug("Stopping container %s", containerId);
|
|
324
|
+
_context4.p = 1;
|
|
325
|
+
_context4.n = 2;
|
|
326
|
+
return withTimeout(container.stop(), TIMEOUTS.CONTAINER_OP);
|
|
327
|
+
case 2:
|
|
328
|
+
logger.info("Stopped container %s", containerId);
|
|
329
|
+
_context4.n = 4;
|
|
330
|
+
break;
|
|
331
|
+
case 3:
|
|
332
|
+
_context4.p = 3;
|
|
333
|
+
_t8 = _context4.v;
|
|
334
|
+
logger.error("Failed to stop container %s", containerId, _t8);
|
|
335
|
+
throw _t8;
|
|
336
|
+
case 4:
|
|
189
337
|
return _context4.a(2);
|
|
190
338
|
}
|
|
191
|
-
}, _callee4);
|
|
339
|
+
}, _callee4, null, [[1, 3]]);
|
|
192
340
|
}));
|
|
193
341
|
return _stopContainer.apply(this, arguments);
|
|
194
342
|
}
|
|
@@ -197,17 +345,28 @@ export function restartContainer(_x5) {
|
|
|
197
345
|
}
|
|
198
346
|
function _restartContainer() {
|
|
199
347
|
_restartContainer = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee5(containerId) {
|
|
200
|
-
var container;
|
|
348
|
+
var container, _t9;
|
|
201
349
|
return _regenerator().w(function (_context5) {
|
|
202
|
-
while (1) switch (_context5.n) {
|
|
350
|
+
while (1) switch (_context5.p = _context5.n) {
|
|
203
351
|
case 0:
|
|
204
352
|
container = docker.getContainer(containerId);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
353
|
+
logger.debug("Restarting container %s", containerId);
|
|
354
|
+
_context5.p = 1;
|
|
355
|
+
_context5.n = 2;
|
|
356
|
+
return withTimeout(container.restart(), TIMEOUTS.CONTAINER_OP);
|
|
357
|
+
case 2:
|
|
358
|
+
logger.info("Restarted container %s", containerId);
|
|
359
|
+
_context5.n = 4;
|
|
360
|
+
break;
|
|
361
|
+
case 3:
|
|
362
|
+
_context5.p = 3;
|
|
363
|
+
_t9 = _context5.v;
|
|
364
|
+
logger.error("Failed to restart container %s", containerId, _t9);
|
|
365
|
+
throw _t9;
|
|
366
|
+
case 4:
|
|
208
367
|
return _context5.a(2);
|
|
209
368
|
}
|
|
210
|
-
}, _callee5);
|
|
369
|
+
}, _callee5, null, [[1, 3]]);
|
|
211
370
|
}));
|
|
212
371
|
return _restartContainer.apply(this, arguments);
|
|
213
372
|
}
|
|
@@ -9,6 +9,33 @@ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length)
|
|
|
9
9
|
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
10
10
|
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
11
11
|
import { docker } from "../dockerService.js";
|
|
12
|
+
import { logger } from "../../logger.js";
|
|
13
|
+
var DEFAULT_CONTAINER_NAME = "Unknown";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Produce a safe, display-friendly container name from the raw Docker payload.
|
|
17
|
+
*
|
|
18
|
+
* @param {Object} container Docker container summary as returned by dockerode.
|
|
19
|
+
* @returns {string} Normalized name without leading slashes and with a fallback applied.
|
|
20
|
+
*/
|
|
21
|
+
function normalizeContainerName(container) {
|
|
22
|
+
var names = container === null || container === void 0 ? void 0 : container.Names;
|
|
23
|
+
var rawName;
|
|
24
|
+
if (Array.isArray(names) && names.length > 0 && typeof names[0] === "string") {
|
|
25
|
+
rawName = names[0];
|
|
26
|
+
} else if (typeof names === "string" && names.trim().length > 0) {
|
|
27
|
+
rawName = names;
|
|
28
|
+
} else if (typeof (container === null || container === void 0 ? void 0 : container.Name) === "string" && container.Name.trim().length > 0) {
|
|
29
|
+
rawName = container.Name;
|
|
30
|
+
} else {
|
|
31
|
+
rawName = DEFAULT_CONTAINER_NAME;
|
|
32
|
+
}
|
|
33
|
+
if (typeof rawName !== "string") {
|
|
34
|
+
return DEFAULT_CONTAINER_NAME;
|
|
35
|
+
}
|
|
36
|
+
var cleanedName = rawName.replace(/^\/+/, "").trim();
|
|
37
|
+
return cleanedName.length > 0 ? cleanedName : DEFAULT_CONTAINER_NAME;
|
|
38
|
+
}
|
|
12
39
|
|
|
13
40
|
/**
|
|
14
41
|
* Return a list of containers with normalized fields for the UI.
|
|
@@ -19,20 +46,23 @@ export function getContainers() {
|
|
|
19
46
|
}
|
|
20
47
|
function _getContainers() {
|
|
21
48
|
_getContainers = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee() {
|
|
22
|
-
var containers;
|
|
49
|
+
var containers, _t;
|
|
23
50
|
return _regenerator().w(function (_context) {
|
|
24
|
-
while (1) switch (_context.n) {
|
|
51
|
+
while (1) switch (_context.p = _context.n) {
|
|
25
52
|
case 0:
|
|
26
|
-
|
|
53
|
+
logger.debug("Listing containers");
|
|
54
|
+
_context.p = 1;
|
|
55
|
+
_context.n = 2;
|
|
27
56
|
return docker.listContainers({
|
|
28
57
|
all: true
|
|
29
58
|
});
|
|
30
|
-
case
|
|
59
|
+
case 2:
|
|
31
60
|
containers = _context.v;
|
|
61
|
+
logger.debug("Retrieved %d containers", containers.length);
|
|
32
62
|
return _context.a(2, containers.map(function (container) {
|
|
33
63
|
return {
|
|
34
64
|
id: container.Id,
|
|
35
|
-
name: (container
|
|
65
|
+
name: normalizeContainerName(container),
|
|
36
66
|
image: container.Image,
|
|
37
67
|
state: container.State,
|
|
38
68
|
status: container.Status,
|
|
@@ -45,18 +75,25 @@ function _getContainers() {
|
|
|
45
75
|
if (publicPorts.length > 0) {
|
|
46
76
|
return _toConsumableArray(new Set(publicPorts));
|
|
47
77
|
}
|
|
48
|
-
//
|
|
78
|
+
// Fall back to exposed private ports when there are no bindings.
|
|
49
79
|
var privatePorts = container.Ports.filter(function (port) {
|
|
50
80
|
return port.PrivatePort;
|
|
51
81
|
}).map(function (port) {
|
|
52
|
-
return "
|
|
82
|
+
return "".concat(port.PrivatePort);
|
|
53
83
|
});
|
|
54
84
|
return _toConsumableArray(new Set(privatePorts));
|
|
55
85
|
}()
|
|
56
86
|
};
|
|
57
87
|
}));
|
|
88
|
+
case 3:
|
|
89
|
+
_context.p = 3;
|
|
90
|
+
_t = _context.v;
|
|
91
|
+
logger.error("Failed to list containers", _t);
|
|
92
|
+
throw _t;
|
|
93
|
+
case 4:
|
|
94
|
+
return _context.a(2);
|
|
58
95
|
}
|
|
59
|
-
}, _callee);
|
|
96
|
+
}, _callee, null, [[1, 3]]);
|
|
60
97
|
}));
|
|
61
98
|
return _getContainers.apply(this, arguments);
|
|
62
99
|
}
|