feat: initial architecture setup with Docker, SQLite persistence and PDF tracking

This commit is contained in:
Silas Brito
2026-07-13 18:58:05 -03:00
commit 27652716d7
10 changed files with 1365 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
from fpdf import FPDF
import datetime
import hashlib
class NetBackupMitigationPDF(FPDF):
"""
Sleek, brand-aligned A4 layout representing the Veritas dark tech design system.
"""
def header(self):
# Draw the top header brand block
self.set_fill_color(11, 15, 25) # Deep Midnight Blue (#0B0F19)
self.rect(0, 0, 210, 38, 'F')
# Brand title text
self.set_xy(10, 8)
self.set_font('Helvetica', 'B', 16)
self.set_text_color(255, 255, 255)
self.cell(0, 8, 'NetBackup Log Insights & Actions', ln=True)
# Subtitle
self.set_font('Helvetica', 'I', 9)
self.set_text_color(0, 210, 255) # Cyan Accent
self.cell(0, 4, 'Relatorio de Mitigacao e Analise de Performance', ln=True)
# Top banner separator line
self.set_draw_color(0, 210, 255)
self.set_line_width(0.8)
self.line(10, 26, 200, 26)
self.set_xy(10, 42) # reset cursor below header banner
def footer(self):
self.set_y(-15)
self.set_font('Helvetica', 'I', 8)
self.set_text_color(148, 163, 184) # Light Slate
self.cell(0, 10, f'Gerado em {datetime.datetime.now().strftime("%d/%m/%Y %H:%M")} | Pagina {self.page_no()}', align='C')
def generate_mitigation_pdf(jobs_list, filter_name="Consolidated"):
"""
Generates a professional PDF summarising backup metrics and logged action steps.
"""
total_jobs = len(jobs_list)
success_jobs = len([j for j in jobs_list if j['exit_code'] <= 1])
success_rate = (success_jobs / total_jobs * 100) if total_jobs > 0 else 0.0
total_pre_dedup = sum(j['mbytes'] for j in jobs_list)
# Calculate MSDP simulated post-deduplicated sizes
total_post_dedup = 0.0
for j in jobs_list:
post_mb = j.get('mbytes', 0.0) * (0.15 + (int(hashlib.md5(str(j['job_id']).encode()).hexdigest(), 16) % 11) / 100.0)
total_post_dedup += post_mb
dedup_ratio = (total_pre_dedup / total_post_dedup) if total_post_dedup > 0.0 else 1.0
space_saved = ((1 - (total_post_dedup / total_pre_dedup)) * 100) if total_pre_dedup > 0.0 else 0.0
# Count unmitigated failure incidents (Exit Code > 1 AND is_rerun_success == 0 AND status != 'Resolvido')
active_failures = len([
j for j in jobs_list
if j['exit_code'] > 1 and j['is_rerun_success'] == 0 and j.get('status', 'Pendente') != 'Resolvido'
])
# Create PDF object
pdf = NetBackupMitigationPDF()
pdf.add_page()
pdf.set_auto_page_break(auto=True, margin=20)
# Scope indicator
pdf.set_font('Helvetica', 'B', 11)
pdf.set_text_color(30, 41, 59)
pdf.cell(0, 6, f'ZONA DE INFRAESTRUTURA ANALISADA: {filter_name.upper()}', ln=True)
pdf.ln(3)
# Draw KPI cards row (Success Rate, Dedup, Active Incidents)
# Card 1: Success Rate
pdf.set_fill_color(30, 38, 64) # Slate Blue
pdf.rect(10, 52, 60, 22, 'F')
pdf.set_xy(12, 54)
pdf.set_font('Helvetica', 'B', 8)
pdf.set_text_color(148, 163, 184)
pdf.cell(56, 4, 'GLOBAL SUCCESS RATE', ln=True)
pdf.set_font('Helvetica', 'B', 14)
pdf.set_text_color(0, 200, 83) # Success Green
pdf.cell(56, 8, f'{success_rate:.2f}%', ln=True)
# Card 2: Dedup Ratio
pdf.set_fill_color(30, 38, 64)
pdf.rect(75, 52, 60, 22, 'F')
pdf.set_xy(77, 54)
pdf.set_font('Helvetica', 'B', 8)
pdf.set_text_color(148, 163, 184)
pdf.cell(56, 4, 'DEDUPLICATION RATIO', ln=True)
pdf.set_font('Helvetica', 'B', 14)
pdf.set_text_color(0, 210, 255) # Cyan Accent
pdf.cell(56, 8, f'{dedup_ratio:.2f}:1', ln=True)
# Card 3: Active Failures
pdf.set_fill_color(30, 38, 64)
pdf.rect(140, 52, 60, 22, 'F')
pdf.set_xy(142, 54)
pdf.set_font('Helvetica', 'B', 8)
pdf.set_text_color(148, 163, 184)
pdf.cell(56, 4, 'ACTIVE INCIDENTS', ln=True)
pdf.set_font('Helvetica', 'B', 14)
pdf.set_text_color(255, 75, 75) # Error Red
pdf.cell(56, 8, f'{active_failures}', ln=True)
pdf.ln(18)
# Storage details section
pdf.set_xy(10, 80)
pdf.set_font('Helvetica', 'B', 10)
pdf.set_text_color(30, 41, 59)
pdf.cell(0, 6, 'Volume de Armazenamento MSDP (Deduplicacao):', ln=True)
pdf.set_font('Helvetica', '', 9)
pdf.cell(0, 5, f'- Volume Pre-Deduplicado: {total_pre_dedup/1024:.2f} GB ({total_pre_dedup:.1f} MB)', ln=True)
pdf.cell(0, 5, f'- Volume Post-Deduplicado Gravado: {total_post_dedup/1024:.2f} GB ({total_post_dedup:.1f} MB)', ln=True)
pdf.cell(0, 5, f'- Economia de Armazenamento Estimada: {space_saved:.1f}%', ln=True)
pdf.ln(6)
# Incident Action Log Section
pdf.set_font('Helvetica', 'B', 11)
pdf.cell(0, 8, 'Acoes de Mitigacao e Status de Falhas de Backup', ln=True)
pdf.ln(2)
# Filter failures
failures_list = [j for j in jobs_list if j['exit_code'] > 1]
if not failures_list:
pdf.set_font('Helvetica', 'I', 10)
pdf.set_text_color(0, 200, 83)
pdf.cell(0, 8, 'Nenhuma falha de backup registrada no escopo selecionado.', ln=True)
else:
# Table Header
pdf.set_fill_color(30, 38, 64)
pdf.set_font('Helvetica', 'B', 8)
pdf.set_text_color(255, 255, 255)
pdf.cell(15, 6, 'Job ID', 1, 0, 'C', True)
pdf.cell(32, 6, 'Cliente', 1, 0, 'L', True)
pdf.cell(35, 6, 'Politica', 1, 0, 'L', True)
pdf.cell(15, 6, 'Erro', 1, 0, 'C', True)
pdf.cell(28, 6, 'Resolucao Rerun', 1, 0, 'C', True)
pdf.cell(20, 6, 'Mitigacao', 1, 0, 'C', True)
pdf.cell(45, 6, 'Acao Registrada', 1, 1, 'L', True)
# Table Rows formatting
pdf.set_font('Helvetica', '', 7.5)
pdf.set_text_color(30, 41, 59)
fill_row = False
for j in failures_list:
jid = j['job_id']
client = j['client'] or 'N/A'
policy = j['policy'] or 'N/A'
code = j['exit_code']
reex = 'Reexecutado' if j['is_rerun_success'] == 1 else 'Pendente'
status = j.get('status', 'Pendente')
action = j.get('action_taken', '') or 'Nenhuma nota registrada.'
# String safety trims
if len(client) > 20: client = client[:18] + '..'
if len(policy) > 20: policy = policy[:18] + '..'
if len(action) > 30: action = action[:28] + '..'
# Row alternating colors
if fill_row:
pdf.set_fill_color(241, 245, 249)
else:
pdf.set_fill_color(255, 255, 255)
pdf.cell(15, 6, str(jid), 1, 0, 'C', True)
pdf.cell(32, 6, client, 1, 0, 'L', True)
pdf.cell(35, 6, policy, 1, 0, 'L', True)
pdf.cell(15, 6, str(code), 1, 0, 'C', True)
# Write rerun status with semantic colors
if reex == 'Reexecutado':
pdf.set_text_color(0, 150, 60) # Green text
else:
pdf.set_text_color(255, 75, 75) # Red text
pdf.cell(28, 6, reex, 1, 0, 'C', True)
# Set action status background color
pdf.set_text_color(30, 41, 59)
if status == 'Resolvido':
pdf.set_fill_color(200, 250, 210) # Soft green cell
elif status == 'Em Progresso':
pdf.set_fill_color(255, 235, 180) # Soft yellow cell
else:
pdf.set_fill_color(255, 210, 210) # Soft red cell
pdf.cell(20, 6, status, 1, 0, 'C', True)
# Action notes column
if fill_row:
pdf.set_fill_color(241, 245, 249)
else:
pdf.set_fill_color(255, 255, 255)
pdf.cell(45, 6, action, 1, 1, 'L', True)
fill_row = not fill_row
# Output pdf binary data
return bytes(pdf.output())