303 lines
7.6 KiB
Vue
303 lines
7.6 KiB
Vue
<script setup lang="ts">
|
|
import { ref, onMounted, onUnmounted, watch } from 'vue'
|
|
import { useRouter, useRoute } from 'vue-router'
|
|
import { useAuthStore } from '../stores/auth'
|
|
import { useWebSocketStore } from '../stores/websocket'
|
|
import Popover from 'primevue/popover'
|
|
import WsStatsPanel from '../components/WsStatsPanel.vue'
|
|
|
|
const router = useRouter()
|
|
const route = useRoute()
|
|
const authStore = useAuthStore()
|
|
const wsStore = useWebSocketStore()
|
|
const wsPopover = ref()
|
|
const sidebarOpen = ref(false)
|
|
|
|
// Close sidebar on route change (mobile)
|
|
watch(() => route.path, () => {
|
|
sidebarOpen.value = false
|
|
})
|
|
|
|
const feVersion = import.meta.env.VITE_APP_VERSION || 'dev'
|
|
const beVersion = ref('...')
|
|
|
|
onMounted(async () => {
|
|
try {
|
|
const apiBase = import.meta.env.VITE_API_URL || 'http://localhost:8000/api'
|
|
const res = await fetch(`${apiBase}/version`)
|
|
const data = await res.json()
|
|
beVersion.value = data.version || 'unknown'
|
|
} catch {
|
|
beVersion.value = '?'
|
|
}
|
|
|
|
wsStore.connect()
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
wsStore.disconnect()
|
|
})
|
|
|
|
function handleLogout() {
|
|
wsStore.disconnect()
|
|
authStore.logout()
|
|
router.push('/login')
|
|
}
|
|
|
|
function toggleWsStats(event: Event) {
|
|
wsPopover.value?.toggle(event)
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="app-layout">
|
|
<!-- Mobile Hamburger -->
|
|
<button class="hamburger" @click="sidebarOpen = !sidebarOpen" :class="{ open: sidebarOpen }">
|
|
<i :class="sidebarOpen ? 'pi pi-times' : 'pi pi-bars'"></i>
|
|
</button>
|
|
|
|
<!-- Overlay -->
|
|
<div v-if="sidebarOpen" class="sidebar-overlay" @click="sidebarOpen = false"></div>
|
|
|
|
<aside class="sidebar" :class="{ open: sidebarOpen }">
|
|
<div class="sidebar-header">
|
|
<h2>🤖 AMS</h2>
|
|
</div>
|
|
|
|
<nav class="sidebar-nav-container">
|
|
<ul class="sidebar-nav">
|
|
<li>
|
|
<router-link to="/">
|
|
<i class="pi pi-home"></i>
|
|
Dashboard
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/agents">
|
|
<i class="pi pi-users"></i>
|
|
Agents
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/projects">
|
|
<i class="pi pi-folder"></i>
|
|
Projekte
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/tasks">
|
|
<i class="pi pi-list-check"></i>
|
|
Tasks
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/logs">
|
|
<i class="pi pi-file"></i>
|
|
Logs
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/labels">
|
|
<i class="pi pi-tags"></i>
|
|
Labels
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/gitlab">
|
|
<i class="pi pi-github"></i>
|
|
GitLab
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/agent-task">
|
|
<i class="pi pi-send"></i>
|
|
Aufgabe stellen
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/agent-tasks-overview">
|
|
<i class="pi pi-list-check"></i>
|
|
Agent-Aufträge
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/backup">
|
|
<i class="pi pi-download"></i>
|
|
Backup
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/cronjobs">
|
|
<i class="pi pi-clock"></i>
|
|
CronJobs
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/workspace">
|
|
<i class="pi pi-folder-open"></i>
|
|
Agent-Dateien
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/secrets">
|
|
<i class="pi pi-lock"></i>
|
|
Secrets
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/token-analytics">
|
|
<i class="pi pi-chart-line"></i>
|
|
Token Analytics
|
|
</router-link>
|
|
</li>
|
|
<li v-if="authStore.user?.role === 'admin'">
|
|
<router-link to="/containers">
|
|
<i class="pi pi-box"></i>
|
|
Container
|
|
</router-link>
|
|
</li>
|
|
<li>
|
|
<router-link to="/settings">
|
|
<i class="pi pi-cog"></i>
|
|
Einstellungen
|
|
</router-link>
|
|
</li>
|
|
</ul>
|
|
</nav>
|
|
|
|
<div class="sidebar-footer">
|
|
<div style="color: rgba(255,255,255,0.5); font-size: 0.875rem; margin-bottom: 0.5rem;">
|
|
{{ authStore.user?.username }}
|
|
</div>
|
|
<button
|
|
@click="handleLogout"
|
|
style="background: none; border: 1px solid rgba(255,255,255,0.2); color: rgba(255,255,255,0.7); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; width: 100%;"
|
|
>
|
|
<i class="pi pi-sign-out" style="margin-right: 0.5rem;"></i>
|
|
Abmelden
|
|
</button>
|
|
<div class="sidebar-version">
|
|
<span class="ws-indicator" :class="{ online: wsStore.isConnected }" :title="wsStore.isConnected ? 'WebSocket verbunden' : 'WebSocket getrennt'" @click="toggleWsStats" style="cursor: pointer;">●</span>
|
|
FE {{ feVersion }} | BE {{ beVersion }}
|
|
</div>
|
|
<Popover ref="wsPopover">
|
|
<WsStatsPanel />
|
|
</Popover>
|
|
</div>
|
|
</aside>
|
|
|
|
<main class="main-content">
|
|
<router-view />
|
|
</main>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.hamburger {
|
|
display: none;
|
|
position: fixed;
|
|
top: 0.75rem;
|
|
left: 0.75rem;
|
|
z-index: 1100;
|
|
width: 40px;
|
|
height: 40px;
|
|
border-radius: 8px;
|
|
border: 1px solid rgba(255, 255, 255, 0.15);
|
|
background: rgba(26, 26, 46, 0.95);
|
|
color: rgba(255, 255, 255, 0.8);
|
|
font-size: 1.1rem;
|
|
cursor: pointer;
|
|
align-items: center;
|
|
justify-content: center;
|
|
backdrop-filter: blur(8px);
|
|
transition: all 0.2s;
|
|
}
|
|
|
|
.hamburger:hover {
|
|
background: rgba(255, 255, 255, 0.1);
|
|
color: #fff;
|
|
}
|
|
|
|
.sidebar-overlay {
|
|
display: none;
|
|
}
|
|
|
|
.sidebar {
|
|
display: flex;
|
|
flex-direction: column;
|
|
transition: transform 0.3s ease;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.hamburger {
|
|
display: flex;
|
|
}
|
|
|
|
.sidebar-overlay {
|
|
display: block;
|
|
position: fixed;
|
|
inset: 0;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 999;
|
|
backdrop-filter: blur(2px);
|
|
}
|
|
|
|
.sidebar {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
bottom: 0;
|
|
z-index: 1000;
|
|
transform: translateX(-100%);
|
|
width: 220px;
|
|
background: #0f0f1e;
|
|
}
|
|
|
|
.sidebar.open {
|
|
transform: translateX(0);
|
|
}
|
|
|
|
.main-content {
|
|
margin-left: 0 !important;
|
|
padding-top: 3.5rem !important;
|
|
}
|
|
}
|
|
|
|
.sidebar-version {
|
|
text-align: center;
|
|
margin-top: 0.75rem;
|
|
color: rgba(255, 255, 255, 0.25);
|
|
font-size: 0.65rem;
|
|
letter-spacing: 0.025em;
|
|
}
|
|
|
|
.ws-indicator {
|
|
color: rgba(239, 68, 68, 0.6);
|
|
font-size: 0.5rem;
|
|
vertical-align: middle;
|
|
margin-right: 0.2rem;
|
|
}
|
|
|
|
.ws-indicator.online {
|
|
color: rgba(34, 197, 94, 0.8);
|
|
}
|
|
|
|
/* Popover Dark Theme */
|
|
:deep(.p-popover) {
|
|
background: #1a1a2e !important;
|
|
border: 1px solid rgba(255, 255, 255, 0.15) !important;
|
|
color: #fff !important;
|
|
}
|
|
|
|
:deep(.p-popover-content) {
|
|
background: #1a1a2e !important;
|
|
color: #fff !important;
|
|
}
|
|
|
|
:deep(.p-popover)::before,
|
|
:deep(.p-popover)::after {
|
|
border-color: transparent !important;
|
|
border-bottom-color: #1a1a2e !important;
|
|
}
|
|
</style>
|