cdd-cli 2.0.3 → 3.0.1
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/LICENSE +23 -0
- package/PLAN_KEYBINDINGS_Y_REALTIME.md +260 -0
- package/README.md +14 -3
- package/dist/App.js +26 -12
- package/dist/components/ContainerList.js +19 -0
- package/dist/components/ContainerRow.js +3 -3
- package/dist/components/LogViewer.js +22 -0
- package/dist/components/MessageFeedback.js +12 -0
- package/dist/components/StatsViewer.js +0 -0
- package/dist/helpers/actionHelpers.js +61 -0
- package/dist/helpers/dockerActions.js +51 -0
- package/dist/{dockerService.js → helpers/dockerService.js} +109 -21
- package/dist/helpers/exitWithMessage.js +26 -0
- package/dist/hooks/useContainers.js +1 -1
- package/dist/hooks/useControls.js +142 -0
- package/dist/hooks/useLogsStream.js +34 -0
- package/dist/hooks/useStatsPolling.js +54 -0
- package/docs/en/README.md +167 -0
- package/package.json +1 -1
- package/src/App.jsx +35 -7
- package/src/components/ContainerList.jsx +31 -0
- package/src/components/ContainerRow.jsx +10 -7
- package/src/components/LogViewer.jsx +23 -0
- package/src/components/MessageFeedback.jsx +11 -0
- package/src/components/StatsViewer.jsx +0 -0
- package/src/helpers/actionHelpers.js +31 -0
- package/src/helpers/dockerActions.js +52 -0
- package/src/helpers/dockerService.js +100 -0
- package/src/helpers/exitWithMessage.js +15 -0
- package/src/hooks/useContainers.js +11 -1
- package/src/hooks/useControls.js +98 -0
- package/src/hooks/useLogsStream.js +25 -0
- package/src/index.js +13 -0
- package/src/dockerService.js +0 -49
package/src/App.jsx
CHANGED
|
@@ -1,13 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Main React component for the CDD CLI UI.
|
|
3
|
+
* Componente principal de React para la UI del CLI CDD.
|
|
4
|
+
*
|
|
5
|
+
* @component
|
|
6
|
+
* @returns {JSX.Element} The rendered app / La app renderizada
|
|
7
|
+
* @example
|
|
8
|
+
* // EN: Render the app
|
|
9
|
+
* // ES: Renderizar la app
|
|
10
|
+
* <App />
|
|
11
|
+
*/
|
|
1
12
|
import React from "react";
|
|
2
13
|
import { Box, Text, Spacer } from "ink";
|
|
3
14
|
import { useContainers } from "./hooks/useContainers";
|
|
4
|
-
import
|
|
15
|
+
import { useControls } from "./hooks/useControls";
|
|
16
|
+
import ContainerList from "./components/ContainerList";
|
|
17
|
+
import MessageFeedback from "./components/MessageFeedback";
|
|
5
18
|
import Header from "./components/Header";
|
|
19
|
+
import LogViewer from "./components/LogViewer";
|
|
6
20
|
|
|
7
21
|
export default function App() {
|
|
8
22
|
const { containers } = useContainers();
|
|
23
|
+
const { selected, message, messageColor, showLogs, logs, exitLogs } =
|
|
24
|
+
useControls(containers);
|
|
9
25
|
|
|
10
26
|
return (
|
|
27
|
+
<>
|
|
11
28
|
<Box
|
|
12
29
|
flexDirection="column"
|
|
13
30
|
borderStyle="round"
|
|
@@ -19,15 +36,26 @@ export default function App() {
|
|
|
19
36
|
{containers.length === 0 ? (
|
|
20
37
|
<Text>No containers found</Text>
|
|
21
38
|
) : (
|
|
22
|
-
containers
|
|
23
|
-
<ContainerRow key={container.id} container={container} />
|
|
24
|
-
))
|
|
39
|
+
<ContainerList containers={containers} selected={selected} />
|
|
25
40
|
)}
|
|
26
41
|
<Spacer />
|
|
27
|
-
<
|
|
28
|
-
|
|
42
|
+
<MessageFeedback message={message} color={messageColor} />
|
|
43
|
+
<Text>Use ↑/↓ for navigation</Text>
|
|
44
|
+
<Text>•I to initiate selected container</Text>
|
|
45
|
+
<Text>•P to stop selected container</Text>
|
|
46
|
+
<Text>•L to view logs of selected container</Text>
|
|
47
|
+
<Text>•Q to quit</Text>
|
|
48
|
+
<Box justifyContent="flex-end" width="100%">
|
|
49
|
+
<Text dimColor>Crafted by Carlos Cochero • 2025</Text>
|
|
29
50
|
</Box>
|
|
30
|
-
<Text dimColor>Crafted by Carlos Cochero • 2025</Text>
|
|
31
51
|
</Box>
|
|
52
|
+
{showLogs && (
|
|
53
|
+
<LogViewer
|
|
54
|
+
logs={logs}
|
|
55
|
+
onExit={exitLogs}
|
|
56
|
+
container={containers[selected]}
|
|
57
|
+
/>
|
|
58
|
+
)}
|
|
59
|
+
</>
|
|
32
60
|
);
|
|
33
61
|
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* List component for Docker containers.
|
|
3
|
+
* Componente de lista para contenedores Docker.
|
|
4
|
+
*
|
|
5
|
+
* @component
|
|
6
|
+
* @param {Object} props - Component props / Props del componente
|
|
7
|
+
* @param {Array} props.containers - Containers to display / Contenedores a mostrar
|
|
8
|
+
* @returns {JSX.Element} Rendered list / Lista renderizada
|
|
9
|
+
* @example
|
|
10
|
+
* // EN: Render with containers
|
|
11
|
+
* // ES: Renderizar con contenedores
|
|
12
|
+
* <ContainerList containers={containers} />
|
|
13
|
+
*/
|
|
14
|
+
import React from "react";
|
|
15
|
+
import { Box, Text } from "ink";
|
|
16
|
+
import ContainerRow from "./ContainerRow";
|
|
17
|
+
|
|
18
|
+
export default function ContainerList({ containers, selected }) {
|
|
19
|
+
return (
|
|
20
|
+
<>
|
|
21
|
+
{containers.map((container, i) => (
|
|
22
|
+
<Box key={container.id} flexDirection="row" alignItems="center">
|
|
23
|
+
<Text color={i === selected ? "green" : undefined}>
|
|
24
|
+
{i === selected ? "➤" : " "}
|
|
25
|
+
</Text>
|
|
26
|
+
<ContainerRow container={container} isSelected={i === selected} />
|
|
27
|
+
</Box>
|
|
28
|
+
))}
|
|
29
|
+
</>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React, { useState, useEffect } from "react";
|
|
2
2
|
import { Box, Text } from "ink";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import { getStats } from "../dockerService.js";
|
|
4
|
+
import { getStats } from "../helpers/dockerService.js";
|
|
5
5
|
import StatsBar from "./StatsBar";
|
|
6
6
|
|
|
7
7
|
const colorByState = (state) => {
|
|
@@ -37,13 +37,16 @@ export default function ContainerRow({ container }) {
|
|
|
37
37
|
{state === "running"
|
|
38
38
|
? chalk.greenBright("🟢 RUNNING")
|
|
39
39
|
: chalk.redBright(`🔴 ${state.toUpperCase()}`)}
|
|
40
|
+
{" "}
|
|
41
|
+
{chalk.yellow(container.ports)}
|
|
42
|
+
{" "}
|
|
43
|
+
{state === "running" && (
|
|
44
|
+
<StatsBar
|
|
45
|
+
cpu={parseFloat(stats.cpuPercent)}
|
|
46
|
+
mem={parseFloat(stats.memPercent)}
|
|
47
|
+
/>
|
|
48
|
+
)}
|
|
40
49
|
</Text>
|
|
41
|
-
{state === "running" && (
|
|
42
|
-
<StatsBar
|
|
43
|
-
cpu={parseFloat(stats.cpuPercent)}
|
|
44
|
-
mem={parseFloat(stats.memPercent)}
|
|
45
|
-
/>
|
|
46
|
-
)}
|
|
47
50
|
</Box>
|
|
48
51
|
);
|
|
49
52
|
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
|
+
|
|
4
|
+
export default function LogViewer({ logs, onExit, container }) {
|
|
5
|
+
useInput((input, key) => {
|
|
6
|
+
if (key.escape) {
|
|
7
|
+
onExit();
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const visibleLogs = logs.slice(-15);
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
<>
|
|
15
|
+
<Text color="green">{container?.name || "Container"} logs, press ESC to exit</Text>
|
|
16
|
+
{visibleLogs.length === 0 ? (
|
|
17
|
+
<Text dimColor>No logs...</Text>
|
|
18
|
+
) : (
|
|
19
|
+
visibleLogs.map((line, idx) => <Text key={idx}>{line}</Text>)
|
|
20
|
+
)}
|
|
21
|
+
</>
|
|
22
|
+
);
|
|
23
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Helper para manejar acciones docker con feedback visual y validación
|
|
2
|
+
export async function handleAction({
|
|
3
|
+
containers,
|
|
4
|
+
selected,
|
|
5
|
+
actionFn,
|
|
6
|
+
actionLabel,
|
|
7
|
+
setMessage,
|
|
8
|
+
setMessageColor,
|
|
9
|
+
stateCheck
|
|
10
|
+
}) {
|
|
11
|
+
const c = containers[selected];
|
|
12
|
+
if (!c) return;
|
|
13
|
+
if (stateCheck && stateCheck(c)) {
|
|
14
|
+
setMessage(stateCheck(c));
|
|
15
|
+
setMessageColor("red");
|
|
16
|
+
setTimeout(() => setMessage(""), 2000);
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
setMessage(`${actionLabel} container...`);
|
|
20
|
+
setMessageColor("green");
|
|
21
|
+
try {
|
|
22
|
+
await actionFn(c.id);
|
|
23
|
+
setMessage(`${actionLabel} container...`);
|
|
24
|
+
setMessageColor("green");
|
|
25
|
+
setTimeout(() => setMessage(""), 3000);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
setMessage(`Failed to ${actionLabel.toLowerCase()} container.`);
|
|
28
|
+
setMessageColor("red");
|
|
29
|
+
setTimeout(() => setMessage(""), 3000);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run a Docker action by name (start, stop, remove).
|
|
3
|
+
* Ejecuta una acción Docker por nombre (start, stop, remove).
|
|
4
|
+
*
|
|
5
|
+
* @param {string} action - Action to perform / Acción a realizar
|
|
6
|
+
* @param {string} container - Container name / Nombre del contenedor
|
|
7
|
+
* @returns {Promise<boolean>} True if successful / True si es exitoso
|
|
8
|
+
* @throws {Error} If Docker is not running / Si Docker no está corriendo
|
|
9
|
+
* @example
|
|
10
|
+
* // EN: Start a container
|
|
11
|
+
* // ES: Iniciar un contenedor
|
|
12
|
+
* await runDockerAction('start', 'my_container');
|
|
13
|
+
*/
|
|
14
|
+
import { spawn } from "child_process";
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Ejecuta una acción de Docker sobre un contenedor y maneja feedback visual.
|
|
18
|
+
* @param {Object} params
|
|
19
|
+
* @param {Array} params.containers - Lista de contenedores.
|
|
20
|
+
* @param {number} params.selected - Índice del contenedor seleccionado.
|
|
21
|
+
* @param {string} params.action - Acción docker (start, stop, restart).
|
|
22
|
+
* @param {string} params.actionLabel - Texto para feedback (Starting, Stopping, etc).
|
|
23
|
+
* @param {Function} params.setMessage - Setter de mensaje visual.
|
|
24
|
+
* @param {Function} params.setMessageColor - Setter de color del mensaje.
|
|
25
|
+
*/
|
|
26
|
+
export function handleDockerAction({ containers, selected, action, actionLabel, setMessage, setMessageColor }) {
|
|
27
|
+
if (containers[selected]) {
|
|
28
|
+
const c = containers[selected];
|
|
29
|
+
const id = c.id || c.name;
|
|
30
|
+
// Validación de estado
|
|
31
|
+
if (action === "start" && (c.state === "running" || c.status === "running")) {
|
|
32
|
+
setMessage("Container is already running.");
|
|
33
|
+
setMessageColor("red");
|
|
34
|
+
setTimeout(() => setMessage(""), 2000);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (action === "stop" && (c.state === "exited" || c.status === "exited" || c.state === "stopped" || c.status === "stopped")) {
|
|
38
|
+
setMessage("Container is already stopped.");
|
|
39
|
+
setMessageColor("red");
|
|
40
|
+
setTimeout(() => setMessage(""), 2000);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
setMessage(`${actionLabel} container...`);
|
|
44
|
+
setMessageColor("green");
|
|
45
|
+
const child = spawn("docker", [action, id]);
|
|
46
|
+
child.on("close", () => {
|
|
47
|
+
setMessage(`${actionLabel} container...`);
|
|
48
|
+
setMessageColor("green");
|
|
49
|
+
setTimeout(() => setMessage(""), 3000);
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Utility to interact with Docker CLI.
|
|
3
|
+
* Utilidad para interactuar con el CLI de Docker.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} cmd - Docker command / Comando Docker
|
|
6
|
+
* @returns {Promise<string>} Output from Docker / Salida de Docker
|
|
7
|
+
* @throws {Error} If command fails / Si el comando falla
|
|
8
|
+
* @example
|
|
9
|
+
* // EN: Run a Docker command
|
|
10
|
+
* // ES: Ejecutar un comando Docker
|
|
11
|
+
* await dockerService('ps -a');
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import Docker from "dockerode";
|
|
15
|
+
const docker = new Docker({ socketPath: "/var/run/docker.sock" });
|
|
16
|
+
|
|
17
|
+
export function getLogsStream(containerId, onData, onEnd, onError) {
|
|
18
|
+
const container = docker.getContainer(containerId);
|
|
19
|
+
container.logs({
|
|
20
|
+
follow: true,
|
|
21
|
+
stdout: true,
|
|
22
|
+
stderr: true,
|
|
23
|
+
tail: 100
|
|
24
|
+
}, (err, stream) => {
|
|
25
|
+
if (err) {
|
|
26
|
+
onError?.(err);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
stream.on('data', chunk => onData?.(chunk.toString()));
|
|
30
|
+
stream.on('end', () => onEnd?.());
|
|
31
|
+
stream.on('error', err => onError?.(err));
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function startContainer(containerId) {
|
|
36
|
+
const container = docker.getContainer(containerId);
|
|
37
|
+
await container.start();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export async function stopContainer(containerId) {
|
|
41
|
+
const container = docker.getContainer(containerId);
|
|
42
|
+
await container.stop();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function restartContainer(containerId) {
|
|
46
|
+
const container = docker.getContainer(containerId);
|
|
47
|
+
await container.restart();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function getContainers() {
|
|
51
|
+
const containers = await docker.listContainers({ all: true });
|
|
52
|
+
return containers.map((container) => ({
|
|
53
|
+
id: container.Id,
|
|
54
|
+
name: container.Names[0].replace("/", ""),
|
|
55
|
+
image: container.Image,
|
|
56
|
+
state: container.State,
|
|
57
|
+
status: container.Status,
|
|
58
|
+
ports:
|
|
59
|
+
[
|
|
60
|
+
...new Set(
|
|
61
|
+
container.Ports.filter((port) => port.PublicPort).map(
|
|
62
|
+
(port) => `${port.PublicPort}:${port.PrivatePort}`
|
|
63
|
+
)
|
|
64
|
+
),
|
|
65
|
+
]
|
|
66
|
+
}));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function getStats(containerId) {
|
|
70
|
+
const container = docker.getContainer(containerId);
|
|
71
|
+
const stream = await container.stats({ stream: false });
|
|
72
|
+
|
|
73
|
+
const cpuDelta =
|
|
74
|
+
stream.cpu_stats.cpu_usage.total_usage -
|
|
75
|
+
stream.precpu_stats.cpu_usage.total_usage;
|
|
76
|
+
const systemDelta =
|
|
77
|
+
stream.cpu_stats.system_cpu_usage - stream.precpu_stats.system_cpu_usage;
|
|
78
|
+
const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * 100 : 0;
|
|
79
|
+
|
|
80
|
+
const memUsage = stream.memory_stats.usage || 0;
|
|
81
|
+
const memLimit = stream.memory_stats.limit || 1;
|
|
82
|
+
const memPercent = (memUsage / memLimit) * 100;
|
|
83
|
+
|
|
84
|
+
const rx = stream.networks
|
|
85
|
+
? Object.values(stream.networks)
|
|
86
|
+
.map((n) => n.rx_bytes)
|
|
87
|
+
.reduce((a, b) => a + b, 0)
|
|
88
|
+
: 0;
|
|
89
|
+
const tx = stream.networks
|
|
90
|
+
? Object.values(stream.networks)
|
|
91
|
+
.map((n) => n.tx_bytes)
|
|
92
|
+
.reduce((a, b) => a + b, 0)
|
|
93
|
+
: 0;
|
|
94
|
+
|
|
95
|
+
return {
|
|
96
|
+
cpuPercent: cpuPercent.toFixed(1),
|
|
97
|
+
memPercent: memPercent.toFixed(1),
|
|
98
|
+
netIO: { rx, tx },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
|
|
3
|
+
export function exitWithMessage({ setMessage, setMessageColor, message = "Exiting...", color = "yellow", delay = 1500 }) {
|
|
4
|
+
setMessage(message);
|
|
5
|
+
setMessageColor(color);
|
|
6
|
+
setTimeout(() => {
|
|
7
|
+
setMessage("");
|
|
8
|
+
if (process.platform === "win32") {
|
|
9
|
+
spawn("cmd", ["/c", "cls"], { stdio: "inherit" });
|
|
10
|
+
} else {
|
|
11
|
+
spawn("clear", [], { stdio: "inherit" });
|
|
12
|
+
}
|
|
13
|
+
process.exit();
|
|
14
|
+
}, delay);
|
|
15
|
+
}
|
|
@@ -1,5 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React hook to manage Docker containers state.
|
|
3
|
+
* Hook de React para gestionar el estado de contenedores Docker.
|
|
4
|
+
*
|
|
5
|
+
* @returns {[Array, Function]} [containers, refresh] / [contenedores, refrescar]
|
|
6
|
+
* @example
|
|
7
|
+
* // EN: Use in a component
|
|
8
|
+
* // ES: Usar en un componente
|
|
9
|
+
* const [containers, refresh] = useContainers();
|
|
10
|
+
*/
|
|
1
11
|
import React, { useState, useEffect } from "react";
|
|
2
|
-
import { getContainers } from "../dockerService";
|
|
12
|
+
import { getContainers } from "../helpers/dockerService";
|
|
3
13
|
|
|
4
14
|
export function useContainers() {
|
|
5
15
|
const [containers, setContainers] = useState([]);
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
|
|
2
|
+
import React, { useState, useRef } from "react";
|
|
3
|
+
import { useInput } from "ink";
|
|
4
|
+
import { startContainer, stopContainer, restartContainer, getLogsStream } from "../helpers/dockerService";
|
|
5
|
+
import { handleAction } from "../helpers/actionHelpers";
|
|
6
|
+
import { exitWithMessage } from "../helpers/exitWithMessage";
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
export function useControls(containers = []) {
|
|
10
|
+
const [selected, setSelected] = useState(0);
|
|
11
|
+
const [message, setMessage] = useState("");
|
|
12
|
+
const [messageColor, setMessageColor] = useState("yellow");
|
|
13
|
+
const [showLogs, setShowLogs] = useState(false);
|
|
14
|
+
const [logs, setLogs] = useState([]);
|
|
15
|
+
const logsStreamRef = useRef(null);
|
|
16
|
+
const total = containers.length;
|
|
17
|
+
|
|
18
|
+
// Handler para salir de la vista de logs
|
|
19
|
+
const exitLogs = () => {
|
|
20
|
+
setShowLogs(false);
|
|
21
|
+
setLogs([]);
|
|
22
|
+
if (logsStreamRef.current) {
|
|
23
|
+
logsStreamRef.current.destroy?.();
|
|
24
|
+
logsStreamRef.current = null;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
useInput((input, key) => {
|
|
29
|
+
if (showLogs) {
|
|
30
|
+
if (input === "q" || key.escape) {
|
|
31
|
+
exitLogs();
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
//==========================================================
|
|
37
|
+
// Menu Navigation
|
|
38
|
+
//==========================================================
|
|
39
|
+
if (key.upArrow && total > 0) {
|
|
40
|
+
setSelected((i) => (i === 0 ? total - 1 : i - 1));
|
|
41
|
+
}
|
|
42
|
+
if (key.downArrow && total > 0) {
|
|
43
|
+
setSelected((i) => (i === total - 1 ? 0 : i + 1));
|
|
44
|
+
}
|
|
45
|
+
if (input === "q") {
|
|
46
|
+
exitWithMessage({ setMessage, setMessageColor });
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
//==========================================================
|
|
51
|
+
// Docker commands
|
|
52
|
+
//==========================================================
|
|
53
|
+
if (input === "i") {
|
|
54
|
+
handleAction({
|
|
55
|
+
containers,
|
|
56
|
+
selected,
|
|
57
|
+
actionFn: startContainer,
|
|
58
|
+
actionLabel: "Starting",
|
|
59
|
+
setMessage,
|
|
60
|
+
setMessageColor,
|
|
61
|
+
stateCheck: c => (c.state === "running" || c.status === "running") && "Container is already running."
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
if (input === "p") {
|
|
65
|
+
handleAction({
|
|
66
|
+
containers,
|
|
67
|
+
selected,
|
|
68
|
+
actionFn: stopContainer,
|
|
69
|
+
actionLabel: "Stopping",
|
|
70
|
+
setMessage,
|
|
71
|
+
setMessageColor,
|
|
72
|
+
stateCheck: c => ((c.state === "exited" || c.status === "exited" || c.state === "stopped" || c.status === "stopped") && "Container is already stopped.")
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
if (input === "r") {
|
|
76
|
+
handleAction({
|
|
77
|
+
containers,
|
|
78
|
+
selected,
|
|
79
|
+
actionFn: restartContainer,
|
|
80
|
+
actionLabel: "Restarting",
|
|
81
|
+
setMessage,
|
|
82
|
+
setMessageColor
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (input === "l" && containers[selected]) {
|
|
86
|
+
setShowLogs(true);
|
|
87
|
+
setLogs([]);
|
|
88
|
+
getLogsStream(
|
|
89
|
+
containers[selected].id,
|
|
90
|
+
(data) => setLogs((prev) => ([...prev, ...data.split("\n").filter(Boolean)])),
|
|
91
|
+
() => {},
|
|
92
|
+
(err) => setLogs((prev) => ([...prev, `Error: ${err.message}`]))
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
return { selected, setSelected, message, messageColor, showLogs, logs, exitLogs };
|
|
98
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import React, { useRef, useCallback } from "react";
|
|
2
|
+
import { getLogsStream } from "../helpers/dockerService";
|
|
3
|
+
|
|
4
|
+
export function useLogsStream() {
|
|
5
|
+
const logsStreamRef = useRef(null);
|
|
6
|
+
|
|
7
|
+
const openLogs = useCallback((containerId, setLogs) => {
|
|
8
|
+
setLogs([]);
|
|
9
|
+
logsStreamRef.current = getLogsStream(
|
|
10
|
+
containerId,
|
|
11
|
+
(data) => setLogs((prev) => ([...prev, ...data.split("\n").filter(Boolean)])),
|
|
12
|
+
() => {},
|
|
13
|
+
(err) => setLogs((prev) => ([...prev, `Error: ${err.message}`]))
|
|
14
|
+
);
|
|
15
|
+
}, []);
|
|
16
|
+
|
|
17
|
+
const closeLogs = useCallback(() => {
|
|
18
|
+
if (logsStreamRef.current) {
|
|
19
|
+
logsStreamRef.current.destroy?.();
|
|
20
|
+
logsStreamRef.current = null;
|
|
21
|
+
}
|
|
22
|
+
}, []);
|
|
23
|
+
|
|
24
|
+
return { openLogs, closeLogs };
|
|
25
|
+
}
|
package/src/index.js
CHANGED
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Entry point for the CDD CLI application.
|
|
5
|
+
* Punto de entrada para la aplicación CLI de CDD.
|
|
6
|
+
*
|
|
7
|
+
* @module index
|
|
8
|
+
* @example
|
|
9
|
+
* // EN: Run the CLI
|
|
10
|
+
* // ES: Ejecutar el CLI
|
|
11
|
+
* node index.js
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
|
|
2
15
|
import React from "react";
|
|
3
16
|
import { render } from "ink";
|
|
4
17
|
import App from './App';
|
package/src/dockerService.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import Docker from 'dockerode';
|
|
2
|
-
const docker = new Docker({ socketPath: '/var/run/docker.sock'});
|
|
3
|
-
|
|
4
|
-
export async function getContainers() {
|
|
5
|
-
const containers = await
|
|
6
|
-
docker.listContainers({ all: true });
|
|
7
|
-
return containers.map(container => ({
|
|
8
|
-
id: container.Id,
|
|
9
|
-
name: container.Names[0].replace('/', ''),
|
|
10
|
-
image: container.Image,
|
|
11
|
-
state: container.State,
|
|
12
|
-
status: container.Status,
|
|
13
|
-
ports: container.Ports.map(port => `${port.PublicPort
|
|
14
|
-
|| '' }:${port.PrivatePort}`).join(', ') || '-'
|
|
15
|
-
}))
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function getStats(containerId) {
|
|
19
|
-
const container = docker.getContainer(containerId);
|
|
20
|
-
const stream = await container.stats({ stream: false });
|
|
21
|
-
|
|
22
|
-
const cpuDelta =
|
|
23
|
-
stream.cpu_stats.cpu_usage.total_usage -
|
|
24
|
-
stream.precpu_stats.cpu_usage.total_usage;
|
|
25
|
-
const systemDelta =
|
|
26
|
-
stream.cpu_stats.system_cpu_usage - stream.precpu_stats.system_cpu_usage;
|
|
27
|
-
const cpuPercent = systemDelta > 0 ? (cpuDelta / systemDelta) * 100 : 0;
|
|
28
|
-
|
|
29
|
-
const memUsage = stream.memory_stats.usage || 0;
|
|
30
|
-
const memLimit = stream.memory_stats.limit || 1;
|
|
31
|
-
const memPercent = (memUsage / memLimit) * 100;
|
|
32
|
-
|
|
33
|
-
const rx = stream.networks
|
|
34
|
-
? Object.values(stream.networks)
|
|
35
|
-
.map(n => n.rx_bytes)
|
|
36
|
-
.reduce((a, b) => a + b, 0)
|
|
37
|
-
: 0;
|
|
38
|
-
const tx = stream.networks
|
|
39
|
-
? Object.values(stream.networks)
|
|
40
|
-
.map(n => n.tx_bytes)
|
|
41
|
-
.reduce((a, b) => a + b, 0)
|
|
42
|
-
: 0;
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
cpuPercent: cpuPercent.toFixed(1),
|
|
46
|
-
memPercent: memPercent.toFixed(1),
|
|
47
|
-
netIO: { rx, tx },
|
|
48
|
-
};
|
|
49
|
-
}
|