cdd-cli 3.2.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.es.md +222 -0
- package/README.md +108 -26
- package/dist/App.js +7 -1
- package/dist/components/ContainerCreationPrompt.js +26 -10
- package/dist/components/ControlsHUD.js +106 -0
- package/dist/components/SuggestionPanel.js +16 -7
- package/dist/components/UsageMenu.js +1 -1
- package/dist/helpers/appState.js +26 -0
- package/dist/helpers/constants.js +60 -21
- package/dist/helpers/dockerHubService.js +111 -0
- package/dist/helpers/dockerService/serviceComponents/containerExec.js +211 -0
- package/dist/helpers/logger.js +1 -1
- package/dist/hooks/creation/useContainerActions.js +22 -5
- package/dist/hooks/creation/useContainerCreation.js +414 -107
- package/dist/hooks/useContainerCommandRouter.js +13 -3
- package/dist/hooks/useControls.js +28 -7
- package/dist/hooks/useShellMode.js +101 -0
- package/dist/index.js +10 -1
- package/package.json +4 -1
- package/.atl/skill-registry.md +0 -30
- package/.editorconfig +0 -12
- package/.eslintrc.json +0 -30
- package/.github/ISSUE_TEMPLATE/bug_report.md +0 -30
- package/.github/ISSUE_TEMPLATE/feature_request.md +0 -19
- package/.github/PULL_REQUEST_TEMPLATE/pull_request.md +0 -14
- package/.github/workflows/ci.yml +0 -32
- package/.github/workflows/docs.yml +0 -42
- package/.prettierignore +0 -4
- package/.prettierrc +0 -8
- package/CHANGELOG.md +0 -165
- package/CODE_OF_CONDUCT.md +0 -12
- package/CONTRIBUTING.md +0 -35
- package/SECURITY.md +0 -5
- package/__mocks__/ink.cjs +0 -17
- package/_config.yml +0 -13
- package/babel.config.cjs +0 -23
- package/coverage/clover.xml +0 -694
- package/coverage/coverage-final.json +0 -21
- package/coverage/lcov-report/base.css +0 -224
- package/coverage/lcov-report/block-navigation.js +0 -87
- package/coverage/lcov-report/components/ContainerCreationPrompt.jsx.html +0 -376
- package/coverage/lcov-report/components/PromptField.jsx.html +0 -229
- package/coverage/lcov-report/components/SuggestionPanel.jsx.html +0 -244
- package/coverage/lcov-report/components/index.html +0 -146
- package/coverage/lcov-report/favicon.png +0 -0
- package/coverage/lcov-report/helpers/constants.js.html +0 -298
- package/coverage/lcov-report/helpers/containerOptionsBuilder.js.html +0 -244
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerActions.js.html +0 -931
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/containerList.js.html +0 -307
- package/coverage/lcov-report/helpers/dockerService/serviceComponents/index.html +0 -131
- package/coverage/lcov-report/helpers/imageNameUtils.js.html +0 -181
- package/coverage/lcov-report/helpers/index.html +0 -191
- package/coverage/lcov-report/helpers/logger.js.html +0 -424
- package/coverage/lcov-report/helpers/safeCall.js.html +0 -139
- package/coverage/lcov-report/helpers/validationHelpers.js.html +0 -346
- package/coverage/lcov-report/hooks/creation/index.html +0 -146
- package/coverage/lcov-report/hooks/creation/useContainerActions.js.html +0 -313
- package/coverage/lcov-report/hooks/creation/useContainerCreation.js.html +0 -805
- package/coverage/lcov-report/hooks/creation/useLogsViewer.js.html +0 -238
- package/coverage/lcov-report/hooks/debug/index.html +0 -116
- package/coverage/lcov-report/hooks/debug/useDebugLogs.js.html +0 -187
- package/coverage/lcov-report/hooks/index.html +0 -161
- package/coverage/lcov-report/hooks/navigation/index.html +0 -116
- package/coverage/lcov-report/hooks/navigation/useContainerSelection.js.html +0 -169
- package/coverage/lcov-report/hooks/useContainerCommandRouter.js.html +0 -454
- package/coverage/lcov-report/hooks/useControls.js.html +0 -826
- package/coverage/lcov-report/hooks/useEraseConfirmation.js.html +0 -202
- package/coverage/lcov-report/hooks/useExitHandler.js.html +0 -292
- package/coverage/lcov-report/index.html +0 -206
- package/coverage/lcov-report/prettify.css +0 -1
- package/coverage/lcov-report/prettify.js +0 -2
- package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
- package/coverage/lcov-report/sorter.js +0 -210
- package/coverage/lcov.info +0 -1402
- package/fix-imports.cjs +0 -54
- package/jest.config.cjs +0 -14
- package/src/App.jsx +0 -96
- package/src/components/ContainerCreationPrompt.jsx +0 -97
- package/src/components/ContainerList.jsx +0 -32
- package/src/components/ContainerRow.jsx +0 -84
- package/src/components/ContainerSection.jsx +0 -24
- package/src/components/Footer.jsx +0 -15
- package/src/components/Header.jsx +0 -41
- package/src/components/LogViewer.jsx +0 -36
- package/src/components/MessageFeedback.jsx +0 -25
- package/src/components/PromptField.jsx +0 -48
- package/src/components/StatsBar.jsx +0 -70
- package/src/components/SuggestionPanel.jsx +0 -53
- package/src/components/UsageMenu.jsx +0 -52
- package/src/helpers/appInfo.js +0 -23
- package/src/helpers/constants.js +0 -71
- package/src/helpers/containerOptionsBuilder.js +0 -53
- package/src/helpers/dockerService/dockerService.js +0 -28
- package/src/helpers/dockerService/serviceComponents/containerActions.js +0 -282
- package/src/helpers/dockerService/serviceComponents/containerList.js +0 -74
- package/src/helpers/dockerService/serviceComponents/containerLogs.js +0 -60
- package/src/helpers/dockerService/serviceComponents/containerStats.js +0 -69
- package/src/helpers/dockerService/serviceComponents/imageUtils.js +0 -32
- package/src/helpers/exitWithMessage.js +0 -32
- package/src/helpers/imageNameUtils.js +0 -32
- package/src/helpers/logger.js +0 -113
- package/src/helpers/safeCall.js +0 -18
- package/src/helpers/validationHelpers.js +0 -87
- package/src/hooks/creation/useContainerActions.js +0 -76
- package/src/hooks/creation/useContainerCreation.js +0 -240
- package/src/hooks/creation/useLogsViewer.js +0 -51
- package/src/hooks/debug/useDebugLogs.js +0 -34
- package/src/hooks/navigation/useContainerSelection.js +0 -28
- package/src/hooks/useContainerCommandRouter.js +0 -123
- package/src/hooks/useContainerStats.js +0 -44
- package/src/hooks/useContainers.js +0 -26
- package/src/hooks/useControls.js +0 -247
- package/src/hooks/useEraseConfirmation.js +0 -39
- package/src/hooks/useExitHandler.js +0 -69
- package/src/index.js +0 -16
- package/test/ContainerCreationPrompt.dom.test.js +0 -68
- package/test/SuggestionPanel.dom.test.js +0 -53
- package/test/constants.test.js +0 -34
- package/test/containerActions.test.js +0 -229
- package/test/containerList.test.js +0 -79
- package/test/containerOptionsBuilder.test.js +0 -36
- package/test/jest.setup.js +0 -7
- package/test/safeCall.test.js +0 -25
- package/test/useContainerCreation.dom.test.js +0 -264
- package/test/useControls.dom.test.js +0 -313
- package/test/validationHelpers.test.js +0 -111
package/README.es.md
ADDED
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
# CDD — CLI Docker Dashboard
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://img.shields.io/npm/v/cdd-cli?color=blue&label=npm%20package" alt="npm version"/>
|
|
5
|
+
<img src="https://img.shields.io/npm/dt/cdd-cli?color=green&label=downloads" alt="npm downloads"/>
|
|
6
|
+
<a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
|
|
7
|
+
<img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
|
|
8
|
+
</a>
|
|
9
|
+
<a href="https://deepwiki.com/Caertos/cdd"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
> **Un dashboard de Docker para la terminal — monitorea, gestiona y crea contenedores sin salir del teclado.**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🎉 Novedades en v4.1
|
|
17
|
+
|
|
18
|
+
**Abrí un shell dentro de cualquier contenedor con una sola tecla.**
|
|
19
|
+
|
|
20
|
+
Presiona `S` y CDD te lleva a un shell interactivo completo (`bash` o `sh`) dentro del contenedor seleccionado — sin necesidad de escribir `docker exec`.
|
|
21
|
+
|
|
22
|
+
- **Shell auto-detectado** — CDD sondea el contenedor y elige `bash` o `sh` automáticamente
|
|
23
|
+
- **Soporte de terminal completo** — ejecutá `psql`, `python3`, `node`, `redis-cli`, o cualquier comando dentro del contenedor
|
|
24
|
+
- **Salida limpia** — escribí `exit` o presioná `Ctrl+D` para volver al dashboard
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Versiones anteriores
|
|
29
|
+
|
|
30
|
+
### v3.2 — Asistente de creación interactivo
|
|
31
|
+
|
|
32
|
+
Olvídate de los flags de `docker run`, las variables de entorno olvidadas y los tags `:latest` que fallan en silencio. Presiona `C` y CDD te guía para crear un contenedor en segundos:
|
|
33
|
+
|
|
34
|
+
- **20 perfiles de imagen curados** disponibles sin conexión — postgres, redis, nginx, node, mysql, mongo y más
|
|
35
|
+
- **Tags por defecto que realmente funcionan**: `postgres:17-alpine`, `redis:7-alpine`, `nginx:1.27-alpine` — sin más fallos silenciosos por `:latest`
|
|
36
|
+
- **Búsqueda en vivo en Docker Hub** con un solo `Tab` — con indicador `[searching Docker Hub...]` para que siempre sepas qué está pasando
|
|
37
|
+
- **Sugerencias contextuales de variables de entorno** — ¿creando un contenedor de Postgres? CDD sugiere `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` automáticamente
|
|
38
|
+
- **HUD sensible al contexto** — solo se muestran las teclas que tienen sentido en ese momento, nada más
|
|
39
|
+
|
|
40
|
+
Así debería sentirse la experiencia de desarrollo.
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Características
|
|
45
|
+
|
|
46
|
+
- 🐳 Vista en vivo de todos los contenedores Docker con estadísticas de CPU/memoria
|
|
47
|
+
- 🔄 Auto-refresco cada pocos segundos — siempre actualizado
|
|
48
|
+
- ⌨️ Acciones controladas por teclado: iniciar, detener, reiniciar, ver logs, eliminar
|
|
49
|
+
- 🎨 Estados de contenedor codificados por color y retroalimentación visual
|
|
50
|
+
- ✨ **Asistente de creación interactivo** — configuración paso a paso con perfiles curados y búsqueda en Hub
|
|
51
|
+
- 🪵 Streaming de logs en tiempo real para el contenedor seleccionado
|
|
52
|
+
- 🐛 Panel de debug en vivo activable con la tecla `D`
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Instalación global
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
npm install -g cdd-cli
|
|
60
|
+
cdd
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Inicio rápido (local)
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
git clone https://github.com/caertos/cdd.git
|
|
69
|
+
cd cdd
|
|
70
|
+
npm install
|
|
71
|
+
npm run build
|
|
72
|
+
node dist/index.js
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Para usar como comando global durante el desarrollo:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
npm link
|
|
79
|
+
cdd
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## Uso
|
|
85
|
+
|
|
86
|
+
Usa `↑` / `↓` para navegar por los contenedores. Atajos de teclado disponibles:
|
|
87
|
+
|
|
88
|
+
| Tecla | Acción |
|
|
89
|
+
|-------|--------|
|
|
90
|
+
| `↑` / `↓` | Navegar la lista de contenedores |
|
|
91
|
+
| `I` | Iniciar el contenedor seleccionado |
|
|
92
|
+
| `P` | Detener el contenedor seleccionado |
|
|
93
|
+
| `R` | Reiniciar el contenedor seleccionado |
|
|
94
|
+
| `C` | Abrir el asistente de creación |
|
|
95
|
+
| `L` | Ver logs del contenedor seleccionado en tiempo real |
|
|
96
|
+
| `S` | Abrir shell interactivo dentro del contenedor seleccionado |
|
|
97
|
+
| `E` | Eliminar el contenedor seleccionado — requiere confirmación |
|
|
98
|
+
| `D` | Activar/desactivar panel de debug en vivo |
|
|
99
|
+
| `Q` | Salir |
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## El Asistente de Creación
|
|
104
|
+
|
|
105
|
+
Presiona `C` desde el dashboard para abrir el asistente. Un **HUD sensible al contexto** en la parte inferior siempre muestra qué teclas están activas en cada paso — sin adivinar.
|
|
106
|
+
|
|
107
|
+
### Paso 0 — Imagen
|
|
108
|
+
|
|
109
|
+
Escribe para filtrar entre **20 perfiles offline curados** (postgres, redis, nginx, node, mysql, mongo, python, golang y más). Los resultados aparecen al instante.
|
|
110
|
+
|
|
111
|
+
Presiona **`Tab`** en cualquier momento para buscar en Docker Hub en vivo. Un indicador `[searching Docker Hub...]` confirma que la búsqueda está en curso. Usa `↑` / `↓` para navegar las sugerencias y `Enter` para seleccionar.
|
|
112
|
+
|
|
113
|
+
**Tags inteligentes por defecto:** al seleccionar un perfil de imagen se aplica automáticamente un tag conocido y funcional — `postgres:17-alpine`, `redis:7-alpine`, `nginx:1.27-alpine`, etc. Sin más contenedores que fallan en silencio por un `:latest` desactualizado.
|
|
114
|
+
|
|
115
|
+
### Paso 1 — Nombre del contenedor
|
|
116
|
+
|
|
117
|
+
Texto libre. Dale a tu contenedor un nombre memorable.
|
|
118
|
+
|
|
119
|
+
### Paso 2 — Mapeo de puertos
|
|
120
|
+
|
|
121
|
+
Ingresa un mapeo de puertos en formato `HOST:CONTENEDOR`, por ejemplo `8080:80`, `5432:5432`. Deja en blanco para omitir.
|
|
122
|
+
|
|
123
|
+
### Paso 3 — Variables de entorno
|
|
124
|
+
|
|
125
|
+
Ingresa pares `CLAVE=VALOR` de a uno. Las **sugerencias contextuales** muestran variables recomendadas para la imagen seleccionada:
|
|
126
|
+
|
|
127
|
+
| Imagen | Variables sugeridas |
|
|
128
|
+
|--------|---------------------|
|
|
129
|
+
| postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
|
|
130
|
+
| mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
|
|
131
|
+
| redis | *(sin variables requeridas)* |
|
|
132
|
+
| mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
|
|
133
|
+
| node / nginx / otros | Variables de runtime comunes según corresponda |
|
|
134
|
+
|
|
135
|
+
Presiona `Enter` en una línea vacía para terminar y crear el contenedor.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Shell Interactivo
|
|
140
|
+
|
|
141
|
+
Presiona `S` desde el dashboard con un contenedor en ejecución seleccionado. CDD hará lo siguiente:
|
|
142
|
+
|
|
143
|
+
1. Detectará el shell disponible dentro del contenedor (`bash` o `sh`)
|
|
144
|
+
2. Abrirá una sesión de terminal interactiva completa
|
|
145
|
+
3. Te dejará en el shell del contenedor
|
|
146
|
+
|
|
147
|
+
Desde ahí podés ejecutar cualquier comando — `psql` para PostgreSQL, `python3` para Python, `node` para Node.js, `redis-cli` para Redis, etc.
|
|
148
|
+
|
|
149
|
+
Escribí `exit` o presioná `Ctrl+D` para salir del shell y volver al dashboard de CDD.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Requisitos
|
|
154
|
+
|
|
155
|
+
- Node.js >= 18
|
|
156
|
+
- Docker instalado y ejecutándose (CDD se conecta al socket local de Docker)
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## Desarrollo
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm install
|
|
164
|
+
npm run build # compila src/ → dist/
|
|
165
|
+
node dist/index.js # ejecuta desde la salida compilada
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
Vuelve a ejecutar `npm run build` después de cualquier cambio en el código fuente. Usa `npm link` para probar el comando global `cdd` localmente.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Tests
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
npm test
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Los tests están en `test/` y cubren helpers, servicios y hooks.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Logging
|
|
183
|
+
|
|
184
|
+
Por defecto CDD muestra mensajes `info`, `warn` y `error`. Para diagnósticos más detallados:
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
CDD_LOG_LEVEL=debug cdd
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Presiona `D` dentro del dashboard para activar el panel de debug en vivo. Presiona `D` nuevamente para ocultarlo.
|
|
191
|
+
|
|
192
|
+
Para capturar los logs en un archivo:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
CDD_LOG_LEVEL=debug cdd > cdd-debug.log 2>&1
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Solución de problemas
|
|
201
|
+
|
|
202
|
+
- **¿No ves contenedores?** Asegúrate de que Docker esté ejecutándose y de que tu usuario tenga acceso al socket de Docker.
|
|
203
|
+
- **¿Errores de permisos en Linux/macOS?** Prueba con `sudo cdd` o agrega tu usuario al grupo `docker`.
|
|
204
|
+
- **¿Windows?** Ejecuta la terminal como Administrador.
|
|
205
|
+
- **¿Falta el directorio `dist/`?** Ejecuta `npm run build` — está en `.gitignore` y no se incluye en el repositorio.
|
|
206
|
+
- **¿La búsqueda del asistente no funciona?** Verifica tu conexión a internet. Los perfiles offline siempre funcionan sin acceso a la red.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## Contribuciones
|
|
211
|
+
|
|
212
|
+
Consulta [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Licencia
|
|
217
|
+
|
|
218
|
+
MIT/ISC — ver [`LICENSE`](LICENSE).
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
🇬🇧 [Read in English](README.md)
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# CDD — CLI Docker Dashboard
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://img.shields.io/npm/v/cdd-cli?color=blue&label=npm%20package" alt="npm version"/>
|
|
@@ -6,24 +6,50 @@
|
|
|
6
6
|
<a href="https://github.com/caertos/cdd/actions/workflows/ci.yml">
|
|
7
7
|
<img src="https://github.com/caertos/cdd/actions/workflows/ci.yml/badge.svg?branch=main" alt="CI status" />
|
|
8
8
|
</a>
|
|
9
|
-
<a href="https://caertos.github.io/cdd/">
|
|
10
|
-
<img src="https://img.shields.io/badge/docs-GitHub%20Pages-blue" alt="Docs" />
|
|
11
|
-
</a>
|
|
12
9
|
<a href="https://deepwiki.com/Caertos/cdd"><img src="https://deepwiki.com/badge.svg" alt="Ask DeepWiki"></a>
|
|
13
10
|
</p>
|
|
14
11
|
|
|
15
|
-
A terminal dashboard for
|
|
12
|
+
> **A terminal dashboard for Docker containers — monitor, manage, and create, all without leaving your keyboard.**
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## 🎉 What's new in v4.1
|
|
17
|
+
|
|
18
|
+
**Open a shell inside any container with a single keystroke.**
|
|
19
|
+
|
|
20
|
+
Press `S` and CDD drops you into a full interactive shell (`bash` or `sh`) inside the selected container — no `docker exec` typing needed.
|
|
21
|
+
|
|
22
|
+
- **Auto-detected shell** — CDD probes the container and picks `bash` or `sh` automatically
|
|
23
|
+
- **Full terminal support** — run `psql`, `python3`, `node`, `redis-cli`, or any command inside the container
|
|
24
|
+
- **Clean exit** — type `exit` or press `Ctrl+D` to return to the dashboard
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Previous releases
|
|
29
|
+
|
|
30
|
+
### v3.2 — Interactive creation wizard
|
|
31
|
+
|
|
32
|
+
Forget `docker run` flags, forgotten env vars, and broken `:latest` tags. Press `C` and CDD guides you through creating a container in seconds:
|
|
33
|
+
|
|
34
|
+
- **20 curated image profiles** available offline — postgres, redis, nginx, node, mysql, mongo, and more
|
|
35
|
+
- **Smart default tags** that actually work: `postgres:17-alpine`, `redis:7-alpine`, `nginx:1.27-alpine` — no more silent `:latest` failures
|
|
36
|
+
- **Live Docker Hub search** with a single `Tab` keystroke — with a `[searching Docker Hub...]` indicator so you always know what's happening
|
|
37
|
+
- **Contextual env var hints** — creating a Postgres container? CDD suggests `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` automatically
|
|
38
|
+
- **Context-sensitive HUD** — only the keys that make sense right now are shown, nothing more
|
|
39
|
+
|
|
40
|
+
This is what developer experience should feel like.
|
|
16
41
|
|
|
17
42
|
---
|
|
18
43
|
|
|
19
44
|
## Features
|
|
20
45
|
|
|
21
|
-
- 🐳
|
|
22
|
-
- 🔄 Auto-refresh every few seconds
|
|
23
|
-
- ⌨️ Keyboard
|
|
24
|
-
-
|
|
25
|
-
-
|
|
26
|
-
-
|
|
46
|
+
- 🐳 Live view of all Docker containers with CPU/memory stats
|
|
47
|
+
- 🔄 Auto-refresh every few seconds — always up to date
|
|
48
|
+
- ⌨️ Keyboard-driven actions: start, stop, restart, log streaming, removal
|
|
49
|
+
- 🎨 Color-coded container states and visual feedback
|
|
50
|
+
- ✨ **Interactive creation wizard** — step-by-step container setup with curated profiles and live Hub search
|
|
51
|
+
- 🪵 Real-time log streaming for any selected container
|
|
52
|
+
- 🐛 Toggleable live debug panel (`D` key)
|
|
27
53
|
|
|
28
54
|
---
|
|
29
55
|
|
|
@@ -46,7 +72,7 @@ npm run build
|
|
|
46
72
|
node dist/index.js
|
|
47
73
|
```
|
|
48
74
|
|
|
49
|
-
To use
|
|
75
|
+
To use as a global command during development:
|
|
50
76
|
|
|
51
77
|
```bash
|
|
52
78
|
npm link
|
|
@@ -57,25 +83,77 @@ cdd
|
|
|
57
83
|
|
|
58
84
|
## Usage
|
|
59
85
|
|
|
60
|
-
Use
|
|
86
|
+
Use `↑` / `↓` to navigate containers. Available keyboard shortcuts:
|
|
61
87
|
|
|
62
88
|
| Key | Action |
|
|
63
89
|
|-----|--------|
|
|
90
|
+
| `↑` / `↓` | Navigate container list |
|
|
64
91
|
| `I` | Start selected container |
|
|
65
92
|
| `P` | Stop selected container |
|
|
66
93
|
| `R` | Restart selected container |
|
|
67
|
-
| `C` |
|
|
68
|
-
| `L` |
|
|
94
|
+
| `C` | Open creation wizard |
|
|
95
|
+
| `L` | Stream logs for selected container |
|
|
96
|
+
| `S` | Open interactive shell inside selected container |
|
|
69
97
|
| `E` | Erase (remove) selected container — confirmation required |
|
|
70
|
-
| `D` | Toggle live debug
|
|
98
|
+
| `D` | Toggle live debug panel |
|
|
71
99
|
| `Q` | Quit |
|
|
72
100
|
|
|
73
101
|
---
|
|
74
102
|
|
|
103
|
+
## The Creation Wizard
|
|
104
|
+
|
|
105
|
+
Press `C` from the dashboard to launch the wizard. A **context-sensitive HUD** at the bottom always shows which keys are active at each step — no guessing required.
|
|
106
|
+
|
|
107
|
+
### Step 0 — Image
|
|
108
|
+
|
|
109
|
+
Type to filter through **20 curated offline profiles** (postgres, redis, nginx, node, mysql, mongo, python, golang, and more). Results appear instantly.
|
|
110
|
+
|
|
111
|
+
Press **`Tab`** at any time to search Docker Hub live. A `[searching Docker Hub...]` indicator confirms the search is running. Use `↑` / `↓` to navigate suggestions, `Enter` to select.
|
|
112
|
+
|
|
113
|
+
**Smart default tags:** selecting an image profile automatically applies a known-good tag — `postgres:17-alpine`, `redis:7-alpine`, `nginx:1.27-alpine`, etc. No more containers that fail silently because of a stale `:latest`.
|
|
114
|
+
|
|
115
|
+
### Step 1 — Container name
|
|
116
|
+
|
|
117
|
+
Free-text input. Give your container a memorable name.
|
|
118
|
+
|
|
119
|
+
### Step 2 — Port mapping
|
|
120
|
+
|
|
121
|
+
Enter a port mapping in `HOST:CONTAINER` format, e.g. `8080:80`, `5432:5432`. Leave blank to skip.
|
|
122
|
+
|
|
123
|
+
### Step 3 — Environment variables
|
|
124
|
+
|
|
125
|
+
Enter `KEY=VALUE` pairs one at a time. **Contextual hints** show recommended variables for the selected image:
|
|
126
|
+
|
|
127
|
+
| Image | Suggested vars |
|
|
128
|
+
|-------|---------------|
|
|
129
|
+
| postgres | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB` |
|
|
130
|
+
| mysql | `MYSQL_ROOT_PASSWORD`, `MYSQL_DATABASE` |
|
|
131
|
+
| redis | *(no required vars)* |
|
|
132
|
+
| mongo | `MONGO_INITDB_ROOT_USERNAME`, `MONGO_INITDB_ROOT_PASSWORD` |
|
|
133
|
+
| node / nginx / others | Common runtime vars as applicable |
|
|
134
|
+
|
|
135
|
+
Press `Enter` on an empty line to finish and create the container.
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Interactive Shell
|
|
140
|
+
|
|
141
|
+
Press `S` from the dashboard while a running container is selected. CDD will:
|
|
142
|
+
|
|
143
|
+
1. Detect the available shell inside the container (`bash` or `sh`)
|
|
144
|
+
2. Open a full interactive terminal session
|
|
145
|
+
3. Drop you into the container's shell
|
|
146
|
+
|
|
147
|
+
From there you can run any command — `psql` for PostgreSQL, `python3` for Python, `node` for Node.js, `redis-cli` for Redis, etc.
|
|
148
|
+
|
|
149
|
+
Type `exit` or press `Ctrl+D` to leave the shell and return to the CDD dashboard.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
75
153
|
## Requirements
|
|
76
154
|
|
|
77
155
|
- Node.js >= 18
|
|
78
|
-
- Docker installed and running (
|
|
156
|
+
- Docker installed and running (CDD connects to the local Docker socket)
|
|
79
157
|
|
|
80
158
|
---
|
|
81
159
|
|
|
@@ -87,7 +165,7 @@ npm run build # compile src/ → dist/
|
|
|
87
165
|
node dist/index.js # run from compiled output
|
|
88
166
|
```
|
|
89
167
|
|
|
90
|
-
Re-run `npm run build` after any source changes.
|
|
168
|
+
Re-run `npm run build` after any source changes. Use `npm link` to test the global `cdd` command locally.
|
|
91
169
|
|
|
92
170
|
---
|
|
93
171
|
|
|
@@ -97,21 +175,19 @@ Re-run `npm run build` after any source changes.
|
|
|
97
175
|
npm test
|
|
98
176
|
```
|
|
99
177
|
|
|
100
|
-
Tests
|
|
178
|
+
Tests live in `test/` and cover helpers, services, and hooks.
|
|
101
179
|
|
|
102
180
|
---
|
|
103
181
|
|
|
104
182
|
## Logging
|
|
105
183
|
|
|
106
|
-
By default
|
|
184
|
+
By default CDD shows `info`, `warn`, and `error` messages. For deeper diagnostics:
|
|
107
185
|
|
|
108
186
|
```bash
|
|
109
187
|
CDD_LOG_LEVEL=debug cdd
|
|
110
|
-
# or
|
|
111
|
-
CDD_LOG_LEVEL=debug node dist/index.js
|
|
112
188
|
```
|
|
113
189
|
|
|
114
|
-
Press `D` inside the
|
|
190
|
+
Press `D` inside the dashboard to toggle the live debug panel in real time. Press `D` again to hide it.
|
|
115
191
|
|
|
116
192
|
To capture logs to a file:
|
|
117
193
|
|
|
@@ -123,9 +199,11 @@ CDD_LOG_LEVEL=debug cdd > cdd-debug.log 2>&1
|
|
|
123
199
|
|
|
124
200
|
## Troubleshooting
|
|
125
201
|
|
|
126
|
-
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
202
|
+
- **No containers visible?** Make sure Docker is running and your user has access to the Docker socket.
|
|
203
|
+
- **Permission errors on Linux/macOS?** Try `sudo cdd` or add your user to the `docker` group.
|
|
204
|
+
- **Windows?** Run your terminal as Administrator.
|
|
205
|
+
- **`dist/` missing?** Run `npm run build` — it's in `.gitignore` and not committed.
|
|
206
|
+
- **Wizard search not working?** Check your internet connection. Offline profiles always work without network access.
|
|
129
207
|
|
|
130
208
|
---
|
|
131
209
|
|
|
@@ -138,3 +216,7 @@ See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
|
138
216
|
## License
|
|
139
217
|
|
|
140
218
|
MIT/ISC — see [`LICENSE`](LICENSE).
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
🇪🇸 [Ver en Español](README.es.md)
|
package/dist/App.js
CHANGED
|
@@ -32,7 +32,13 @@ export default function App() {
|
|
|
32
32
|
portInput: controls.portInput,
|
|
33
33
|
envInput: controls.envInput,
|
|
34
34
|
message: controls.message,
|
|
35
|
-
messageColor: controls.messageColor
|
|
35
|
+
messageColor: controls.messageColor,
|
|
36
|
+
suggestions: controls.creation.suggestions,
|
|
37
|
+
selectedSuggestionIndex: controls.creation.selectedSuggestionIndex,
|
|
38
|
+
visibleOffset: controls.creation.visibleOffset,
|
|
39
|
+
isSearchingHub: controls.creation.isSearchingHub,
|
|
40
|
+
hubResults: controls.creation.hubResults,
|
|
41
|
+
hasSuggestedEnv: controls.creation.hasSuggestedEnv
|
|
36
42
|
});
|
|
37
43
|
}
|
|
38
44
|
return /*#__PURE__*/React.createElement(React.Fragment, null, /*#__PURE__*/React.createElement(Box, {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import { Box
|
|
2
|
+
import { Box } from 'ink';
|
|
3
3
|
import { PromptField, PromptMessage } from './PromptField.js';
|
|
4
4
|
import { SuggestionPanel } from './SuggestionPanel.js';
|
|
5
|
+
import { ControlsHUD } from './ControlsHUD.js';
|
|
5
6
|
import PropTypes from 'prop-types';
|
|
6
7
|
|
|
7
8
|
/**
|
|
@@ -35,9 +36,15 @@ export default function ContainerCreationPrompt(props) {
|
|
|
35
36
|
_props$selectedSugges = props.selectedSuggestionIndex,
|
|
36
37
|
selectedSuggestionIndex = _props$selectedSugges === void 0 ? -1 : _props$selectedSugges,
|
|
37
38
|
_props$visibleOffset = props.visibleOffset,
|
|
38
|
-
visibleOffset = _props$visibleOffset === void 0 ? 0 : _props$visibleOffset
|
|
39
|
+
visibleOffset = _props$visibleOffset === void 0 ? 0 : _props$visibleOffset,
|
|
40
|
+
_props$hubResults = props.hubResults,
|
|
41
|
+
hubResults = _props$hubResults === void 0 ? null : _props$hubResults,
|
|
42
|
+
_props$isSearchingHub = props.isSearchingHub,
|
|
43
|
+
isSearchingHub = _props$isSearchingHub === void 0 ? false : _props$isSearchingHub,
|
|
44
|
+
_props$hasSuggestedEn = props.hasSuggestedEnv,
|
|
45
|
+
hasSuggestedEnv = _props$hasSuggestedEn === void 0 ? false : _props$hasSuggestedEn;
|
|
39
46
|
var prompts = [{
|
|
40
|
-
label: 'Name of the image to create (e.g., nginx:
|
|
47
|
+
label: 'Name of the image to create (e.g., nginx:1.27-alpine):',
|
|
41
48
|
value: imageName,
|
|
42
49
|
required: true
|
|
43
50
|
}, {
|
|
@@ -57,7 +64,9 @@ export default function ContainerCreationPrompt(props) {
|
|
|
57
64
|
label = _ref.label,
|
|
58
65
|
value = _ref.value,
|
|
59
66
|
required = _ref.required;
|
|
60
|
-
var
|
|
67
|
+
var activeItems = hubResults !== null && hubResults !== void 0 ? hubResults : suggestions;
|
|
68
|
+
var showSuggestions = step === 0 && (isSearchingHub || activeItems.length > 0);
|
|
69
|
+
var hasSuggestions = (suggestions === null || suggestions === void 0 ? void 0 : suggestions.length) > 0 || (hubResults === null || hubResults === void 0 ? void 0 : hubResults.length) > 0;
|
|
61
70
|
return /*#__PURE__*/React.createElement(Box, {
|
|
62
71
|
flexDirection: "column",
|
|
63
72
|
borderStyle: "round",
|
|
@@ -68,15 +77,19 @@ export default function ContainerCreationPrompt(props) {
|
|
|
68
77
|
value: value,
|
|
69
78
|
required: required
|
|
70
79
|
}), showSuggestions && /*#__PURE__*/React.createElement(SuggestionPanel, {
|
|
71
|
-
items:
|
|
80
|
+
items: activeItems,
|
|
72
81
|
selectedIndex: selectedSuggestionIndex,
|
|
73
|
-
visibleOffset: visibleOffset
|
|
82
|
+
visibleOffset: visibleOffset,
|
|
83
|
+
isLoading: isSearchingHub
|
|
74
84
|
}), /*#__PURE__*/React.createElement(PromptMessage, {
|
|
75
85
|
message: message,
|
|
76
86
|
color: messageColor
|
|
77
|
-
}), /*#__PURE__*/React.createElement(
|
|
78
|
-
|
|
79
|
-
|
|
87
|
+
}), /*#__PURE__*/React.createElement(ControlsHUD, {
|
|
88
|
+
step: step,
|
|
89
|
+
hasSuggestions: hasSuggestions,
|
|
90
|
+
isSearchingHub: isSearchingHub,
|
|
91
|
+
hasSuggestedEnv: hasSuggestedEnv
|
|
92
|
+
}));
|
|
80
93
|
}
|
|
81
94
|
ContainerCreationPrompt.propTypes = {
|
|
82
95
|
step: PropTypes.number.isRequired,
|
|
@@ -88,7 +101,10 @@ ContainerCreationPrompt.propTypes = {
|
|
|
88
101
|
messageColor: PropTypes.string,
|
|
89
102
|
suggestions: PropTypes.arrayOf(PropTypes.string),
|
|
90
103
|
selectedSuggestionIndex: PropTypes.number,
|
|
91
|
-
visibleOffset: PropTypes.number
|
|
104
|
+
visibleOffset: PropTypes.number,
|
|
105
|
+
hubResults: PropTypes.arrayOf(PropTypes.string),
|
|
106
|
+
isSearchingHub: PropTypes.bool,
|
|
107
|
+
hasSuggestedEnv: PropTypes.bool
|
|
92
108
|
};
|
|
93
109
|
|
|
94
110
|
// Named export for test compatibility with jest ESM interop
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { Box, Text } from 'ink';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Returns the contextual keyboard hints for the current wizard state.
|
|
6
|
+
*
|
|
7
|
+
* @param {number} step - Current wizard step (0-3)
|
|
8
|
+
* @param {boolean} hasSuggestions - Whether suggestions/hub results are visible
|
|
9
|
+
* @param {boolean} isSearchingHub - Whether a Hub search is in progress
|
|
10
|
+
* @param {boolean} [hasSuggestedEnv=false] - Whether the image has suggestedEnv entries
|
|
11
|
+
* @returns {{ key: string, label: string }[]}
|
|
12
|
+
*/
|
|
13
|
+
function getHints(step, hasSuggestions, isSearchingHub) {
|
|
14
|
+
var hasSuggestedEnv = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
|
15
|
+
if (step === 0) {
|
|
16
|
+
if (hasSuggestions) {
|
|
17
|
+
return [{
|
|
18
|
+
key: '↑↓',
|
|
19
|
+
label: 'Navigate'
|
|
20
|
+
}, {
|
|
21
|
+
key: 'Enter',
|
|
22
|
+
label: 'Select'
|
|
23
|
+
}, {
|
|
24
|
+
key: 'Esc',
|
|
25
|
+
label: 'Cancel'
|
|
26
|
+
}];
|
|
27
|
+
}
|
|
28
|
+
var hints = [];
|
|
29
|
+
if (!isSearchingHub) hints.push({
|
|
30
|
+
key: 'Tab',
|
|
31
|
+
label: 'Search Hub'
|
|
32
|
+
});
|
|
33
|
+
hints.push({
|
|
34
|
+
key: '↑↓',
|
|
35
|
+
label: 'Browse'
|
|
36
|
+
});
|
|
37
|
+
hints.push({
|
|
38
|
+
key: 'Enter',
|
|
39
|
+
label: 'Confirm'
|
|
40
|
+
});
|
|
41
|
+
hints.push({
|
|
42
|
+
key: 'Esc',
|
|
43
|
+
label: 'Cancel'
|
|
44
|
+
});
|
|
45
|
+
return hints;
|
|
46
|
+
}
|
|
47
|
+
if (step === 3) {
|
|
48
|
+
var _hints = [];
|
|
49
|
+
if (hasSuggestedEnv) _hints.push({
|
|
50
|
+
key: 'Tab',
|
|
51
|
+
label: 'Insert next env'
|
|
52
|
+
});
|
|
53
|
+
_hints.push({
|
|
54
|
+
key: 'Enter',
|
|
55
|
+
label: 'Continue'
|
|
56
|
+
});
|
|
57
|
+
_hints.push({
|
|
58
|
+
key: 'Esc',
|
|
59
|
+
label: 'Cancel'
|
|
60
|
+
});
|
|
61
|
+
return _hints;
|
|
62
|
+
}
|
|
63
|
+
return [{
|
|
64
|
+
key: 'Enter',
|
|
65
|
+
label: 'Continue'
|
|
66
|
+
}, {
|
|
67
|
+
key: 'Esc',
|
|
68
|
+
label: 'Cancel'
|
|
69
|
+
}];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Displays a row of contextual keyboard shortcut hints for the active wizard step.
|
|
74
|
+
*
|
|
75
|
+
* @param {Object} props
|
|
76
|
+
* @param {number} props.step - Current wizard step
|
|
77
|
+
* @param {boolean} [props.hasSuggestions=false] - Whether suggestions/hub results are visible
|
|
78
|
+
* @param {boolean} [props.isSearchingHub=false] - Whether a Hub search is in progress
|
|
79
|
+
* @param {boolean} [props.hasSuggestedEnv=false] - Whether the image has suggestedEnv entries
|
|
80
|
+
*/
|
|
81
|
+
function ControlsHUD(_ref) {
|
|
82
|
+
var step = _ref.step,
|
|
83
|
+
_ref$hasSuggestions = _ref.hasSuggestions,
|
|
84
|
+
hasSuggestions = _ref$hasSuggestions === void 0 ? false : _ref$hasSuggestions,
|
|
85
|
+
_ref$isSearchingHub = _ref.isSearchingHub,
|
|
86
|
+
isSearchingHub = _ref$isSearchingHub === void 0 ? false : _ref$isSearchingHub,
|
|
87
|
+
_ref$hasSuggestedEnv = _ref.hasSuggestedEnv,
|
|
88
|
+
hasSuggestedEnv = _ref$hasSuggestedEnv === void 0 ? false : _ref$hasSuggestedEnv;
|
|
89
|
+
var hints = getHints(step, hasSuggestions, isSearchingHub, hasSuggestedEnv);
|
|
90
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
91
|
+
flexDirection: "row",
|
|
92
|
+
columnGap: 2
|
|
93
|
+
}, hints.map(function (_ref2) {
|
|
94
|
+
var key = _ref2.key,
|
|
95
|
+
label = _ref2.label;
|
|
96
|
+
return /*#__PURE__*/React.createElement(Box, {
|
|
97
|
+
key: key
|
|
98
|
+
}, /*#__PURE__*/React.createElement(Text, {
|
|
99
|
+
color: "cyan"
|
|
100
|
+
}, "[", key, "]"), /*#__PURE__*/React.createElement(Text, {
|
|
101
|
+
dimColor: true
|
|
102
|
+
}, " ", label));
|
|
103
|
+
}));
|
|
104
|
+
}
|
|
105
|
+
export default ControlsHUD;
|
|
106
|
+
export { getHints, ControlsHUD };
|
|
@@ -19,26 +19,35 @@ export function SuggestionPanel(_ref) {
|
|
|
19
19
|
selectedIndex = _ref.selectedIndex,
|
|
20
20
|
visibleOffset = _ref.visibleOffset,
|
|
21
21
|
_ref$maxVisible = _ref.maxVisible,
|
|
22
|
-
maxVisible = _ref$maxVisible === void 0 ? 6 : _ref$maxVisible
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
maxVisible = _ref$maxVisible === void 0 ? 6 : _ref$maxVisible,
|
|
23
|
+
_ref$isLoading = _ref.isLoading,
|
|
24
|
+
isLoading = _ref$isLoading === void 0 ? false : _ref$isLoading;
|
|
25
|
+
var hasItems = items && items.length > 0;
|
|
26
|
+
if (!hasItems && !isLoading) return null;
|
|
27
|
+
var visible = hasItems ? items.slice(visibleOffset, visibleOffset + maxVisible) : [];
|
|
25
28
|
return /*#__PURE__*/React.createElement(Box, {
|
|
26
29
|
flexDirection: "column",
|
|
27
30
|
borderStyle: "single",
|
|
28
31
|
borderColor: "cyan",
|
|
29
|
-
paddingX: 1
|
|
32
|
+
paddingX: 1,
|
|
33
|
+
"data-testid": "suggestion-panel"
|
|
30
34
|
}, visible.map(function (item, localIdx) {
|
|
31
35
|
var globalIdx = visibleOffset + localIdx;
|
|
32
36
|
var isFocused = globalIdx === selectedIndex;
|
|
33
37
|
return /*#__PURE__*/React.createElement(Text, {
|
|
34
38
|
key: item,
|
|
35
|
-
color: isFocused ? 'cyan' : undefined
|
|
39
|
+
color: isFocused ? 'cyan' : undefined,
|
|
40
|
+
dimColor: !isFocused
|
|
36
41
|
}, isFocused ? "\u203A ".concat(item) : " ".concat(item));
|
|
37
|
-
})
|
|
42
|
+
}), isLoading && /*#__PURE__*/React.createElement(Text, {
|
|
43
|
+
dimColor: true,
|
|
44
|
+
color: "gray"
|
|
45
|
+
}, ' [searching Docker Hub...]'));
|
|
38
46
|
}
|
|
39
47
|
SuggestionPanel.propTypes = {
|
|
40
48
|
items: PropTypes.arrayOf(PropTypes.string).isRequired,
|
|
41
49
|
selectedIndex: PropTypes.number.isRequired,
|
|
42
50
|
visibleOffset: PropTypes.number.isRequired,
|
|
43
|
-
maxVisible: PropTypes.number
|
|
51
|
+
maxVisible: PropTypes.number,
|
|
52
|
+
isLoading: PropTypes.bool
|
|
44
53
|
};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import { Box, Text } from 'ink';
|
|
3
|
-
var SHORTCUTS = ['Use ↑/↓ for navigation', 'I to initiate selected container', 'P to stop selected container', 'R to restart selected container', 'C to create a container', 'L to view logs of selected container', 'D to toggle debug log panel', 'E to remove selected container', 'Q to quit'];
|
|
3
|
+
var SHORTCUTS = ['Use ↑/↓ for navigation', 'I to initiate selected container', 'P to stop selected container', 'R to restart selected container', 'C to create a container', 'L to view logs of selected container', 'S to open shell in selected container', 'D to toggle debug log panel', 'E to remove selected container', 'Q to quit'];
|
|
4
4
|
var GRID_COLUMNS = 3;
|
|
5
5
|
|
|
6
6
|
/**
|