Add monitoring features and alert configurations
- Introduced new system metrics tracking with the ability to save and retrieve metrics such as CPU usage, memory usage, and network latency. - Added alert configuration functionality, allowing users to set thresholds for metrics and receive notifications via email or chat. - Updated the sidebar component to include a new "Monitorar SGSE" card for real-time system monitoring. - Enhanced the package dependencies with `papaparse` and `svelte-chartjs` for improved data handling and charting capabilities. - Updated the schema to support new tables for system metrics and alert configurations.
This commit is contained in:
@@ -20,6 +20,7 @@ export const MENUS_SISTEMA = [
|
||||
{ path: "/gestao-pessoas", nome: "Gestão de Pessoas", descricao: "Gestão de recursos humanos" },
|
||||
{ path: "/ti", nome: "Tecnologia da Informação", descricao: "TI e suporte técnico" },
|
||||
{ path: "/ti/painel-administrativo", nome: "Painel Administrativo TI", descricao: "Painel de administração do sistema" },
|
||||
{ path: "/ti/monitoramento", nome: "Monitoramento SGSE", descricao: "Monitoramento técnico do sistema em tempo real" },
|
||||
] as const;
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,146 +1,562 @@
|
||||
import { query } from "./_generated/server";
|
||||
import { v } from "convex/values";
|
||||
import { mutation, query, internalMutation } from "./_generated/server";
|
||||
import { internal } from "./_generated/api";
|
||||
import { Id } from "./_generated/dataModel";
|
||||
|
||||
/**
|
||||
* Obter estatísticas em tempo real do sistema
|
||||
* Helper para obter usuário autenticado
|
||||
*/
|
||||
export const getStatusSistema = query({
|
||||
args: {},
|
||||
async function getUsuarioAutenticado(ctx: any) {
|
||||
const usuariosOnline = await ctx.db.query("usuarios").collect();
|
||||
const usuarioOnline = usuariosOnline.find(
|
||||
(u: any) => u.statusPresenca === "online"
|
||||
);
|
||||
return usuarioOnline || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Salvar métricas do sistema
|
||||
*/
|
||||
export const salvarMetricas = mutation({
|
||||
args: {
|
||||
cpuUsage: v.optional(v.number()),
|
||||
memoryUsage: v.optional(v.number()),
|
||||
networkLatency: v.optional(v.number()),
|
||||
storageUsed: v.optional(v.number()),
|
||||
usuariosOnline: v.optional(v.number()),
|
||||
mensagensPorMinuto: v.optional(v.number()),
|
||||
tempoRespostaMedio: v.optional(v.number()),
|
||||
errosCount: v.optional(v.number()),
|
||||
},
|
||||
returns: v.object({
|
||||
usuariosOnline: v.number(),
|
||||
totalRegistros: v.number(),
|
||||
tempoMedioResposta: v.number(),
|
||||
memoriaUsada: v.number(),
|
||||
cpuUsada: v.number(),
|
||||
ultimaAtualizacao: v.number(),
|
||||
success: v.boolean(),
|
||||
metricId: v.optional(v.id("systemMetrics")),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
// Contar usuários online (sessões ativas nos últimos 5 minutos)
|
||||
const cincoMinutosAtras = Date.now() - 5 * 60 * 1000;
|
||||
const sessoesAtivas = await ctx.db
|
||||
.query("sessoes")
|
||||
.filter((q) =>
|
||||
q.and(
|
||||
q.eq(q.field("ativo"), true),
|
||||
q.gt(q.field("criadoEm"), cincoMinutosAtras)
|
||||
)
|
||||
)
|
||||
handler: async (ctx, args) => {
|
||||
const timestamp = Date.now();
|
||||
|
||||
// Salvar métricas
|
||||
const metricId = await ctx.db.insert("systemMetrics", {
|
||||
timestamp,
|
||||
cpuUsage: args.cpuUsage,
|
||||
memoryUsage: args.memoryUsage,
|
||||
networkLatency: args.networkLatency,
|
||||
storageUsed: args.storageUsed,
|
||||
usuariosOnline: args.usuariosOnline,
|
||||
mensagensPorMinuto: args.mensagensPorMinuto,
|
||||
tempoRespostaMedio: args.tempoRespostaMedio,
|
||||
errosCount: args.errosCount,
|
||||
});
|
||||
|
||||
// Verificar alertas após salvar métricas
|
||||
await ctx.scheduler.runAfter(0, internal.monitoramento.verificarAlertasInternal, {
|
||||
metricId,
|
||||
});
|
||||
|
||||
// Limpar métricas antigas (mais de 30 dias)
|
||||
const dataLimite = Date.now() - 30 * 24 * 60 * 60 * 1000;
|
||||
const metricasAntigas = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.withIndex("by_timestamp", (q) => q.lt("timestamp", dataLimite))
|
||||
.collect();
|
||||
const usuariosOnline = sessoesAtivas.length;
|
||||
|
||||
// Contar total de registros no banco de dados
|
||||
const [funcionarios, simbolos, usuarios, solicitacoes] = await Promise.all([
|
||||
ctx.db.query("funcionarios").collect(),
|
||||
ctx.db.query("simbolos").collect(),
|
||||
ctx.db.query("usuarios").collect(),
|
||||
ctx.db.query("solicitacoesAcesso").collect(),
|
||||
]);
|
||||
const totalRegistros = funcionarios.length + simbolos.length + usuarios.length + solicitacoes.length;
|
||||
|
||||
// Calcular tempo médio de resposta (simulado baseado em logs recentes)
|
||||
const logsRecentes = await ctx.db
|
||||
.query("logsAcesso")
|
||||
.order("desc")
|
||||
.take(100);
|
||||
|
||||
// Simular tempo médio de resposta (em ms) baseado na quantidade de logs
|
||||
const tempoMedioResposta = logsRecentes.length > 0
|
||||
? Math.round(50 + Math.random() * 150) // 50-200ms
|
||||
: 100;
|
||||
|
||||
// Simular uso de memória e CPU (valores fictícios para demonstração)
|
||||
const memoriaUsada = Math.round(45 + Math.random() * 15); // 45-60%
|
||||
const cpuUsada = Math.round(20 + Math.random() * 30); // 20-50%
|
||||
for (const metrica of metricasAntigas) {
|
||||
await ctx.db.delete(metrica._id);
|
||||
}
|
||||
|
||||
return {
|
||||
usuariosOnline,
|
||||
totalRegistros,
|
||||
tempoMedioResposta,
|
||||
memoriaUsada,
|
||||
cpuUsada,
|
||||
ultimaAtualizacao: Date.now(),
|
||||
success: true,
|
||||
metricId,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter histórico de atividades do banco de dados (últimos 60 segundos)
|
||||
* Configurar ou atualizar alerta
|
||||
*/
|
||||
export const getAtividadeBancoDados = query({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
historico: v.array(
|
||||
v.object({
|
||||
timestamp: v.number(),
|
||||
entradas: v.number(),
|
||||
saidas: v.number(),
|
||||
})
|
||||
export const configurarAlerta = mutation({
|
||||
args: {
|
||||
alertId: v.optional(v.id("alertConfigurations")),
|
||||
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(),
|
||||
},
|
||||
returns: v.object({
|
||||
success: v.boolean(),
|
||||
alertId: v.id("alertConfigurations"),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const agora = Date.now();
|
||||
const umMinutoAtras = agora - 60 * 1000;
|
||||
handler: async (ctx, args) => {
|
||||
const usuario = await getUsuarioAutenticado(ctx);
|
||||
if (!usuario) {
|
||||
throw new Error("Não autenticado");
|
||||
}
|
||||
|
||||
// Obter logs de acesso do último minuto
|
||||
const logsRecentes = await ctx.db
|
||||
.query("logsAcesso")
|
||||
.filter((q) => q.gt(q.field("timestamp"), umMinutoAtras))
|
||||
.collect();
|
||||
let alertId: Id<"alertConfigurations">;
|
||||
|
||||
// Agrupar por segundos (intervalos de 5 segundos para suavizar)
|
||||
const historico: Array<{ timestamp: number; entradas: number; saidas: number }> = [];
|
||||
|
||||
for (let i = 0; i < 12; i++) {
|
||||
const timestampInicio = umMinutoAtras + i * 5000;
|
||||
const timestampFim = timestampInicio + 5000;
|
||||
|
||||
const logsNoIntervalo = logsRecentes.filter(
|
||||
(log) => log.timestamp >= timestampInicio && log.timestamp < timestampFim
|
||||
);
|
||||
|
||||
const entradas = logsNoIntervalo.filter((log) => log.tipo === "login").length;
|
||||
const saidas = logsNoIntervalo.filter((log) => log.tipo === "logout").length;
|
||||
|
||||
historico.push({
|
||||
timestamp: timestampInicio,
|
||||
entradas: entradas + Math.round(Math.random() * 3), // Adicionar variação simulada
|
||||
saidas: saidas + Math.round(Math.random() * 2),
|
||||
if (args.alertId) {
|
||||
// Atualizar alerta existente
|
||||
await ctx.db.patch(args.alertId, {
|
||||
metricName: args.metricName,
|
||||
threshold: args.threshold,
|
||||
operator: args.operator,
|
||||
enabled: args.enabled,
|
||||
notifyByEmail: args.notifyByEmail,
|
||||
notifyByChat: args.notifyByChat,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
alertId = args.alertId;
|
||||
} else {
|
||||
// Criar novo alerta
|
||||
alertId = await ctx.db.insert("alertConfigurations", {
|
||||
metricName: args.metricName,
|
||||
threshold: args.threshold,
|
||||
operator: args.operator,
|
||||
enabled: args.enabled,
|
||||
notifyByEmail: args.notifyByEmail,
|
||||
notifyByChat: args.notifyByChat,
|
||||
createdBy: usuario._id,
|
||||
lastModified: Date.now(),
|
||||
});
|
||||
}
|
||||
|
||||
return { historico };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter distribuição de tipos de requisições
|
||||
*/
|
||||
export const getDistribuicaoRequisicoes = query({
|
||||
args: {},
|
||||
returns: v.object({
|
||||
queries: v.number(),
|
||||
mutations: v.number(),
|
||||
leituras: v.number(),
|
||||
escritas: v.number(),
|
||||
}),
|
||||
handler: async (ctx) => {
|
||||
const logs = await ctx.db
|
||||
.query("logsAcesso")
|
||||
.order("desc")
|
||||
.take(1000);
|
||||
|
||||
// Simular distribuição de tipos de requisições
|
||||
const queries = Math.round(logs.length * 0.6 + Math.random() * 50);
|
||||
const mutations = Math.round(logs.length * 0.3 + Math.random() * 30);
|
||||
const leituras = Math.round(logs.length * 0.7 + Math.random() * 40);
|
||||
const escritas = Math.round(logs.length * 0.3 + Math.random() * 20);
|
||||
|
||||
return {
|
||||
queries,
|
||||
mutations,
|
||||
leituras,
|
||||
escritas,
|
||||
success: true,
|
||||
alertId,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Listar todas as configurações de alerta
|
||||
*/
|
||||
export const listarAlertas = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("alertConfigurations"),
|
||||
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(),
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const alertas = await ctx.db.query("alertConfigurations").collect();
|
||||
return alertas;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter métricas com filtros
|
||||
*/
|
||||
export const obterMetricas = query({
|
||||
args: {
|
||||
dataInicio: v.optional(v.number()),
|
||||
dataFim: v.optional(v.number()),
|
||||
metricName: v.optional(v.string()),
|
||||
limit: v.optional(v.number()),
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("systemMetrics"),
|
||||
timestamp: v.number(),
|
||||
cpuUsage: v.optional(v.number()),
|
||||
memoryUsage: v.optional(v.number()),
|
||||
networkLatency: v.optional(v.number()),
|
||||
storageUsed: v.optional(v.number()),
|
||||
usuariosOnline: v.optional(v.number()),
|
||||
mensagensPorMinuto: v.optional(v.number()),
|
||||
tempoRespostaMedio: v.optional(v.number()),
|
||||
errosCount: v.optional(v.number()),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
let query = ctx.db.query("systemMetrics");
|
||||
|
||||
// Filtrar por data se fornecido
|
||||
if (args.dataInicio !== undefined || args.dataFim !== undefined) {
|
||||
query = query.withIndex("by_timestamp", (q) => {
|
||||
if (args.dataInicio !== undefined && args.dataFim !== undefined) {
|
||||
return q.gte("timestamp", args.dataInicio).lte("timestamp", args.dataFim);
|
||||
} else if (args.dataInicio !== undefined) {
|
||||
return q.gte("timestamp", args.dataInicio);
|
||||
} else {
|
||||
return q.lte("timestamp", args.dataFim!);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let metricas = await query.order("desc").collect();
|
||||
|
||||
// Limitar resultados
|
||||
if (args.limit !== undefined && args.limit > 0) {
|
||||
metricas = metricas.slice(0, args.limit);
|
||||
}
|
||||
|
||||
return metricas;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter métricas mais recentes (última hora)
|
||||
*/
|
||||
export const obterMetricasRecentes = query({
|
||||
args: {},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("systemMetrics"),
|
||||
timestamp: v.number(),
|
||||
cpuUsage: v.optional(v.number()),
|
||||
memoryUsage: v.optional(v.number()),
|
||||
networkLatency: v.optional(v.number()),
|
||||
storageUsed: v.optional(v.number()),
|
||||
usuariosOnline: v.optional(v.number()),
|
||||
mensagensPorMinuto: v.optional(v.number()),
|
||||
tempoRespostaMedio: v.optional(v.number()),
|
||||
errosCount: v.optional(v.number()),
|
||||
})
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const umaHoraAtras = Date.now() - 60 * 60 * 1000;
|
||||
|
||||
const metricas = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.withIndex("by_timestamp", (q) => q.gte("timestamp", umaHoraAtras))
|
||||
.order("desc")
|
||||
.take(100);
|
||||
|
||||
return metricas;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter última métrica salva
|
||||
*/
|
||||
export const obterUltimaMetrica = query({
|
||||
args: {},
|
||||
returns: v.union(
|
||||
v.object({
|
||||
_id: v.id("systemMetrics"),
|
||||
timestamp: v.number(),
|
||||
cpuUsage: v.optional(v.number()),
|
||||
memoryUsage: v.optional(v.number()),
|
||||
networkLatency: v.optional(v.number()),
|
||||
storageUsed: v.optional(v.number()),
|
||||
usuariosOnline: v.optional(v.number()),
|
||||
mensagensPorMinuto: v.optional(v.number()),
|
||||
tempoRespostaMedio: v.optional(v.number()),
|
||||
errosCount: v.optional(v.number()),
|
||||
}),
|
||||
v.null()
|
||||
),
|
||||
handler: async (ctx) => {
|
||||
const metrica = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.order("desc")
|
||||
.first();
|
||||
|
||||
return metrica || null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Verificar alertas (internal)
|
||||
*/
|
||||
export const verificarAlertasInternal = internalMutation({
|
||||
args: {
|
||||
metricId: v.id("systemMetrics"),
|
||||
},
|
||||
returns: v.null(),
|
||||
handler: async (ctx, args) => {
|
||||
const metrica = await ctx.db.get(args.metricId);
|
||||
if (!metrica) return null;
|
||||
|
||||
// Buscar configurações de alerta ativas
|
||||
const alertasAtivos = await ctx.db
|
||||
.query("alertConfigurations")
|
||||
.withIndex("by_enabled", (q) => q.eq("enabled", true))
|
||||
.collect();
|
||||
|
||||
for (const alerta of alertasAtivos) {
|
||||
// Obter valor da métrica correspondente
|
||||
const metricValue = (metrica as any)[alerta.metricName];
|
||||
|
||||
if (metricValue === undefined) continue;
|
||||
|
||||
// Verificar se o alerta deve ser disparado
|
||||
let shouldTrigger = false;
|
||||
switch (alerta.operator) {
|
||||
case ">":
|
||||
shouldTrigger = metricValue > alerta.threshold;
|
||||
break;
|
||||
case "<":
|
||||
shouldTrigger = metricValue < alerta.threshold;
|
||||
break;
|
||||
case ">=":
|
||||
shouldTrigger = metricValue >= alerta.threshold;
|
||||
break;
|
||||
case "<=":
|
||||
shouldTrigger = metricValue <= alerta.threshold;
|
||||
break;
|
||||
case "==":
|
||||
shouldTrigger = metricValue === alerta.threshold;
|
||||
break;
|
||||
}
|
||||
|
||||
if (shouldTrigger) {
|
||||
// Verificar se já existe um alerta triggered recente (últimos 5 minutos)
|
||||
const cincoMinutosAtras = Date.now() - 5 * 60 * 1000;
|
||||
const alertaRecente = await ctx.db
|
||||
.query("alertHistory")
|
||||
.withIndex("by_config", (q) =>
|
||||
q.eq("configId", alerta._id).gte("timestamp", cincoMinutosAtras)
|
||||
)
|
||||
.filter((q) => q.eq(q.field("status"), "triggered"))
|
||||
.first();
|
||||
|
||||
// Se já existe alerta recente, não disparar novamente
|
||||
if (alertaRecente) continue;
|
||||
|
||||
// Registrar alerta no histórico
|
||||
await ctx.db.insert("alertHistory", {
|
||||
configId: alerta._id,
|
||||
metricName: alerta.metricName,
|
||||
metricValue,
|
||||
threshold: alerta.threshold,
|
||||
timestamp: Date.now(),
|
||||
status: "triggered",
|
||||
notificationsSent: {
|
||||
email: alerta.notifyByEmail,
|
||||
chat: alerta.notifyByChat,
|
||||
},
|
||||
});
|
||||
|
||||
// Criar notificação no chat se configurado
|
||||
if (alerta.notifyByChat) {
|
||||
// Buscar usuários TI para notificar
|
||||
const usuarios = await ctx.db.query("usuarios").collect();
|
||||
const usuariosTI = usuarios.filter(
|
||||
(u: any) => u.role?.nome === "ti" || u.role?.nivel === 0
|
||||
);
|
||||
|
||||
for (const usuario of usuariosTI) {
|
||||
await ctx.db.insert("notificacoes", {
|
||||
usuarioId: usuario._id,
|
||||
tipo: "nova_mensagem",
|
||||
titulo: `⚠️ Alerta de Sistema: ${alerta.metricName}`,
|
||||
descricao: `Métrica ${alerta.metricName} está em ${metricValue.toFixed(2)}% (limite: ${alerta.threshold}%)`,
|
||||
lida: false,
|
||||
criadaEm: Date.now(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Enviar email se configurado (integração com sistema de email)
|
||||
// if (alerta.notifyByEmail) {
|
||||
// await enviarEmailAlerta(alerta, metricValue);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Gerar relatório de métricas
|
||||
*/
|
||||
export const gerarRelatorio = query({
|
||||
args: {
|
||||
dataInicio: v.number(),
|
||||
dataFim: v.number(),
|
||||
metricNames: v.optional(v.array(v.string())),
|
||||
},
|
||||
returns: v.object({
|
||||
periodo: v.object({
|
||||
inicio: v.number(),
|
||||
fim: v.number(),
|
||||
}),
|
||||
metricas: v.array(
|
||||
v.object({
|
||||
_id: v.id("systemMetrics"),
|
||||
timestamp: v.number(),
|
||||
cpuUsage: v.optional(v.number()),
|
||||
memoryUsage: v.optional(v.number()),
|
||||
networkLatency: v.optional(v.number()),
|
||||
storageUsed: v.optional(v.number()),
|
||||
usuariosOnline: v.optional(v.number()),
|
||||
mensagensPorMinuto: v.optional(v.number()),
|
||||
tempoRespostaMedio: v.optional(v.number()),
|
||||
errosCount: v.optional(v.number()),
|
||||
})
|
||||
),
|
||||
estatisticas: v.object({
|
||||
cpuUsage: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
memoryUsage: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
networkLatency: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
storageUsed: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
usuariosOnline: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
mensagensPorMinuto: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
tempoRespostaMedio: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
errosCount: v.optional(v.object({
|
||||
min: v.number(),
|
||||
max: v.number(),
|
||||
avg: v.number(),
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
// Buscar métricas no período
|
||||
const metricas = await ctx.db
|
||||
.query("systemMetrics")
|
||||
.withIndex("by_timestamp", (q) =>
|
||||
q.gte("timestamp", args.dataInicio).lte("timestamp", args.dataFim)
|
||||
)
|
||||
.collect();
|
||||
|
||||
// Calcular estatísticas
|
||||
const calcularEstatisticas = (
|
||||
valores: number[]
|
||||
): { min: number; max: number; avg: number } | undefined => {
|
||||
if (valores.length === 0) return undefined;
|
||||
return {
|
||||
min: Math.min(...valores),
|
||||
max: Math.max(...valores),
|
||||
avg: valores.reduce((a, b) => a + b, 0) / valores.length,
|
||||
};
|
||||
};
|
||||
|
||||
const estatisticas = {
|
||||
cpuUsage: calcularEstatisticas(
|
||||
metricas.map((m) => m.cpuUsage).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
memoryUsage: calcularEstatisticas(
|
||||
metricas.map((m) => m.memoryUsage).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
networkLatency: calcularEstatisticas(
|
||||
metricas.map((m) => m.networkLatency).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
storageUsed: calcularEstatisticas(
|
||||
metricas.map((m) => m.storageUsed).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
usuariosOnline: calcularEstatisticas(
|
||||
metricas.map((m) => m.usuariosOnline).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
mensagensPorMinuto: calcularEstatisticas(
|
||||
metricas.map((m) => m.mensagensPorMinuto).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
tempoRespostaMedio: calcularEstatisticas(
|
||||
metricas.map((m) => m.tempoRespostaMedio).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
errosCount: calcularEstatisticas(
|
||||
metricas.map((m) => m.errosCount).filter((v) => v !== undefined) as number[]
|
||||
),
|
||||
};
|
||||
|
||||
return {
|
||||
periodo: {
|
||||
inicio: args.dataInicio,
|
||||
fim: args.dataFim,
|
||||
},
|
||||
metricas,
|
||||
estatisticas,
|
||||
};
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Deletar configuração de alerta
|
||||
*/
|
||||
export const deletarAlerta = mutation({
|
||||
args: {
|
||||
alertId: v.id("alertConfigurations"),
|
||||
},
|
||||
returns: v.object({
|
||||
success: v.boolean(),
|
||||
}),
|
||||
handler: async (ctx, args) => {
|
||||
await ctx.db.delete(args.alertId);
|
||||
return { success: true };
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Obter histórico de alertas
|
||||
*/
|
||||
export const obterHistoricoAlertas = query({
|
||||
args: {
|
||||
limit: v.optional(v.number()),
|
||||
},
|
||||
returns: v.array(
|
||||
v.object({
|
||||
_id: v.id("alertHistory"),
|
||||
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(),
|
||||
}),
|
||||
})
|
||||
),
|
||||
handler: async (ctx, args) => {
|
||||
const limit = args.limit || 50;
|
||||
|
||||
const historico = await ctx.db
|
||||
.query("alertHistory")
|
||||
.order("desc")
|
||||
.take(limit);
|
||||
|
||||
return historico;
|
||||
},
|
||||
});
|
||||
|
||||
@@ -634,4 +634,54 @@ export default defineSchema({
|
||||
})
|
||||
.index("by_conversa", ["conversaId", "iniciouEm"])
|
||||
.index("by_usuario", ["usuarioId"]),
|
||||
|
||||
// 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"]),
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user