diff --git a/.gitignore b/.gitignore index a6f5532..ee966c4 100755 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,10 @@ npm-debug.log* yarn-debug.log* yarn-error.log* +# Python cache +__pycache__/ +*.py[cod] +*$py.class # Arquivos compilados *.so diff --git a/Dockerfile.installer-test b/Dockerfile.installer-test new file mode 100644 index 0000000..6720415 --- /dev/null +++ b/Dockerfile.installer-test @@ -0,0 +1,73 @@ +# Dockerfile para testar o instalador do FazAI v2.0 +# Simula ambiente limpo Ubuntu para validar processo de instalação completo + +FROM ubuntu:22.04 + +# Evitar prompts interativos durante instalação +ENV DEBIAN_FRONTEND=noninteractive + +# Instalar dependências básicas necessárias para o FazAI +RUN apt-get update && apt-get install -y \ + # Utilitários básicos + sudo \ + curl \ + wget \ + git \ + lsb-release \ + ca-certificates \ + gnupg \ + # Ferramentas de desenvolvimento + build-essential \ + cmake \ + pkg-config \ + # Python e pip + python3 \ + python3-pip \ + python3-dev \ + python3-venv \ + # Node.js será instalado pelo installer do FazAI + # Bibliotecas de desenvolvimento + libssl-dev \ + libffi-dev \ + libcurl4-openssl-dev \ + # Limpeza + && rm -rf /var/lib/apt/lists/* + +# Criar usuário não-root para simular ambiente real +RUN useradd -ms /bin/bash fazai && \ + echo "fazai ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \ + usermod -aG sudo fazai + +# Preparar diretórios do FazAI +RUN mkdir -p /opt/fazai /etc/fazai /var/log/fazai /var/lib/fazai && \ + chown -R fazai:fazai /opt/fazai /etc/fazai /var/log/fazai /var/lib/fazai + +# Definir diretório de trabalho +WORKDIR /workspace + +# Copiar código fonte (quando usado com volume mount) +# COPY . /workspace/ + +# Ajustar permissões para o usuário fazai +RUN chown -R fazai:fazai /workspace + +# Trocar para usuário não-root +USER fazai + +# Variáveis de ambiente para o FazAI +ENV FAZAI_PORT=3120 +ENV NODE_ENV=development +ENV FAZAI_LOG_LEVEL=debug + +# Script de entrada para testes +COPY --chown=fazai:fazai docker-installer-test-entrypoint.sh /usr/local/bin/ +RUN chmod +x /usr/local/bin/docker-installer-test-entrypoint.sh + +# Comando padrão: entrar no bash para testes manuais +CMD ["bash"] + +# Labels para organização +LABEL maintainer="Roger Luft " +LABEL description="Container para testar instalador do FazAI v2.0" +LABEL version="2.0" +LABEL project="FazAI" \ No newline at end of file diff --git a/bin/fazai-containers b/bin/fazai-containers new file mode 100755 index 0000000..9376dd5 --- /dev/null +++ b/bin/fazai-containers @@ -0,0 +1,27 @@ +#!/bin/bash +# FazAI Container Manager CLI +# Wrapper para a ferramenta TUI de gerenciamento de containers + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +CONTAINER_TUI="/opt/fazai/tools/container_manager_tui.py" + +# Fallback para desenvolvimento +if [ ! -f "$CONTAINER_TUI" ]; then + CONTAINER_TUI="$SCRIPT_DIR/../opt/fazai/tools/container_manager_tui.py" +fi + +# Verificar se o arquivo existe +if [ ! -f "$CONTAINER_TUI" ]; then + echo "Erro: Arquivo $CONTAINER_TUI não encontrado" + echo "Execute a instalação completa do FazAI primeiro" + exit 1 +fi + +# Verificar dependências +if ! python3 -c "import textual" >/dev/null 2>&1; then + echo "Instalando dependência textual..." + pip install textual +fi + +# Executar TUI +exec python3 "$CONTAINER_TUI" "$@" \ No newline at end of file diff --git a/docker-installer-test-entrypoint.sh b/docker-installer-test-entrypoint.sh new file mode 100755 index 0000000..fa9515a --- /dev/null +++ b/docker-installer-test-entrypoint.sh @@ -0,0 +1,203 @@ +#!/bin/bash +# Entrypoint para container de teste do instalador FazAI +# Script para automatizar testes de instalação/desinstalação + +set -e + +# Cores para output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +log() { + echo -e "${BLUE}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1" +} + +log_success() { + echo -e "${GREEN}[SUCCESS]${NC} $1" +} + +log_warning() { + echo -e "${YELLOW}[WARNING]${NC} $1" +} + +log_error() { + echo -e "${RED}[ERROR]${NC} $1" +} + +# Função para testar instalação +test_installation() { + log "🚀 Iniciando teste de instalação do FazAI..." + + # Verificar se estamos no diretório correto + if [ ! -f "install.sh" ]; then + log_error "Arquivo install.sh não encontrado no diretório atual" + log "Certifique-se de que o volume está montado corretamente" + return 1 + fi + + # Executar instalação + log "Executando: sudo ./install.sh" + if sudo ./install.sh; then + log_success "Instalação concluída com sucesso!" + else + log_error "Falha na instalação" + return 1 + fi + + # Verificar se o CLI foi instalado + if command -v fazai >/dev/null 2>&1; then + log_success "CLI 'fazai' encontrado no PATH" + fazai --version || log_warning "Falha ao obter versão do fazai" + else + log_warning "CLI 'fazai' não encontrado no PATH" + fi + + # Verificar se o serviço está configurado + if systemctl list-unit-files | grep -q fazai; then + log_success "Serviço fazai encontrado no systemd" + else + log_warning "Serviço fazai não encontrado no systemd" + fi + + # Verificar estrutura de diretórios + for dir in "/opt/fazai" "/etc/fazai" "/var/log/fazai"; do + if [ -d "$dir" ]; then + log_success "Diretório $dir criado" + else + log_warning "Diretório $dir não encontrado" + fi + done + + log_success "Teste de instalação concluído!" +} + +# Função para testar desinstalação +test_uninstallation() { + log "🧹 Iniciando teste de desinstalação do FazAI..." + + if [ ! -f "uninstall.sh" ]; then + log_error "Arquivo uninstall.sh não encontrado" + return 1 + fi + + # Executar desinstalação + log "Executando: sudo ./uninstall.sh" + if sudo ./uninstall.sh; then + log_success "Desinstalação concluída com sucesso!" + else + log_error "Falha na desinstalação" + return 1 + fi + + # Verificar limpeza + if ! command -v fazai >/dev/null 2>&1; then + log_success "CLI 'fazai' removido do PATH" + else + log_warning "CLI 'fazai' ainda presente no PATH" + fi + + log_success "Teste de desinstalação concluído!" +} + +# Função para executar testes automatizados +run_automated_tests() { + log "🔄 Executando testes automatizados..." + + # Teste completo: instalação + desinstalação + test_installation + + log "Aguardando 5 segundos antes da desinstalação..." + sleep 5 + + test_uninstallation + + log_success "Testes automatizados concluídos!" +} + +# Função para modo interativo +interactive_mode() { + log "🎯 Modo interativo do container de teste FazAI" + echo "" + echo "Comandos disponíveis:" + echo " install - Testar instalação" + echo " uninstall - Testar desinstalação" + echo " auto - Executar testes automatizados" + echo " shell - Abrir shell bash" + echo " help - Mostrar esta ajuda" + echo "" + + while true; do + read -p "fazai-test> " cmd + + case "$cmd" in + "install") + test_installation + ;; + "uninstall") + test_uninstallation + ;; + "auto") + run_automated_tests + ;; + "shell"|"bash") + log "Abrindo shell bash..." + exec bash + ;; + "help"|"h") + interactive_mode + ;; + "exit"|"quit"|"q") + log "Saindo..." + exit 0 + ;; + "") + # Enter vazio, apenas continuar + ;; + *) + log_warning "Comando não reconhecido: $cmd" + echo "Digite 'help' para ver comandos disponíveis" + ;; + esac + done +} + +# Main +main() { + log "🐳 Container de teste do instalador FazAI v2.0" + log "Usuário atual: $(whoami)" + log "Diretório: $(pwd)" + log "Sistema: $(lsb_release -d | cut -f2)" + echo "" + + # Verificar argumentos + case "${1:-interactive}" in + "install") + test_installation + ;; + "uninstall") + test_uninstallation + ;; + "auto") + run_automated_tests + ;; + "interactive"|"") + interactive_mode + ;; + *) + log_error "Argumento inválido: $1" + echo "Uso: $0 [install|uninstall|auto|interactive]" + exit 1 + ;; + esac +} + +# Trap para limpeza +trap 'log "Interrompido pelo usuário"' INT TERM + +# Executar main se chamado diretamente +if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then + main "$@" +fi \ No newline at end of file diff --git a/docs/CONTAINER_MANAGER.md b/docs/CONTAINER_MANAGER.md new file mode 100644 index 0000000..1add633 --- /dev/null +++ b/docs/CONTAINER_MANAGER.md @@ -0,0 +1,242 @@ +# 🐳 FazAI Container Manager TUI + +Interface TUI interativa para gerenciar containers Docker com templates pré-definidos, especialmente otimizada para testes de desenvolvimento do FazAI. + +## 🚀 Funcionalidades + +### 📋 Templates Pré-definidos +- **Ubuntu 22.04 LTS**: Sistema limpo para testes gerais +- **FazAI Installer Test**: Container especializado para testar o instalador +- **Qdrant Vector Database**: Banco vetorial para RAG do FazAI +- **Nginx Development**: Servidor web para testes +- **Node.js 22 Development**: Ambiente Node.js para desenvolvimento + +### 🔧 Operações de Container +- ✅ Listar containers existentes +- ✅ Criar containers a partir de templates +- ✅ Visualizar status e detalhes +- ✅ Limpar containers parados e imagens órfãs +- 🔄 Start/Stop/Restart containers (em desenvolvimento) +- 📄 Visualizar logs (em desenvolvimento) + +## 📦 Instalação + +O Container Manager TUI é instalado automaticamente com o FazAI. Para uso manual: + +```bash +# Instalar dependência +pip install textual + +# Tornar executável +chmod +x /opt/fazai/tools/container_manager_tui.py +``` + +## 🎯 Uso + +### Via npm script (recomendado) +```bash +npm run containers-tui +``` + +### Via CLI wrapper +```bash +fazai-containers +``` + +### Execução direta +```bash +python3 /opt/fazai/tools/container_manager_tui.py +``` + +## 🖥️ Interface + +### Abas Principais +1. **Containers**: Lista containers existentes com status +2. **Templates**: Mostra templates disponíveis +3. **Images**: Lista imagens Docker locais +4. **Logs**: Visualização de logs (futuro) + +### Controles +- **Tab**: Navegar entre abas +- **Enter**: Selecionar item/executar ação +- **Atalhos**: + - `q`: Sair + - `r`: Atualizar dados + - Botões: Atualizar, Criar Container, Limpar Tudo + +## 🛠️ Templates Detalhados + +### FazAI Installer Test +Template especializado para testar instalação do FazAI: + +```yaml +Template: FazAI Installer Test +Imagem: ubuntu:22.04 (customizada) +Portas: 3120:3120 +Volumes: /workspace (código fonte) +Variáveis: + - FAZAI_PORT=3120 + - NODE_ENV=development +``` + +#### Uso do Template FazAI Installer Test + +1. **Criar container pelo TUI**: + - Selecione a aba "Templates" + - Escolha "FazAI Installer Test" + - Pressione Enter para criar + +2. **Uso manual**: +```bash +# Criar e executar container +docker run -it --name fazai-test \ + -p 3120:3120 \ + -v $(pwd):/workspace \ + -e FAZAI_PORT=3120 \ + -e NODE_ENV=development \ + fazai-installer-test + +# Dentro do container, testar instalação +cd /workspace +sudo ./install.sh + +# Ou usar o script automatizado +docker-installer-test-entrypoint.sh auto +``` + +3. **Script de teste automatizado**: +O container inclui script para testes automatizados: +```bash +# Teste completo (instalação + desinstalação) +docker-installer-test-entrypoint.sh auto + +# Apenas instalação +docker-installer-test-entrypoint.sh install + +# Apenas desinstalação +docker-installer-test-entrypoint.sh uninstall + +# Modo interativo +docker-installer-test-entrypoint.sh interactive +``` + +## 📁 Arquivos Relacionados + +- **TUI Principal**: `/opt/fazai/tools/container_manager_tui.py` +- **CLI Wrapper**: `/bin/fazai-containers` +- **Dockerfile Teste**: `/Dockerfile.installer-test` +- **Script Entrypoint**: `/docker-installer-test-entrypoint.sh` +- **Testes**: `/tests/container_manager.test.sh` + +## 🔍 Desenvolvendo Templates + +Para criar novos templates, edite `CONTAINER_TEMPLATES` em `container_manager_tui.py`: + +```python +"meu-template": { + "name": "Nome Descritivo", + "image": "imagem:tag", + "description": "Descrição do template", + "command": "comando_inicial", + "ports": ["porta_host:porta_container"], + "volumes": ["volume_host:volume_container"], + "env_vars": {"VAR": "valor"}, + "interactive": True/False, + "dockerfile_content": "..." # Opcional +} +``` + +### Campos Obrigatórios +- `name`: Nome exibido na interface +- `image`: Imagem Docker base +- `description`: Descrição do template +- `command`: Comando inicial do container +- `ports`: Lista de mapeamentos de porta +- `volumes`: Lista de volumes montados +- `env_vars`: Dicionário de variáveis de ambiente +- `interactive`: Se o container deve ser interativo + +### Campos Opcionais +- `dockerfile_content`: Conteúdo de Dockerfile customizado + +## 🧪 Testes + +Execute os testes para verificar funcionalidade: + +```bash +# Teste completo +bash tests/container_manager.test.sh + +# Verificar apenas dependências +python3 -c "import textual; print('✓ Textual OK')" + +# Testar funções básicas +python3 -c " +import sys +sys.path.append('/opt/fazai/tools') +import container_manager_tui +result = container_manager_tui.run_docker_command(['--version']) +print('✓ Docker OK' if result['success'] else '✗ Docker ERROR') +" +``` + +## 🐛 Solução de Problemas + +### Erro: "Textual não instalado" +```bash +pip install textual +``` + +### Erro: "Docker não disponível" +Verifique se Docker está instalado e rodando: +```bash +docker --version +sudo systemctl start docker +``` + +### TUI não abre +Verifique se está executando em terminal que suporta TUI: +```bash +# Teste básico +python3 -c "import textual; print('Textual OK')" +``` + +### Permissões Docker +Adicione usuário ao grupo docker: +```bash +sudo usermod -aG docker $USER +# Relogar na sessão +``` + +## 🔄 Roadmap + +### Versão Atual (v1.0) +- ✅ Interface TUI com navegação por abas +- ✅ Templates pré-definidos +- ✅ Criação de containers +- ✅ Limpeza automática +- ✅ Template especializado FazAI Installer Test + +### Próximas Versões +- [ ] Operações de container (start/stop/restart/delete) +- [ ] Visualização de logs em tempo real +- [ ] Editor de templates via interface +- [ ] Importar/exportar configurações de templates +- [ ] Integração com docker-compose +- [ ] Suporte a networks customizadas +- [ ] Monitoramento de recursos (CPU/Memory) +- [ ] Notificações e alertas + +## 📝 Contribuição + +Para contribuir com o Container Manager: + +1. Siga os padrões do FazAI (ver `AGENTS.md`) +2. Use `lower_snake_case` para nomes de arquivos +3. Mantenha logs estruturados (winston JSON) +4. Adicione testes para novas funcionalidades +5. Atualize documentação + +## 📄 Licença + +Mesmo licenciamento do FazAI - Creative Commons Attribution 4.0 International (CC BY 4.0). \ No newline at end of file diff --git a/install.sh b/install.sh index f842e0c..3d84373 100755 --- a/install.sh +++ b/install.sh @@ -1190,7 +1190,8 @@ EOF "opt/fazai/tools/modsecurity.js" \ "opt/fazai/tools/spamexperts.js" \ "opt/fazai/tools/web_search.js" \ - "opt/fazai/tools/cloudflare.js"; do + "opt/fazai/tools/cloudflare.js" \ + "opt/fazai/tools/container_manager_tui.py"; do if [ -f "$t" ]; then if ! copy_with_verification "$t" "/opt/fazai/tools/" "Tool $(basename $t)"; then copy_errors=$((copy_errors+1)) @@ -1237,6 +1238,17 @@ EOF ln -sf /opt/fazai/tools/fazai-tui.sh /usr/local/bin/fazai-tui log "SUCCESS" "Dashboard TUI completo instalado em /usr/local/bin/fazai-tui" fi + + # Instalar Container Manager TUI + if [ -f "bin/fazai-containers" ]; then + if ! copy_with_verification "bin/fazai-containers" "/opt/fazai/bin/" "Container Manager CLI"; then + copy_errors=$((copy_errors+1)) + else + chmod +x /opt/fazai/bin/fazai-containers + ln -sf /opt/fazai/bin/fazai-containers /usr/local/bin/fazai-containers + log "SUCCESS" "Container Manager CLI instalado em /usr/local/bin/fazai-containers" + fi + fi if command -v cargo >/dev/null 2>&1; then log "INFO" "Compilando TUI em Rust..." if cargo build --release --manifest-path=tui/Cargo.toml >/tmp/fazai_tui_build.log 2>&1; then diff --git a/opt/fazai/tools/__pycache__/container_manager_tui.cpython-312.pyc b/opt/fazai/tools/__pycache__/container_manager_tui.cpython-312.pyc new file mode 100644 index 0000000..ceea42d Binary files /dev/null and b/opt/fazai/tools/__pycache__/container_manager_tui.cpython-312.pyc differ diff --git a/opt/fazai/tools/container_manager_tui.py b/opt/fazai/tools/container_manager_tui.py new file mode 100755 index 0000000..6877c65 --- /dev/null +++ b/opt/fazai/tools/container_manager_tui.py @@ -0,0 +1,476 @@ +#!/usr/bin/env python3 +""" +FazAI Container Manager TUI +Interface TUI interativa para gerenciar containers Docker com templates pré-definidos. +Seguindo padrões do FazAI: lower_snake_case para ferramentas, logging estruturado. +""" + +import subprocess +import json +import os +import asyncio +from datetime import datetime +from pathlib import Path + +try: + from textual.app import App, ComposeResult + from textual.widgets import ( + Header, Footer, Button, Static, ListView, ListItem, + Input, Label, TextArea, Tabs, Tab, DataTable, Tree + ) + from textual.containers import Horizontal, Vertical, Container + from textual.reactive import reactive + from textual.message import Message + from textual import events +except ImportError: + print("Erro: Instale a biblioteca Textual: pip install textual") + exit(1) + +# Templates de containers pré-definidos +CONTAINER_TEMPLATES = { + "ubuntu-22.04": { + "name": "Ubuntu 22.04 LTS", + "image": "ubuntu:22.04", + "description": "Sistema Ubuntu limpo para testes gerais", + "command": "bash", + "ports": [], + "volumes": [], + "env_vars": {}, + "interactive": True + }, + "fazai-installer-test": { + "name": "FazAI Installer Test", + "image": "ubuntu:22.04", + "description": "Container para testar o instalador do FazAI", + "command": "bash", + "ports": ["3120:3120"], + "volumes": ["/home/runner/work/FazAI/FazAI:/workspace"], + "env_vars": { + "FAZAI_PORT": "3120", + "NODE_ENV": "development" + }, + "interactive": True, + "dockerfile_content": """FROM ubuntu:22.04 + +# Instala dependências básicas para FazAI +RUN apt-get update && apt-get install -y \\ + sudo curl git build-essential cmake \\ + python3 python3-pip python3-dev \\ + pkg-config libssl-dev libffi-dev \\ + wget lsb-release && \\ + rm -rf /var/lib/apt/lists/* + +# Cria usuário não-root +RUN useradd -ms /bin/bash fazai && \\ + echo "fazai ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers + +WORKDIR /workspace +USER fazai + +CMD ["bash"] +""" + }, + "qdrant-vector-db": { + "name": "Qdrant Vector Database", + "image": "qdrant/qdrant:latest", + "description": "Banco vetorial para RAG do FazAI", + "command": "", + "ports": ["6333:6333"], + "volumes": ["qdrant_storage:/qdrant/storage"], + "env_vars": {}, + "interactive": False + }, + "nginx-dev": { + "name": "Nginx Development", + "image": "nginx:alpine", + "description": "Servidor web para testes", + "command": "", + "ports": ["8080:80"], + "volumes": [], + "env_vars": {}, + "interactive": False + }, + "node-22": { + "name": "Node.js 22 Development", + "image": "node:22-alpine", + "description": "Ambiente Node.js para desenvolvimento", + "command": "sh", + "ports": ["3000:3000"], + "volumes": ["/home/runner/work/FazAI/FazAI:/workspace"], + "env_vars": { + "NODE_ENV": "development" + }, + "interactive": True + } +} + +def run_docker_command(cmd, capture_output=True): + """Executa comando Docker e retorna resultado""" + try: + full_cmd = ["docker"] + cmd + result = subprocess.run( + full_cmd, + capture_output=capture_output, + text=True, + timeout=30 + ) + return { + "success": result.returncode == 0, + "stdout": result.stdout.strip() if capture_output else "", + "stderr": result.stderr.strip() if capture_output else "", + "returncode": result.returncode + } + except subprocess.TimeoutExpired: + return {"success": False, "error": "Timeout executando comando Docker"} + except Exception as e: + return {"success": False, "error": str(e)} + +def get_containers(): + """Lista containers existentes""" + result = run_docker_command(["ps", "-a", "--format", "json"]) + if not result["success"]: + return [] + + containers = [] + for line in result["stdout"].split('\n'): + if line.strip(): + try: + container = json.loads(line) + containers.append({ + "id": container.get("ID", "")[:12], + "name": container.get("Names", "").replace("/", ""), + "image": container.get("Image", ""), + "status": container.get("State", ""), + "ports": container.get("Ports", ""), + "created": container.get("CreatedAt", "") + }) + except json.JSONDecodeError: + continue + return containers + +def get_images(): + """Lista imagens disponíveis""" + result = run_docker_command(["images", "--format", "json"]) + if not result["success"]: + return [] + + images = [] + for line in result["stdout"].split('\n'): + if line.strip(): + try: + image = json.loads(line) + images.append({ + "repository": image.get("Repository", ""), + "tag": image.get("Tag", ""), + "id": image.get("ID", "")[:12], + "size": image.get("Size", ""), + "created": image.get("CreatedAt", "") + }) + except json.JSONDecodeError: + continue + return images + +class ContainerManagerApp(App): + """Aplicação TUI principal para gerenciar containers""" + + CSS = """ + .header { + background: blue; + color: white; + height: 3; + content-align: center middle; + } + + .sidebar { + width: 30%; + background: $panel; + border-right: wide $primary; + } + + .main-content { + width: 70%; + } + + .container-item { + padding: 1; + margin: 1; + border: round $primary; + } + + .template-item { + padding: 1; + margin: 1; + border: round $secondary; + background: $panel; + } + + .status-running { + color: green; + } + + .status-stopped { + color: red; + } + + .status-created { + color: yellow; + } + """ + + current_tab = reactive("containers") + + def compose(self) -> ComposeResult: + """Compõe a interface principal""" + yield Header(show_clock=True) + + with Container(): + yield Static("🐳 FazAI Container Manager TUI", classes="header") + + with Horizontal(): + with Vertical(classes="sidebar"): + yield Tabs("containers", "templates", "images", "logs") + yield Button("Atualizar", id="refresh", variant="primary") + yield Button("Criar Container", id="create", variant="success") + yield Button("Limpar Tudo", id="cleanup", variant="error") + + with Vertical(classes="main-content", id="main-content"): + yield DataTable(id="data-table") + yield TextArea(id="details", read_only=True) + + yield Footer() + + def on_mount(self) -> None: + """Inicialização da aplicação""" + self.title = "FazAI Container Manager" + self.refresh_data() + + def on_tabs_tab_activated(self, event: Tabs.TabActivated) -> None: + """Handler para mudança de aba""" + self.current_tab = event.tab.id + self.refresh_data() + + def refresh_data(self) -> None: + """Atualiza dados na tabela principal""" + table = self.query_one("#data-table", DataTable) + table.clear(columns=True) + + if self.current_tab == "containers": + self.refresh_containers(table) + elif self.current_tab == "templates": + self.refresh_templates(table) + elif self.current_tab == "images": + self.refresh_images(table) + + def refresh_containers(self, table: DataTable) -> None: + """Atualiza lista de containers""" + table.add_columns("ID", "Nome", "Imagem", "Status", "Portas") + + containers = get_containers() + for container in containers: + status_class = f"status-{container['status'].lower()}" + table.add_row( + container["id"], + container["name"], + container["image"], + container["status"], + container["ports"] + ) + + def refresh_templates(self, table: DataTable) -> None: + """Atualiza lista de templates""" + table.add_columns("Template", "Imagem", "Descrição", "Portas") + + for template_id, template in CONTAINER_TEMPLATES.items(): + table.add_row( + template["name"], + template["image"], + template["description"], + ", ".join(template["ports"]) + ) + + def refresh_images(self, table: DataTable) -> None: + """Atualiza lista de imagens""" + table.add_columns("Repositório", "Tag", "ID", "Tamanho", "Criado") + + images = get_images() + for image in images: + table.add_row( + image["repository"], + image["tag"], + image["id"], + image["size"], + image["created"] + ) + + def on_button_pressed(self, event: Button.Pressed) -> None: + """Handler para botões""" + if event.button.id == "refresh": + self.refresh_data() + elif event.button.id == "create": + self.create_container_dialog() + elif event.button.id == "cleanup": + self.cleanup_containers() + + def on_data_table_row_selected(self, event: DataTable.RowSelected) -> None: + """Handler para seleção de linha na tabela""" + details = self.query_one("#details", TextArea) + + if self.current_tab == "containers": + self.show_container_details(event.row_key.value) + elif self.current_tab == "templates": + self.show_template_details(event.row_key.value) + + def show_container_details(self, row_index: int) -> None: + """Mostra detalhes do container selecionado""" + containers = get_containers() + if 0 <= row_index < len(containers): + container = containers[row_index] + + # Obter informações detalhadas + inspect_result = run_docker_command(["inspect", container["id"]]) + + details_text = f"""Container: {container["name"]} ({container["id"]}) +Imagem: {container["image"]} +Status: {container["status"]} +Portas: {container["ports"]} +Criado: {container["created"]} + +Ações disponíveis: +- [Enter] Executar bash no container +- [s] Start [t] Stop [r] Restart [d] Delete +- [l] Ver logs [i] Inspecionar +""" + + details = self.query_one("#details", TextArea) + details.text = details_text + + def show_template_details(self, row_index: int) -> None: + """Mostra detalhes do template selecionado""" + template_items = list(CONTAINER_TEMPLATES.items()) + if 0 <= row_index < len(template_items): + template_id, template = template_items[row_index] + + details_text = f"""Template: {template["name"]} +ID: {template_id} +Imagem: {template["image"]} +Descrição: {template["description"]} + +Configuração: +- Comando: {template["command"]} +- Portas: {", ".join(template["ports"]) if template["ports"] else "Nenhuma"} +- Volumes: {", ".join(template["volumes"]) if template["volumes"] else "Nenhum"} +- Interativo: {"Sim" if template["interactive"] else "Não"} + +Variáveis de ambiente: +""" + + for key, value in template["env_vars"].items(): + details_text += f"- {key}={value}\n" + + if "dockerfile_content" in template: + details_text += "\n--- Dockerfile customizado disponível ---" + + details_text += "\n\n[Enter] Criar container a partir deste template" + + details = self.query_one("#details", TextArea) + details.text = details_text + + def create_container_dialog(self) -> None: + """Abre diálogo para criar container""" + # Para simplicidade, criar a partir do primeiro template + template_id = "fazai-installer-test" + self.create_container_from_template(template_id) + + def create_container_from_template(self, template_id: str) -> None: + """Cria container a partir de template""" + if template_id not in CONTAINER_TEMPLATES: + return + + template = CONTAINER_TEMPLATES[template_id] + + # Gerar nome único + timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") + container_name = f"{template_id}_{timestamp}" + + # Montar comando Docker + docker_cmd = ["run", "-d", "--name", container_name] + + # Adicionar portas + for port in template["ports"]: + docker_cmd.extend(["-p", port]) + + # Adicionar volumes + for volume in template["volumes"]: + docker_cmd.extend(["-v", volume]) + + # Adicionar variáveis de ambiente + for key, value in template["env_vars"].items(): + docker_cmd.extend(["-e", f"{key}={value}"]) + + # Adicionar flags para interativo + if template["interactive"]: + docker_cmd.extend(["-it"]) + + # Adicionar imagem e comando + docker_cmd.append(template["image"]) + if template["command"]: + docker_cmd.append(template["command"]) + + # Executar comando + result = run_docker_command(docker_cmd) + + details = self.query_one("#details", TextArea) + if result["success"]: + details.text = f"✅ Container '{container_name}' criado com sucesso!\n\nID: {result['stdout']}" + self.refresh_data() + else: + details.text = f"❌ Erro ao criar container:\n{result.get('stderr', result.get('error', 'Erro desconhecido'))}" + + def cleanup_containers(self) -> None: + """Remove containers parados e imagens não utilizadas""" + details = self.query_one("#details", TextArea) + + # Remover containers parados + prune_result = run_docker_command(["container", "prune", "-f"]) + + # Remover imagens órfãs + image_prune_result = run_docker_command(["image", "prune", "-f"]) + + cleanup_text = "🧹 Limpeza realizada:\n\n" + + if prune_result["success"]: + cleanup_text += f"Containers removidos:\n{prune_result['stdout']}\n\n" + else: + cleanup_text += f"Erro ao remover containers: {prune_result.get('stderr', 'Erro desconhecido')}\n\n" + + if image_prune_result["success"]: + cleanup_text += f"Imagens removidas:\n{image_prune_result['stdout']}" + else: + cleanup_text += f"Erro ao remover imagens: {image_prune_result.get('stderr', 'Erro desconhecido')}" + + details.text = cleanup_text + self.refresh_data() + + def on_key(self, event: events.Key) -> None: + """Handler para teclas globais""" + if event.key == "q": + self.exit() + elif event.key == "r": + self.refresh_data() + +def main(): + """Função principal""" + # Verificar se Docker está disponível + docker_check = run_docker_command(["--version"]) + if not docker_check["success"]: + print("❌ Erro: Docker não está disponível ou não está instalado.") + print("Instale o Docker antes de usar esta ferramenta.") + return 1 + + print("🐳 Iniciando FazAI Container Manager TUI...") + app = ContainerManagerApp() + app.run() + return 0 + +if __name__ == "__main__": + exit(main()) \ No newline at end of file diff --git a/package.json b/package.json index 67163b4..3a55662 100755 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "prebuild": "npm rebuild ffi-napi-v22 || echo \"ffi-napi build skipped\"", "tui": "bash opt/fazai/tools/fazai-tui.sh", "config-tui": "bash opt/fazai/tools/fazai-config-tui.sh", + "containers-tui": "python3 opt/fazai/tools/container_manager_tui.py", "web": "bash opt/fazai/tools/fazai_web.sh" }, "dependencies": { diff --git a/tests/container_manager.test.sh b/tests/container_manager.test.sh new file mode 100755 index 0000000..3aa45b2 --- /dev/null +++ b/tests/container_manager.test.sh @@ -0,0 +1,154 @@ +#!/bin/bash +# Teste para o FazAI Container Manager TUI +# Verifica se a ferramenta está instalada e funcional + +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +echo "=== Teste do FazAI Container Manager TUI ===" + +# Verificar se Docker está disponível +if ! command -v docker >/dev/null 2>&1; then + echo "SKIP: Docker não está disponível - pulando testes de container" + exit 0 +fi + +# Verificar se o arquivo TUI existe +CONTAINER_TUI="$PROJECT_ROOT/opt/fazai/tools/container_manager_tui.py" +if [ ! -f "$CONTAINER_TUI" ]; then + echo "FAIL: Arquivo $CONTAINER_TUI não encontrado" + exit 1 +fi + +echo "✓ Arquivo TUI encontrado: $CONTAINER_TUI" + +# Verificar se é executável +if [ ! -x "$CONTAINER_TUI" ]; then + echo "FAIL: Arquivo TUI não é executável" + exit 1 +fi + +echo "✓ Arquivo TUI é executável" + +# Testar dependências Python +if ! python3 -c "import json, subprocess, os, asyncio" 2>/dev/null; then + echo "FAIL: Dependências Python básicas não disponíveis" + exit 1 +fi + +echo "✓ Dependências Python básicas OK" + +# Testar dependência Textual (opcional) +if python3 -c "import textual" 2>/dev/null; then + echo "✓ Biblioteca Textual disponível" + TEXTUAL_AVAILABLE=true +else + echo "! Biblioteca Textual não disponível (será instalada quando necessário)" + TEXTUAL_AVAILABLE=false +fi + +# Verificar CLI wrapper +CLI_WRAPPER="$PROJECT_ROOT/bin/fazai-containers" +if [ ! -f "$CLI_WRAPPER" ]; then + echo "FAIL: CLI wrapper não encontrado: $CLI_WRAPPER" + exit 1 +fi + +echo "✓ CLI wrapper encontrado: $CLI_WRAPPER" + +if [ ! -x "$CLI_WRAPPER" ]; then + echo "FAIL: CLI wrapper não é executável" + exit 1 +fi + +echo "✓ CLI wrapper é executável" + +# Verificar Dockerfile de teste +DOCKER_TEST_FILE="$PROJECT_ROOT/Dockerfile.installer-test" +if [ ! -f "$DOCKER_TEST_FILE" ]; then + echo "FAIL: Dockerfile de teste não encontrado: $DOCKER_TEST_FILE" + exit 1 +fi + +echo "✓ Dockerfile de teste encontrado" + +# Verificar script de entrypoint +ENTRYPOINT_SCRIPT="$PROJECT_ROOT/docker-installer-test-entrypoint.sh" +if [ ! -f "$ENTRYPOINT_SCRIPT" ]; then + echo "FAIL: Script de entrypoint não encontrado: $ENTRYPOINT_SCRIPT" + exit 1 +fi + +echo "✓ Script de entrypoint encontrado" + +if [ ! -x "$ENTRYPOINT_SCRIPT" ]; then + echo "FAIL: Script de entrypoint não é executável" + exit 1 +fi + +echo "✓ Script de entrypoint é executável" + +# Teste funcional básico (sem executar TUI, apenas importar) +if python3 -c " +import sys +sys.path.insert(0, '$PROJECT_ROOT/opt/fazai/tools') +try: + import container_manager_tui + # Testar funções básicas + result = container_manager_tui.run_docker_command(['--version']) + if result['success']: + print('Docker funcional via TUI') + else: + print('Docker não funcional via TUI') + sys.exit(1) +except ImportError as e: + print(f'Erro ao importar TUI: {e}') + sys.exit(1) +except Exception as e: + print(f'Erro no teste funcional: {e}') + sys.exit(1) +" 2>/dev/null; then + echo "✓ Teste funcional básico OK" +else + echo "FAIL: Teste funcional básico falhou" + exit 1 +fi + +# Testar templates (verificar se estão bem formados) +python3 -c " +import sys +sys.path.insert(0, '$PROJECT_ROOT/opt/fazai/tools') +import container_manager_tui + +templates = container_manager_tui.CONTAINER_TEMPLATES +print(f'Templates disponíveis: {len(templates)}') + +for template_id, template in templates.items(): + required_fields = ['name', 'image', 'description', 'command', 'ports', 'volumes', 'env_vars', 'interactive'] + for field in required_fields: + if field not in template: + print(f'Template {template_id} não tem campo obrigatório: {field}') + sys.exit(1) + print(f'✓ Template {template_id}: {template[\"name\"]}') + +print('Todos os templates estão bem formados') +" + +if [ $? -eq 0 ]; then + echo "✓ Validação de templates OK" +else + echo "FAIL: Validação de templates falhou" + exit 1 +fi + +echo "" +echo "=== Todos os testes passaram! ===" +echo "Container Manager TUI está funcional" +echo "" +echo "Para usar:" +echo " npm run containers-tui" +echo " ou" +echo " $CLI_WRAPPER" +echo "" \ No newline at end of file