feat(admin): 添加临时不可调度功能
当账号触发特定错误码和关键词匹配时,自动临时禁用调度: 后端: - 新增 TempUnschedCache Redis 缓存层 - RateLimitService 支持规则匹配和状态管理 - 添加 GET/DELETE /accounts/:id/temp-unschedulable API - 数据库迁移添加 temp_unschedulable_until/reason 字段 前端: - 账号状态指示器显示临时不可调度状态 - 新增 TempUnschedStatusModal 详情弹窗 - 创建/编辑账号时支持配置规则和预设模板 - 完整的中英文国际化支持
This commit is contained in:
@@ -1,7 +1,16 @@
|
||||
<template>
|
||||
<div class="flex items-center gap-2">
|
||||
<!-- Main Status Badge -->
|
||||
<span :class="['badge text-xs', statusClass]">
|
||||
<button
|
||||
v-if="isTempUnschedulable"
|
||||
type="button"
|
||||
:class="['badge text-xs', statusClass, 'cursor-pointer']"
|
||||
:title="t('admin.accounts.tempUnschedulable.viewDetails')"
|
||||
@click="handleTempUnschedClick"
|
||||
>
|
||||
{{ statusText }}
|
||||
</button>
|
||||
<span v-else :class="['badge text-xs', statusClass]">
|
||||
{{ statusText }}
|
||||
</span>
|
||||
|
||||
@@ -83,26 +92,25 @@
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tier Indicator -->
|
||||
<span
|
||||
v-if="tierDisplay"
|
||||
class="inline-flex items-center rounded bg-blue-100 px-1.5 py-0.5 text-xs font-medium text-blue-700 dark:bg-blue-900/30 dark:text-blue-400"
|
||||
>
|
||||
{{ tierDisplay }}
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import type { Account } from '@/types'
|
||||
import { formatTime } from '@/utils/format'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const props = defineProps<{
|
||||
account: Account
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'show-temp-unsched', account: Account): void
|
||||
}>()
|
||||
|
||||
// Computed: is rate limited (429)
|
||||
const isRateLimited = computed(() => {
|
||||
if (!props.account.rate_limit_reset_at) return false
|
||||
@@ -115,6 +123,12 @@ const isOverloaded = computed(() => {
|
||||
return new Date(props.account.overload_until) > new Date()
|
||||
})
|
||||
|
||||
// Computed: is temp unschedulable
|
||||
const isTempUnschedulable = computed(() => {
|
||||
if (!props.account.temp_unschedulable_until) return false
|
||||
return new Date(props.account.temp_unschedulable_until) > new Date()
|
||||
})
|
||||
|
||||
// Computed: has error status
|
||||
const hasError = computed(() => {
|
||||
return props.account.status === 'error'
|
||||
@@ -122,6 +136,12 @@ const hasError = computed(() => {
|
||||
|
||||
// Computed: status badge class
|
||||
const statusClass = computed(() => {
|
||||
if (hasError.value) {
|
||||
return 'badge-danger'
|
||||
}
|
||||
if (isTempUnschedulable.value) {
|
||||
return 'badge-warning'
|
||||
}
|
||||
if (!props.account.schedulable || isRateLimited.value || isOverloaded.value) {
|
||||
return 'badge-gray'
|
||||
}
|
||||
@@ -139,32 +159,24 @@ const statusClass = computed(() => {
|
||||
|
||||
// Computed: status text
|
||||
const statusText = computed(() => {
|
||||
if (hasError.value) {
|
||||
return t('common.error')
|
||||
}
|
||||
if (isTempUnschedulable.value) {
|
||||
return t('admin.accounts.status.tempUnschedulable')
|
||||
}
|
||||
if (!props.account.schedulable) {
|
||||
return 'Paused'
|
||||
return t('admin.accounts.status.paused')
|
||||
}
|
||||
if (isRateLimited.value || isOverloaded.value) {
|
||||
return 'Limited'
|
||||
return t('admin.accounts.status.limited')
|
||||
}
|
||||
return props.account.status
|
||||
})
|
||||
|
||||
// Computed: tier display
|
||||
const tierDisplay = computed(() => {
|
||||
const credentials = props.account.credentials as Record<string, any> | undefined
|
||||
const tierId = credentials?.tier_id
|
||||
if (!tierId || tierId === 'unknown') return null
|
||||
|
||||
const tierMap: Record<string, string> = {
|
||||
'free': 'Free',
|
||||
'payg': 'Pay-as-you-go',
|
||||
'pay-as-you-go': 'Pay-as-you-go',
|
||||
'enterprise': 'Enterprise',
|
||||
'LEGACY': 'Legacy',
|
||||
'PRO': 'Pro',
|
||||
'ULTRA': 'Ultra'
|
||||
}
|
||||
|
||||
return tierMap[tierId] || tierId
|
||||
})
|
||||
const handleTempUnschedClick = () => {
|
||||
if (!isTempUnschedulable.value) return
|
||||
emit('show-temp-unsched', props.account)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user