Files
nexus/frontend/src/views/DashboardView.vue
T
developer 6e532f64f5
CI - Build & Test / Backend (.NET) (push) Successful in 26s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 17s
CI - Build & Test / Security Check (push) Successful in 3s
fix: AgentModal bei Klick auf Card verdrahtet
- @select Handler auf TeamNetwork
- selectedAgent State + onAgentSelect()
- AgentModal importiert und gerendert (v-if selectedAgent)
- Close via X oder Overlay
2026-06-09 22:19:12 +02:00

213 lines
6.4 KiB
Vue

<script setup lang="ts">
import { onMounted, onUnmounted, ref } from 'vue'
import MissionCard from '../components/dashboard/MissionCard.vue'
import OperationsFeed from '../components/dashboard/OperationsFeed.vue'
import TeamNetwork from '../components/dashboard/TeamNetwork.vue'
import ChatPanel from '../components/dashboard/ChatPanel.vue'
import QueuePanel from '../components/dashboard/QueuePanel.vue'
import AgentModal from '../components/dashboard/AgentModal.vue'
import { useDashboardData } from '../composables/useDashboardData'
import type { AgentNodeData } from '../../composables/useDashboardData'
const {
agents, missions, feedEntries, chatMessages,
irisBusy, irisFocus, irisRuntime, queue,
getAgentRuntime, startRuntime, stopRuntime,
sendChat, removeQueueItem, moveQueueItem, changeQueuePriority,
} = useDashboardData()
const selectedAgent = ref<AgentNodeData | null>(null)
function onAgentSelect(id: string) {
const agent = agents.value.find(a => a.id === id)
if (agent) selectedAgent.value = agent
}
onMounted(startRuntime)
onUnmounted(stopRuntime)
function onChatSend(text: string): void { sendChat(text) }
function onQueueMoveUp(id: string): void {
const idx = queue.value.findIndex(q => q.id === id)
if (idx > 0) moveQueueItem(idx, idx - 1)
}
function onQueueMoveDown(id: string): void {
const idx = queue.value.findIndex(q => q.id === id)
if (idx < queue.value.length - 1) moveQueueItem(idx, idx + 1)
}
function onQueueExecuteNow(id: string): void {
const item = queue.value.find(q => q.id === id)
if (item) console.log('[Dashboard] Execute now:', item.text)
}
</script>
<template>
<div class="dashboard">
<div class="col-left">
<section class="missions-section">
<h2 class="column-title">Active Missions</h2>
<MissionCard v-for="m in missions" :key="m.id" :mission="m" />
</section>
<OperationsFeed :entries="feedEntries" />
</div>
<div class="col-center">
<!-- Quote Pill -->
<div class="quote-pill">
<span class="quote-text">"An autonomous organization of AI agents that does work for me and produces value 24/7"</span>
</div>
<!-- Header -->
<div class="team-header">
<h1 class="team-title">AI Team Network</h1>
<p class="team-subtitle">{{ agents.length }} AI agents, connected in real-time.</p>
<p class="team-description">Mission Control orchestriert ein Team spezialisierter Agenten jeder mit eigener Identität, eigenem Workspace und klaren Verantwortlichkeiten. Die Pulse zeigen aktive Kommunikationsflüsse.</p>
</div>
<TeamNetwork
:agents="agents"
:iris-runtime="irisRuntime"
:get-agent-runtime="getAgentRuntime"
:iris-focus="irisFocus"
@select="onAgentSelect"
/>
<!-- Legend -->
<div class="legend-row">
<div class="legend-item">
<span class="legend-dot active-pulse"></span>
<span>Aktive Verbindung</span>
</div>
<div class="legend-item">
<span class="legend-dot idle-pulse"></span>
<span>Idle</span>
</div>
<div class="legend-item">
<span class="legend-dot pulse-dot"></span>
<span>Datenfluss (Pulse)</span>
</div>
</div>
</div>
<div class="col-right">
<ChatPanel :messages="chatMessages" :iris-busy="irisBusy" :iris-focus="irisFocus" @send="onChatSend" />
<QueuePanel :items="queue" @remove="removeQueueItem" @move-up="onQueueMoveUp" @move-down="onQueueMoveDown" @change-priority="changeQueuePriority" @execute-now="onQueueExecuteNow" />
</div>
<AgentModal
v-if="selectedAgent"
:agent="selectedAgent"
:runtime="getAgentRuntime(selectedAgent.id)"
@close="selectedAgent = null"
/>
</div>
</template>
<style scoped>
.dashboard {
display: grid; grid-template-columns: 280px 1fr 320px; gap: 14px;
height: 100%; min-height: 0;
animation: fade-in 0.35s ease-out;
}
@keyframes fade-in {
from { opacity: 0; transform: translateY(8px); }
to { opacity: 1; transform: translateY(0); }
}
.dashboard ::-webkit-scrollbar { width: 5px; height: 5px; }
.dashboard ::-webkit-scrollbar-track { background: transparent; }
.dashboard ::-webkit-scrollbar-thumb { background: rgba(139,124,246,0.2); border-radius: 3px; }
.dashboard ::-webkit-scrollbar-thumb:hover { background: rgba(139,124,246,0.35); }
.col-left { display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-right: 4px; }
.col-center { overflow-y: auto; padding: 0 4px; min-height: 0; display: flex; flex-direction: column; gap: 12px; }
/* Quote Pill */
.quote-pill {
background: var(--panel);
border: 1px solid rgba(139, 124, 246, 0.25);
border-radius: 14px;
padding: 14px 22px;
box-shadow: 0 0 18px rgba(139, 124, 246, 0.06), inset 0 0 18px rgba(139, 124, 246, 0.03);
text-align: center;
}
.quote-text {
font-style: italic;
font-size: 12px;
color: #9ea5b3;
line-height: 1.5;
}
/* Team Header */
.team-header {
text-align: center;
}
.team-title {
font-size: 26px;
font-weight: 600;
color: #e8eaf0;
margin: 0 0 6px;
}
.team-subtitle {
font-size: 12px;
color: #7e8799;
margin: 0 0 4px;
}
.team-description {
font-size: 10.5px;
color: #6b7385;
margin: 0;
max-width: 560px;
margin-left: auto;
margin-right: auto;
}
/* Legend */
.legend-row {
display: flex;
align-items: center;
justify-content: center;
gap: 24px;
padding: 12px 20px;
background: var(--panel);
border: 1px solid var(--line);
border-radius: 10px;
}
.legend-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 10px;
color: #7e8799;
}
.legend-dot {
width: 8px;
height: 8px;
border-radius: 50%;
flex-shrink: 0;
}
.active-pulse {
background: #51d49a;
box-shadow: 0 0 6px rgba(81, 212, 154, 0.6);
}
.idle-pulse {
background: #3a3f4b;
}
.pulse-dot {
background: white;
width: 6px;
height: 6px;
animation: legend-pulse 1.5s ease-in-out infinite;
}
@keyframes legend-pulse {
0%, 100% { opacity: 0.3; transform: scale(0.8); }
50% { opacity: 1; transform: scale(1.2); }
}
.col-right { display: flex; flex-direction: column; gap: 12px; overflow-y: auto; padding-left: 4px; }
.missions-section { display: flex; flex-direction: column; gap: 8px; }
.column-title { margin: 0; font-size: 13px; font-weight: 600; color: #e8eaf0; letter-spacing: 0.01em; }
@media (max-width: 1100px) {
.dashboard { grid-template-columns: 1fr; }
.col-left, .col-center, .col-right { overflow: visible; padding: 0; }
}
</style>