feat(subsystem): 子系统导航增加可展开子 Tab 与详情页
- AppSidebar: 统一入口组下子系统导航改为可展开列表,展示 6 个子系统子 Tab - 每个子 Tab 带功能描述前缀(如 多集群发布平台 Wayne)便于用户定位 - 新建 SubsystemDetail.vue 统一详情模板(统计卡片 + 门户卡片 + 打开按钮) - 新增路由 /subsystem/detail/:name - subsystem API mock 数据补充 label、domain、category 字段 - SubsystemCard 概览卡片同步显示 label + name 格式
This commit is contained in:
@@ -5,9 +5,12 @@ import { redirectToSSO } from '@/utils/sso'
|
||||
export interface Subsystem {
|
||||
id: number
|
||||
name: string
|
||||
label: string
|
||||
description: string
|
||||
icon: string
|
||||
url: string
|
||||
domain: string
|
||||
category: string
|
||||
status: 'integrated' | 'integrating'
|
||||
sso_enabled: boolean
|
||||
}
|
||||
@@ -21,54 +24,72 @@ const mockSubsystems: Subsystem[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Wayne',
|
||||
description: '多集群容器管理平台',
|
||||
label: '多集群发布平台',
|
||||
description: '业务容器发布、命名空间与配额管理,复用 Wayne 原生多租户能力。',
|
||||
icon: 'W',
|
||||
url: 'https://wayne.qiniu.com',
|
||||
domain: 'wayne.xinfra.internal',
|
||||
category: '容器管理',
|
||||
status: 'integrated',
|
||||
sso_enabled: true,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'CloudDM',
|
||||
description: '数据库管理与SQL审核',
|
||||
label: 'SQL 审核平台',
|
||||
description: '数据库 SQL 上线统一审核,支持 LDAP 用户组到角色的自动映射。',
|
||||
icon: 'DM',
|
||||
url: 'https://clouddm.qiniu.com',
|
||||
domain: 'clouddm.xinfra.internal',
|
||||
category: '数据库',
|
||||
status: 'integrated',
|
||||
sso_enabled: true,
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'CacheCloud',
|
||||
description: 'Redis 云管理平台',
|
||||
label: 'Redis 管理平台',
|
||||
description: 'Redis 实例全生命周期管理,登录与监控运维统一入口。',
|
||||
icon: 'CC',
|
||||
url: 'https://cachecloud.qiniu.com',
|
||||
status: 'integrated',
|
||||
domain: 'cachecloud.xinfra.internal',
|
||||
category: '缓存',
|
||||
status: 'integrating',
|
||||
sso_enabled: true,
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: 'Apollo',
|
||||
description: '配置中心',
|
||||
label: '配置中心',
|
||||
description: '统一配置管理,按机房 Cluster 隔离,支持灰度发布、版本回滚与变更审计。',
|
||||
icon: 'AP',
|
||||
url: 'https://apollo.xinfra.internal',
|
||||
status: 'integrated',
|
||||
domain: 'apollo.xinfra.internal',
|
||||
category: '配置中心',
|
||||
status: 'integrating',
|
||||
sso_enabled: true,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: 'qpass',
|
||||
description: '密码管理平台',
|
||||
label: '七牛统一运维平台',
|
||||
description: '夜莺、Zabbix、VictoriaMetrics 告警统一收敛与值班通知,按 P0/P1 分级推送。',
|
||||
icon: 'QP',
|
||||
url: 'https://qpass.xinfra.internal',
|
||||
domain: 'qpass.xinfra.internal',
|
||||
category: '告警值班',
|
||||
status: 'integrated',
|
||||
sso_enabled: true,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Grafana',
|
||||
description: '监控可视化平台',
|
||||
label: '指标可视化平台',
|
||||
description: '对接 VictoriaMetrics 数据源,K8s / 容器 / 业务指标统一仪表盘展示。',
|
||||
icon: 'GF',
|
||||
url: 'https://grafana.xinfra.internal',
|
||||
domain: 'grafana.xinfra.internal',
|
||||
category: '可观测性',
|
||||
status: 'integrated',
|
||||
sso_enabled: true,
|
||||
},
|
||||
@@ -83,6 +104,10 @@ export const subsystemApi = {
|
||||
})
|
||||
},
|
||||
|
||||
getSubsystemByName(name: string): Subsystem | undefined {
|
||||
return mockSubsystems.find((s) => s.name.toLowerCase() === name.toLowerCase())
|
||||
},
|
||||
|
||||
async getSSOUrl(id: number): Promise<ApiResponse<SSOResponse>> {
|
||||
const subsystem = mockSubsystems.find((s) => s.id === id)
|
||||
if (!subsystem) {
|
||||
|
||||
@@ -35,9 +35,28 @@
|
||||
</div>
|
||||
<div class="nav-group">
|
||||
<div class="nav-label">统一入口</div>
|
||||
<router-link to="/subsystem" class="nav-item" active-class="active">
|
||||
<span class="ic">↗</span>子系统导航
|
||||
</router-link>
|
||||
<div class="nav-item nav-expand" :class="{ active: isPortalActive }">
|
||||
<router-link to="/subsystem" class="expand-label">
|
||||
<span class="ic">↗</span>子系统导航
|
||||
</router-link>
|
||||
<span class="expand-toggle" @click="portalExpanded = !portalExpanded">
|
||||
{{ portalExpanded ? '▾' : '▸' }}
|
||||
</span>
|
||||
</div>
|
||||
<div v-show="portalExpanded" class="nav-children">
|
||||
<router-link
|
||||
v-for="sys in subsystems"
|
||||
:key="sys.name"
|
||||
:to="`/subsystem/detail/${sys.name.toLowerCase()}`"
|
||||
class="nav-item nav-child"
|
||||
active-class="active"
|
||||
>
|
||||
<span class="child-dot" :style="{ background: dotColor(sys.icon) }"></span>{{ sys.label }} {{ sys.name }}
|
||||
</router-link>
|
||||
<router-link to="/subsystem" class="nav-item nav-child" active-class="active">
|
||||
<span class="ic" style="font-size:12px;">▦</span>全部总览
|
||||
</router-link>
|
||||
</div>
|
||||
<router-link to="/subsystem/authz" class="nav-item" active-class="active">
|
||||
<span class="ic">▦</span>子系统赋权
|
||||
</router-link>
|
||||
@@ -58,6 +77,36 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
|
||||
const route = useRoute()
|
||||
const portalExpanded = ref(true)
|
||||
|
||||
const subsystems = [
|
||||
{ name: 'Wayne', label: '多集群发布平台', icon: 'W' },
|
||||
{ name: 'CloudDM', label: 'SQL 审核平台', icon: 'DM' },
|
||||
{ name: 'CacheCloud', label: 'Redis 管理平台', icon: 'CC' },
|
||||
{ name: 'Apollo', label: '配置中心', icon: 'AP' },
|
||||
{ name: 'qpass', label: '七牛统一运维平台', icon: 'QP' },
|
||||
{ name: 'Grafana', label: '指标可视化平台', icon: 'GF' },
|
||||
]
|
||||
|
||||
const isPortalActive = computed(() => {
|
||||
return route.path.startsWith('/subsystem/detail/') || route.path === '/subsystem'
|
||||
})
|
||||
|
||||
function dotColor(icon: string): string {
|
||||
const colors: Record<string, string> = {
|
||||
W: 'var(--tag-blue-text)',
|
||||
DM: 'var(--tag-green-text)',
|
||||
CC: 'var(--err)',
|
||||
AP: 'var(--tag-blue-text)',
|
||||
QP: 'var(--tag-amber-text)',
|
||||
GF: 'var(--warn)',
|
||||
}
|
||||
return colors[icon] || 'var(--text-dim)'
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -117,6 +166,10 @@
|
||||
border-color: var(--accent);
|
||||
}
|
||||
|
||||
.nav-item.active .expand-label {
|
||||
color: var(--accent);
|
||||
}
|
||||
|
||||
.nav-item .badge {
|
||||
margin-left: auto;
|
||||
font-family: var(--mono);
|
||||
@@ -142,4 +195,50 @@
|
||||
color: var(--text-dim);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.nav-expand {
|
||||
justify-content: flex-start;
|
||||
padding: 0;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.expand-label {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex: 1;
|
||||
padding: 10px;
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.expand-toggle {
|
||||
padding: 10px;
|
||||
font-size: 12px;
|
||||
opacity: 0.6;
|
||||
cursor: pointer;
|
||||
border-radius: 0 6px 6px 0;
|
||||
}
|
||||
|
||||
.expand-toggle:hover {
|
||||
opacity: 1;
|
||||
background: var(--bg-panel-2);
|
||||
}
|
||||
|
||||
.nav-children {
|
||||
padding-left: 8px;
|
||||
}
|
||||
|
||||
.nav-child {
|
||||
padding: 7px 10px 7px 26px;
|
||||
font-size: 14px;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.child-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
{{ system.icon }}
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ system.name }}</h4>
|
||||
<div class="ext-sub">{{ system.description }}</div>
|
||||
<h4>{{ system.label }} {{ system.name }}</h4>
|
||||
<div class="ext-sub">{{ system.domain }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{ description }}</p>
|
||||
<p>{{ system.description }}</p>
|
||||
<div class="sso-row">
|
||||
<span class="sso-dot" :class="{ warn: system.status === 'integrating' }"></span>
|
||||
{{ system.status === 'integrated' ? 'LDAP 原生配置接入 · 零代码' : 'LDAP 接入改造中' }}
|
||||
@@ -49,18 +49,6 @@ const iconColor = computed(() => {
|
||||
return colors[props.system.icon] || 'var(--text-mid)'
|
||||
})
|
||||
|
||||
const description = computed(() => {
|
||||
const descs: Record<string, string> = {
|
||||
Wayne: '业务容器发布、命名空间与配额管理,复用 Wayne 原生多租户能力。',
|
||||
CloudDM: '数据库 SQL 上线统一审核,支持 LDAP 用户组到角色的自动映射。',
|
||||
CacheCloud: 'Redis 实例全生命周期管理,登录与监控运维统一入口。',
|
||||
Apollo: '统一配置管理,按机房 Cluster 隔离,支持灰度发布、版本回滚与变更审计。',
|
||||
qpass: '夜莺、Zabbix、VictoriaMetrics 告警统一收敛与值班通知,按 P0/P1 分级推送。',
|
||||
Grafana: '对接 VictoriaMetrics 数据源,K8s / 容器 / 业务指标统一仪表盘展示。',
|
||||
}
|
||||
return descs[props.system.name] || ''
|
||||
})
|
||||
|
||||
const handleClick = async () => {
|
||||
if (props.system.sso_enabled) {
|
||||
const { data } = await subsystemApi.getSSOUrl(props.system.id)
|
||||
|
||||
@@ -34,6 +34,12 @@ const router = createRouter({
|
||||
component: () => import('@/views/subsystem/Navigation.vue'),
|
||||
meta: { title: '子系统导航' },
|
||||
},
|
||||
{
|
||||
path: 'subsystem/detail/:name',
|
||||
name: 'SubsystemDetail',
|
||||
component: () => import('@/views/subsystem/SubsystemDetail.vue'),
|
||||
meta: { title: '子系统详情' },
|
||||
},
|
||||
{
|
||||
path: 'subsystem/authz',
|
||||
name: 'SubsystemAuthz',
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
<template>
|
||||
<div v-if="system">
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>{{ system.name }}</h1>
|
||||
<p>{{ system.description }} · {{ system.domain }}</p>
|
||||
</div>
|
||||
<span class="env-pill" :class="system.status === 'integrated' ? '' : 'warn'">
|
||||
{{ system.status === 'integrated' ? '● 已接入' : '● 接入中' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="stat-row">
|
||||
<div class="stat-card">
|
||||
<div class="label">接入状态</div>
|
||||
<div class="value" :class="system.status === 'integrated' ? 'accent' : 'warn'">
|
||||
{{ system.status === 'integrated' ? '已接入' : '接入中' }}
|
||||
</div>
|
||||
<div class="delta">{{ system.category }}</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">SSO 登录</div>
|
||||
<div class="value" :class="system.sso_enabled ? 'accent' : ''">
|
||||
{{ system.sso_enabled ? '已启用' : '未启用' }}
|
||||
</div>
|
||||
<div class="delta">LDAP 原生配置 · 同账号同密码</div>
|
||||
</div>
|
||||
<div class="stat-card">
|
||||
<div class="label">访问域名</div>
|
||||
<div class="value" style="font-size:14px;">{{ system.domain }}</div>
|
||||
<div class="delta">内网 DNS 解析</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="panel">
|
||||
<div class="panel-head">
|
||||
<h3>统一入口</h3>
|
||||
<span class="meta">{{ system.name }} · LDAP 单点登录</span>
|
||||
</div>
|
||||
<div class="panel-body" style="padding:16px;">
|
||||
<div class="ext-card" style="max-width:560px;">
|
||||
<div class="ext-top">
|
||||
<div class="ext-logo" :style="{ background: bgColor, color: iconColor }">
|
||||
{{ system.icon }}
|
||||
</div>
|
||||
<div>
|
||||
<h4>{{ system.name }}</h4>
|
||||
<div class="ext-sub">{{ system.domain }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<p>{{ detailDesc }}</p>
|
||||
<div class="sso-row">
|
||||
<span class="sso-dot" :class="{ warn: system.status === 'integrating' }"></span>
|
||||
{{ system.status === 'integrated' ? 'LDAP 原生配置接入 · 已上线' : 'LDAP 接入改造中' }}
|
||||
</div>
|
||||
<button class="btn btn-primary" style="margin-top:8px;align-self:flex-start;" @click="openPortal">
|
||||
打开 {{ system.name }} ↗
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else class="page-head">
|
||||
<div><h1>子系统未找到</h1><p>请从左侧导航选择一个子系统</p></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { subsystemApi, type Subsystem } from '@/api/subsystem'
|
||||
import { getToken } from '@/utils/auth'
|
||||
import { redirectToSSO } from '@/utils/sso'
|
||||
|
||||
const route = useRoute()
|
||||
|
||||
const system = computed<Subsystem | undefined>(() => {
|
||||
const name = route.params.name as string
|
||||
return subsystemApi.getSubsystemByName(name)
|
||||
})
|
||||
|
||||
const bgColor = computed(() => {
|
||||
if (!system.value) return 'var(--bg-panel-2)'
|
||||
const colors: Record<string, string> = {
|
||||
W: 'var(--logo-w-bg)',
|
||||
DM: 'var(--logo-dm-bg)',
|
||||
CC: 'var(--logo-cc-bg)',
|
||||
AP: 'var(--logo-ap-bg)',
|
||||
QP: 'var(--logo-qp-bg)',
|
||||
GF: 'var(--logo-gf-bg)',
|
||||
}
|
||||
return colors[system.value.icon] || 'var(--bg-panel-2)'
|
||||
})
|
||||
|
||||
const iconColor = computed(() => {
|
||||
if (!system.value) return 'var(--text-mid)'
|
||||
const colors: Record<string, string> = {
|
||||
W: 'var(--tag-blue-text)',
|
||||
DM: 'var(--tag-green-text)',
|
||||
CC: 'var(--err)',
|
||||
AP: 'var(--tag-blue-text)',
|
||||
QP: 'var(--tag-amber-text)',
|
||||
GF: 'var(--warn)',
|
||||
}
|
||||
return colors[system.value.icon] || 'var(--text-mid)'
|
||||
})
|
||||
|
||||
const detailDesc = computed(() => {
|
||||
if (!system.value) return ''
|
||||
const descs: Record<string, string> = {
|
||||
Wayne: '业务容器发布、命名空间与配额管理,复用 Wayne 原生多租户能力。所有集群的容器发布均可通过此入口统一操作。',
|
||||
CloudDM: '数据库 SQL 上线统一审核,支持 LDAP 用户组到角色的自动映射。变更记录可追溯,审计合规。',
|
||||
CacheCloud: 'Redis 实例全生命周期管理,登录与监控运维统一入口。按业务线隔离实例,统一密码管理。',
|
||||
Apollo: '统一管理多机房 Config Service Cluster 的配置发布、灰度、回滚与变更审计,所有机房配置项均可在此单一入口检索与编辑。',
|
||||
qpass: '夜莺、Zabbix、VictoriaMetrics 告警统一收敛与值班通知,按 P0/P1 分级推送。值班表自动轮转。',
|
||||
Grafana: '对接 VictoriaMetrics 数据源,K8s / 容器 / 业务指标统一仪表盘展示。预置核心看板,支持自定义。',
|
||||
}
|
||||
return descs[system.value.name] || system.value.description
|
||||
})
|
||||
|
||||
const openPortal = async () => {
|
||||
if (!system.value) return
|
||||
if (system.value.sso_enabled) {
|
||||
try {
|
||||
const { data } = await subsystemApi.getSSOUrl(system.value.id)
|
||||
window.location.assign(data.sso_url)
|
||||
} catch {
|
||||
// 错误已由 request 拦截器统一处理
|
||||
}
|
||||
} else {
|
||||
window.open(system.value.url, '_blank')
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.env-pill {
|
||||
font-size: 13px;
|
||||
color: var(--accent);
|
||||
background: var(--accent-dim);
|
||||
padding: 4px 14px;
|
||||
border-radius: 20px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.env-pill.warn {
|
||||
color: var(--warn);
|
||||
background: rgba(255, 180, 0, 0.1);
|
||||
}
|
||||
|
||||
.panel-body .ext-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: var(--accent);
|
||||
color: #fff;
|
||||
border: none;
|
||||
padding: 8px 18px;
|
||||
border-radius: 6px;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.btn-primary:hover {
|
||||
opacity: 0.85;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user