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
|
dist-ssr
|
||||||
*.local
|
*.local
|
||||||
|
|
||||||
|
# Arquivos de ambiente
|
||||||
|
.env
|
||||||
|
|
||||||
# Editor directories and files
|
# Editor directories and files
|
||||||
.vscode/*
|
.vscode/*
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
|
@ -9,7 +9,10 @@
|
||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"axios": "^1.11.0",
|
||||||
"lucide-vue-next": "^0.539.0",
|
"lucide-vue-next": "^0.539.0",
|
||||||
|
"pinia": "^3.0.3",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"vue": "^3.5.18",
|
"vue": "^3.5.18",
|
||||||
"vue-router": "4"
|
"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">
|
<script setup lang="ts">
|
||||||
import SidePanel from './components/SidePanel.vue';
|
import Sessions from './pages/Sessions.vue';
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<SidePanel />
|
<main class="flex-1">
|
||||||
<main class="flex-1">
|
<Sessions />
|
||||||
<RouterView />
|
</main>
|
||||||
</main>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</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 { createApp } from 'vue';
|
||||||
import router from '../router'
|
import App from './App.vue';
|
||||||
import App from './App.vue'
|
import './style.css';
|
||||||
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 {
|
export interface ServerData {
|
||||||
name: string;
|
name: string;
|
||||||
ip: string;
|
ipAddress: string;
|
||||||
totalRam: string;
|
memoryTotal: string;
|
||||||
totalRamUsed: string;
|
memoryUsed: string;
|
||||||
cachedRam: string;
|
memoryFree: string;
|
||||||
freeRam: string;
|
memoryCache: string;
|
||||||
totalStorage: string;
|
diskSize: string;
|
||||||
usedStorage: string;
|
diskUsed: string;
|
||||||
freeStorage: string;
|
diskAvailable: string;
|
||||||
|
diskPercentage: string;
|
||||||
|
appName: string[];
|
||||||
|
caution: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const mockServersList: ServerData[] = [
|
export const mockServersList: ServerData[] = [
|
||||||
{
|
{
|
||||||
name: "Servidor Web 01",
|
name: "SESSÃO 7",
|
||||||
ip: "192.168.1.10",
|
ipAddress: "172.31.187.39",
|
||||||
totalRam: "16GB",
|
memoryTotal: "2.0Gi",
|
||||||
totalRamUsed: "8.5GB",
|
memoryUsed: "100Mi",
|
||||||
cachedRam: "2.1GB",
|
memoryFree: "953Mi",
|
||||||
freeRam: "7.5GB",
|
memoryCache: "993Mi",
|
||||||
totalStorage: "500GB",
|
diskSize: "20G",
|
||||||
usedStorage: "320GB",
|
diskUsed: "3.1G",
|
||||||
freeStorage: "180GB"
|
diskAvailable: "16G",
|
||||||
|
diskPercentage: "17%",
|
||||||
|
appName: [
|
||||||
|
"el_lojas",
|
||||||
|
"poc_colombia_test",
|
||||||
|
"polimix",
|
||||||
|
"same"
|
||||||
|
],
|
||||||
|
caution: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Servidor BD Principal",
|
name: "Servidor BD Principal",
|
||||||
ip: "192.168.1.11",
|
ipAddress: "192.168.1.11",
|
||||||
totalRam: "32GB",
|
memoryTotal: "32Gi",
|
||||||
totalRamUsed: "24.2GB",
|
memoryUsed: "24.2Gi",
|
||||||
cachedRam: "4.8GB",
|
memoryFree: "7.8Gi",
|
||||||
freeRam: "7.8GB",
|
memoryCache: "4.8Gi",
|
||||||
totalStorage: "1TB",
|
diskSize: "1T",
|
||||||
usedStorage: "750GB",
|
diskUsed: "750G",
|
||||||
freeStorage: "250GB"
|
diskAvailable: "250G",
|
||||||
|
diskPercentage: "75%",
|
||||||
|
appName: [
|
||||||
|
"database_primary",
|
||||||
|
"backup_service"
|
||||||
|
],
|
||||||
|
caution: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Servidor API",
|
name: "Servidor API",
|
||||||
ip: "192.168.1.12",
|
ipAddress: "192.168.1.12",
|
||||||
totalRam: "8GB",
|
memoryTotal: "8Gi",
|
||||||
totalRamUsed: "3.2GB",
|
memoryUsed: "3.2Gi",
|
||||||
cachedRam: "1.1GB",
|
memoryFree: "4.8Gi",
|
||||||
freeRam: "4.8GB",
|
memoryCache: "1.1Gi",
|
||||||
totalStorage: "250GB",
|
diskSize: "250G",
|
||||||
usedStorage: "120GB",
|
diskUsed: "120G",
|
||||||
freeStorage: "130GB"
|
diskAvailable: "130G",
|
||||||
|
diskPercentage: "48%",
|
||||||
|
appName: [
|
||||||
|
"api_gateway",
|
||||||
|
"auth_service"
|
||||||
|
],
|
||||||
|
caution: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Servidor Backup",
|
name: "Servidor Backup",
|
||||||
ip: "192.168.1.13",
|
ipAddress: "192.168.1.13",
|
||||||
totalRam: "16GB",
|
memoryTotal: "16Gi",
|
||||||
totalRamUsed: "5.1GB",
|
memoryUsed: "5.1Gi",
|
||||||
cachedRam: "2.5GB",
|
memoryFree: "10.9Gi",
|
||||||
freeRam: "10.9GB",
|
memoryCache: "2.5Gi",
|
||||||
totalStorage: "2TB",
|
diskSize: "2T",
|
||||||
usedStorage: "1.2TB",
|
diskUsed: "1.2T",
|
||||||
freeStorage: "800GB"
|
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",
|
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||||
|
|
||||||
/* Linting */
|
/* Linting */
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"erasableSyntaxOnly": true,
|
"erasableSyntaxOnly": true,
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"noUncheckedSideEffectImports": true
|
"noUncheckedSideEffectImports": true
|
||||||
},
|
},
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
"include": ["src/**/*.ts", "src/**/*.tsx"]
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue';
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
})
|
server: {
|
||||||
|
port: 3333,
|
||||||
|
host: '0.0.0.0',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
Loading…
Reference in New Issue