Enviar arquivos para "/"
This commit is contained in:
96
Dockerfile
96
Dockerfile
@@ -1,46 +1,50 @@
|
|||||||
# Dockerfile para Monitor Evolution API + Webhook
|
# Dockerfile para Monitor Evolution API + Webhook
|
||||||
# Deploy otimizado para Coolify
|
# Deploy otimizado para Coolify
|
||||||
|
|
||||||
FROM python:3.11-slim
|
FROM python:3.11-slim
|
||||||
|
|
||||||
# Definir diretório de trabalho
|
# Definir diretório de trabalho
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Variáveis de ambiente para Python
|
# Variáveis de ambiente para Python
|
||||||
ENV PYTHONDONTWRITEBYTECODE=1
|
ENV PYTHONDONTWRITEBYTECODE=1
|
||||||
ENV PYTHONUNBUFFERED=1
|
ENV PYTHONUNBUFFERED=1
|
||||||
|
|
||||||
# Variáveis de ambiente para Streamlit
|
# Variáveis de ambiente para Streamlit
|
||||||
ENV STREAMLIT_SERVER_PORT=3000
|
ENV STREAMLIT_SERVER_PORT=3000
|
||||||
ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
|
ENV STREAMLIT_SERVER_ADDRESS=0.0.0.0
|
||||||
ENV STREAMLIT_SERVER_HEADLESS=true
|
ENV STREAMLIT_SERVER_HEADLESS=true
|
||||||
ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
|
ENV STREAMLIT_BROWSER_GATHER_USAGE_STATS=false
|
||||||
|
|
||||||
# Instalar dependências do sistema
|
# Timezone padrão (pode ser sobrescrito via variável de ambiente no Coolify)
|
||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
|
ENV TIMEZONE=America/Sao_Paulo
|
||||||
curl \
|
ENV TZ=America/Sao_Paulo
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
|
||||||
|
# Instalar dependências do sistema
|
||||||
# Atualizar pip
|
RUN apt-get update && apt-get install -y --no-install-recommends \
|
||||||
RUN pip install --no-cache-dir --upgrade pip
|
curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
# Copiar arquivos da aplicação
|
|
||||||
COPY app.py monitor_logic.py ./
|
# Atualizar pip
|
||||||
|
RUN pip install --no-cache-dir --upgrade pip
|
||||||
# Copiar requirements.txt se existir, caso contrário instalar dependências diretamente
|
|
||||||
COPY requirements.txt* ./
|
# Copiar arquivos da aplicação
|
||||||
RUN if [ -f requirements.txt ]; then \
|
COPY app.py monitor_logic.py ./
|
||||||
pip install --no-cache-dir -r requirements.txt; \
|
|
||||||
else \
|
# Copiar requirements.txt se existir, caso contrário instalar dependências diretamente
|
||||||
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; \
|
COPY requirements.txt* ./
|
||||||
fi
|
RUN if [ -f requirements.txt ]; then \
|
||||||
|
pip install --no-cache-dir -r requirements.txt; \
|
||||||
# Expor porta do Streamlit (padrão Coolify)
|
else \
|
||||||
EXPOSE 3000
|
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
|
||||||
# Healthcheck para Coolify
|
|
||||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
# Expor porta do Streamlit (padrão Coolify)
|
||||||
CMD curl --fail http://localhost:3000/_stcore/health || exit 1
|
EXPOSE 3000
|
||||||
|
|
||||||
# Comando para iniciar a aplicação na porta 3000
|
# Healthcheck para Coolify
|
||||||
CMD ["streamlit", "run", "app.py", "--server.port=3000", "--server.address=0.0.0.0"]
|
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"]
|
||||||
|
|||||||
388
app.py
388
app.py
@@ -1,190 +1,198 @@
|
|||||||
import streamlit as st
|
import streamlit as st
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
import time
|
import time
|
||||||
from datetime import datetime
|
import os
|
||||||
from apscheduler.schedulers.background import BackgroundScheduler
|
from datetime import datetime
|
||||||
from monitor_logic import run_monitor_check, diagnose_n8n_webhook
|
import pytz
|
||||||
|
from apscheduler.schedulers.background import BackgroundScheduler
|
||||||
# ===== Configuração da Página (DEVE ser a primeira chamada Streamlit!) =====
|
from monitor_logic import run_monitor_check, diagnose_n8n_webhook
|
||||||
st.set_page_config(
|
|
||||||
page_title="Monitor Evolution API",
|
# Carregar timezone da variável de ambiente (padrão: America/Sao_Paulo)
|
||||||
page_icon="🤖",
|
TIMEZONE = os.getenv("TIMEZONE", "America/Sao_Paulo")
|
||||||
layout="wide"
|
tz = pytz.timezone(TIMEZONE)
|
||||||
)
|
|
||||||
|
# ===== Configuração da Página (DEVE ser a primeira chamada Streamlit!) =====
|
||||||
# Função para gerar tabela HTML customizada
|
st.set_page_config(
|
||||||
def gerar_tabela_html(logs):
|
page_title="Monitor Evolution API",
|
||||||
"""Gera tabela HTML com estilos inline para garantir funcionamento."""
|
page_icon="🤖",
|
||||||
if not logs:
|
layout="wide"
|
||||||
return ""
|
)
|
||||||
|
|
||||||
# Estilos inline
|
# Função para gerar tabela HTML customizada
|
||||||
estilo_tabela = "width: 100%; border-collapse: collapse; margin-top: 10px;"
|
def gerar_tabela_html(logs):
|
||||||
estilo_header = "background-color: #f0f2f6; padding: 12px 15px; text-align: center; font-weight: bold; font-size: 16px; border-bottom: 2px solid #ddd;"
|
"""Gera tabela HTML com estilos inline para garantir funcionamento."""
|
||||||
estilo_celula = "padding: 10px 15px; text-align: center; font-size: 14px; border-bottom: 1px solid #eee;"
|
if not logs:
|
||||||
estilo_celula_detalhes = "padding: 10px 15px; text-align: left; font-size: 14px; border-bottom: 1px solid #eee;"
|
return ""
|
||||||
|
|
||||||
html = f'<table style="{estilo_tabela}">'
|
# Estilos inline
|
||||||
|
estilo_tabela = "width: 100%; border-collapse: collapse; margin-top: 10px;"
|
||||||
# Cabeçalho
|
estilo_header = "background-color: #f0f2f6; padding: 12px 15px; text-align: center; font-weight: bold; font-size: 16px; border-bottom: 2px solid #ddd;"
|
||||||
html += '<thead><tr>'
|
estilo_celula = "padding: 10px 15px; text-align: center; font-size: 14px; border-bottom: 1px solid #eee;"
|
||||||
html += f'<th style="{estilo_header}">Hora</th>'
|
estilo_celula_detalhes = "padding: 10px 15px; text-align: left; font-size: 14px; border-bottom: 1px solid #eee;"
|
||||||
html += f'<th style="{estilo_header}">Status</th>'
|
|
||||||
html += f'<th style="{estilo_header}">WhatsApp</th>'
|
html = f'<table style="{estilo_tabela}">'
|
||||||
html += f'<th style="{estilo_header}">Email</th>'
|
|
||||||
html += f'<th style="{estilo_header}">Detalhes</th>'
|
# Cabeçalho
|
||||||
html += '</tr></thead>'
|
html += '<thead><tr>'
|
||||||
|
html += f'<th style="{estilo_header}">Hora</th>'
|
||||||
# Corpo
|
html += f'<th style="{estilo_header}">Status</th>'
|
||||||
html += '<tbody>'
|
html += f'<th style="{estilo_header}">WhatsApp</th>'
|
||||||
for log in logs:
|
html += f'<th style="{estilo_header}">Email</th>'
|
||||||
html += '<tr>'
|
html += f'<th style="{estilo_header}">Detalhes</th>'
|
||||||
html += f'<td style="{estilo_celula}">{log["Hora"]}</td>'
|
html += '</tr></thead>'
|
||||||
html += f'<td style="{estilo_celula}">{log["Status"]}</td>'
|
|
||||||
html += f'<td style="{estilo_celula}">{log["WhatsApp"]}</td>'
|
# Corpo
|
||||||
html += f'<td style="{estilo_celula}">{log["Email"]}</td>'
|
html += '<tbody>'
|
||||||
html += f'<td style="{estilo_celula_detalhes}">{log["Detalhes"]}</td>'
|
for log in logs:
|
||||||
html += '</tr>'
|
html += '<tr>'
|
||||||
html += '</tbody></table>'
|
html += f'<td style="{estilo_celula}">{log["Hora"]}</td>'
|
||||||
|
html += f'<td style="{estilo_celula}">{log["Status"]}</td>'
|
||||||
return html
|
html += f'<td style="{estilo_celula}">{log["WhatsApp"]}</td>'
|
||||||
|
html += f'<td style="{estilo_celula}">{log["Email"]}</td>'
|
||||||
# ===== Inicializar estado da sessão =====
|
html += f'<td style="{estilo_celula_detalhes}">{log["Detalhes"]}</td>'
|
||||||
if 'logs' not in st.session_state:
|
html += '</tr>'
|
||||||
st.session_state['logs'] = []
|
html += '</tbody></table>'
|
||||||
|
|
||||||
if 'last_run' not in st.session_state:
|
return html
|
||||||
st.session_state['last_run'] = "Nunca"
|
|
||||||
|
# ===== Inicializar estado da sessão =====
|
||||||
if 'intervalo_horas' not in st.session_state:
|
if 'logs' not in st.session_state:
|
||||||
st.session_state['intervalo_horas'] = 3
|
st.session_state['logs'] = []
|
||||||
|
|
||||||
# ===== Função wrapper para o Scheduler gravar logs =====
|
if 'last_run' not in st.session_state:
|
||||||
def scheduled_job():
|
st.session_state['last_run'] = "Nunca"
|
||||||
"""Executa verificação agendada (roda em background thread)."""
|
|
||||||
print(f"[{datetime.now()}] Executando verificação agendada...")
|
if 'intervalo_horas' not in st.session_state:
|
||||||
status, messages, whatsapp_status, email_status = run_monitor_check()
|
st.session_state['intervalo_horas'] = 3
|
||||||
print(f"Status: {status} | WhatsApp: {whatsapp_status} | Email: {email_status}")
|
|
||||||
print("\n".join(messages))
|
# ===== Função wrapper para o Scheduler gravar logs =====
|
||||||
|
def scheduled_job():
|
||||||
# ===== Configurar o Agendador (Singleton via cache_resource) =====
|
"""Executa verificação agendada (roda em background thread)."""
|
||||||
@st.cache_resource
|
now = datetime.now(tz)
|
||||||
def init_scheduler(intervalo_horas):
|
print(f"[{now}] Executando verificação agendada...")
|
||||||
scheduler = BackgroundScheduler(timezone="America/Sao_Paulo")
|
status, messages, whatsapp_status, email_status = run_monitor_check()
|
||||||
# Job com intervalo configurável a partir de 00:00
|
print(f"Status: {status} | WhatsApp: {whatsapp_status} | Email: {email_status}")
|
||||||
scheduler.add_job(
|
print("\n".join(messages))
|
||||||
scheduled_job,
|
|
||||||
'interval',
|
# ===== Configurar o Agendador (Singleton via cache_resource) =====
|
||||||
hours=intervalo_horas,
|
@st.cache_resource
|
||||||
start_date='2025-01-01 00:00:00'
|
def init_scheduler(intervalo_horas):
|
||||||
)
|
scheduler = BackgroundScheduler(timezone=TIMEZONE)
|
||||||
scheduler.start()
|
# Job com intervalo configurável a partir de 00:00
|
||||||
return scheduler
|
scheduler.add_job(
|
||||||
|
scheduled_job,
|
||||||
scheduler = init_scheduler(st.session_state['intervalo_horas'])
|
'interval',
|
||||||
|
hours=intervalo_horas,
|
||||||
# ===== Interface Principal =====
|
start_date='2025-01-01 00:00:00'
|
||||||
st.title("🤖 Monitor Evolution API + Webhook")
|
)
|
||||||
st.markdown("---")
|
scheduler.start()
|
||||||
|
return scheduler
|
||||||
# Métricas superiores
|
|
||||||
col1, col2 = st.columns(2)
|
scheduler = init_scheduler(st.session_state['intervalo_horas'])
|
||||||
|
|
||||||
with col1:
|
# ===== Interface Principal =====
|
||||||
st.metric(label="Última Verificação", value=st.session_state['last_run'])
|
st.title("🤖 Monitor Evolution API + Webhook")
|
||||||
|
st.markdown("---")
|
||||||
with col2:
|
|
||||||
st.write("### Status Agendador")
|
# Métricas superiores
|
||||||
intervalo_atual = st.session_state['intervalo_horas']
|
col1, col2 = st.columns(2)
|
||||||
st.success(f"✅ Ativo (a cada {intervalo_atual}h desde 00:00)")
|
|
||||||
|
with col1:
|
||||||
st.markdown("### Ações")
|
st.metric(label="Última Verificação", value=st.session_state['last_run'])
|
||||||
|
|
||||||
# Botão de execução manual
|
with col2:
|
||||||
if st.button("🚀 Executar Verificação Agora", type="primary"):
|
st.write("### Status Agendador")
|
||||||
with st.spinner('Verificando status dos serviços...'):
|
intervalo_atual = st.session_state['intervalo_horas']
|
||||||
status, messages, whatsapp_status, email_status = run_monitor_check()
|
st.success(f"✅ Ativo (a cada {intervalo_atual}h desde 00:00)")
|
||||||
st.session_state['last_run'] = datetime.now().strftime("%H:%M:%S")
|
|
||||||
|
st.markdown("### Ações")
|
||||||
# Adicionar ao histórico de logs
|
|
||||||
timestamp = datetime.now().strftime("%H:%M:%S")
|
# Botão de execução manual
|
||||||
entry = {
|
if st.button("🚀 Executar Verificação Agora", type="primary"):
|
||||||
"Hora": timestamp,
|
with st.spinner('Verificando status dos serviços...'):
|
||||||
"Status": "✅ Sucesso" if status else "❌ Erro",
|
status, messages, whatsapp_status, email_status = run_monitor_check()
|
||||||
"WhatsApp": whatsapp_status,
|
now = datetime.now(tz)
|
||||||
"Email": email_status,
|
st.session_state['last_run'] = now.strftime("%H:%M:%S")
|
||||||
"Detalhes": " | ".join(messages)
|
|
||||||
}
|
# Adicionar ao histórico de logs
|
||||||
st.session_state['logs'].insert(0, entry) # Adiciona no topo
|
timestamp = now.strftime("%H:%M:%S")
|
||||||
|
entry = {
|
||||||
if status:
|
"Hora": timestamp,
|
||||||
st.success("✅ Todos os serviços estão operacionais!")
|
"Status": "✅ Sucesso" if status else "❌ Erro",
|
||||||
else:
|
"WhatsApp": whatsapp_status,
|
||||||
st.error("❌ Falha detectada em um ou mais serviços. Verifique os logs.")
|
"Email": email_status,
|
||||||
|
"Detalhes": " | ".join(messages)
|
||||||
st.markdown("---")
|
}
|
||||||
st.markdown("### 📋 Histórico de Execuções (Sessão Atual)")
|
st.session_state['logs'].insert(0, entry) # Adiciona no topo
|
||||||
|
|
||||||
if st.session_state['logs']:
|
if status:
|
||||||
# Renderizar tabela HTML customizada
|
st.success("✅ Todos os serviços estão operacionais!")
|
||||||
tabela_html = gerar_tabela_html(st.session_state['logs'])
|
else:
|
||||||
st.markdown(tabela_html, unsafe_allow_html=True)
|
st.error("❌ Falha detectada em um ou mais serviços. Verifique os logs.")
|
||||||
else:
|
|
||||||
st.info("ℹ️ Nenhuma verificação executada nesta sessão ainda.")
|
st.markdown("---")
|
||||||
|
st.markdown("### 📋 Histórico de Execuções (Sessão Atual)")
|
||||||
# ===== Sidebar =====
|
|
||||||
st.sidebar.title("ℹ️ Sobre")
|
if st.session_state['logs']:
|
||||||
st.sidebar.info(
|
# Renderizar tabela HTML customizada
|
||||||
"""
|
tabela_html = gerar_tabela_html(st.session_state['logs'])
|
||||||
Este aplicativo monitora:
|
st.markdown(tabela_html, unsafe_allow_html=True)
|
||||||
|
else:
|
||||||
1. **Evolution API**: Verifica se a instância está online.
|
st.info("ℹ️ Nenhuma verificação executada nesta sessão ainda.")
|
||||||
2. **N8N Webhook**: Verifica se o endpoint responde.
|
|
||||||
|
# ===== Sidebar =====
|
||||||
**Notificações:**
|
st.sidebar.title("ℹ️ Sobre")
|
||||||
- ✅ **Sucesso**: Envia WhatsApp.
|
st.sidebar.info(
|
||||||
- ❌ **Erro**: Envia Email.
|
"""
|
||||||
"""
|
Este aplicativo monitora:
|
||||||
)
|
|
||||||
|
1. **Evolution API**: Verifica se a instância está online.
|
||||||
st.sidebar.markdown("---")
|
2. **N8N Webhook**: Verifica se o endpoint responde.
|
||||||
|
|
||||||
# Configuração do Intervalo de Disparo
|
**Notificações:**
|
||||||
st.sidebar.markdown("### ⏱️ Intervalo de Disparo")
|
- ✅ **Sucesso**: Envia WhatsApp.
|
||||||
intervalo_slider = st.sidebar.slider(
|
- ❌ **Erro**: Envia Email.
|
||||||
"Executar a cada (horas):",
|
"""
|
||||||
min_value=1,
|
)
|
||||||
max_value=12,
|
|
||||||
value=st.session_state['intervalo_horas'],
|
st.sidebar.markdown("---")
|
||||||
step=1,
|
|
||||||
help="Define o intervalo entre execuções automáticas a partir de 00:00"
|
# Configuração do Intervalo de Disparo
|
||||||
)
|
st.sidebar.markdown("### ⏱️ Intervalo de Disparo")
|
||||||
|
intervalo_slider = st.sidebar.slider(
|
||||||
# Mostrar horários de execução baseados no intervalo
|
"Executar a cada (horas):",
|
||||||
horarios = [f"{h:02d}:00" for h in range(0, 24, intervalo_slider)]
|
min_value=1,
|
||||||
st.sidebar.caption(f"Horários: {', '.join(horarios)}")
|
max_value=12,
|
||||||
|
value=st.session_state['intervalo_horas'],
|
||||||
# Verificar se o intervalo mudou
|
step=1,
|
||||||
if intervalo_slider != st.session_state['intervalo_horas']:
|
help="Define o intervalo entre execuções automáticas a partir de 00:00"
|
||||||
st.sidebar.warning("⚠️ Reinicie a aplicação para aplicar o novo intervalo.")
|
)
|
||||||
st.session_state['intervalo_horas'] = intervalo_slider
|
|
||||||
|
# Mostrar horários de execução baseados no intervalo
|
||||||
st.sidebar.markdown("---")
|
horarios = [f"{h:02d}:00" for h in range(0, 24, intervalo_slider)]
|
||||||
|
st.sidebar.caption(f"Horários: {', '.join(horarios)}")
|
||||||
# Diagnóstico N8N na Sidebar
|
|
||||||
with st.sidebar.expander("🛠️ Diagnóstico N8N"):
|
# Verificar se o intervalo mudou
|
||||||
st.write("Teste isolado do Webhook N8N")
|
if intervalo_slider != st.session_state['intervalo_horas']:
|
||||||
if st.button("Testar Webhook N8N", key="btn_diag"):
|
st.sidebar.warning("⚠️ Reinicie a aplicação para aplicar o novo intervalo.")
|
||||||
with st.spinner("Enviando POST de teste..."):
|
st.session_state['intervalo_horas'] = intervalo_slider
|
||||||
success, log_data = diagnose_n8n_webhook()
|
|
||||||
|
st.sidebar.markdown("---")
|
||||||
if success:
|
|
||||||
st.success(f"✅ Status: {log_data['status_code']}")
|
# Diagnóstico N8N na Sidebar
|
||||||
else:
|
with st.sidebar.expander("🛠️ Diagnóstico N8N"):
|
||||||
st.error(f"❌ Erro: {log_data.get('status_code') or log_data.get('error')}")
|
st.write("Teste isolado do Webhook N8N")
|
||||||
|
if st.button("Testar Webhook N8N", key="btn_diag"):
|
||||||
st.json(log_data)
|
with st.spinner("Enviando POST de teste..."):
|
||||||
|
success, log_data = diagnose_n8n_webhook()
|
||||||
# Informações de configuração
|
|
||||||
st.sidebar.markdown("---")
|
if success:
|
||||||
st.sidebar.caption(f"Intervalo atual: {st.session_state['intervalo_horas']}h")
|
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")
|
||||||
|
|||||||
559
monitor_logic.py
559
monitor_logic.py
@@ -1,274 +1,285 @@
|
|||||||
import os
|
import os
|
||||||
import requests
|
import requests
|
||||||
import smtplib
|
import smtplib
|
||||||
from email.mime.text import MIMEText
|
from email.mime.text import MIMEText
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dotenv import load_dotenv
|
import pytz
|
||||||
|
from dotenv import load_dotenv
|
||||||
# Carregar variáveis de ambiente
|
|
||||||
load_dotenv()
|
# Carregar variáveis de ambiente
|
||||||
|
load_dotenv()
|
||||||
# Configurações
|
|
||||||
EVOLUTION_API_URL = os.getenv("EVOLUTION_API_URL")
|
# Configurações
|
||||||
EVOLUTION_INSTANCE_ID = os.getenv("EVOLUTION_INSTANCE_ID")
|
EVOLUTION_API_URL = os.getenv("EVOLUTION_API_URL")
|
||||||
EVOLUTION_API_TOKEN = os.getenv("EVOLUTION_API_TOKEN")
|
EVOLUTION_INSTANCE_ID = os.getenv("EVOLUTION_INSTANCE_ID")
|
||||||
EVOLUTION_SENDER_INSTANCE = os.getenv("EVOLUTION_SENDER_INSTANCE", "Wander")
|
EVOLUTION_API_TOKEN = os.getenv("EVOLUTION_API_TOKEN")
|
||||||
N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL")
|
EVOLUTION_SENDER_INSTANCE = os.getenv("EVOLUTION_SENDER_INSTANCE", "Wander")
|
||||||
WHATSAPP_NUMBER = os.getenv("WHATSAPP_NUMBER")
|
N8N_WEBHOOK_URL = os.getenv("N8N_WEBHOOK_URL")
|
||||||
SMTP_SERVER = os.getenv("SMTP_SERVER", "smtp-mail.outlook.com")
|
WHATSAPP_NUMBER = os.getenv("WHATSAPP_NUMBER")
|
||||||
SMTP_PORT = int(os.getenv("SMTP_PORT", 587))
|
SMTP_SERVER = os.getenv("SMTP_SERVER", "smtp-mail.outlook.com")
|
||||||
SMTP_EMAIL = os.getenv("SMTP_EMAIL")
|
SMTP_PORT = int(os.getenv("SMTP_PORT", 587))
|
||||||
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD")
|
SMTP_EMAIL = os.getenv("SMTP_EMAIL")
|
||||||
EMAIL_TO = os.getenv("EMAIL_TO")
|
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD")
|
||||||
|
EMAIL_TO = os.getenv("EMAIL_TO")
|
||||||
def get_headers():
|
|
||||||
return {
|
# Timezone configurável via variável de ambiente
|
||||||
"apikey": EVOLUTION_API_TOKEN,
|
TIMEZONE = os.getenv("TIMEZONE", "America/Sao_Paulo")
|
||||||
"Authorization": f"Bearer {EVOLUTION_API_TOKEN}",
|
tz = pytz.timezone(TIMEZONE)
|
||||||
"Content-Type": "application/json"
|
|
||||||
}
|
def get_now():
|
||||||
|
"""Retorna datetime atual com timezone configurado."""
|
||||||
def send_email_error(error_details):
|
return datetime.now(tz)
|
||||||
"""Envia email de erro caso algo falhe."""
|
|
||||||
try:
|
def get_headers():
|
||||||
subject = "🚨 ERRO - Monitor Evolution API"
|
return {
|
||||||
body = f"Falha no monitoramento.\nHorário: {datetime.now().strftime('%d/%m/%Y %H:%M:%S')}\n\nDetalhes do Erro:\n{error_details}"
|
"apikey": EVOLUTION_API_TOKEN,
|
||||||
|
"Authorization": f"Bearer {EVOLUTION_API_TOKEN}",
|
||||||
msg = MIMEText(body)
|
"Content-Type": "application/json"
|
||||||
msg['Subject'] = subject
|
}
|
||||||
msg['From'] = SMTP_EMAIL
|
|
||||||
msg['To'] = EMAIL_TO
|
def send_email_error(error_details):
|
||||||
|
"""Envia email de erro caso algo falhe."""
|
||||||
# Ler configuração de segurança (Default: STARTTLS)
|
try:
|
||||||
smtp_security = os.getenv("SMTP_SECURITY", "STARTTLS").upper()
|
now = get_now()
|
||||||
|
subject = "🚨 ERRO - Monitor Evolution API"
|
||||||
if smtp_security == "SSL":
|
body = f"Falha no monitoramento.\nHorário: {now.strftime('%d/%m/%Y %H:%M:%S')}\n\nDetalhes do Erro:\n{error_details}"
|
||||||
server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
|
|
||||||
else:
|
msg = MIMEText(body)
|
||||||
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
|
msg['Subject'] = subject
|
||||||
if smtp_security == "STARTTLS":
|
msg['From'] = SMTP_EMAIL
|
||||||
server.starttls()
|
msg['To'] = EMAIL_TO
|
||||||
|
|
||||||
with server:
|
# Ler configuração de segurança (Default: STARTTLS)
|
||||||
server.login(SMTP_EMAIL, SMTP_PASSWORD)
|
smtp_security = os.getenv("SMTP_SECURITY", "STARTTLS").upper()
|
||||||
server.send_message(msg)
|
|
||||||
|
if smtp_security == "SSL":
|
||||||
return True, "Email de erro enviado com sucesso."
|
server = smtplib.SMTP_SSL(SMTP_SERVER, SMTP_PORT)
|
||||||
except Exception as e:
|
else:
|
||||||
return False, f"Falha ao enviar email: {str(e)}"
|
server = smtplib.SMTP(SMTP_SERVER, SMTP_PORT)
|
||||||
|
if smtp_security == "STARTTLS":
|
||||||
def send_whatsapp_success():
|
server.starttls()
|
||||||
"""Envia mensagem de sucesso via Evolution API."""
|
|
||||||
try:
|
with server:
|
||||||
url = f"{EVOLUTION_API_URL}/message/sendText/{EVOLUTION_SENDER_INSTANCE}"
|
server.login(SMTP_EMAIL, SMTP_PASSWORD)
|
||||||
|
server.send_message(msg)
|
||||||
now = datetime.now()
|
|
||||||
message_text = (
|
return True, "Email de erro enviado com sucesso."
|
||||||
f"✅ *MONITOR OK*\n\n"
|
except Exception as e:
|
||||||
f"📅 Data: {now.strftime('%d/%m/%Y')}\n"
|
return False, f"Falha ao enviar email: {str(e)}"
|
||||||
f"🕐 Horário: {now.strftime('%H:%M:%S')}\n\n"
|
|
||||||
f"• Evolution API: Online\n"
|
def send_whatsapp_success():
|
||||||
f"• Webhook Instância: Habilitado\n"
|
"""Envia mensagem de sucesso via Evolution API."""
|
||||||
f"• Webhook N8N: Funcionando\n\n"
|
try:
|
||||||
f"Todos os serviços operacionais."
|
url = f"{EVOLUTION_API_URL}/message/sendText/{EVOLUTION_SENDER_INSTANCE}"
|
||||||
)
|
|
||||||
|
now = get_now()
|
||||||
payload = {
|
message_text = (
|
||||||
"number": WHATSAPP_NUMBER,
|
f"✅ *MONITOR OK*\n\n"
|
||||||
"text": message_text
|
f"📅 Data: {now.strftime('%d/%m/%Y')}\n"
|
||||||
}
|
f"🕐 Horário: {now.strftime('%H:%M:%S')}\n\n"
|
||||||
|
f"• Evolution API: Online\n"
|
||||||
# A Evolution API às vezes aceita apikey no header ou como query param,
|
f"• Webhook Instância: Habilitado\n"
|
||||||
# mas o padrão aqui é usar o header configurado.
|
f"• Webhook N8N: Funcionando\n\n"
|
||||||
response = requests.post(url, json=payload, headers=get_headers(), timeout=10)
|
f"Todos os serviços operacionais."
|
||||||
|
)
|
||||||
if response.status_code == 200 or response.status_code == 201:
|
|
||||||
return True, "WhatsApp de sucesso enviado."
|
payload = {
|
||||||
else:
|
"number": WHATSAPP_NUMBER,
|
||||||
return False, f"Falha ao enviar WhatsApp: {response.text}"
|
"text": message_text
|
||||||
|
}
|
||||||
except Exception as e:
|
|
||||||
return False, f"Erro na requisição WhatsApp: {str(e)}"
|
# A Evolution API às vezes aceita apikey no header ou como query param,
|
||||||
|
# mas o padrão aqui é usar o header configurado.
|
||||||
def run_monitor_check():
|
response = requests.post(url, json=payload, headers=get_headers(), timeout=10)
|
||||||
"""Executa o fluxo de verificação completo."""
|
|
||||||
log_messages = []
|
if response.status_code == 200 or response.status_code == 201:
|
||||||
whatsapp_status = "-"
|
return True, "WhatsApp de sucesso enviado."
|
||||||
email_status = "-"
|
else:
|
||||||
|
return False, f"Falha ao enviar WhatsApp: {response.text}"
|
||||||
# Passo 1: Verificar Health Evolution API
|
|
||||||
try:
|
except Exception as e:
|
||||||
# Endpoint correto: /instance/connectionState/{instanceName}
|
return False, f"Erro na requisição WhatsApp: {str(e)}"
|
||||||
# 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}"
|
def run_monitor_check():
|
||||||
resp_health = requests.get(health_url, headers=get_headers(), timeout=10)
|
"""Executa o fluxo de verificação completo."""
|
||||||
|
log_messages = []
|
||||||
if resp_health.status_code == 200:
|
whatsapp_status = "-"
|
||||||
# Validar se a instância está realmente conectada (state: open)
|
email_status = "-"
|
||||||
try:
|
|
||||||
data = resp_health.json()
|
# Passo 1: Verificar Health Evolution API
|
||||||
instance_data = data.get('instance', {})
|
try:
|
||||||
state = instance_data.get('state') or data.get('state')
|
# Endpoint correto: /instance/connectionState/{instanceName}
|
||||||
|
# Usa o nome da instância (EVOLUTION_SENDER_INSTANCE) e não o ID
|
||||||
if state == 'open':
|
health_url = f"{EVOLUTION_API_URL}/instance/connectionState/{EVOLUTION_SENDER_INSTANCE}"
|
||||||
log_messages.append("✅ Evolution API Online")
|
resp_health = requests.get(health_url, headers=get_headers(), timeout=10)
|
||||||
else:
|
|
||||||
# Estado não é 'open' (ex: close, closed, connecting) = ERRO!
|
if resp_health.status_code == 200:
|
||||||
error_msg = f"Evolution API Desconectada (State: {state})"
|
# Validar se a instância está realmente conectada (state: open)
|
||||||
log_messages.append(f"❌ {error_msg}")
|
try:
|
||||||
|
data = resp_health.json()
|
||||||
# Enviar email de erro
|
instance_data = data.get('instance', {})
|
||||||
email_ok, _ = send_email_error(error_msg)
|
state = instance_data.get('state') or data.get('state')
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
|
||||||
|
if state == 'open':
|
||||||
return False, log_messages, whatsapp_status, email_status
|
log_messages.append("✅ Evolution API Online")
|
||||||
|
else:
|
||||||
except Exception as json_err:
|
# Estado não é 'open' (ex: close, closed, connecting) = ERRO!
|
||||||
# Se falhar ler JSON mas deu 200 OK, verificar se é HTML
|
error_msg = f"Evolution API Desconectada (State: {state})"
|
||||||
raw_text = resp_health.text.strip()
|
log_messages.append(f"❌ {error_msg}")
|
||||||
if raw_text.startswith('<!doctype') or raw_text.startswith('<html'):
|
|
||||||
error_msg = "Evolution API retornou HTML (possível erro de autenticação)"
|
# Enviar email de erro
|
||||||
elif raw_text:
|
email_ok, _ = send_email_error(error_msg)
|
||||||
error_msg = "Evolution API retornou JSON inválido"
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
else:
|
|
||||||
error_msg = "Evolution API retornou resposta vazia"
|
return False, log_messages, whatsapp_status, email_status
|
||||||
|
|
||||||
log_messages.append(f"❌ {error_msg}")
|
except Exception as json_err:
|
||||||
email_ok, _ = send_email_error(error_msg)
|
# Se falhar ler JSON mas deu 200 OK, verificar se é HTML
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
raw_text = resp_health.text.strip()
|
||||||
|
if raw_text.startswith('<!doctype') or raw_text.startswith('<html'):
|
||||||
return False, log_messages, whatsapp_status, email_status
|
error_msg = "Evolution API retornou HTML (possível erro de autenticação)"
|
||||||
else:
|
elif raw_text:
|
||||||
error_msg = f"Evolution API Offline. Status: {resp_health.status_code}"
|
error_msg = "Evolution API retornou JSON inválido"
|
||||||
log_messages.append(f"❌ {error_msg}")
|
else:
|
||||||
|
error_msg = "Evolution API retornou resposta vazia"
|
||||||
# Tenta enviar email de erro
|
|
||||||
email_ok, _ = send_email_error(error_msg)
|
log_messages.append(f"❌ {error_msg}")
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
email_ok, _ = send_email_error(error_msg)
|
||||||
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
return False, log_messages, whatsapp_status, email_status
|
|
||||||
|
return False, log_messages, whatsapp_status, email_status
|
||||||
except Exception as e:
|
else:
|
||||||
error_msg = f"Erro ao conectar na Evolution API: {str(e)}"
|
error_msg = f"Evolution API Offline. Status: {resp_health.status_code}"
|
||||||
log_messages.append(f"❌ {error_msg}")
|
log_messages.append(f"❌ {error_msg}")
|
||||||
|
|
||||||
email_ok, _ = send_email_error(error_msg)
|
# Tenta enviar email de erro
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
email_ok, _ = send_email_error(error_msg)
|
||||||
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
return False, log_messages, whatsapp_status, email_status
|
|
||||||
|
return False, log_messages, whatsapp_status, email_status
|
||||||
# Passo 1.5: Verificar se o Webhook da Instância está habilitado
|
|
||||||
try:
|
except Exception as e:
|
||||||
webhook_url = f"{EVOLUTION_API_URL}/webhook/find/{EVOLUTION_SENDER_INSTANCE}"
|
error_msg = f"Erro ao conectar na Evolution API: {str(e)}"
|
||||||
resp_webhook_config = requests.get(webhook_url, headers=get_headers(), timeout=15)
|
log_messages.append(f"❌ {error_msg}")
|
||||||
|
|
||||||
if resp_webhook_config.status_code == 200:
|
email_ok, _ = send_email_error(error_msg)
|
||||||
webhook_data = resp_webhook_config.json()
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
# Verificar se enabled é True
|
|
||||||
webhook_enabled = webhook_data.get('enabled', False)
|
return False, log_messages, whatsapp_status, email_status
|
||||||
webhook_url_configured = webhook_data.get('url', 'N/A')
|
|
||||||
|
# Passo 1.5: Verificar se o Webhook da Instância está habilitado
|
||||||
if webhook_enabled:
|
try:
|
||||||
log_messages.append("✅ Webhook Instância OK")
|
webhook_url = f"{EVOLUTION_API_URL}/webhook/find/{EVOLUTION_SENDER_INSTANCE}"
|
||||||
else:
|
resp_webhook_config = requests.get(webhook_url, headers=get_headers(), timeout=15)
|
||||||
error_msg = f"Webhook da Instância DESABILITADO"
|
|
||||||
log_messages.append(f"❌ {error_msg}")
|
if resp_webhook_config.status_code == 200:
|
||||||
|
webhook_data = resp_webhook_config.json()
|
||||||
email_ok, _ = send_email_error(error_msg)
|
# Verificar se enabled é True
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
webhook_enabled = webhook_data.get('enabled', False)
|
||||||
|
webhook_url_configured = webhook_data.get('url', 'N/A')
|
||||||
return False, log_messages, whatsapp_status, email_status
|
|
||||||
else:
|
if webhook_enabled:
|
||||||
# Se não conseguir verificar, apenas loga aviso mas continua
|
log_messages.append("✅ Webhook Instância OK")
|
||||||
log_messages.append(f"⚠️ Não foi possível verificar webhook da instância (Status: {resp_webhook_config.status_code})")
|
else:
|
||||||
|
error_msg = f"Webhook da Instância DESABILITADO"
|
||||||
except Exception as e:
|
log_messages.append(f"❌ {error_msg}")
|
||||||
# Se falhar a verificação do webhook, loga aviso mas continua
|
|
||||||
log_messages.append(f"⚠️ Erro ao verificar webhook da instância: {str(e)[:50]}")
|
email_ok, _ = send_email_error(error_msg)
|
||||||
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
# Passo 2: Testar Webhook N8N
|
|
||||||
try:
|
return False, log_messages, whatsapp_status, email_status
|
||||||
# Nota: O fluxo original faz um POST simples
|
else:
|
||||||
resp_webhook = requests.post(N8N_WEBHOOK_URL, json={"check": "ping"}, timeout=10)
|
# Se não conseguir verificar, apenas loga aviso mas continua
|
||||||
|
log_messages.append(f"⚠️ Não foi possível verificar webhook da instância (Status: {resp_webhook_config.status_code})")
|
||||||
if resp_webhook.status_code == 200:
|
|
||||||
log_messages.append("✅ Webhook N8N OK")
|
except Exception as e:
|
||||||
else:
|
# Se falhar a verificação do webhook, loga aviso mas continua
|
||||||
error_msg = f"Webhook N8N falhou. Status: {resp_webhook.status_code}"
|
log_messages.append(f"⚠️ Erro ao verificar webhook da instância: {str(e)[:50]}")
|
||||||
log_messages.append(f"❌ {error_msg}")
|
|
||||||
|
# Passo 2: Testar Webhook N8N
|
||||||
email_ok, _ = send_email_error(error_msg)
|
try:
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
# Nota: O fluxo original faz um POST simples
|
||||||
|
resp_webhook = requests.post(N8N_WEBHOOK_URL, json={"check": "ping"}, timeout=10)
|
||||||
return False, log_messages, whatsapp_status, email_status
|
|
||||||
|
if resp_webhook.status_code == 200:
|
||||||
except Exception as e:
|
log_messages.append("✅ Webhook N8N OK")
|
||||||
error_msg = f"Erro ao conectar no Webhook N8N: {str(e)}"
|
else:
|
||||||
log_messages.append(f"❌ {error_msg}")
|
error_msg = f"Webhook N8N falhou. Status: {resp_webhook.status_code}"
|
||||||
|
log_messages.append(f"❌ {error_msg}")
|
||||||
email_ok, _ = send_email_error(error_msg)
|
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
email_ok, _ = send_email_error(error_msg)
|
||||||
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
return False, log_messages, whatsapp_status, email_status
|
|
||||||
|
return False, log_messages, whatsapp_status, email_status
|
||||||
# Passo 3: Se chegou aqui, tudo OK -> Enviar WhatsApp
|
|
||||||
# Se está tudo OK, email de erro não é enviado, então mantemos email_status como "-" ou podemos por "N/A"
|
except Exception as e:
|
||||||
# O usuário pediu "Sucesso ou Error". Se não enviou nada, talvez "-" seja melhor que "Error".
|
error_msg = f"Erro ao conectar no Webhook N8N: {str(e)}"
|
||||||
|
log_messages.append(f"❌ {error_msg}")
|
||||||
success, msg = send_whatsapp_success()
|
|
||||||
if success:
|
email_ok, _ = send_email_error(error_msg)
|
||||||
log_messages.append(f"✅ {msg}")
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
whatsapp_status = "Sucesso"
|
|
||||||
return True, log_messages, whatsapp_status, email_status
|
return False, log_messages, whatsapp_status, email_status
|
||||||
else:
|
|
||||||
# Se falhar enviar o whats, é um erro também?
|
# Passo 3: Se chegou aqui, tudo OK -> Enviar WhatsApp
|
||||||
whatsapp_status = "Erro"
|
# 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".
|
||||||
# O fluxo original manda email de erro se o check falhar.
|
|
||||||
# Se o envio do whats falhar, vamos mandar email
|
success, msg = send_whatsapp_success()
|
||||||
error_msg = f"Serviços Online, mas falha ao enviar WhatsApp: {msg}"
|
if success:
|
||||||
log_messages.append(f"⚠️ {error_msg}")
|
log_messages.append(f"✅ {msg}")
|
||||||
|
whatsapp_status = "Sucesso"
|
||||||
email_ok, _ = send_email_error(error_msg)
|
return True, log_messages, whatsapp_status, email_status
|
||||||
email_status = "Sucesso" if email_ok else "Erro"
|
else:
|
||||||
|
# Se falhar enviar o whats, é um erro também?
|
||||||
return False, log_messages, whatsapp_status, email_status
|
whatsapp_status = "Erro"
|
||||||
|
|
||||||
def diagnose_n8n_webhook():
|
# O fluxo original manda email de erro se o check falhar.
|
||||||
"""Realiza um teste detalhado do Webhook N8N para diagnóstico."""
|
# Se o envio do whats falhar, vamos mandar email
|
||||||
try:
|
error_msg = f"Serviços Online, mas falha ao enviar WhatsApp: {msg}"
|
||||||
url = N8N_WEBHOOK_URL
|
log_messages.append(f"⚠️ {error_msg}")
|
||||||
payload = {"check": "diagnosis_ping", "timestamp": str(datetime.now())}
|
|
||||||
|
email_ok, _ = send_email_error(error_msg)
|
||||||
# Preparar log
|
email_status = "Sucesso" if email_ok else "Erro"
|
||||||
log = {
|
|
||||||
"url": url,
|
return False, log_messages, whatsapp_status, email_status
|
||||||
"method": "POST",
|
|
||||||
"payload_sent": payload,
|
def diagnose_n8n_webhook():
|
||||||
"status_code": None,
|
"""Realiza um teste detalhado do Webhook N8N para diagnóstico."""
|
||||||
"response_body": None,
|
try:
|
||||||
"headers_received": None,
|
url = N8N_WEBHOOK_URL
|
||||||
"error": None
|
now = get_now()
|
||||||
}
|
payload = {"check": "diagnosis_ping", "timestamp": str(now)}
|
||||||
|
|
||||||
response = requests.post(url, json=payload, timeout=10)
|
# Preparar log
|
||||||
|
log = {
|
||||||
log["status_code"] = response.status_code
|
"url": url,
|
||||||
log["response_body"] = response.text
|
"method": "POST",
|
||||||
log["headers_received"] = dict(response.headers)
|
"payload_sent": payload,
|
||||||
|
"status_code": None,
|
||||||
if response.status_code == 200:
|
"response_body": None,
|
||||||
return True, log
|
"headers_received": None,
|
||||||
else:
|
"error": None
|
||||||
return False, log
|
}
|
||||||
|
|
||||||
except Exception as e:
|
response = requests.post(url, json=payload, timeout=10)
|
||||||
log["error"] = str(e)
|
|
||||||
return False, log
|
log["status_code"] = response.status_code
|
||||||
|
log["response_body"] = response.text
|
||||||
if __name__ == "__main__":
|
log["headers_received"] = dict(response.headers)
|
||||||
# Teste rápido se rodar direto
|
|
||||||
status, logs, wa_status, em_status = run_monitor_check()
|
if response.status_code == 200:
|
||||||
print(f"Status: {status}")
|
return True, log
|
||||||
print(f"WhatsApp: {wa_status} | Email: {em_status}")
|
else:
|
||||||
print("\n".join(logs))
|
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))
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
streamlit>=1.28.0
|
streamlit>=1.28.0
|
||||||
requests>=2.31.0
|
requests>=2.31.0
|
||||||
python-dotenv>=1.0.0
|
python-dotenv>=1.0.0
|
||||||
apscheduler>=3.10.0
|
apscheduler>=3.10.0
|
||||||
pandas>=2.0.0
|
pandas>=2.0.0
|
||||||
|
pytz>=2023.3
|
||||||
|
|||||||
Reference in New Issue
Block a user