4ad0f9e493
## Backend — Service Layer & Repository Refactoring ### Neue Services (21 neue Dateien) **Interfaces & Implementierungen:** - `IOpenClawGatewayClient` — Interface für OpenClawGatewayClient (DIP-Fix: DashboardController hing an konkreter Klasse) - `IAgentConfigService` / `AgentConfigService` — Agent-Config-File-I/O aus AgentsController extrahiert - `IProjectService` / `ProjectService` — Projekt-CRUD + Activity-Logging (SRP) - `ITaskService` / `TaskService` — Task-State-Machine, Approve/Reject, Dashboard-Operationen (eliminiert Duplikation zwischen TasksController und DashboardController) - `IDashboardService` / `DashboardService` — Queue-Aggregation, Priority-Normalisierung, Gateway-Delegation - `IOperationsService` / `OperationsService` — Metriken-Berechnung aus OperationsController - `ITeamService` / `TeamService` — IDENTITY.md-Lesen aus TeamController - `IMemoryService` / `MemoryService` — File-I/O aus MemoryController - `IIncidentService` / `IncidentService` — File-Parsing (Regex-Source-Generatoren) aus IncidentsController - `IDocService` / `DocService` — Directory-Scan aus DocsController - `ICalendarService` / `CalendarService` — Gateway-HTTP-Calls + Fallback-Daten aus CalendarController ### Repository-Fixes **IUserRepository / UserRepository:** - `SaveChangesAsync` entfernt (leaky abstraction — Caller sollten nie SaveChanges steuern) - `RevokeTokenAsync(tokenHash)` — atomares Token-Revoke inkl. SaveChanges - `RevokeFamilyAsync(familyId)` — Batch-Revoke einer Token-Familie inkl. SaveChanges - `RemoveExpiredTokensAsync` speichert jetzt selbst (war vorher dependent auf nachfolgenden Save) ### AuthService-Fixes - `GetUserAsync`: unnötiges `Task.Run` entfernt → direkt `_users.GetByIdAsync().AsTask()` - `RevokeAsync`: delegiert jetzt an `IUserRepository.RevokeTokenAsync` - `RefreshAsync`: Token-Reuse-Detection delegiert an `IUserRepository.RevokeFamilyAsync` ### Bug-Fix - `OpenClawGatewayClient.ReadAgentGoalAsync`: pre-existing `CS1656` behoben (`reader` war `using`-Variable und wurde neu zugewiesen — in `reader2` umbenannt) ### Controller (16 Stück — alle slim) Alle Controller reduziert auf: Input validieren → Service aufrufen → HTTP-Result zurückgeben. Kein Business-Logic, kein File-I/O, keine direkte Repository-Nutzung (außer AgentsController für Activity-Log). **Program.cs — neue Registrierungen:** - `AddHttpClient<IOpenClawGatewayClient, OpenClawGatewayClient>` (war vorher konkrete Klasse) - Scoped: IDashboardService, IProjectService, ITaskService, IOperationsService, ITeamService, ICalendarService - Singleton: IAgentConfigService, IMemoryService, IIncidentService, IDocService --- ## Frontend — Dashboard V2 Components **AgentDetailModal.vue, IrisChat.vue, TaskStrip.vue:** - V2 Design-System: Dark Space Theme, Glass-Panels, Gradient-Akzente - Stores (agents, chat, tasks) nutzen Service + Mapper-Pattern - NexusLayout, FlowBoard, Topbar — Layoutfixes für fullHeight-Route-Meta Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
150 lines
3.2 KiB
Vue
150 lines
3.2 KiB
Vue
<script setup lang="ts">
|
|
import type { TaskItem } from './types'
|
|
|
|
defineProps<{
|
|
tasks: TaskItem[]
|
|
loading?: boolean
|
|
error?: string | null
|
|
}>()
|
|
|
|
function prioLabel(p: TaskItem['priority']): string {
|
|
return p === 'high' ? 'P0' : p === 'medium' ? 'P1' : 'P2'
|
|
}
|
|
|
|
function prioColor(p: TaskItem['priority']): string {
|
|
return p === 'high' ? '#fda4b0' : p === 'medium' ? '#fcd34d' : '#9db6ff'
|
|
}
|
|
|
|
function dotClass(s: TaskItem['status']): string {
|
|
return s === 'active' ? 'work' : s === 'blocked' ? 'block' : 'queue'
|
|
}
|
|
|
|
function statusLabel(s: TaskItem['status']): string {
|
|
return s === 'active' ? 'Läuft' : s === 'blocked' ? 'Blocker' : 'Queue'
|
|
}
|
|
</script>
|
|
|
|
<template>
|
|
<div class="tstrip">
|
|
<template v-if="loading">
|
|
<div v-for="n in 4" :key="n" class="tcard skeleton"></div>
|
|
</template>
|
|
|
|
<div v-else-if="error" class="tstrip-msg">⚠ {{ error }}</div>
|
|
<div v-else-if="!tasks.length" class="tstrip-msg">Keine aktiven Tasks</div>
|
|
|
|
<template v-else>
|
|
<div
|
|
v-for="task in tasks.slice(0, 4)"
|
|
:key="task.id"
|
|
class="tcard"
|
|
:class="{ block: task.status === 'blocked' }"
|
|
>
|
|
<div class="tcard-row">
|
|
<span class="pr" :style="{ background: 'rgba(124,108,255,.14)', color: prioColor(task.priority) }">
|
|
{{ prioLabel(task.priority) }}
|
|
</span>
|
|
<span class="dot" :class="dotClass(task.status)"></span>
|
|
<span class="stl">{{ statusLabel(task.status) }}</span>
|
|
</div>
|
|
<div class="tt">{{ task.title }}</div>
|
|
<div class="ow">{{ task.agent }}</div>
|
|
</div>
|
|
</template>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.tstrip {
|
|
display: flex;
|
|
gap: 10px;
|
|
overflow: hidden;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.tcard {
|
|
flex: 1;
|
|
min-width: 0;
|
|
padding: 11px 13px;
|
|
border-radius: 12px;
|
|
background: var(--glass);
|
|
border: 1px solid var(--line);
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
.tcard.block {
|
|
border-color: rgba(251,113,133,.35);
|
|
background: rgba(251,113,133,.07);
|
|
}
|
|
|
|
.tcard-row {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 7px;
|
|
}
|
|
|
|
.pr {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 10px;
|
|
font-weight: 600;
|
|
padding: 1px 6px;
|
|
border-radius: 5px;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
flex: 0 0 auto;
|
|
}
|
|
|
|
.dot.work { background: var(--st-work); animation: pulse-work 1.8s infinite; }
|
|
.dot.queue { background: var(--st-queue); }
|
|
.dot.block { background: var(--st-block); animation: pulse-block 1.4s infinite; }
|
|
.dot.idle { background: var(--st-idle); }
|
|
|
|
.stl {
|
|
margin-left: auto;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 10px;
|
|
color: var(--tx-3);
|
|
}
|
|
|
|
.tt {
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
margin-top: 7px;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
color: var(--tx);
|
|
}
|
|
|
|
.ow {
|
|
font-size: 10.5px;
|
|
color: var(--tx-3);
|
|
margin-top: 5px;
|
|
}
|
|
|
|
.skeleton {
|
|
height: 78px;
|
|
background: var(--glass);
|
|
animation: skeleton-pulse 1.5s ease-in-out infinite;
|
|
}
|
|
|
|
@keyframes skeleton-pulse {
|
|
0%, 100% { opacity: 0.5; }
|
|
50% { opacity: 0.8; }
|
|
}
|
|
|
|
.tstrip-msg {
|
|
font-family: 'Manrope', sans-serif;
|
|
font-size: 11px;
|
|
color: var(--tx-3);
|
|
padding: 12px;
|
|
white-space: nowrap;
|
|
}
|
|
</style>
|