mirror of
https://gitee.com/wanwujie/sub2api
synced 2026-04-03 15:02:13 +08:00
Merge pull request #854 from james-6-23/main
feat(admin): 支持定时测试自动恢复并统一账号恢复入口
This commit is contained in:
@@ -32,14 +32,10 @@
|
||||
{{ t('admin.accounts.refreshToken') }}
|
||||
</button>
|
||||
</template>
|
||||
<div v-if="account.status === 'error' || isRateLimited || isOverloaded" class="my-1 border-t border-gray-100 dark:border-dark-700"></div>
|
||||
<button v-if="account.status === 'error'" @click="$emit('reset-status', account); $emit('close')" class="flex w-full items-center gap-2 px-4 py-2 text-sm text-yellow-600 hover:bg-gray-100 dark:hover:bg-dark-700">
|
||||
<div v-if="hasRecoverableState" class="my-1 border-t border-gray-100 dark:border-dark-700"></div>
|
||||
<button v-if="hasRecoverableState" @click="$emit('recover-state', account); $emit('close')" class="flex w-full items-center gap-2 px-4 py-2 text-sm text-emerald-600 hover:bg-gray-100 dark:hover:bg-dark-700">
|
||||
<Icon name="sync" size="sm" />
|
||||
{{ t('admin.accounts.resetStatus') }}
|
||||
</button>
|
||||
<button v-if="isRateLimited || isOverloaded" @click="$emit('clear-rate-limit', account); $emit('close')" class="flex w-full items-center gap-2 px-4 py-2 text-sm text-amber-600 hover:bg-gray-100 dark:hover:bg-dark-700">
|
||||
<Icon name="clock" size="sm" />
|
||||
{{ t('admin.accounts.clearRateLimit') }}
|
||||
{{ t('admin.accounts.recoverState') }}
|
||||
</button>
|
||||
<button v-if="hasQuotaLimit" @click="$emit('reset-quota', account); $emit('close')" class="flex w-full items-center gap-2 px-4 py-2 text-sm text-teal-600 hover:bg-gray-100 dark:hover:bg-dark-700">
|
||||
<Icon name="refresh" size="sm" />
|
||||
@@ -59,7 +55,7 @@ import { Icon } from '@/components/icons'
|
||||
import type { Account } from '@/types'
|
||||
|
||||
const props = defineProps<{ show: boolean; account: Account | null; position: { top: number; left: number } | null }>()
|
||||
const emit = defineEmits(['close', 'test', 'stats', 'schedule', 'reauth', 'refresh-token', 'reset-status', 'clear-rate-limit', 'reset-quota'])
|
||||
const emit = defineEmits(['close', 'test', 'stats', 'schedule', 'reauth', 'refresh-token', 'recover-state', 'reset-quota'])
|
||||
const { t } = useI18n()
|
||||
const isRateLimited = computed(() => {
|
||||
if (props.account?.rate_limit_reset_at && new Date(props.account.rate_limit_reset_at) > new Date()) {
|
||||
@@ -75,6 +71,10 @@ const isRateLimited = computed(() => {
|
||||
return false
|
||||
})
|
||||
const isOverloaded = computed(() => props.account?.overload_until && new Date(props.account.overload_until) > new Date())
|
||||
const isTempUnschedulable = computed(() => props.account?.temp_unschedulable_until && new Date(props.account.temp_unschedulable_until) > new Date())
|
||||
const hasRecoverableState = computed(() => {
|
||||
return props.account?.status === 'error' || Boolean(isRateLimited.value) || Boolean(isOverloaded.value) || Boolean(isTempUnschedulable.value)
|
||||
})
|
||||
const hasQuotaLimit = computed(() => {
|
||||
return props.account?.type === 'apikey' && (
|
||||
(props.account?.quota_limit ?? 0) > 0 ||
|
||||
|
||||
@@ -41,8 +41,24 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
<label class="mb-1 flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.scheduledTests.cronExpression') }}
|
||||
<HelpTooltip>
|
||||
<template #trigger>
|
||||
<span class="inline-flex h-4 w-4 cursor-help items-center justify-center rounded-full border border-gray-400/70 text-[10px] font-semibold text-gray-400 transition-colors hover:border-primary-500 hover:text-primary-600 dark:border-gray-500 dark:text-gray-500 dark:hover:border-primary-400 dark:hover:text-primary-400">
|
||||
?
|
||||
</span>
|
||||
</template>
|
||||
<div class="space-y-1.5">
|
||||
<p class="font-medium">{{ t('admin.scheduledTests.cronTooltipTitle') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipMeaning') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleEvery30Min') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleHourly') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleDaily') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleWeekly') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipRange') }}</p>
|
||||
</div>
|
||||
</HelpTooltip>
|
||||
</label>
|
||||
<Input
|
||||
v-model="newPlan.cron_expression"
|
||||
@@ -51,8 +67,22 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
<label class="mb-1 flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.scheduledTests.maxResults') }}
|
||||
<HelpTooltip>
|
||||
<template #trigger>
|
||||
<span class="inline-flex h-4 w-4 cursor-help items-center justify-center rounded-full border border-gray-400/70 text-[10px] font-semibold text-gray-400 transition-colors hover:border-primary-500 hover:text-primary-600 dark:border-gray-500 dark:text-gray-500 dark:hover:border-primary-400 dark:hover:text-primary-400">
|
||||
?
|
||||
</span>
|
||||
</template>
|
||||
<div class="space-y-1.5">
|
||||
<p class="font-medium">{{ t('admin.scheduledTests.maxResultsTooltipTitle') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipMeaning') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipBody') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipExample') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipRange') }}</p>
|
||||
</div>
|
||||
</HelpTooltip>
|
||||
</label>
|
||||
<Input
|
||||
v-model="newPlan.max_results"
|
||||
@@ -66,6 +96,17 @@
|
||||
{{ t('admin.scheduledTests.enabled') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<div>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<Toggle v-model="newPlan.auto_recover" />
|
||||
{{ t('admin.scheduledTests.autoRecover') }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t('admin.scheduledTests.autoRecoverHelp') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex justify-end gap-2">
|
||||
<button
|
||||
@@ -135,6 +176,14 @@
|
||||
{{ plan.enabled ? t('admin.scheduledTests.enabled') : '' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- Auto Recover Badge -->
|
||||
<span
|
||||
v-if="plan.auto_recover"
|
||||
class="inline-flex items-center rounded-full bg-emerald-100 px-2 py-0.5 text-xs font-medium text-emerald-700 dark:bg-emerald-500/20 dark:text-emerald-400"
|
||||
>
|
||||
{{ t('admin.scheduledTests.autoRecover') }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
@@ -202,8 +251,24 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
<label class="mb-1 flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.scheduledTests.cronExpression') }}
|
||||
<HelpTooltip>
|
||||
<template #trigger>
|
||||
<span class="inline-flex h-4 w-4 cursor-help items-center justify-center rounded-full border border-gray-400/70 text-[10px] font-semibold text-gray-400 transition-colors hover:border-primary-500 hover:text-primary-600 dark:border-gray-500 dark:text-gray-500 dark:hover:border-primary-400 dark:hover:text-primary-400">
|
||||
?
|
||||
</span>
|
||||
</template>
|
||||
<div class="space-y-1.5">
|
||||
<p class="font-medium">{{ t('admin.scheduledTests.cronTooltipTitle') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipMeaning') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleEvery30Min') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleHourly') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleDaily') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipExampleWeekly') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.cronTooltipRange') }}</p>
|
||||
</div>
|
||||
</HelpTooltip>
|
||||
</label>
|
||||
<Input
|
||||
v-model="editForm.cron_expression"
|
||||
@@ -212,8 +277,22 @@
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label class="mb-1 block text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
<label class="mb-1 flex items-center gap-1 text-xs font-medium text-gray-600 dark:text-gray-400">
|
||||
{{ t('admin.scheduledTests.maxResults') }}
|
||||
<HelpTooltip>
|
||||
<template #trigger>
|
||||
<span class="inline-flex h-4 w-4 cursor-help items-center justify-center rounded-full border border-gray-400/70 text-[10px] font-semibold text-gray-400 transition-colors hover:border-primary-500 hover:text-primary-600 dark:border-gray-500 dark:text-gray-500 dark:hover:border-primary-400 dark:hover:text-primary-400">
|
||||
?
|
||||
</span>
|
||||
</template>
|
||||
<div class="space-y-1.5">
|
||||
<p class="font-medium">{{ t('admin.scheduledTests.maxResultsTooltipTitle') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipMeaning') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipBody') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipExample') }}</p>
|
||||
<p>{{ t('admin.scheduledTests.maxResultsTooltipRange') }}</p>
|
||||
</div>
|
||||
</HelpTooltip>
|
||||
</label>
|
||||
<Input
|
||||
v-model="editForm.max_results"
|
||||
@@ -227,6 +306,17 @@
|
||||
{{ t('admin.scheduledTests.enabled') }}
|
||||
</label>
|
||||
</div>
|
||||
<div class="flex items-end">
|
||||
<div>
|
||||
<label class="flex items-center gap-2 text-sm text-gray-700 dark:text-gray-300">
|
||||
<Toggle v-model="editForm.auto_recover" />
|
||||
{{ t('admin.scheduledTests.autoRecover') }}
|
||||
</label>
|
||||
<p class="mt-0.5 text-xs text-gray-400 dark:text-gray-500">
|
||||
{{ t('admin.scheduledTests.autoRecoverHelp') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mt-3 flex justify-end gap-2">
|
||||
<button
|
||||
@@ -377,6 +467,7 @@ import { ref, reactive, watch } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import BaseDialog from '@/components/common/BaseDialog.vue'
|
||||
import ConfirmDialog from '@/components/common/ConfirmDialog.vue'
|
||||
import HelpTooltip from '@/components/common/HelpTooltip.vue'
|
||||
import Select, { type SelectOption } from '@/components/common/Select.vue'
|
||||
import Input from '@/components/common/Input.vue'
|
||||
import Toggle from '@/components/common/Toggle.vue'
|
||||
@@ -416,14 +507,16 @@ const editForm = reactive({
|
||||
model_id: '' as string,
|
||||
cron_expression: '' as string,
|
||||
max_results: '100' as string,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
auto_recover: false
|
||||
})
|
||||
|
||||
const newPlan = reactive({
|
||||
model_id: '' as string,
|
||||
cron_expression: '' as string,
|
||||
max_results: '100' as string,
|
||||
enabled: true
|
||||
enabled: true,
|
||||
auto_recover: false
|
||||
})
|
||||
|
||||
const resetNewPlan = () => {
|
||||
@@ -431,6 +524,7 @@ const resetNewPlan = () => {
|
||||
newPlan.cron_expression = ''
|
||||
newPlan.max_results = '100'
|
||||
newPlan.enabled = true
|
||||
newPlan.auto_recover = false
|
||||
}
|
||||
|
||||
// Load plans when dialog opens
|
||||
@@ -472,7 +566,8 @@ const handleCreate = async () => {
|
||||
model_id: newPlan.model_id,
|
||||
cron_expression: newPlan.cron_expression,
|
||||
enabled: newPlan.enabled,
|
||||
max_results: maxResults
|
||||
max_results: maxResults,
|
||||
auto_recover: newPlan.auto_recover
|
||||
})
|
||||
appStore.showSuccess(t('admin.scheduledTests.createSuccess'))
|
||||
showAddForm.value = false
|
||||
@@ -504,6 +599,7 @@ const startEdit = (plan: ScheduledTestPlan) => {
|
||||
editForm.cron_expression = plan.cron_expression
|
||||
editForm.max_results = String(plan.max_results)
|
||||
editForm.enabled = plan.enabled
|
||||
editForm.auto_recover = plan.auto_recover
|
||||
}
|
||||
|
||||
const cancelEdit = () => {
|
||||
@@ -518,7 +614,8 @@ const handleEdit = async () => {
|
||||
model_id: editForm.model_id,
|
||||
cron_expression: editForm.cron_expression,
|
||||
max_results: Number(editForm.max_results) || 100,
|
||||
enabled: editForm.enabled
|
||||
enabled: editForm.enabled,
|
||||
auto_recover: editForm.auto_recover
|
||||
})
|
||||
const index = plans.value.findIndex((p) => p.id === editingPlanId.value)
|
||||
if (index !== -1) {
|
||||
|
||||
Reference in New Issue
Block a user