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 = """ Relatório de Disponibilidade - iTGuys
iTGuys
Classificação da Informação: RELATÓRIO
RESTRITO Página 1 de 1

RELATÓRIO DE DISPONIBILIDADE

RESUMO EXECUTIVO

Este documento apresenta um resumo da disponibilidade dos equipamentos monitorados no cliente Enseg, bem como um resumo de eventuais incidentes que causaram indisponibilidade no período apurado.

DADOS DO MONITORAMENTO
Início da amostragem {{ start_date }}
Fim da amostragem {{ end_date }}
Local Infraestrutura do Cliente / Datacenter iTGuys
GRÁFICOS
{% for chart in charts %}
{{ chart | safe }}
{% endfor %}
INCIDENTES
{% if alertas_html %} {{ alertas_html | safe }} {% endif %} {% if note %}
Nota: {{ note }}
{% endif %} {% if not alertas_html and not note %}

Não houve incidentes registrados no período apurado.

{% endif %}
""" # 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()