feat: implementa dashboard de monitoramento de servidores

- Adiciona biblioteca lucide-vue-next para ícones modernos
- Cria componente ServerCard com informações detalhadas:
  * Exibe nome, IP e status do servidor
  * Barras de progresso para RAM e armazenamento
  * Cores dinâmicas baseadas na porcentagem de uso
  * Informações detalhadas de memória (livre, cache)
- Aprimora SidePanel com funcionalidade de colapsar:
  * Botão para expandir/recolher o painel lateral
  * Ícones lucide para navegação (Dashboard, Settings)
  * Estados ativos baseados na rota atual
- Implementa dados mock para 4 servidores de exemplo
- Atualiza Dashboard com layout em grid dos cards de servidor
master
Artur Oliveira 2025-08-14 13:04:17 -03:00
parent eec21b7118
commit f80f277660
6 changed files with 205 additions and 14 deletions

View File

@ -9,6 +9,7 @@
"preview": "vite preview"
},
"dependencies": {
"lucide-vue-next": "^0.539.0",
"vue": "^3.5.18",
"vue-router": "4"
},

View File

@ -0,0 +1,90 @@
<script setup lang="ts">
import { Server } from 'lucide-vue-next';
import { computed } from 'vue';
import type { ServerData } from '../mocks/mockServersList';
const props = defineProps<ServerData>();
// Função para calcular a porcentagem de uso do armazenamento
const storagePercentage = computed(() => {
// Função para converter para GB
const convertToGB = (value: string): number => {
const num = parseFloat(value.replace(/[^\d.]/g, ''));
if (value.includes('TB')) {
return num * 1024; // Converte TB para GB
}
return num; // Já está em GB
};
const totalGB = convertToGB(props.totalStorage);
const usedGB = convertToGB(props.usedStorage);
return Math.round((usedGB / totalGB) * 100);
});
// Função para calcular a porcentagem de uso da RAM
const ramPercentage = computed(() => {
const total = parseFloat(props.totalRam.replace(/[^\d.]/g, ''));
const used = parseFloat(props.totalRamUsed.replace(/[^\d.]/g, ''));
return Math.round((used / total) * 100);
});
</script>
<template>
<div class="bg-cardBg text-text rounded-lg p-4">
<div class="flex flex-row items-center gap-4">
<div class="p-2 bg-bg rounded-md">
<Server :size="32" />
</div>
<div>
<h1 class="text-xl font-bold">{{ props.name }}</h1>
<h1 class="text-green-600">{{ props.ip }}</h1>
</div>
</div>
<div class="h-1 bg-bg my-4" />
<div>
<div class="flex flex-row justify-between text-lg">
<h1 class="font-bold">Ram total: </h1>
<h1>{{ props.totalRam }}</h1>
</div>
<!-- Barra de progresso da RAM (menor) -->
<div class="mt-1 mb-2">
<div class="flex justify-between text-md text-gray-400 mb-1">
<span>{{ props.totalRamUsed }}</span>
<span>{{ ramPercentage }}%</span>
</div>
<div class="w-full bg-bg rounded-full h-1.5">
<div class="h-1.5 rounded-full transition-all duration-300"
:class="ramPercentage > 80 ? 'bg-red-400' : ramPercentage > 60 ? 'bg-yellow-400' : 'bg-blue-400'"
:style="`width: ${ramPercentage}%`"></div>
</div>
<!-- Informações detalhadas da RAM -->
<div class="flex justify-between text-md text-gray-400 mt-1">
<span>Livre: {{ props.freeRam }}</span>
<span>Cache: {{ props.cachedRam }}</span>
</div>
</div>
<div class="flex flex-row justify-between text-lg">
<h1 class="font-bold">Armazenamento: </h1>
<h1>{{ props.totalStorage }}</h1>
</div>
<!-- Barra de progresso do armazenamento -->
<div class="mt-2 mb-3">
<div class="flex justify-between text-sm text-gray-400 mb-1">
<span>Usado: {{ props.usedStorage }}</span>
<span>{{ storagePercentage }}%</span>
</div>
<div class="w-full bg-bg rounded-full h-2">
<div class="h-2 rounded-full transition-all duration-300"
:class="storagePercentage > 80 ? 'bg-red-500' : storagePercentage > 60 ? 'bg-yellow-500' : 'bg-green-500'"
:style="`width: ${storagePercentage}%`"></div>
</div>
</div>
</div>
</div>
</template>

View File

@ -1,22 +1,53 @@
<script setup lang="ts">
import { useRouter } from 'vue-router';
import NavButton from './NavButton.vue';
import { ChevronLeft, ChevronRight, LayoutDashboard, Settings } from 'lucide-vue-next';
import { ref } from 'vue';
import { useRoute } from 'vue-router';
const route = useRouter();
const route = useRoute();
const isCollapsed = ref(false);
const toggleCollapse = () => {
isCollapsed.value = !isCollapsed.value;
};
</script>
<template>
<div class="h-screen bg-cardBg w-fit p-10 border-r-2 border-border flex flex-col gap-10">
<h1 class="text-accent font-bold text-3xl">Omnihit Manager</h1>
<div class="flex flex-col gap-3">
<RouterLink to="/">
<NavButton title="painel" :isActive="true" />
</RouterLink>
<RouterLink to="/sections">
<NavButton title="Seções" :isActive="true" />
</RouterLink>
<div class="h-screen bg-cardBg border-r-2 border-border flex flex-col gap-10 transition-all duration-300"
:class="isCollapsed ? 'w-20 py-10 px-4' : 'w-fit p-10'">
<div class="flex items-center justify-between">
<h1 class="text-accent font-bold text-3xl transition-opacity duration-300"
:class="isCollapsed ? 'opacity-0 hidden' : 'opacity-100'">
Omnihit Manager
</h1>
<button @click="toggleCollapse"
class="p-2 rounded-lg hover:bg-bg transition-colors duration-200 text-accent">
<ChevronLeft v-if="!isCollapsed" :size="24" />
<ChevronRight v-else :size="24" />
</button>
</div>
<div class="flex flex-col gap-3">
<RouterLink to="/" class="block">
<div class="flex items-center gap-3 text-2xl bg-bg p-3 w-full rounded-lg border-2 border-border hover:bg-cardHover transition duration-150 cursor-pointer"
:class="route.path === '/' ? 'text-accent border-accent' : 'text-text'">
<LayoutDashboard :size="24" class="flex-shrink-0" />
<span class="capitalize font-bold transition-opacity duration-300"
:class="isCollapsed ? 'opacity-0 hidden' : 'opacity-100'">
Painel
</span>
</div>
</RouterLink>
<RouterLink to="/sections" class="block">
<div class="flex items-center gap-3 text-2xl bg-bg p-3 w-full rounded-lg border-2 border-border hover:bg-cardHover transition duration-150 cursor-pointer"
:class="route.path === '/sections' ? 'text-accent border-accent' : 'text-text'">
<Settings :size="24" class="flex-shrink-0" />
<span class="capitalize font-bold transition-opacity duration-300"
:class="isCollapsed ? 'opacity-0 hidden' : 'opacity-100'">
Seções
</span>
</div>
</RouterLink>
</div>
</div>
</template>

View File

@ -0,0 +1,58 @@
export interface ServerData {
name: string;
ip: string;
totalRam: string;
totalRamUsed: string;
cachedRam: string;
freeRam: string;
totalStorage: string;
usedStorage: string;
freeStorage: string;
}
export const mockServersList: ServerData[] = [
{
name: "Servidor Web 01",
ip: "192.168.1.10",
totalRam: "16GB",
totalRamUsed: "8.5GB",
cachedRam: "2.1GB",
freeRam: "7.5GB",
totalStorage: "500GB",
usedStorage: "320GB",
freeStorage: "180GB"
},
{
name: "Servidor BD Principal",
ip: "192.168.1.11",
totalRam: "32GB",
totalRamUsed: "24.2GB",
cachedRam: "4.8GB",
freeRam: "7.8GB",
totalStorage: "1TB",
usedStorage: "750GB",
freeStorage: "250GB"
},
{
name: "Servidor API",
ip: "192.168.1.12",
totalRam: "8GB",
totalRamUsed: "3.2GB",
cachedRam: "1.1GB",
freeRam: "4.8GB",
totalStorage: "250GB",
usedStorage: "120GB",
freeStorage: "130GB"
},
{
name: "Servidor Backup",
ip: "192.168.1.13",
totalRam: "16GB",
totalRamUsed: "5.1GB",
cachedRam: "2.5GB",
freeRam: "10.9GB",
totalStorage: "2TB",
usedStorage: "1.2TB",
freeStorage: "800GB"
}
];

View File

@ -1,8 +1,14 @@
<script setup lang="ts">
import ServerCard from '../components/ServerCard.vue';
import { mockServersList } from '../mocks/mockServersList';
const servers = mockServersList;
</script>
<template>
<div class="bg-bg h-screen text-text">
<div class="bg-bg h-screen text-text p-10">
<div class="grid grid-cols-4 gap-6">
<ServerCard v-for="server in servers" :key="server.ip" v-bind="server" />
</div>
</div>
</template>

View File

@ -871,6 +871,11 @@ lru-cache@^10.2.0:
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119"
integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==
lucide-vue-next@^0.539.0:
version "0.539.0"
resolved "https://registry.yarnpkg.com/lucide-vue-next/-/lucide-vue-next-0.539.0.tgz#e10c39ab86f08f9fee9180df9c3c8e89c65632ee"
integrity sha512-8Y75ekxsBqW+9YZPCbxE6KXoCbNmJYUujKP+nK2cIqmONJXvUSeyroEW4DV1Kjlw8ZvmfKwP0FpdjPzuKvRsQw==
magic-string@^0.30.17:
version "0.30.17"
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.17.tgz#450a449673d2460e5bbcfba9a61916a1714c7453"