feat: refatorar estrutura do projeto e adicionar funcionalidade de websocket
- Adiciona arquivo .env.example com variável VITE_URL_BACKEND - Inclui .env no .gitignore - Adiciona novas dependências: axios, pinia, socket.io-client, - Remove sistema de roteamento anterior e simplifica App.vue - Substitui componentes antigos (NavButton, ServerCard, SidePanel) por novo SessionCard - Remove páginas antigas (Dashboard, Sections) e cria nova página Sessions - Implementa composable useWebSocket para conexão com backend via socket.io - Cria store Pinia para gerenciamento de estado das sessões e aplicações - Define interfaces TypeScript para tipos de dados do websocket - Atualiza mock de servidores para nova estrutura de dados - Configura servidor Vite para hospedagem na porta 3333 - Ajusta configuração do TypeScriptmaster
parent
f80f277660
commit
4f31f091e6
|
@ -0,0 +1 @@
|
|||
VITE_URL_BACKEND=http://seu-backend-url
|
|
@ -12,6 +12,9 @@ dist
|
|||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Arquivos de ambiente
|
||||
.env
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
|
|
@ -9,7 +9,10 @@
|
|||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^1.11.0",
|
||||
"lucide-vue-next": "^0.539.0",
|
||||
"pinia": "^3.0.3",
|
||||
"socket.io-client": "^4.8.1",
|
||||
"vue": "^3.5.18",
|
||||
"vue-router": "4"
|
||||
},
|
||||
|
|
22
router.ts
22
router.ts
|
@ -1,22 +0,0 @@
|
|||
import { createRouter, createWebHashHistory } from 'vue-router';
|
||||
import Dashboard from './src/pages/Dashboard.vue';
|
||||
import Sections from './src/pages/Sections.vue';
|
||||
const routes = [
|
||||
{
|
||||
path: '/',
|
||||
name: 'dashboard',
|
||||
component: Dashboard
|
||||
},
|
||||
{
|
||||
path: '/sections',
|
||||
name: 'Seções',
|
||||
component: Sections
|
||||
}
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes
|
||||
})
|
||||
|
||||
export default router;
|
15
src/App.vue
15
src/App.vue
|
@ -1,14 +1,11 @@
|
|||
<script setup lang="ts">
|
||||
import SidePanel from './components/SidePanel.vue';
|
||||
|
||||
|
||||
import Sessions from './pages/Sessions.vue';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex">
|
||||
<SidePanel />
|
||||
<main class="flex-1">
|
||||
<RouterView />
|
||||
</main>
|
||||
</div>
|
||||
<div class="flex">
|
||||
<main class="flex-1">
|
||||
<Sessions />
|
||||
</main>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,16 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
interface Props {
|
||||
title: string;
|
||||
isActive: boolean;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<button :class="`text-2xl bg-bg p-2 w-full rounded-lg border-2 border-border
|
||||
capitalize font-bold hover:bg-cardHover transition duration-150
|
||||
${props.isActive ? 'text-accent' : ''}`">
|
||||
{{ props.title }}
|
||||
</button>
|
||||
</template>
|
|
@ -1,90 +0,0 @@
|
|||
<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>
|
|
@ -0,0 +1,139 @@
|
|||
<script setup lang="ts">
|
||||
import { Server } from 'lucide-vue-next';
|
||||
import { computed } from 'vue';
|
||||
import type { Session } from '../props/websocketResponse';
|
||||
|
||||
interface Props {
|
||||
session: Session;
|
||||
index: number;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
|
||||
const convertMemorySize = (size: string) => {
|
||||
if (size.endsWith('Gi')) {
|
||||
return size.replace('Gi', 'GB');
|
||||
} else if (size.endsWith('Mi')) {
|
||||
return size.replace('Mi', 'MB');
|
||||
}
|
||||
return size;
|
||||
};
|
||||
|
||||
// Função para calcular a porcentagem de uso da RAM
|
||||
const ramPercentage = computed(() => {
|
||||
// Função para converter para Mi (Mebibytes)
|
||||
const convertToMi = (value: string): number => {
|
||||
const num = parseFloat(value.replace(/[^\d.]/g, ''));
|
||||
if (value.includes('Gi')) {
|
||||
return num * 1024; // Converte Gi para Mi
|
||||
}
|
||||
if (value.includes('Ti')) {
|
||||
return num * 1024 * 1024; // Converte Ti para Mi
|
||||
}
|
||||
return num; // Já está em Mi
|
||||
};
|
||||
|
||||
const totalMi = convertToMi(props.session.memoryTotal);
|
||||
const usedMi = convertToMi(props.session.memoryUsed);
|
||||
|
||||
return Math.round((usedMi / totalMi) * 100);
|
||||
});
|
||||
|
||||
// Função para converter a porcentagem de disco de string para número
|
||||
const diskPercentageNumber = computed(() => {
|
||||
const totalSize = parseFloat(props.session.diskSize.replace(/[^\d.]/g, ''));
|
||||
const usedSize = parseFloat(props.session.diskUsed.replace(/[^\d.]/g, ''));
|
||||
|
||||
return Math.round((usedSize / totalSize) * 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">{{ session.name }}</h1>
|
||||
<h1 class="text-green-600">{{ session.ipAddress }}</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>{{ convertMemorySize(session.memoryTotal) }}</h1>
|
||||
</div>
|
||||
|
||||
<!-- Barra de progresso da RAM -->
|
||||
<div class="mt-1 mb-2">
|
||||
<div class="flex justify-between text-md text-gray-400 mb-1">
|
||||
<span>{{ convertMemorySize(session.memoryUsed) }}</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: {{ convertMemorySize(session.memoryFree) }}</span>
|
||||
<span>Cache: {{ convertMemorySize(session.memoryCache) }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row justify-between text-lg">
|
||||
<h1 class="font-bold">Armazenamento:</h1>
|
||||
<h1>{{ session.diskSize }}</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: {{ session.diskUsed }}</span>
|
||||
<span>{{ diskPercentageNumber }}%</span>
|
||||
</div>
|
||||
<div class="w-full bg-bg rounded-full h-2">
|
||||
<div
|
||||
class="h-2 rounded-full transition-all duration-300"
|
||||
:class="
|
||||
diskPercentageNumber > 80
|
||||
? 'bg-red-500'
|
||||
: diskPercentageNumber > 60
|
||||
? 'bg-yellow-500'
|
||||
: 'bg-green-500'
|
||||
"
|
||||
:style="`width: ${diskPercentageNumber}%`"
|
||||
></div>
|
||||
</div>
|
||||
<div class="flex justify-between text-sm text-gray-400 mt-1">
|
||||
<span>Disponível: {{ session.diskAvailable }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Aplicações hospedadas -->
|
||||
<div class="mt-4">
|
||||
<h2 class="font-bold text-lg mb-2">Aplicações:</h2>
|
||||
<div class="flex flex-wrap gap-1">
|
||||
<span
|
||||
v-if="session.appName && session.appName.length > 0"
|
||||
v-for="app in session.appName"
|
||||
:key="app"
|
||||
class="px-2 py-1 bg-accent text-white text-xs rounded"
|
||||
>
|
||||
{{ app }}
|
||||
</span>
|
||||
<span v-else class="text-gray-400 text-sm">Nenhuma aplicação encontrada.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -1,53 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
import { ChevronLeft, ChevronRight, LayoutDashboard, Settings } from 'lucide-vue-next';
|
||||
import { ref } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
const route = useRoute();
|
||||
const isCollapsed = ref(false);
|
||||
|
||||
const toggleCollapse = () => {
|
||||
isCollapsed.value = !isCollapsed.value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<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>
|
|
@ -0,0 +1,84 @@
|
|||
import { io, type Socket } from 'socket.io-client';
|
||||
import { useStore } from '../stores/store';
|
||||
import { onMounted, onUnmounted, ref } from 'vue';
|
||||
import type { RefreshDataPayload } from '../props/websocketResponse';
|
||||
|
||||
export function useWebSocket() {
|
||||
const store = useStore();
|
||||
const socket = ref<Socket | null>(null);
|
||||
const intervalIds = ref<{ applications: number[]; sessions: number[] }>({
|
||||
applications: [],
|
||||
sessions: [],
|
||||
});
|
||||
|
||||
const clearIntervals = (type?: 'applications' | 'sessions') => {
|
||||
if (type) {
|
||||
intervalIds.value[type].forEach((id) => clearInterval(id));
|
||||
intervalIds.value[type] = [];
|
||||
} else {
|
||||
Object.values(intervalIds.value)
|
||||
.flat()
|
||||
.forEach((id) => clearInterval(id));
|
||||
intervalIds.value = { applications: [], sessions: [] };
|
||||
}
|
||||
};
|
||||
|
||||
const connectWebSocket = () => {
|
||||
let sessionId = localStorage.getItem('sessionId');
|
||||
|
||||
socket.value = io(import.meta.env.VITE_URL_BACKEND, {
|
||||
query: { sessionId },
|
||||
});
|
||||
|
||||
socket.value.on('connect', () => {
|
||||
if (!sessionId) {
|
||||
sessionId = Math.random().toString(36).substring(2, 11);
|
||||
localStorage.setItem('sessionId', sessionId);
|
||||
socket.value?.emit('sessionId', sessionId);
|
||||
}
|
||||
});
|
||||
|
||||
socket.value.on('refreshData', (data: RefreshDataPayload) => {
|
||||
clearIntervals();
|
||||
store.setApplications(data.applications);
|
||||
store.setSessions(data.sessions);
|
||||
store.isLoading = false;
|
||||
});
|
||||
|
||||
socket.value.on('error', () => {
|
||||
alert(
|
||||
'Atenção! Tentativa de conexão QRcode inválida. O dispositivo lido não corresponde com o telefone da aplicação.'
|
||||
);
|
||||
});
|
||||
|
||||
socket.value.on('disconnect', (reason) => {
|
||||
console.log(`Desconectado: ${reason}`);
|
||||
localStorage.removeItem('sessionId');
|
||||
});
|
||||
|
||||
socket.value.on('reconnect', (attemptNumber) => {
|
||||
console.log(`Reconectado após ${attemptNumber} tentativas.`);
|
||||
});
|
||||
};
|
||||
|
||||
const disconnectWebSocket = () => {
|
||||
clearIntervals();
|
||||
socket.value?.disconnect();
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
connectWebSocket();
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
disconnectWebSocket();
|
||||
});
|
||||
|
||||
return {
|
||||
socket,
|
||||
intervalIds,
|
||||
clearIntervals,
|
||||
connectWebSocket,
|
||||
disconnectWebSocket,
|
||||
};
|
||||
}
|
14
src/main.ts
14
src/main.ts
|
@ -1,6 +1,10 @@
|
|||
import { createApp } from 'vue'
|
||||
import router from '../router'
|
||||
import App from './App.vue'
|
||||
import './style.css'
|
||||
import { createApp } from 'vue';
|
||||
import App from './App.vue';
|
||||
import './style.css';
|
||||
import { createPinia } from 'pinia';
|
||||
|
||||
createApp(App).use(router).mount('#app')
|
||||
const app = createApp(App);
|
||||
const pinia = createPinia();
|
||||
|
||||
app.use(pinia);
|
||||
app.mount('#app');
|
||||
|
|
|
@ -1,58 +1,87 @@
|
|||
export interface ServerData {
|
||||
name: string;
|
||||
ip: string;
|
||||
totalRam: string;
|
||||
totalRamUsed: string;
|
||||
cachedRam: string;
|
||||
freeRam: string;
|
||||
totalStorage: string;
|
||||
usedStorage: string;
|
||||
freeStorage: string;
|
||||
ipAddress: string;
|
||||
memoryTotal: string;
|
||||
memoryUsed: string;
|
||||
memoryFree: string;
|
||||
memoryCache: string;
|
||||
diskSize: string;
|
||||
diskUsed: string;
|
||||
diskAvailable: string;
|
||||
diskPercentage: string;
|
||||
appName: string[];
|
||||
caution: boolean;
|
||||
}
|
||||
|
||||
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: "SESSÃO 7",
|
||||
ipAddress: "172.31.187.39",
|
||||
memoryTotal: "2.0Gi",
|
||||
memoryUsed: "100Mi",
|
||||
memoryFree: "953Mi",
|
||||
memoryCache: "993Mi",
|
||||
diskSize: "20G",
|
||||
diskUsed: "3.1G",
|
||||
diskAvailable: "16G",
|
||||
diskPercentage: "17%",
|
||||
appName: [
|
||||
"el_lojas",
|
||||
"poc_colombia_test",
|
||||
"polimix",
|
||||
"same"
|
||||
],
|
||||
caution: false
|
||||
},
|
||||
{
|
||||
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"
|
||||
ipAddress: "192.168.1.11",
|
||||
memoryTotal: "32Gi",
|
||||
memoryUsed: "24.2Gi",
|
||||
memoryFree: "7.8Gi",
|
||||
memoryCache: "4.8Gi",
|
||||
diskSize: "1T",
|
||||
diskUsed: "750G",
|
||||
diskAvailable: "250G",
|
||||
diskPercentage: "75%",
|
||||
appName: [
|
||||
"database_primary",
|
||||
"backup_service"
|
||||
],
|
||||
caution: true
|
||||
},
|
||||
{
|
||||
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"
|
||||
ipAddress: "192.168.1.12",
|
||||
memoryTotal: "8Gi",
|
||||
memoryUsed: "3.2Gi",
|
||||
memoryFree: "4.8Gi",
|
||||
memoryCache: "1.1Gi",
|
||||
diskSize: "250G",
|
||||
diskUsed: "120G",
|
||||
diskAvailable: "130G",
|
||||
diskPercentage: "48%",
|
||||
appName: [
|
||||
"api_gateway",
|
||||
"auth_service"
|
||||
],
|
||||
caution: false
|
||||
},
|
||||
{
|
||||
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"
|
||||
ipAddress: "192.168.1.13",
|
||||
memoryTotal: "16Gi",
|
||||
memoryUsed: "5.1Gi",
|
||||
memoryFree: "10.9Gi",
|
||||
memoryCache: "2.5Gi",
|
||||
diskSize: "2T",
|
||||
diskUsed: "1.2T",
|
||||
diskAvailable: "800G",
|
||||
diskPercentage: "60%",
|
||||
appName: [
|
||||
"backup_manager",
|
||||
"file_sync"
|
||||
],
|
||||
caution: false
|
||||
}
|
||||
];
|
||||
|
|
|
@ -1,14 +0,0 @@
|
|||
<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 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>
|
|
@ -1,6 +0,0 @@
|
|||
<script setup lang="ts">
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<h1 class="text-text">Section</h1>
|
||||
</template>
|
|
@ -0,0 +1,49 @@
|
|||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useWebSocket } from '../composables/useWebSocket';
|
||||
import SessionCard from '../components/SessionCard.vue';
|
||||
import { useStore } from '../stores/store';
|
||||
|
||||
const store = useStore();
|
||||
const { sessions, isLoading, reminderSessions } = storeToRefs(store);
|
||||
|
||||
useWebSocket();
|
||||
|
||||
// Compute se há alertas
|
||||
const hasSessionAlerts = computed(() => reminderSessions.value);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-bg min-h-screen text-text">
|
||||
<!-- Header de navegação -->
|
||||
<div class="border-b border-border bg-cardBg">
|
||||
<div class="p-6">
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex items-center gap-2">
|
||||
<h1 class="text-2xl font-bold text-accent">SESSÕES</h1>
|
||||
<div v-if="hasSessionAlerts" class="w-2 h-2 bg-red-500 rounded-full"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading -->
|
||||
<div v-if="isLoading" class="flex justify-center items-center h-64">
|
||||
<div class="animate-spin rounded-full h-12 w-12 border-b-2 border-accent"></div>
|
||||
</div>
|
||||
|
||||
<!-- Conteúdo principal -->
|
||||
<div v-else class="p-6">
|
||||
<!-- Grid de sessões -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
||||
<SessionCard
|
||||
v-for="(session, index) in sessions"
|
||||
:key="`session_${index}`"
|
||||
:session="session"
|
||||
:index="index"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,30 @@
|
|||
export interface Application {
|
||||
id: string;
|
||||
name: string;
|
||||
db: string;
|
||||
status: string;
|
||||
diskUse?: string;
|
||||
diskSize?: string;
|
||||
diskAvailable?: string;
|
||||
url?: string;
|
||||
qrcode?: string;
|
||||
number?: string;
|
||||
}
|
||||
|
||||
export interface Session {
|
||||
name: string;
|
||||
ipAddress: string;
|
||||
memoryTotal: string;
|
||||
memoryUsed: string;
|
||||
memoryCache: string;
|
||||
memoryFree: string;
|
||||
diskSize: string;
|
||||
diskUsed: string;
|
||||
diskAvailable: string;
|
||||
appName?: string[];
|
||||
}
|
||||
|
||||
export interface RefreshDataPayload {
|
||||
applications: Application[];
|
||||
sessions: Session[];
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
import { defineStore } from 'pinia';
|
||||
import type { Application, Session } from '../props/websocketResponse';
|
||||
import { computed, reactive, ref } from 'vue';
|
||||
|
||||
export const useStore = defineStore('application', () => {
|
||||
const applications = ref<Application[]>([]);
|
||||
const sessions = ref<Session[]>([]);
|
||||
const isLoading = ref(true);
|
||||
const reminderSessions = ref(false);
|
||||
|
||||
const setApplications = (apps: Application[]) => {
|
||||
applications.value = apps;
|
||||
};
|
||||
|
||||
const setSessions = (sessionData: Session[]) => {
|
||||
sessions.value = sessionData;
|
||||
};
|
||||
|
||||
return {
|
||||
applications,
|
||||
sessions,
|
||||
isLoading,
|
||||
reminderSessions,
|
||||
setApplications,
|
||||
setSessions,
|
||||
};
|
||||
});
|
|
@ -1,15 +1,15 @@
|
|||
{
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||
}
|
||||
|
|
|
@ -1,7 +1,11 @@
|
|||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
import { defineConfig } from 'vite';
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
})
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
port: 3333,
|
||||
host: '0.0.0.0',
|
||||
},
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue