diff --git a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte index 313e836..feca7b3 100644 --- a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte +++ b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte @@ -504,6 +504,87 @@ const data = new Date(valor); return Number.isNaN(data.getTime()) ? Date.now() : data.getTime(); } + // Imprimir conteúdo do relatório (usa campo observações JSON estruturado) + function imprimirRelatorio(r: { + _id: Id<'reportRequests'>; + status: 'pendente' | 'processando' | 'concluido' | 'falhou'; + criadoEm: number; + concluidoEm?: number; + observacoes?: string; + }) { + if (typeof window === 'undefined') return; + const win = window.open('', '_blank', 'noopener,noreferrer,width=900,height=700'); + if (!win) return; + let conteudo: string; + try { + const data = r.observacoes ? JSON.parse(r.observacoes) as any : null; + const total = data?.total ?? '—'; + const porSeveridade = data?.porSeveridade ?? {}; + const porAtaque = data?.porAtaque ?? {}; + const linhasSev = Object.entries(porSeveridade) + .map(([k, v]) => `${k}${v as number}`) + .join(''); + const linhasAtk = Object.entries(porAtaque) + .map(([k, v]) => `${k}${v as number}`) + .join(''); + const criadoStr = new Date(r.criadoEm).toLocaleString('pt-BR', { hour12: false }); + const concluidoStr = r.concluidoEm ? new Date(r.concluidoEm).toLocaleString('pt-BR', { hour12: false }) : '—'; + const agoraStr = new Date().toLocaleString('pt-BR', { hour12: false }); + conteudo = + '' + + 'Relatório de Segurança' + + '' + + '
' + + '

Relatório de Segurança

' + + '
Status: ' + r.status + ' · Criado: ' + criadoStr + ' · Concluído: ' + concluidoStr + '
' + + '

Resumo

' + + '

Total de eventos no período: ' + total + '

' + + '

Por Severidade

' + + (linhasSev || '') + + '
SeveridadeQtde

Por Tipo de Ataque

' + + (linhasAtk || '') + + '
TipoQtde
' + + '' + + ''; + } catch { + const obs = (r.observacoes ?? '').replace(/'; + const scriptClose = ''; + conteudo = + '' + + 'Relatório' + + '
' + obs + '
' + + scriptOpen + 'window.print()' + scriptClose; + } + win.document.open(); + win.document.write(conteudo); + win.document.close(); + } + async function excluirRelatorio(relatorioId: Id<'reportRequests'>) { + if (!confirm('Excluir este relatório? Esta ação não pode ser desfeita.')) return; + try { + const resp = await client.mutation(api.security.deletarRelatorio, { relatorioId }); + if (resp?.success) { + feedback = { tipo: 'success', mensagem: 'Relatório excluído.' }; + } else { + feedback = { tipo: 'error', mensagem: 'Não foi possível excluir o relatório.' }; + } + } catch (erro: unknown) { + feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; + } + } async function gerarRelatorioAvancado(e: SubmitEvent) { e.preventDefault(); @@ -1396,6 +1477,7 @@ Criado Concluído Observações + Ações @@ -1407,6 +1489,26 @@ {new Date(r.criadoEm).toLocaleString('pt-BR', { hour12: false })} {r.concluidoEm ? new Date(r.concluidoEm).toLocaleString('pt-BR', { hour12: false }) : '-'} {r.observacoes ?? '-'} + +
+ + +
+ {/each} diff --git a/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte index 158c064..a4b735a 100644 --- a/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte +++ b/apps/web/src/routes/(dashboard)/ti/cibersecurity/+page.svelte @@ -1,6 +1,13 @@ @@ -23,7 +30,13 @@ Voltar para TI - + {#if browser && Comp} + + {:else} +
+ Carregando módulo de cibersegurança… +
+ {/if} diff --git a/packages/backend/convex/security.ts b/packages/backend/convex/security.ts index 26d1852..9db7bdb 100644 --- a/packages/backend/convex/security.ts +++ b/packages/backend/convex/security.ts @@ -1301,6 +1301,31 @@ export const healthStatus = query({ }; } }); +/** + * Excluir relatório gerado (e artefato, se houver) + */ +export const deletarRelatorio = mutation({ + args: { + relatorioId: v.id('reportRequests') + }, + returns: v.object({ success: v.boolean() }), + handler: async (ctx, args) => { + const doc = await ctx.db.get(args.relatorioId); + if (!doc) { + return { success: false }; + } + // Remover arquivo em storage se existir + if (doc.resultadoId) { + try { + await ctx.storage.delete(doc.resultadoId); + } catch { + // Ignorar falha ao excluir artefato de storage + } + } + await ctx.db.delete(args.relatorioId); + return { success: true }; + } +}); export const processarRelatorioSegurancaInternal = internalMutation({ args: { relatorioId: v.id('reportRequests')