Commit d367d1cd authored by yangjianbo's avatar yangjianbo
Browse files

Merge branch 'main' into test-sora

parents d7011163 3c46f7d2
......@@ -2,9 +2,42 @@
<AppLayout>
<TablePageLayout>
<template #filters>
<div class="space-y-3">
<!-- Row 1: Actions -->
<div class="flex flex-wrap items-center gap-3">
<!-- Left: Search + Filters -->
<div class="relative w-full sm:w-64">
<Icon
name="search"
size="md"
class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
/>
<input
v-model="searchQuery"
type="text"
:placeholder="t('admin.proxies.searchProxies')"
class="input pl-10"
@input="handleSearch"
/>
</div>
<div class="w-full sm:w-40">
<Select
v-model="filters.protocol"
:options="protocolOptions"
:placeholder="t('admin.proxies.allProtocols')"
@change="loadProxies"
/>
</div>
<div class="w-full sm:w-36">
<Select
v-model="filters.status"
:options="statusOptions"
:placeholder="t('admin.proxies.allStatus')"
@change="loadProxies"
/>
</div>
<!-- Right: All action buttons -->
<div class="flex flex-1 flex-wrap items-center justify-end gap-2">
<button
@click="loadProxies"
:disabled="loading"
......@@ -42,41 +75,6 @@
{{ t('admin.proxies.createProxy') }}
</button>
</div>
<!-- Row 2: Search + Filters -->
<div class="flex flex-wrap items-center gap-3">
<div class="relative w-full sm:w-64">
<Icon
name="search"
size="md"
class="absolute left-3 top-1/2 -translate-y-1/2 text-gray-400 dark:text-gray-500"
/>
<input
v-model="searchQuery"
type="text"
:placeholder="t('admin.proxies.searchProxies')"
class="input pl-10"
@input="handleSearch"
/>
</div>
<div class="w-full sm:w-40">
<Select
v-model="filters.protocol"
:options="protocolOptions"
:placeholder="t('admin.proxies.allProtocols')"
@change="loadProxies"
/>
</div>
<div class="w-full sm:w-36">
<Select
v-model="filters.status"
:options="statusOptions"
:placeholder="t('admin.proxies.allStatus')"
@change="loadProxies"
/>
</div>
</div>
</div>
</template>
......
<template>
<AppLayout>
<TablePageLayout>
<template #actions>
<div class="flex justify-end gap-3">
<button
@click="loadCodes"
:disabled="loading"
class="btn btn-secondary"
:title="t('common.refresh')"
>
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
</button>
<button @click="showGenerateDialog = true" class="btn btn-primary">
{{ t('admin.redeem.generateCodes') }}
</button>
</div>
</template>
<template #filters>
<div class="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div class="max-w-md flex-1">
<div class="flex flex-wrap items-center gap-3">
<!-- Left: Search + Filters -->
<div class="flex-1 sm:max-w-64">
<input
v-model="searchQuery"
type="text"
......@@ -28,7 +13,6 @@
@input="handleSearch"
/>
</div>
<div class="flex gap-2">
<Select
v-model="filters.type"
:options="filterTypeOptions"
......@@ -41,9 +25,23 @@
class="w-36"
@change="loadCodes"
/>
<!-- Right: Action buttons -->
<div class="flex flex-1 flex-wrap items-center justify-end gap-2">
<button
@click="loadCodes"
:disabled="loading"
class="btn btn-secondary"
:title="t('common.refresh')"
>
<Icon name="refresh" size="md" :class="loading ? 'animate-spin' : ''" />
</button>
<button @click="handleExportCodes" class="btn btn-secondary">
{{ t('admin.redeem.exportCsv') }}
</button>
<button @click="showGenerateDialog = true" class="btn btn-primary">
{{ t('admin.redeem.generateCodes') }}
</button>
</div>
</div>
</template>
......
......@@ -3,9 +3,9 @@
<TablePageLayout>
<!-- Single Row: Search, Filters, and Actions -->
<template #filters>
<div class="flex w-full flex-col gap-3 md:flex-row md:flex-wrap-reverse md:items-center md:justify-between md:gap-4">
<div class="flex flex-wrap items-center gap-3">
<!-- Left: Search + Active Filters -->
<div class="flex min-w-[280px] flex-1 flex-wrap content-start items-center gap-3 md:order-1">
<div class="flex flex-1 flex-wrap items-center gap-3">
<!-- Search Box -->
<div class="relative w-full md:w-64">
<Icon
......@@ -100,7 +100,7 @@
</div>
<!-- Right: Actions and Settings -->
<div class="flex w-full items-center justify-between gap-2 md:order-2 md:ml-auto md:max-w-full md:flex-wrap md:justify-end md:gap-3">
<div class="flex flex-wrap items-center justify-end gap-2">
<!-- Mobile: Secondary buttons (icon only) -->
<div class="flex items-center gap-2 md:contents">
<!-- Refresh Button -->
......@@ -342,8 +342,11 @@
</div>
</template>
<template #cell-concurrency="{ value }">
<span class="text-sm text-gray-700 dark:text-gray-300">{{ value }}</span>
<template #cell-concurrency="{ row }">
<UserConcurrencyCell
:current="row.current_concurrency ?? 0"
:max="row.concurrency"
/>
</template>
<template #cell-status="{ value }">
......@@ -535,6 +538,7 @@ import EmptyState from '@/components/common/EmptyState.vue'
import GroupBadge from '@/components/common/GroupBadge.vue'
import Select from '@/components/common/Select.vue'
import UserAttributesConfigModal from '@/components/user/UserAttributesConfigModal.vue'
import UserConcurrencyCell from '@/components/user/UserConcurrencyCell.vue'
import UserCreateModal from '@/components/admin/user/UserCreateModal.vue'
import UserEditModal from '@/components/admin/user/UserEditModal.vue'
import UserApiKeysModal from '@/components/admin/user/UserApiKeysModal.vue'
......
......@@ -56,7 +56,6 @@ interface SummaryRow {
total_accounts: number
available_accounts: number
rate_limited_accounts: number
scope_rate_limit_count?: Record<string, number>
error_accounts: number
// 并发统计
total_concurrency: number
......@@ -122,7 +121,7 @@ const platformRows = computed((): SummaryRow[] => {
total_accounts: totalAccounts,
available_accounts: availableAccounts,
rate_limited_accounts: safeNumber(avail.rate_limit_count),
scope_rate_limit_count: avail.scope_rate_limit_count,
error_accounts: safeNumber(avail.error_count),
total_concurrency: totalConcurrency,
used_concurrency: usedConcurrency,
......@@ -162,7 +161,7 @@ const groupRows = computed((): SummaryRow[] => {
total_accounts: totalAccounts,
available_accounts: availableAccounts,
rate_limited_accounts: safeNumber(avail.rate_limit_count),
scope_rate_limit_count: avail.scope_rate_limit_count,
error_accounts: safeNumber(avail.error_count),
total_concurrency: totalConcurrency,
used_concurrency: usedConcurrency,
......@@ -329,14 +328,6 @@ function formatDuration(seconds: number): string {
return `${hours}h`
}
function formatScopeName(scope: string): string {
const names: Record<string, string> = {
claude: 'Claude',
gemini_text: 'Gemini',
gemini_image: 'Image'
}
return names[scope] || scope
}
watch(
() => realtimeEnabled.value,
......@@ -505,18 +496,6 @@ watch(
{{ t('admin.ops.concurrency.rateLimited', { count: row.rate_limited_accounts }) }}
</span>
<!-- Scope 限流 ( Antigravity) -->
<template v-if="row.scope_rate_limit_count && Object.keys(row.scope_rate_limit_count).length > 0">
<span
v-for="(count, scope) in row.scope_rate_limit_count"
:key="scope"
class="rounded-full bg-orange-100 px-1.5 py-0.5 font-semibold text-orange-700 dark:bg-orange-900/30 dark:text-orange-400"
:title="t('admin.ops.concurrency.scopeRateLimitedTooltip', { scope, count })"
>
{{ formatScopeName(scope as string) }} {{ count }}
</span>
</template>
<!-- 异常账号 -->
<span
v-if="row.error_accounts > 0"
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment