refactor(servers): 精简行操作并收拢批量诊断

一键登录文案、移除监测/站点行按钮,检测路径与诊断移至批量栏。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
r
2026-06-21 11:23:39 +08:00
parent 7add9384c1
commit 58843a1ef4
6 changed files with 77 additions and 102 deletions
@@ -0,0 +1,32 @@
# 服务器列表操作列与批量栏调整
**日期**2026-06-21
## 变更摘要
- 行内宝塔按钮文案改为「一键登录」
- 移除行内「监测」「站点」按钮(站点链接仍在名称/域名列可点)
- 「检测路径」「诊断」从每行操作移至顶部批量操作栏(需勾选服务器)
- 批量「检测路径」文案统一;「诊断」须单选 1 台
## 动机
行操作过多拥挤;路径检测与 Agent 诊断属批量运维动作,与「健康检查」同类。
## 涉及文件
- `frontend/src/components/servers/ServerTableRowActions.vue`
- `frontend/src/components/servers/ServerBatchActionBar.vue`
- `frontend/src/pages/ServersPage.vue`
- `frontend/src/components/servers/ServerUnsetPathPanel.vue`
- `frontend/src/constants/serverTableHeaders.ts`
## 迁移 / 重启
仅前端构建部署。
## 验证
1. `#/servers` 行内见「一键登录」,无「检测路径/诊断」
2. 勾选多台 → 批量栏可「检测路径」
3. 单选 1 台 → 批量栏「诊断」弹出 Agent 诊断对话框
@@ -5,7 +5,8 @@
<v-spacer /> <v-spacer />
<v-btn size="small" variant="text" @click="$emit('batch-category')">修改分类</v-btn> <v-btn size="small" variant="text" @click="$emit('batch-category')">修改分类</v-btn>
<v-btn size="small" variant="text" @click="$emit('health-check')">健康检查</v-btn> <v-btn size="small" variant="text" @click="$emit('health-check')">健康检查</v-btn>
<v-btn size="small" variant="text" @click="$emit('detect-path')">检测目标路径</v-btn> <v-btn size="small" variant="text" @click="$emit('detect-path')">检测路径</v-btn>
<v-btn size="small" variant="text" :loading="agentDiagnoseLoading" @click="$emit('agent-diagnose')">诊断</v-btn>
<v-btn size="small" variant="text" @click="$emit('install-agent')">安装Agent</v-btn> <v-btn size="small" variant="text" @click="$emit('install-agent')">安装Agent</v-btn>
<v-btn size="small" variant="text" @click="$emit('upgrade-agent')">升级Agent</v-btn> <v-btn size="small" variant="text" @click="$emit('upgrade-agent')">升级Agent</v-btn>
<v-btn size="small" variant="text" color="error" @click="$emit('uninstall-agent')">卸载Agent</v-btn> <v-btn size="small" variant="text" color="error" @click="$emit('uninstall-agent')">卸载Agent</v-btn>
@@ -18,12 +19,14 @@
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ defineProps<{
count: number count: number
agentDiagnoseLoading?: boolean
}>() }>()
defineEmits<{ defineEmits<{
'batch-category': [] 'batch-category': []
'health-check': [] 'health-check': []
'detect-path': [] 'detect-path': []
'agent-diagnose': []
'install-agent': [] 'install-agent': []
'upgrade-agent': [] 'upgrade-agent': []
'uninstall-agent': [] 'uninstall-agent': []
@@ -1,16 +1,5 @@
<template> <template>
<div class="server-row-actions d-flex ga-1 align-center justify-end flex-nowrap"> <div class="server-row-actions d-flex ga-1 align-center justify-end flex-nowrap">
<v-btn
:icon="pinnedSlot != null ? 'mdi-check' : 'mdi-plus'"
size="x-small"
variant="text"
:color="pinnedSlot != null ? 'success' : 'primary'"
density="compact"
class="server-row-actions__pin"
:title="pinnedSlot != null ? `已在监测 · 槽 ${pinnedSlot + 1}` : '加入实时监测'"
:loading="pinLoading"
@click.stop="$emit('pin')"
/>
<v-btn <v-btn
variant="text" variant="text"
size="x-small" size="x-small"
@@ -26,23 +15,12 @@
size="x-small" size="x-small"
color="deep-orange" color="deep-orange"
density="compact" density="compact"
class="server-row-actions__btn" class="server-row-actions__btn server-row-actions__btn--wide"
title="一键登录宝塔面板" title="一键登录宝塔面板"
:loading="btLoginLoading" :loading="btLoginLoading"
@click.stop="$emit('bt-login')" @click.stop="$emit('bt-login')"
> >
宝塔 一键登录
</v-btn>
<v-btn
variant="text"
size="x-small"
density="compact"
class="server-row-actions__btn"
:disabled="!siteUrl"
:title="siteUrl ? `打开 ${siteHost || siteUrl}` : '无站点 URL'"
@click.stop="siteUrl && $emit('site')"
>
站点
</v-btn> </v-btn>
<v-btn <v-btn
variant="text" variant="text"
@@ -53,25 +31,6 @@
> >
文件 文件
</v-btn> </v-btn>
<v-btn
variant="text"
size="x-small"
density="compact"
class="server-row-actions__btn"
@click.stop="$emit('detect-path')"
>
检测路径
</v-btn>
<v-btn
variant="text"
size="x-small"
density="compact"
class="server-row-actions__btn"
:loading="diagnoseLoading"
@click.stop="$emit('diagnose')"
>
诊断
</v-btn>
<v-btn <v-btn
variant="text" variant="text"
size="x-small" size="x-small"
@@ -96,34 +55,25 @@
<script setup lang="ts"> <script setup lang="ts">
defineProps<{ defineProps<{
pinnedSlot?: number | null
pinLoading?: boolean
btLoginLoading?: boolean btLoginLoading?: boolean
siteUrl?: string | null
siteHost?: string | null
diagnoseLoading?: boolean
}>() }>()
defineEmits<{ defineEmits<{
pin: []
terminal: [] terminal: []
'bt-login': [] 'bt-login': []
site: []
files: [] files: []
'detect-path': []
diagnose: []
edit: [] edit: []
delete: [] delete: []
}>() }>()
</script> </script>
<style scoped> <style scoped>
.server-row-actions__pin {
flex: 0 0 28px;
}
.server-row-actions__btn { .server-row-actions__btn {
min-width: 52px; min-width: 52px;
padding-inline: 6px; padding-inline: 6px;
} }
.server-row-actions__btn--wide {
min-width: 72px;
}
</style> </style>
@@ -52,9 +52,11 @@
<ServerBatchActionBar <ServerBatchActionBar
:count="selectedCount" :count="selectedCount"
:agent-diagnose-loading="agentDiagnoseLoading"
@batch-category="$emit('batch-category')" @batch-category="$emit('batch-category')"
@health-check="$emit('health-check')" @health-check="$emit('health-check')"
@detect-path="$emit('detect-path')" @detect-path="$emit('detect-path')"
@agent-diagnose="$emit('agent-diagnose')"
@install-agent="$emit('install-agent')" @install-agent="$emit('install-agent')"
@upgrade-agent="$emit('upgrade-agent')" @upgrade-agent="$emit('upgrade-agent')"
@uninstall-agent="$emit('uninstall-agent')" @uninstall-agent="$emit('uninstall-agent')"
@@ -167,19 +169,10 @@
<template #item.actions="{ item }"> <template #item.actions="{ item }">
<ServerTableRowActions <ServerTableRowActions
:pinned-slot="pinnedMap[item.id] ?? null"
:pin-loading="pinLoadingId === item.id"
:bt-login-loading="btLoginLoadingId === item.id" :bt-login-loading="btLoginLoadingId === item.id"
:site-url="resolveServerSiteUrl(item)"
:site-host="resolveServerSiteHost(item)"
:diagnose-loading="agentDiagnoseLoadingId === item.id"
@pin="$emit('pin', item)"
@terminal="$emit('terminal', item)" @terminal="$emit('terminal', item)"
@bt-login="$emit('bt-login', item)" @bt-login="$emit('bt-login', item)"
@site="$emit('site', item)"
@files="$emit('files', item)" @files="$emit('files', item)"
@detect-path="$emit('detect-path-single', item)"
@diagnose="$emit('agent-diagnose', item)"
@edit="$emit('edit', item)" @edit="$emit('edit', item)"
@delete="$emit('delete', item)" @delete="$emit('delete', item)"
/> />
@@ -214,7 +207,6 @@ import ServerInlineDetail from '@/components/servers/ServerInlineDetail.vue'
import ServerBatchActionBar from '@/components/servers/ServerBatchActionBar.vue' import ServerBatchActionBar from '@/components/servers/ServerBatchActionBar.vue'
import ServerTableRowActions from '@/components/servers/ServerTableRowActions.vue' import ServerTableRowActions from '@/components/servers/ServerTableRowActions.vue'
import { SERVER_DATA_TABLE_HEADERS } from '@/constants/serverTableHeaders' import { SERVER_DATA_TABLE_HEADERS } from '@/constants/serverTableHeaders'
import { resolveServerSiteHost, resolveServerSiteUrl } from '@/utils/serverSiteUrl'
const props = defineProps<{ const props = defineProps<{
highlight?: boolean highlight?: boolean
@@ -231,10 +223,8 @@ const props = defineProps<{
editingPathId: number | null editingPathId: number | null
editingPathValue: string editingPathValue: string
savingPathId: number | null savingPathId: number | null
pinnedMap: Record<number, number>
pinLoadingId: number | null
btLoginLoadingId: number | null btLoginLoadingId: number | null
agentDiagnoseLoadingId: number | null agentDiagnoseLoading: boolean
}>() }>()
const emit = defineEmits<{ const emit = defineEmits<{
@@ -250,10 +240,6 @@ const emit = defineEmits<{
terminal: [item: ServerApiItem] terminal: [item: ServerApiItem]
'bt-login': [item: ServerApiItem] 'bt-login': [item: ServerApiItem]
files: [item: ServerApiItem] files: [item: ServerApiItem]
site: [item: ServerApiItem]
pin: [item: ServerApiItem]
'detect-path-single': [item: ServerApiItem]
'agent-diagnose': [item: ServerApiItem]
edit: [item: ServerApiItem] edit: [item: ServerApiItem]
delete: [item: ServerApiItem] delete: [item: ServerApiItem]
'edit-path': [item: ServerApiItem] 'edit-path': [item: ServerApiItem]
@@ -262,6 +248,7 @@ const emit = defineEmits<{
'batch-category': [] 'batch-category': []
'health-check': [] 'health-check': []
'detect-path': [] 'detect-path': []
'agent-diagnose': []
'install-agent': [] 'install-agent': []
'upgrade-agent': [] 'upgrade-agent': []
'uninstall-agent': [] 'uninstall-agent': []
+1 -1
View File
@@ -7,5 +7,5 @@ export const SERVER_DATA_TABLE_HEADERS = [
{ title: '目标路径', key: 'target_path', width: 200 }, { title: '目标路径', key: 'target_path', width: 200 },
{ title: 'Agent', key: 'agent_version', width: 100 }, { title: 'Agent', key: 'agent_version', width: 100 },
{ title: '心跳', key: 'last_heartbeat', width: 120 }, { title: '心跳', key: 'last_heartbeat', width: 120 },
{ title: '操作', key: 'actions', width: 380, align: 'end' as const, sortable: false }, { title: '操作', key: 'actions', width: 280, align: 'end' as const, sortable: false },
] ]
+30 -27
View File
@@ -49,10 +49,8 @@
:editing-path-id="editingPathId" :editing-path-id="editingPathId"
:editing-path-value="editingPathValue" :editing-path-value="editingPathValue"
:saving-path-id="savingPathId" :saving-path-id="savingPathId"
:pinned-map="pinnedMap"
:pin-loading-id="pinLoadingId"
:bt-login-loading-id="btLoginLoadingId" :bt-login-loading-id="btLoginLoadingId"
:agent-diagnose-loading-id="agentDiagnoseLoadingId" :agent-diagnose-loading="agentDiagnoseLoading"
@refresh="loadUnsetPathServers()" @refresh="loadUnsetPathServers()"
@update:page="onUnsetPageChange" @update:page="onUnsetPageChange"
@update:items-per-page="onUnsetItemsPerPageChange" @update:items-per-page="onUnsetItemsPerPageChange"
@@ -63,10 +61,6 @@
@terminal="openTerminal" @terminal="openTerminal"
@bt-login="openBtLogin($event.id)" @bt-login="openBtLogin($event.id)"
@files="openFiles" @files="openFiles"
@site="openBrowser"
@pin="onPinServer"
@detect-path-single="detectPathSingle"
@agent-diagnose="openAgentDiagnose"
@edit="editServer" @edit="editServer"
@delete="confirmDelete" @delete="confirmDelete"
@edit-path="startEditPath" @edit-path="startEditPath"
@@ -75,6 +69,7 @@
@batch-category="openBatchCategory('unset')" @batch-category="openBatchCategory('unset')"
@health-check="batchHealthCheck('unset')" @health-check="batchHealthCheck('unset')"
@detect-path="openDetectPathConfirm('unset')" @detect-path="openDetectPathConfirm('unset')"
@agent-diagnose="batchAgentDiagnose('unset')"
@install-agent="batchInstallAgent('unset')" @install-agent="batchInstallAgent('unset')"
@upgrade-agent="batchUpgradeAgent('unset')" @upgrade-agent="batchUpgradeAgent('unset')"
@uninstall-agent="batchUninstallAgent('unset')" @uninstall-agent="batchUninstallAgent('unset')"
@@ -182,9 +177,11 @@
<!-- Batch Actions --> <!-- Batch Actions -->
<ServerBatchActionBar <ServerBatchActionBar
:count="selectedIds.size" :count="selectedIds.size"
:agent-diagnose-loading="agentDiagnoseLoading"
@batch-category="openBatchCategory('main')" @batch-category="openBatchCategory('main')"
@health-check="batchHealthCheck('main')" @health-check="batchHealthCheck('main')"
@detect-path="openDetectPathConfirm('main')" @detect-path="openDetectPathConfirm('main')"
@agent-diagnose="batchAgentDiagnose('main')"
@install-agent="batchInstallAgent('main')" @install-agent="batchInstallAgent('main')"
@upgrade-agent="batchUpgradeAgent('main')" @upgrade-agent="batchUpgradeAgent('main')"
@uninstall-agent="batchUninstallAgent('main')" @uninstall-agent="batchUninstallAgent('main')"
@@ -328,19 +325,10 @@
<template #item.actions="{ item }"> <template #item.actions="{ item }">
<ServerTableRowActions <ServerTableRowActions
:pinned-slot="pinnedMap[item.id] ?? null"
:pin-loading="pinLoadingId === item.id"
:bt-login-loading="btLoginLoadingId === item.id" :bt-login-loading="btLoginLoadingId === item.id"
:site-url="resolveServerSiteUrl(item)"
:site-host="resolveServerSiteHost(item)"
:diagnose-loading="agentDiagnoseLoadingId === item.id"
@pin="onPinServer(item)"
@terminal="openTerminal(item)" @terminal="openTerminal(item)"
@bt-login="openBtLogin(item.id)" @bt-login="openBtLogin(item.id)"
@site="openBrowser(item)"
@files="openFiles(item)" @files="openFiles(item)"
@detect-path="detectPathSingle(item)"
@diagnose="openAgentDiagnose(item)"
@edit="editServer(item)" @edit="editServer(item)"
@delete="confirmDelete(item)" @delete="confirmDelete(item)"
/> />
@@ -1086,14 +1074,6 @@ function onUnsetItemsPerPageChange(n: number) {
void loadUnsetPathServers() void loadUnsetPathServers()
} }
async function detectPathSingle(item: ServerApiItem) {
try {
await submitServerBatchJob('detect-path', [item.id])
} catch (e: unknown) {
snackbar(formatApiError(e, '提交检测任务失败'), 'error')
}
}
function clearExpandedForPathMigration(serverId: number, newPath: string) { function clearExpandedForPathMigration(serverId: number, newPath: string) {
const unset = isUnsetTargetPath(newPath) const unset = isUnsetTargetPath(newPath)
if (expandedMainIds.value.includes(serverId) && unset) expandedMainIds.value = [] if (expandedMainIds.value.includes(serverId) && unset) expandedMainIds.value = []
@@ -1571,17 +1551,41 @@ function openFiles(item: ServerApiItem) {
const showAgentDiagnose = ref(false) const showAgentDiagnose = ref(false)
const agentDiagnoseLoading = ref(false) const agentDiagnoseLoading = ref(false)
const agentDiagnoseLoadingId = ref<number | null>(null)
const agentActionLoadingId = ref<number | null>(null) const agentActionLoadingId = ref<number | null>(null)
const agentDiagnoseResult = ref<AgentDiagnoseResult | null>(null) const agentDiagnoseResult = ref<AgentDiagnoseResult | null>(null)
const agentDiagnoseTarget = ref<ServerApiItem | null>(null) const agentDiagnoseTarget = ref<ServerApiItem | null>(null)
function resolveBatchServerItem(scope: BatchScope, serverId: number): ServerApiItem | undefined {
const { items } = batchSelection(scope)
const fromSelection = items.value.find(
(entry): entry is ServerApiItem => typeof entry === 'object' && entry.id === serverId,
)
if (fromSelection) return fromSelection
const list = scope === 'main' ? servers.value : unsetPathServers.value
return list.find(s => s.id === serverId)
}
async function batchAgentDiagnose(scope: BatchScope) {
batchScope.value = scope
const { items } = batchSelection(scope)
const ids = normalizeServerIds(items.value)
if (ids.length !== 1) {
snackbar(ids.length === 0 ? '请先选择 1 台服务器' : 'Agent 诊断每次仅支持单选 1 台', 'warning')
return
}
const item = resolveBatchServerItem(scope, ids[0])
if (!item) {
snackbar('未找到所选服务器', 'error')
return
}
await openAgentDiagnose(item)
}
async function openAgentDiagnose(item: ServerApiItem) { async function openAgentDiagnose(item: ServerApiItem) {
agentDiagnoseTarget.value = item agentDiagnoseTarget.value = item
agentDiagnoseResult.value = null agentDiagnoseResult.value = null
showAgentDiagnose.value = true showAgentDiagnose.value = true
agentDiagnoseLoading.value = true agentDiagnoseLoading.value = true
agentDiagnoseLoadingId.value = item.id
try { try {
agentDiagnoseResult.value = await http.post<AgentDiagnoseResult>(`/servers/${item.id}/agent-diagnose`, {}) agentDiagnoseResult.value = await http.post<AgentDiagnoseResult>(`/servers/${item.id}/agent-diagnose`, {})
} catch (e: unknown) { } catch (e: unknown) {
@@ -1589,7 +1593,6 @@ async function openAgentDiagnose(item: ServerApiItem) {
snackbar(formatApiError(e, 'Agent 诊断失败'), 'error') snackbar(formatApiError(e, 'Agent 诊断失败'), 'error')
} finally { } finally {
agentDiagnoseLoading.value = false agentDiagnoseLoading.value = false
agentDiagnoseLoadingId.value = null
} }
} }