cdd-cli 4.0.0 → 4.2.0
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/README.es.md +60 -26
- package/README.md +60 -26
- package/dist/App.js +1 -0
- package/dist/components/ContainerCreationPrompt.js +14 -0
- package/dist/components/PromptField.js +13 -6
- package/dist/components/TextField.js +54 -0
- package/dist/components/UsageMenu.js +1 -1
- package/dist/helpers/appState.js +26 -0
- package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
- package/dist/helpers/textEditing.js +293 -0
- package/dist/hooks/creation/useContainerCreation.js +132 -15
- package/dist/hooks/useContainerCommandRouter.js +12 -2
- package/dist/hooks/useControls.js +21 -26
- package/dist/hooks/useShellMode.js +101 -0
- package/dist/index.js +3 -1
- package/package.json +4 -1
- package/.atl/skill-registry.md +0 -30
- package/.editorconfig +0 -12
- package/.eslintrc.json +0 -30
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/docs.yml +0 -42
- package/.prettierignore +0 -4
- package/.prettierrc +0 -8
- package/.~lock.ROADMAP.md# +0 -1
- package/CHANGELOG.md +0 -216
- package/CODE_OF_CONDUCT.md +0 -12
- package/CONTRIBUTING.md +0 -35
- package/ROADMAP.md +0 -647
- package/SECURITY.md +0 -5
- package/__mocks__/ink.cjs +0 -17
- package/_config.yml +0 -13
- package/babel.config.cjs +0 -23
- package/coverage/clover.xml +0 -575
- package/coverage/coverage-final.json +0 -17
- package/coverage/lcov-report/ContainerCreationPrompt.jsx.html +0 -400
- package/coverage/lcov-report/ControlsHUD.jsx.html +0 -256
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -394
- package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -268
- package/coverage/lcov-report/components/index.html +0 -146
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/helpers/constants.js.html +0 -352
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
- package/coverage/lcov-report/helpers/dockerHubService.js.html +0 -310
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
- package/coverage/lcov-report/helpers/index.html +0 -206
- package/coverage/lcov-report/helpers/logger.js.html +0 -424
- package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
- package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
- package/coverage/lcov-report/hooks/creation/index.html +0 -146
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -982
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
- package/coverage/lcov-report/hooks/debug/index.html +0 -116
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
- package/coverage/lcov-report/hooks/index.html +0 -161
- package/coverage/lcov-report/hooks/navigation/index.html +0 -116
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
- package/coverage/lcov-report/hooks/useControls.js.html +0 -868
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
- package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
- package/coverage/lcov-report/index.html +0 -176
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -1163
- package/fix-imports.cjs +0 -54
- package/jest.config.cjs +0 -14
- package/src/App.jsx +0 -102
- package/src/components/ContainerCreationPrompt.jsx +0 -107
- package/src/components/ContainerList.jsx +0 -32
- package/src/components/ContainerRow.jsx +0 -84
- package/src/components/ContainerSection.jsx +0 -24
- package/src/components/ControlsHUD.jsx +0 -66
- package/src/components/Footer.jsx +0 -15
- package/src/components/Header.jsx +0 -41
- package/src/components/LogViewer.jsx +0 -36
- package/src/components/MessageFeedback.jsx +0 -25
- package/src/components/PromptField.jsx +0 -48
- package/src/components/StatsBar.jsx +0 -70
- package/src/components/SuggestionPanel.jsx +0 -61
- package/src/components/UsageMenu.jsx +0 -52
- package/src/helpers/appInfo.js +0 -23
- package/src/helpers/constants.js +0 -89
- package/src/helpers/containerOptionsBuilder.js +0 -53
- package/src/helpers/dockerHubService.js +0 -75
- package/src/helpers/dockerService/dockerService.js +0 -28
- package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
- package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
- package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
- package/src/helpers/exitWithMessage.js +0 -32
- package/src/helpers/imageNameUtils.js +0 -32
- package/src/helpers/logger.js +0 -112
- package/src/helpers/safeCall.js +0 -18
- package/src/helpers/validationHelpers.js +0 -87
- package/src/hooks/creation/useContainerActions.js +0 -89
- package/src/hooks/creation/useContainerCreation.js +0 -377
- package/src/hooks/creation/useLogsViewer.js +0 -51
- package/src/hooks/debug/useDebugLogs.js +0 -34
- package/src/hooks/navigation/useContainerSelection.js +0 -28
- package/src/hooks/useContainerCommandRouter.js +0 -123
- package/src/hooks/useContainerStats.js +0 -44
- package/src/hooks/useContainers.js +0 -26
- package/src/hooks/useControls.js +0 -263
- package/src/hooks/useEraseConfirmation.js +0 -39
- package/src/hooks/useExitHandler.js +0 -69
- package/src/index.js +0 -20
- package/test/ContainerCreationPrompt.dom.test.js +0 -313
- package/test/ControlsHUD.dom.test.js +0 -125
- package/test/SuggestionPanel.dom.test.js +0 -83
- package/test/constants.test.js +0 -41
- package/test/containerActions.test.js +0 -229
- package/test/containerList.test.js +0 -79
- package/test/containerOptionsBuilder.test.js +0 -36
- package/test/dockerHubService.test.js +0 -213
- package/test/jest.setup.js +0 -7
- package/test/logger.test.js +0 -58
- package/test/resolveImageTag.test.js +0 -26
- package/test/safeCall.test.js +0 -25
- package/test/useContainerCreation.dom.test.js +0 -567
- package/test/useContainerCreationHub.dom.test.js +0 -184
- package/test/useControls.dom.test.js +0 -519
- package/test/validationHelpers.test.js +0 -111
package/fix-imports.cjs
DELETED
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// Script para agregar automáticamente la extensión .js a los imports relativos en dist/
|
|
2
|
-
const fs = require('fs');
|
|
3
|
-
const path = require('path');
|
|
4
|
-
|
|
5
|
-
function needsExtension(p) {
|
|
6
|
-
// already has a known extension
|
|
7
|
-
return !/\.(js|jsx|mjs|json)$/.test(p);
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
function normalizePath(p) {
|
|
11
|
-
return needsExtension(p) ? p + '.js' : p;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function addJsExtensionToImports(filePath) {
|
|
15
|
-
let code = fs.readFileSync(filePath, 'utf8');
|
|
16
|
-
|
|
17
|
-
// from '...'
|
|
18
|
-
code = code.replace(/from\s+(['"])(\.{1,2}\/[^'"\)]+?)\1/g, (m, quote, p) => {
|
|
19
|
-
return `from ${quote}${normalizePath(p)}${quote}`;
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
// side-effect imports: import '...';
|
|
23
|
-
code = code.replace(/(^|\n)\s*import\s+(['"])(\.{1,2}\/[^'"\)]+?)\2/gm, (m, pre, quote, p) => {
|
|
24
|
-
return `${pre}import ${quote}${normalizePath(p)}${quote}`;
|
|
25
|
-
});
|
|
26
|
-
|
|
27
|
-
// export ... from '...'
|
|
28
|
-
code = code.replace(/export[\s\S]*?from\s+(['"])(\.{1,2}\/[^'"\)]+?)\1/g, (m, quote, p) => {
|
|
29
|
-
return m.replace(p, normalizePath(p));
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// dynamic imports import('...')
|
|
33
|
-
code = code.replace(/import\(\s*(['"])(\.{1,2}\/[^'"\)]+?)\1\s*\)/g, (m, quote, p) => {
|
|
34
|
-
return `import(${quote}${normalizePath(p)}${quote})`;
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// Convert any remaining .jsx import specifiers to .js
|
|
38
|
-
code = code.replace(/(['"])(\.{1,2}\/[^'"\s]+?)\.jsx\1/g, (m, q, p) => `${q}${p}.js${q}`);
|
|
39
|
-
|
|
40
|
-
fs.writeFileSync(filePath, code, 'utf8');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function processDir(dir) {
|
|
44
|
-
fs.readdirSync(dir).forEach(file => {
|
|
45
|
-
const fullPath = path.join(dir, file);
|
|
46
|
-
if (fs.statSync(fullPath).isDirectory()) {
|
|
47
|
-
processDir(fullPath);
|
|
48
|
-
} else if (file.endsWith('.js')) {
|
|
49
|
-
addJsExtensionToImports(fullPath);
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
processDir(path.join(__dirname, 'dist'));
|
package/jest.config.cjs
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
module.exports = {
|
|
2
|
-
testEnvironment: 'node',
|
|
3
|
-
setupFilesAfterEnv: ['./test/jest.setup.js'],
|
|
4
|
-
transform: {
|
|
5
|
-
'^.+\\.[tj]sx?$': 'babel-jest'
|
|
6
|
-
},
|
|
7
|
-
// Allow babel-jest to transform ESM-only packages used by components
|
|
8
|
-
transformIgnorePatterns: [
|
|
9
|
-
'/node_modules/(?!(ink|ink-testing-library|ansi-styles|chalk|cli-cursor|cli-spinners|is-unicode-supported|restore-cursor|signal-exit|slice-ansi|strip-ansi|wrap-ansi|yoga-layout-prebuilt)/)'
|
|
10
|
-
],
|
|
11
|
-
moduleNameMapper: {
|
|
12
|
-
'^ink$': '<rootDir>/__mocks__/ink.cjs'
|
|
13
|
-
}
|
|
14
|
-
};
|
package/src/App.jsx
DELETED
|
@@ -1,102 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Main React component for the CDD CLI UI.
|
|
3
|
-
* Handles container listing, action feedback, optional creation prompt,
|
|
4
|
-
* and renders the live debug log panel (toggle with the D shortcut).
|
|
5
|
-
*
|
|
6
|
-
* @component
|
|
7
|
-
* @returns {JSX.Element} The rendered app
|
|
8
|
-
* @example
|
|
9
|
-
* // Render the app
|
|
10
|
-
* <App />
|
|
11
|
-
*/
|
|
12
|
-
import React from 'react';
|
|
13
|
-
import { Box, Text, Spacer } from 'ink';
|
|
14
|
-
import { useContainers } from './hooks/useContainers.js';
|
|
15
|
-
import { useControls } from './hooks/useControls.js';
|
|
16
|
-
import ContainerSection from './components/ContainerSection.jsx';
|
|
17
|
-
import MessageFeedback from './components/MessageFeedback.jsx';
|
|
18
|
-
import Header from './components/Header.jsx';
|
|
19
|
-
import LogViewer from './components/LogViewer.jsx';
|
|
20
|
-
import ContainerCreationPrompt from './components/ContainerCreationPrompt.jsx';
|
|
21
|
-
import UsageMenu from './components/UsageMenu.jsx';
|
|
22
|
-
import Footer from './components/Footer.jsx';
|
|
23
|
-
|
|
24
|
-
export default function App() {
|
|
25
|
-
const { containers } = useContainers();
|
|
26
|
-
const controls = useControls(containers);
|
|
27
|
-
|
|
28
|
-
if (controls.creatingContainer) {
|
|
29
|
-
return (
|
|
30
|
-
<ContainerCreationPrompt
|
|
31
|
-
step={controls.creationStep}
|
|
32
|
-
imageName={controls.imageNameInput}
|
|
33
|
-
containerName={controls.containerNameInput}
|
|
34
|
-
portInput={controls.portInput}
|
|
35
|
-
envInput={controls.envInput}
|
|
36
|
-
message={controls.message}
|
|
37
|
-
messageColor={controls.messageColor}
|
|
38
|
-
suggestions={controls.creation.suggestions}
|
|
39
|
-
selectedSuggestionIndex={controls.creation.selectedSuggestionIndex}
|
|
40
|
-
visibleOffset={controls.creation.visibleOffset}
|
|
41
|
-
isSearchingHub={controls.creation.isSearchingHub}
|
|
42
|
-
hubResults={controls.creation.hubResults}
|
|
43
|
-
hasSuggestedEnv={controls.creation.hasSuggestedEnv}
|
|
44
|
-
/>
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return (
|
|
49
|
-
<>
|
|
50
|
-
<Box
|
|
51
|
-
flexDirection="column"
|
|
52
|
-
borderStyle="round"
|
|
53
|
-
borderColor="cyan"
|
|
54
|
-
padding={1}
|
|
55
|
-
>
|
|
56
|
-
<Header count={containers.length} />
|
|
57
|
-
<Text> </Text>
|
|
58
|
-
<ContainerSection
|
|
59
|
-
containers={containers}
|
|
60
|
-
selected={controls.selected}
|
|
61
|
-
/>
|
|
62
|
-
<Spacer />
|
|
63
|
-
<MessageFeedback
|
|
64
|
-
message={controls.message}
|
|
65
|
-
color={controls.messageColor}
|
|
66
|
-
/>
|
|
67
|
-
<UsageMenu />
|
|
68
|
-
{controls.showDebugLogs && (
|
|
69
|
-
<Box
|
|
70
|
-
marginTop={1}
|
|
71
|
-
flexDirection="column"
|
|
72
|
-
borderStyle="round"
|
|
73
|
-
borderColor="gray"
|
|
74
|
-
padding={1}
|
|
75
|
-
>
|
|
76
|
-
<Text color="cyan">Debug log — press D or ESC to close</Text>
|
|
77
|
-
{controls.debugLogs.length === 0 ? (
|
|
78
|
-
<Text dimColor>
|
|
79
|
-
No debug entries yet. Run with CDD_LOG_LEVEL=debug for verbose
|
|
80
|
-
output.
|
|
81
|
-
</Text>
|
|
82
|
-
) : (
|
|
83
|
-
controls.debugLogs.slice(-15).map((line, idx) => (
|
|
84
|
-
<Text key={idx} color="gray">
|
|
85
|
-
{line}
|
|
86
|
-
</Text>
|
|
87
|
-
))
|
|
88
|
-
)}
|
|
89
|
-
</Box>
|
|
90
|
-
)}
|
|
91
|
-
<Footer />
|
|
92
|
-
</Box>
|
|
93
|
-
{controls.showLogs && (
|
|
94
|
-
<LogViewer
|
|
95
|
-
logs={controls.logs}
|
|
96
|
-
onExit={controls.exitLogs}
|
|
97
|
-
container={containers[controls.selected]}
|
|
98
|
-
/>
|
|
99
|
-
)}
|
|
100
|
-
</>
|
|
101
|
-
);
|
|
102
|
-
}
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box } from 'ink';
|
|
3
|
-
import { PromptField, PromptMessage } from './PromptField.jsx';
|
|
4
|
-
import { SuggestionPanel } from './SuggestionPanel.jsx';
|
|
5
|
-
import { ControlsHUD } from './ControlsHUD.jsx';
|
|
6
|
-
import PropTypes from 'prop-types';
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Prompt UI shown when creating a new container.
|
|
10
|
-
* Presents a step-by-step form (image, name, ports, env) and displays
|
|
11
|
-
* a contextual message provided by the creation hook.
|
|
12
|
-
*
|
|
13
|
-
* @param {Object} props
|
|
14
|
-
* @param {number} props.step - Current step index (0..3)
|
|
15
|
-
* @param {string} props.imageName - Value for the image name field
|
|
16
|
-
* @param {string} props.containerName - Value for the container name field
|
|
17
|
-
* @param {string} props.portInput - Value for the ports input field
|
|
18
|
-
* @param {string} props.envInput - Value for the environment variables field
|
|
19
|
-
* @param {string} props.message - Contextual message to display (errors/hints)
|
|
20
|
-
* @param {string} props.messageColor - Color for the contextual message
|
|
21
|
-
* @param {string[]} [props.suggestions] - Autocomplete suggestions (shown only on step 0)
|
|
22
|
-
* @param {number} [props.selectedSuggestionIndex] - Currently focused suggestion index
|
|
23
|
-
* @param {number} [props.visibleOffset] - First visible suggestion offset
|
|
24
|
-
* @returns {JSX.Element}
|
|
25
|
-
*/
|
|
26
|
-
export default function ContainerCreationPrompt(props) {
|
|
27
|
-
const {
|
|
28
|
-
step,
|
|
29
|
-
imageName,
|
|
30
|
-
containerName,
|
|
31
|
-
portInput,
|
|
32
|
-
envInput,
|
|
33
|
-
message,
|
|
34
|
-
messageColor,
|
|
35
|
-
suggestions = [],
|
|
36
|
-
selectedSuggestionIndex = -1,
|
|
37
|
-
visibleOffset = 0,
|
|
38
|
-
hubResults = null,
|
|
39
|
-
isSearchingHub = false,
|
|
40
|
-
hasSuggestedEnv = false,
|
|
41
|
-
} = props;
|
|
42
|
-
const prompts = [
|
|
43
|
-
{
|
|
44
|
-
label: 'Name of the image to create (e.g., nginx:1.27-alpine):',
|
|
45
|
-
value: imageName,
|
|
46
|
-
required: true,
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
label: 'Name of the container (optional):',
|
|
50
|
-
value: containerName,
|
|
51
|
-
required: false,
|
|
52
|
-
},
|
|
53
|
-
{
|
|
54
|
-
label: 'Ports (optional, format 8080:80,443:443):',
|
|
55
|
-
value: portInput,
|
|
56
|
-
required: false,
|
|
57
|
-
},
|
|
58
|
-
{
|
|
59
|
-
label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
|
|
60
|
-
value: envInput,
|
|
61
|
-
required: false,
|
|
62
|
-
},
|
|
63
|
-
];
|
|
64
|
-
const { label, value, required } = prompts[step] || {};
|
|
65
|
-
const activeItems = hubResults ?? suggestions;
|
|
66
|
-
const showSuggestions = step === 0 && (isSearchingHub || activeItems.length > 0);
|
|
67
|
-
const hasSuggestions = (suggestions?.length > 0) || (hubResults?.length > 0);
|
|
68
|
-
return (
|
|
69
|
-
<Box
|
|
70
|
-
flexDirection="column"
|
|
71
|
-
borderStyle="round"
|
|
72
|
-
borderColor="yellow"
|
|
73
|
-
padding={1}
|
|
74
|
-
>
|
|
75
|
-
<PromptField label={label} value={value} required={required} />
|
|
76
|
-
{showSuggestions && (
|
|
77
|
-
<SuggestionPanel
|
|
78
|
-
items={activeItems}
|
|
79
|
-
selectedIndex={selectedSuggestionIndex}
|
|
80
|
-
visibleOffset={visibleOffset}
|
|
81
|
-
isLoading={isSearchingHub}
|
|
82
|
-
/>
|
|
83
|
-
)}
|
|
84
|
-
<PromptMessage message={message} color={messageColor} />
|
|
85
|
-
<ControlsHUD step={step} hasSuggestions={hasSuggestions} isSearchingHub={isSearchingHub} hasSuggestedEnv={hasSuggestedEnv} />
|
|
86
|
-
</Box>
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
ContainerCreationPrompt.propTypes = {
|
|
91
|
-
step: PropTypes.number.isRequired,
|
|
92
|
-
imageName: PropTypes.string,
|
|
93
|
-
containerName: PropTypes.string,
|
|
94
|
-
portInput: PropTypes.string,
|
|
95
|
-
envInput: PropTypes.string,
|
|
96
|
-
message: PropTypes.string,
|
|
97
|
-
messageColor: PropTypes.string,
|
|
98
|
-
suggestions: PropTypes.arrayOf(PropTypes.string),
|
|
99
|
-
selectedSuggestionIndex: PropTypes.number,
|
|
100
|
-
visibleOffset: PropTypes.number,
|
|
101
|
-
hubResults: PropTypes.arrayOf(PropTypes.string),
|
|
102
|
-
isSearchingHub: PropTypes.bool,
|
|
103
|
-
hasSuggestedEnv: PropTypes.bool,
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
// Named export for test compatibility with jest ESM interop
|
|
107
|
-
export { ContainerCreationPrompt };
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* List component for Docker containers.
|
|
3
|
-
*
|
|
4
|
-
* @component
|
|
5
|
-
* @param {Object} props - Component props
|
|
6
|
-
* @param {Array} props.containers - Containers to display
|
|
7
|
-
* @returns {JSX.Element} Rendered list
|
|
8
|
-
* @example
|
|
9
|
-
* // Render with containers
|
|
10
|
-
* <ContainerList containers={containers} />
|
|
11
|
-
*/
|
|
12
|
-
import React from 'react';
|
|
13
|
-
import PropTypes from 'prop-types';
|
|
14
|
-
import { Box } from 'ink';
|
|
15
|
-
import ContainerRow from './ContainerRow.jsx';
|
|
16
|
-
|
|
17
|
-
export default function ContainerList({ containers, selected }) {
|
|
18
|
-
return (
|
|
19
|
-
<>
|
|
20
|
-
{containers.map((container, i) => (
|
|
21
|
-
<Box key={container.id} flexDirection="row" paddingLeft={1}>
|
|
22
|
-
<ContainerRow container={container} isSelected={i === selected} />
|
|
23
|
-
</Box>
|
|
24
|
-
))}
|
|
25
|
-
</>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
ContainerList.propTypes = {
|
|
30
|
-
containers: PropTypes.arrayOf(PropTypes.object).isRequired,
|
|
31
|
-
selected: PropTypes.number,
|
|
32
|
-
};
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import StatsBar from './StatsBar.jsx';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
|
-
import { useContainerStats } from '../hooks/useContainerStats.js';
|
|
6
|
-
|
|
7
|
-
const stateText = (state) => {
|
|
8
|
-
if (state === 'running') return { text: '🟢 RUNNING', color: 'green' };
|
|
9
|
-
if (state === 'exited') return { text: '🔴 EXITED', color: 'red' };
|
|
10
|
-
if (state === 'paused') return { text: '🟠 PAUSED', color: 'yellow' };
|
|
11
|
-
return { text: state.toUpperCase(), color: 'gray' };
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Row component that renders information and live stats for a container.
|
|
16
|
-
*
|
|
17
|
-
* @param {Object} props
|
|
18
|
-
* @param {Object} props.container - Container object with id, name, image, state and ports
|
|
19
|
-
* @returns {JSX.Element}
|
|
20
|
-
*/
|
|
21
|
-
export default function ContainerRow({ container, isSelected = false }) {
|
|
22
|
-
const { id, name, image, state } = container;
|
|
23
|
-
const { stats, statsError } = useContainerStats(id, state);
|
|
24
|
-
|
|
25
|
-
// Format ports for display (no leading space to avoid layout shifts)
|
|
26
|
-
const formatPorts = (ports) => {
|
|
27
|
-
if (!ports || ports.length === 0) return '';
|
|
28
|
-
if (Array.isArray(ports)) {
|
|
29
|
-
return ports.map((p, _i) => `🔗 ${p}`).join(' ');
|
|
30
|
-
}
|
|
31
|
-
return `🔗 ${ports}`;
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
// Helper to truncate long strings to a max length without adding trailing spaces
|
|
35
|
-
const truncate = (s, max = 20) => {
|
|
36
|
-
if (!s) return '';
|
|
37
|
-
return s.length > max ? s.slice(0, max - 1) + '…' : s;
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
const stateInfo = stateText(state);
|
|
41
|
-
return (
|
|
42
|
-
<Box flexDirection="column" marginBottom={1}>
|
|
43
|
-
<Box flexDirection="row" alignItems="center">
|
|
44
|
-
<Box width={2} minWidth={2} justifyContent="flex-end" paddingRight={1}>
|
|
45
|
-
<Text color={isSelected ? 'green' : undefined}>
|
|
46
|
-
{isSelected ? '➤' : ' '}
|
|
47
|
-
</Text>
|
|
48
|
-
</Box>
|
|
49
|
-
<Box width={18} flexShrink={1} paddingRight={1}>
|
|
50
|
-
<Text color="cyan">{truncate(name, 18)}</Text>
|
|
51
|
-
</Box>
|
|
52
|
-
<Box width={18} flexShrink={1} paddingRight={1}>
|
|
53
|
-
<Text color="gray">{truncate(image, 18)}</Text>
|
|
54
|
-
</Box>
|
|
55
|
-
<Box width={14} minWidth={12} paddingRight={1}>
|
|
56
|
-
<Text color={stateInfo.color}>{stateInfo.text}</Text>
|
|
57
|
-
</Box>
|
|
58
|
-
<Box flexGrow={1} flexShrink={1} paddingLeft={0} paddingRight={1}>
|
|
59
|
-
<Text color="yellow">{formatPorts(container.ports)}</Text>
|
|
60
|
-
</Box>
|
|
61
|
-
<Box flexShrink={0} paddingLeft={1}>
|
|
62
|
-
{state === 'running' ? (
|
|
63
|
-
<StatsBar
|
|
64
|
-
cpu={parseFloat(stats.cpuPercent)}
|
|
65
|
-
mem={parseFloat(stats.memPercent)}
|
|
66
|
-
/>
|
|
67
|
-
) : null}
|
|
68
|
-
</Box>
|
|
69
|
-
</Box>
|
|
70
|
-
{statsError && <Text color="red">{statsError}</Text>}
|
|
71
|
-
</Box>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
ContainerRow.propTypes = {
|
|
76
|
-
container: PropTypes.shape({
|
|
77
|
-
id: PropTypes.string.isRequired,
|
|
78
|
-
name: PropTypes.string.isRequired,
|
|
79
|
-
image: PropTypes.string,
|
|
80
|
-
state: PropTypes.string,
|
|
81
|
-
ports: PropTypes.oneOfType([PropTypes.array, PropTypes.string]),
|
|
82
|
-
}).isRequired,
|
|
83
|
-
isSelected: PropTypes.bool,
|
|
84
|
-
};
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
import ContainerList from './ContainerList.jsx';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Section that displays the list of containers or a message when none exist.
|
|
8
|
-
*
|
|
9
|
-
* @param {Object} props
|
|
10
|
-
* @param {Array<Object>} props.containers - Array of container objects to display
|
|
11
|
-
* @param {number} [props.selected] - Index of the currently selected container
|
|
12
|
-
* @returns {JSX.Element}
|
|
13
|
-
*/
|
|
14
|
-
export default function ContainerSection({ containers, selected }) {
|
|
15
|
-
if (!containers || containers.length === 0) {
|
|
16
|
-
return <Text>No containers found</Text>;
|
|
17
|
-
}
|
|
18
|
-
return <ContainerList containers={containers} selected={selected} />;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
ContainerSection.propTypes = {
|
|
22
|
-
containers: PropTypes.array.isRequired,
|
|
23
|
-
selected: PropTypes.number,
|
|
24
|
-
};
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Returns the contextual keyboard hints for the current wizard state.
|
|
6
|
-
*
|
|
7
|
-
* @param {number} step - Current wizard step (0-3)
|
|
8
|
-
* @param {boolean} hasSuggestions - Whether suggestions/hub results are visible
|
|
9
|
-
* @param {boolean} isSearchingHub - Whether a Hub search is in progress
|
|
10
|
-
* @param {boolean} [hasSuggestedEnv=false] - Whether the image has suggestedEnv entries
|
|
11
|
-
* @returns {{ key: string, label: string }[]}
|
|
12
|
-
*/
|
|
13
|
-
function getHints(step, hasSuggestions, isSearchingHub, hasSuggestedEnv = false) {
|
|
14
|
-
if (step === 0) {
|
|
15
|
-
if (hasSuggestions) {
|
|
16
|
-
return [
|
|
17
|
-
{ key: '↑↓', label: 'Navigate' },
|
|
18
|
-
{ key: 'Enter', label: 'Select' },
|
|
19
|
-
{ key: 'Esc', label: 'Cancel' },
|
|
20
|
-
];
|
|
21
|
-
}
|
|
22
|
-
const hints = [];
|
|
23
|
-
if (!isSearchingHub) hints.push({ key: 'Tab', label: 'Search Hub' });
|
|
24
|
-
hints.push({ key: '↑↓', label: 'Browse' });
|
|
25
|
-
hints.push({ key: 'Enter', label: 'Confirm' });
|
|
26
|
-
hints.push({ key: 'Esc', label: 'Cancel' });
|
|
27
|
-
return hints;
|
|
28
|
-
}
|
|
29
|
-
if (step === 3) {
|
|
30
|
-
const hints = [];
|
|
31
|
-
if (hasSuggestedEnv) hints.push({ key: 'Tab', label: 'Insert next env' });
|
|
32
|
-
hints.push({ key: 'Enter', label: 'Continue' });
|
|
33
|
-
hints.push({ key: 'Esc', label: 'Cancel' });
|
|
34
|
-
return hints;
|
|
35
|
-
}
|
|
36
|
-
return [
|
|
37
|
-
{ key: 'Enter', label: 'Continue' },
|
|
38
|
-
{ key: 'Esc', label: 'Cancel' },
|
|
39
|
-
];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Displays a row of contextual keyboard shortcut hints for the active wizard step.
|
|
44
|
-
*
|
|
45
|
-
* @param {Object} props
|
|
46
|
-
* @param {number} props.step - Current wizard step
|
|
47
|
-
* @param {boolean} [props.hasSuggestions=false] - Whether suggestions/hub results are visible
|
|
48
|
-
* @param {boolean} [props.isSearchingHub=false] - Whether a Hub search is in progress
|
|
49
|
-
* @param {boolean} [props.hasSuggestedEnv=false] - Whether the image has suggestedEnv entries
|
|
50
|
-
*/
|
|
51
|
-
function ControlsHUD({ step, hasSuggestions = false, isSearchingHub = false, hasSuggestedEnv = false }) {
|
|
52
|
-
const hints = getHints(step, hasSuggestions, isSearchingHub, hasSuggestedEnv);
|
|
53
|
-
return (
|
|
54
|
-
<Box flexDirection="row" columnGap={2}>
|
|
55
|
-
{hints.map(({ key, label }) => (
|
|
56
|
-
<Box key={key}>
|
|
57
|
-
<Text color="cyan">[{key}]</Text>
|
|
58
|
-
<Text dimColor> {label}</Text>
|
|
59
|
-
</Box>
|
|
60
|
-
))}
|
|
61
|
-
</Box>
|
|
62
|
-
);
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export default ControlsHUD;
|
|
66
|
-
export { getHints, ControlsHUD };
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Simple footer displayed at the bottom of the interface.
|
|
6
|
-
*
|
|
7
|
-
* @returns {JSX.Element}
|
|
8
|
-
*/
|
|
9
|
-
export default function Footer() {
|
|
10
|
-
return (
|
|
11
|
-
<Box justifyContent="flex-end" width="100%">
|
|
12
|
-
<Text dimColor>Crafted by Carlos Cochero 2025</Text>
|
|
13
|
-
</Box>
|
|
14
|
-
);
|
|
15
|
-
}
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import PropTypes from 'prop-types';
|
|
5
|
-
import { getAppVersion } from '../helpers/appInfo.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* Header component displayed at the top of the CLI UI.
|
|
9
|
-
*
|
|
10
|
-
* @component
|
|
11
|
-
* @param {Object} props
|
|
12
|
-
* @param {number} props.count - Number of containers currently detected
|
|
13
|
-
* @returns {JSX.Element}
|
|
14
|
-
* @example
|
|
15
|
-
* <Header count={3} />
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
const VERSION = getAppVersion();
|
|
19
|
-
|
|
20
|
-
export default function Header({ count }) {
|
|
21
|
-
const formattedVersion = VERSION === 'unknown' ? 'unknown' : `v${VERSION}`;
|
|
22
|
-
|
|
23
|
-
return (
|
|
24
|
-
<Box justifyContent="space-between">
|
|
25
|
-
<Text color="cyanBright">
|
|
26
|
-
🐳 {chalk.bold('CDD')}
|
|
27
|
-
<Text color="gray"> — CLI Docker Dashboard</Text>
|
|
28
|
-
</Text>
|
|
29
|
-
<Box flexDirection="column" alignItems="flex-end">
|
|
30
|
-
<Text color="gray">{formattedVersion}</Text>
|
|
31
|
-
<Text color="gray">
|
|
32
|
-
{count} container{count === 1 ? '' : 's'} found
|
|
33
|
-
</Text>
|
|
34
|
-
</Box>
|
|
35
|
-
</Box>
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
Header.propTypes = {
|
|
40
|
-
count: PropTypes.number.isRequired,
|
|
41
|
-
};
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Log viewer overlay component. Shows the most recent lines.
|
|
7
|
-
* Purely presentational — keyboard handling is centralized in useControls.
|
|
8
|
-
*
|
|
9
|
-
* @param {Object} props
|
|
10
|
-
* @param {Array<string>} props.logs - Array of log lines
|
|
11
|
-
* @param {Function} [props.onExit] - Optional callback when the viewer closes
|
|
12
|
-
* @param {Object} [props.container] - Optional container metadata (used for title)
|
|
13
|
-
* @returns {JSX.Element}
|
|
14
|
-
*/
|
|
15
|
-
export default function LogViewer({ logs, onExit: _onExit, container }) {
|
|
16
|
-
const visibleLogs = logs.slice(-15);
|
|
17
|
-
|
|
18
|
-
return (
|
|
19
|
-
<>
|
|
20
|
-
<Text color="green">
|
|
21
|
-
{container?.name ?? 'Container'} logs, press ESC to exit
|
|
22
|
-
</Text>
|
|
23
|
-
{visibleLogs.length === 0 ? (
|
|
24
|
-
<Text dimColor>No logs...</Text>
|
|
25
|
-
) : (
|
|
26
|
-
visibleLogs.map((line, idx) => <Text key={idx}>{line}</Text>)
|
|
27
|
-
)}
|
|
28
|
-
</>
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
LogViewer.propTypes = {
|
|
33
|
-
logs: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
34
|
-
onExit: PropTypes.func,
|
|
35
|
-
container: PropTypes.object,
|
|
36
|
-
};
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Box, Text } from 'ink';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Small component that shows a feedback message in the UI.
|
|
7
|
-
*
|
|
8
|
-
* @param {Object} props
|
|
9
|
-
* @param {string} props.message - Message to display. If falsy, component returns null.
|
|
10
|
-
* @param {string} [props.color] - Optional color name for the message text.
|
|
11
|
-
* @returns {JSX.Element|null}
|
|
12
|
-
*/
|
|
13
|
-
export default function MessageFeedback({ message, color }) {
|
|
14
|
-
if (!message) return null;
|
|
15
|
-
return (
|
|
16
|
-
<Box marginBottom={1}>
|
|
17
|
-
<Text color={color}>{message}</Text>
|
|
18
|
-
</Box>
|
|
19
|
-
);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
MessageFeedback.propTypes = {
|
|
23
|
-
message: PropTypes.string,
|
|
24
|
-
color: PropTypes.string,
|
|
25
|
-
};
|
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import React from 'react';
|
|
2
|
-
import { Text } from 'ink';
|
|
3
|
-
import PropTypes from 'prop-types';
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* PromptField shows a label and the current typed value for an input field.
|
|
7
|
-
* The cursor is represented with a trailing underscore.
|
|
8
|
-
*
|
|
9
|
-
* @param {Object} props
|
|
10
|
-
* @param {string} props.label - Label shown above the input value
|
|
11
|
-
* @param {string} props.value - Current value of the input
|
|
12
|
-
* @param {boolean} [props.required=false] - Whether the field is required
|
|
13
|
-
* @returns {JSX.Element}
|
|
14
|
-
*/
|
|
15
|
-
export function PromptField({ label, value, required }) {
|
|
16
|
-
// If the field is required and empty, show in red
|
|
17
|
-
const isEmpty = required && !value.trim();
|
|
18
|
-
return (
|
|
19
|
-
<>
|
|
20
|
-
<Text>{label}</Text>
|
|
21
|
-
<Text color={isEmpty ? 'red' : 'cyan'}>{value}_</Text>
|
|
22
|
-
</>
|
|
23
|
-
);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Small helper to show a message below prompts (errors, hints, etc.).
|
|
28
|
-
*
|
|
29
|
-
* @param {Object} props
|
|
30
|
-
* @param {string} props.message - Message text to display
|
|
31
|
-
* @param {string} [props.color] - Optional color for the message
|
|
32
|
-
* @returns {JSX.Element|null}
|
|
33
|
-
*/
|
|
34
|
-
export function PromptMessage({ message, color }) {
|
|
35
|
-
if (!message) return null;
|
|
36
|
-
return <Text color={color || 'yellow'}>{message}</Text>;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
PromptField.propTypes = {
|
|
40
|
-
label: PropTypes.string,
|
|
41
|
-
value: PropTypes.string,
|
|
42
|
-
required: PropTypes.bool,
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
PromptMessage.propTypes = {
|
|
46
|
-
message: PropTypes.string,
|
|
47
|
-
color: PropTypes.string,
|
|
48
|
-
};
|