1845 lines
54 KiB
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}
|