feat(auth): complete sso subsystem integration

This commit is contained in:
mac
2026-07-16 15:52:56 +08:00
parent f8c389606f
commit eb2621d914
24 changed files with 1977 additions and 111 deletions
+16 -7
View File
@@ -6,12 +6,10 @@
<div class="mark">xi</div>
xinfra
</div>
<h2>统一基础设施平台</h2>
<p>正在跳转到 SSO 登录</p>
<h2>{{ title }}</h2>
<p>{{ subtitle }}</p>
</div>
<el-button type="primary" size="large" style="width: 100%" @click="redirectToSSO()">
重新跳转
</el-button>
<el-button type="primary" size="large" style="width: 100%" @click="redirectToSSO('', '/')">{{ buttonText }}</el-button>
<div class="login-footer">
<p>统一 LDAP 账号,同账号同密码</p>
</div>
@@ -20,10 +18,21 @@
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { computed, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { redirectToSSO } from '@/utils/sso'
onMounted(() => redirectToSSO())
const route = useRoute()
const loggedOut = computed(() => route.query.logged_out === '1')
const title = computed(() => loggedOut.value ? '已退出登录' : '统一基础设施平台')
const subtitle = computed(() => loggedOut.value ? '本地登录态已清除' : '正在跳转到 SSO 登录')
const buttonText = computed(() => loggedOut.value ? '重新登录' : '重新跳转')
onMounted(() => {
if (!loggedOut.value) {
redirectToSSO('', '/')
}
})
</script>
<style scoped>
@@ -0,0 +1,443 @@
<template>
<div>
<div class="page-head">
<div>
<h1>子系统赋权</h1>
<p>Wayne / CloudDM 入口权限、默认角色与授权状态</p>
</div>
<el-button type="primary">新增授权</el-button>
</div>
<div class="stat-row">
<div class="stat-card">
<div class="label">接入子系统</div>
<div class="value">2</div>
<div class="delta">Wayne · CloudDM</div>
</div>
<div class="stat-card">
<div class="label">授权主体</div>
<div class="value">6</div>
<div class="delta">用户 3 · 用户组 3</div>
</div>
<div class="stat-card">
<div class="label">待审批</div>
<div class="value" style="color: var(--warn)">2</div>
<div class="delta">最近提交 10:18</div>
</div>
<div class="stat-card">
<div class="label">默认授权</div>
<div class="value" style="font-size: 16px; color: var(--accent)">● 生效</div>
<div class="delta">新用户默认只读</div>
</div>
</div>
<div class="toolbar">
<el-select v-model="filters.system" placeholder="全部子系统" style="width: 150px">
<el-option label="全部子系统" value="" />
<el-option label="Wayne" value="Wayne" />
<el-option label="CloudDM" value="CloudDM" />
</el-select>
<el-select v-model="filters.type" placeholder="全部主体" style="width: 150px">
<el-option label="全部主体" value="" />
<el-option label="用户" value="user" />
<el-option label="用户组" value="group" />
</el-select>
<el-select v-model="filters.status" placeholder="全部状态" style="width: 150px">
<el-option label="全部状态" value="" />
<el-option label="已生效" value="active" />
<el-option label="待审批" value="pending" />
<el-option label="已停用" value="disabled" />
</el-select>
<el-input v-model="filters.keyword" placeholder="搜索账号 / 用户组 / 角色" style="flex: 1" />
</div>
<div class="matrix">
<div v-for="system in systems" :key="system.name" class="system-card">
<div class="system-top">
<div class="logo" :class="system.className">{{ system.icon }}</div>
<div>
<h3>{{ system.name }}</h3>
<p>{{ system.defaultPolicy }}</p>
</div>
</div>
<div class="role-grid">
<div v-for="role in system.roles" :key="role.name" class="role-cell">
<span>{{ role.name }}</span>
<strong>{{ role.count }}</strong>
</div>
</div>
</div>
</div>
<div class="panel">
<div class="panel-head">
<h3>授权列表</h3>
<span class="meta">数据源:AuthServer · 子系统授权</span>
</div>
<div class="panel-body">
<table>
<thead>
<tr>
<th>授权主体</th>
<th>类型</th>
<th>子系统</th>
<th>角色 / 范围</th>
<th>来源</th>
<th>状态</th>
<th>最近变更</th>
<th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filteredGrants" :key="item.id" class="tr-hover">
<td>
<div class="principal">
<span class="avatar">{{ item.initial }}</span>
<div>
<div class="strong">{{ item.principal }}</div>
<div class="sub mono">{{ item.detail }}</div>
</div>
</div>
</td>
<td><span class="tag">{{ item.type === 'user' ? '用户' : '用户组' }}</span></td>
<td class="mono">{{ item.system }}</td>
<td>
<span class="role">{{ item.role }}</span>
<span class="scope mono">{{ item.scope }}</span>
</td>
<td class="mono">{{ item.source }}</td>
<td :class="['status-text', item.statusClass]">● {{ item.statusText }}</td>
<td class="mono">{{ item.updatedAt }}</td>
<td>
<div class="actions">
<button type="button">编辑</button>
<button type="button" class="danger">停用</button>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { computed, reactive } from 'vue'
const filters = reactive({
system: '',
type: '',
status: '',
keyword: '',
})
const systems = [
{
name: 'Wayne',
icon: 'W',
className: 'wayne',
defaultPolicy: '默认 namespace 只读 · DemoGroupId=23',
roles: [
{ name: '只读', count: 18 },
{ name: '发布', count: 6 },
{ name: '管理员', count: 2 },
],
},
{
name: 'CloudDM',
icon: 'DM',
className: 'clouddm',
defaultPolicy: 'OIDC 登录 · SQL 审核角色映射',
roles: [
{ name: '查询', count: 21 },
{ name: '审核', count: 5 },
{ name: '管理员', count: 1 },
],
},
]
const grants = [
{ id: 1, principal: 'eastsales@qiniu.com', initial: 'E', detail: 'eastsales', type: 'user', system: 'Wayne', role: '默认只读', scope: 'namespace=demo', source: 'SSO 自动初始化', status: 'active', statusText: '已生效', statusClass: 'ok', updatedAt: '2026-07-15 10:12' },
{ id: 2, principal: 'platform-admin', initial: 'P', detail: 'LDAP group', type: 'group', system: 'Wayne', role: '管理员', scope: 'all namespaces', source: '手动授权', status: 'active', statusText: '已生效', statusClass: 'ok', updatedAt: '2026-07-14 18:40' },
{ id: 3, principal: 'dba-reviewers', initial: 'D', detail: 'LDAP group', type: 'group', system: 'CloudDM', role: 'SQL 审核', scope: 'prod / staging', source: '手动授权', status: 'active', statusText: '已生效', statusClass: 'ok', updatedAt: '2026-07-14 16:05' },
{ id: 4, principal: 'las-dev', initial: 'L', detail: 'LDAP group', type: 'group', system: 'CloudDM', role: '查询', scope: 'las schemas', source: '审批流', status: 'pending', statusText: '待审批', statusClass: 'warn', updatedAt: '2026-07-15 10:18' },
{ id: 5, principal: 'ops-user1@qiniu.com', initial: 'O', detail: 'ops-user1', type: 'user', system: 'Wayne', role: '发布', scope: 'namespace=demo', source: '审批流', status: 'pending', statusText: '待审批', statusClass: 'warn', updatedAt: '2026-07-15 09:55' },
{ id: 6, principal: 'temp-sql@qiniu.com', initial: 'T', detail: 'temp-sql', type: 'user', system: 'CloudDM', role: '查询', scope: 'expired', source: '临时授权', status: 'disabled', statusText: '已停用', statusClass: 'idle', updatedAt: '2026-07-13 20:30' },
]
const filteredGrants = computed(() => {
const keyword = filters.keyword.trim().toLowerCase()
return grants.filter((item) => {
if (filters.system && item.system !== filters.system) return false
if (filters.type && item.type !== filters.type) return false
if (filters.status && item.status !== filters.status) return false
if (!keyword) return true
return [item.principal, item.detail, item.system, item.role, item.scope]
.join(' ')
.toLowerCase()
.includes(keyword)
})
})
</script>
<style scoped>
.page-head {
display: flex;
align-items: flex-end;
justify-content: space-between;
margin-bottom: 18px;
}
.page-head h1 {
font-size: 19px;
margin: 0 0 4px;
font-weight: 700;
}
.page-head p {
margin: 0;
color: var(--text-dim);
font-size: 12.5px;
}
.stat-row {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 12px;
margin-bottom: 18px;
}
.stat-card {
background: var(--bg-panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 14px 16px;
}
.stat-card .label {
font-size: 11px;
color: var(--text-dim);
margin-bottom: 8px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.stat-card .value {
font-family: var(--mono);
font-size: 24px;
font-weight: 600;
}
.stat-card .delta {
font-size: 11px;
color: var(--text-dim);
margin-top: 4px;
}
.toolbar {
display: flex;
gap: 10px;
margin-bottom: 14px;
align-items: center;
}
.matrix {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 14px;
margin-bottom: 16px;
}
.system-card {
background: var(--bg-panel);
border: 1px solid var(--line);
border-radius: 8px;
padding: 16px;
}
.system-top {
display: flex;
gap: 12px;
align-items: center;
margin-bottom: 14px;
}
.logo {
width: 38px;
height: 38px;
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--mono);
font-weight: 700;
font-size: 13px;
}
.logo.wayne {
background: #1C2A3A;
color: #7FB8FF;
}
.logo.clouddm {
background: #1C3A2E;
color: #7FFFC2;
}
.system-top h3 {
margin: 0 0 4px;
font-size: 14px;
}
.system-top p {
margin: 0;
color: var(--text-dim);
font-size: 11.5px;
}
.role-grid {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: 8px;
}
.role-cell {
border: 1px solid var(--line-soft);
background: var(--bg-panel-2);
border-radius: 6px;
padding: 10px;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12px;
color: var(--text-mid);
}
.role-cell strong {
font-family: var(--mono);
color: var(--text-hi);
}
.panel {
background: var(--bg-panel);
border: 1px solid var(--line);
border-radius: 8px;
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 13px 16px;
border-bottom: 1px solid var(--line-soft);
}
.panel-head h3 {
margin: 0;
font-size: 13.5px;
font-weight: 600;
}
.panel-head .meta {
font-size: 11.5px;
color: var(--text-dim);
font-family: var(--mono);
}
.panel-body {
padding: 4px 0;
overflow-x: auto;
}
table {
width: 100%;
min-width: 980px;
border-collapse: collapse;
font-size: 12.5px;
}
th {
text-align: left;
color: var(--text-dim);
font-weight: 500;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.4px;
padding: 9px 16px;
border-bottom: 1px solid var(--line-soft);
}
td {
padding: 11px 16px;
border-bottom: 1px solid var(--line-soft);
color: var(--text-mid);
}
.tr-hover:hover {
background: #171D28;
}
.strong {
color: var(--text-hi);
font-weight: 600;
}
.sub {
color: var(--text-dim);
font-size: 11px;
margin-top: 2px;
}
.principal {
display: flex;
align-items: center;
gap: 10px;
}
.avatar {
width: 28px;
height: 28px;
border-radius: 50%;
background: #2A3142;
display: flex;
align-items: center;
justify-content: center;
font-family: var(--mono);
color: var(--text-hi);
font-size: 11px;
}
.role {
color: var(--text-hi);
margin-right: 8px;
}
.scope {
color: var(--text-dim);
font-size: 11px;
}
.actions {
display: flex;
gap: 8px;
}
.actions button {
border: 1px solid var(--line);
background: var(--bg-panel-2);
color: var(--text-mid);
border-radius: 5px;
padding: 4px 8px;
font-size: 12px;
}
.actions button:hover {
color: var(--text-hi);
border-color: #3A4356;
}
.actions button.danger {
color: #FF9A95;
}
</style>