Initial commit: Nexus Mission Control Platform
- ASP.NET Core 10 Backend (JWT Auth, Agent config API) - Vue 3 Frontend (Dashboard, Team, Agents, Config Editor) - PostgreSQL Database - Docker Compose setup - Mission Control Dashboard redesign
This commit is contained in:
@@ -0,0 +1,210 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
|
||||
type FeedStatus = 'running' | 'done' | 'waiting' | 'error' | 'info'
|
||||
|
||||
interface FeedItem {
|
||||
time: string
|
||||
status: FeedStatus
|
||||
text: string
|
||||
label: string
|
||||
date: 'today' | 'yesterday' | 'week'
|
||||
}
|
||||
|
||||
const allFeed: FeedItem[] = [
|
||||
{ time: '09:17', status: 'running', text: 'OpenClaw analysiert Memory-Datenbank.', label: 'Memory', date: 'today' },
|
||||
{ time: '09:19', status: 'done', text: 'Repository Refactoring abgeschlossen.', label: 'Coding', date: 'today' },
|
||||
{ time: '09:21', status: 'done', text: '3 neue Erinnerungen gespeichert.', label: 'Memory', date: 'today' },
|
||||
{ time: '09:25', status: 'done', text: 'Dungeon-Service erfolgreich kompiliert.', label: 'Coding', date: 'today' },
|
||||
{ time: '09:28', status: 'error', text: 'Build fehlgeschlagen — NullReferenceException in EnemyFactory.', label: 'Coding', date: 'today' },
|
||||
{ time: '09:31', status: 'waiting', text: 'Iris hat "Steuerunterlagen" auf Freitag verschoben.', label: 'Personal', date: 'today' },
|
||||
{ time: '10:02', status: 'running', text: 'Programmer arbeitet an TeamView-Redesign.', label: 'Coding', date: 'today' },
|
||||
{ time: '10:15', status: 'done', text: 'AgentDetailView deployed.', label: 'System', date: 'today' },
|
||||
{ time: '10:22', status: 'running', text: 'Architekt prüft Compose-Konfiguration.', label: 'System', date: 'today' },
|
||||
{ time: '10:45', status: 'done', text: 'Reviewer: Code-Review abgeschlossen, keine Findings.', label: 'Agenten', date: 'today' },
|
||||
{ time: '11:00', status: 'running', text: 'Researcher analysiert API-Dokumentation.', label: 'Research', date: 'today' },
|
||||
{ time: '11:30', status: 'waiting', text: 'Executor wartet auf Deployment-Freigabe.', label: 'System', date: 'today' },
|
||||
{ time: '15:22', status: 'done', text: 'Nexus Dashboard Migration geplant.', label: 'Coding', date: 'yesterday' },
|
||||
{ time: '16:05', status: 'done', text: 'Docker Compose Optimierung abgeschlossen.', label: 'System', date: 'yesterday' },
|
||||
]
|
||||
|
||||
const feedFilter = ref<string | null>(null)
|
||||
const filterLabels = ['Alle', 'Coding', 'Research', 'Personal', 'Memory', 'Agenten', 'System']
|
||||
|
||||
const filteredFeed = computed(() => {
|
||||
if (!feedFilter.value || feedFilter.value === 'Alle') return allFeed
|
||||
return allFeed.filter(item => item.label === feedFilter.value)
|
||||
})
|
||||
|
||||
const feedGroups = computed(() => {
|
||||
const groups: { date: string; items: FeedItem[] }[] = []
|
||||
const dates = ['today', 'yesterday', 'week'] as const
|
||||
for (const d of dates) {
|
||||
const items = filteredFeed.value.filter(i => i.date === d)
|
||||
if (items.length) {
|
||||
groups.push({
|
||||
date: d === 'today' ? 'Heute' : d === 'yesterday' ? 'Gestern' : 'Diese Woche',
|
||||
items,
|
||||
})
|
||||
}
|
||||
}
|
||||
return groups
|
||||
})
|
||||
|
||||
const statusColor = (s: FeedStatus): string => {
|
||||
const m: Record<FeedStatus, string> = {
|
||||
running: '#3b82f6',
|
||||
done: '#22c55e',
|
||||
waiting: '#eab308',
|
||||
error: '#ef4444',
|
||||
info: '#6b7385',
|
||||
}
|
||||
return m[s]
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="feed-panel">
|
||||
<h2 class="feed-title">Operations Feed</h2>
|
||||
|
||||
<div class="filter-pills">
|
||||
<button
|
||||
v-for="label in filterLabels"
|
||||
:key="label"
|
||||
:class="{ active: feedFilter === label || (!feedFilter && label === 'Alle') }"
|
||||
@click="feedFilter = label === 'Alle' ? null : label"
|
||||
>
|
||||
{{ label }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="feed-list">
|
||||
<template v-for="group in feedGroups" :key="group.date">
|
||||
<div class="feed-date-heading">{{ group.date }}</div>
|
||||
<div
|
||||
v-for="(item, idx) in group.items"
|
||||
:key="idx"
|
||||
class="feed-item"
|
||||
>
|
||||
<span class="feed-time">{{ item.time }}</span>
|
||||
<span class="feed-dot" :style="{ background: statusColor(item.status) }"></span>
|
||||
<span class="feed-text">{{ item.text }}</span>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.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;
|
||||
}
|
||||
.feed-panel:hover {
|
||||
border-color: rgba(139, 124, 246, 0.18);
|
||||
}
|
||||
.feed-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
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;
|
||||
}
|
||||
.feed-date-heading {
|
||||
font-size: 9px;
|
||||
font-weight: 700;
|
||||
color: #6b7385;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
padding: 8px 0 4px;
|
||||
}
|
||||
.feed-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 5px 6px;
|
||||
border-radius: 6px;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
.feed-item:hover {
|
||||
background: rgba(139, 124, 246, 0.04);
|
||||
}
|
||||
.feed-time {
|
||||
font-size: 9px;
|
||||
color: #6b7385;
|
||||
flex-shrink: 0;
|
||||
width: 36px;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
.feed-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
box-shadow: 0 0 4px currentColor;
|
||||
}
|
||||
.feed-text {
|
||||
font-size: 10.5px;
|
||||
line-height: 1.3;
|
||||
color: #7e8799;
|
||||
}
|
||||
|
||||
@media (max-width: 900px) {
|
||||
.feed-panel {
|
||||
order: 2;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user