初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
+216
View File
@@ -0,0 +1,216 @@
<script setup lang="ts">
import { NButton, NDataTable, NPopconfirm, NSwitch, useDialog, useMessage, type DataTableColumns } from 'naive-ui'
import { h, ref } from 'vue'
import {
activateIdp,
createSignOnExemption,
deleteIdp,
deleteSignOnExemption,
downloadSamlMetadata,
listIdps,
listSignOnRules,
} from '@/api/tenant'
import FootNote from '@/components/FootNote.vue'
import StatusBadge from '@/components/StatusBadge.vue'
import IdpFormModal from '@/components/tenant/IdpFormModal.vue'
import { useAsync } from '@/composables/useAsync'
import type { IdentityProvider, SignOnRule } from '@/types/api'
const props = defineProps<{ cfgId: number }>()
const message = useMessage()
const dialog = useDialog()
const idps = useAsync(() => listIdps(props.cfgId))
const rules = useAsync(() => listSignOnRules(props.cfgId))
const showCreate = ref(false)
async function act(fn: () => Promise<unknown>, ok: string) {
try {
await fn()
message.success(ok)
void idps.run()
void rules.run()
} catch (e) {
message.error(e instanceof Error ? e.message : '操作失败')
}
}
const idpColumns: DataTableColumns<IdentityProvider> = [
{
title: 'IdP',
key: 'name',
minWidth: 220,
render: (row) =>
h('div', [
h('div', { class: 'font-medium' }, row.name),
h('div', { class: 'mono text-xs text-ink-3 mt-px break-all' }, row.partnerProviderId),
]),
},
{ title: '协议', key: 'type', width: 80 },
{
title: '状态',
key: 'enabled',
width: 100,
render: (row) =>
h(StatusBadge, {
kind: row.enabled ? 'run' : 'stop',
label: row.enabled ? '已启用' : '已停用',
}),
},
{
title: 'JIT',
key: 'jitEnabled',
width: 70,
render: (row) => h('span', { class: 'text-[13px]' }, row.jitEnabled ? '开启' : '关闭'),
},
{
title: '操作',
key: 'actions',
width: 160,
render: (row) =>
h('div', { class: 'flex items-center gap-3' }, [
h(NSwitch, {
size: 'small',
value: row.enabled,
onUpdateValue: (v: boolean) =>
act(() => activateIdp(props.cfgId, row.id, v), v ? '已激活并显示到登录页' : '已停用'),
}),
h(
NButton,
{ size: 'tiny', quaternary: true, type: 'error', onClick: () => confirmDeleteIdp(row) },
{ default: () => '删除' },
),
]),
},
]
const ruleColumns: DataTableColumns<SignOnRule> = [
{ title: '#', key: 'sequence', width: 50 },
{
title: '规则',
key: 'name',
minWidth: 180,
render: (row) =>
h('div', [
h('div', { class: 'mono text-[12.5px]' }, row.name),
h(
'div',
{ class: 'text-xs text-ink-3 mt-px' },
row.conditionAttribute ? `${row.conditionAttribute} in ${row.conditionValue}` : '默认兜底规则',
),
]),
},
{
title: '认证因子',
key: 'authenticationFactor',
width: 120,
render: (row) =>
h(StatusBadge, {
kind: row.authenticationFactor === 'IDP' ? 'prov' : 'check',
label: row.authenticationFactor === 'IDP' ? '仅 IdP' : 'IdP + 2FA',
dot: false,
}),
},
{
title: '操作',
key: 'actions',
width: 110,
render: (row) =>
row.builtIn
? h('span', { class: 'text-xs text-ink-3' }, '内置')
: h(
NPopconfirm,
{
onPositiveClick: () =>
act(() => deleteSignOnExemption(props.cfgId, row.id), '已删除免 MFA 规则并恢复优先级'),
},
{
trigger: () =>
h(NButton, { size: 'tiny', quaternary: true, type: 'error' }, { default: () => '删除' }),
default: () => '删除该免 MFA 规则?删除后经此 IdP 登录将重新要求 MFA',
},
),
},
]
async function doDownloadMetadata() {
try {
await downloadSamlMetadata(props.cfgId)
message.success('SAML 元数据已下载')
} catch (e) {
message.error(e instanceof Error ? e.message : '下载失败')
}
}
function confirmDeleteIdp(row: IdentityProvider) {
dialog.warning({
title: '删除身份提供商',
content: () =>
h('div', { class: 'text-[13px]' }, [
h('p', `确定删除 IdP「${row.name}」?此操作不可恢复。`),
h('p', { class: 'mt-1.5 text-ink-3' }, '将自动清理关联的免 MFA 规则,并从登录页移除'),
]),
positiveText: '确认删除',
negativeText: '取消',
onPositiveClick: () =>
act(() => deleteIdp(props.cfgId, row.id), `已删除 IdP「${row.name}」及其关联规则`),
})
}
function addExemption() {
const enabled = idps.data.value?.find((i) => i.enabled)
if (!enabled) {
message.warning('没有已启用的 IdP')
return
}
void act(
() => createSignOnExemption(props.cfgId, enabled.id),
`已为 ${enabled.name} 创建免 MFA 规则并置顶`,
)
}
</script>
<template>
<div class="flex flex-col gap-4">
<div class="panel">
<div class="flex items-center justify-between border-b border-line-soft px-4.5 py-3.5">
<div class="text-sm font-semibold">SAML 身份提供商</div>
<div class="flex items-center gap-2">
<NButton size="small" @click="doDownloadMetadata">下载 SP 元数据</NButton>
<NButton size="small" type="primary" @click="showCreate = true">创建 IdP</NButton>
</div>
</div>
<NDataTable
:columns="idpColumns"
:data="idps.data.value ?? []"
:loading="idps.loading.value"
:scroll-x="620"
:row-key="(r: IdentityProvider) => r.id"
/>
<FootNote>
推荐流程:下载 SP SAML 元数据导入 IdP 侧 → 创建 IdP → 激活(自动显示到登录页)→ 创建免 MFA
规则;停用与删除会先将 IdP 移出登录页
</FootNote>
</div>
<div class="panel">
<div class="flex items-center justify-between border-b border-line-soft px-4.5 py-3.5">
<div class="text-sm font-semibold">Sign-on 策略(免 MFA 规则)</div>
<NButton size="small" @click="addExemption">为已启用 IdP 创建免 MFA 规则</NButton>
</div>
<NDataTable
:columns="ruleColumns"
:data="rules.data.value ?? []"
:loading="rules.loading.value"
:scroll-x="560"
:row-key="(r: SignOnRule) => r.id"
/>
<FootNote>
sign-on 策略管控全租户控制台登录;Oracle 预置规则不可删除,请勿手工改动预置规则顺序
</FootNote>
</div>
<IdpFormModal v-model:show="showCreate" :cfg-id="cfgId" @created="idps.run()" />
</div>
</template>