feat: ship agent-first mission control v0.2.57
CI - Build & Test / Backend (.NET) (push) Successful in 42s
CI - Build & Test / Frontend (Vue/TS) (push) Successful in 2m46s
CI - Build & Test / Security Check (push) Successful in 3s
CI - Build & Test / Deploy Nexus (push) Successful in 56s

This commit is contained in:
AzuTear
2026-07-31 22:39:47 +02:00
parent 3bc7622977
commit f5552218bc
535 changed files with 95242 additions and 8791 deletions
+151 -87
View File
@@ -1,24 +1,13 @@
<script setup lang="ts">
/**
* AlertBar — Status-Übersicht im V2 Dashboard
*
* Props:
* activeCount Agents mit status 'work'
* thinkCount Agents mit status 'think'
* idleCount Agents mit status 'idle'
* blockerCount Blocker-Anzahl
* todayCost Kosten heute (z.B. "$6.40")
* todayTokens Token heute (z.B. "282k")
*/
import { icons } from '../../../composables/icons'
import { CircleDollarSign } from '@lucide/vue'
defineProps<{
activeCount: number
thinkCount: number
idleCount: number
blockerCount: number
todayCost: string
todayTokens: string
todayCost: string | null
todayTokens: string | null
blockerLabel?: string
}>()
@@ -28,97 +17,113 @@ defineEmits<{
</script>
<template>
<div class="alertbar glass-panel">
<!-- Active (arbeitet) -->
<div class="seg">
<span class="dot work"></span>
<span class="seg-label">{{ activeCount }} arbeiten</span>
<section class="alertbar glass-panel" aria-label="Operationsstatus">
<div class="status-cluster">
<span class="seg" :aria-label="`${activeCount} Agents arbeiten`">
<span class="dot work" aria-hidden="true"></span>
<strong>{{ activeCount }}</strong>
<span class="seg-label">aktiv</span>
</span>
<span class="seg" :aria-label="`${thinkCount} Agents planen`">
<span class="dot think" aria-hidden="true"></span>
<strong>{{ thinkCount }}</strong>
<span class="seg-label">planen</span>
</span>
<span class="seg idle-seg" :aria-label="`${idleCount} Agents sind bereit`">
<span class="dot idle" aria-hidden="true"></span>
<strong>{{ idleCount }}</strong>
<span class="seg-label">bereit</span>
</span>
</div>
<!-- Think (plant) -->
<div class="seg">
<span class="dot think"></span>
<span class="seg-label">{{ thinkCount }} planen</span>
<div
v-if="todayCost || todayTokens"
class="cost-seg"
:aria-label="`Gemeldete Gateway-Telemetrie: ${todayCost || 'keine Kosten'}, ${todayTokens || 'keine'} Token`"
>
<CircleDollarSign class="seg-icon" :size="13" aria-hidden="true" />
<span class="today-label">Gemeldet</span>
<strong v-if="todayCost" class="cost-value">{{ todayCost }}</strong>
<span v-if="todayTokens" class="token-value"><template v-if="todayCost">· </template>{{ todayTokens }} Token</span>
</div>
<!-- Idle (bereit) -->
<div class="seg">
<span class="dot idle"></span>
<span class="seg-label">{{ idleCount }} bereit</span>
</div>
<!-- Separator -->
<div class="sep"></div>
<!-- Kosten heute -->
<div class="seg tx2">
<span class="seg-icon" v-html="icons.coin || ''"></span>
heute <span class="cost-value">{{ todayCost }}</span> · {{ todayTokens }}
</div>
<!-- Blocker Alert (rechts) -->
<button
v-if="blockerCount > 0"
class="blk"
type="button"
:aria-label="blockerLabel || `${blockerCount} Blocker`"
@click="$emit('blockerClick')"
>
<span class="dot block"></span>
{{ blockerLabel || `${blockerCount} Blocker` }}
<span class="dot block" aria-hidden="true"></span>
<span class="blocker-full">{{ blockerLabel || `${blockerCount} Blocker` }}</span>
<span class="blocker-short">{{ blockerCount }} Blocker</span>
</button>
</div>
</section>
</template>
<style scoped>
.alertbar {
min-height: 44px;
display: flex;
align-items: center;
gap: 16px;
padding: 6px 8px 6px 12px;
border-radius: var(--r);
flex: 0 0 auto;
overflow: hidden;
}
.status-cluster {
display: flex;
align-items: center;
gap: 14px;
padding: 11px 16px;
border-radius: var(--r);
flex-wrap: wrap;
min-width: 0;
}
.seg {
display: flex;
.seg,
.cost-seg {
display: inline-flex;
align-items: center;
gap: 8px;
font-size: 12.5px;
font-weight: 600;
color: var(--tx);
gap: 6px;
white-space: nowrap;
}
.seg-label {
font-family: var(--font-body);
font-size: 11.5px;
color: var(--tx-2);
}
.seg-icon :deep(svg) {
width: 14px;
height: 14px;
flex: 0 0 auto;
color: var(--a-mid);
.seg strong {
color: var(--tx);
font-family: var(--font-mono-v2);
font-size: 11px;
font-variant-numeric: tabular-nums;
}
.tx2 .seg-icon :deep(svg) {
.seg-icon {
display: flex;
color: var(--tx-3);
}
.seg-icon :deep(svg) {
width: 13px;
height: 13px;
}
.dot {
width: 9px;
height: 9px;
width: 7px;
height: 7px;
border-radius: 50%;
flex: 0 0 auto;
}
.dot.work {
background: var(--st-work);
box-shadow: 0 0 0 0 rgba(61,220,151,.55);
animation: pulse-work 1.8s infinite;
}
.dot.think {
background: var(--st-think);
box-shadow: 0 0 0 0 rgba(52,214,245,.55);
animation: pulse-think 1.8s infinite;
}
@@ -128,19 +133,18 @@ defineEmits<{
.dot.block {
background: var(--st-block);
box-shadow: 0 0 0 0 rgba(251,113,133,.55);
animation: pulse-block 1.8s infinite;
}
.sep {
width: 1px;
height: 20px;
background: var(--line-2);
flex: 0 0 auto;
.cost-seg {
min-width: 0;
padding-left: 16px;
border-left: 1px solid var(--line-2);
}
.cost-value {
font-family: 'JetBrains Mono', monospace;
font-family: var(--font-mono-v2);
font-size: 11px;
font-variant-numeric: tabular-nums;
background: var(--grad);
-webkit-background-clip: text;
@@ -148,45 +152,105 @@ defineEmits<{
color: transparent;
}
.token-value {
color: var(--tx-3);
font-family: var(--font-mono-v2);
font-size: 10.5px;
}
.blk {
margin-left: auto;
max-width: 42%;
min-height: 32px;
display: inline-flex;
align-items: center;
gap: 9px;
padding: 6px 12px;
gap: 8px;
padding: 0 11px;
border-radius: 9px;
background: rgba(251,113,133,.12);
border: 1px solid rgba(251,113,133,.3);
font-size: 12.5px;
font-weight: 600;
background: var(--status-block-bg);
border: 1px solid var(--status-block-line);
color: #fda4b0;
cursor: pointer;
transition: background .15s;
font-family: 'Manrope', sans-serif;
transition: background .15s, border-color .15s;
font-family: var(--font-body);
font-size: 11.5px;
font-weight: 700;
white-space: nowrap;
}
.blk:hover {
background: rgba(251,113,133,.22);
background: rgba(251, 113, 133, .18);
border-color: rgba(251, 113, 133, .42);
}
@media (max-width: 767px) {
.blk:focus-visible {
outline: none;
box-shadow: var(--focus-ring);
}
.blocker-full {
min-width: 0;
overflow: hidden;
text-overflow: ellipsis;
}
.blocker-short {
display: none;
}
@media (max-width: 700px) {
.alertbar {
flex-wrap: wrap;
gap: 8px;
padding: 10px;
gap: 10px;
padding-left: 10px;
}
.seg {
flex: 0 0 calc(50% - 4px);
.status-cluster {
gap: 10px;
}
.sep {
.cost-seg {
margin-left: auto;
padding-left: 10px;
}
.today-label,
.token-value,
.blocker-full {
display: none;
}
.blocker-short {
display: inline;
}
.blk {
margin-left: 0;
max-width: none;
}
}
@media (max-width: 460px) {
.alertbar {
gap: 8px;
}
.status-cluster {
gap: 8px;
}
.idle-seg,
.cost-seg {
display: none;
}
.blk {
margin-left: auto;
}
}
@media (prefers-reduced-motion: reduce) {
.dot {
animation: none;
}
}
</style>