初始提交:OCI 面板前端(含 GenAI 网关一期)
This commit is contained in:
@@ -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>
|
||||
Reference in New Issue
Block a user