Initial commit: Automation script for Enseg reports
This commit is contained in:
commit
daaf433c2a
|
|
@ -0,0 +1,7 @@
|
|||
__pycache__/
|
||||
*.pyc
|
||||
*.html
|
||||
*.xlsx
|
||||
.venv/
|
||||
.env
|
||||
*copy.py
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
# Manual de Uso - Gerador de Relatório Enseg
|
||||
|
||||
Este script Python automatiza a geração de relatórios de disponibilidade e desempenho para o cliente **Enseg**, utilizando dados do **Zabbix API**. O relatório final é gerado em formato **HTML** com gráficos interativos e layout profissional.
|
||||
|
||||
## Conteúdo do Relatório
|
||||
|
||||
O relatório gerado inclui:
|
||||
1. **Resumo Executivo**: Visão geral do período.
|
||||
2. **Notas Extraordinárias** (Opcional): Seção para comunicar manutenções ou falhas sistêmicas.
|
||||
3. **Dados do Monitoramento**: Período de amostragem e local.
|
||||
4. **Gráficos de Desempenho**: Gráficos individuais por host mostrando:
|
||||
* **Status de Ping** (Área Verde): Indica disponibilidade (Up/Down).
|
||||
* **Perda de Pacotes** (Linha Vermelha): Indica degradação da conexão.
|
||||
5. **Incidentes**: Tabela com histórico de quedas (Ping inacessível) ou indisponibilidade, incluindo duração e justificativa.
|
||||
|
||||
---
|
||||
|
||||
## Pré-requisitos
|
||||
|
||||
Certifique-se de ter o **Python 3** instalado.
|
||||
|
||||
### Instalar Dependências
|
||||
|
||||
Abra o terminal na pasta do projeto e execute:
|
||||
|
||||
```bash
|
||||
pip install -r requirements.txt
|
||||
```
|
||||
|
||||
*As bibliotecas principais são: `pyzabbix`, `pandas`, `plotly`, `jinja2`, `requests`.*
|
||||
|
||||
---
|
||||
|
||||
## Como Executar
|
||||
|
||||
O script é executado via linha de comando (CMD, PowerShell ou Terminal).
|
||||
|
||||
### Comando Básico (Padrão 7 dias)
|
||||
|
||||
```bash
|
||||
python gerar_relatorio_enseg_zabbix.py --user "seu.usuario" --password "sua.senha"
|
||||
```
|
||||
|
||||
### Argumentos Disponíveis
|
||||
|
||||
| Argumento | Obrigatório? | Descrição | Exemplo |
|
||||
| :--- | :---: | :--- | :--- |
|
||||
| `--user` | **Sim** | Seu usuário do Zabbix. | `--user "joao.silva"` |
|
||||
| `--password` | **Sim** | Sua senha do Zabbix. | `--password "123Mudar"` |
|
||||
| `--days` | Não | Número de dias para análise (Padrão: 7). | `--days 15` |
|
||||
| `--note` | Não | Nota extraordinária para o relatório (ex: Manutenção). | `--note "Falha na fibra"` |
|
||||
|
||||
### Exemplos de Uso
|
||||
|
||||
**1. Gerar relatório dos últimos 15 dias:**
|
||||
|
||||
```bash
|
||||
python gerar_relatorio_enseg_zabbix.py --user "joao.goncalves" --password "******" --days 15
|
||||
```
|
||||
|
||||
**2. Adicionar uma Nota de Manutenção:**
|
||||
|
||||
Se houver uma falha conhecida que justifique a falta de dados ou incidentes, use a opção `--note`:
|
||||
|
||||
```bash
|
||||
python gerar_relatorio_enseg_zabbix.py --user "joao.goncalves" --password "******" --note "Interrupção programada para troca de switch no dia 10/02."
|
||||
```
|
||||
*Isso criará uma caixa amarela de destaque no relatório com o texto informado.*
|
||||
|
||||
---
|
||||
|
||||
## Filtros Automáticos
|
||||
|
||||
O script já possui filtros configurados internamente para garantir a precisão do relatório:
|
||||
|
||||
* **Grupo de Hosts**: Apenas hosts do grupo `[CLIENTES] - ENSEG`.
|
||||
* **Exclusões**: Ignora hosts com nomes contendo:
|
||||
* `Antena`
|
||||
* `AP`
|
||||
* `Ramal IP`
|
||||
* `DVR`
|
||||
* `Impressora`
|
||||
* `Painel Solar`
|
||||
* **Período**: Considera sempre do dia inicial à 00:00 até ontem às 23:59.
|
||||
|
||||
## Solução de Problemas
|
||||
|
||||
* **Erro de Autenticação**: Verifique se usuário e senha estão corretos e se você tem permissão de acesso ao Zabbix.
|
||||
* **Nenhum dado encontrado**: Verifique se o Zabbix está coletando dados para os hosts do grupo Enseg.
|
||||
* **Gráfico Vazio**: Pode indicar que o host não tem itens de monitoramento de ICMP Ping (`icmpping`, `icmppingloss`) configurados ou coletando.
|
||||
|
||||
---
|
||||
**Desenvolvido por iTGuys**
|
||||
|
|
@ -0,0 +1,501 @@
|
|||
import argparse
|
||||
import pandas as pd
|
||||
from pyzabbix import ZabbixAPI
|
||||
from datetime import datetime, timedelta
|
||||
import sys
|
||||
import plotly.graph_objects as go
|
||||
from plotly.subplots import make_subplots
|
||||
import jinja2
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description="Gerar Relatório Enseg via Zabbix API (HTML)")
|
||||
parser.add_argument('--user', required=True, help="Usuário do Zabbix")
|
||||
parser.add_argument('--password', required=True, help="Senha do Zabbix")
|
||||
parser.add_argument('--days', type=int, default=7, help="Dias para análise (padrão: 7)")
|
||||
parser.add_argument('--note', type=str, default="", help="Nota extraordinária para o relatório")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
zabbix_url = "https://noc.itguys.com.br"
|
||||
|
||||
print(f"Conectando ao Zabbix ({zabbix_url})...")
|
||||
|
||||
try:
|
||||
zapi = ZabbixAPI(zabbix_url)
|
||||
zapi.login(args.user, args.password)
|
||||
print(f"Autenticado com sucesso como: {args.user}")
|
||||
except Exception as e:
|
||||
print(f"Erro ao conectar/autenticar no Zabbix: {e}")
|
||||
sys.exit(1)
|
||||
|
||||
# Definir intervalo de tempo (Ontem 23:59:59 para trás até 00:00:00 do dia inicial)
|
||||
today = datetime.now()
|
||||
yesterday = today - timedelta(days=1)
|
||||
end_date = yesterday.replace(hour=23, minute=59, second=59, microsecond=0)
|
||||
|
||||
# Start date ajustado para 00:00:00
|
||||
start_date_raw = end_date - timedelta(days=args.days - 1) # -1 porque se dias=7, queremos incluir ontem + 6 dias atras?
|
||||
# Se days=7 e ontem=12. 12,11,10,9,8,7,6 (7 dias). 12-6 = 6.
|
||||
start_date = start_date_raw.replace(hour=0, minute=0, second=0, microsecond=0)
|
||||
|
||||
time_from = int(start_date.timestamp())
|
||||
time_till = int(end_date.timestamp())
|
||||
|
||||
# Formatar datas para exibição
|
||||
periodo_str = f"{start_date.strftime('%d/%m/%Y %H:%M')} a {end_date.strftime('%d/%m/%Y %H:%M')}"
|
||||
|
||||
print(f"Buscando Grupo de Host '[CLIENTES] - ENSEG'...")
|
||||
groups = zapi.hostgroup.get(
|
||||
filter={"name": "[CLIENTES] - ENSEG"},
|
||||
output=["groupid", "name"]
|
||||
)
|
||||
|
||||
if not groups:
|
||||
print("Grupo '[CLIENTES] - ENSEG' não encontrado.")
|
||||
sys.exit(0)
|
||||
|
||||
group_id = groups[0]['groupid']
|
||||
|
||||
print(f"Buscando hosts do grupo...")
|
||||
hosts = zapi.host.get(
|
||||
groupids=group_id,
|
||||
output=["hostid", "name"],
|
||||
searchWildcardsEnabled=True
|
||||
)
|
||||
|
||||
# Filtrar Hosts
|
||||
filtered_hosts = []
|
||||
ignored_terms = ["antena", "ap ", "ramal ip", "dvr", "impressora", "painel solar"]
|
||||
|
||||
for h in hosts:
|
||||
name_lower = h['name'].lower()
|
||||
if any(term in name_lower for term in ignored_terms):
|
||||
continue
|
||||
filtered_hosts.append(h)
|
||||
|
||||
hosts = filtered_hosts
|
||||
if not hosts:
|
||||
print("Nenhum host encontrado após filtros.")
|
||||
sys.exit(0)
|
||||
|
||||
print(f"Analisando {len(hosts)} hosts.")
|
||||
host_ids = [h['hostid'] for h in hosts]
|
||||
host_map = {h['hostid']: h['name'] for h in hosts}
|
||||
|
||||
# 1. ALERTAS
|
||||
print("Buscando alertas de 'Ping' ou 'Unavailable'...")
|
||||
triggers = zapi.trigger.get(
|
||||
hostids=host_ids,
|
||||
search={"description": "Ping*"},
|
||||
output=["triggerid", "description", "priority"],
|
||||
searchWildcardsEnabled=True,
|
||||
expandDescription=1
|
||||
)
|
||||
if not triggers:
|
||||
triggers = zapi.trigger.get(
|
||||
hostids=host_ids,
|
||||
search={"description": "*ICMP*"},
|
||||
output=["triggerid", "description", "priority"],
|
||||
searchWildcardsEnabled=True,
|
||||
expandDescription=1
|
||||
)
|
||||
trigger_ids = [t['triggerid'] for t in triggers]
|
||||
|
||||
alertas_data = []
|
||||
if trigger_ids:
|
||||
events = zapi.event.get(
|
||||
objectids=trigger_ids,
|
||||
time_from=time_from,
|
||||
time_till=time_till,
|
||||
output="extend",
|
||||
select_acknowledges="extend",
|
||||
sortfield="clock",
|
||||
sortorder="DESC"
|
||||
)
|
||||
events_problem = [e for e in events if e['value'] == '1']
|
||||
|
||||
# Maps
|
||||
triggers_info = zapi.trigger.get(triggerids=trigger_ids, output=["triggerid", "description"], selectHosts=["hostid", "name"])
|
||||
trigger_host_map = {}
|
||||
trigger_desc_map = {}
|
||||
for t in triggers_info:
|
||||
if t['hosts']: trigger_host_map[t['triggerid']] = t['hosts'][0]
|
||||
trigger_desc_map[t['triggerid']] = t['description']
|
||||
|
||||
for e in events_problem:
|
||||
obj_id = e['objectid']
|
||||
host_info = trigger_host_map.get(obj_id)
|
||||
if not host_info: continue
|
||||
|
||||
start_ts = int(e['clock'])
|
||||
start_dt = datetime.fromtimestamp(start_ts)
|
||||
r_eventid = e.get('r_eventid')
|
||||
duration_str = "Em andamento"
|
||||
|
||||
if r_eventid and r_eventid != '0':
|
||||
try:
|
||||
r_evt_api = zapi.event.get(eventids=r_eventid, output=["clock"])
|
||||
if r_evt_api:
|
||||
duration_str = str(timedelta(seconds=int(r_evt_api[0]['clock']) - start_ts))
|
||||
except: pass
|
||||
else:
|
||||
duration_str = f"{str(timedelta(seconds=int(datetime.now().timestamp()) - start_ts))} (Ativo)"
|
||||
|
||||
alertas_data.append({
|
||||
"Data Inicio": start_dt.strftime("%d/%m/%Y %H:%M:%S"),
|
||||
"Host": host_info['name'],
|
||||
"Problema": trigger_desc_map.get(obj_id, "Desconhecido"),
|
||||
"Duração": duration_str,
|
||||
"Justificativa": ""
|
||||
})
|
||||
|
||||
# 2. GRÁFICOS (Histórico)
|
||||
print("Gerando gráficos de desempenho...")
|
||||
charts_html = []
|
||||
|
||||
# Keys to fetch (Removido Latência conforme solicitado)
|
||||
keys = {
|
||||
'icmpping': 'Ping (Status)',
|
||||
'icmppingloss': 'Perda (%)'
|
||||
}
|
||||
|
||||
# Buscar itens de todos os hosts de uma vez
|
||||
items = zapi.item.get(
|
||||
hostids=host_ids,
|
||||
search={"key_": "icmpping*"},
|
||||
output=["itemid", "hostid", "key_", "name", "value_type"],
|
||||
searchWildcardsEnabled=True
|
||||
)
|
||||
|
||||
# Organizar itens por host
|
||||
host_items = {h_id: {} for h_id in host_ids}
|
||||
for item in items:
|
||||
# Simplificar key (remover parametros se houver, ex icmpping[,,,])
|
||||
key_simple = item['key_'].split('[')[0]
|
||||
if key_simple in keys:
|
||||
host_items[item['hostid']][key_simple] = item
|
||||
|
||||
for host_id in host_ids:
|
||||
host_name = host_map[host_id]
|
||||
h_items = host_items.get(host_id, {})
|
||||
|
||||
if not h_items:
|
||||
print(f"Skipping {host_name}: itens de ping não encontrados.")
|
||||
continue
|
||||
|
||||
# Buscar histórico
|
||||
history_data = {}
|
||||
|
||||
for k, item in h_items.items():
|
||||
hist = zapi.history.get(
|
||||
history=item['value_type'],
|
||||
itemids=[item['itemid']],
|
||||
time_from=time_from,
|
||||
time_till=time_till,
|
||||
output='extend',
|
||||
sortfield='clock',
|
||||
sortorder='ASC'
|
||||
)
|
||||
df = pd.DataFrame(hist)
|
||||
if not df.empty:
|
||||
# Convert clock to int first, then datetime
|
||||
df['clock'] = pd.to_datetime(df['clock'].astype(int), unit='s') - timedelta(hours=3)
|
||||
df['value'] = pd.to_numeric(df['value'])
|
||||
history_data[k] = df
|
||||
|
||||
if not history_data:
|
||||
continue
|
||||
|
||||
# Criar Gráfico com Plotly (Tema Escuro)
|
||||
fig = make_subplots(specs=[[{"secondary_y": True}]])
|
||||
|
||||
# 1. Ping Status (Left Y - Area Verde)
|
||||
if 'icmpping' in history_data:
|
||||
df = history_data['icmpping']
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df['clock'],
|
||||
y=df['value'],
|
||||
name="Ping (Status)",
|
||||
line=dict(color='#00CC00', width=1), # Verde Zabbix
|
||||
fill='tozeroy', # Preencher área
|
||||
mode='lines'
|
||||
),
|
||||
secondary_y=False
|
||||
)
|
||||
|
||||
# 2. Perda (Right Y - Linha/Area Vermelha)
|
||||
if 'icmppingloss' in history_data:
|
||||
df = history_data['icmppingloss']
|
||||
# Filtrar valores zerados para não poluir visualmente se quiser, mas Zabbix mostra tudo.
|
||||
# O exemplo mostra spikes vermelhos.
|
||||
fig.add_trace(
|
||||
go.Scatter(
|
||||
x=df['clock'],
|
||||
y=df['value'],
|
||||
name="Perda (%)",
|
||||
line=dict(color='red', width=2),
|
||||
fill='tozeroy', # Spikes sólidos parecem preenchidos
|
||||
mode='lines'
|
||||
),
|
||||
secondary_y=True
|
||||
)
|
||||
|
||||
fig.update_layout(
|
||||
title={
|
||||
'text': f"{host_name}: Disponibilidade x Perda de Pacotes",
|
||||
'font': {'color': 'white', 'size': 14}
|
||||
},
|
||||
height=350,
|
||||
template="plotly_dark", # Tema Escuro
|
||||
paper_bgcolor='rgb(30, 30, 30)', # Fundo container
|
||||
plot_bgcolor='rgb(30, 30, 30)', # Fundo plot
|
||||
legend=dict(orientation="h", yanchor="bottom", y=-0.2, xanchor="left", x=0, font=dict(color='white')),
|
||||
margin=dict(l=40, r=40, t=60, b=40)
|
||||
)
|
||||
|
||||
# Eixo Y Esquerdo (Ping Status 0-1)
|
||||
fig.update_yaxes(
|
||||
title_text="Status (1=Up, 0=Down)",
|
||||
secondary_y=False,
|
||||
range=[0, 1.1], # Começa do 0 absoluto para alinhar
|
||||
showgrid=True,
|
||||
gridcolor='rgb(50, 50, 50)',
|
||||
tickfont=dict(color='white'),
|
||||
title_font=dict(color='white')
|
||||
)
|
||||
|
||||
# Eixo Y Direito (Perda %)
|
||||
fig.update_yaxes(
|
||||
title_text="Perda (%)",
|
||||
secondary_y=True,
|
||||
showgrid=False,
|
||||
tickfont=dict(color='red'),
|
||||
title_font=dict(color='white'), # Título branco conforme solicitado
|
||||
rangemode="tozero" # Começar do zero
|
||||
)
|
||||
|
||||
# Eixo X
|
||||
fig.update_xaxes(
|
||||
showgrid=True,
|
||||
gridcolor='rgb(50, 50, 50)',
|
||||
tickfont=dict(color='white')
|
||||
)
|
||||
|
||||
charts_html.append(fig.to_html(full_html=False, include_plotlyjs='cdn'))
|
||||
print(f"Gráfico gerado para {host_name}")
|
||||
|
||||
# GERAÇÃO HTML
|
||||
print("Gerando relatório HTML...")
|
||||
|
||||
# CSS e Template Baseado na Identidade Visual (Azul/Verde/Cinza)
|
||||
html_template = """
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Relatório de Disponibilidade - iTGuys</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Segoe+UI:wght@400;600;700&display=swap');
|
||||
|
||||
body {
|
||||
font-family: 'Segoe UI', sans-serif;
|
||||
color: #333;
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
background-color: #fff;
|
||||
}
|
||||
.page-container { max-width: 1200px; margin: 0 auto; }
|
||||
|
||||
/* Header Top */
|
||||
.header-top { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 40px; border-bottom: 2px solid #ddd; padding-bottom: 20px; }
|
||||
.logo-placeholder {
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
color: #333;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.logo-it { color: #00B0F0; }
|
||||
.logo-guys { color: #333; }
|
||||
|
||||
/* Header Info Box */
|
||||
.info-box { border: 1px solid #00B050; border-collapse: collapse; font-size: 12px; font-family: sans-serif; }
|
||||
.info-box td { border: 1px solid #00B050; padding: 5px 15px; text-align: center; }
|
||||
.lbl-blue { background-color: #DCE6F1; color: #1F497D; font-weight: bold; }
|
||||
.lbl-restrito { color: #1F497D; font-style: italic; font-weight: bold; font-size: 14px; }
|
||||
|
||||
/* Main Title */
|
||||
h1.main-title {
|
||||
color: #366092;
|
||||
font-size: 28px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 400;
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
|
||||
/* Sections */
|
||||
.section-bar {
|
||||
background-color: #95B3D7; /* Azul Médio */
|
||||
color: white;
|
||||
padding: 8px 15px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
margin-top: 40px;
|
||||
margin-bottom: 20px;
|
||||
border-left: 10px solid #00B050; /* Verde Accent */
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
/* Data Monitoring Table */
|
||||
.monitoring-data {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 20px;
|
||||
border-top: 2px solid #366092;
|
||||
border-bottom: 2px solid #366092;
|
||||
}
|
||||
.monitoring-data td { padding: 8px 15px; color: #1F497D; font-size: 14px; }
|
||||
.monitoring-data .label {
|
||||
width: 200px;
|
||||
text-align: right;
|
||||
font-weight: bold;
|
||||
background-color: #fff;
|
||||
}
|
||||
.monitoring-data .value {
|
||||
background-color: #DCE6F1;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Incidents Table */
|
||||
.incidents-table { width: 100%; border-collapse: collapse; font-size: 13px; }
|
||||
.incidents-table th {
|
||||
background-color: #366092;
|
||||
color: white;
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
text-transform: uppercase;
|
||||
font-size: 12px;
|
||||
}
|
||||
.incidents-table td { border: 1px solid #ccc; padding: 8px; color: #333; }
|
||||
.incidents-table tr:nth-child(even) { background-color: #f2f2f2; }
|
||||
|
||||
/* Charts */
|
||||
.chart-wrapper {
|
||||
margin-bottom: 30px;
|
||||
border: 1px solid #ccc;
|
||||
padding: 5px;
|
||||
background-color: #000; /* Fundo preto para combinar com o gráfico dark */
|
||||
}
|
||||
|
||||
.summary-text { font-size: 14px; color: #333; margin-bottom: 20px; line-height: 1.5; }
|
||||
|
||||
.extra-note {
|
||||
background-color: #fff3cd;
|
||||
border: 1px solid #ffeeba;
|
||||
color: #856404;
|
||||
padding: 15px;
|
||||
margin-top: 15px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container">
|
||||
<!-- Header -->
|
||||
<div class="header-top">
|
||||
<div class="logo-placeholder">
|
||||
<span class="logo-it">iT</span><span class="logo-guys">Guys</span>
|
||||
</div>
|
||||
<table class="info-box">
|
||||
<tr>
|
||||
<td class="lbl-blue">Classificação da Informação:</td>
|
||||
<td class="lbl-blue">RELATÓRIO</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="lbl-restrito">RESTRITO</td>
|
||||
<td>Página 1 de 1</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<h1 class="main-title">RELATÓRIO DE DISPONIBILIDADE</h1>
|
||||
|
||||
<!-- Resumo Executivo -->
|
||||
<div class="section-bar">RESUMO EXECUTIVO</div>
|
||||
<p class="summary-text">
|
||||
Este documento apresenta um resumo da disponibilidade dos equipamentos monitorados no cliente <strong>Enseg</strong>,
|
||||
bem como um resumo de eventuais incidentes que causaram indisponibilidade no período apurado.
|
||||
</p>
|
||||
|
||||
<!-- Dados do Monitoramento -->
|
||||
<div class="section-bar">DADOS DO MONITORAMENTO</div>
|
||||
<table class="monitoring-data">
|
||||
<tr>
|
||||
<td class="label">Início da amostragem</td>
|
||||
<td class="value">{{ start_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Fim da amostragem</td>
|
||||
<td class="value">{{ end_date }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="label">Local</td>
|
||||
<td class="value">Infraestrutura do Cliente / Datacenter iTGuys</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<!-- Gráficos -->
|
||||
<div class="section-bar">GRÁFICOS</div>
|
||||
{% for chart in charts %}
|
||||
<div class="chart-wrapper">
|
||||
{{ chart | safe }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
|
||||
<!-- Incidentes -->
|
||||
<div class="section-bar">INCIDENTES</div>
|
||||
{% if alertas_html %}
|
||||
{{ alertas_html | safe }}
|
||||
{% endif %}
|
||||
|
||||
{% if note %}
|
||||
<div class="extra-note">
|
||||
<strong>Nota:</strong> {{ note }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if not alertas_html and not note %}
|
||||
<p>Não houve incidentes registrados no período apurado.</p>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
# Criar DataFrame de Alertas para HTML (aplicando classes do pandas)
|
||||
df_alertas = pd.DataFrame(alertas_data)
|
||||
alertas_html = ""
|
||||
if not df_alertas.empty:
|
||||
# Pandas to_html com classes, mas sem border hardcoded
|
||||
alertas_html = df_alertas.to_html(index=False, classes='incidents-table', border=0)
|
||||
|
||||
template = jinja2.Template(html_template)
|
||||
html_content = template.render(
|
||||
start_date=start_date.strftime('%d/%m/%Y %H:%M'),
|
||||
end_date=end_date.strftime('%d/%m/%Y %H:%M'),
|
||||
periodo=periodo_str,
|
||||
alertas_html=alertas_html,
|
||||
charts=charts_html,
|
||||
note=args.note
|
||||
)
|
||||
|
||||
filename = f"Relatorio_Enseg_{datetime.now().strftime('%Y-%m-%d')}.html"
|
||||
with open(filename, "w", encoding="utf-8") as f:
|
||||
f.write(html_content)
|
||||
|
||||
print(f"Relatório gerado com sucesso: {filename}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
pandas
|
||||
pyzabbix
|
||||
requests
|
||||
plotly
|
||||
jinja2
|
||||
Loading…
Reference in New Issue