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