su2a / frontend /src /components /user /monitor /MonitorCard.vue
cjovs's picture
PG local + bucket backup/restore, DB: su2adb
126ef31 verified
Raw
History Blame Contribute Delete
4.29 kB
<template>
<button
type="button"
class="group text-left p-5 rounded-2xl min-h-[280px] w-full bg-white/70 backdrop-blur-xl border border-gray-200/80 shadow-card dark:bg-dark-800/60 dark:border-dark-700/70 hover:-translate-y-1 hover:shadow-card-hover dark:hover:border-primary-500/30 hover:border-gray-300 transition-all duration-300 ease-out flex flex-col"
@click="emit('click')"
>
<!-- Header: icon + name/model + status chip -->
<div class="flex items-start gap-3">
<span
class="w-9 h-9 rounded-xl ring-1 ring-black/5 dark:ring-white/10 grid place-items-center flex-shrink-0"
:class="[providerGradient(item.provider), providerTintClass]"
>
<ProviderIcon :provider="item.provider" :size="20" />
</span>
<div class="flex-1 min-w-0">
<div class="text-base font-semibold truncate text-gray-900 dark:text-gray-100">
{{ item.name }}
</div>
<div class="mt-0.5 flex items-center gap-1.5 min-w-0">
<span
class="inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium flex-shrink-0"
:class="providerBadgeClass(item.provider)"
>
{{ providerLabel(item.provider) }}
</span>
<span class="font-mono text-xs truncate text-gray-500 dark:text-gray-400">
{{ item.primary_model }}
</span>
<span
v-if="item.group_name"
class="inline-flex items-center rounded-md px-1.5 py-0.5 text-[10px] font-medium bg-gray-100 text-gray-600 dark:bg-dark-700 dark:text-gray-300 flex-shrink-0"
>
{{ item.group_name }}
</span>
</div>
</div>
<span
class="px-2.5 py-1 rounded-full text-xs font-semibold flex-shrink-0"
:class="statusBadgeClass(item.primary_status)"
>
{{ statusLabel(item.primary_status) }}
</span>
</div>
<!-- Metrics -->
<MonitorMetricPair
primary-icon="bolt"
:primary-label="t('monitorCommon.dialogLatency')"
:primary-value="formatLatency(item.primary_latency_ms)"
primary-unit="ms"
secondary-icon="globe"
:secondary-label="t('monitorCommon.endpointPing')"
:secondary-value="formatLatency(item.primary_ping_latency_ms)"
secondary-unit="ms"
/>
<!-- Divider -->
<div class="mt-4 border-t border-gray-100 dark:border-dark-700/60"></div>
<!-- Availability row -->
<MonitorAvailabilityRow
:window-label="availabilityLabel"
:value="availabilityValue"
:samples-label="extraModelsCountLabel"
/>
<!-- Timeline -->
<MonitorTimeline
:buckets="item.timeline"
:countdown-seconds="countdownSeconds"
/>
</button>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import type { UserMonitorView } from '@/api/channelMonitor'
import {
useChannelMonitorFormat,
providerGradient,
} from '@/composables/useChannelMonitorFormat'
import ProviderIcon from './ProviderIcon.vue'
import MonitorMetricPair from './MonitorMetricPair.vue'
import MonitorAvailabilityRow from './MonitorAvailabilityRow.vue'
import MonitorTimeline from './MonitorTimeline.vue'
const PROVIDER_TINT: Record<string, string> = {
openai: 'text-emerald-600 dark:text-emerald-300',
anthropic: 'text-orange-600 dark:text-orange-300',
gemini: 'text-sky-600 dark:text-sky-300',
}
const props = defineProps<{
item: UserMonitorView
window: '7d' | '15d' | '30d'
availabilityValue: number | null
countdownSeconds: number
}>()
const emit = defineEmits<{
(e: 'click'): void
}>()
const { t } = useI18n()
const {
statusLabel,
statusBadgeClass,
providerLabel,
providerBadgeClass,
formatLatency,
} = useChannelMonitorFormat()
const providerTintClass = computed(() =>
PROVIDER_TINT[props.item.provider] ?? 'text-gray-500 dark:text-gray-300'
)
const availabilityLabel = computed(() => {
const win = t(`channelStatus.windowTab.${props.window}`)
return `${t('monitorCommon.availabilityPrefix')} · ${win}`
})
const extraModelsCountLabel = computed(() => {
const count = props.item.extra_models?.length ?? 0
if (count === 0) return undefined
return t('monitorCommon.extraModelsCount', { n: count })
})
</script>