Files
nexus/frontend/src/App.vue
T
developer 5244e9fd3d
CI - Build & Test / Backend (.NET) (push) Successful in 31s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 17s
CI - Build & Test / Security Check (push) Successful in 3s
feat: AI Team Network ins Dashboard integriert, Vollbreite, dynamisches Grid
- TeamNetwork ins Dashboard verschoben (center column)
- 3-Spalten-Layout auf volle Desktop-Breite (kein max-width)
- Agent-Grid dynamisch: 2 Spalten, erweitert nach unten (4→6→8 Agenten)
- SVG-Bézier-Linien mit ResizeObserver passen sich an
- 'Team' aus Navigation, Router und standaloneViews entfernt
- /team Route gelöscht
2026-06-09 21:49:10 +02:00

169 lines
4.4 KiB
Vue

<script setup lang="ts">
import { computed, onMounted, ref } from 'vue'
import { Activity } from '@lucide/vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import { useOperationsStore } from './stores/operations'
import { useAuthStore } from './stores/auth'
import AppSidebar from './components/layout/AppSidebar.vue'
import AppHeader from './components/layout/AppHeader.vue'
import ModuleView from './components/ModuleView.vue'
const store = useOperationsStore()
const auth = useAuthStore()
const route = useRoute()
const router = useRouter()
const activeView = computed(() => {
if (route.name === 'Settings') return 'Settings'
if (route.name === 'ProjectDetail') return 'ProjectDetail'
return String(route.name ?? 'Dashboard')
})
const routePaths: Record<string, string> = {
Dashboard: '/dashboard', Memory: '/memory', Docs: '/docs', Security: '/security',
Projects: '/projects', 'Task Board': '/tasks', Incidents: '/incidents', Calendar: '/calendar',
Agents: '/agents', Models: '/models', Activity: '/activity', 'Mobile Chat': '/chat', Settings: '/settings',
}
const navigate = (label: string) => {
mobileNavOpen.value = false
return router.push(routePaths[label] ?? '/dashboard')
}
const mobileNavOpen = ref(false)
const standaloneViews = computed(() => ['Dashboard', 'Settings', 'ProjectDetail', 'Memory', 'Docs', 'Security', 'Incidents', 'Calendar', 'AgentDetail', 'Agents'].includes(activeView.value))
onMounted(() => {
if (auth.isAuthenticated) store.refresh()
})
</script>
<template>
<RouterView v-if="route.name === 'Login'" />
<div v-else class="shell">
<AppSidebar
:active-view="activeView"
:mobile-nav-open="mobileNavOpen"
:queued-tasks="store.snapshot.metrics.queuedTasks"
:incidents="store.snapshot.metrics.incidents"
@navigate="navigate"
/>
<main>
<AppHeader
:connected="store.connected"
@toggle-mobile-nav="mobileNavOpen = !mobileNavOpen"
/>
<section class="content">
<RouterView v-if="standaloneViews" />
<template v-else>
<div class="page-heading">
<div>
<span class="eyebrow">MISSION CONTROL</span>
<h1>{{ activeView }}</h1>
<p>System overview and operational intelligence across Noveria.</p>
</div>
<button class="refresh" @click="store.refresh()">
<Activity :size="15" :class="{ spin: store.loading }" />
Refresh
</button>
</div>
<ModuleView
:view="activeView"
:snapshot="store.snapshot"
:routing="store.routing"
@create-project="store.createProject"
@create-task="store.createTask"
@update-task-state="store.updateTaskState"
/>
</template>
</section>
</main>
</div>
</template>
<style scoped>
:root {
--bg: #0b0d13;
--panel: #11141b;
--line: #1f2330;
--accent: #7b6ef2;
--accent-soft: rgba(123,110,242,.08);
--text: #e8eaf0;
--text-dim: #6f7889;
--green: #27ae60;
--red: #e74c3c;
--yellow: #f1c40f;
--orange: #e67e22;
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { font-size: 15px; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: var(--bg);
color: var(--text);
-webkit-font-smoothing: antialiased;
}
.shell {
display: flex;
height: 100vh;
overflow: hidden;
}
main {
flex: 1;
display: flex;
flex-direction: column;
min-width: 0;
}
.content {
flex: 1;
overflow-y: auto;
padding: 20px;
}
.page-heading {
display: flex;
align-items: flex-start;
justify-content: space-between;
margin-bottom: 20px;
gap: 12px;
}
.page-heading h1 { margin: 0; font-size: 18px; }
.page-heading p { margin: 4px 0 0; font-size: 10px; color: var(--text-dim); }
.eyebrow {
font-size: 8.5px;
font-weight: 700;
letter-spacing: .12em;
color: var(--accent);
text-transform: uppercase;
}
.refresh {
display: flex;
align-items: center;
gap: 5px;
flex-shrink: 0;
padding: 6px 11px;
border: 1px solid var(--line);
border-radius: 6px;
background: transparent;
color: var(--text-dim);
font-size: 9px;
cursor: pointer;
transition: background .15s;
}
.refresh:hover { background: var(--accent-soft); color: #d8dbe3; }
.spin { animation: spin 1s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }
@media (max-width: 860px) {
.kanban { grid-template-columns: 1fr; }
}
</style>