feat: initial architecture setup with Docker, SQLite persistence and PDF tracking
This commit is contained in:
+204
@@ -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())
|
||||
Reference in New Issue
Block a user