-
Operations Feed
-
-
-
+
-
- {{ group.date }}
-
-
- {{ item.time }}
-
- {{ item.text }}
-
-
-
+
+
+ {{ entry.time }}
+ ·
+
+ {{ entry.agent }}
+
+ {{ entry.action }}
+
+
+
+
+ No operations recorded yet.
+
@@ -101,134 +41,115 @@ const statusColor = (s: FeedStatus): string => {
.feed-panel {
display: flex;
flex-direction: column;
- gap: 8px;
- min-height: 420px;
- padding: 18px;
- background: rgba(22, 27, 34, 0.8);
- border: 1px solid rgba(139, 124, 246, 0.12);
- border-radius: 16px;
- box-shadow: 0 4px 24px rgba(0,0,0,0.3);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
- transition: all 0.2s ease;
+ gap: 10px;
+ padding: 14px;
+ background: rgba(22, 27, 34, 0.65);
+ border: 1px solid rgba(139, 124, 246, 0.08);
+ border-radius: 14px;
+ transition: border-color 0.2s ease;
+ backdrop-filter: blur(6px);
+ -webkit-backdrop-filter: blur(6px);
}
.feed-panel:hover {
- border-color: rgba(139, 124, 246, 0.18);
+ border-color: rgba(139, 124, 246, 0.15);
}
-.feed-title {
- font-size: 14px;
- font-weight: 600;
+
+.feed-header {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+.feed-icon {
+ color: #a78bfa;
+}
+.feed-header h2 {
margin: 0;
+ font-size: 11px;
+ font-weight: 600;
color: #e8eaf0;
}
-/* Filter pills */
-.filter-pills {
- display: flex;
- gap: 4px;
- overflow-x: auto;
- scrollbar-width: none;
- -ms-overflow-style: none;
- padding-bottom: 2px;
-}
-.filter-pills::-webkit-scrollbar {
- display: none;
-}
-.filter-pills button {
- flex-shrink: 0;
- padding: 4px 10px;
- border: 1px solid rgba(139, 124, 246, 0.08);
- border-radius: 20px;
- background: transparent;
- color: #6b7385;
- font-size: 9px;
- font-weight: 600;
- cursor: pointer;
- transition: all 0.2s ease;
- white-space: nowrap;
-}
-.filter-pills button:hover {
- border-color: rgba(139, 124, 246, 0.25);
- color: #7e8799;
-}
-.filter-pills button.active {
- background: rgba(139, 124, 246, 0.12);
- border-color: rgba(139, 124, 246, 0.25);
- color: #a78bfa;
-}
-
-/* Feed list */
.feed-list {
display: flex;
flex-direction: column;
gap: 2px;
- overflow-y: auto;
- flex: 1;
+ position: relative;
}
-.feed-group-items {
- display: contents;
-}
-.feed-date-heading {
- font-size: 9px;
- font-weight: 700;
- color: #6b7385;
- text-transform: uppercase;
- letter-spacing: 0.08em;
- padding: 8px 0 4px;
-}
-.feed-item {
+
+.feed-entry {
display: flex;
align-items: center;
- gap: 8px;
+ gap: 5px;
padding: 5px 6px;
border-radius: 6px;
+ font-size: 9.5px;
+ line-height: 1.3;
transition: background 0.15s;
}
-.feed-item:hover {
- background: rgba(139, 124, 246, 0.04);
+.feed-entry:hover {
+ background: rgba(255, 255, 255, 0.03);
}
+
.feed-time {
- font-size: 9px;
color: #6b7385;
flex-shrink: 0;
- width: 36px;
font-variant-numeric: tabular-nums;
+ width: 32px;
}
-.feed-dot {
- width: 7px;
- height: 7px;
- border-radius: 50%;
+.feed-bullet {
+ color: #6b7385;
flex-shrink: 0;
- box-shadow: 0 0 4px currentColor;
}
-.feed-text {
- font-size: 10.5px;
- line-height: 1.3;
+.feed-agent {
+ font-weight: 600;
+ flex-shrink: 0;
+}
+.agent-iris {
+ color: #a78bfa;
+}
+.agent-developer {
+ color: #3b82f6;
+}
+.agent-devops {
+ color: #eab308;
+}
+.agent-researcher {
+ color: #22c55e;
+}
+.agent-reviewer {
+ color: #a855f7;
+}
+.feed-action {
color: #7e8799;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
-/* TransitionGroup animations */
-.feed-item-enter-active {
- transition: all 0.3s ease;
-}
-.feed-item-leave-active {
- transition: all 0.3s ease;
-}
-.feed-item-enter-from {
- opacity: 0;
- transform: translateX(-12px);
-}
-.feed-item-leave-to {
- opacity: 0;
- transform: translateX(12px);
-}
-.feed-item-move {
- transition: all 0.3s ease;
+.feed-empty {
+ text-align: center;
+ padding: 12px 0;
+ font-size: 10px;
+ color: #6b7385;
}
-@media (max-width: 900px) {
- .feed-panel {
- order: 2;
- }
+/* TransitionGroup */
+.feed-enter-active {
+ transition: all 0.3s ease;
+}
+.feed-leave-active {
+ transition: all 0.3s ease;
+ position: absolute;
+}
+.feed-enter-from {
+ opacity: 0;
+ transform: translateX(-10px);
+}
+.feed-leave-to {
+ opacity: 0;
+ transform: translateX(10px);
+}
+.feed-move {
+ transition: transform 0.3s ease;
}
diff --git a/frontend/src/components/dashboard/QueuePanel.vue b/frontend/src/components/dashboard/QueuePanel.vue
new file mode 100644
index 0000000..d5dba3e
--- /dev/null
+++ b/frontend/src/components/dashboard/QueuePanel.vue
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item.priority }}
+
+ {{ item.waitTime }}
+
+
{{ item.text }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/components/dashboard/TeamNetwork.vue b/frontend/src/components/dashboard/TeamNetwork.vue
new file mode 100644
index 0000000..adfdd2a
--- /dev/null
+++ b/frontend/src/components/dashboard/TeamNetwork.vue
@@ -0,0 +1,345 @@
+
+
+
+
+
+
+
+
+
+
Iris
+ Chief of Staff
+
+
+
Breaking down tasks and coordinating specialists
+
+
+ Session Runtime
+ {{ irisRuntime }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ irisFocus }}
+
+
+
+
+
+
+
+
diff --git a/frontend/src/composables/useDashboardData.ts b/frontend/src/composables/useDashboardData.ts
new file mode 100644
index 0000000..49095d1
--- /dev/null
+++ b/frontend/src/composables/useDashboardData.ts
@@ -0,0 +1,297 @@
+import { ref, reactive, computed } from 'vue'
+
+export interface AgentNodeData {
+ id: string
+ name: string
+ role: string
+ description: string
+ color: string
+ icon: string
+ currentTask: string
+ goal: string
+ progress: number
+ workload: number // 0-100
+ active: boolean
+ runtimeSeconds: number
+ workingFeed: string[]
+}
+
+export interface MissionData {
+ id: string
+ name: string
+ progress: number
+ currentTask: string
+ lastActivity: string
+ remainingTasks: number
+ status: 'healthy' | 'attention' | 'blocked' | 'paused'
+}
+
+export interface FeedEntry {
+ time: string
+ agent: string
+ action: string
+ timestamp: number
+}
+
+export interface ChatMessage {
+ id: string
+ sender: 'user' | 'iris'
+ text: string
+ timestamp: number
+}
+
+export interface QueueItem {
+ id: string
+ text: string
+ priority: 'high' | 'medium' | 'low'
+ waitTime: string
+}
+
+const now = Date.now()
+
+export function useDashboardData() {
+ const sessionStart = Date.now()
+
+ // Runtime counter
+ const runtimeSeconds = ref(0)
+ let runtimeInterval: ReturnType
| null = null
+
+ function startRuntime() {
+ const startTs = sessionStart
+ runtimeSeconds.value = Math.floor((Date.now() - startTs) / 1000)
+ runtimeInterval = setInterval(() => {
+ runtimeSeconds.value = Math.floor((Date.now() - startTs) / 1000)
+ }, 1000)
+ }
+
+ function stopRuntime() {
+ if (runtimeInterval) {
+ clearInterval(runtimeInterval)
+ runtimeInterval = null
+ }
+ }
+
+ const formatRuntime = (seconds: number): string => {
+ const m = Math.floor(seconds / 60)
+ const s = seconds % 60
+ return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
+ }
+
+ const irisRuntime = computed(() => formatRuntime(runtimeSeconds.value))
+
+ // Agent runtimes (simulated)
+ const agentStartTimes = reactive>({
+ developer: now - 3600000,
+ devops: now - 1800000,
+ researcher: now - 2700000,
+ reviewer: now - 900000,
+ })
+
+ const getAgentRuntime = (id: string): string => {
+ const start = agentStartTimes[id]
+ if (!start) return '00:00'
+ const secs = Math.floor((now - start) / 1000)
+ const m = Math.floor(secs / 60)
+ const s = secs % 60
+ return `${String(m).padStart(2, '0')}:${String(s).padStart(2, '0')}`
+ }
+
+ // Agents
+ const agents = ref([
+ {
+ id: 'developer',
+ name: 'Developer',
+ role: 'Backend & Frontend',
+ description: 'Implements features across the stack with TypeScript, C#, and Vue.',
+ color: '#3b82f6',
+ icon: 'code',
+ currentTask: 'Building Dungeon System API endpoints',
+ goal: 'Complete Dungeon CRUD + room generation',
+ progress: 62,
+ workload: 65,
+ active: true,
+ runtimeSeconds: 3600,
+ workingFeed: [
+ 'Created DungeonController',
+ 'Defined dungeon schema',
+ 'Implementing room generation algorithm',
+ 'Writing unit tests for RoomFactory',
+ ],
+ },
+ {
+ id: 'devops',
+ name: 'DevOps',
+ role: 'Infrastructure & CI/CD',
+ description: 'Manages Docker, deployment pipelines, and system reliability.',
+ color: '#eab308',
+ icon: 'server',
+ currentTask: 'Optimizing Docker Compose caching',
+ goal: 'Reduce build times by 40%',
+ progress: 45,
+ workload: 40,
+ active: false,
+ runtimeSeconds: 1800,
+ workingFeed: [
+ 'Analyzed Docker layer cache',
+ 'Optimized COPY order in Dockerfile',
+ 'Added .dockerignore for node_modules',
+ 'Testing incremental builds',
+ ],
+ },
+ {
+ id: 'researcher',
+ name: 'Researcher',
+ role: 'Analysis & Documentation',
+ description: 'Researches APIs, patterns, and best practices. Maintains docs.',
+ color: '#22c55e',
+ icon: 'search',
+ currentTask: 'Analyzing WebSocket alternatives',
+ goal: 'Recommend real-time communication strategy',
+ progress: 30,
+ workload: 25,
+ active: true,
+ runtimeSeconds: 2700,
+ workingFeed: [
+ 'Evaluated WebSocket vs SSE vs WebRTC',
+ 'Documented SignalR limitations',
+ 'Prototyping WebSocket fallback',
+ ],
+ },
+ {
+ id: 'reviewer',
+ name: 'Reviewer',
+ role: 'Code Quality & Testing',
+ description: 'Reviews pull requests, enforces standards, runs test suites.',
+ color: '#a855f7',
+ icon: 'shield',
+ currentTask: 'Reviewing Dungeon System PR',
+ goal: 'Zero critical findings before merge',
+ progress: 80,
+ workload: 50,
+ active: false,
+ runtimeSeconds: 900,
+ workingFeed: [
+ 'Reviewed DungeonController.cs',
+ 'Found 3 minor style issues',
+ 'Approved RoomValidator',
+ 'Running integration tests',
+ ],
+ },
+ ])
+
+ // Missions
+ const missions = ref([
+ {
+ id: 'dungeon-system',
+ name: 'Dungeon System',
+ progress: 62,
+ currentTask: 'Implement room generation',
+ lastActivity: '3 min ago',
+ remainingTasks: 8,
+ status: 'healthy',
+ },
+ {
+ id: 'dashboard-redesign',
+ name: 'Dashboard Redesign',
+ progress: 45,
+ currentTask: 'AI Team Network layout',
+ lastActivity: 'Just now',
+ remainingTasks: 6,
+ status: 'healthy',
+ },
+ {
+ id: 'infra-optimization',
+ name: 'Infra Optimization',
+ progress: 30,
+ currentTask: 'Optimize build caching',
+ lastActivity: '12 min ago',
+ remainingTasks: 4,
+ status: 'attention',
+ },
+ {
+ id: 'auth-system',
+ name: 'Auth System',
+ progress: 88,
+ currentTask: 'Finalize refresh token flow',
+ lastActivity: '45 min ago',
+ remainingTasks: 2,
+ status: 'healthy',
+ },
+ ])
+
+ // Feed
+ const feedEntries = ref([
+ { time: '20:42', agent: 'Developer', action: 'Created DungeonController endpoints', timestamp: now - 60000 },
+ { time: '20:38', agent: 'DevOps', action: 'Optimized Docker COPY order', timestamp: now - 300000 },
+ { time: '20:35', agent: 'Iris', action: 'Delegated room generation to Developer', timestamp: now - 540000 },
+ { time: '20:28', agent: 'Researcher', action: 'Documented WebSocket vs SSE analysis', timestamp: now - 780000 },
+ { time: '20:22', agent: 'Reviewer', action: 'Approved RoomValidator PR', timestamp: now - 900000 },
+ { time: '20:15', agent: 'DevOps', action: 'Added .dockerignore for node_modules', timestamp: now - 1200000 },
+ { time: '20:08', agent: 'Iris', action: 'Broke down Dungeon System tasks', timestamp: now - 1500000 },
+ { time: '19:55', agent: 'Developer', action: 'Defined dungeon schema models', timestamp: now - 1800000 },
+ ])
+
+ // Chat
+ const chatMessages = ref([
+ { id: 'm1', sender: 'iris', text: 'Guten Abend, Bao. Ready to continue the Dungeon System?', timestamp: now - 600000 },
+ { id: 'm2', sender: 'user', text: "Yes, what's the status?", timestamp: now - 540000 },
+ { id: 'm3', sender: 'iris', text: "Developer is at 62% on room generation. Reviewer approved the schema. I'd recommend focusing on the room connection logic next.", timestamp: now - 480000 },
+ ])
+
+ const irisBusy = ref(true)
+ const irisFocus = ref('Breaking down Dungeon System for DevOps and Developer')
+
+ // Queue
+ const queue = ref([
+ { id: 'q1', text: 'Deploy latest dashboard build to preview', priority: 'high', waitTime: '2 min' },
+ { id: 'q2', text: 'Review infrastructure cost analysis', priority: 'medium', waitTime: '8 min' },
+ { id: 'q3', text: 'Schedule B2 German lesson review', priority: 'low', waitTime: '15 min' },
+ { id: 'q4', text: 'Update project roadmap document', priority: 'medium', waitTime: '12 min' },
+ ])
+
+ function sendChat(text: string): void {
+ if (!text.trim()) return
+ chatMessages.value.push({
+ id: `user-${Date.now()}`,
+ sender: 'user',
+ text: text.trim(),
+ timestamp: Date.now(),
+ })
+ }
+
+ function removeQueueItem(id: string): void {
+ const idx = queue.value.findIndex(q => q.id === id)
+ if (idx !== -1) queue.value.splice(idx, 1)
+ }
+
+ function moveQueueItem(fromIdx: number, toIdx: number): void {
+ if (toIdx < 0 || toIdx >= queue.value.length) return
+ const [item] = queue.value.splice(fromIdx, 1)
+ queue.value.splice(toIdx, 0, item)
+ }
+
+ function changeQueuePriority(id: string, priority: QueueItem['priority']): void {
+ const item = queue.value.find(q => q.id === id)
+ if (item) item.priority = priority
+ }
+
+ return {
+ agents,
+ missions,
+ feedEntries,
+ chatMessages,
+ irisBusy,
+ irisFocus,
+ irisRuntime,
+ queue,
+ runtimeSeconds,
+ getAgentRuntime,
+ startRuntime,
+ stopRuntime,
+ formatRuntime,
+ sendChat,
+ removeQueueItem,
+ moveQueueItem,
+ changeQueuePriority,
+ }
+}
diff --git a/frontend/src/composables/useTimer.ts b/frontend/src/composables/useTimer.ts
new file mode 100644
index 0000000..f0c1da0
--- /dev/null
+++ b/frontend/src/composables/useTimer.ts
@@ -0,0 +1,37 @@
+import { ref, onMounted, onUnmounted } from 'vue'
+
+export function useTimer() {
+ const elapsed = ref(0)
+ let interval: ReturnType | null = null
+
+ function start() {
+ if (interval !== null) return
+ const startTime = Date.now()
+ elapsed.value = 0
+ interval = setInterval(() => {
+ elapsed.value = Math.floor((Date.now() - startTime) / 1000)
+ }, 1000)
+ }
+
+ function stop() {
+ if (interval !== null) {
+ clearInterval(interval)
+ interval = null
+ }
+ }
+
+ function format(minutes: number, seconds: number): string {
+ return `${String(minutes).padStart(2, '0')}:${String(seconds).padStart(2, '0')}`
+ }
+
+ const formatted = (): string => {
+ const m = Math.floor(elapsed.value / 60)
+ const s = elapsed.value % 60
+ return format(m, s)
+ }
+
+ onMounted(start)
+ onUnmounted(stop)
+
+ return { elapsed, formatted, start, stop }
+}
diff --git a/frontend/src/views/DashboardView.vue b/frontend/src/views/DashboardView.vue
index 933681f..ef62ecd 100644
--- a/frontend/src/views/DashboardView.vue
+++ b/frontend/src/views/DashboardView.vue
@@ -1,116 +1,85 @@
-
-
-
MISSION CONTROL
-
Übersicht
+
+
+
-
-
-
-
-
-
+
+
+
+
+
+
-
-
-
-