129 lines
4.2 KiB
Vue

<template>
<section class="py-3 md:py-4">
<div class="flex items-center justify-end gap-3 flex-wrap">
<div
role="tablist"
class="inline-flex p-0.5 rounded-xl bg-gray-100 dark:bg-dark-800 border border-gray-200/60 dark:border-dark-700/60 text-xs"
>
<button
v-for="opt in windowOptions"
:key="opt.value"
type="button"
role="tab"
:aria-selected="window === opt.value"
class="px-3 py-1 rounded-lg transition-colors"
:class="window === opt.value
? 'bg-white dark:bg-dark-700 shadow-sm text-gray-900 dark:text-white font-semibold'
: 'text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200'"
@click="emit('update:window', opt.value)"
>
{{ opt.label }}
</button>
</div>
<span
class="inline-flex items-center px-2.5 py-1 rounded-full text-xs font-semibold tracking-wider uppercase"
:class="overallChipClass"
>
<span
class="w-1.5 h-1.5 rounded-full mr-1.5"
:class="overallDotClass"
></span>
{{ overallLabel }}
</span>
<button
type="button"
class="h-8 w-8 rounded-lg flex items-center justify-center text-gray-500 hover:text-gray-700 hover:bg-gray-100 dark:text-gray-400 dark:hover:text-gray-200 dark:hover:bg-dark-700 transition-colors disabled:opacity-50"
:disabled="loading"
:title="t('common.refresh')"
@click="emit('refresh')"
>
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
</button>
<AutoRefreshButton
v-if="autoRefresh"
:enabled="autoRefresh.enabled.value"
:interval-seconds="autoRefresh.intervalSeconds.value"
:countdown="autoRefresh.countdown.value"
:intervals="autoRefresh.intervals"
@update:enabled="autoRefresh.setEnabled"
@update:interval="autoRefresh.setInterval"
/>
<div class="text-xs text-gray-500 dark:text-gray-400 tabular-nums">
{{ updatedLabel }}
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import Icon from '@/components/icons/Icon.vue'
import AutoRefreshButton from '@/components/common/AutoRefreshButton.vue'
import { useChannelMonitorFormat } from '@/composables/useChannelMonitorFormat'
export type MonitorWindow = '7d' | '15d' | '30d'
export type OverallStatus = 'operational' | 'degraded'
const props = defineProps<{
overallStatus: OverallStatus
updatedAt: string | null
intervalSeconds: number
window: MonitorWindow
loading: boolean
autoRefresh?: {
enabled: { value: boolean }
intervalSeconds: { value: number }
countdown: { value: number }
intervals: readonly number[]
setEnabled: (v: boolean) => void
setInterval: (v: number) => void
}
}>()
const emit = defineEmits<{
(e: 'update:window', value: MonitorWindow): void
(e: 'refresh'): void
}>()
const { t } = useI18n()
const { formatRelativeTime } = useChannelMonitorFormat()
const windowOptions = computed<{ value: MonitorWindow; label: string }[]>(() => [
{ value: '7d', label: t('channelStatus.windowTab.7d') },
{ value: '15d', label: t('channelStatus.windowTab.15d') },
{ value: '30d', label: t('channelStatus.windowTab.30d') },
])
const overallLabel = computed(() => t(`channelStatus.overall.${props.overallStatus}`))
const overallChipClass = computed(() => {
switch (props.overallStatus) {
case 'operational':
return 'bg-emerald-100 text-emerald-700 dark:bg-emerald-500/15 dark:text-emerald-300'
case 'degraded':
default:
return 'bg-amber-100 text-amber-700 dark:bg-amber-500/15 dark:text-amber-300'
}
})
const overallDotClass = computed(() => {
switch (props.overallStatus) {
case 'operational':
return 'bg-emerald-500 animate-pulse'
case 'degraded':
default:
return 'bg-amber-500 animate-pulse'
}
})
const updatedLabel = computed(() => {
if (!props.updatedAt) return t('monitorCommon.updatedAt', { time: '--' })
return t('monitorCommon.updatedAt', { time: formatRelativeTime(props.updatedAt) })
})
</script>