Files
sgse-app/packages/backend/convex/tables/system.ts

247 lines
8.7 KiB
TypeScript

import { defineTable } from 'convex/server';
import { v } from 'convex/values';
import { ataqueCiberneticoTipo, severidadeSeguranca } from './security';
export const systemTables = {
// Logs de Atividades do Sistema
logsAtividades: defineTable({
usuarioId: v.id('usuarios'),
acao: v.string(),
recurso: v.string(),
recursoId: v.optional(v.string()),
detalhes: v.optional(v.string()),
timestamp: v.number()
})
.index('by_usuario', ['usuarioId'])
.index('by_acao', ['acao'])
.index('by_recurso', ['recurso'])
.index('by_timestamp', ['timestamp'])
.index('by_recurso_id', ['recurso', 'recursoId']),
// Configuração de Email/SMTP
configuracaoEmail: defineTable({
servidor: v.string(), // smtp.gmail.com
porta: v.number(), // 587, 465, etc.
usuario: v.string(),
senhaHash: v.string(), // senha criptografada reversível (AES-GCM) - necessário para descriptografar e usar no SMTP
emailRemetente: v.string(),
nomeRemetente: v.string(),
usarSSL: v.boolean(),
usarTLS: v.boolean(),
ativo: v.boolean(),
testadoEm: v.optional(v.number()),
configuradoPor: v.id('usuarios'),
atualizadoEm: v.number()
}).index('by_ativo', ['ativo']),
// Fila de Emails
notificacoesEmail: defineTable({
destinatario: v.string(), // email
destinatarioId: v.optional(v.id('usuarios')),
assunto: v.string(),
corpo: v.string(), // HTML ou texto
templateId: v.optional(v.id('templatesMensagens')),
status: v.union(
v.literal('pendente'),
v.literal('enviando'),
v.literal('enviado'),
v.literal('falha')
),
tentativas: v.number(),
ultimaTentativa: v.optional(v.number()),
erroDetalhes: v.optional(v.string()),
enviadoPor: v.id('usuarios'),
criadoEm: v.number(),
enviadoEm: v.optional(v.number()),
agendadaPara: v.optional(v.number()) // timestamp para agendamento
})
.index('by_status', ['status'])
.index('by_destinatario', ['destinatarioId'])
.index('by_enviado_por', ['enviadoPor'])
.index('by_criado_em', ['criadoEm'])
.index('by_agendamento', ['agendadaPara']),
// Rate Limiting de Emails
rateLimitEmails: defineTable({
remetenteId: v.id('usuarios'),
timestamp: v.number(),
contador: v.number(), // quantidade de emails enviados neste período
periodo: v.union(
v.literal('minuto'), // último minuto
v.literal('hora') // última hora
)
})
.index('by_remetente_periodo', ['remetenteId', 'periodo', 'timestamp'])
.index('by_timestamp', ['timestamp']),
// Tabelas de Monitoramento do Sistema
systemMetrics: defineTable({
timestamp: v.number(),
// Métricas de Sistema
cpuUsage: v.optional(v.number()),
memoryUsage: v.optional(v.number()),
networkLatency: v.optional(v.number()),
storageUsed: v.optional(v.number()),
// Métricas de Aplicação
usuariosOnline: v.optional(v.number()),
mensagensPorMinuto: v.optional(v.number()),
tempoRespostaMedio: v.optional(v.number()),
errosCount: v.optional(v.number())
}).index('by_timestamp', ['timestamp']),
alertConfigurations: defineTable({
metricName: v.string(),
threshold: v.number(),
operator: v.union(
v.literal('>'),
v.literal('<'),
v.literal('>='),
v.literal('<='),
v.literal('==')
),
enabled: v.boolean(),
notifyByEmail: v.boolean(),
notifyByChat: v.boolean(),
createdBy: v.id('usuarios'),
lastModified: v.number()
}).index('by_enabled', ['enabled']),
alertHistory: defineTable({
configId: v.id('alertConfigurations'),
metricName: v.string(),
metricValue: v.number(),
threshold: v.number(),
timestamp: v.number(),
status: v.union(v.literal('triggered'), v.literal('resolved')),
notificationsSent: v.object({
email: v.boolean(),
chat: v.boolean()
})
})
.index('by_timestamp', ['timestamp'])
.index('by_status', ['status'])
.index('by_config', ['configId', 'timestamp']),
rateLimitConfig: defineTable({
nome: v.string(),
tipo: v.union(
v.literal('ip'),
v.literal('usuario'),
v.literal('endpoint'),
v.literal('global')
),
identificador: v.optional(v.string()),
limite: v.number(),
janelaSegundos: v.number(),
estrategia: v.union(
v.literal('fixed_window'),
v.literal('sliding_window'),
v.literal('token_bucket')
),
acaoExcedido: v.union(v.literal('bloquear'), v.literal('throttle'), v.literal('alertar')),
bloqueioTemporarioSegundos: v.optional(v.number()),
ativo: v.boolean(),
prioridade: v.number(),
criadoPor: v.id('usuarios'),
atualizadoPor: v.optional(v.id('usuarios')),
criadoEm: v.number(),
atualizadoEm: v.number(),
notas: v.optional(v.string()),
tags: v.optional(v.array(v.string()))
})
.index('by_tipo_identificador', ['tipo', 'identificador'])
.index('by_ativo', ['ativo'])
.index('by_prioridade', ['prioridade']),
alertConfigs: defineTable({
nome: v.string(),
canais: v.object({
email: v.boolean(),
chat: v.boolean()
}),
emails: v.array(v.string()),
chatUsers: v.array(v.string()),
severidadeMin: severidadeSeguranca,
tiposAtaque: v.optional(v.array(ataqueCiberneticoTipo)),
reenvioMin: v.number(),
templateCodigo: v.optional(v.string()), // Template a ser usado para email/chat
criadoPor: v.id('usuarios'),
criadoEm: v.number(),
atualizadoEm: v.number()
}).index('by_criadoEm', ['criadoEm']),
// Configurações Gerais
config: defineTable({
comprasSetorId: v.optional(v.id('setores')),
financeiroSetorId: v.optional(v.id('setores')),
juridicoSetorId: v.optional(v.id('setores')),
convenioSetorId: v.optional(v.id('setores')),
programasEsportivosSetorId: v.optional(v.id('setores')),
comunicacaoSetorId: v.optional(v.id('setores')),
tiSetorId: v.optional(v.id('setores')),
criadoPor: v.id('usuarios'),
atualizadoEm: v.number()
}),
// Templates de Mensagens
templatesMensagens: defineTable({
codigo: v.string(), // "USUARIO_BLOQUEADO", "SENHA_RESETADA", etc.
nome: v.string(),
tipo: v.union(
v.literal('sistema'), // predefinido, não editável
v.literal('customizado') // criado por TI_MASTER
),
titulo: v.string(),
corpo: v.string(), // pode ter variáveis {{variavel}}
htmlCorpo: v.optional(v.string()), // versão HTML do corpo (com wrapper)
variaveis: v.optional(v.array(v.string())), // ["motivo", "senha", etc.]
categoria: v.optional(v.union(v.literal('email'), v.literal('chat'), v.literal('ambos'))), // categoria do template
tags: v.optional(v.array(v.string())), // tags para organização
criadoPor: v.optional(v.id('usuarios')),
criadoEm: v.number()
})
.index('by_codigo', ['codigo'])
.index('by_tipo', ['tipo'])
.index('by_criado_por', ['criadoPor'])
.index('by_categoria', ['categoria']),
// Configuração de Jitsi Meet
configuracaoJitsi: defineTable({
domain: v.string(), // Domínio do servidor Jitsi (ex: "localhost:8443" ou "meet.example.com")
appId: v.string(), // ID da aplicação Jitsi
roomPrefix: v.string(), // Prefixo para nomes de salas
useHttps: v.boolean(), // Usar HTTPS
acceptSelfSignedCert: v.optional(v.boolean()), // Aceitar certificados autoassinados (útil para desenvolvimento)
ativo: v.boolean(), // Configuração ativa
testadoEm: v.optional(v.number()), // Timestamp do último teste de conexão
configuradoEm: v.optional(v.number()), // Timestamp da última configuração do servidor Docker
configuradoNoServidor: v.optional(v.boolean()), // Indica se a configuração foi aplicada no servidor
configuradoNoServidorEm: v.optional(v.number()), // Timestamp de quando foi configurado no servidor
configuradoPor: v.id('usuarios'), // Usuário que configurou
atualizadoEm: v.number(), // Timestamp de atualização
jitsiConfigPath: v.optional(v.string()), // Caminho da configuração do Jitsi no servidor (ex: "~/.jitsi-meet-cfg")
sshUsername: v.optional(v.string()), // Usuário SSH para acesso ao servidor
sshPasswordHash: v.optional(v.string()), // Hash da senha SSH (criptografada)
sshPort: v.optional(v.number()) // Porta SSH (padrão: 22)
}).index('by_ativo', ['ativo']),
// Logs de Erros do Servidor (500, etc)
errosServidor: defineTable({
statusCode: v.number(), // Código HTTP do erro (500, 502, etc)
mensagem: v.string(), // Mensagem do erro
stack: v.optional(v.string()), // Stack trace do erro
url: v.optional(v.string()), // URL onde ocorreu o erro
method: v.optional(v.string()), // Método HTTP (GET, POST, etc)
ipAddress: v.optional(v.string()), // IP do cliente
userAgent: v.optional(v.string()), // User agent do navegador
usuarioId: v.optional(v.id('usuarios')), // Usuário autenticado (se houver)
notificado: v.boolean(), // Se a equipe técnica já foi notificada
notificadoEm: v.optional(v.number()), // Timestamp da notificação
criadoEm: v.number() // Timestamp do erro
})
.index('by_status_code', ['statusCode'])
.index('by_notificado', ['notificado'])
.index('by_criado_em', ['criadoEm'])
.index('by_usuario', ['usuarioId'])
};