diff --git a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte index 6d20698..a60e04a 100644 --- a/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte +++ b/apps/web/src/lib/components/ti/CybersecurityWizcard.svelte @@ -25,40 +25,45 @@ const alertConfigs = useQuery(api.security.listarAlertConfigs, { limit: 100 }); // Estado local para preferências de alertas + let alertNomeConfig = $state(''); let alertEmails = $state(''); - let alertSeveridadeMin: SeveridadeSeguranca = 'alto'; - let alertTiposAtaque: string[] = []; + let alertSeveridadeMin = $state('alto'); + let alertTiposAtaque = $state([]); let alertReenvioMin = $state(15); let alertTemplate = $state('incidente_critico'); let alertUsersChat = $state(''); - let chatSeveridadeMin: SeveridadeSeguranca = 'alto'; - let chatTiposAtaque: string[] = []; + let chatSeveridadeMin = $state('alto'); + let chatTiposAtaque = $state([]); let chatReenvioMin = $state(10); let enviarPorEmail = $state(true); let enviarPorChat = $state(false); - let editarAlertConfigId: Id<'alertConfigs'> | null = null; + let editarAlertConfigId = $state | null>(null); - // Sugestões a partir de configs salvas - const sugestoesEmails = $derived.by(() => { - const set = new Set(); - for (const cfg of alertConfigs?.data ?? []) { - for (const e of cfg.emails ?? []) set.add(e); - } - for (const u of usuariosQuery?.data ?? []) { - if (u?.email) set.add(u.email); - } - return Array.from(set).slice(0, 16); - }); - const sugestoesChatUsers = $derived.by(() => { - const set = new Set(); - for (const cfg of alertConfigs?.data ?? []) { - for (const u of cfg.chatUsers ?? []) set.add(u); - } - for (const u of usuariosQuery?.data ?? []) { - if (u?.username) set.add(u.username); - } - return Array.from(set).slice(0, 16); - }); + // Sugestões a partir de configs salvas (podem ser usadas no futuro para autocomplete) + // const sugestoesEmails = $derived.by(() => { + // const emails: string[] = []; + // for (const cfg of alertConfigs?.data ?? []) { + // for (const e of cfg.emails ?? []) { + // if (!emails.includes(e)) emails.push(e); + // } + // } + // for (const u of usuariosQuery?.data ?? []) { + // if (u?.email && !emails.includes(u.email)) emails.push(u.email); + // } + // return emails.slice(0, 16); + // }); + // const sugestoesChatUsers = $derived.by(() => { + // const emails: string[] = []; + // for (const cfg of alertConfigs?.data ?? []) { + // for (const u of cfg.chatUsers ?? []) { + // if (!emails.includes(u)) emails.push(u); + // } + // } + // for (const u of usuariosQuery?.data ?? []) { + // if (u?.email && !emails.includes(u.email)) emails.push(u.email); + // } + // return emails.slice(0, 16); + // }); function adicionarEmailSugestao(email: string) { const linhas = alertEmails .split('\n') @@ -86,12 +91,31 @@ .map((s) => s.trim()) .filter((s) => s.length > 0); } + function limparFormularioAlertas() { + alertNomeConfig = ''; + alertEmails = ''; + alertSeveridadeMin = 'alto'; + alertTiposAtaque = []; + alertReenvioMin = 15; + alertTemplate = 'incidente_critico'; + alertUsersChat = ''; + chatSeveridadeMin = 'alto'; + chatTiposAtaque = []; + chatReenvioMin = 10; + enviarPorEmail = true; + enviarPorChat = false; + editarAlertConfigId = null; + } async function salvarPreferenciasAlertas(configId?: string) { try { + if (!alertNomeConfig.trim()) { + feedback = { tipo: 'error', mensagem: 'Por favor, informe um nome para a configuração.' }; + return; + } const canais = { email: enviarPorEmail, chat: enviarPorChat }; const resp = await client.mutation(api.security.salvarAlertConfig, { configId: configId as Id<'alertConfigs'> | undefined, - nome: alertTemplate || 'Notificação', + nome: alertNomeConfig.trim(), canais, emails: parseLinhasParaArray(alertEmails), chatUsers: parseLinhasParaArray(alertUsersChat), @@ -100,8 +124,12 @@ reenvioMin: alertReenvioMin, criadoPor: obterUsuarioId() }); - feedback = { tipo: 'success', mensagem: 'Preferências salvas.' }; + feedback = { + tipo: 'success', + mensagem: editarAlertConfigId ? 'Configuração atualizada.' : 'Nova configuração salva.' + }; editarAlertConfigId = resp._id as Id<'alertConfigs'>; + // Não limpar o formulário após salvar, permitindo edição contínua return resp._id; } catch (erro: unknown) { feedback = { tipo: 'error', mensagem: mensagemErro(erro) }; @@ -125,7 +153,8 @@ tiposAtaque?: AtaqueCiberneticoTipo[]; reenvioMin: number; }) { - alertTemplate = config.nome ?? 'incidente_critico'; + alertNomeConfig = config.nome ?? ''; + alertTemplate = 'incidente_critico'; // Mantém o template padrão enviarPorEmail = config.canais?.email ?? true; enviarPorChat = config.canais?.chat ?? false; alertEmails = (config.emails ?? []).join('\n'); @@ -133,7 +162,15 @@ alertSeveridadeMin = config.severidadeMin ?? 'alto'; alertTiposAtaque = (config.tiposAtaque ?? []) as string[]; alertReenvioMin = config.reenvioMin ?? 15; + chatSeveridadeMin = config.severidadeMin ?? 'alto'; + chatTiposAtaque = (config.tiposAtaque ?? []) as string[]; + chatReenvioMin = config.reenvioMin ?? 10; editarAlertConfigId = config._id; + // Scroll suave até o formulário + setTimeout(() => { + const formSection = document.querySelector('[data-alert-form]'); + formSection?.scrollIntoView({ behavior: 'smooth', block: 'start' }); + }, 100); } const severidadesDisponiveis: SeveridadeSeguranca[] = [ 'informativo', @@ -323,9 +360,9 @@ } return buckets; }); - const maxBucketTotal = $derived.by(() => - Math.max(1, ...timelineBuckets.map((b) => Object.values(b.counts).reduce((a, n) => a + n, 0))) - ); + // const maxBucketTotal = $derived.by(() => + // Math.max(1, ...timelineBuckets.map((b) => Object.values(b.counts).reduce((a, n) => a + n, 0))) + // ); function maxSeriesValue(dataset: Array>): number { let max = 1; @@ -1150,10 +1187,10 @@

Realtime por tipo (60 min)

- {#each timelineBuckets as b} + {#each timelineBuckets as b (b.inicio)}
- {#each tiposParaChart as t} + {#each tiposParaChart as t (t)} {#if b.counts[t] > 0}
- {#each tiposParaChart as t} + {#each tiposParaChart as t (t)} {t.replace('_', ' ')} {/each}
@@ -1200,15 +1237,13 @@ {#each [...new Map(timelineBuckets .map((b) => Object.entries(b.topDestinos)) .flat() - .reduce((acc, [k, v]) => acc.set(k, (acc.get(k) ?? 0) + (v as number)), new Map()))].slice(0, 8) as item} - {#key item[0]} - {@const partes = item[0].split('|')} - - {partes[0]} - {partes[1]} - {item[1] as number} - - {/key} + .reduce((acc, [k, v]) => acc.set(k, (acc.get(k) ?? 0) + (v as number)), new Map()))].slice(0, 8) as item (item[0])} + {@const partes = item[0].split('|')} + + {partes[0]} + {partes[1]} + {item[1] as number} + {/each} @@ -1387,7 +1422,6 @@ mensagem: `🧹 ${resultado.removidos} eventos de teste removidos` }; novosEventos = 0; - ultimoTotalEventos = (eventos?.data ?? []).length - (resultado.removidos ?? 0); } catch (error) { feedback = { tipo: 'error', @@ -1972,15 +2006,59 @@ -
+

Alertas e Notificações

- Configure destinatários, níveis e tipos de alarme e reenvio para monitoramento de - segurança. + Configure múltiplas configurações de alertas. Cada configuração pode ter diferentes + destinatários, níveis e tipos de alarme.

+ +
+ + +
+
+ + +
@@ -2299,9 +2377,7 @@ }} > {#each usuariosParaChat?.data ?? [] as u (u._id)} - + {/each}
@@ -2461,8 +2537,17 @@
- -
+ +
+ {#if editarAlertConfigId} + + {/if}