Files
sgse-app/apps/web/src/lib/components/ti/SystemMonitorCardLocal.svelte

1845 lines
54 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 type { FunctionReturnType } from 'convex/server';
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';
import { obterInformacoesDispositivo } from '$lib/utils/deviceInfo';
const client = useConvexClient();
let showAlertModal = $state(false);
let showReportModal = $state(false);
let intervalId: ReturnType<typeof setInterval> | null = null;
type Metrics = {
timestamp: number;
cpuUsage: number;
memoryUsage: number;
networkLatency: number;
storageUsed: number;
usuariosOnline: number;
mensagensPorMinuto: number;
tempoRespostaMedio: number;
errosCount: number;
fps: number;
networkType: string;
networkSpeed: number;
browserName: string;
browserVersion: string;
screenResolution: string;
deviceMemory: number;
hardwareConcurrency: number;
cacheSize: number;
batteryLevel: number;
batteryCharging: boolean;
uptime: number;
httpRequests: number;
wsConnections: number;
wsStatus: string;
indexedDBSize: number;
serviceWorkerStatus: string;
domNodes: number;
jsHeapSize: number;
// GPS
latitude?: number;
longitude?: number;
gpsPrecision?: number;
gpsConfidence?: number;
// Acelerômetro
accelerometerX?: number;
accelerometerY?: number;
accelerometerZ?: number;
movementDetected?: boolean;
movementMagnitude?: number;
sensorAvailable?: boolean;
};
type NetworkInformationEx = {
effectiveType?: string;
downlink?: number;
rtt?: number;
};
type NavigatorWithConn = Navigator & {
connection?: NetworkInformationEx;
mozConnection?: NetworkInformationEx;
webkitConnection?: NetworkInformationEx;
deviceMemory?: number;
getBattery?: () => Promise<{ level: number; charging: boolean }>;
};
type PerformanceWithMemory = Performance & {
memory?: {
usedJSHeapSize: number;
jsHeapSizeLimit: number;
};
};
// Estado local das métricas
let metrics = $state<Metrics>({
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: Array<Metrics> = $state([]);
// Dados para gráficos
let lineChartData = $derived({
labels: metricsHistory
.map((m) => {
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) => {
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();
for (let i = 0; i < 100000; i++) {
void 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 {
const perf = performance as PerformanceWithMemory;
if (perf.memory) {
const { usedJSHeapSize, jsHeapSizeLimit } = perf.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.png', {
method: 'HEAD',
cache: 'no-cache'
});
} catch {
// 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 {
// 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 {
type UsuariosList = FunctionReturnType<typeof api.chat.listarTodosUsuarios>;
const usuarios = (await client.query(api.chat.listarTodosUsuarios, {})) as UsuariosList;
return usuarios.filter((u) => 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 {
// 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: unknown[]) {
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() {
const nav = navigator as NavigatorWithConn;
const connection = nav.connection || nav.mozConnection || nav.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 {
const nav = navigator as NavigatorWithConn;
if (nav.getBattery) {
const battery = await nav.getBattery();
return {
level: Math.round(battery.level * 100),
charging: battery.charging
};
}
} catch {
// 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.bind(window);
window.fetch = ((...args) => {
httpRequestCount++;
return originalFetch(...args);
}) as typeof window.fetch;
// Contador de requisições XMLHttpRequest
const originalXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function (
method: string,
url: string | URL,
async?: boolean,
username?: string | null,
password?: string | null
) {
httpRequestCount++;
// @ts-expect-error - Forward to original open with same args
return originalXHROpen.apply(this, [method, url, async, username, password]);
};
// 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 {
// 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 {
const perf = performance as PerformanceWithMemory;
if (perf.memory) {
const usedHeap = perf.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,
deviceInfo
] = await Promise.all([
estimateCPU(),
Promise.resolve(getMemoryUsage()),
measureLatency(),
getStorageUsage(),
getUsuariosOnline(),
getResponseTime(),
getBatteryInfo(),
getIndexedDBSize(),
obterInformacoesDispositivo().catch(() => ({}) as Record<string, unknown>) // Capturar erro se falhar
]);
const browserInfo = getBrowserInfo();
const networkInfo = getNetworkInfo();
const wsInfo = getWebSocketStatus();
const newMetrics: Metrics = {
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 NavigatorWithConn).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(),
// GPS
latitude: deviceInfo.latitude,
longitude: deviceInfo.longitude,
gpsPrecision: deviceInfo.precisao,
gpsConfidence: deviceInfo.confiabilidadeGPS,
// Acelerômetro
accelerometerX: deviceInfo.acelerometro?.x,
accelerometerY: deviceInfo.acelerometro?.y,
accelerometerZ: deviceInfo.acelerometro?.z,
movementDetected: deviceInfo.acelerometro?.movimentoDetectado,
movementMagnitude: deviceInfo.acelerometro?.magnitude,
sensorAvailable: deviceInfo.sensorDisponivel
};
// 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 from-base-100 to-base-200 border-primary/20 border-2 bg-linear-to-br shadow-2xl">
<div class="card-body">
<!-- Header -->
<div class="mb-6 flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div class="flex items-center gap-2">
<div class="badge badge-success badge-lg animate-pulse gap-2">
<div class="h-2 w-2 rounded-full bg-white"></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 gap-6 md:grid-cols-2 lg:grid-cols-4">
<!-- CPU Usage -->
<div
class="stat bg-base-100 border-primary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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)} mt-2 w-full"
value={metrics.cpuUsage}
max="100"
></progress>
</div>
<!-- Memory Usage -->
<div
class="stat bg-base-100 border-success/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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)} mt-2 w-full"
value={metrics.memoryUsage}
max="100"
></progress>
</div>
<!-- Network Latency -->
<div
class="stat bg-base-100 border-warning/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 mt-2 w-full"
value={Math.min((metrics.networkLatency || 0) / 10, 100)}
max="100"
></progress>
</div>
<!-- Storage Usage -->
<div
class="stat bg-base-100 border-info/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 mt-2 w-full" value={metrics.storageUsed} max="100"
></progress>
</div>
<!-- Usuários Online -->
<div
class="stat bg-base-100 border-accent/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-secondary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-primary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-error/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-success/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-info/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-primary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-secondary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-accent/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-warning/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-info/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-success/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 mt-2 w-full"
value={metrics.batteryLevel}
max="100"
></progress>
</div>
<!-- Tempo de Atividade -->
<div
class="stat bg-base-100 border-primary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-info/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-success/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-warning/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-secondary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-accent/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 border-error/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<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 GPS e Sensores -->
<div class="mt-8">
<div class="divider">
<h2 class="text-primary flex items-center gap-3 text-2xl font-bold">
<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="M17.657 16.657L13.414 20.9a1.998 1.998 0 01-2.827 0l-4.244-4.243a8 8 0 1111.314 0z"
/>
<path
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M15 11a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
GPS e Sensores
</h2>
</div>
<div class="mt-6 grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3">
<!-- Latitude -->
{#if metrics.latitude !== undefined}
<div
class="stat bg-base-100 border-primary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Latitude</div>
<div class="stat-value text-primary text-2xl">{metrics.latitude.toFixed(6)}</div>
<div class="stat-desc mt-2">
<div class="badge badge-primary badge-sm">GPS</div>
</div>
</div>
{/if}
<!-- Longitude -->
{#if metrics.longitude !== undefined}
<div
class="stat bg-base-100 border-primary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Longitude</div>
<div class="stat-value text-primary text-2xl">{metrics.longitude.toFixed(6)}</div>
<div class="stat-desc mt-2">
<div class="badge badge-primary badge-sm">GPS</div>
</div>
</div>
{/if}
<!-- Precisão GPS -->
{#if metrics.gpsPrecision !== undefined}
<div
class="stat bg-base-100 border-info/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Precisão GPS</div>
<div class="stat-value text-info text-2xl">{metrics.gpsPrecision.toFixed(2)}m</div>
<div class="stat-desc mt-2">
<div class="badge badge-info badge-sm">Precisão</div>
</div>
</div>
{/if}
<!-- Confiança GPS -->
{#if metrics.gpsConfidence !== undefined}
<div
class="stat bg-base-100 border-success/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Confiança GPS</div>
<div class="stat-value text-success text-2xl">
{(metrics.gpsConfidence * 100).toFixed(1)}%
</div>
<div class="stat-desc mt-2">
<div class="badge badge-success badge-sm">Confiança</div>
</div>
</div>
{/if}
<!-- Acelerômetro X -->
{#if metrics.accelerometerX !== undefined}
<div
class="stat bg-base-100 border-warning/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Acelerômetro X</div>
<div class="stat-value text-warning text-2xl">{metrics.accelerometerX.toFixed(3)}</div>
<div class="stat-desc mt-2">
<div class="badge badge-warning badge-sm">m/s²</div>
</div>
</div>
{/if}
<!-- Acelerômetro Y -->
{#if metrics.accelerometerY !== undefined}
<div
class="stat bg-base-100 border-warning/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Acelerômetro Y</div>
<div class="stat-value text-warning text-2xl">{metrics.accelerometerY.toFixed(3)}</div>
<div class="stat-desc mt-2">
<div class="badge badge-warning badge-sm">m/s²</div>
</div>
</div>
{/if}
<!-- Acelerômetro Z -->
{#if metrics.accelerometerZ !== undefined}
<div
class="stat bg-base-100 border-warning/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Acelerômetro Z</div>
<div class="stat-value text-warning text-2xl">{metrics.accelerometerZ.toFixed(3)}</div>
<div class="stat-desc mt-2">
<div class="badge badge-warning badge-sm">m/s²</div>
</div>
</div>
{/if}
<!-- Magnitude de Movimento -->
{#if metrics.movementMagnitude !== undefined}
<div
class="stat bg-base-100 border-error/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Magnitude de Movimento</div>
<div class="stat-value text-error text-2xl">{metrics.movementMagnitude.toFixed(3)}</div>
<div class="stat-desc mt-2">
<div class="badge badge-error badge-sm">m/s²</div>
</div>
</div>
{/if}
<!-- Movimento Detectado -->
{#if metrics.movementDetected !== undefined}
<div
class="stat bg-base-100 border-accent/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Movimento Detectado</div>
<div class="stat-value text-accent text-3xl">
{metrics.movementDetected ? 'Sim' : 'Não'}
</div>
<div class="stat-desc mt-2">
<div
class="badge {metrics.movementDetected
? 'badge-success'
: 'badge-warning'} badge-sm"
>
{metrics.movementDetected ? 'Ativo' : 'Inativo'}
</div>
</div>
</div>
{/if}
<!-- Status do Sensor -->
{#if metrics.sensorAvailable !== undefined}
<div
class="stat bg-base-100 border-secondary/10 rounded-2xl border shadow-lg transition-all duration-300 hover:scale-105 hover:shadow-xl"
>
<div class="stat-title font-semibold">Sensor Disponível</div>
<div class="stat-value text-secondary text-3xl">
{metrics.sensorAvailable ? 'Sim' : 'Não'}
</div>
<div class="stat-desc mt-2">
<div
class="badge {metrics.sensorAvailable ? 'badge-success' : 'badge-ghost'} badge-sm"
>
{metrics.sensorAvailable ? 'Disponível' : 'Indisponível'}
</div>
</div>
</div>
{/if}
</div>
</div>
<!-- Seção de Gráficos Interativos -->
{#if metricsHistory.length > 5}
<div class="mt-8">
<div class="divider">
<h2 class="text-primary flex items-center gap-3 text-2xl font-bold">
<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="mt-6 grid grid-cols-1 gap-6 lg:grid-cols-2">
<!-- Gráfico de Linha: Recursos do Sistema -->
<div class="card bg-base-100 border-primary/20 border shadow-xl">
<div class="card-body">
<h3 class="card-title text-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-primary h-6 w-6"
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 border-info/20 border shadow-xl">
<div class="card-body">
<h3 class="card-title text-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-info h-6 w-6"
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 border-success/20 border shadow-xl">
<div class="card-body">
<h3 class="card-title text-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-success h-6 w-6"
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 border-warning/20 border shadow-xl">
<div class="card-body">
<h3 class="card-title text-lg">
<svg
xmlns="http://www.w3.org/2000/svg"
class="text-warning h-6 w-6"
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="h-6 w-6 shrink-0 stroke-current"
>
<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="h-6 w-6 shrink-0 stroke-current"
>
<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) - Métricas Técnicas + GPS + Sensores + 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 |
<strong>GPS:</strong> Latitude, Longitude, Precisão, Confiança |
<strong>Sensores:</strong> Acelerômetro (X, Y, Z), Magnitude, Movimento Detectado
<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}