初始提交: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
+176
View File
@@ -0,0 +1,176 @@
<script setup lang="ts">
import { NButton, NDataTable, NPopconfirm, useDialog, useMessage, type DataTableColumns } from 'naive-ui'
import { h, ref } from 'vue'
import { clearUserMfa, deleteUser, deleteUserApiKeys, listUsers, resetUserPassword } from '@/api/tenant'
import FootNote from '@/components/FootNote.vue'
import StatusBadge from '@/components/StatusBadge.vue'
import UserFormModal from '@/components/tenant/UserFormModal.vue'
import { fmtTime } from '@/composables/useFormat'
import { useAsync } from '@/composables/useAsync'
import type { IamUser } from '@/types/api'
const props = defineProps<{ cfgId: number }>()
const message = useMessage()
const dialog = useDialog()
const users = useAsync(() => listUsers(props.cfgId))
const showForm = ref(false)
const editingUser = ref<IamUser | null>(null)
function openCreate() {
editingUser.value = null
showForm.value = true
}
function openEdit(row: IamUser) {
editingUser.value = row
showForm.value = true
}
async function act(fn: () => Promise<unknown>, ok: string) {
try {
await fn()
message.success(ok)
void users.run()
} catch (e) {
message.error(e instanceof Error ? e.message : '操作失败')
}
}
function confirmDelete(row: IamUser) {
dialog.warning({
title: '删除用户',
content: () =>
h('div', { class: 'text-[13px]' }, [
h('p', `确定删除用户「${row.name}」?此操作不可恢复。`),
h('p', { class: 'mt-1.5 text-ink-3' }, '删除后该用户的全部 API Key 将一并吊销'),
]),
positiveText: '确认删除',
negativeText: '取消',
onPositiveClick: () => act(() => deleteUser(props.cfgId, row.id), `已删除用户 ${row.name}`),
})
}
async function resetPwd(row: IamUser) {
try {
const { password } = await resetUserPassword(props.cfgId, row.id)
dialog.success({
title: '一次性密码',
content: () =>
h('div', [
h('p', { class: 'mb-2 text-[13px]' }, `${row.name} 的新密码(首次登录须修改):`),
h('code', { class: 'mono rounded bg-line-soft px-2 py-1 select-all' }, password),
]),
positiveText: '已保存',
})
} catch (e) {
message.error(e instanceof Error ? e.message : '重置失败')
}
}
const columns: DataTableColumns<IamUser> = [
{
title: '用户',
key: 'name',
minWidth: 220,
render: (row) =>
h('div', [
h('div', { class: 'flex items-center gap-2' }, [
h('span', { class: 'font-medium' }, row.name),
row.isCurrentUser
? h(StatusBadge, { kind: 'snatch', label: '当前签名用户', dot: false })
: null,
]),
h('div', { class: 'text-xs text-ink-3 mt-px' }, row.email || row.description || '—'),
]),
},
{
title: '状态',
key: 'lifecycleState',
width: 90,
render: (row) =>
h(StatusBadge, {
kind: row.lifecycleState === 'ACTIVE' ? 'run' : 'stop',
label: row.lifecycleState === 'ACTIVE' ? '活跃' : row.lifecycleState,
}),
},
{
title: 'MFA',
key: 'mfaActivated',
width: 70,
render: (row) => h('span', { class: 'text-[13px] tabular-nums' }, row.mfaActivated ? '已启用' : '未启用'),
},
{
title: '创建时间',
key: 'timeCreated',
width: 150,
render: (row) => h('span', { class: 'text-[13px] text-ink-2' }, fmtTime(row.timeCreated)),
},
{
title: '操作',
key: 'actions',
width: 290,
render: (row) =>
h('div', { class: 'flex items-center gap-1' }, [
h(NButton, { size: 'tiny', quaternary: true, onClick: () => openEdit(row) }, { default: () => '修改' }),
h(NButton, { size: 'tiny', quaternary: true, onClick: () => resetPwd(row) }, { default: () => '重置密码' }),
h(
NPopconfirm,
{
onPositiveClick: () =>
act(() => clearUserMfa(props.cfgId, row.id), `已清除 ${row.name} 的全部 MFA`),
},
{
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '清 MFA' }),
default: () => '清除该用户全部 MFA 因子?',
},
),
h(
NPopconfirm,
{
onPositiveClick: () =>
act(() => deleteUserApiKeys(props.cfgId, row.id), `已删除 ${row.name} 的 API Key`),
},
{
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '删 Key' }),
default: () => '删除该用户全部 API Key(跳过当前配置使用中的指纹)?',
},
),
row.isCurrentUser
? null
: h(
NButton,
{ size: 'tiny', quaternary: true, type: 'error', onClick: () => confirmDelete(row) },
{ default: () => '删除' },
),
]),
},
]
</script>
<template>
<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">IAM 用户</div>
<NButton size="small" type="primary" @click="openCreate">添加用户</NButton>
</div>
<NDataTable
:columns="columns"
:data="users.data.value ?? []"
:loading="users.loading.value"
:scroll-x="800"
:row-key="(r: IamUser) => r.id"
/>
<FootNote>
重置密码生成一次性密码,用户首次登录须修改;当前配置签名用户不可删除,避免面板失联
</FootNote>
<UserFormModal
v-model:show="showForm"
:cfg-id="cfgId"
:user="editingUser"
@created="users.run()"
/>
</div>
</template>