cdd-cli 2.0.3 → 3.0.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.
- package/PLAN_KEYBINDINGS_Y_REALTIME.md +260 -0
- 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/package.json +1 -1
- package/src/App.jsx +24 -7
- package/src/components/ContainerList.jsx +18 -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 +39 -0
- package/src/helpers/dockerService.js +88 -0
- package/src/helpers/exitWithMessage.js +15 -0
- package/src/hooks/useContainers.js +1 -1
- package/src/hooks/useControls.js +98 -0
- package/src/hooks/useLogsStream.js +25 -0
- package/src/dockerService.js +0 -49
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Plan de Implementación: Keybindings y Funcionalidades Avanzadas para CDD
|
|
2
|
+
|
|
3
|
+
Este documento te guiará paso a paso para agregar keybindings y la integración de logs y estadísticas en tiempo real a tu proyecto CLI/CDD. Además, incluye recomendaciones de investigación para que puedas aprender y experimentar por ti mismo.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 1. Keybindings (Atajos de Teclado)
|
|
8
|
+
|
|
9
|
+
### Objetivo
|
|
10
|
+
Permitir la navegación y control de contenedores Docker usando el teclado.
|
|
11
|
+
|
|
12
|
+
### Acciones y Sugerencias
|
|
13
|
+
- **↑ / ↓**: Navegar entre contenedores
|
|
14
|
+
- **Enter**: Seleccionar contenedor
|
|
15
|
+
- **L**: Mostrar logs en vivo
|
|
16
|
+
- **S**: Mostrar estadísticas
|
|
17
|
+
- **R**: Reiniciar contenedor
|
|
18
|
+
- **I**: Iniciar contenedor
|
|
19
|
+
- **P**: Pausar/Detener contenedor
|
|
20
|
+
- **Q**: Salir
|
|
21
|
+
|
|
22
|
+
### ¿Cómo hacerlo?
|
|
23
|
+
|
|
24
|
+
### Usando Ink para Keybindings y UI CLI React
|
|
25
|
+
|
|
26
|
+
Si ya tienes Ink instalado, es la mejor opción para construir tu CLI interactiva con React. Ink te permite crear componentes reutilizables, manejar el estado y capturar eventos de teclado de forma reactiva, todo en la terminal.
|
|
27
|
+
|
|
28
|
+
#### Funciones y hooks clave de Ink:
|
|
29
|
+
|
|
30
|
+
- **`<Text>` y `<Box>`**: Componentes básicos para mostrar texto y organizar la UI en la terminal.
|
|
31
|
+
- **Por qué:** Permiten estructurar y dar formato a la salida, igual que en React web.
|
|
32
|
+
|
|
33
|
+
- **`useInput`**: Hook para capturar cualquier tecla presionada por el usuario.
|
|
34
|
+
- **Cómo:**
|
|
35
|
+
```js
|
|
36
|
+
import { useInput } from 'ink';
|
|
37
|
+
useInput((input, key) => {
|
|
38
|
+
if (key.upArrow) { /* lógica para ↑ */ }
|
|
39
|
+
if (key.downArrow) { /* lógica para ↓ */ }
|
|
40
|
+
if (input === 'q') { /* salir */ }
|
|
41
|
+
// ...otros keybindings
|
|
42
|
+
|
|
43
|
+
### ¿Cómo hacerlo?
|
|
44
|
+
|
|
45
|
+
#### Usando Ink para Keybindings y UI CLI React
|
|
46
|
+
|
|
47
|
+
Si ya tienes Ink instalado, es la mejor opción para construir tu CLI interactiva con React. Ink te permite crear componentes reutilizables, manejar el estado y capturar eventos de teclado de forma reactiva, todo en la terminal.
|
|
48
|
+
|
|
49
|
+
##### Funciones y hooks clave de Ink:
|
|
50
|
+
|
|
51
|
+
- **`<Text>` y `<Box>`**: Componentes básicos para mostrar texto y organizar la UI en la terminal.
|
|
52
|
+
- **Por qué:** Permiten estructurar y dar formato a la salida, igual que en React web.
|
|
53
|
+
|
|
54
|
+
- **`useInput`**: Hook para capturar cualquier tecla presionada por el usuario.
|
|
55
|
+
- **Cómo:**
|
|
56
|
+
```js
|
|
57
|
+
import { useInput } from 'ink';
|
|
58
|
+
useInput((input, key) => {
|
|
59
|
+
if (key.upArrow) { /* lógica para ↑ */ }
|
|
60
|
+
if (key.downArrow) { /* lógica para ↓ */ }
|
|
61
|
+
if (input === 'q') { /* salir */ }
|
|
62
|
+
// ...otros keybindings
|
|
63
|
+
});
|
|
64
|
+
```
|
|
65
|
+
- **Por qué:** Es la forma más sencilla y reactiva de manejar atajos de teclado en la terminal con Ink.
|
|
66
|
+
|
|
67
|
+
- **Estado React**: Usa `useState` y `useEffect` igual que en React web para manejar selección, logs, stats, etc.
|
|
68
|
+
- **Por qué:** Permite que la UI se actualice automáticamente cuando cambian los datos o el estado.
|
|
69
|
+
|
|
70
|
+
##### Ejemplo básico de navegación con Ink:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import React, { useState } from 'react';
|
|
74
|
+
import { render, Box, Text, useInput } from 'ink';
|
|
75
|
+
|
|
76
|
+
const containers = ['web', 'db', 'cache'];
|
|
77
|
+
|
|
78
|
+
const App = () => {
|
|
79
|
+
const [selected, setSelected] = useState(0);
|
|
80
|
+
|
|
81
|
+
useInput((input, key) => {
|
|
82
|
+
if (key.upArrow) setSelected(i => (i === 0 ? containers.length - 1 : i - 1));
|
|
83
|
+
if (key.downArrow) setSelected(i => (i === containers.length - 1 ? 0 : i + 1));
|
|
84
|
+
if (input === 'q') process.exit();
|
|
85
|
+
// ...otros keybindings
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
return (
|
|
89
|
+
<Box flexDirection="column">
|
|
90
|
+
{containers.map((name, i) => (
|
|
91
|
+
<Text key={name} color={i === selected ? 'green' : undefined}>
|
|
92
|
+
{i === selected ? '>' : ' '} {name}
|
|
93
|
+
</Text>
|
|
94
|
+
))}
|
|
95
|
+
<Text>Usa ↑/↓ para navegar, Q para salir</Text>
|
|
96
|
+
</Box>
|
|
97
|
+
);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
render(<App />);
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
##### ¿Por qué Ink?
|
|
104
|
+
- Permite una experiencia de desarrollo y mantenimiento muy similar a React web.
|
|
105
|
+
- Facilita la captura de teclas y el renderizado dinámico sin dependencias extra.
|
|
106
|
+
- Es ideal para CLIs interactivas, menús, dashboards y visualización en tiempo real.
|
|
107
|
+
|
|
108
|
+
Consulta la [documentación oficial de Ink](https://github.com/vadimdemedes/ink) para más ejemplos y detalles.
|
|
109
|
+
#### Ejemplo básico de navegación con Ink:
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
import React, { useState } from 'react';
|
|
113
|
+
import { render, Box, Text, useInput } from 'ink';
|
|
114
|
+
|
|
115
|
+
const containers = ['web', 'db', 'cache'];
|
|
116
|
+
|
|
117
|
+
const App = () => {
|
|
118
|
+
const [selected, setSelected] = useState(0);
|
|
119
|
+
|
|
120
|
+
useInput((input, key) => {
|
|
121
|
+
if (key.upArrow) setSelected(i => (i === 0 ? containers.length - 1 : i - 1));
|
|
122
|
+
if (key.downArrow) setSelected(i => (i === containers.length - 1 ? 0 : i + 1));
|
|
123
|
+
if (input === 'q') process.exit();
|
|
124
|
+
// ...otros keybindings
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
return (
|
|
128
|
+
<Box flexDirection="column">
|
|
129
|
+
{containers.map((name, i) => (
|
|
130
|
+
<Text key={name} color={i === selected ? 'green' : undefined}>
|
|
131
|
+
{i === selected ? '>' : ' '} {name}
|
|
132
|
+
</Text>
|
|
133
|
+
))}
|
|
134
|
+
<Text>Usa ↑/↓ para navegar, Q para salir</Text>
|
|
135
|
+
</Box>
|
|
136
|
+
);
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
render(<App />);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### ¿Por qué Ink?
|
|
143
|
+
- Permite una experiencia de desarrollo y mantenimiento muy similar a React web.
|
|
144
|
+
- Facilita la captura de teclas y el renderizado dinámico sin dependencias extra.
|
|
145
|
+
- Es ideal para CLIs interactivas, menús, dashboards y visualización en tiempo real.
|
|
146
|
+
|
|
147
|
+
Consulta la [documentación oficial de Ink](https://github.com/vadimdemedes/ink) para más ejemplos y detalles.
|
|
148
|
+
|
|
149
|
+
---
|
|
150
|
+
|
|
151
|
+
## 2. Integración de Logs y Stats en Tiempo Real
|
|
152
|
+
|
|
153
|
+
### Logs en Vivo
|
|
154
|
+
- Utiliza el comando `docker logs -f <container>` para obtener logs en streaming.
|
|
155
|
+
- Investiga cómo manejar streams en Node.js con `child_process.spawn`.
|
|
156
|
+
- Si quieres mostrar los logs en la UI, revisa cómo actualizar el estado en tiempo real.
|
|
157
|
+
|
|
158
|
+
### Estadísticas en Tiempo Real
|
|
159
|
+
- Usa el comando `docker stats <container> --no-stream` para obtener stats puntuales, o sin `--no-stream` para flujo continuo.
|
|
160
|
+
- Investiga cómo parsear la salida de estos comandos y mostrarla en la UI.
|
|
161
|
+
- Considera usar la librería [dockerode](https://github.com/apocas/dockerode) para interactuar con Docker desde Node.js de forma programática.
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 3. Recomendaciones de Investigación
|
|
166
|
+
|
|
167
|
+
- **Node.js y Streams**: Aprende sobre el módulo `stream` y cómo manejar datos en tiempo real.
|
|
168
|
+
- **Captura de Teclas en Terminal**: Explora cómo funcionan los eventos de teclado en aplicaciones CLI.
|
|
169
|
+
- **Dockerode**: Investiga esta librería para controlar Docker desde Node.js.
|
|
170
|
+
- **React y Eventos Globales**: Si tu UI es React, revisa cómo manejar eventos globales de teclado.
|
|
171
|
+
- **Websockets**: Si decides separar backend y frontend, aprende sobre websockets para comunicación en tiempo real.
|
|
172
|
+
- **UX en CLI**: Investiga buenas prácticas para interfaces de usuario en la terminal (ejemplo: [blessed](https://github.com/chjj/blessed)).
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
## 4. Siguiente Paso Sugerido
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
## 5. Recursos Útiles
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## 6. Planificación Detallada Paso a Paso
|
|
184
|
+
|
|
185
|
+
### Paso 1: Definir el tipo de interfaz
|
|
186
|
+
- Si tu aplicación es CLI y ya tienes Ink, úsalo como framework principal para toda la UI y keybindings.
|
|
187
|
+
- Ink te permite crear componentes, manejar el estado y capturar teclas de forma reactiva, todo en la terminal.
|
|
188
|
+
- Si necesitas algo que Ink no cubre, evalúa otras librerías, pero prioriza Ink para evitar dependencias innecesarias.
|
|
189
|
+
|
|
190
|
+
### Paso 2: Investigar y elegir librerías para keybindings
|
|
191
|
+
- Para CLI con Ink, usa el hook `useInput` para capturar teclas y manejar la navegación y acciones.
|
|
192
|
+
- Haz pruebas simples con `useInput` para capturar teclas y mostrar la tecla presionada o cambiar el estado.
|
|
193
|
+
|
|
194
|
+
### Paso 3: Implementar navegación entre contenedores (↑/↓)
|
|
195
|
+
- Usa `useState` para el índice seleccionado.
|
|
196
|
+
- Renderiza la lista de contenedores con `<Text>` y `<Box>`, resaltando el seleccionado.
|
|
197
|
+
- Usa `useInput` para capturar las flechas y actualizar el índice.
|
|
198
|
+
- Haz que la navegación sea cíclica para mejor UX.
|
|
199
|
+
|
|
200
|
+
### Paso 4: Implementar selección de contenedor (Enter)
|
|
201
|
+
- Al presionar Enter, guarda el contenedor seleccionado en el estado.
|
|
202
|
+
- Muestra un menú de acciones o detalles usando componentes condicionales de Ink.
|
|
203
|
+
|
|
204
|
+
### Paso 5: Implementar acciones sobre el contenedor
|
|
205
|
+
- L: Mostrar logs en vivo
|
|
206
|
+
- S: Mostrar estadísticas en tiempo real
|
|
207
|
+
- R: Reiniciar contenedor
|
|
208
|
+
- I: Iniciar contenedor
|
|
209
|
+
- P: Pausar/Detener contenedor
|
|
210
|
+
- Q: Salir
|
|
211
|
+
- Para cada acción:
|
|
212
|
+
- Usa `useInput` para detectar la tecla y cambiar el estado de la vista.
|
|
213
|
+
- Crea una función que ejecute el comando Docker correspondiente usando `child_process` o `dockerode`.
|
|
214
|
+
- Muestra el resultado en la UI usando componentes Ink.
|
|
215
|
+
|
|
216
|
+
### Paso 6: Integrar logs en tiempo real
|
|
217
|
+
- Usa `docker logs -f <container>` con `child_process.spawn` para obtener logs en streaming.
|
|
218
|
+
- Lee los datos del stream y actualiza el estado con los logs recibidos.
|
|
219
|
+
- Renderiza los logs en tiempo real usando `<Text>` y el estado de React.
|
|
220
|
+
- Permite salir de la vista de logs con una tecla usando `useInput`.
|
|
221
|
+
|
|
222
|
+
### Paso 7: Integrar estadísticas en tiempo real
|
|
223
|
+
- Usa `docker stats <container>` o la API de `dockerode` para obtener stats.
|
|
224
|
+
- Actualiza el estado periódicamente (con `setInterval` o un efecto) y renderiza los stats en la UI.
|
|
225
|
+
- Permite salir de la vista de stats con una tecla usando `useInput`.
|
|
226
|
+
|
|
227
|
+
### Paso 8: Mejorar la experiencia de usuario
|
|
228
|
+
- Agrega mensajes de ayuda o leyenda de teclas usando `<Text>`.
|
|
229
|
+
- Maneja errores de Docker y muestra mensajes claros en la UI.
|
|
230
|
+
- Permite refrescar la lista de contenedores con una tecla usando `useInput`.
|
|
231
|
+
|
|
232
|
+
### Paso 9: Refactorizar y documentar
|
|
233
|
+
- Organiza el código en componentes Ink y hooks reutilizables.
|
|
234
|
+
- Documenta cada función y componente.
|
|
235
|
+
- Escribe un README con ejemplos de uso y keybindings.
|
|
236
|
+
|
|
237
|
+
### Paso 10: Pruebas y mejoras
|
|
238
|
+
- Prueba todos los keybindings y flujos en la terminal.
|
|
239
|
+
- Pide feedback a otros usuarios.
|
|
240
|
+
- Agrega nuevas funcionalidades según necesidades.
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
## 7. Consejos para Aprender y Disfrutar el Proceso
|
|
245
|
+
- Investiga cada librería antes de usarla.
|
|
246
|
+
- Haz pruebas pequeñas y ve integrando poco a poco.
|
|
247
|
+
- Lee la documentación oficial de Docker y Node.js.
|
|
248
|
+
- No dudes en romper cosas: ¡así se aprende!
|
|
249
|
+
- Si te atoras, busca ejemplos en GitHub o StackOverflow.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
- [Node.js Child Process](https://nodejs.org/api/child_process.html)
|
|
253
|
+
- [Docker CLI Reference](https://docs.docker.com/engine/reference/commandline/cli/)
|
|
254
|
+
- [Inquirer.js](https://www.npmjs.com/package/inquirer)
|
|
255
|
+
- [dockerode](https://github.com/apocas/dockerode)
|
|
256
|
+
- [blessed](https://github.com/chjj/blessed)
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
¡Diviértete programando y aprendiendo! Si tienes dudas sobre algún punto, investiga primero y luego pregunta para profundizar.
|
package/dist/App.js
CHANGED
|
@@ -1,28 +1,42 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import { Box, Text, Spacer } from "ink";
|
|
3
3
|
import { useContainers } from "./hooks/useContainers.js";
|
|
4
|
-
import
|
|
4
|
+
import { useControls } from "./hooks/useControls.js";
|
|
5
|
+
import ContainerList from "./components/ContainerList.js";
|
|
6
|
+
import MessageFeedback from "./components/MessageFeedback.js";
|
|
5
7
|
import Header from "./components/Header.js";
|
|
8
|
+
import LogViewer from "./components/LogViewer.js";
|
|
6
9
|
export default function App() {
|
|
7
10
|
var _useContainers = useContainers(),
|
|
8
11
|
containers = _useContainers.containers;
|
|
9
|
-
|
|
12
|
+
var _useControls = useControls(containers),
|
|
13
|
+
selected = _useControls.selected,
|
|
14
|
+
message = _useControls.message,
|
|
15
|
+
messageColor = _useControls.messageColor,
|
|
16
|
+
showLogs = _useControls.showLogs,
|
|
17
|
+
logs = _useControls.logs,
|
|
18
|
+
exitLogs = _useControls.exitLogs;
|
|
19
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Box, {
|
|
10
20
|
flexDirection: "column",
|
|
11
21
|
borderStyle: "round",
|
|
12
22
|
borderColor: "cyan",
|
|
13
23
|
padding: 1
|
|
14
24
|
}, /*#__PURE__*/React.createElement(Header, {
|
|
15
25
|
count: containers.length
|
|
16
|
-
}), /*#__PURE__*/React.createElement(Text, null, " "), containers.length === 0 ? /*#__PURE__*/React.createElement(Text, null, "No containers found") :
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
}), /*#__PURE__*/React.createElement(Text, null, " "), containers.length === 0 ? /*#__PURE__*/React.createElement(Text, null, "No containers found") : /*#__PURE__*/React.createElement(ContainerList, {
|
|
27
|
+
containers: containers,
|
|
28
|
+
selected: selected
|
|
29
|
+
}), /*#__PURE__*/React.createElement(Spacer, null), /*#__PURE__*/React.createElement(MessageFeedback, {
|
|
30
|
+
message: message,
|
|
31
|
+
color: messageColor
|
|
32
|
+
}), /*#__PURE__*/React.createElement(Text, null, "Use \u2191/\u2193 for navigation"), /*#__PURE__*/React.createElement(Text, null, "\u2022I to initiate selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022P to stop selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022L to view logs of selected container"), /*#__PURE__*/React.createElement(Text, null, "\u2022Q to quit"), /*#__PURE__*/React.createElement(Box, {
|
|
33
|
+
justifyContent: "flex-end",
|
|
34
|
+
width: "100%"
|
|
23
35
|
}, /*#__PURE__*/React.createElement(Text, {
|
|
24
36
|
dimColor: true
|
|
25
|
-
}, "
|
|
26
|
-
|
|
27
|
-
|
|
37
|
+
}, "Crafted by Carlos Cochero \u2022 2025"))), showLogs && /*#__PURE__*/React.createElement(LogViewer, {
|
|
38
|
+
logs: logs,
|
|
39
|
+
onExit: exitLogs,
|
|
40
|
+
container: containers[selected]
|
|
41
|
+
}));
|
|
28
42
|
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
import ContainerRow from "./ContainerRow.js";
|
|
4
|
+
export default function ContainerList(_ref) {
|
|
5
|
+
var containers = _ref.containers,
|
|
6
|
+
selected = _ref.selected;
|
|
7
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, containers.map(function (container, i) {
|
|
8
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
9
|
+
key: container.id,
|
|
10
|
+
flexDirection: "row",
|
|
11
|
+
alignItems: "center"
|
|
12
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
13
|
+
color: i === selected ? "green" : undefined
|
|
14
|
+
}, i === selected ? "➤" : " "), /*#__PURE__*/React.createElement(ContainerRow, {
|
|
15
|
+
container: container,
|
|
16
|
+
isSelected: i === selected
|
|
17
|
+
}));
|
|
18
|
+
}));
|
|
19
|
+
}
|
|
@@ -11,7 +11,7 @@ function _arrayWithHoles(r) { if (Array.isArray(r)) return r; }
|
|
|
11
11
|
import React, { useState, useEffect } from "react";
|
|
12
12
|
import { Box, Text } from "ink";
|
|
13
13
|
import chalk from "chalk";
|
|
14
|
-
import { getStats } from "../dockerService.js";
|
|
14
|
+
import { getStats } from "../helpers/dockerService.js";
|
|
15
15
|
import StatsBar from "./StatsBar.js";
|
|
16
16
|
var colorByState = function colorByState(state) {
|
|
17
17
|
if (state === "running") return chalk.greenBright("🟢 RUNNING");
|
|
@@ -67,8 +67,8 @@ export default function ContainerRow(_ref) {
|
|
|
67
67
|
return /*#__PURE__*/React.createElement(Box, {
|
|
68
68
|
flexDirection: "column",
|
|
69
69
|
marginBottom: 1
|
|
70
|
-
}, /*#__PURE__*/React.createElement(Text, null, chalk.cyan(name.padEnd(20)), " ", chalk.gray(image.padEnd(20)), " ", state === "running" ? chalk.greenBright("🟢 RUNNING") : chalk.redBright("\uD83D\uDD34 ".concat(state.toUpperCase()))), state === "running" && /*#__PURE__*/React.createElement(StatsBar, {
|
|
70
|
+
}, /*#__PURE__*/React.createElement(Text, null, chalk.cyan(name.padEnd(20)), " ", chalk.gray(image.padEnd(20)), " ", state === "running" ? chalk.greenBright("🟢 RUNNING") : chalk.redBright("\uD83D\uDD34 ".concat(state.toUpperCase())), " ", chalk.yellow(container.ports), " ", state === "running" && /*#__PURE__*/React.createElement(StatsBar, {
|
|
71
71
|
cpu: parseFloat(stats.cpuPercent),
|
|
72
72
|
mem: parseFloat(stats.memPercent)
|
|
73
|
-
}));
|
|
73
|
+
})));
|
|
74
74
|
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Text, useInput } from "ink";
|
|
3
|
+
export default function LogViewer(_ref) {
|
|
4
|
+
var logs = _ref.logs,
|
|
5
|
+
onExit = _ref.onExit,
|
|
6
|
+
container = _ref.container;
|
|
7
|
+
useInput(function (input, key) {
|
|
8
|
+
if (key.escape) {
|
|
9
|
+
onExit();
|
|
10
|
+
}
|
|
11
|
+
});
|
|
12
|
+
var visibleLogs = logs.slice(-15);
|
|
13
|
+
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Text, {
|
|
14
|
+
color: "green"
|
|
15
|
+
}, (container === null || container === void 0 ? void 0 : container.name) || "Container", " logs, press ESC to exit"), visibleLogs.length === 0 ? /*#__PURE__*/React.createElement(Text, {
|
|
16
|
+
dimColor: true
|
|
17
|
+
}, "No logs...") : visibleLogs.map(function (line, idx) {
|
|
18
|
+
return /*#__PURE__*/React.createElement(Text, {
|
|
19
|
+
key: idx
|
|
20
|
+
}, line);
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import React from "react";
|
|
2
|
+
import { Box, Text } from "ink";
|
|
3
|
+
export default function MessageFeedback(_ref) {
|
|
4
|
+
var message = _ref.message,
|
|
5
|
+
color = _ref.color;
|
|
6
|
+
if (!message) return null;
|
|
7
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
8
|
+
marginBottom: 1
|
|
9
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
10
|
+
color: color
|
|
11
|
+
}, message));
|
|
12
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
function _regenerator() { /*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/babel/babel/blob/main/packages/babel-helpers/LICENSE */ var e, t, r = "function" == typeof Symbol ? Symbol : {}, n = r.iterator || "@@iterator", o = r.toStringTag || "@@toStringTag"; function i(r, n, o, i) { var c = n && n.prototype instanceof Generator ? n : Generator, u = Object.create(c.prototype); return _regeneratorDefine2(u, "_invoke", function (r, n, o) { var i, c, u, f = 0, p = o || [], y = !1, G = { p: 0, n: 0, v: e, a: d, f: d.bind(e, 4), d: function d(t, r) { return i = t, c = 0, u = e, G.n = r, a; } }; function d(r, n) { for (c = r, u = n, t = 0; !y && f && !o && t < p.length; t++) { var o, i = p[t], d = G.p, l = i[2]; r > 3 ? (o = l === n) && (u = i[(c = i[4]) ? 5 : (c = 3, 3)], i[4] = i[5] = e) : i[0] <= d && ((o = r < 2 && d < i[1]) ? (c = 0, G.v = n, G.n = i[1]) : d < l && (o = r < 3 || i[0] > n || n > l) && (i[4] = r, i[5] = n, G.n = l, c = 0)); } if (o || r > 1) return a; throw y = !0, n; } return function (o, p, l) { if (f > 1) throw TypeError("Generator is already running"); for (y && 1 === p && d(p, l), c = p, u = l; (t = c < 2 ? e : u) || !y;) { i || (c ? c < 3 ? (c > 1 && (G.n = -1), d(c, u)) : G.n = u : G.v = u); try { if (f = 2, i) { if (c || (o = "next"), t = i[o]) { if (!(t = t.call(i, u))) throw TypeError("iterator result is not an object"); if (!t.done) return t; u = t.value, c < 2 && (c = 0); } else 1 === c && (t = i["return"]) && t.call(i), c < 2 && (u = TypeError("The iterator does not provide a '" + o + "' method"), c = 1); i = e; } else if ((t = (y = G.n < 0) ? u : r.call(n, G)) !== a) break; } catch (t) { i = e, c = 1, u = t; } finally { f = 1; } } return { value: t, done: y }; }; }(r, o, i), !0), u; } var a = {}; function Generator() {} function GeneratorFunction() {} function GeneratorFunctionPrototype() {} t = Object.getPrototypeOf; var c = [][n] ? t(t([][n]())) : (_regeneratorDefine2(t = {}, n, function () { return this; }), t), u = GeneratorFunctionPrototype.prototype = Generator.prototype = Object.create(c); function f(e) { return Object.setPrototypeOf ? Object.setPrototypeOf(e, GeneratorFunctionPrototype) : (e.__proto__ = GeneratorFunctionPrototype, _regeneratorDefine2(e, o, "GeneratorFunction")), e.prototype = Object.create(u), e; } return GeneratorFunction.prototype = GeneratorFunctionPrototype, _regeneratorDefine2(u, "constructor", GeneratorFunctionPrototype), _regeneratorDefine2(GeneratorFunctionPrototype, "constructor", GeneratorFunction), GeneratorFunction.displayName = "GeneratorFunction", _regeneratorDefine2(GeneratorFunctionPrototype, o, "GeneratorFunction"), _regeneratorDefine2(u), _regeneratorDefine2(u, o, "Generator"), _regeneratorDefine2(u, n, function () { return this; }), _regeneratorDefine2(u, "toString", function () { return "[object Generator]"; }), (_regenerator = function _regenerator() { return { w: i, m: f }; })(); }
|
|
2
|
+
function _regeneratorDefine2(e, r, n, t) { var i = Object.defineProperty; try { i({}, "", {}); } catch (e) { i = 0; } _regeneratorDefine2 = function _regeneratorDefine(e, r, n, t) { function o(r, n) { _regeneratorDefine2(e, r, function (e) { return this._invoke(r, n, e); }); } r ? i ? i(e, r, { value: n, enumerable: !t, configurable: !t, writable: !t }) : e[r] = n : (o("next", 0), o("throw", 1), o("return", 2)); }, _regeneratorDefine2(e, r, n, t); }
|
|
3
|
+
function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
|
|
4
|
+
function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
|
|
5
|
+
// Helper para manejar acciones docker con feedback visual y validación
|
|
6
|
+
export function handleAction(_x) {
|
|
7
|
+
return _handleAction.apply(this, arguments);
|
|
8
|
+
}
|
|
9
|
+
function _handleAction() {
|
|
10
|
+
_handleAction = _asyncToGenerator(/*#__PURE__*/_regenerator().m(function _callee(_ref) {
|
|
11
|
+
var containers, selected, actionFn, actionLabel, setMessage, setMessageColor, stateCheck, c, _t;
|
|
12
|
+
return _regenerator().w(function (_context) {
|
|
13
|
+
while (1) switch (_context.p = _context.n) {
|
|
14
|
+
case 0:
|
|
15
|
+
containers = _ref.containers, selected = _ref.selected, actionFn = _ref.actionFn, actionLabel = _ref.actionLabel, setMessage = _ref.setMessage, setMessageColor = _ref.setMessageColor, stateCheck = _ref.stateCheck;
|
|
16
|
+
c = containers[selected];
|
|
17
|
+
if (c) {
|
|
18
|
+
_context.n = 1;
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
return _context.a(2);
|
|
22
|
+
case 1:
|
|
23
|
+
if (!(stateCheck && stateCheck(c))) {
|
|
24
|
+
_context.n = 2;
|
|
25
|
+
break;
|
|
26
|
+
}
|
|
27
|
+
setMessage(stateCheck(c));
|
|
28
|
+
setMessageColor("red");
|
|
29
|
+
setTimeout(function () {
|
|
30
|
+
return setMessage("");
|
|
31
|
+
}, 2000);
|
|
32
|
+
return _context.a(2);
|
|
33
|
+
case 2:
|
|
34
|
+
setMessage("".concat(actionLabel, " container..."));
|
|
35
|
+
setMessageColor("green");
|
|
36
|
+
_context.p = 3;
|
|
37
|
+
_context.n = 4;
|
|
38
|
+
return actionFn(c.id);
|
|
39
|
+
case 4:
|
|
40
|
+
setMessage("".concat(actionLabel, " container..."));
|
|
41
|
+
setMessageColor("green");
|
|
42
|
+
setTimeout(function () {
|
|
43
|
+
return setMessage("");
|
|
44
|
+
}, 3000);
|
|
45
|
+
_context.n = 6;
|
|
46
|
+
break;
|
|
47
|
+
case 5:
|
|
48
|
+
_context.p = 5;
|
|
49
|
+
_t = _context.v;
|
|
50
|
+
setMessage("Failed to ".concat(actionLabel.toLowerCase(), " container."));
|
|
51
|
+
setMessageColor("red");
|
|
52
|
+
setTimeout(function () {
|
|
53
|
+
return setMessage("");
|
|
54
|
+
}, 3000);
|
|
55
|
+
case 6:
|
|
56
|
+
return _context.a(2);
|
|
57
|
+
}
|
|
58
|
+
}, _callee, null, [[3, 5]]);
|
|
59
|
+
}));
|
|
60
|
+
return _handleAction.apply(this, arguments);
|
|
61
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawn } from "child_process";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Ejecuta una acción de Docker sobre un contenedor y maneja feedback visual.
|
|
5
|
+
* @param {Object} params
|
|
6
|
+
* @param {Array} params.containers - Lista de contenedores.
|
|
7
|
+
* @param {number} params.selected - Índice del contenedor seleccionado.
|
|
8
|
+
* @param {string} params.action - Acción docker (start, stop, restart).
|
|
9
|
+
* @param {string} params.actionLabel - Texto para feedback (Starting, Stopping, etc).
|
|
10
|
+
* @param {Function} params.setMessage - Setter de mensaje visual.
|
|
11
|
+
* @param {Function} params.setMessageColor - Setter de color del mensaje.
|
|
12
|
+
*/
|
|
13
|
+
export function handleDockerAction(_ref) {
|
|
14
|
+
var containers = _ref.containers,
|
|
15
|
+
selected = _ref.selected,
|
|
16
|
+
action = _ref.action,
|
|
17
|
+
actionLabel = _ref.actionLabel,
|
|
18
|
+
setMessage = _ref.setMessage,
|
|
19
|
+
setMessageColor = _ref.setMessageColor;
|
|
20
|
+
if (containers[selected]) {
|
|
21
|
+
var c = containers[selected];
|
|
22
|
+
var id = c.id || c.name;
|
|
23
|
+
// Validación de estado
|
|
24
|
+
if (action === "start" && (c.state === "running" || c.status === "running")) {
|
|
25
|
+
setMessage("Container is already running.");
|
|
26
|
+
setMessageColor("red");
|
|
27
|
+
setTimeout(function () {
|
|
28
|
+
return setMessage("");
|
|
29
|
+
}, 2000);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (action === "stop" && (c.state === "exited" || c.status === "exited" || c.state === "stopped" || c.status === "stopped")) {
|
|
33
|
+
setMessage("Container is already stopped.");
|
|
34
|
+
setMessageColor("red");
|
|
35
|
+
setTimeout(function () {
|
|
36
|
+
return setMessage("");
|
|
37
|
+
}, 2000);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
setMessage("".concat(actionLabel, " container..."));
|
|
41
|
+
setMessageColor("green");
|
|
42
|
+
var child = spawn("docker", [action, id]);
|
|
43
|
+
child.on("close", function () {
|
|
44
|
+
setMessage("".concat(actionLabel, " container..."));
|
|
45
|
+
setMessageColor("green");
|
|
46
|
+
setTimeout(function () {
|
|
47
|
+
return setMessage("");
|
|
48
|
+
}, 3000);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
}
|