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
| Severidade | Qtde |
' +
+ (linhasSev || '| — |
') +
+ '
Por Tipo de Ataque
| Tipo | Qtde |
' +
+ (linhasAtk || '| — |
') +
+ '
' +
+ '' +
+ '';
+ } 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')