feat(btpanel): 入口改为服务器列表
面板登录页改为全量表格与行内一键登录,侧栏首项更名为服务器列表。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,7 +4,9 @@
|
||||
|
||||
## 摘要
|
||||
|
||||
将 commit `82426b19`(独立宝塔模块 + SSH 自动获取 API)部署至生产 `api.synaglobal.vip`。
|
||||
将 `82426b19` + `d00294c8`(独立宝塔模块 + SSH 自动获取 API)部署至生产 `api.synaglobal.vip`。
|
||||
|
||||
**部署结果(2026-06-21)**:Gitea push → 生产 `git reset --hard origin/main` → `nexus-1panel.sh upgrade` 成功;容器 `nexus-prod-nexus-1` healthy;`/health` ok;容器内注册 28 条 `/api/btpanel/*` 路由。
|
||||
|
||||
## 变更范围
|
||||
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
# Changelog — 宝塔入口改为服务器列表(2026-06-21)
|
||||
|
||||
**日期**:2026-06-21
|
||||
|
||||
## 摘要
|
||||
|
||||
将「宝塔 · 面板登录」从单台下拉选择改为主入口**服务器列表**,侧栏首项更名为「服务器列表」。
|
||||
|
||||
## 动机
|
||||
|
||||
- 2000+ 子机需一览配置状态与一键登录,单台 picker 不符合运维习惯
|
||||
- 与 Nexus 服务器列表心智一致:先选机再操作
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `frontend/src/pages/btpanel/BtPanelLoginPage.vue` — `v-data-table` 全量列表 + 搜索 + 行内「一键登录」
|
||||
- `frontend/src/App.vue` — 侧栏文案与图标
|
||||
- `frontend/src/router/index.ts` — `/btpanel/servers` 重定向至 `/btpanel/login`
|
||||
|
||||
## 迁移 / 重启
|
||||
|
||||
- 仅前端;`vite build` 或 Docker upgrade 后生效
|
||||
|
||||
## 验证
|
||||
|
||||
```bash
|
||||
cd frontend && npx vite build
|
||||
# 登录 → 宝塔面板 → 服务器列表 → 表格展示全部服务器 → 行内一键登录
|
||||
```
|
||||
@@ -394,7 +394,7 @@ const sysItems = [
|
||||
]
|
||||
|
||||
const btPanelItems = [
|
||||
{ to: '/btpanel/login', title: '面板登录', icon: 'mdi-login' },
|
||||
{ to: '/btpanel/login', title: '服务器列表', icon: 'mdi-server' },
|
||||
{ to: '/btpanel/monitor', title: '系统监控', icon: 'mdi-speedometer' },
|
||||
{ to: '/btpanel/sites', title: '网站列表', icon: 'mdi-web' },
|
||||
{ to: '/btpanel/sites/create', title: '创建网站', icon: 'mdi-web-plus' },
|
||||
|
||||
@@ -1,37 +1,141 @@
|
||||
<template>
|
||||
<BtPanelPageShell title="宝塔 · 面板登录" subtitle="生成临时链接,免输密码打开宝塔(A1/A5)">
|
||||
<template #default="{ serverId, server }">
|
||||
<v-card variant="outlined" rounded="lg">
|
||||
<v-card-text>
|
||||
<v-chip v-if="server" size="small" class="mb-3" :color="server.configured ? 'success' : 'warning'" variant="tonal">
|
||||
{{ server.configured ? 'API 已配置' : '未配置 API,将尝试 SSH 生成链接' }}
|
||||
</v-chip>
|
||||
<div class="d-flex flex-wrap ga-2">
|
||||
<v-btn color="primary" :loading="loading" prepend-icon="mdi-open-in-new" @click="openLogin(serverId!)">
|
||||
一键登录宝塔
|
||||
</v-btn>
|
||||
<v-btn variant="tonal" :to="`/btpanel/settings?server_id=${serverId}`">连接配置</v-btn>
|
||||
</div>
|
||||
<p class="text-caption text-medium-emphasis mt-4 mb-0">
|
||||
链接约 3 小时内有效、一次性使用。请确保本机浏览器能访问该服务器面板端口。
|
||||
</p>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
</BtPanelPageShell>
|
||||
<v-container fluid class="pa-6">
|
||||
<div class="d-flex flex-wrap align-center ga-3 mb-4">
|
||||
<div>
|
||||
<h1 class="text-h6 font-weight-bold">宝塔 · 服务器列表</h1>
|
||||
<p class="text-caption text-medium-emphasis mb-0">
|
||||
选择服务器一键打开宝塔面板(临时链接约 3 小时有效)
|
||||
</p>
|
||||
</div>
|
||||
<v-spacer />
|
||||
<v-text-field
|
||||
v-model="search"
|
||||
density="compact"
|
||||
hide-details
|
||||
clearable
|
||||
prepend-inner-icon="mdi-magnify"
|
||||
label="搜索名称 / IP / 分类"
|
||||
style="max-width: 320px; min-width: 200px"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<v-data-table
|
||||
:headers="headers"
|
||||
:items="filteredServers"
|
||||
:loading="loading"
|
||||
density="compact"
|
||||
item-value="id"
|
||||
hover
|
||||
>
|
||||
<template #item.configured="{ item }">
|
||||
<v-chip size="x-small" :color="item.configured ? 'success' : 'warning'" variant="tonal">
|
||||
{{ item.configured ? 'API 已配置' : '未配置' }}
|
||||
</v-chip>
|
||||
</template>
|
||||
<template #item.bootstrap_state="{ item }">
|
||||
<v-chip
|
||||
v-if="item.bootstrap_state"
|
||||
size="x-small"
|
||||
:color="bootstrapChipColor(item)"
|
||||
variant="tonal"
|
||||
>
|
||||
{{ bootstrapLabel(item.bootstrap_state) }}
|
||||
</v-chip>
|
||||
<span v-else class="text-medium-emphasis text-caption">—</span>
|
||||
</template>
|
||||
<template #item.base_url="{ item }">
|
||||
<span class="text-caption text-truncate d-inline-block" style="max-width: 220px">
|
||||
{{ item.base_url || '—' }}
|
||||
</span>
|
||||
</template>
|
||||
<template #item.actions="{ item }">
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
prepend-icon="mdi-open-in-new"
|
||||
:loading="loginLoadingId === item.id"
|
||||
@click="openLogin(item.id)"
|
||||
>
|
||||
一键登录
|
||||
</v-btn>
|
||||
<v-btn
|
||||
size="small"
|
||||
variant="text"
|
||||
class="ml-1"
|
||||
:to="`/btpanel/settings?server_id=${item.id}`"
|
||||
>
|
||||
连接配置
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import BtPanelPageShell from '@/components/btpanel/BtPanelPageShell.vue'
|
||||
import { createBtLoginUrl } from '@/api/btpanel'
|
||||
import { ref } from 'vue'
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import { createBtLoginUrl, listBtServers, type BtPanelServerRow } from '@/api/btpanel'
|
||||
|
||||
defineOptions({ name: 'BtPanelLoginPage' })
|
||||
|
||||
const loading = ref(false)
|
||||
const loginLoadingId = ref<number | null>(null)
|
||||
const search = ref('')
|
||||
const servers = ref<BtPanelServerRow[]>([])
|
||||
|
||||
const headers = [
|
||||
{ title: '名称', key: 'name', minWidth: 120 },
|
||||
{ title: '地址', key: 'domain', minWidth: 120 },
|
||||
{ title: '分类', key: 'category', minWidth: 80 },
|
||||
{ title: 'API', key: 'configured', width: 110, sortable: false },
|
||||
{ title: '引导', key: 'bootstrap_state', width: 100, sortable: false },
|
||||
{ title: '面板地址', key: 'base_url', minWidth: 160, sortable: false },
|
||||
{ title: '操作', key: 'actions', width: 200, sortable: false, align: 'end' as const },
|
||||
]
|
||||
|
||||
const bootstrapStateLabels: Record<string, string> = {
|
||||
pending: '等待获取',
|
||||
installing: '安装中',
|
||||
ready: '已完成',
|
||||
failed: 'SSH 失败',
|
||||
disabled: '已关闭',
|
||||
}
|
||||
|
||||
function bootstrapLabel(state: string) {
|
||||
return bootstrapStateLabels[state] ?? state
|
||||
}
|
||||
|
||||
function bootstrapChipColor(item: BtPanelServerRow) {
|
||||
if (item.configured || item.bootstrap_state === 'ready') return 'success'
|
||||
if (item.bootstrap_state === 'failed') return 'error'
|
||||
if (item.bootstrap_state === 'installing') return 'warning'
|
||||
return 'default'
|
||||
}
|
||||
|
||||
const filteredServers = computed(() => {
|
||||
const q = search.value.trim().toLowerCase()
|
||||
if (!q) return servers.value
|
||||
return servers.value.filter(s =>
|
||||
[s.name, s.domain, s.category ?? '', s.base_url ?? '']
|
||||
.join(' ')
|
||||
.toLowerCase()
|
||||
.includes(q),
|
||||
)
|
||||
})
|
||||
|
||||
async function loadServers() {
|
||||
loading.value = true
|
||||
try {
|
||||
servers.value = await listBtServers()
|
||||
} catch (e: unknown) {
|
||||
window.$snackbar?.(e instanceof Error ? e.message : '加载服务器列表失败', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function openLogin(serverId: number) {
|
||||
loading.value = true
|
||||
loginLoadingId.value = serverId
|
||||
try {
|
||||
const { url } = await createBtLoginUrl(serverId)
|
||||
window.open(url, '_blank', 'noopener,noreferrer')
|
||||
@@ -39,7 +143,9 @@ async function openLogin(serverId: number) {
|
||||
} catch (e: unknown) {
|
||||
window.$snackbar?.(e instanceof Error ? e.message : '生成链接失败', 'error')
|
||||
} finally {
|
||||
loading.value = false
|
||||
loginLoadingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => { void loadServers() })
|
||||
</script>
|
||||
|
||||
@@ -23,6 +23,7 @@ const routes = [
|
||||
{ path: '/login', name: 'Login', component: () => import('@/pages/LoginPage.vue'), meta: { public: true } },
|
||||
// ── 宝塔面板(独立模块,不与运维页合并)──
|
||||
{ path: '/btpanel/login', name: 'BtPanelLogin', component: () => import('@/pages/btpanel/BtPanelLoginPage.vue') },
|
||||
{ path: '/btpanel/servers', redirect: '/btpanel/login' },
|
||||
{ path: '/btpanel/monitor', name: 'BtPanelMonitor', component: () => import('@/pages/btpanel/BtPanelMonitorPage.vue') },
|
||||
{ path: '/btpanel/sites', name: 'BtPanelSites', component: () => import('@/pages/btpanel/BtPanelSitesPage.vue') },
|
||||
{ path: '/btpanel/sites/create', name: 'BtPanelSiteCreate', component: () => import('@/pages/btpanel/BtPanelSiteCreatePage.vue') },
|
||||
|
||||
Reference in New Issue
Block a user