From 00f3a643fe815c58a1b5facf680f5c6ac3faa73d Mon Sep 17 00:00:00 2001 From: WanderMotta Date: Tue, 30 Dec 2025 22:08:12 -0300 Subject: [PATCH] Commit inicial - upload de todos os arquivos da pasta --- .dockerignore | 40 +++++++ .env | 24 ++++ Dockerfile | 46 ++++++++ README_Monitor.md | 35 ++++++ app.py | 190 ++++++++++++++++++++++++++++++++ github.bat | 30 +++++ monitor_logic.py | 274 ++++++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 5 + 8 files changed, 644 insertions(+) create mode 100644 .dockerignore create mode 100644 .env create mode 100644 Dockerfile create mode 100644 README_Monitor.md create mode 100644 app.py create mode 100644 github.bat create mode 100644 monitor_logic.py create mode 100644 requirements.txt diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ad2261f --- /dev/null +++ b/.dockerignore @@ -0,0 +1,40 @@ +# Git +.git +.gitignore + +# Python +__pycache__ +*.py[cod] +*$py.class +*.so +.Python +.env +.venv +env/ +venv/ +ENV/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo + +# Docker +Dockerfile +.dockerignore + +# Documentação +*.md +!README_Monitor.md + +# Logs e cache +*.log +.cache/ + +# Arquivos temporários +*.tmp +*.temp +.DS_Store +Thumbs.db + diff --git a/.env b/.env new file mode 100644 index 0000000..f4ef071 --- /dev/null +++ b/.env @@ -0,0 +1,24 @@ +# Configurações do Evolution API +# URL base da sua instância (sem a barra final) +EVOLUTION_API_URL=http://evo-x40k0g00kw4k4k00k44wssgc.31.97.64.165.sslip.io +# Nome da instância para checagem de saúde +EVOLUTION_INSTANCE_ID=33c2dae2-8c3a-4333-972d-813169e287c8 +# Token de Autenticação (Bearer Token) +EVOLUTION_API_TOKEN=OWYyih16w1vZCm4rSza9axTz57bzOJ3Y +# Instância de envio de mensagem (pode ser a mesma ou nome amigável 'Wander') +EVOLUTION_SENDER_INSTANCE=Wander + +# Configurações do Webhook N8N +N8N_WEBHOOK_URL=https://n8n.aplicativopro.com/webhook/a352fb9d-8f6c-4fbb-a4ab-46886af81789 + +# Configurações de Notificação (Whatsapp) +WHATSAPP_NUMBER=5511996947374 + +# Configurações de Email (SMTP) +SMTP_SERVER=smtp.hostinger.com +SMTP_PORT=465 +SMTP_EMAIL=wander@amixxam.com +# Senha do email ou Senha de Aplicativo +SMTP_PASSWORD=Luraba@3 +EMAIL_TO=wandermotta@hotmail.com +SMTP_SECURITY=SSL diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..83df167 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,46 @@ +# Dockerfile para Monitor Evolution API + Webhook +# Deploy otimizado para Coolify + +FROM python:3.11-slim + +# Definir diretório de trabalho +WORKDIR /app + +# Variáveis de ambiente para Python +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 + +# Variáveis de ambiente para Streamlit +ENV STREAMLIT_SERVER_PORT=3000 +ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0 +ENV STREAMLIT_SERVER_HEADLESS=true +ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false + +# Instalar dependências do sistema +RUN apt-get update && apt-get install -y --no-install-recommends \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Atualizar pip +RUN pip install --no-cache-dir --upgrade pip + +# Copiar arquivos da aplicação +COPY app.py monitor_logic.py ./ + +# Copiar requirements.txt se existir, caso contrário instalar dependências diretamente +COPY requirements.txt* ./ +RUN if [ -f requirements.txt ]; then \ + pip install --no-cache-dir -r requirements.txt; \ + else \ + pip install --no-cache-dir streamlit>=1.28.0 requests>=2.31.0 python-dotenv>=1.0.0 apscheduler>=3.10.0 pandas>=2.0.0; \ + fi + +# Expor porta do Streamlit (padrão Coolify) +EXPOSE 3000 + +# Healthcheck para Coolify +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl --fail http://localhost:3000/_stcore/health || exit 1 + +# Comando para iniciar a aplicação na porta 3000 +CMD ["streamlit", "run", "app.py", "--server.port=3000", "--server.address=0.0.0.0"] diff --git a/README_Monitor.md b/README_Monitor.md new file mode 100644 index 0000000..195e3e7 --- /dev/null +++ b/README_Monitor.md @@ -0,0 +1,35 @@ +# Monitor Evolution API + Webhook + +Aplicativo desenvolvido em Python + Streamlit para monitorar a saúde da Evolution API e do Webhook N8N. + +## 🛠️ Instalação + +1. **Instale as dependências**: + Abra o terminal na pasta do projeto e execute: + ```bash + pip install -r requirements.txt + ``` + +2. **Configure o ambiente**: + - Copie o arquivo `.env.example` e renomeie para `.env`. + - Abra o arquivo `.env` e preencha com suas credenciais: + - `EVOLUTION_API_TOKEN`: Seu token da API. + - `SMTP_PASSWORD`: Senha do email `wandermotta@hotmail.com`. + +## 🚀 Como Rodar + +Execute o comando abaixo no terminal: + +```bash +streamlit run app.py +``` + +O navegador abrirá automaticamente com o painel de controle. + +## ⚙️ Funcionalidades + +- **Monitoramento Automático**: O sistema verifica os serviços automaticamente às **07:00**, **13:00** e **17:00**. +- **Botão Executar Agor**: Permite testar o fluxo imediatamente. +- **Notificações**: + - Se tudo estiver **OK**: Envia WhatsApp para `5511996947374`. + - Se houver **ERRO**: Envia email para `wandermotta@hotmail.com`. diff --git a/app.py b/app.py new file mode 100644 index 0000000..5e01d70 --- /dev/null +++ b/app.py @@ -0,0 +1,190 @@ +import streamlit as st +import pandas as pd +import time +from datetime import datetime +from apscheduler.schedulers.background import BackgroundScheduler +from monitor_logic import run_monitor_check, diagnose_n8n_webhook + +# ===== Configuração da Página (DEVE ser a primeira chamada Streamlit!) ===== +st.set_page_config( + page_title="Monitor Evolution API", + page_icon="🤖", + layout="wide" +) + +# Função para gerar tabela HTML customizada +def gerar_tabela_html(logs): + """Gera tabela HTML com estilos inline para garantir funcionamento.""" + if not logs: + return "" + + # Estilos inline + estilo_tabela = "width: 100%; border-collapse: collapse; margin-top: 10px;" + estilo_header = "background-color: #f0f2f6; padding: 12px 15px; text-align: center; font-weight: bold; font-size: 16px; border-bottom: 2px solid #ddd;" + estilo_celula = "padding: 10px 15px; text-align: center; font-size: 14px; border-bottom: 1px solid #eee;" + estilo_celula_detalhes = "padding: 10px 15px; text-align: left; font-size: 14px; border-bottom: 1px solid #eee;" + + html = f'' + + # Cabeçalho + html += '' + html += f'' + html += f'' + html += f'' + html += f'' + html += f'' + html += '' + + # Corpo + html += '' + for log in logs: + html += '' + html += f'' + html += f'' + html += f'' + html += f'' + html += f'' + html += '' + html += '
HoraStatusWhatsAppEmailDetalhes
{log["Hora"]}{log["Status"]}{log["WhatsApp"]}{log["Email"]}{log["Detalhes"]}
' + + return html + +# ===== Inicializar estado da sessão ===== +if 'logs' not in st.session_state: + st.session_state['logs'] = [] + +if 'last_run' not in st.session_state: + st.session_state['last_run'] = "Nunca" + +if 'intervalo_horas' not in st.session_state: + st.session_state['intervalo_horas'] = 3 + +# ===== Função wrapper para o Scheduler gravar logs ===== +def scheduled_job(): + """Executa verificação agendada (roda em background thread).""" + print(f"[{datetime.now()}] Executando verificação agendada...") + status, messages, whatsapp_status, email_status = run_monitor_check() + print(f"Status: {status} | WhatsApp: {whatsapp_status} | Email: {email_status}") + print("\n".join(messages)) + +# ===== Configurar o Agendador (Singleton via cache_resource) ===== +@st.cache_resource +def init_scheduler(intervalo_horas): + scheduler = BackgroundScheduler(timezone="America/Sao_Paulo") + # Job com intervalo configurável a partir de 00:00 + scheduler.add_job( + scheduled_job, + 'interval', + hours=intervalo_horas, + start_date='2025-01-01 00:00:00' + ) + scheduler.start() + return scheduler + +scheduler = init_scheduler(st.session_state['intervalo_horas']) + +# ===== Interface Principal ===== +st.title("🤖 Monitor Evolution API + Webhook") +st.markdown("---") + +# Métricas superiores +col1, col2 = st.columns(2) + +with col1: + st.metric(label="Última Verificação", value=st.session_state['last_run']) + +with col2: + st.write("### Status Agendador") + intervalo_atual = st.session_state['intervalo_horas'] + st.success(f"✅ Ativo (a cada {intervalo_atual}h desde 00:00)") + +st.markdown("### Ações") + +# Botão de execução manual +if st.button("🚀 Executar Verificação Agora", type="primary"): + with st.spinner('Verificando status dos serviços...'): + status, messages, whatsapp_status, email_status = run_monitor_check() + st.session_state['last_run'] = datetime.now().strftime("%H:%M:%S") + + # Adicionar ao histórico de logs + timestamp = datetime.now().strftime("%H:%M:%S") + entry = { + "Hora": timestamp, + "Status": "✅ Sucesso" if status else "❌ Erro", + "WhatsApp": whatsapp_status, + "Email": email_status, + "Detalhes": " | ".join(messages) + } + st.session_state['logs'].insert(0, entry) # Adiciona no topo + + if status: + st.success("✅ Todos os serviços estão operacionais!") + else: + st.error("❌ Falha detectada em um ou mais serviços. Verifique os logs.") + +st.markdown("---") +st.markdown("### 📋 Histórico de Execuções (Sessão Atual)") + +if st.session_state['logs']: + # Renderizar tabela HTML customizada + tabela_html = gerar_tabela_html(st.session_state['logs']) + st.markdown(tabela_html, unsafe_allow_html=True) +else: + st.info("ℹ️ Nenhuma verificação executada nesta sessão ainda.") + +# ===== Sidebar ===== +st.sidebar.title("ℹ️ Sobre") +st.sidebar.info( + """ + Este aplicativo monitora: + + 1. **Evolution API**: Verifica se a instância está online. + 2. **N8N Webhook**: Verifica se o endpoint responde. + + **Notificações:** + - ✅ **Sucesso**: Envia WhatsApp. + - ❌ **Erro**: Envia Email. + """ +) + +st.sidebar.markdown("---") + +# Configuração do Intervalo de Disparo +st.sidebar.markdown("### ⏱️ Intervalo de Disparo") +intervalo_slider = st.sidebar.slider( + "Executar a cada (horas):", + min_value=1, + max_value=12, + value=st.session_state['intervalo_horas'], + step=1, + help="Define o intervalo entre execuções automáticas a partir de 00:00" +) + +# Mostrar horários de execução baseados no intervalo +horarios = [f"{h:02d}:00" for h in range(0, 24, intervalo_slider)] +st.sidebar.caption(f"Horários: {', '.join(horarios)}") + +# Verificar se o intervalo mudou +if intervalo_slider != st.session_state['intervalo_horas']: + st.sidebar.warning("⚠️ Reinicie a aplicação para aplicar o novo intervalo.") + st.session_state['intervalo_horas'] = intervalo_slider + +st.sidebar.markdown("---") + +# Diagnóstico N8N na Sidebar +with st.sidebar.expander("🛠️ Diagnóstico N8N"): + st.write("Teste isolado do Webhook N8N") + if st.button("Testar Webhook N8N", key="btn_diag"): + with st.spinner("Enviando POST de teste..."): + success, log_data = diagnose_n8n_webhook() + + if success: + st.success(f"✅ Status: {log_data['status_code']}") + else: + st.error(f"❌ Erro: {log_data.get('status_code') or log_data.get('error')}") + + st.json(log_data) + +# Informações de configuração +st.sidebar.markdown("---") +st.sidebar.caption(f"Intervalo atual: {st.session_state['intervalo_horas']}h") diff --git a/github.bat b/github.bat new file mode 100644 index 0000000..7b7b4a6 --- /dev/null +++ b/github.bat @@ -0,0 +1,30 @@ +@echo off +echo === INICIANDO UPLOAD PARA GITHUB === + +REM Inicializar repositório Git +echo Inicializando repositorio Git... +git init + +REM Adicionar todos os arquivos +echo Adicionando todos os arquivos... +git add . + +REM Fazer commit inicial +echo Realizando commit inicial... +git commit -m "Commit inicial - upload de todos os arquivos da pasta" + +REM Adicionar repositório remoto +echo Conectando ao repositorio remoto... +git remote add origin https://gitea.aplicativopro.com/wander/Monitor-N8N.git + +REM Definir branch principal +echo Definindo branch principal como 'main'... +git branch -M main + +REM Fazer push para o GitHub +echo Fazendo upload para o GitHub... +git push -u origin main + +echo === UPLOAD CONCLUIDO COM SUCESSO! === + +pause \ No newline at end of file diff --git a/monitor_logic.py b/monitor_logic.py new file mode 100644 index 0000000..ea4d469 --- /dev/null +++ b/monitor_logic.py @@ -0,0 +1,274 @@ +import os +import requests +import smtplib +from email.mime.text import MIMEText +from datetime import datetime +from dotenv import load_dotenv + +# Carregar variáveis de ambiente +load_dotenv() + +# Configurações +EVOLUTION_API_URL = os.getenv("EVOLUTION_API_URL") +EVOLUTION_INSTANCE_ID = os.getenv("EVOLUTION_INSTANCE_ID") +EVOLUTION_API_TOKEN = os.getenv("EVOLUTION_API_TOKEN") +EVOLUTION_SENDER_INSTANCE = os.getenv("EVOLUTION_SENDER_INSTANCE", "Wander") +N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL") +WHATSAPP_NUMBER = os.getenv("WHATSAPP_NUMBER") +SMTP_SERVER = os.getenv("SMTP_SERVER", "smtp-mail.outlook.com") +SMTP_PORT = int(os.getenv("SMTP_PORT", 587)) +SMTP_EMAIL = os.getenv("SMTP_EMAIL") +SMTP_PASSWORD = os.getenv("SMTP_PASSWORD") +EMAIL_TO = os.getenv("EMAIL_TO") + +def get_headers(): + return { + "apikey": EVOLUTION_API_TOKEN, + "Authorization": f"Bearer {EVOLUTION_API_TOKEN}", + "Content-Type": "application/json" + } + +def send_email_error(error_details): + """Envia email de erro caso algo falhe.""" + try: + subject = "🚨 ERRO - Monitor Evolution API" + body = f"Falha no monitoramento.\nHorário: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n\nDetalhes do Erro:\n{error_details}" + + msg = MIMEText(body) + msg['Subject'] = subject + msg['From'] = SMTP_EMAIL + msg['To'] = EMAIL_TO + + # Ler configuração de segurança (Default: STARTTLS) + smtp_security = os.getenv("SMTP_SECURITY", "STARTTLS").upper() + + if smtp_security == "SSL": + server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT) + else: + server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT) + if smtp_security == "STARTTLS": + server.starttls() + + with server: + server.login(SMTP_EMAIL, SMTP_PASSWORD) + server.send_message(msg) + + return True, "Email de erro enviado com sucesso." + except Exception as e: + return False, f"Falha ao enviar email: {str(e)}" + +def send_whatsapp_success(): + """Envia mensagem de sucesso via Evolution API.""" + try: + url = f"{EVOLUTION_API_URL}/message/sendText/{EVOLUTION_SENDER_INSTANCE}" + + now = datetime.now() + message_text = ( + f"✅ *MONITOR OK*\n\n" + f"📅 Data: {now.strftime('%d/%m/%Y')}\n" + f"🕐 Horário: {now.strftime('%H:%M:%S')}\n\n" + f"• Evolution API: Online\n" + f"• Webhook Instância: Habilitado\n" + f"• Webhook N8N: Funcionando\n\n" + f"Todos os serviços operacionais." + ) + + payload = { + "number": WHATSAPP_NUMBER, + "text": message_text + } + + # A Evolution API às vezes aceita apikey no header ou como query param, + # mas o padrão aqui é usar o header configurado. + response = requests.post(url, json=payload, headers=get_headers(), timeout=10) + + if response.status_code == 200 or response.status_code == 201: + return True, "WhatsApp de sucesso enviado." + else: + return False, f"Falha ao enviar WhatsApp: {response.text}" + + except Exception as e: + return False, f"Erro na requisição WhatsApp: {str(e)}" + +def run_monitor_check(): + """Executa o fluxo de verificação completo.""" + log_messages = [] + whatsapp_status = "-" + email_status = "-" + + # Passo 1: Verificar Health Evolution API + try: + # Endpoint correto: /instance/connectionState/{instanceName} + # Usa o nome da instância (EVOLUTION_SENDER_INSTANCE) e não o ID + health_url = f"{EVOLUTION_API_URL}/instance/connectionState/{EVOLUTION_SENDER_INSTANCE}" + resp_health = requests.get(health_url, headers=get_headers(), timeout=10) + + if resp_health.status_code == 200: + # Validar se a instância está realmente conectada (state: open) + try: + data = resp_health.json() + instance_data = data.get('instance', {}) + state = instance_data.get('state') or data.get('state') + + if state == 'open': + log_messages.append("✅ Evolution API Online") + else: + # Estado não é 'open' (ex: close, closed, connecting) = ERRO! + error_msg = f"Evolution API Desconectada (State: {state})" + log_messages.append(f"❌ {error_msg}") + + # Enviar email de erro + email_ok, _ = send_email_error(error_msg) + email_status = "Sucesso" if email_ok else "Erro" + + return False, log_messages, whatsapp_status, email_status + + except Exception as json_err: + # Se falhar ler JSON mas deu 200 OK, verificar se é HTML + raw_text = resp_health.text.strip() + if raw_text.startswith(' Enviar WhatsApp + # Se está tudo OK, email de erro não é enviado, então mantemos email_status como "-" ou podemos por "N/A" + # O usuário pediu "Sucesso ou Error". Se não enviou nada, talvez "-" seja melhor que "Error". + + success, msg = send_whatsapp_success() + if success: + log_messages.append(f"✅ {msg}") + whatsapp_status = "Sucesso" + return True, log_messages, whatsapp_status, email_status + else: + # Se falhar enviar o whats, é um erro também? + whatsapp_status = "Erro" + + # O fluxo original manda email de erro se o check falhar. + # Se o envio do whats falhar, vamos mandar email + error_msg = f"Serviços Online, mas falha ao enviar WhatsApp: {msg}" + log_messages.append(f"⚠️ {error_msg}") + + email_ok, _ = send_email_error(error_msg) + email_status = "Sucesso" if email_ok else "Erro" + + return False, log_messages, whatsapp_status, email_status + +def diagnose_n8n_webhook(): + """Realiza um teste detalhado do Webhook N8N para diagnóstico.""" + try: + url = N8N_WEBHOOK_URL + payload = {"check": "diagnosis_ping", "timestamp": str(datetime.now())} + + # Preparar log + log = { + "url": url, + "method": "POST", + "payload_sent": payload, + "status_code": None, + "response_body": None, + "headers_received": None, + "error": None + } + + response = requests.post(url, json=payload, timeout=10) + + log["status_code"] = response.status_code + log["response_body"] = response.text + log["headers_received"] = dict(response.headers) + + if response.status_code == 200: + return True, log + else: + return False, log + + except Exception as e: + log["error"] = str(e) + return False, log + +if __name__ == "__main__": + # Teste rápido se rodar direto + status, logs, wa_status, em_status = run_monitor_check() + print(f"Status: {status}") + print(f"WhatsApp: {wa_status} | Email: {em_status}") + print("\n".join(logs)) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..a1eaff3 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +streamlit>=1.28.0 +requests>=2.31.0 +python-dotenv>=1.0.0 +apscheduler>=3.10.0 +pandas>=2.0.0