Files
nexus/frontend/src/components/dashboard/v2/TaskStrip.vue
T
reviewer 4ad0f9e493
CI - Build & Test / Backend (.NET) (push) Failing after 25s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 17s
CI - Build & Test / Security Check (push) Successful in 2s
refactor: SOLID architecture — backend service layer + frontend V2 components
## 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>
2026-06-14 08:34:58 +02:00

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>