cdd-cli 3.1.6 → 3.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +1 -1
- package/.github/workflows/ci.yml +3 -0
- package/.github/workflows/docs.yml +8 -1
- package/CHANGELOG.md +23 -0
- package/CONTRIBUTING.md +28 -15
- package/README.md +56 -88
- package/babel.config.cjs +18 -1
- package/dist/components/ContainerRow.js +5 -78
- package/dist/components/LogViewer.js +4 -9
- package/dist/helpers/containerOptionsBuilder.js +58 -0
- package/dist/hooks/creation/useContainerCreation.js +16 -1
- package/dist/hooks/debug/useDebugLogs.js +46 -0
- package/dist/hooks/navigation/useContainerSelection.js +36 -0
- package/dist/hooks/useContainerCommandRouter.js +151 -0
- package/dist/{helpers/actionHelpers.js → hooks/useContainerStats.js} +76 -78
- package/dist/hooks/useControls.js +83 -342
- package/dist/hooks/useEraseConfirmation.js +47 -0
- package/dist/hooks/useExitHandler.js +68 -0
- package/jest.config.cjs +1 -1
- package/package.json +4 -5
- package/src/components/ContainerRow.jsx +3 -38
- package/src/components/LogViewer.jsx +4 -10
- package/src/helpers/containerOptionsBuilder.js +42 -0
- package/src/hooks/creation/useContainerCreation.js +15 -0
- package/src/hooks/debug/useDebugLogs.js +29 -0
- package/src/hooks/navigation/useContainerSelection.js +25 -0
- package/src/hooks/useContainerCommandRouter.js +110 -0
- package/src/hooks/useContainerStats.js +44 -0
- package/src/hooks/useControls.js +68 -272
- package/src/hooks/useEraseConfirmation.js +39 -0
- package/src/hooks/useExitHandler.js +66 -0
- package/test/useContainerCreation.dom.test.js +3 -0
- package/.babelrc +0 -6
- package/FIXES_APPLIED.md +0 -419
- package/SECURITY.md +0 -5
- package/dist/hooks/useLogsStream.js +0 -41
- package/docs/_config.yml +0 -13
- package/docs/components_ContainerCreationPrompt.jsx.html +0 -114
- package/docs/components_ContainerRow.jsx.html +0 -151
- package/docs/components_ContainerSection.jsx.html +0 -75
- package/docs/components_Footer.jsx.html +0 -66
- package/docs/components_Header.jsx.html +0 -84
- package/docs/components_LogViewer.jsx.html +0 -91
- package/docs/components_MessageFeedback.jsx.html +0 -76
- package/docs/components_PromptField.jsx.html +0 -99
- package/docs/components_StatsBar.jsx.html +0 -105
- package/docs/components_UsageMenu.jsx.html +0 -73
- package/docs/fonts/OpenSans-Bold-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Bold-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Bold-webfont.woff +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-BoldItalic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-BoldItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Italic-webfont.svg +0 -1830
- package/docs/fonts/OpenSans-Italic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Light-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Light-webfont.svg +0 -1831
- package/docs/fonts/OpenSans-Light-webfont.woff +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.eot +0 -0
- package/docs/fonts/OpenSans-LightItalic-webfont.svg +0 -1835
- package/docs/fonts/OpenSans-LightItalic-webfont.woff +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.eot +0 -0
- package/docs/fonts/OpenSans-Regular-webfont.svg +0 -1831
- package/docs/fonts/OpenSans-Regular-webfont.woff +0 -0
- package/docs/global.html +0 -6145
- package/docs/helpers_actionHelpers.js.html +0 -94
- package/docs/helpers_constants.js.html +0 -81
- package/docs/helpers_dockerService_dockerService.js.html +0 -79
- package/docs/helpers_dockerService_serviceComponents_containerActions.js.html +0 -153
- package/docs/helpers_dockerService_serviceComponents_containerList.js.html +0 -81
- package/docs/helpers_dockerService_serviceComponents_containerLogs.js.html +0 -85
- package/docs/helpers_dockerService_serviceComponents_containerStats.js.html +0 -107
- package/docs/helpers_dockerService_serviceComponents_imageUtils.js.html +0 -82
- package/docs/helpers_exitWithMessage.js.html +0 -77
- package/docs/helpers_safeCall.js.html +0 -68
- package/docs/helpers_validationHelpers.js.html +0 -96
- package/docs/hooks_creation_useContainerActions.js.html +0 -120
- package/docs/hooks_creation_useContainerCreation.js.html +0 -152
- package/docs/hooks_creation_useLogsViewer.js.html +0 -102
- package/docs/hooks_useControls.js.html +0 -298
- package/docs/hooks_useLogsStream.js.html +0 -82
- package/docs/index.html +0 -65
- package/docs/index.js.html +0 -65
- package/docs/module-index.html +0 -171
- package/docs/scripts/linenumber.js +0 -25
- package/docs/scripts/prettify/Apache-License-2.0.txt +0 -202
- package/docs/scripts/prettify/lang-css.js +0 -2
- package/docs/scripts/prettify/prettify.js +0 -28
- package/docs/styles/jsdoc-default.css +0 -358
- package/docs/styles/prettify-jsdoc.css +0 -111
- package/docs/styles/prettify-tomorrow.css +0 -132
- package/esbuild.config.cjs +0 -11
- package/src/helpers/actionHelpers.js +0 -52
- package/src/hooks/useLogsStream.js +0 -31
- package/test/actionHelpers.test.js +0 -106
|
@@ -1,9 +1,8 @@
|
|
|
1
|
-
import React
|
|
1
|
+
import React from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
|
-
import { getStats } from "../helpers/dockerService/serviceComponents/containerStats";
|
|
4
|
-
import { REFRESH_INTERVALS } from "../helpers/constants.js";
|
|
5
3
|
import StatsBar from "./StatsBar.jsx";
|
|
6
4
|
import PropTypes from 'prop-types';
|
|
5
|
+
import { useContainerStats } from '../hooks/useContainerStats.js';
|
|
7
6
|
|
|
8
7
|
const stateText = (state) => {
|
|
9
8
|
if (state === "running") return { text: "🟢 RUNNING", color: "green" };
|
|
@@ -21,11 +20,7 @@ const stateText = (state) => {
|
|
|
21
20
|
*/
|
|
22
21
|
export default function ContainerRow({ container, isSelected = false }) {
|
|
23
22
|
const { id, name, image, state } = container;
|
|
24
|
-
const
|
|
25
|
-
cpuPercent: 0,
|
|
26
|
-
memPercent: 0,
|
|
27
|
-
netIO: { rx: 0, tx: 0 },
|
|
28
|
-
});
|
|
23
|
+
const { stats, statsError } = useContainerStats(id, state);
|
|
29
24
|
|
|
30
25
|
// Format ports for display (no leading space to avoid layout shifts)
|
|
31
26
|
const formatPorts = (ports) => {
|
|
@@ -42,36 +37,6 @@ export default function ContainerRow({ container, isSelected = false }) {
|
|
|
42
37
|
return s.length > max ? s.slice(0, max - 1) + "…" : s;
|
|
43
38
|
};
|
|
44
39
|
|
|
45
|
-
const [statsError, setStatsError] = useState("");
|
|
46
|
-
useEffect(() => {
|
|
47
|
-
if (state !== "running") return;
|
|
48
|
-
|
|
49
|
-
let isMounted = true;
|
|
50
|
-
|
|
51
|
-
const fetchStats = async () => {
|
|
52
|
-
try {
|
|
53
|
-
const s = await getStats(id);
|
|
54
|
-
if (isMounted) {
|
|
55
|
-
setStats(s);
|
|
56
|
-
setStatsError("");
|
|
57
|
-
}
|
|
58
|
-
} catch (err) {
|
|
59
|
-
if (isMounted) {
|
|
60
|
-
setStats({ cpuPercent: 0, memPercent: 0, netIO: { rx: 0, tx: 0 } });
|
|
61
|
-
setStatsError("Error fetching stats");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
fetchStats();
|
|
67
|
-
const timer = setInterval(fetchStats, REFRESH_INTERVALS.CONTAINER_STATS);
|
|
68
|
-
|
|
69
|
-
return () => {
|
|
70
|
-
isMounted = false;
|
|
71
|
-
clearInterval(timer);
|
|
72
|
-
};
|
|
73
|
-
}, [id, state]);
|
|
74
|
-
|
|
75
40
|
const stateInfo = stateText(state);
|
|
76
41
|
return (
|
|
77
42
|
<Box flexDirection="column" marginBottom={1}>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
-
import { Text
|
|
3
|
-
import { safeCall } from "../helpers/safeCall";
|
|
2
|
+
import { Text } from "ink";
|
|
4
3
|
import PropTypes from 'prop-types';
|
|
5
4
|
|
|
6
5
|
/**
|
|
7
|
-
* Log viewer overlay component. Shows the most recent lines
|
|
6
|
+
* Log viewer overlay component. Shows the most recent lines.
|
|
7
|
+
* Purely presentational — keyboard handling is centralized in useControls.
|
|
8
8
|
*
|
|
9
9
|
* @param {Object} props
|
|
10
10
|
* @param {Array<string>} props.logs - Array of log lines
|
|
@@ -12,13 +12,7 @@ import PropTypes from 'prop-types';
|
|
|
12
12
|
* @param {Object} [props.container] - Optional container metadata (used for title)
|
|
13
13
|
* @returns {JSX.Element}
|
|
14
14
|
*/
|
|
15
|
-
export default function LogViewer({ logs, onExit, container }) {
|
|
16
|
-
useInput((input, key) => {
|
|
17
|
-
if (key.escape) {
|
|
18
|
-
safeCall(onExit);
|
|
19
|
-
}
|
|
20
|
-
});
|
|
21
|
-
|
|
15
|
+
export default function LogViewer({ logs, onExit: _onExit, container }) {
|
|
22
16
|
const visibleLogs = logs.slice(-15);
|
|
23
17
|
|
|
24
18
|
return (
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Builds Docker container creation options from raw user inputs.
|
|
3
|
+
*
|
|
4
|
+
* This is a pure function — it has no side effects and calls no services.
|
|
5
|
+
* It mirrors exactly the option-building logic in useControls.js onCreate callback.
|
|
6
|
+
*
|
|
7
|
+
* @param {object} params
|
|
8
|
+
* @param {string} params.imageName - Docker image name (e.g. "nginx:alpine")
|
|
9
|
+
* @param {string} [params.containerName] - Optional container name
|
|
10
|
+
* @param {string} [params.portInput] - Comma-separated host:container port pairs
|
|
11
|
+
* (e.g. "8080:80,3000:3000")
|
|
12
|
+
* @param {string} [params.envInput] - Comma-separated KEY=VALUE pairs
|
|
13
|
+
* (e.g. "NODE_ENV=production,PORT=3000")
|
|
14
|
+
* @returns {object} Docker container options ready to pass to createContainer
|
|
15
|
+
*/
|
|
16
|
+
export function buildContainerOptions({ imageName: _imageName, containerName, portInput, envInput }) {
|
|
17
|
+
const env = (envInput || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
18
|
+
const ports = (portInput || "").split(",").map(s => s.trim()).filter(Boolean);
|
|
19
|
+
|
|
20
|
+
const ExposedPorts = {};
|
|
21
|
+
const PortBindings = {};
|
|
22
|
+
|
|
23
|
+
ports.forEach(pair => {
|
|
24
|
+
const [host, cont] = pair.split(":");
|
|
25
|
+
if (!host || !cont) return;
|
|
26
|
+
const key = `${cont}/tcp`;
|
|
27
|
+
ExposedPorts[key] = {};
|
|
28
|
+
PortBindings[key] = PortBindings[key] || [];
|
|
29
|
+
PortBindings[key].push({ HostPort: `${host}` });
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const options = {
|
|
33
|
+
Tty: true,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
if (Object.keys(ExposedPorts).length) options.ExposedPorts = ExposedPorts;
|
|
37
|
+
if (Object.keys(PortBindings).length) options.HostConfig = { PortBindings };
|
|
38
|
+
if (env.length) options.Env = env;
|
|
39
|
+
if (containerName) options.name = containerName;
|
|
40
|
+
|
|
41
|
+
return options;
|
|
42
|
+
}
|
|
@@ -80,6 +80,20 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
|
|
|
80
80
|
safeCall(onCancel);
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
+
/**
|
|
84
|
+
* Resets all creation state to initial values without triggering onCancel.
|
|
85
|
+
* Used by the command router when the user presses 'c' to open the creation wizard.
|
|
86
|
+
*/
|
|
87
|
+
function resetCreation() {
|
|
88
|
+
setStep(0);
|
|
89
|
+
setImageName("");
|
|
90
|
+
setContainerName("");
|
|
91
|
+
setPortInput("");
|
|
92
|
+
setEnvInput("");
|
|
93
|
+
setMessage("Insert the name of the image to create: ");
|
|
94
|
+
setMessageColor("yellow");
|
|
95
|
+
}
|
|
96
|
+
|
|
83
97
|
return {
|
|
84
98
|
step,
|
|
85
99
|
setStep,
|
|
@@ -97,5 +111,6 @@ export function useContainerCreation({ onCreate, onCancel, dbImages = [] }) {
|
|
|
97
111
|
setMessageColor,
|
|
98
112
|
nextStep,
|
|
99
113
|
cancelCreation,
|
|
114
|
+
resetCreation,
|
|
100
115
|
};
|
|
101
116
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { logger } from '../../helpers/logger.js';
|
|
3
|
+
|
|
4
|
+
export function useDebugLogs() {
|
|
5
|
+
const [showDebugLogs, setShowDebugLogs] = useState(false);
|
|
6
|
+
const [debugLogs, setDebugLogs] = useState([]);
|
|
7
|
+
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
const unsubscribe = logger.subscribe((entry) => {
|
|
10
|
+
setDebugLogs((prev) => {
|
|
11
|
+
const extras = (entry.args || []).map((arg) => {
|
|
12
|
+
if (typeof arg === "string") return arg;
|
|
13
|
+
if (typeof arg === "number" || typeof arg === "boolean") return String(arg);
|
|
14
|
+
try {
|
|
15
|
+
return JSON.stringify(arg);
|
|
16
|
+
} catch (err) {
|
|
17
|
+
return "[unserializable]";
|
|
18
|
+
}
|
|
19
|
+
}).filter(Boolean);
|
|
20
|
+
const line = extras.length ? `${entry.formatted} ${extras.join(" ")}` : entry.formatted;
|
|
21
|
+
const next = [...prev, line];
|
|
22
|
+
return next.slice(-200);
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
return unsubscribe;
|
|
26
|
+
}, []);
|
|
27
|
+
|
|
28
|
+
return { showDebugLogs, setShowDebugLogs, debugLogs };
|
|
29
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { useState, useCallback } from 'react';
|
|
2
|
+
|
|
3
|
+
export function useContainerSelection(total) {
|
|
4
|
+
const [selected, setSelected] = useState(0);
|
|
5
|
+
|
|
6
|
+
const handleNavigation = useCallback((input, key) => {
|
|
7
|
+
if (total === 0) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (key.upArrow) {
|
|
12
|
+
setSelected((i) => (i === 0 ? total - 1 : i - 1));
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (key.downArrow) {
|
|
17
|
+
setSelected((i) => (i === total - 1 ? 0 : i + 1));
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return false;
|
|
22
|
+
}, [total]);
|
|
23
|
+
|
|
24
|
+
return { selected, setSelected, handleNavigation };
|
|
25
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook responsible solely for routing Docker action keystrokes to the correct handler.
|
|
5
|
+
* Has no domain state of its own — it delegates every side-effect to its dependencies.
|
|
6
|
+
*
|
|
7
|
+
* Handled keys:
|
|
8
|
+
* i — start container
|
|
9
|
+
* p — stop container
|
|
10
|
+
* r — restart container
|
|
11
|
+
* e — request erase confirmation
|
|
12
|
+
* l — open logs viewer and start log stream
|
|
13
|
+
* c — open container creation wizard (reset + activate)
|
|
14
|
+
* d — toggle debug log panel
|
|
15
|
+
*
|
|
16
|
+
* @param {Object} params
|
|
17
|
+
* @param {Object} params.actions - API from useContainerActions
|
|
18
|
+
* @param {Array<Object>} params.containers - Current list of Docker containers
|
|
19
|
+
* @param {number} params.selected - Index of the currently selected container
|
|
20
|
+
* @param {Object} params.creation - API from useContainerCreation (must expose resetCreation)
|
|
21
|
+
* @param {Object} params.logsViewer - API from useLogsViewer
|
|
22
|
+
* @param {Function} params.startLogsStream - Starts the log stream for a container id
|
|
23
|
+
* @param {Function} params.onStartErase - Callback: activates confirmErase state
|
|
24
|
+
* @param {Function} params.onToggleDebug - Callback: toggles showDebugLogs state
|
|
25
|
+
* @param {Function} params.onStartCreate - Callback: sets creatingContainer = true
|
|
26
|
+
* @returns {{ handleDockerCommands: Function }}
|
|
27
|
+
*/
|
|
28
|
+
export function useContainerCommandRouter({
|
|
29
|
+
actions,
|
|
30
|
+
containers,
|
|
31
|
+
selected,
|
|
32
|
+
creation,
|
|
33
|
+
logsViewer,
|
|
34
|
+
startLogsStream,
|
|
35
|
+
onStartErase,
|
|
36
|
+
onToggleDebug,
|
|
37
|
+
onStartCreate,
|
|
38
|
+
}) {
|
|
39
|
+
const handleDockerCommands = useCallback((input) => {
|
|
40
|
+
const container = containers[selected];
|
|
41
|
+
|
|
42
|
+
if (input === "i") {
|
|
43
|
+
actions.handleAction({
|
|
44
|
+
actionFn: async (id) => await actions.startContainer(id),
|
|
45
|
+
actionLabel: "Starting",
|
|
46
|
+
selected,
|
|
47
|
+
stateCheck: (c) =>
|
|
48
|
+
(c.state === "running" || c.status === "running") &&
|
|
49
|
+
"Container is already running.",
|
|
50
|
+
});
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (input === "p") {
|
|
55
|
+
actions.handleAction({
|
|
56
|
+
actionFn: async (id) => await actions.stopContainer(id),
|
|
57
|
+
actionLabel: "Stopping",
|
|
58
|
+
selected,
|
|
59
|
+
stateCheck: (c) =>
|
|
60
|
+
(c.state === "exited" ||
|
|
61
|
+
c.status === "exited" ||
|
|
62
|
+
c.state === "stopped" ||
|
|
63
|
+
c.status === "stopped") &&
|
|
64
|
+
"Container is already stopped.",
|
|
65
|
+
});
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (input === "r") {
|
|
70
|
+
actions.handleAction({
|
|
71
|
+
actionFn: async (id) => await actions.restartContainer(id),
|
|
72
|
+
actionLabel: "Restarting",
|
|
73
|
+
selected,
|
|
74
|
+
});
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (input === "e") {
|
|
79
|
+
if (!container) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
onStartErase();
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (input === "l") {
|
|
87
|
+
if (!container) {
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
logsViewer.openLogs();
|
|
91
|
+
startLogsStream(container.id);
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (input === "c") {
|
|
96
|
+
onStartCreate();
|
|
97
|
+
creation.resetCreation();
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (input === "d") {
|
|
102
|
+
onToggleDebug();
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return false;
|
|
107
|
+
}, [actions, containers, creation, logsViewer, onStartCreate, onStartErase, onToggleDebug, selected, startLogsStream]);
|
|
108
|
+
|
|
109
|
+
return { handleDockerCommands };
|
|
110
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { getStats } from '../helpers/dockerService/serviceComponents/containerStats.js';
|
|
3
|
+
import { REFRESH_INTERVALS } from '../helpers/constants.js';
|
|
4
|
+
|
|
5
|
+
const DEFAULT_STATS = { cpuPercent: 0, memPercent: 0, netIO: { rx: 0, tx: 0 } };
|
|
6
|
+
|
|
7
|
+
export function useContainerStats(id, state) {
|
|
8
|
+
const [stats, setStats] = useState(DEFAULT_STATS);
|
|
9
|
+
const [statsError, setStatsError] = useState('');
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
if (state !== 'running') {
|
|
13
|
+
setStats(DEFAULT_STATS);
|
|
14
|
+
setStatsError('');
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
let isMounted = true;
|
|
19
|
+
|
|
20
|
+
const fetchStats = async () => {
|
|
21
|
+
try {
|
|
22
|
+
const s = await getStats(id);
|
|
23
|
+
if (isMounted) {
|
|
24
|
+
setStats(s);
|
|
25
|
+
setStatsError('');
|
|
26
|
+
}
|
|
27
|
+
} catch (err) {
|
|
28
|
+
if (isMounted) {
|
|
29
|
+
setStats(DEFAULT_STATS);
|
|
30
|
+
setStatsError('Error fetching stats');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
fetchStats();
|
|
36
|
+
const timer = setInterval(fetchStats, REFRESH_INTERVALS.CONTAINER_STATS);
|
|
37
|
+
return () => {
|
|
38
|
+
isMounted = false;
|
|
39
|
+
clearInterval(timer);
|
|
40
|
+
};
|
|
41
|
+
}, [id, state]);
|
|
42
|
+
|
|
43
|
+
return { stats, statsError };
|
|
44
|
+
}
|