Files
nexus/frontend/src/components/dashboard/v2/AlertBar.vue
T
AzuTear f5552218bc
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
feat: ship agent-first mission control v0.2.57
2026-07-31 22:39:47 +02:00

257 lines
4.9 KiB
Vue

<script setup lang="ts">
import { CircleDollarSign } from '@lucide/vue'
defineProps<{
activeCount: number
thinkCount: number
idleCount: number
blockerCount: number
todayCost: string | null
todayTokens: string | null
blockerLabel?: string
}>()
defineEmits<{
blockerClick: []
}>()
</script>
<template>
<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>
<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>
<button
v-if="blockerCount > 0"
class="blk"
type="button"
:aria-label="blockerLabel || `${blockerCount} Blocker`"
@click="$emit('blockerClick')"
>
<span class="dot block" aria-hidden="true"></span>
<span class="blocker-full">{{ blockerLabel || `${blockerCount} Blocker` }}</span>
<span class="blocker-short">{{ blockerCount }} Blocker</span>
</button>
</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;
min-width: 0;
}
.seg,
.cost-seg {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
font-family: var(--font-body);
font-size: 11.5px;
color: var(--tx-2);
}
.seg strong {
color: var(--tx);
font-family: var(--font-mono-v2);
font-size: 11px;
font-variant-numeric: tabular-nums;
}
.seg-icon {
display: flex;
color: var(--tx-3);
}
.seg-icon :deep(svg) {
width: 13px;
height: 13px;
}
.dot {
width: 7px;
height: 7px;
border-radius: 50%;
flex: 0 0 auto;
}
.dot.work {
background: var(--st-work);
animation: pulse-work 1.8s infinite;
}
.dot.think {
background: var(--st-think);
animation: pulse-think 1.8s infinite;
}
.dot.idle {
background: var(--st-idle);
}
.dot.block {
background: var(--st-block);
animation: pulse-block 1.8s infinite;
}
.cost-seg {
min-width: 0;
padding-left: 16px;
border-left: 1px solid var(--line-2);
}
.cost-value {
font-family: var(--font-mono-v2);
font-size: 11px;
font-variant-numeric: tabular-nums;
background: var(--grad);
-webkit-background-clip: text;
background-clip: text;
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: 8px;
padding: 0 11px;
border-radius: 9px;
background: var(--status-block-bg);
border: 1px solid var(--status-block-line);
color: #fda4b0;
cursor: pointer;
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, .18);
border-color: rgba(251, 113, 133, .42);
}
.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 {
gap: 10px;
padding-left: 10px;
}
.status-cluster {
gap: 10px;
}
.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>