修复前端调试与基础交互问题

This commit is contained in:
MMEXA
2026-07-04 05:24:40 +08:00
parent b86d4e1f0c
commit 242081433e
33 changed files with 525 additions and 320 deletions
@@ -325,6 +325,7 @@
<MetricCell
label="进程 CPU"
:value="formatBasisPointsPercent(gatewayMetrics?.process.processCpuUsageBasisPoints)"
:value-class="resourceToneClass(gatewayProcessCpuPercent, 70, 90)"
/>
<MetricCell
label="RSS"
+7 -5
View File
@@ -2016,12 +2016,14 @@ function refreshOverviewInBackground(): void {
}
function applyQuotaRefreshResultToCurrentPage(result: Awaited<ReturnType<typeof refreshProviderQuota>>): void {
const successfulResults = Array.isArray(result.results)
? result.results.filter((item) => item.status === 'success' && item.quota_snapshot)
: []
if (successfulResults.length === 0) return
const quotaByKeyId = new Map<string, NonNullable<NonNullable<typeof result.results>[number]['quota_snapshot']>>()
for (const item of result.results) {
if (item.status === 'success' && item.quota_snapshot) {
quotaByKeyId.set(item.key_id, item.quota_snapshot)
}
}
if (quotaByKeyId.size === 0) return
const quotaByKeyId = new Map(successfulResults.map((item) => [item.key_id, item.quota_snapshot!]))
keyPage.value.keys = keyPage.value.keys.map((key) => {
const quotaSnapshot = quotaByKeyId.get(key.key_id)
if (!quotaSnapshot) return key
-4
View File
@@ -650,10 +650,6 @@ function updateNodeDetailState(nodeId: string, patch: Partial<ProxyNodeDetailSta
}
}
function isNodeExpanded(nodeId: string) {
return expandedNodeIds.value.has(nodeId)
}
function toggleNodeDetails(node: ProxyNode) {
const next = new Set(expandedNodeIds.value)
if (next.has(node.id)) {
@@ -26,11 +26,21 @@
<Select v-model="logLevel">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="trace">trace</SelectItem>
<SelectItem value="debug">debug</SelectItem>
<SelectItem value="info">info</SelectItem>
<SelectItem value="warn">warn</SelectItem>
<SelectItem value="error">error</SelectItem>
<SelectItem value="trace">
trace
</SelectItem>
<SelectItem value="debug">
debug
</SelectItem>
<SelectItem value="info">
info
</SelectItem>
<SelectItem value="warn">
warn
</SelectItem>
<SelectItem value="error">
error
</SelectItem>
</SelectContent>
</Select>
</div>
@@ -49,9 +59,15 @@
<Select v-model="schedulingState">
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="active">active</SelectItem>
<SelectItem value="draining">draining</SelectItem>
<SelectItem value="cordoned">cordoned</SelectItem>
<SelectItem value="active">
active
</SelectItem>
<SelectItem value="draining">
draining
</SelectItem>
<SelectItem value="cordoned">
cordoned
</SelectItem>
</SelectContent>
</Select>
<p class="text-xs text-muted-foreground">
@@ -1,172 +1,170 @@
<template>
<template>
<TableRow
class="border-b border-border/40 hover:bg-muted/30 transition-colors"
:class="expanded ? 'bg-muted/20' : ''"
>
<TableCell class="w-[28px] min-w-[28px] max-w-[28px] p-0 pl-2 text-center">
<button
type="button"
class="inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
:title="expanded ? legacyT('收起数据') : legacyT('展开数据')"
@click="$emit('toggle-details')"
>
<ChevronDown
v-if="expanded"
class="h-3.5 w-3.5"
/>
<ChevronRight
v-else
class="h-3.5 w-3.5"
/>
</button>
</TableCell>
<TableCell class="py-4 min-w-0">
<div class="flex items-center gap-1.5 min-w-0">
<span
class="min-w-0 truncate text-sm font-semibold"
:title="node.name"
>{{ node.name }}</span>
<Badge
v-if="node.is_manual"
variant="outline"
class="text-[10px] px-1.5 py-0"
>
{{ legacyT('手动') }}
</Badge>
<Badge
v-if="node.tunnel_mode"
variant="outline"
class="text-[10px] px-1.5 py-0"
>
Tunnel
</Badge>
<Badge
v-if="schedulingBadge"
:variant="schedulingBadge.variant"
class="text-[10px] px-1.5 py-0"
>
{{ legacyT(schedulingBadge.label) }}
</Badge>
<HardwareTooltip :node="node" />
</div>
</TableCell>
<TableCell class="py-4 min-w-0">
<code
class="block min-w-0 truncate text-xs text-muted-foreground"
:title="proxyNodeAddress(node)"
>{{ proxyNodeAddress(node) }}</code>
</TableCell>
<TableCell class="py-4 min-w-0">
<span
class="block min-w-0 truncate text-sm text-muted-foreground"
:title="legacyT(formatProxyNodeRegion(node.region))"
>{{ legacyT(formatProxyNodeRegion(node.region)) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<Badge
:variant="proxyNodeStatusVariant(node.status)"
:title="legacyT(proxyNodeStatusTitle(node))"
class="font-medium px-2.5 py-0.5 text-xs"
>
{{ legacyT(proxyNodeStatusLabel(node)) }}
</Badge>
</TableCell>
<TableCell class="py-4 text-center">
<span class="text-sm tabular-nums">{{ formatProxyNodeNumber(node.total_requests) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<span
class="text-sm tabular-nums"
:class="proxyNodeFailureRate(node) > 5 ? 'text-destructive font-medium' : ''"
>{{ formatProxyNodeFailureRate(node) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<span class="text-sm tabular-nums">{{ node.avg_latency_ms != null ? `${node.avg_latency_ms.toFixed(0)}ms` : '-' }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<span class="text-sm tabular-nums">{{ node.is_manual ? '-' : proxyNodeVersion(node) }}</span>
</TableCell>
<TableCell class="py-4 min-w-0">
<span class="block min-w-0 truncate text-xs text-muted-foreground">{{ formatProxyNodeTime(node.last_heartbeat_at, locale) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<div class="flex items-center justify-center gap-0.5">
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
:title="testing ? legacyT('测试中...') : legacyT('测试连通性')"
:disabled="testing"
@click="$emit('test')"
>
<Loader2
v-if="testing"
class="h-4 w-4 animate-spin"
/>
<Activity
v-else
class="h-4 w-4"
/>
</Button>
<Button
v-if="node.is_manual"
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('编辑')"
@click="$emit('edit')"
>
<SquarePen class="h-4 w-4" />
</Button>
<Button
v-if="!node.is_manual"
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('远程配置')"
@click="$emit('config')"
>
<Settings class="h-4 w-4" />
</Button>
<Button
v-if="!node.is_manual"
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('连接事件')"
@click="$emit('view-events')"
>
<History class="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('删除')"
@click="$emit('delete')"
>
<Trash2 class="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow
v-if="expanded"
class="border-b border-border/40 hover:bg-transparent"
>
<TableCell
colspan="11"
class="p-0"
<TableRow
class="border-b border-border/40 hover:bg-muted/30 transition-colors"
:class="expanded ? 'bg-muted/20' : ''"
>
<TableCell class="w-[28px] min-w-[28px] max-w-[28px] p-0 pl-2 text-center">
<button
type="button"
class="inline-flex h-5 w-5 items-center justify-center rounded-md text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-1"
:title="expanded ? legacyT('收起数据') : legacyT('展开数据')"
@click="$emit('toggle-details')"
>
<ProxyNodeDataPanel
:node="node"
:state="detailState"
@refresh="$emit('refresh-details')"
<ChevronDown
v-if="expanded"
class="h-3.5 w-3.5"
/>
</TableCell>
</TableRow>
</template>
<ChevronRight
v-else
class="h-3.5 w-3.5"
/>
</button>
</TableCell>
<TableCell class="py-4 min-w-0">
<div class="flex items-center gap-1.5 min-w-0">
<span
class="min-w-0 truncate text-sm font-semibold"
:title="node.name"
>{{ node.name }}</span>
<Badge
v-if="node.is_manual"
variant="outline"
class="text-[10px] px-1.5 py-0"
>
{{ legacyT('手动') }}
</Badge>
<Badge
v-if="node.tunnel_mode"
variant="outline"
class="text-[10px] px-1.5 py-0"
>
Tunnel
</Badge>
<Badge
v-if="schedulingBadge"
:variant="schedulingBadge.variant"
class="text-[10px] px-1.5 py-0"
>
{{ legacyT(schedulingBadge.label) }}
</Badge>
<HardwareTooltip :node="node" />
</div>
</TableCell>
<TableCell class="py-4 min-w-0">
<code
class="block min-w-0 truncate text-xs text-muted-foreground"
:title="proxyNodeAddress(node)"
>{{ proxyNodeAddress(node) }}</code>
</TableCell>
<TableCell class="py-4 min-w-0">
<span
class="block min-w-0 truncate text-sm text-muted-foreground"
:title="legacyT(formatProxyNodeRegion(node.region))"
>{{ legacyT(formatProxyNodeRegion(node.region)) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<Badge
:variant="proxyNodeStatusVariant(node.status)"
:title="legacyT(proxyNodeStatusTitle(node))"
class="font-medium px-2.5 py-0.5 text-xs"
>
{{ legacyT(proxyNodeStatusLabel(node)) }}
</Badge>
</TableCell>
<TableCell class="py-4 text-center">
<span class="text-sm tabular-nums">{{ formatProxyNodeNumber(node.total_requests) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<span
class="text-sm tabular-nums"
:class="proxyNodeFailureRate(node) > 5 ? 'text-destructive font-medium' : ''"
>{{ formatProxyNodeFailureRate(node) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<span class="text-sm tabular-nums">{{ node.avg_latency_ms != null ? `${node.avg_latency_ms.toFixed(0)}ms` : '-' }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<span class="text-sm tabular-nums">{{ node.is_manual ? '-' : proxyNodeVersion(node) }}</span>
</TableCell>
<TableCell class="py-4 min-w-0">
<span class="block min-w-0 truncate text-xs text-muted-foreground">{{ formatProxyNodeTime(node.last_heartbeat_at, locale) }}</span>
</TableCell>
<TableCell class="py-4 text-center">
<div class="flex items-center justify-center gap-0.5">
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
:title="testing ? legacyT('测试中...') : legacyT('测试连通性')"
:disabled="testing"
@click="$emit('test')"
>
<Loader2
v-if="testing"
class="h-4 w-4 animate-spin"
/>
<Activity
v-else
class="h-4 w-4"
/>
</Button>
<Button
v-if="node.is_manual"
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('编辑')"
@click="$emit('edit')"
>
<SquarePen class="h-4 w-4" />
</Button>
<Button
v-if="!node.is_manual"
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('远程配置')"
@click="$emit('config')"
>
<Settings class="h-4 w-4" />
</Button>
<Button
v-if="!node.is_manual"
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('连接事件')"
@click="$emit('view-events')"
>
<History class="h-4 w-4" />
</Button>
<Button
variant="ghost"
size="icon"
class="h-8 w-8"
:title="legacyT('删除')"
@click="$emit('delete')"
>
<Trash2 class="h-4 w-4" />
</Button>
</div>
</TableCell>
</TableRow>
<TableRow
v-if="expanded"
class="border-b border-border/40 hover:bg-transparent"
>
<TableCell
colspan="11"
class="p-0"
>
<ProxyNodeDataPanel
:node="node"
:state="detailState"
@refresh="$emit('refresh-details')"
/>
</TableCell>
</TableRow>
</template>
<script setup lang="ts">