cdd-cli 3.1.7 → 3.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.
Files changed (136) hide show
  1. package/.eslintrc.json +1 -1
  2. package/.github/workflows/ci.yml +3 -0
  3. package/.github/workflows/docs.yml +8 -1
  4. package/CHANGELOG.md +40 -0
  5. package/CONTRIBUTING.md +28 -15
  6. package/README.md +56 -88
  7. package/babel.config.cjs +18 -1
  8. package/coverage/clover.xml +604 -0
  9. package/coverage/coverage-final.json +17 -0
  10. package/coverage/lcov-report/base.css +224 -0
  11. package/coverage/lcov-report/block-navigation.js +87 -0
  12. package/coverage/lcov-report/favicon.png +0 -0
  13. package/coverage/lcov-report/helpers/constants.js.html +235 -0
  14. package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +211 -0
  15. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +853 -0
  16. package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +289 -0
  17. package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +131 -0
  18. package/coverage/lcov-report/helpers/index.html +176 -0
  19. package/coverage/lcov-report/helpers/logger.js.html +424 -0
  20. package/coverage/lcov-report/helpers/safeCall.js.html +139 -0
  21. package/coverage/lcov-report/helpers/validationHelpers.js.html +394 -0
  22. package/coverage/lcov-report/hooks/creation/index.html +146 -0
  23. package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +292 -0
  24. package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +478 -0
  25. package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +238 -0
  26. package/coverage/lcov-report/hooks/debug/index.html +116 -0
  27. package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +172 -0
  28. package/coverage/lcov-report/hooks/index.html +161 -0
  29. package/coverage/lcov-report/hooks/navigation/index.html +116 -0
  30. package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +160 -0
  31. package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +415 -0
  32. package/coverage/lcov-report/hooks/useControls.js.html +685 -0
  33. package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +202 -0
  34. package/coverage/lcov-report/hooks/useExitHandler.js.html +283 -0
  35. package/coverage/lcov-report/index.html +191 -0
  36. package/coverage/lcov-report/prettify.css +1 -0
  37. package/coverage/lcov-report/prettify.js +2 -0
  38. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  39. package/coverage/lcov-report/sorter.js +210 -0
  40. package/coverage/lcov.info +1216 -0
  41. package/dist/components/ContainerRow.js +5 -78
  42. package/dist/components/LogViewer.js +4 -9
  43. package/dist/helpers/containerOptionsBuilder.js +58 -0
  44. package/dist/hooks/creation/useContainerCreation.js +16 -1
  45. package/dist/hooks/debug/useDebugLogs.js +46 -0
  46. package/dist/hooks/navigation/useContainerSelection.js +36 -0
  47. package/dist/hooks/useContainerCommandRouter.js +151 -0
  48. package/dist/{helpers/actionHelpers.js → hooks/useContainerStats.js} +76 -78
  49. package/dist/hooks/useControls.js +83 -342
  50. package/dist/hooks/useEraseConfirmation.js +47 -0
  51. package/dist/hooks/useExitHandler.js +68 -0
  52. package/jest.config.cjs +1 -1
  53. package/package.json +2 -3
  54. package/src/components/ContainerRow.jsx +3 -38
  55. package/src/components/LogViewer.jsx +4 -10
  56. package/src/helpers/constants.js +20 -0
  57. package/src/helpers/containerOptionsBuilder.js +42 -0
  58. package/src/helpers/dockerService/serviceComponents/containerActions.js +55 -8
  59. package/src/helpers/validationHelpers.js +75 -18
  60. package/src/hooks/creation/useContainerCreation.js +34 -4
  61. package/src/hooks/debug/useDebugLogs.js +29 -0
  62. package/src/hooks/navigation/useContainerSelection.js +25 -0
  63. package/src/hooks/useContainerCommandRouter.js +110 -0
  64. package/src/hooks/useContainerStats.js +44 -0
  65. package/src/hooks/useControls.js +75 -274
  66. package/src/hooks/useEraseConfirmation.js +39 -0
  67. package/src/hooks/useExitHandler.js +66 -0
  68. package/test/containerActions.test.js +104 -13
  69. package/test/containerOptionsBuilder.test.js +36 -0
  70. package/test/useContainerCreation.dom.test.js +46 -2
  71. package/test/useControls.dom.test.js +117 -0
  72. package/test/validationHelpers.test.js +51 -0
  73. package/.babelrc +0 -6
  74. package/FIXES_APPLIED.md +0 -419
  75. package/SECURITY.md +0 -5
  76. package/dist/hooks/useLogsStream.js +0 -41
  77. package/docs/_config.yml +0 -13
  78. package/docs/components_ContainerCreationPrompt.jsx.html +0 -114
  79. package/docs/components_ContainerRow.jsx.html +0 -151
  80. package/docs/components_ContainerSection.jsx.html +0 -75
  81. package/docs/components_Footer.jsx.html +0 -66
  82. package/docs/components_Header.jsx.html +0 -84
  83. package/docs/components_LogViewer.jsx.html +0 -91
  84. package/docs/components_MessageFeedback.jsx.html +0 -76
  85. package/docs/components_PromptField.jsx.html +0 -99
  86. package/docs/components_StatsBar.jsx.html +0 -105
  87. package/docs/components_UsageMenu.jsx.html +0 -73
  88. package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
  89. package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
  90. package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
  91. package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
  92. package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
  93. package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
  94. package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
  95. package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
  96. package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
  97. package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
  98. package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
  99. package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
  100. package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
  101. package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
  102. package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
  103. package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
  104. package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
  105. package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
  106. package/docs/global.html +0 -6145
  107. package/docs/helpers_actionHelpers.js.html +0 -94
  108. package/docs/helpers_constants.js.html +0 -81
  109. package/docs/helpers_dockerService_dockerService.js.html +0 -79
  110. package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +0 -153
  111. package/docs/helpers_dockerService_serviceComponents_containerList.js.html +0 -81
  112. package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +0 -85
  113. package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +0 -107
  114. package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +0 -82
  115. package/docs/helpers_exitWithMessage.js.html +0 -77
  116. package/docs/helpers_safeCall.js.html +0 -68
  117. package/docs/helpers_validationHelpers.js.html +0 -96
  118. package/docs/hooks_creation_useContainerActions.js.html +0 -120
  119. package/docs/hooks_creation_useContainerCreation.js.html +0 -152
  120. package/docs/hooks_creation_useLogsViewer.js.html +0 -102
  121. package/docs/hooks_useControls.js.html +0 -298
  122. package/docs/hooks_useLogsStream.js.html +0 -82
  123. package/docs/index.html +0 -65
  124. package/docs/index.js.html +0 -65
  125. package/docs/module-index.html +0 -171
  126. package/docs/scripts/linenumber.js +0 -25
  127. package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
  128. package/docs/scripts/prettify/lang-css.js +0 -2
  129. package/docs/scripts/prettify/prettify.js +0 -28
  130. package/docs/styles/jsdoc-default.css +0 -358
  131. package/docs/styles/prettify-jsdoc.css +0 -111
  132. package/docs/styles/prettify-tomorrow.css +0 -132
  133. package/esbuild.config.cjs +0 -11
  134. package/src/helpers/actionHelpers.js +0 -52
  135. package/src/hooks/useLogsStream.js +0 -31
  136. package/test/actionHelpers.test.js +0 -106
@@ -0,0 +1,36 @@
1
+ let buildContainerOptions;
2
+
3
+ beforeAll(async () => {
4
+ const mod = await import('../src/helpers/containerOptionsBuilder.js');
5
+ buildContainerOptions = mod.buildContainerOptions;
6
+ });
7
+
8
+ describe('buildContainerOptions', () => {
9
+ test('basic image only sets Tty', () => {
10
+ const opts = buildContainerOptions({ imageName: 'nginx' });
11
+ expect(opts.Tty).toBe(true);
12
+ expect(opts.Env).toBeUndefined();
13
+ });
14
+
15
+ test('env var with value containing = is preserved intact', () => {
16
+ const opts = buildContainerOptions({ imageName: 'nginx', envInput: 'TOKEN=a=b' });
17
+ expect(opts.Env).toEqual(['TOKEN=a=b']);
18
+ });
19
+
20
+ test('multiple env vars with = in values are all preserved', () => {
21
+ const opts = buildContainerOptions({ imageName: 'nginx', envInput: 'TOKEN=a=b,DSN=user:pass@host/db?opt=1' });
22
+ expect(opts.Env).toContain('TOKEN=a=b');
23
+ expect(opts.Env).toContain('DSN=user:pass@host/db?opt=1');
24
+ });
25
+
26
+ test('port input builds ExposedPorts and PortBindings', () => {
27
+ const opts = buildContainerOptions({ imageName: 'nginx', portInput: '8080:80' });
28
+ expect(opts.ExposedPorts).toEqual({ '80/tcp': {} });
29
+ expect(opts.HostConfig.PortBindings['80/tcp']).toEqual([{ HostPort: '8080' }]);
30
+ });
31
+
32
+ test('container name is included when provided', () => {
33
+ const opts = buildContainerOptions({ imageName: 'nginx', containerName: 'my-nginx' });
34
+ expect(opts.name).toBe('my-nginx');
35
+ });
36
+ });
@@ -1,9 +1,13 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
1
4
  import React, { useEffect } from 'react';
2
5
  import { render, act } from '@testing-library/react';
3
6
  import { useContainerCreation } from '../src/hooks/creation/useContainerCreation.js';
7
+ import { IMAGE_PROFILES } from '../src/helpers/constants.js';
4
8
 
5
- function HookTester({ onCreate, onCancel, dbImages, expose }) {
6
- const hook = useContainerCreation({ onCreate, onCancel, dbImages });
9
+ function HookTester({ onCreate, onCancel, dbImages, imageProfiles, expose }) {
10
+ const hook = useContainerCreation({ onCreate, onCancel, dbImages, imageProfiles });
7
11
  // keep exposing the latest hook values on every render
8
12
  useEffect(() => {
9
13
  if (expose) expose.current = hook;
@@ -94,4 +98,44 @@ describe('useContainerCreation (DOM render)', () => {
94
98
  expect(created.length).toBe(1);
95
99
  expect(created[0]).toEqual({ imageName: 'redis', containerName: '', portInput: '', envInput: 'FOO=bar' });
96
100
  });
101
+
102
+ test('step 3 with mysql image: missing MYSQL_ROOT_PASSWORD blocks creation', () => {
103
+ const created = [];
104
+ const expose = { current: null };
105
+
106
+ render(<HookTester onCreate={(d) => created.push(d)} onCancel={() => {}} dbImages={[]} imageProfiles={IMAGE_PROFILES} expose={expose} />);
107
+
108
+ // advance to step 3 (env) with mysql image
109
+ act(() => { expose.current.setImageName('mysql:8'); });
110
+ act(() => { expose.current.nextStep(); }); // step 0 → 1
111
+ act(() => { expose.current.nextStep(); }); // step 1 → 2
112
+ act(() => { expose.current.nextStep(); }); // step 2 → 3
113
+
114
+ expect(expose.current.step).toBe(3);
115
+
116
+ // try to advance without required env var
117
+ act(() => { expose.current.nextStep(); });
118
+
119
+ expect(expose.current.step).toBe(3); // still blocked
120
+ expect(expose.current.message).toMatch(/MYSQL_ROOT_PASSWORD/i);
121
+ expect(created.length).toBe(0);
122
+ });
123
+
124
+ test('step 3 with mysql image: valid env advances and calls onCreate', () => {
125
+ const created = [];
126
+ const expose = { current: null };
127
+
128
+ render(<HookTester onCreate={(d) => created.push(d)} onCancel={() => {}} dbImages={[]} imageProfiles={IMAGE_PROFILES} expose={expose} />);
129
+
130
+ act(() => { expose.current.setImageName('mysql:8'); });
131
+ act(() => { expose.current.nextStep(); });
132
+ act(() => { expose.current.nextStep(); });
133
+ act(() => { expose.current.nextStep(); }); // at step 3
134
+
135
+ act(() => { expose.current.setEnvInput('MYSQL_ROOT_PASSWORD=secret'); });
136
+ act(() => { expose.current.nextStep(); }); // should call onCreate
137
+
138
+ expect(created.length).toBe(1);
139
+ expect(created[0].imageName).toBe('mysql:8');
140
+ });
97
141
  });
@@ -0,0 +1,117 @@
1
+ /**
2
+ * @jest-environment jsdom
3
+ */
4
+ import React, { useEffect } from 'react';
5
+ import { render, act } from '@testing-library/react';
6
+ import { jest } from '@jest/globals';
7
+
8
+ // Mock ink so that useInput and useApp are no-ops (require real TTY in production)
9
+ await jest.unstable_mockModule('ink', () => ({
10
+ useInput: () => {},
11
+ useApp: () => ({ exit: () => {} }),
12
+ }));
13
+
14
+ // Mock containerActions — expose all named exports so dependents don't break
15
+ const mockSvcCreateContainer = jest.fn();
16
+ await jest.unstable_mockModule(
17
+ '../src/helpers/dockerService/serviceComponents/containerActions.js',
18
+ () => ({
19
+ createContainer: mockSvcCreateContainer,
20
+ removeContainer: jest.fn().mockResolvedValue(undefined),
21
+ startContainer: jest.fn().mockResolvedValue(undefined),
22
+ stopContainer: jest.fn().mockResolvedValue(undefined),
23
+ restartContainer: jest.fn().mockResolvedValue(undefined),
24
+ })
25
+ );
26
+
27
+ // Mock getLogsStream to avoid side effects
28
+ await jest.unstable_mockModule(
29
+ '../src/helpers/dockerService/serviceComponents/containerLogs.js',
30
+ () => ({ getLogsStream: () => {} })
31
+ );
32
+
33
+ const { useControls } = await import('../src/hooks/useControls.js');
34
+
35
+ function HookTester({ containers, expose }) {
36
+ const hook = useControls(containers);
37
+ useEffect(() => {
38
+ if (expose) expose.current = hook;
39
+ });
40
+ return null;
41
+ }
42
+
43
+ // Helper: advance creation wizard to the final step and trigger onCreate
44
+ async function completeCreationWizard(expose, imageName = 'nginx') {
45
+ act(() => { expose.current.creation.setImageName(imageName); });
46
+ act(() => { expose.current.creation.nextStep(); }); // step 0 → 1
47
+ act(() => { expose.current.creation.nextStep(); }); // step 1 → 2
48
+ act(() => { expose.current.creation.nextStep(); }); // step 2 → 3
49
+ // step 3: call nextStep to trigger onCreate (async)
50
+ await act(async () => { expose.current.creation.nextStep(); });
51
+ }
52
+
53
+ describe('useControls (FR6 — port mapping in success message)', () => {
54
+ beforeEach(() => {
55
+ mockSvcCreateContainer.mockReset();
56
+ });
57
+
58
+ test('success message includes port mapping after container creation', async () => {
59
+ const ports = [
60
+ { containerPort: '3306', hostPort: '3306', protocol: 'tcp', source: 'auto' },
61
+ ];
62
+ mockSvcCreateContainer.mockResolvedValue({ id: 'cid-abc', ports });
63
+
64
+ const expose = { current: null };
65
+ render(<HookTester containers={[]} expose={expose} />);
66
+
67
+ await completeCreationWizard(expose, 'nginx');
68
+
69
+ expect(expose.current.actions.message).toBe(
70
+ 'Created container cid-abc | Ports: 3306→3306/tcp'
71
+ );
72
+ expect(expose.current.actions.messageColor).toBe('green');
73
+ });
74
+
75
+ test('success message has no port section when ports array is empty', async () => {
76
+ mockSvcCreateContainer.mockResolvedValue({ id: 'cid-xyz', ports: [] });
77
+
78
+ const expose = { current: null };
79
+ render(<HookTester containers={[]} expose={expose} />);
80
+
81
+ await completeCreationWizard(expose, 'alpine');
82
+
83
+ expect(expose.current.actions.message).toBe('Created container cid-xyz');
84
+ expect(expose.current.actions.messageColor).toBe('green');
85
+ });
86
+
87
+ test('success message includes multiple port mappings', async () => {
88
+ const ports = [
89
+ { containerPort: '80', hostPort: '8080', protocol: 'tcp', source: 'user' },
90
+ { containerPort: '443', hostPort: '8443', protocol: 'tcp', source: 'user' },
91
+ ];
92
+ mockSvcCreateContainer.mockResolvedValue({ id: 'cid-multi', ports });
93
+
94
+ const expose = { current: null };
95
+ render(<HookTester containers={[]} expose={expose} />);
96
+
97
+ await completeCreationWizard(expose, 'nginx');
98
+
99
+ expect(expose.current.actions.message).toBe(
100
+ 'Created container cid-multi | Ports: 8080→80/tcp, 8443→443/tcp'
101
+ );
102
+ });
103
+
104
+ test('error message is shown when svcCreateContainer rejects', async () => {
105
+ mockSvcCreateContainer.mockRejectedValue(new Error('pull access denied'));
106
+
107
+ const expose = { current: null };
108
+ render(<HookTester containers={[]} expose={expose} />);
109
+
110
+ await completeCreationWizard(expose, 'badimage');
111
+
112
+ expect(expose.current.actions.message).toBe(
113
+ 'Error creating container: pull access denied'
114
+ );
115
+ expect(expose.current.actions.messageColor).toBe('red');
116
+ });
117
+ });
@@ -1,10 +1,13 @@
1
1
  let validatePorts;
2
2
  let validateEnvVars;
3
+ let IMAGE_PROFILES;
3
4
 
4
5
  beforeAll(async () => {
5
6
  const mod = await import('../src/helpers/validationHelpers.js');
6
7
  validatePorts = mod.validatePorts;
7
8
  validateEnvVars = mod.validateEnvVars;
9
+ const constants = await import('../src/helpers/constants.js');
10
+ IMAGE_PROFILES = constants.IMAGE_PROFILES;
8
11
  });
9
12
 
10
13
  describe('validatePorts', () => {
@@ -58,3 +61,51 @@ describe('validateEnvVars', () => {
58
61
  expect(validateEnvVars('MY-VAR=value')).toBe(false);
59
62
  });
60
63
  });
64
+
65
+ describe('validateEnvVars — contextual validation with IMAGE_PROFILES', () => {
66
+ test('mysql:8 — missing MYSQL_ROOT_PASSWORD is invalid', () => {
67
+ const result = validateEnvVars('FOO=bar', 'mysql:8', IMAGE_PROFILES);
68
+ expect(result.valid).toBe(false);
69
+ expect(result.errors.some(e => e.includes('MYSQL_ROOT_PASSWORD'))).toBe(true);
70
+ });
71
+
72
+ test('mysql:8 — with MYSQL_ROOT_PASSWORD is valid', () => {
73
+ const result = validateEnvVars('MYSQL_ROOT_PASSWORD=secret', 'mysql:8', IMAGE_PROFILES);
74
+ expect(result.valid).toBe(true);
75
+ expect(result.errors).toHaveLength(0);
76
+ });
77
+
78
+ test('docker.io/library/postgres:16 — missing POSTGRES_PASSWORD is invalid', () => {
79
+ const result = validateEnvVars('', 'docker.io/library/postgres:16', IMAGE_PROFILES);
80
+ expect(result.valid).toBe(false);
81
+ expect(result.errors.some(e => e.includes('POSTGRES_PASSWORD'))).toBe(true);
82
+ });
83
+
84
+ test('docker.io/library/postgres:16 — with POSTGRES_PASSWORD is valid', () => {
85
+ const result = validateEnvVars('POSTGRES_PASSWORD=pass', 'docker.io/library/postgres:16', IMAGE_PROFILES);
86
+ expect(result.valid).toBe(true);
87
+ });
88
+
89
+ test('TOKEN=a=b is preserved (split on first = only)', () => {
90
+ const result = validateEnvVars('TOKEN=a=b', 'nginx', IMAGE_PROFILES);
91
+ expect(result.valid).toBe(true);
92
+ expect(result.parsedEnv['TOKEN']).toBe('a=b');
93
+ });
94
+
95
+ test('image not in profiles — only syntactic validation applies', () => {
96
+ const result = validateEnvVars('FOO=bar', 'nginx', IMAGE_PROFILES);
97
+ expect(result.valid).toBe(true);
98
+ expect(result.parsedEnv['FOO']).toBe('bar');
99
+ });
100
+
101
+ test('empty input for image not in profiles — valid', () => {
102
+ const result = validateEnvVars('', 'nginx', IMAGE_PROFILES);
103
+ expect(result.valid).toBe(true);
104
+ expect(result.parsedEnv).toEqual({});
105
+ });
106
+
107
+ test('syntactically invalid var is still rejected', () => {
108
+ const result = validateEnvVars('INVALID-VAR=x', 'nginx', IMAGE_PROFILES);
109
+ expect(result.valid).toBe(false);
110
+ });
111
+ });
package/.babelrc DELETED
@@ -1,6 +0,0 @@
1
- {
2
- "presets": [
3
- ["@babel/preset-env", { "modules": false }],
4
- "@babel/preset-react"
5
- ]
6
- }
package/FIXES_APPLIED.md DELETED
@@ -1,419 +0,0 @@
1
- # Applied Fixes - CDD CLI
2
-
3
- **Date:** 2025-10-17
4
- **Project:** CDD-CLI (CLI Docker Dashboard)
5
-
6
- ---
7
-
8
- ## Fixes Summary
9
-
10
- A total of 11 critical fixes were applied to address security, stability, and portability issues identified during the code audit.
11
-
12
- ---
13
-
14
- ## 1. APPLIED CRITICAL FIXES
15
-
16
- ### ✅ 1.1. Import Order Fix
17
-
18
- **File:** `src/helpers/dockerService/serviceComponents/containerActions.js`
19
-
20
- **Issue:** Imports were declared after exports.
21
-
22
- **Applied fix:**
23
- ```javascript
24
- // BEFORE (incorrect):
25
- export async function removeContainer(containerId) { ... }
26
- import { docker } from "../dockerService";
27
-
28
- // AFTER (correct):
29
- import { docker } from "../dockerService";
30
- import { imageExists, pullImage } from "./imageUtils.js";
31
- export async function removeContainer(containerId) { ... }
32
- ```
33
-
34
- **Impact:** Removes potential reference errors and follows ES6 standards.
35
-
36
- ---
37
-
38
- ### ✅ 1.2. Optional Ports in Container Creation
39
-
40
- **File:** `src/hooks/creation/useContainerCreation.js`
41
-
42
- **Issue:** Users were forced to specify ports, but many containers don’t need them.
43
-
44
- **Applied fix:**
45
- ```javascript
46
- // BEFORE:
47
- if (!portInput.trim()) {
48
- setMessage("You must specify at least one port to expose");
49
- return;
50
- }
51
-
52
- // AFTER:
53
- // Ports are optional — only validate if provided
54
- if (portInput.trim() && !validatePorts(portInput)) {
55
- setMessage("Port format must be host:container...");
56
- return;
57
- }
58
- ```
59
-
60
- **Impact:** Allows creating containers without exposed ports (workers, internal services, etc.).
61
-
62
- ---
63
-
64
- ### ✅ 1.3. Error Handling in getLogsStream
65
-
66
- **File:** `src/helpers/dockerService/serviceComponents/containerLogs.js`
67
-
68
- **Issue:** No try-catch to handle synchronous exceptions.
69
-
70
- **Applied fix:**
71
- ```javascript
72
- export function getLogsStream(containerId, onData, onEnd, onError) {
73
- try {
74
- const container = docker.getContainer(containerId);
75
- // ... rest of the code
76
- } catch (err) {
77
- onError?.(err);
78
- }
79
- }
80
- ```
81
-
82
- **Impact:** Prevents crashes when the container doesn’t exist or connection errors occur.
83
-
84
- ---
85
-
86
- ### ✅ 1.4. Cross-Platform Docker Socket Configuration
87
-
88
- **File:** `src/helpers/dockerService/dockerService.js`
89
-
90
- **Issue:** Hardcoded path `/var/run/docker.sock` only works on Linux/macOS.
91
-
92
- **Applied fix:**
93
- ```javascript
94
- // BEFORE:
95
- const docker = new Docker({ socketPath: "/var/run/docker.sock" });
96
-
97
- // AFTER:
98
- // Use default configuration that automatically handles:
99
- // - /var/run/docker.sock on Linux/macOS
100
- // - //./pipe/docker_engine on Windows
101
- const docker = new Docker();
102
- ```
103
-
104
- **Impact:** Works on Windows, Linux, and macOS without manual changes.
105
-
106
- ---
107
-
108
- ### ✅ 1.5. Race Condition Prevention in Stats
109
-
110
- **File:** `src/components/ContainerRow.jsx`
111
-
112
- **Issue:** State updates on unmounted components caused memory leaks.
113
-
114
- **Applied fix:**
115
- ```javascript
116
- useEffect(() => {
117
- if (state !== "running") return;
118
-
119
- let isMounted = true; // ← Mount flag
120
-
121
- const fetchStats = async () => {
122
- try {
123
- const s = await getStats(id);
124
- if (isMounted) { // ← Only update if mounted
125
- setStats(s);
126
- }
127
- } catch (err) {
128
- if (isMounted) {
129
- setStatsError("Error fetching stats");
130
- }
131
- }
132
- };
133
-
134
- fetchStats();
135
- const timer = setInterval(fetchStats, 1500);
136
-
137
- return () => {
138
- isMounted = false; // ← Cleanup
139
- clearInterval(timer);
140
- };
141
- }, [id, state]);
142
- ```
143
-
144
- **Impact:** Eliminates React warnings and prevents memory leaks.
145
-
146
- ---
147
-
148
- ### ✅ 1.6. Environment Variables Validation
149
-
150
- **File:** `src/helpers/validationHelpers.js`
151
-
152
- **Issue:** `validateEnvVars` always returned `true`.
153
-
154
- **Applied fix:**
155
- ```javascript
156
- export function validateEnvVars(envInput) {
157
- if (!envInput || !envInput.trim()) return true; // Empty is valid
158
-
159
- const vars = envInput.split(",").map(v => v.trim()).filter(Boolean);
160
- const invalid = vars.find(v => {
161
- const parts = v.split("=");
162
- if (parts.length < 2) return true; // Must be VAR=value
163
- const varName = parts[0].trim();
164
- // Names must be alphanumeric with underscores
165
- if (!/^[A-Z_][A-Z0-9_]*$/i.test(varName)) return true;
166
- return false;
167
- });
168
-
169
- return !invalid;
170
- }
171
- ```
172
-
173
- **Impact:** Detects malformed variables before sending them to Docker.
174
-
175
- ---
176
-
177
- ### ✅ 1.7. CPU Stats Calculation Fix
178
-
179
- **File:** `src/helpers/dockerService/serviceComponents/containerStats.js`
180
-
181
- **Issue:** Not normalized by number of CPUs, producing wrong values on multi-core hosts.
182
-
183
- **Applied fix:**
184
- ```javascript
185
- // Determine number of CPUs
186
- const numCpus = stream.cpu_stats.online_cpus ||
187
- stream.cpu_stats.cpu_usage.percpu_usage?.length || 1;
188
-
189
- // Compute normalized percentage
190
- const cpuPercent = systemDelta > 0
191
- ? ((cpuDelta / systemDelta) * numCpus * 100)
192
- : 0;
193
- ```
194
-
195
- **Impact:** Correct CPU stats on multi-core systems.
196
-
197
- ---
198
-
199
- ### ✅ 1.8. Timeouts in Docker Operations
200
-
201
- **File:** `src/helpers/dockerService/serviceComponents/containerActions.js`
202
-
203
- **Issue:** Operations without timeouts could freeze the UI indefinitely.
204
-
205
- **Applied fix:**
206
- ```javascript
207
- function withTimeout(promise, ms = 30000) {
208
- return Promise.race([
209
- promise,
210
- new Promise((_, reject) =>
211
- setTimeout(() => reject(new Error('Operation timed out')), ms)
212
- )
213
- ]);
214
- }
215
-
216
- export async function startContainer(containerId) {
217
- const container = docker.getContainer(containerId);
218
- await withTimeout(container.start(), 30000);
219
- }
220
- ```
221
-
222
- **Impact:** Prevents frozen UI during long or failing operations.
223
-
224
- ---
225
-
226
- ### ✅ 1.9. In-Memory Log Limit
227
-
228
- **File:** `src/hooks/useControls.js`
229
-
230
- **Issue:** Logs accumulated indefinitely causing a memory leak.
231
-
232
- **Applied fix:**
233
- ```javascript
234
- getLogsStream(
235
- containers[selected].id,
236
- (data) => logsViewer.setLogs((prev) => {
237
- const newLogs = [...prev, ...data.split("\n").filter(Boolean)];
238
- // Limit to last 1000 lines
239
- return newLogs.slice(-1000);
240
- }),
241
- // ...
242
- );
243
- ```
244
-
245
- **Impact:** Prevents memory leaks on long-running log streams.
246
-
247
- ---
248
-
249
- ### ✅ 1.10. Duplicate Message Fix
250
-
251
- **File:** `src/helpers/actionHelpers.js`
252
-
253
- **Issue:** Success message was identical to the start message.
254
-
255
- **Applied fix:**
256
- ```javascript
257
- // BEFORE:
258
- setMessage(`${actionLabel} container...`); // start
259
- await actionFn(c.id);
260
- setMessage(`${actionLabel} container...`); // success (duplicate)
261
-
262
- // AFTER:
263
- setMessage(`${actionLabel} container...`); // start
264
- await actionFn(c.id);
265
- setMessage(`${actionLabel} container completed successfully`); // success
266
- ```
267
-
268
- **Impact:** Clear feedback that the operation completed.
269
-
270
- ---
271
-
272
- ### ✅ 1.11. Container Names Validation
273
-
274
- **File:** `src/helpers/dockerService/serviceComponents/containerList.js`
275
-
276
- **Issue:** Didn’t validate when `Names` was empty.
277
-
278
- **Applied fix:**
279
- ```javascript
280
- // BEFORE:
281
- name: container.Names[0].replace("/", ""),
282
-
283
- // AFTER:
284
- name: (container.Names && container.Names[0] || 'Unknown').replace("/", ""),
285
- ```
286
-
287
- **Impact:** Prevents crashes when Docker returns unexpected data.
288
-
289
- ---
290
-
291
- ## 2. TEST IMPROVEMENTS
292
-
293
- ### ✅ Tests for validateEnvVars
294
-
295
- **File:** `test/validationHelpers.test.js`
296
-
297
- **New tests added:**
298
- - Empty input is valid
299
- - Valid single env var
300
- - Valid multiple env vars
301
- - Valid env var with underscores
302
- - Invalid env var without equals sign
303
- - Invalid env var with invalid name
304
- - Invalid env var with special characters in name
305
-
306
- **Result:**
307
- ```
308
- Test Suites: 1 passed, 1 total
309
- Tests: 12 passed, 12 total (previously: 5)
310
- ```
311
-
312
- ---
313
-
314
- ## 3. SECURITY ANALYSIS
315
-
316
- ### ✅ CodeQL Security Scan
317
-
318
- **Result:** ✅ 0 vulnerabilities found
319
-
320
- ```
321
- Analysis Result for 'javascript'. Found 0 alert(s):
322
- - javascript: No alerts found.
323
- ```
324
-
325
- ---
326
-
327
- ## 4. BUILD VERIFICATION
328
-
329
- ### ✅ Successful Build
330
-
331
- ```bash
332
- $ npm run build
333
- Successfully compiled 28 files with Babel (815ms).
334
- ```
335
-
336
- ---
337
-
338
- ## 5. OVERALL IMPACT OF FIXES
339
-
340
- ### Security
341
- - ✅ No security vulnerabilities detected
342
- - ✅ Improved input validation
343
- - ✅ Robust error handling
344
-
345
- ### Stability
346
- - ✅ Memory leak prevention
347
- - ✅ Race condition prevention
348
- - ✅ Crash prevention due to unhandled errors
349
- - ✅ Timeouts for potentially long operations
350
-
351
- ### Portability
352
- - ✅ Windows compatibility
353
- - ✅ Linux compatibility
354
- - ✅ macOS compatibility
355
-
356
- ### User Experience
357
- - ✅ Optional ports in container creation
358
- - ✅ Clearer feedback messages
359
- - ✅ Correct CPU statistics
360
- - ✅ Better error handling with informative messages
361
-
362
- ### Code Quality
363
- - ✅ ES6 compliant
364
- - ✅ Better test coverage (5 → 12 tests)
365
- - ✅ More maintainable code
366
-
367
- ---
368
-
369
- ## 6. MODIFIED FILES
370
-
371
- 1. `src/helpers/dockerService/serviceComponents/containerActions.js`
372
- 2. `src/hooks/creation/useContainerCreation.js`
373
- 3. `src/helpers/dockerService/serviceComponents/containerLogs.js`
374
- 4. `src/helpers/dockerService/dockerService.js`
375
- 5. `src/components/ContainerRow.jsx`
376
- 6. `src/helpers/validationHelpers.js`
377
- 7. `src/helpers/dockerService/serviceComponents/containerStats.js`
378
- 8. `src/hooks/useControls.js`
379
- 9. `src/helpers/actionHelpers.js`
380
- 10. `src/helpers/dockerService/serviceComponents/containerList.js`
381
- 11. `test/validationHelpers.test.js`
382
-
383
- ---
384
-
385
- ## 7. FUTURE RECOMMENDATIONS
386
-
387
- While the critical issues have been addressed, the internal audit yielded additional recommendations for future improvements:
388
-
389
- ### Medium Priority
390
- - Add more unit tests
391
- - Implement PropTypes or migrate to TypeScript
392
- - Extract magic numbers into constants
393
- - Improve the logging system
394
-
395
- ### Low Priority
396
- - Implement i18n (internationalization)
397
- - Improve JSDoc documentation
398
- - Consider websockets for real-time updates
399
- - Implement retry logic for Docker reconnection
400
-
401
- ---
402
-
403
- ## 8. CONCLUSION
404
-
405
- We applied 11 critical fixes that significantly improve:
406
-
407
- - Security: 0 vulnerabilities
408
- - Stability: Prevention of memory leaks and race conditions
409
- - Portability: Works on Windows, Linux, and macOS
410
- - Quality: +140% more tests (5 → 12)
411
-
412
- All fixes have been tested and verified via:
413
- - ✅ Unit tests (12/12 passing)
414
- - ✅ Successful build
415
- - ✅ CodeQL security analysis (0 alerts)
416
-
417
- ---
418
-
419
- **End of fixes document**