cdd-cli 3.2.2 → 4.1.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.
Files changed (126) hide show
  1. package/README.es.md +222 -0
  2. package/README.md +108 -26
  3. package/dist/App.js +7 -1
  4. package/dist/components/ContainerCreationPrompt.js +26 -10
  5. package/dist/components/ControlsHUD.js +106 -0
  6. package/dist/components/SuggestionPanel.js +16 -7
  7. package/dist/components/UsageMenu.js +1 -1
  8. package/dist/helpers/appState.js +26 -0
  9. package/dist/helpers/constants.js +60 -21
  10. package/dist/helpers/dockerHubService.js +111 -0
  11. package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
  12. package/dist/helpers/logger.js +1 -1
  13. package/dist/hooks/creation/useContainerActions.js +22 -5
  14. package/dist/hooks/creation/useContainerCreation.js +414 -107
  15. package/dist/hooks/useContainerCommandRouter.js +13 -3
  16. package/dist/hooks/useControls.js +28 -7
  17. package/dist/hooks/useShellMode.js +101 -0
  18. package/dist/index.js +10 -1
  19. package/package.json +4 -1
  20. package/.atl/skill-registry.md +0 -30
  21. package/.editorconfig +0 -12
  22. package/.eslintrc.json +0 -30
  23. package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
  24. package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
  25. package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
  26. package/.github/workflows/ci.yml +0 -32
  27. package/.github/workflows/docs.yml +0 -42
  28. package/.prettierignore +0 -4
  29. package/.prettierrc +0 -8
  30. package/CHANGELOG.md +0 -165
  31. package/CODE_OF_CONDUCT.md +0 -12
  32. package/CONTRIBUTING.md +0 -35
  33. package/SECURITY.md +0 -5
  34. package/__mocks__/ink.cjs +0 -17
  35. package/_config.yml +0 -13
  36. package/babel.config.cjs +0 -23
  37. package/coverage/clover.xml +0 -694
  38. package/coverage/coverage-final.json +0 -21
  39. package/coverage/lcov-report/base.css +0 -224
  40. package/coverage/lcov-report/block-navigation.js +0 -87
  41. package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -376
  42. package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
  43. package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -244
  44. package/coverage/lcov-report/components/index.html +0 -146
  45. package/coverage/lcov-report/favicon.png +0 -0
  46. package/coverage/lcov-report/helpers/constants.js.html +0 -298
  47. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
  48. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
  49. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
  50. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
  51. package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
  52. package/coverage/lcov-report/helpers/index.html +0 -191
  53. package/coverage/lcov-report/helpers/logger.js.html +0 -424
  54. package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
  55. package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
  56. package/coverage/lcov-report/hooks/creation/index.html +0 -146
  57. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
  58. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -805
  59. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
  60. package/coverage/lcov-report/hooks/debug/index.html +0 -116
  61. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
  62. package/coverage/lcov-report/hooks/index.html +0 -161
  63. package/coverage/lcov-report/hooks/navigation/index.html +0 -116
  64. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
  65. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
  66. package/coverage/lcov-report/hooks/useControls.js.html +0 -826
  67. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
  68. package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
  69. package/coverage/lcov-report/index.html +0 -206
  70. package/coverage/lcov-report/prettify.css +0 -1
  71. package/coverage/lcov-report/prettify.js +0 -2
  72. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  73. package/coverage/lcov-report/sorter.js +0 -210
  74. package/coverage/lcov.info +0 -1402
  75. package/fix-imports.cjs +0 -54
  76. package/jest.config.cjs +0 -14
  77. package/src/App.jsx +0 -96
  78. package/src/components/ContainerCreationPrompt.jsx +0 -97
  79. package/src/components/ContainerList.jsx +0 -32
  80. package/src/components/ContainerRow.jsx +0 -84
  81. package/src/components/ContainerSection.jsx +0 -24
  82. package/src/components/Footer.jsx +0 -15
  83. package/src/components/Header.jsx +0 -41
  84. package/src/components/LogViewer.jsx +0 -36
  85. package/src/components/MessageFeedback.jsx +0 -25
  86. package/src/components/PromptField.jsx +0 -48
  87. package/src/components/StatsBar.jsx +0 -70
  88. package/src/components/SuggestionPanel.jsx +0 -53
  89. package/src/components/UsageMenu.jsx +0 -52
  90. package/src/helpers/appInfo.js +0 -23
  91. package/src/helpers/constants.js +0 -71
  92. package/src/helpers/containerOptionsBuilder.js +0 -53
  93. package/src/helpers/dockerService/dockerService.js +0 -28
  94. package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
  95. package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
  96. package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
  97. package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
  98. package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
  99. package/src/helpers/exitWithMessage.js +0 -32
  100. package/src/helpers/imageNameUtils.js +0 -32
  101. package/src/helpers/logger.js +0 -113
  102. package/src/helpers/safeCall.js +0 -18
  103. package/src/helpers/validationHelpers.js +0 -87
  104. package/src/hooks/creation/useContainerActions.js +0 -76
  105. package/src/hooks/creation/useContainerCreation.js +0 -240
  106. package/src/hooks/creation/useLogsViewer.js +0 -51
  107. package/src/hooks/debug/useDebugLogs.js +0 -34
  108. package/src/hooks/navigation/useContainerSelection.js +0 -28
  109. package/src/hooks/useContainerCommandRouter.js +0 -123
  110. package/src/hooks/useContainerStats.js +0 -44
  111. package/src/hooks/useContainers.js +0 -26
  112. package/src/hooks/useControls.js +0 -247
  113. package/src/hooks/useEraseConfirmation.js +0 -39
  114. package/src/hooks/useExitHandler.js +0 -69
  115. package/src/index.js +0 -16
  116. package/test/ContainerCreationPrompt.dom.test.js +0 -68
  117. package/test/SuggestionPanel.dom.test.js +0 -53
  118. package/test/constants.test.js +0 -34
  119. package/test/containerActions.test.js +0 -229
  120. package/test/containerList.test.js +0 -79
  121. package/test/containerOptionsBuilder.test.js +0 -36
  122. package/test/jest.setup.js +0 -7
  123. package/test/safeCall.test.js +0 -25
  124. package/test/useContainerCreation.dom.test.js +0 -264
  125. package/test/useControls.dom.test.js +0 -313
  126. 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,96 +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
- />
39
- );
40
- }
41
-
42
- return (
43
- <>
44
- <Box
45
- flexDirection="column"
46
- borderStyle="round"
47
- borderColor="cyan"
48
- padding={1}
49
- >
50
- <Header count={containers.length} />
51
- <Text> </Text>
52
- <ContainerSection
53
- containers={containers}
54
- selected={controls.selected}
55
- />
56
- <Spacer />
57
- <MessageFeedback
58
- message={controls.message}
59
- color={controls.messageColor}
60
- />
61
- <UsageMenu />
62
- {controls.showDebugLogs && (
63
- <Box
64
- marginTop={1}
65
- flexDirection="column"
66
- borderStyle="round"
67
- borderColor="gray"
68
- padding={1}
69
- >
70
- <Text color="cyan">Debug log — press D or ESC to close</Text>
71
- {controls.debugLogs.length === 0 ? (
72
- <Text dimColor>
73
- No debug entries yet. Run with CDD_LOG_LEVEL=debug for verbose
74
- output.
75
- </Text>
76
- ) : (
77
- controls.debugLogs.slice(-15).map((line, idx) => (
78
- <Text key={idx} color="gray">
79
- {line}
80
- </Text>
81
- ))
82
- )}
83
- </Box>
84
- )}
85
- <Footer />
86
- </Box>
87
- {controls.showLogs && (
88
- <LogViewer
89
- logs={controls.logs}
90
- onExit={controls.exitLogs}
91
- container={containers[controls.selected]}
92
- />
93
- )}
94
- </>
95
- );
96
- }
@@ -1,97 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { PromptField, PromptMessage } from './PromptField.jsx';
4
- import { SuggestionPanel } from './SuggestionPanel.jsx';
5
- import PropTypes from 'prop-types';
6
-
7
- /**
8
- * Prompt UI shown when creating a new container.
9
- * Presents a step-by-step form (image, name, ports, env) and displays
10
- * a contextual message provided by the creation hook.
11
- *
12
- * @param {Object} props
13
- * @param {number} props.step - Current step index (0..3)
14
- * @param {string} props.imageName - Value for the image name field
15
- * @param {string} props.containerName - Value for the container name field
16
- * @param {string} props.portInput - Value for the ports input field
17
- * @param {string} props.envInput - Value for the environment variables field
18
- * @param {string} props.message - Contextual message to display (errors/hints)
19
- * @param {string} props.messageColor - Color for the contextual message
20
- * @param {string[]} [props.suggestions] - Autocomplete suggestions (shown only on step 0)
21
- * @param {number} [props.selectedSuggestionIndex] - Currently focused suggestion index
22
- * @param {number} [props.visibleOffset] - First visible suggestion offset
23
- * @returns {JSX.Element}
24
- */
25
- export default function ContainerCreationPrompt(props) {
26
- const {
27
- step,
28
- imageName,
29
- containerName,
30
- portInput,
31
- envInput,
32
- message,
33
- messageColor,
34
- suggestions = [],
35
- selectedSuggestionIndex = -1,
36
- visibleOffset = 0,
37
- } = props;
38
- const prompts = [
39
- {
40
- label: 'Name of the image to create (e.g., nginx:latest):',
41
- value: imageName,
42
- required: true,
43
- },
44
- {
45
- label: 'Name of the container (optional):',
46
- value: containerName,
47
- required: false,
48
- },
49
- {
50
- label: 'Ports (optional, format 8080:80,443:443):',
51
- value: portInput,
52
- required: false,
53
- },
54
- {
55
- label: 'Environment variables (optional, format VAR1=val1,VAR2=val2):',
56
- value: envInput,
57
- required: false,
58
- },
59
- ];
60
- const { label, value, required } = prompts[step] || {};
61
- const showSuggestions = step === 0 && suggestions.length > 0;
62
- return (
63
- <Box
64
- flexDirection="column"
65
- borderStyle="round"
66
- borderColor="yellow"
67
- padding={1}
68
- >
69
- <PromptField label={label} value={value} required={required} />
70
- {showSuggestions && (
71
- <SuggestionPanel
72
- items={suggestions}
73
- selectedIndex={selectedSuggestionIndex}
74
- visibleOffset={visibleOffset}
75
- />
76
- )}
77
- <PromptMessage message={message} color={messageColor} />
78
- <Text dimColor>Press Enter to continue, Escape to cancel</Text>
79
- </Box>
80
- );
81
- }
82
-
83
- ContainerCreationPrompt.propTypes = {
84
- step: PropTypes.number.isRequired,
85
- imageName: PropTypes.string,
86
- containerName: PropTypes.string,
87
- portInput: PropTypes.string,
88
- envInput: PropTypes.string,
89
- message: PropTypes.string,
90
- messageColor: PropTypes.string,
91
- suggestions: PropTypes.arrayOf(PropTypes.string),
92
- selectedSuggestionIndex: PropTypes.number,
93
- visibleOffset: PropTypes.number,
94
- };
95
-
96
- // Named export for test compatibility with jest ESM interop
97
- 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,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
- };
@@ -1,70 +0,0 @@
1
- import React from 'react';
2
- import { Text } from 'ink';
3
- import chalk from 'chalk';
4
- import PropTypes from 'prop-types';
5
-
6
- const BAR_WIDTH = 8;
7
-
8
- /**
9
- * Small visual bar that shows CPU and memory usage percentages.
10
- *
11
- * @param {Object} props
12
- * @param {number} props.cpu - CPU percentage (values outside 0-100 are clamped)
13
- * @param {number} props.mem - Memory percentage (values outside 0-100 are clamped)
14
- * @returns {JSX.Element}
15
- */
16
- export default function StatsBar({ cpu, mem }) {
17
- const cpuBar = makeBar(cpu, BAR_WIDTH);
18
- const memBar = makeBar(mem, BAR_WIDTH);
19
-
20
- return (
21
- <Text>
22
- CPU:{cpuBar}
23
- {colorize(cpu, ` ${formatPercent(cpu)}%`)} MEM:{memBar}
24
- {colorize(mem, ` ${formatPercent(mem)}%`)}
25
- </Text>
26
- );
27
- }
28
-
29
- StatsBar.propTypes = {
30
- cpu: PropTypes.number.isRequired,
31
- mem: PropTypes.number.isRequired,
32
- };
33
-
34
- /**
35
- * Build a fixed-width bar representation from a numeric percentage.
36
- *
37
- * @param {number} value - Percentage value (values outside 0-100 are clamped)
38
- * @param {number} width - Desired bar width in characters
39
- * @returns {string} Colored bar string
40
- */
41
- function makeBar(value, width) {
42
- const safeWidth = Math.max(1, Math.round(width));
43
- const normalized = Number.isFinite(value)
44
- ? Math.min(Math.max(value, 0), 100)
45
- : 0;
46
- const filled = Math.round((normalized / 100) * safeWidth);
47
- const empty = Math.max(0, safeWidth - filled);
48
- const bar = '█'.repeat(filled) + '░'.repeat(empty);
49
- return colorize(normalized, bar);
50
- }
51
-
52
- function formatPercent(value) {
53
- if (!Number.isFinite(value)) return 0;
54
- const normalized = Math.min(Math.max(value, 0), 100);
55
- if (normalized >= 10) return Math.round(normalized);
56
- return normalized.toFixed(1).replace(/\.0$/, '');
57
- }
58
-
59
- /**
60
- * Colorize a value or text depending on thresholds.
61
- *
62
- * @param {number} value - Numeric value used to pick color; receives clamped percentage
63
- * @param {string} [text] - Text to colorize, defaults to the numeric string
64
- * @returns {string}
65
- */
66
- function colorize(value, text = value.toString()) {
67
- if (value < 50) return chalk.greenBright(text);
68
- if (value < 80) return chalk.yellowBright(text);
69
- return chalk.redBright(text);
70
- }