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
@@ -1,60 +0,0 @@
1
- import { docker } from '../dockerService.js';
2
- import { safeCall } from '../../safeCall.js';
3
- import { logger } from '../../logger.js';
4
-
5
- /**
6
- * Return a stream of logs from a container and call callbacks for events.
7
- *
8
- * @param {string} containerId - Docker container id
9
- * @param {Function} onData - Called with chunk string when data arrives
10
- * @param {Function} onEnd - Called when stream ends
11
- * @param {Function} onError - Called on error
12
- */
13
- export function getLogsStream(containerId, onData, onEnd, onError) {
14
- // Use shared safeCall util to call optional callbacks safely
15
-
16
- try {
17
- const container = docker.getContainer(containerId);
18
- logger.debug('Opening logs stream for container %s', containerId);
19
- container.logs(
20
- {
21
- follow: true,
22
- stdout: true,
23
- stderr: true,
24
- tail: 100,
25
- },
26
- (err, stream) => {
27
- if (err) {
28
- logger.error(
29
- 'Failed to open logs stream for container %s',
30
- containerId,
31
- err
32
- );
33
- safeCall(onError, err);
34
- return;
35
- }
36
- logger.info('Logs stream established for container %s', containerId);
37
- stream.on('data', (chunk) => safeCall(onData, chunk.toString()));
38
- stream.on('end', () => {
39
- logger.debug('Logs stream ended for container %s', containerId);
40
- safeCall(onEnd);
41
- });
42
- stream.on('error', (streamErr) => {
43
- logger.error(
44
- 'Logs stream error for container %s',
45
- containerId,
46
- streamErr
47
- );
48
- safeCall(onError, streamErr);
49
- });
50
- }
51
- );
52
- } catch (err) {
53
- logger.error(
54
- 'Unexpected error while opening logs stream for container %s',
55
- containerId,
56
- err
57
- );
58
- safeCall(onError, err);
59
- }
60
- }
@@ -1,69 +0,0 @@
1
- import { docker } from '../dockerService.js';
2
- import { logger } from '../../logger.js';
3
-
4
- /**
5
- * Retrieve a snapshot of container resource usage (CPU, memory, network).
6
- *
7
- * @param {string} containerId - Docker container id
8
- * @returns {Promise<Object>} Object with cpuPercent, memPercent and netIO {rx,tx}
9
- */
10
- export async function getStats(containerId) {
11
- const container = docker.getContainer(containerId);
12
- logger.debug('Fetching stats for container %s', containerId);
13
- let stream;
14
- try {
15
- stream = await container.stats({ stream: false });
16
- } catch (err) {
17
- logger.error('Failed to fetch stats for container %s', containerId, err);
18
- throw err;
19
- }
20
-
21
- const cpuDelta =
22
- stream.cpu_stats.cpu_usage.total_usage -
23
- stream.precpu_stats.cpu_usage.total_usage;
24
- const systemDelta =
25
- stream.cpu_stats.system_cpu_usage - stream.precpu_stats.system_cpu_usage;
26
-
27
- // Get number of CPUs for proper normalization
28
- let numCpus = 1;
29
- if (
30
- typeof stream.cpu_stats.online_cpus === 'number' &&
31
- stream.cpu_stats.online_cpus > 0
32
- ) {
33
- numCpus = stream.cpu_stats.online_cpus;
34
- } else if (
35
- stream.cpu_stats.cpu_usage &&
36
- Array.isArray(stream.cpu_stats.cpu_usage.percpu_usage) &&
37
- stream.cpu_stats.cpu_usage.percpu_usage.length > 0
38
- ) {
39
- numCpus = stream.cpu_stats.cpu_usage.percpu_usage.length;
40
- }
41
-
42
- // Calculate normalized CPU percentage
43
- const cpuPercent =
44
- systemDelta > 0 ? (cpuDelta / systemDelta) * numCpus * 100 : 0;
45
-
46
- const memUsage = stream.memory_stats.usage || 0;
47
- const memLimit = stream.memory_stats.limit || 1;
48
- const memPercent = (memUsage / memLimit) * 100;
49
-
50
- const rx = stream.networks
51
- ? Object.values(stream.networks)
52
- .map((n) => n.rx_bytes)
53
- .reduce((a, b) => a + b, 0)
54
- : 0;
55
- const tx = stream.networks
56
- ? Object.values(stream.networks)
57
- .map((n) => n.tx_bytes)
58
- .reduce((a, b) => a + b, 0)
59
- : 0;
60
-
61
- const snapshot = {
62
- cpuPercent: cpuPercent.toFixed(1),
63
- memPercent: memPercent.toFixed(1),
64
- netIO: { rx, tx },
65
- };
66
-
67
- logger.debug('Stats fetched for container %s', containerId, snapshot);
68
- return snapshot;
69
- }
@@ -1,32 +0,0 @@
1
- import { docker } from '../dockerService.js';
2
-
3
- /**
4
- * Check whether an image exists locally.
5
- * @param {string} imageName - Image name or tag
6
- * @returns {Promise<boolean>}
7
- */
8
- export async function imageExists(imageName) {
9
- const images = await docker.listImages();
10
- return images.some(
11
- (img) =>
12
- (img.RepoTags || []).includes(imageName) ||
13
- (img.RepoDigests || []).some((d) => d.includes(imageName))
14
- );
15
- }
16
-
17
- /**
18
- * Pull an image from the registry.
19
- * @param {string} imageName - Image name to pull
20
- * @returns {Promise<void>}
21
- */
22
- export async function pullImage(imageName) {
23
- await new Promise((resolve, reject) => {
24
- docker.pull(imageName, (err, stream) => {
25
- if (err) return reject(new Error('Error pulling image: ' + err.message));
26
- docker.modem.followProgress(stream, (pullErr) => {
27
- if (pullErr) reject(new Error('Error during pull: ' + pullErr.message));
28
- else resolve();
29
- });
30
- });
31
- });
32
- }
@@ -1,32 +0,0 @@
1
- import { spawn } from 'child_process';
2
- import { EXIT_DELAY } from './constants';
3
-
4
- /**
5
- * Show an exit message using provided setters, clear the terminal and exit after a delay.
6
- *
7
- * @param {Object} params
8
- * @param {Function} params.setMessage - Setter for message text
9
- * @param {Function} params.setMessageColor - Setter for message color
10
- * @param {string} [params.message] - Message to display
11
- * @param {string} [params.color] - Color for the message
12
- * @param {number} [params.delay] - Delay in milliseconds before exiting
13
- */
14
- export function exitWithMessage({
15
- setMessage,
16
- setMessageColor,
17
- message = 'Exiting...',
18
- color = 'yellow',
19
- delay = EXIT_DELAY,
20
- }) {
21
- setMessage(message);
22
- setMessageColor(color);
23
- setTimeout(() => {
24
- setMessage('');
25
- if (process.platform === 'win32') {
26
- spawn('cmd', ['/c', 'cls'], { stdio: 'inherit' });
27
- } else {
28
- spawn('clear', [], { stdio: 'inherit' });
29
- }
30
- process.exit();
31
- }, delay);
32
- }
@@ -1,32 +0,0 @@
1
- /**
2
- * Utility for normalizing Docker image names.
3
- * Extracted from validationHelpers.js and containerActions.js — both had
4
- * identical implementations; consolidated here to avoid duplication.
5
- */
6
-
7
- /**
8
- * Normalize an image name to its base name (strips registry prefix and tag).
9
- *
10
- * Examples:
11
- * "docker.io/library/postgres:16-alpine" → "postgres"
12
- * "nginx:alpine" → "nginx"
13
- * "myregistry.io/myorg/myapp:latest" → "myapp"
14
- *
15
- * @param {string} imageName - Raw image name (from user input or Docker API)
16
- * @returns {string} Base image name in lowercase, empty string if input is falsy
17
- */
18
- export function normalizeImageName(imageName) {
19
- if (!imageName) return '';
20
- // Remove registry prefix (everything up to and including the last '/')
21
- let name = imageName;
22
- const slashIdx = name.lastIndexOf('/');
23
- if (slashIdx !== -1) {
24
- name = name.slice(slashIdx + 1);
25
- }
26
- // Remove tag (everything from the first ':' onward)
27
- const colonIdx = name.indexOf(':');
28
- if (colonIdx !== -1) {
29
- name = name.slice(0, colonIdx);
30
- }
31
- return name.toLowerCase();
32
- }
@@ -1,113 +0,0 @@
1
- /**
2
- * Supported log levels in ascending verbosity.
3
- * Lower numeric values represent higher severity.
4
- */
5
- const LOG_LEVELS = {
6
- error: 0,
7
- warn: 1,
8
- info: 2,
9
- debug: 3,
10
- };
11
-
12
- /**
13
- * @typedef {Object} LogEntry
14
- * @property {string} level Severity string (error, warn, info, debug)
15
- * @property {string} message Primary log message
16
- * @property {any[]} args Additional payload values forwarded to listeners
17
- * @property {string} timestamp ISO timestamp when the entry was created
18
- * @property {string} formatted Preformatted message used for console output
19
- */
20
-
21
- /**
22
- * @callback LogListener
23
- * @param {LogEntry} entry Log entry data emitted by the logger
24
- */
25
-
26
- function parseLevel(value) {
27
- if (typeof value !== 'string') {
28
- return null;
29
- }
30
- const normalized = value.trim().toLowerCase();
31
- if (normalized in LOG_LEVELS) {
32
- return LOG_LEVELS[normalized];
33
- }
34
- return null;
35
- }
36
-
37
- const envLevel =
38
- parseLevel(process.env.CDD_LOG_LEVEL) ??
39
- parseLevel(process.env.LOG_LEVEL) ??
40
- LOG_LEVELS.info;
41
-
42
- const currentLevel = typeof envLevel === 'number' ? envLevel : LOG_LEVELS.info;
43
-
44
- const listeners = new Set();
45
-
46
- function shouldLog(level) {
47
- const target = LOG_LEVELS[level];
48
- return typeof target === 'number' && target <= currentLevel;
49
- }
50
-
51
- function log(level, message, ...args) {
52
- if (!shouldLog(level)) {
53
- return;
54
- }
55
-
56
- const timestamp = new Date().toISOString();
57
- const upperLevel = level.toUpperCase();
58
- const formatted = `[${timestamp}] [${upperLevel}] ${message}`;
59
- const entry = { level, message, args, timestamp, formatted };
60
-
61
- const shouldWriteToConsole =
62
- listeners.size === 0 || level === 'error' || level === 'warn';
63
-
64
- if (shouldWriteToConsole) {
65
- const method = level === 'debug' ? 'log' : level;
66
- if (typeof console[method] === 'function') {
67
- console[method](formatted, ...args);
68
- } else {
69
- console.log(formatted, ...args);
70
- }
71
- }
72
-
73
- listeners.forEach((listener) => {
74
- try {
75
- listener(entry);
76
- } catch (err) {
77
- // Ignore listener errors so logging remains resilient.
78
- }
79
- });
80
- }
81
-
82
- /**
83
- * Shared logger instance with leveled output and in-memory subscriptions.
84
- * Consumers can import `{ logger }` to emit messages or to listen for
85
- * log entries (used by the CLI debug panel).
86
- */
87
- export const logger = {
88
- debug(message, ...args) {
89
- log('debug', message, ...args);
90
- },
91
- info(message, ...args) {
92
- log('info', message, ...args);
93
- },
94
- warn(message, ...args) {
95
- log('warn', message, ...args);
96
- },
97
- error(message, ...args) {
98
- log('error', message, ...args);
99
- },
100
- /**
101
- * Subscribe to log events.
102
- *
103
- * @param {LogListener} listener Listener invoked for every log entry that passes the current level filter.
104
- * @returns {function(): void} Cleanup function that removes the listener when called.
105
- */
106
- subscribe(listener) {
107
- if (typeof listener !== 'function') {
108
- return () => {};
109
- }
110
- listeners.add(listener);
111
- return () => listeners.delete(listener);
112
- },
113
- };
@@ -1,18 +0,0 @@
1
- import { logger } from './logger.js';
2
-
3
- /**
4
- * Call a function safely: if it's a function call it with provided args and
5
- * catch any errors so they don't bubble to the host application.
6
- *
7
- * @param {Function} fn
8
- * @param {...any} args
9
- */
10
- export function safeCall(fn, ...args) {
11
- if (typeof fn === 'function') {
12
- try {
13
- return fn(...args);
14
- } catch (e) {
15
- logger.error('safeCall caught error', e);
16
- }
17
- }
18
- }
@@ -1,87 +0,0 @@
1
- // Reusable validations for ports and environment variables
2
- import { normalizeImageName } from './imageNameUtils.js';
3
-
4
- /**
5
- * Validate a comma-separated list of port mappings in the form "host:container".
6
- * Examples of valid input: "8080:80, 3000:3000"
7
- *
8
- * @param {string} portInput - Comma-separated port mappings
9
- * @returns {boolean} True when all mappings are valid, false otherwise
10
- */
11
- export function validatePorts(portInput) {
12
- // Empty or whitespace-only input is considered valid (ports are optional)
13
- if (!portInput || !portInput.trim()) return true;
14
- const ports = portInput
15
- .split(',')
16
- .map((p) => p.trim())
17
- .filter(Boolean);
18
- if (ports.length === 0) return true;
19
- const invalid = ports.find((pair) => {
20
- const [host, cont] = pair.split(':');
21
- return !host || !cont || isNaN(Number(host)) || isNaN(Number(cont));
22
- });
23
- return !invalid;
24
- }
25
-
26
- /**
27
- * Validate environment variable input as comma-separated VAR=value pairs.
28
- *
29
- * When called with 3 arguments (envInput, imageName, imageProfiles), performs
30
- * contextual validation: checks that all required env vars for the given image
31
- * are present and non-empty. Returns an object { valid, errors, parsedEnv }.
32
- *
33
- * When called with 1 argument (legacy), returns a boolean for backward compatibility.
34
- *
35
- * @param {string} envInput - Comma-separated environment variable assignments
36
- * @param {string} [imageName] - Optional Docker image name for contextual validation
37
- * @param {Object} [imageProfiles] - Optional map of image profiles (from constants.js)
38
- * @returns {boolean|{valid: boolean, errors: string[], parsedEnv: Record<string,string>}}
39
- */
40
- export function validateEnvVars(envInput, imageName, imageProfiles) {
41
- const contextual = imageName !== undefined && imageProfiles !== undefined;
42
-
43
- // Parse: split on FIRST '=' only
44
- const parsedEnv = {};
45
- const syntaxErrors = [];
46
-
47
- if (envInput && envInput.trim()) {
48
- const vars = envInput
49
- .split(',')
50
- .map((v) => v.trim())
51
- .filter(Boolean);
52
- for (const v of vars) {
53
- const eqIdx = v.indexOf('=');
54
- if (eqIdx === -1) {
55
- syntaxErrors.push(`"${v}" is missing an '=' sign`);
56
- continue;
57
- }
58
- const varName = v.slice(0, eqIdx).trim();
59
- const varValue = v.slice(eqIdx + 1);
60
- if (!/^[A-Z_][A-Z0-9_]*$/i.test(varName)) {
61
- syntaxErrors.push(`"${varName}" is not a valid variable name`);
62
- continue;
63
- }
64
- parsedEnv[varName] = varValue;
65
- }
66
- }
67
-
68
- if (!contextual) {
69
- // Legacy: return boolean
70
- return syntaxErrors.length === 0;
71
- }
72
-
73
- // Contextual: also check required env vars from the profile
74
- const errors = [...syntaxErrors];
75
- const baseName = normalizeImageName(imageName);
76
- const profile = imageProfiles[baseName];
77
-
78
- if (profile && profile.requiredEnv && profile.requiredEnv.length) {
79
- for (const required of profile.requiredEnv) {
80
- if (!parsedEnv[required] || parsedEnv[required].trim() === '') {
81
- errors.push(`Missing required env var: ${required}`);
82
- }
83
- }
84
- }
85
-
86
- return { valid: errors.length === 0, errors, parsedEnv };
87
- }
@@ -1,76 +0,0 @@
1
- import { useState } from 'react';
2
- import { safeCall } from '../../helpers/safeCall.js';
3
- import {
4
- startContainer as svcStartContainer,
5
- stopContainer as svcStopContainer,
6
- restartContainer as svcRestartContainer,
7
- removeContainer as svcRemoveContainer,
8
- } from '../../helpers/dockerService/serviceComponents/containerActions.js';
9
-
10
- /**
11
- * Custom hook to manage container actions (start, stop, restart, remove).
12
- * Handles feedback messages and exposes helpers for each action.
13
- *
14
- * @param {Object} params
15
- * @param {Array} params.containers - List of containers
16
- * @param {Function} params.onAction - Callback after action is performed
17
- * @returns {Object} Action helpers and feedback state
18
- */
19
- export function useContainerActions({ containers, onAction }) {
20
- const [message, setMessage] = useState('');
21
- const [messageColor, setMessageColor] = useState('yellow');
22
-
23
- /**
24
- * Handles a generic container action and sets feedback.
25
- * @param {Function} actionFn - The async action function (start, stop, etc)
26
- * @param {string} actionLabel - Action label for feedback
27
- * @param {number} selected - Index of selected container
28
- * @param {Function} [stateCheck] - Optional function to check state before action
29
- */
30
- async function handleAction({ actionFn, actionLabel, selected, stateCheck }) {
31
- const container = containers[selected];
32
- if (!container) return;
33
- if (stateCheck) {
34
- const checkMsg = stateCheck(container);
35
- if (checkMsg) {
36
- setMessage(checkMsg);
37
- setMessageColor('yellow');
38
- return;
39
- }
40
- }
41
- setMessage(`${actionLabel} container...`);
42
- setMessageColor('yellow');
43
- try {
44
- await actionFn(container.id);
45
- setMessage(`${actionLabel} container successful.`);
46
- setMessageColor('green');
47
- safeCall(onAction);
48
- } catch (err) {
49
- setMessage(
50
- `Failed to ${actionLabel.toLowerCase()} container: ${err.message}`
51
- );
52
- setMessageColor('red');
53
- }
54
- }
55
-
56
- return {
57
- message,
58
- setMessage,
59
- messageColor,
60
- setMessageColor,
61
- handleAction,
62
- // Expose concrete actions so callers can use actions.startContainer(id)
63
- startContainer: async (id) => {
64
- return await svcStartContainer(id);
65
- },
66
- stopContainer: async (id) => {
67
- return await svcStopContainer(id);
68
- },
69
- restartContainer: async (id) => {
70
- return await svcRestartContainer(id);
71
- },
72
- removeContainer: async (id) => {
73
- return await svcRemoveContainer(id);
74
- },
75
- };
76
- }