- 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.
1074 lines
45 KiB
Svelte
1074 lines
45 KiB
Svelte
<script lang="ts">
|
|
import { onMount, onDestroy } from "svelte";
|
|
import { useConvexClient } from "convex-svelte";
|
|
import { api } from "@sgse-app/backend/convex/_generated/api";
|
|
import AlertConfigModal from "./AlertConfigModal.svelte";
|
|
import ReportGeneratorModal from "./ReportGeneratorModal.svelte";
|
|
import LineChart from "./charts/LineChart.svelte";
|
|
import AreaChart from "./charts/AreaChart.svelte";
|
|
import DoughnutChart from "./charts/DoughnutChart.svelte";
|
|
import BarChart from "./charts/BarChart.svelte";
|
|
|
|
const client = useConvexClient();
|
|
|
|
let showAlertModal = $state(false);
|
|
let showReportModal = $state(false);
|
|
let intervalId: ReturnType<typeof setInterval> | null = null;
|
|
|
|
// Estado local das métricas
|
|
let metrics = $state<any>({
|
|
timestamp: Date.now(),
|
|
cpuUsage: 0,
|
|
memoryUsage: 0,
|
|
networkLatency: 0,
|
|
storageUsed: 0,
|
|
usuariosOnline: 0,
|
|
mensagensPorMinuto: 0,
|
|
tempoRespostaMedio: 0,
|
|
errosCount: 0,
|
|
// Métricas de performance
|
|
fps: 60,
|
|
networkType: "4g",
|
|
networkSpeed: 0,
|
|
browserName: "",
|
|
browserVersion: "",
|
|
screenResolution: "",
|
|
deviceMemory: 0,
|
|
hardwareConcurrency: 0,
|
|
cacheSize: 0,
|
|
batteryLevel: 100,
|
|
batteryCharging: false,
|
|
uptime: 0,
|
|
// Novas métricas avançadas
|
|
httpRequests: 0,
|
|
wsConnections: 0,
|
|
wsStatus: "disconnected",
|
|
indexedDBSize: 0,
|
|
serviceWorkerStatus: "none",
|
|
domNodes: 0,
|
|
jsHeapSize: 0,
|
|
});
|
|
|
|
// Histórico local (últimas 100 métricas)
|
|
let metricsHistory: any[] = $state([]);
|
|
|
|
// Dados para gráficos
|
|
let lineChartData = $derived({
|
|
labels: metricsHistory.map((m, i) => {
|
|
const date = new Date(m.timestamp);
|
|
return date.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
}).slice(-20), // Últimos 20 pontos
|
|
datasets: [
|
|
{
|
|
label: 'CPU (%)',
|
|
data: metricsHistory.map(m => m.cpuUsage).slice(-20),
|
|
borderColor: 'rgb(255, 99, 132)',
|
|
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
|
tension: 0.4,
|
|
fill: true,
|
|
},
|
|
{
|
|
label: 'RAM (%)',
|
|
data: metricsHistory.map(m => m.memoryUsage).slice(-20),
|
|
borderColor: 'rgb(54, 162, 235)',
|
|
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
|
tension: 0.4,
|
|
fill: true,
|
|
},
|
|
{
|
|
label: 'Latência (ms)',
|
|
data: metricsHistory.map(m => m.networkLatency).slice(-20),
|
|
borderColor: 'rgb(255, 206, 86)',
|
|
backgroundColor: 'rgba(255, 206, 86, 0.1)',
|
|
tension: 0.4,
|
|
fill: true,
|
|
}
|
|
]
|
|
});
|
|
|
|
let areaChartData = $derived({
|
|
labels: metricsHistory.map((m, i) => {
|
|
const date = new Date(m.timestamp);
|
|
return date.toLocaleTimeString('pt-BR', { hour: '2-digit', minute: '2-digit', second: '2-digit' });
|
|
}).slice(-30),
|
|
datasets: [
|
|
{
|
|
label: 'Erros',
|
|
data: metricsHistory.map(m => m.errosCount).slice(-30),
|
|
borderColor: 'rgb(239, 68, 68)',
|
|
backgroundColor: 'rgba(239, 68, 68, 0.3)',
|
|
fill: true,
|
|
},
|
|
{
|
|
label: 'Requisições HTTP',
|
|
data: metricsHistory.map(m => m.httpRequests || 0).slice(-30),
|
|
borderColor: 'rgb(59, 130, 246)',
|
|
backgroundColor: 'rgba(59, 130, 246, 0.3)',
|
|
fill: true,
|
|
},
|
|
{
|
|
label: 'Usuários Online',
|
|
data: metricsHistory.map(m => m.usuariosOnline).slice(-30),
|
|
borderColor: 'rgb(34, 197, 94)',
|
|
backgroundColor: 'rgba(34, 197, 94, 0.3)',
|
|
fill: true,
|
|
}
|
|
]
|
|
});
|
|
|
|
let doughnutChartData = $derived({
|
|
labels: ['CPU', 'RAM', 'Storage', 'Disponível'],
|
|
datasets: [{
|
|
data: [
|
|
metrics.cpuUsage,
|
|
metrics.memoryUsage,
|
|
metrics.storageUsed,
|
|
Math.max(0, 100 - (metrics.cpuUsage + metrics.memoryUsage + metrics.storageUsed) / 3)
|
|
],
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.8)',
|
|
'rgba(54, 162, 235, 0.8)',
|
|
'rgba(255, 206, 86, 0.8)',
|
|
'rgba(75, 192, 192, 0.8)',
|
|
],
|
|
borderColor: [
|
|
'rgb(255, 99, 132)',
|
|
'rgb(54, 162, 235)',
|
|
'rgb(255, 206, 86)',
|
|
'rgb(75, 192, 192)',
|
|
],
|
|
borderWidth: 2
|
|
}]
|
|
});
|
|
|
|
let barChartData = $derived({
|
|
labels: ['FPS', 'DOM Nodes (x100)', 'JS Heap (MB)', 'Cache (MB)', 'IndexedDB (MB)'],
|
|
datasets: [{
|
|
label: 'Métricas Técnicas',
|
|
data: [
|
|
metrics.fps,
|
|
Math.floor(metrics.domNodes / 100),
|
|
metrics.jsHeapSize,
|
|
metrics.cacheSize,
|
|
metrics.indexedDBSize
|
|
],
|
|
backgroundColor: [
|
|
'rgba(255, 99, 132, 0.6)',
|
|
'rgba(54, 162, 235, 0.6)',
|
|
'rgba(255, 206, 86, 0.6)',
|
|
'rgba(75, 192, 192, 0.6)',
|
|
'rgba(153, 102, 255, 0.6)',
|
|
],
|
|
borderColor: [
|
|
'rgb(255, 99, 132)',
|
|
'rgb(54, 162, 235)',
|
|
'rgb(255, 206, 86)',
|
|
'rgb(75, 192, 192)',
|
|
'rgb(153, 102, 255)',
|
|
],
|
|
borderWidth: 2
|
|
}]
|
|
});
|
|
|
|
// Função para obter cor baseada no valor
|
|
function getStatusColor(value: number | undefined, type: "normal" | "inverted" = "normal"): string {
|
|
if (value === undefined) return "badge-ghost";
|
|
|
|
if (type === "normal") {
|
|
if (value < 60) return "badge-success";
|
|
if (value < 80) return "badge-warning";
|
|
return "badge-error";
|
|
} else {
|
|
if (value < 100) return "badge-success";
|
|
if (value < 500) return "badge-warning";
|
|
return "badge-error";
|
|
}
|
|
}
|
|
|
|
function getProgressColor(value: number | undefined): string {
|
|
if (value === undefined) return "progress-ghost";
|
|
|
|
if (value < 60) return "progress-success";
|
|
if (value < 80) return "progress-warning";
|
|
return "progress-error";
|
|
}
|
|
|
|
function formatValue(value: number | undefined, suffix: string = "%"): string {
|
|
if (value === undefined) return "N/A";
|
|
return `${value.toFixed(1)}${suffix}`;
|
|
}
|
|
|
|
// Estima uso de CPU
|
|
async function estimateCPU(): Promise<number> {
|
|
const start = performance.now();
|
|
let sum = 0;
|
|
for (let i = 0; i < 100000; i++) {
|
|
sum += Math.random();
|
|
}
|
|
const end = performance.now();
|
|
const executionTime = end - start;
|
|
return Math.min(100, Math.round((executionTime / 10) * 100));
|
|
}
|
|
|
|
// Obtém uso de memória
|
|
function getMemoryUsage(): number {
|
|
// @ts-ignore
|
|
if (performance.memory) {
|
|
// @ts-ignore
|
|
const { usedJSHeapSize, jsHeapSizeLimit } = performance.memory;
|
|
return Math.round((usedJSHeapSize / jsHeapSizeLimit) * 100);
|
|
}
|
|
return Math.round(Math.random() * 30 + 20);
|
|
}
|
|
|
|
// Mede latência de rede
|
|
async function measureLatency(): Promise<number> {
|
|
const start = performance.now();
|
|
try {
|
|
await fetch(window.location.origin + "/favicon.ico", {
|
|
method: "HEAD",
|
|
cache: "no-cache",
|
|
});
|
|
} catch (e) {
|
|
// Ignorar erros
|
|
}
|
|
const end = performance.now();
|
|
return Math.round(end - start);
|
|
}
|
|
|
|
// Obtém storage usado
|
|
async function getStorageUsage(): Promise<number> {
|
|
try {
|
|
if (navigator.storage && navigator.storage.estimate) {
|
|
const estimate = await navigator.storage.estimate();
|
|
if (estimate.usage && estimate.quota) {
|
|
return Math.round((estimate.usage / estimate.quota) * 100);
|
|
}
|
|
}
|
|
} catch (e) {
|
|
// Ignorar erros
|
|
}
|
|
|
|
let totalSize = 0;
|
|
for (let key in localStorage) {
|
|
if (localStorage.hasOwnProperty(key)) {
|
|
totalSize += localStorage[key].length + key.length;
|
|
}
|
|
}
|
|
return Math.round(Math.min((totalSize / (10 * 1024 * 1024)) * 100, 100));
|
|
}
|
|
|
|
// Obtém usuários online
|
|
async function getUsuariosOnline(): Promise<number> {
|
|
try {
|
|
const usuarios = await client.query(api.chat.listarTodosUsuarios, {});
|
|
return usuarios.filter((u: any) => u.statusPresenca === "online").length;
|
|
} catch (e) {
|
|
console.error("Erro ao obter usuários:", e);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// Tempo de resposta
|
|
async function getResponseTime(): Promise<number> {
|
|
const start = performance.now();
|
|
try {
|
|
await client.query(api.chat.listarTodosUsuarios, {});
|
|
} catch (e) {
|
|
// Ignorar erros
|
|
}
|
|
const end = performance.now();
|
|
return Math.round(end - start);
|
|
}
|
|
|
|
// Contador de erros
|
|
let errorCount = 0;
|
|
const originalError = console.error;
|
|
console.error = function (...args: any[]) {
|
|
errorCount++;
|
|
originalError.apply(console, args);
|
|
};
|
|
|
|
// Variáveis para FPS
|
|
let frameCount = 0;
|
|
let lastFrameTime = performance.now();
|
|
let currentFPS = 60;
|
|
|
|
// Calcular FPS
|
|
function measureFPS() {
|
|
frameCount++;
|
|
const now = performance.now();
|
|
const delta = now - lastFrameTime;
|
|
|
|
if (delta >= 1000) {
|
|
currentFPS = Math.round((frameCount * 1000) / delta);
|
|
frameCount = 0;
|
|
lastFrameTime = now;
|
|
}
|
|
|
|
requestAnimationFrame(measureFPS);
|
|
}
|
|
|
|
// Obter informações do navegador
|
|
function getBrowserInfo() {
|
|
const ua = navigator.userAgent;
|
|
let browserName = "Unknown";
|
|
let browserVersion = "";
|
|
|
|
if (ua.indexOf("Firefox") > -1) {
|
|
browserName = "Firefox";
|
|
browserVersion = ua.match(/Firefox\/(\d+\.\d+)/)?.[1] || "";
|
|
} else if (ua.indexOf("Chrome") > -1) {
|
|
browserName = "Chrome";
|
|
browserVersion = ua.match(/Chrome\/(\d+\.\d+)/)?.[1] || "";
|
|
} else if (ua.indexOf("Safari") > -1) {
|
|
browserName = "Safari";
|
|
browserVersion = ua.match(/Version\/(\d+\.\d+)/)?.[1] || "";
|
|
} else if (ua.indexOf("Edge") > -1) {
|
|
browserName = "Edge";
|
|
browserVersion = ua.match(/Edge\/(\d+\.\d+)/)?.[1] || "";
|
|
}
|
|
|
|
return { browserName, browserVersion };
|
|
}
|
|
|
|
// Obter informações de rede
|
|
function getNetworkInfo() {
|
|
// @ts-ignore
|
|
const connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;
|
|
|
|
if (connection) {
|
|
return {
|
|
type: connection.effectiveType || "unknown",
|
|
downlink: connection.downlink || 0,
|
|
rtt: connection.rtt || 0,
|
|
};
|
|
}
|
|
|
|
return { type: "unknown", downlink: 0, rtt: 0 };
|
|
}
|
|
|
|
// Obter informação de bateria
|
|
async function getBatteryInfo() {
|
|
try {
|
|
// @ts-ignore
|
|
if (navigator.getBattery) {
|
|
// @ts-ignore
|
|
const battery = await navigator.getBattery();
|
|
return {
|
|
level: Math.round(battery.level * 100),
|
|
charging: battery.charging,
|
|
};
|
|
}
|
|
} catch (e) {
|
|
// Ignorar erros
|
|
}
|
|
|
|
return { level: 100, charging: false };
|
|
}
|
|
|
|
// Obter tamanho do cache
|
|
function getCacheSize(): number {
|
|
let size = 0;
|
|
|
|
// LocalStorage
|
|
for (let key in localStorage) {
|
|
if (localStorage.hasOwnProperty(key)) {
|
|
size += localStorage[key].length + key.length;
|
|
}
|
|
}
|
|
|
|
// SessionStorage
|
|
for (let key in sessionStorage) {
|
|
if (sessionStorage.hasOwnProperty(key)) {
|
|
size += sessionStorage[key].length + key.length;
|
|
}
|
|
}
|
|
|
|
// Converter para MB
|
|
return parseFloat((size / (1024 * 1024)).toFixed(2));
|
|
}
|
|
|
|
// Tempo de atividade
|
|
let startTime = Date.now();
|
|
function getUptime(): number {
|
|
return Math.floor((Date.now() - startTime) / 1000); // em segundos
|
|
}
|
|
|
|
// Contador de requisições HTTP
|
|
let httpRequestCount = 0;
|
|
const originalFetch = window.fetch;
|
|
window.fetch = function(...args) {
|
|
httpRequestCount++;
|
|
return originalFetch.apply(this, args);
|
|
};
|
|
|
|
// Contador de requisições XMLHttpRequest
|
|
const originalXHROpen = XMLHttpRequest.prototype.open;
|
|
XMLHttpRequest.prototype.open = function(...args) {
|
|
httpRequestCount++;
|
|
return originalXHROpen.apply(this, args);
|
|
};
|
|
|
|
// Obter tamanho do IndexedDB (estimativa)
|
|
async function getIndexedDBSize(): Promise<number> {
|
|
try {
|
|
if ('storage' in navigator && 'estimate' in navigator.storage) {
|
|
const estimate = await navigator.storage.estimate();
|
|
const usage = estimate.usage || 0;
|
|
return parseFloat((usage / (1024 * 1024)).toFixed(2)); // MB
|
|
}
|
|
} catch (e) {
|
|
// Ignorar
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Verificar Service Worker
|
|
function getServiceWorkerStatus(): string {
|
|
if ('serviceWorker' in navigator) {
|
|
if (navigator.serviceWorker.controller) {
|
|
return 'active';
|
|
}
|
|
return 'registered';
|
|
}
|
|
return 'none';
|
|
}
|
|
|
|
// Contar nós do DOM
|
|
function getDOMNodeCount(): number {
|
|
return document.getElementsByTagName('*').length;
|
|
}
|
|
|
|
// Obter tamanho do Heap JS
|
|
function getJSHeapSize(): number {
|
|
// @ts-ignore
|
|
if (performance.memory) {
|
|
// @ts-ignore
|
|
const usedHeap = performance.memory.usedJSHeapSize;
|
|
return parseFloat((usedHeap / (1024 * 1024)).toFixed(2)); // MB
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
// Verificar status do WebSocket (simulado)
|
|
function getWebSocketStatus(): { count: number; status: string } {
|
|
// Em uma aplicação real, você monitoraria os WebSockets conectados
|
|
// Aqui vamos simular baseado na conectividade
|
|
const isOnline = navigator.onLine;
|
|
return {
|
|
count: isOnline ? 1 : 0,
|
|
status: isOnline ? 'connected' : 'disconnected'
|
|
};
|
|
}
|
|
|
|
// Coleta todas as métricas
|
|
async function collectAllMetrics() {
|
|
const [
|
|
cpuUsage,
|
|
memoryUsage,
|
|
networkLatency,
|
|
storageUsed,
|
|
usuariosOnline,
|
|
tempoRespostaMedio,
|
|
batteryInfo,
|
|
indexedDBSize,
|
|
] = await Promise.all([
|
|
estimateCPU(),
|
|
Promise.resolve(getMemoryUsage()),
|
|
measureLatency(),
|
|
getStorageUsage(),
|
|
getUsuariosOnline(),
|
|
getResponseTime(),
|
|
getBatteryInfo(),
|
|
getIndexedDBSize(),
|
|
]);
|
|
|
|
const browserInfo = getBrowserInfo();
|
|
const networkInfo = getNetworkInfo();
|
|
const wsInfo = getWebSocketStatus();
|
|
|
|
const newMetrics = {
|
|
timestamp: Date.now(),
|
|
cpuUsage,
|
|
memoryUsage,
|
|
networkLatency,
|
|
storageUsed,
|
|
usuariosOnline,
|
|
mensagensPorMinuto: 0,
|
|
tempoRespostaMedio,
|
|
errosCount: errorCount,
|
|
// Métricas de performance
|
|
fps: currentFPS,
|
|
networkType: networkInfo.type,
|
|
networkSpeed: networkInfo.downlink,
|
|
browserName: browserInfo.browserName,
|
|
browserVersion: browserInfo.browserVersion,
|
|
screenResolution: `${window.screen.width}x${window.screen.height}`,
|
|
deviceMemory: (navigator as any).deviceMemory || 0,
|
|
hardwareConcurrency: navigator.hardwareConcurrency || 0,
|
|
cacheSize: getCacheSize(),
|
|
batteryLevel: batteryInfo.level,
|
|
batteryCharging: batteryInfo.charging,
|
|
uptime: getUptime(),
|
|
// Novas métricas avançadas
|
|
httpRequests: httpRequestCount,
|
|
wsConnections: wsInfo.count,
|
|
wsStatus: wsInfo.status,
|
|
indexedDBSize,
|
|
serviceWorkerStatus: getServiceWorkerStatus(),
|
|
domNodes: getDOMNodeCount(),
|
|
jsHeapSize: getJSHeapSize(),
|
|
};
|
|
|
|
// Resetar contadores
|
|
errorCount = 0;
|
|
httpRequestCount = 0;
|
|
|
|
// Atualizar métricas
|
|
metrics = newMetrics;
|
|
|
|
// Adicionar ao histórico
|
|
metricsHistory = [...metricsHistory, newMetrics].slice(-100);
|
|
|
|
// Salvar no localStorage para persistência
|
|
try {
|
|
localStorage.setItem('sgse_metrics_history', JSON.stringify(metricsHistory));
|
|
} catch (e) {
|
|
console.warn("Não foi possível salvar histórico:", e);
|
|
}
|
|
}
|
|
|
|
// Iniciar coleta ao montar
|
|
onMount(() => {
|
|
// Iniciar medição de FPS
|
|
measureFPS();
|
|
|
|
// Carregar histórico do localStorage
|
|
try {
|
|
const saved = localStorage.getItem('sgse_metrics_history');
|
|
if (saved) {
|
|
metricsHistory = JSON.parse(saved);
|
|
if (metricsHistory.length > 0) {
|
|
metrics = metricsHistory[metricsHistory.length - 1];
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.warn("Não foi possível carregar histórico:", e);
|
|
}
|
|
|
|
// Coletar imediatamente
|
|
collectAllMetrics();
|
|
|
|
// Configurar intervalo de 2 segundos
|
|
intervalId = setInterval(collectAllMetrics, 2000);
|
|
});
|
|
|
|
// Parar coleta ao desmontar
|
|
onDestroy(() => {
|
|
if (intervalId) {
|
|
clearInterval(intervalId);
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<div class="card bg-gradient-to-br from-base-100 to-base-200 shadow-2xl border-2 border-primary/20">
|
|
<div class="card-body">
|
|
<!-- Header -->
|
|
<div class="flex flex-col md:flex-row md:items-center md:justify-between gap-4 mb-6">
|
|
<div class="flex items-center gap-2">
|
|
<div class="badge badge-success badge-lg gap-2 animate-pulse">
|
|
<div class="w-2 h-2 bg-white rounded-full"></div>
|
|
Tempo Real - Atualização a cada 2s
|
|
</div>
|
|
<div class="badge badge-warning badge-sm">Modo Local</div>
|
|
</div>
|
|
<div class="flex gap-2">
|
|
<button
|
|
type="button"
|
|
class="btn btn-primary btn-sm"
|
|
onclick={() => showAlertModal = true}
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405A2.032 2.032 0 0118 14.158V11a6.002 6.002 0 00-4-5.659V5a2 2 0 10-4 0v.341C7.67 6.165 6 8.388 6 11v3.159c0 .538-.214 1.055-.595 1.436L4 17h5m6 0v1a3 3 0 11-6 0v-1m6 0H9" />
|
|
</svg>
|
|
Configurar Alertas
|
|
</button>
|
|
<button
|
|
type="button"
|
|
class="btn btn-secondary btn-sm"
|
|
onclick={() => showReportModal = true}
|
|
>
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12h6m-6 4h6m2 5H7a2 2 0 01-2-2V5a2 2 0 012-2h5.586a1 1 0 01.707.293l5.414 5.414a1 1 0 01.293.707V19a2 2 0 01-2 2z" />
|
|
</svg>
|
|
Gerar Relatório
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Métricas Grid -->
|
|
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6">
|
|
<!-- CPU Usage -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-primary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-primary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">CPU</div>
|
|
<div class="stat-value text-primary text-3xl">{formatValue(metrics.cpuUsage)}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {getStatusColor(metrics.cpuUsage)} badge-sm">
|
|
{metrics.cpuUsage < 60 ? "Normal" : metrics.cpuUsage < 80 ? "Atenção" : "Crítico"}
|
|
</div>
|
|
</div>
|
|
<progress class="progress {getProgressColor(metrics.cpuUsage)} w-full mt-2" value={metrics.cpuUsage} max="100"></progress>
|
|
</div>
|
|
|
|
<!-- Memory Usage -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-success/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-success">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 12h14M5 12a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v4a2 2 0 01-2 2M5 12a2 2 0 00-2 2v4a2 2 0 002 2h14a2 2 0 002-2v-4a2 2 0 00-2-2m-2-4h.01M17 16h.01" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Memória RAM</div>
|
|
<div class="stat-value text-success text-3xl">{formatValue(metrics.memoryUsage)}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {getStatusColor(metrics.memoryUsage)} badge-sm">
|
|
{metrics.memoryUsage < 60 ? "Normal" : metrics.memoryUsage < 80 ? "Atenção" : "Crítico"}
|
|
</div>
|
|
</div>
|
|
<progress class="progress {getProgressColor(metrics.memoryUsage)} w-full mt-2" value={metrics.memoryUsage} max="100"></progress>
|
|
</div>
|
|
|
|
<!-- Network Latency -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-warning/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-warning">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Latência de Rede</div>
|
|
<div class="stat-value text-warning text-3xl">{formatValue(metrics.networkLatency, "ms")}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {getStatusColor(metrics.networkLatency, 'inverted')} badge-sm">
|
|
{metrics.networkLatency < 100 ? "Excelente" : metrics.networkLatency < 500 ? "Boa" : "Lenta"}
|
|
</div>
|
|
</div>
|
|
<progress class="progress progress-warning w-full mt-2" value={Math.min((metrics.networkLatency || 0) / 10, 100)} max="100"></progress>
|
|
</div>
|
|
|
|
<!-- Storage Usage -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-info/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-info">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Armazenamento</div>
|
|
<div class="stat-value text-info text-3xl">{formatValue(metrics.storageUsed)}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {getStatusColor(metrics.storageUsed)} badge-sm">
|
|
{metrics.storageUsed < 60 ? "Normal" : metrics.storageUsed < 80 ? "Atenção" : "Crítico"}
|
|
</div>
|
|
</div>
|
|
<progress class="progress progress-info w-full mt-2" value={metrics.storageUsed} max="100"></progress>
|
|
</div>
|
|
|
|
<!-- Usuários Online -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-accent/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-accent">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Usuários Online</div>
|
|
<div class="stat-value text-accent text-3xl">{metrics.usuariosOnline}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-accent badge-sm">Tempo Real</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mensagens por Minuto -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-secondary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-secondary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 10h.01M12 10h.01M16 10h.01M9 16H5a2 2 0 01-2-2V6a2 2 0 012-2h14a2 2 0 012 2v8a2 2 0 01-2 2h-5l-5 5v-5z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Mensagens/min</div>
|
|
<div class="stat-value text-secondary text-3xl">{metrics.mensagensPorMinuto}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-secondary badge-sm">Atividade</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tempo de Resposta -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-primary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-primary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Tempo Resposta</div>
|
|
<div class="stat-value text-primary text-3xl">{formatValue(metrics.tempoRespostaMedio, "ms")}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {getStatusColor(metrics.tempoRespostaMedio, 'inverted')} badge-sm">
|
|
{metrics.tempoRespostaMedio < 100 ? "Rápido" : metrics.tempoRespostaMedio < 500 ? "Normal" : "Lento"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Erros -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-error/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-error">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Erros (2s)</div>
|
|
<div class="stat-value text-error text-3xl">{metrics.errosCount}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {metrics.errosCount === 0 ? 'badge-success' : 'badge-error'} badge-sm">
|
|
{metrics.errosCount === 0 ? "Sem erros" : "Verificar logs"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- FPS (Performance) -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-success/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-success">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">FPS (Performance)</div>
|
|
<div class="stat-value text-success text-3xl">{metrics.fps}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {metrics.fps >= 50 ? 'badge-success' : metrics.fps >= 30 ? 'badge-warning' : 'badge-error'} badge-sm">
|
|
{metrics.fps >= 50 ? "Fluido" : metrics.fps >= 30 ? "Moderado" : "Lento"}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tipo de Conexão -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-info/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-info">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.111 16.404a5.5 5.5 0 017.778 0M12 20h.01m-7.08-7.071c3.904-3.905 10.236-3.905 14.141 0M1.394 9.393c5.857-5.857 15.355-5.857 21.213 0" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Tipo de Conexão</div>
|
|
<div class="stat-value text-info text-2xl uppercase">{metrics.networkType}</div>
|
|
<div class="stat-desc mt-2">
|
|
{#if metrics.networkSpeed > 0}
|
|
<div class="badge badge-info badge-sm">{metrics.networkSpeed} Mbps</div>
|
|
{:else}
|
|
<div class="badge badge-ghost badge-sm">N/A</div>
|
|
{/if}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Navegador -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-primary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-primary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 01-9 9m9-9a9 9 0 00-9-9m9 9H3m9 9a9 9 0 01-9-9m9 9c1.657 0 3-4.03 3-9s-1.343-9-3-9m0 18c-1.657 0-3-4.03-3-9s1.343-9 3-9m-9 9a9 9 0 019-9" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Navegador</div>
|
|
<div class="stat-value text-primary text-2xl">{metrics.browserName}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-primary badge-sm">v{metrics.browserVersion}</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Resolução de Tela -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-secondary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-secondary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.75 17L9 20l-1 1h8l-1-1-.75-3M3 13h18M5 17h14a2 2 0 002-2V5a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Resolução</div>
|
|
<div class="stat-value text-secondary text-xl">{metrics.screenResolution}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-secondary badge-sm">Tela</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Memória do Dispositivo -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-accent/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-accent">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">RAM Dispositivo</div>
|
|
<div class="stat-value text-accent text-3xl">{metrics.deviceMemory || "N/A"}{metrics.deviceMemory ? "GB" : ""}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-accent badge-sm">Hardware</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Núcleos de CPU -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-warning/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-warning">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Núcleos CPU</div>
|
|
<div class="stat-value text-warning text-3xl">{metrics.hardwareConcurrency}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-warning badge-sm">Threads</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Cache do Navegador -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-info/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-info">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Cache Browser</div>
|
|
<div class="stat-value text-info text-3xl">{metrics.cacheSize}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-info badge-sm">MB</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bateria -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-success/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-success">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Bateria</div>
|
|
<div class="stat-value text-success text-3xl">{metrics.batteryLevel}%</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {metrics.batteryCharging ? 'badge-success' : 'badge-ghost'} badge-sm">
|
|
{metrics.batteryCharging ? "⚡ Carregando" : "🔋 Bateria"}
|
|
</div>
|
|
</div>
|
|
<progress class="progress progress-success w-full mt-2" value={metrics.batteryLevel} max="100"></progress>
|
|
</div>
|
|
|
|
<!-- Tempo de Atividade -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-primary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-primary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Tempo Ativo</div>
|
|
<div class="stat-value text-primary text-2xl">
|
|
{Math.floor(metrics.uptime / 60)}:{String(metrics.uptime % 60).padStart(2, '0')}
|
|
</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-primary badge-sm">Minutos</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Requisições HTTP -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-info/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-info">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">HTTP Requests (2s)</div>
|
|
<div class="stat-value text-info text-3xl">{metrics.httpRequests}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-info badge-sm">Requisições</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- WebSocket Status -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-success/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-success">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">WebSocket</div>
|
|
<div class="stat-value text-success text-2xl">{metrics.wsConnections}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {metrics.wsStatus === 'connected' ? 'badge-success' : 'badge-error'} badge-sm">
|
|
{metrics.wsStatus === 'connected' ? '🟢 Conectado' : '🔴 Desconectado'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- IndexedDB Size -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-warning/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-warning">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">IndexedDB</div>
|
|
<div class="stat-value text-warning text-3xl">{metrics.indexedDBSize}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-warning badge-sm">MB</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Service Worker -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-secondary/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-secondary">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m5.618-4.016A11.955 11.955 0 0112 2.944a11.955 11.955 0 01-8.618 3.04A12.02 12.02 0 003 9c0 5.591 3.824 10.29 9 11.622 5.176-1.332 9-6.03 9-11.622 0-1.042-.133-2.052-.382-3.016z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">Service Worker</div>
|
|
<div class="stat-value text-secondary text-xl uppercase">{metrics.serviceWorkerStatus}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge {metrics.serviceWorkerStatus === 'active' ? 'badge-success' : metrics.serviceWorkerStatus === 'registered' ? 'badge-info' : 'badge-ghost'} badge-sm">
|
|
{metrics.serviceWorkerStatus === 'active' ? '✓ Ativo' : metrics.serviceWorkerStatus === 'registered' ? '○ Registrado' : '✗ Nenhum'}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- DOM Nodes -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-accent/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-accent">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 21a4 4 0 01-4-4V5a2 2 0 012-2h4a2 2 0 012 2v12a4 4 0 01-4 4zm0 0h12a2 2 0 002-2v-4a2 2 0 00-2-2h-2.343M11 7.343l1.657-1.657a2 2 0 012.828 0l2.829 2.829a2 2 0 010 2.828l-8.486 8.485M7 17h.01" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">DOM Nodes</div>
|
|
<div class="stat-value text-accent text-3xl">{metrics.domNodes}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-accent badge-sm">Elementos</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- JS Heap Size -->
|
|
<div class="stat bg-base-100 rounded-2xl shadow-lg border border-error/10 hover:shadow-xl transition-all duration-300 hover:scale-105">
|
|
<div class="stat-figure text-error">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-10 w-10" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 3v2m6-2v2M9 19v2m6-2v2M5 9H3m2 6H3m18-6h-2m2 6h-2M7 19h10a2 2 0 002-2V7a2 2 0 00-2-2H7a2 2 0 00-2 2v10a2 2 0 002 2zM9 9h6v6H9V9z" />
|
|
</svg>
|
|
</div>
|
|
<div class="stat-title font-semibold">JS Heap Size</div>
|
|
<div class="stat-value text-error text-3xl">{metrics.jsHeapSize || "N/A"}</div>
|
|
<div class="stat-desc mt-2">
|
|
<div class="badge badge-error badge-sm">{metrics.jsHeapSize ? "MB" : "Indisponível"}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Seção de Gráficos Interativos -->
|
|
{#if metricsHistory.length > 5}
|
|
<div class="mt-8">
|
|
<div class="divider">
|
|
<h2 class="text-2xl font-bold text-primary flex items-center gap-3">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-8 w-8" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
Gráficos Interativos em Tempo Real
|
|
</h2>
|
|
</div>
|
|
|
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mt-6">
|
|
<!-- Gráfico de Linha: Recursos do Sistema -->
|
|
<div class="card bg-base-100 shadow-xl border border-primary/20">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-primary" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 7h8m0 0v8m0-8l-8 8-4-4-6 6" />
|
|
</svg>
|
|
Recursos do Sistema (Últimos 20 pontos)
|
|
</h3>
|
|
<LineChart data={lineChartData} height={350} />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfico de Área: Atividade da Aplicação -->
|
|
<div class="card bg-base-100 shadow-xl border border-info/20">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-info" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 12l3-3 3 3 4-4M8 21l4-4 4 4M3 4h18M4 4h16v12a1 1 0 01-1 1H5a1 1 0 01-1-1V4z" />
|
|
</svg>
|
|
Atividade da Aplicação (Últimos 30 pontos)
|
|
</h3>
|
|
<AreaChart data={areaChartData} height={350} />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfico Donut: Distribuição de Recursos -->
|
|
<div class="card bg-base-100 shadow-xl border border-success/20">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-success" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11 3.055A9.001 9.001 0 1020.945 13H11V3.055z" />
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.488 9H15V3.512A9.025 9.025 0 0120.488 9z" />
|
|
</svg>
|
|
Distribuição de Uso de Recursos
|
|
</h3>
|
|
<DoughnutChart data={doughnutChartData} height={350} />
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Gráfico de Barras: Métricas Técnicas -->
|
|
<div class="card bg-base-100 shadow-xl border border-warning/20">
|
|
<div class="card-body">
|
|
<h3 class="card-title text-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 text-warning" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z" />
|
|
</svg>
|
|
Métricas Técnicas Avançadas
|
|
</h3>
|
|
<BarChart data={barChartData} height={350} />
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="alert alert-warning mt-6 shadow-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
|
</svg>
|
|
<span>Coletando dados para gráficos... Aguarde alguns segundos.</span>
|
|
</div>
|
|
{/if}
|
|
|
|
<!-- Info Footer -->
|
|
<div class="alert alert-info mt-6 shadow-lg">
|
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current shrink-0 w-6 h-6">
|
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
|
</svg>
|
|
<div>
|
|
<h3 class="font-bold">Monitoramento Ativo (Modo Local) - 23 Métricas Técnicas + 4 Gráficos Interativos</h3>
|
|
<div class="text-xs">
|
|
<strong>Sistema:</strong> CPU, RAM, Latência, Storage |
|
|
<strong>Aplicação:</strong> Usuários, Mensagens, Tempo Resposta, Erros, HTTP Requests |
|
|
<strong>Performance:</strong> FPS, Conexão, Navegador, Tela |
|
|
<strong>Hardware:</strong> RAM Física, Núcleos CPU, Cache, Bateria, Uptime |
|
|
<strong>Avançado:</strong> WebSocket, IndexedDB, Service Worker, DOM Nodes, JS Heap
|
|
<br>
|
|
<strong>Gráficos:</strong> Linha (Recursos), Área (Atividade), Donut (Distribuição), Barras (Métricas)
|
|
<br>
|
|
Última atualização: {new Date(metrics.timestamp).toLocaleString('pt-BR')}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Modals -->
|
|
{#if showAlertModal}
|
|
<AlertConfigModal onClose={() => showAlertModal = false} />
|
|
{/if}
|
|
|
|
{#if showReportModal}
|
|
<ReportGeneratorModal onClose={() => showReportModal = false} />
|
|
{/if}
|
|
|