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 = """
| Classificação da Informação: | RELATÓRIO |
| RESTRITO | Página 1 de 1 |
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.
| Início da amostragem | {{ start_date }} |
| Fim da amostragem | {{ end_date }} |
| Local | Infraestrutura do Cliente / Datacenter iTGuys |
Não houve incidentes registrados no período apurado.
{% endif %}