+50
-8
@@ -13,6 +13,7 @@ import {
|
|||||||
import { mockOn, mocked, rawFetch, request } from './request'
|
import { mockOn, mocked, rawFetch, request } from './request'
|
||||||
import type {
|
import type {
|
||||||
AuditEventsResult,
|
AuditEventsResult,
|
||||||
|
CreatedApiKey,
|
||||||
IamUser,
|
IamUser,
|
||||||
IdentityDomain,
|
IdentityDomain,
|
||||||
IamUserDetail,
|
IamUserDetail,
|
||||||
@@ -26,6 +27,7 @@ import type {
|
|||||||
SignOnRule,
|
SignOnRule,
|
||||||
SubscriptionDetail,
|
SubscriptionDetail,
|
||||||
SubscriptionSummary,
|
SubscriptionSummary,
|
||||||
|
UserApiKey,
|
||||||
} from '@/types/api'
|
} from '@/types/api'
|
||||||
|
|
||||||
// ---- 配额 ----
|
// ---- 配额 ----
|
||||||
@@ -169,15 +171,55 @@ export function clearUserMfa(id: number, userId: string, domainId?: string): Pro
|
|||||||
return request(`/oci-configs/${id}/users/${userId}/mfa-devices`, { method: 'DELETE', query: { domainId } })
|
return request(`/oci-configs/${id}/users/${userId}/mfa-devices`, { method: 'DELETE', query: { domainId } })
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deleteUserApiKeys(
|
// ---- 用户 API 签名 Key ----
|
||||||
id: number,
|
|
||||||
userId: string,
|
const mockCreatedKey: CreatedApiKey = {
|
||||||
includeCurrent = false,
|
fingerprint: '11:22:33:44:55:66:77:88:99:00:aa:bb:cc:dd:ee:ff',
|
||||||
): Promise<{ deletedApiKeys: number }> {
|
privateKey: '-----BEGIN RSA PRIVATE KEY-----\nMOCKMOCKMOCK\n-----END RSA PRIVATE KEY-----\n',
|
||||||
if (mockOn) return mocked({ deletedApiKeys: 1 })
|
configIni:
|
||||||
return request(`/oci-configs/${id}/users/${userId}/api-keys`, {
|
'[DEFAULT]\nuser=ocid1.user.oc1..mock\nfingerprint=11:22:33:44:55:66:77:88:99:00:aa:bb:cc:dd:ee:ff\ntenancy=ocid1.tenancy.oc1..mock\nregion=ap-tokyo-1\nkey_file=~/.oci/oci_api_key.pem\n',
|
||||||
|
}
|
||||||
|
|
||||||
|
export function listUserApiKeys(id: number, userId: string): Promise<{ items: UserApiKey[] }> {
|
||||||
|
if (mockOn)
|
||||||
|
return mocked({
|
||||||
|
items: [
|
||||||
|
{
|
||||||
|
fingerprint: 'aa:bb:cc:dd:ee:ff:00:11:22:33:44:55:66:77:88:99',
|
||||||
|
timeCreated: '2026-07-01T08:00:00Z',
|
||||||
|
isCurrent: true,
|
||||||
|
configIni: mockCreatedKey.configIni,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
fingerprint: '99:88:77:66:55:44:33:22:11:00:ff:ee:dd:cc:bb:aa',
|
||||||
|
timeCreated: '2026-07-10T08:00:00Z',
|
||||||
|
isCurrent: false,
|
||||||
|
configIni: mockCreatedKey.configIni,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
return request(`/oci-configs/${id}/users/${userId}/api-keys`)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 后端生成密钥对并上传公钥;私钥仅本次返回 */
|
||||||
|
export function addUserApiKey(id: number, userId: string): Promise<CreatedApiKey> {
|
||||||
|
if (mockOn) return mocked({ ...mockCreatedKey })
|
||||||
|
return request(`/oci-configs/${id}/users/${userId}/api-keys`, { method: 'POST' })
|
||||||
|
}
|
||||||
|
|
||||||
|
export function deleteUserApiKey(id: number, userId: string, fingerprint: string): Promise<void> {
|
||||||
|
if (mockOn) return mocked(undefined)
|
||||||
|
return request(`/oci-configs/${id}/users/${userId}/api-keys/${encodeURIComponent(fingerprint)}`, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
query: { includeCurrent },
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把刚创建的 key 设为本配置签名凭据(验证可用后落库,不删旧 key);私钥为创建时下发的那份回传 */
|
||||||
|
export function activateApiKey(id: number, fingerprint: string, privateKey: string): Promise<void> {
|
||||||
|
if (mockOn) return mocked(undefined)
|
||||||
|
return request(`/oci-configs/${id}/activate-api-key`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: { fingerprint, privateKey },
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,227 @@
|
|||||||
|
<script setup lang="ts">
|
||||||
|
import { NButton, NModal } from 'naive-ui'
|
||||||
|
import { computed, ref, watch } from 'vue'
|
||||||
|
|
||||||
|
import { activateApiKey, addUserApiKey, deleteUserApiKey, listUserApiKeys } from '@/api/tenant'
|
||||||
|
import ConfirmPop from '@/components/ConfirmPop.vue'
|
||||||
|
import { fmtTime } from '@/composables/useFormat'
|
||||||
|
import { useMobileModal } from '@/composables/useMobileModal'
|
||||||
|
import { useToast } from '@/composables/useToast'
|
||||||
|
import type { CreatedApiKey, IamUser, UserApiKey } from '@/types/api'
|
||||||
|
|
||||||
|
const props = defineProps<{ show: boolean; cfgId: number; user: IamUser | null }>()
|
||||||
|
const emit = defineEmits<{ 'update:show': [boolean] }>()
|
||||||
|
|
||||||
|
const toast = useToast()
|
||||||
|
const { modalStyle, modalClass } = useMobileModal(760)
|
||||||
|
|
||||||
|
const keys = ref<UserApiKey[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const busy = ref(false)
|
||||||
|
const activating = ref(false)
|
||||||
|
const activated = ref(false)
|
||||||
|
const selectedFp = ref('')
|
||||||
|
/** 本次弹窗内刚创建的 key;选中它时右栏进入结果态(私钥仅此一次可下载) */
|
||||||
|
const created = ref<CreatedApiKey | null>(null)
|
||||||
|
|
||||||
|
const selected = computed(() => keys.value.find((k) => k.fingerprint === selectedFp.value) ?? null)
|
||||||
|
const isCreatedView = computed(
|
||||||
|
() => !!created.value && selectedFp.value === created.value.fingerprint,
|
||||||
|
)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.show,
|
||||||
|
(show) => {
|
||||||
|
if (show) {
|
||||||
|
created.value = null
|
||||||
|
activated.value = false
|
||||||
|
selectedFp.value = ''
|
||||||
|
void reload()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
async function reload() {
|
||||||
|
if (!props.user) return
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
keys.value = (await listUserApiKeys(props.cfgId, props.user.id)).items
|
||||||
|
if (!keys.value.some((k) => k.fingerprint === selectedFp.value))
|
||||||
|
selectedFp.value = keys.value[0]?.fingerprint ?? ''
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(e instanceof Error ? e.message : '加载失败')
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doAdd() {
|
||||||
|
if (!props.user || busy.value || keys.value.length >= 3) return
|
||||||
|
busy.value = true
|
||||||
|
try {
|
||||||
|
const res = await addUserApiKey(props.cfgId, props.user.id)
|
||||||
|
created.value = res
|
||||||
|
activated.value = false
|
||||||
|
await reload()
|
||||||
|
selectedFp.value = res.fingerprint
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(e instanceof Error ? e.message : '创建失败')
|
||||||
|
} finally {
|
||||||
|
busy.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function doDelete(fp: string) {
|
||||||
|
if (!props.user) return
|
||||||
|
try {
|
||||||
|
await deleteUserApiKey(props.cfgId, props.user.id, fp)
|
||||||
|
toast.success('已删除')
|
||||||
|
if (created.value?.fingerprint === fp) created.value = null
|
||||||
|
await reload()
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(e instanceof Error ? e.message : '删除失败')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 把刚创建的 key 设为面板签名凭据;私钥仅存在于本次弹窗内存中 */
|
||||||
|
async function doActivate() {
|
||||||
|
if (!created.value || activating.value) return
|
||||||
|
activating.value = true
|
||||||
|
try {
|
||||||
|
await activateApiKey(props.cfgId, created.value.fingerprint, created.value.privateKey)
|
||||||
|
activated.value = true
|
||||||
|
toast.success('面板签名凭据已切换到该 key')
|
||||||
|
await reload()
|
||||||
|
} catch (e) {
|
||||||
|
toast.error(e instanceof Error ? e.message : '设置失败')
|
||||||
|
} finally {
|
||||||
|
activating.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function download() {
|
||||||
|
if (!created.value) return
|
||||||
|
const blob = new Blob([created.value.privateKey], { type: 'application/x-pem-file' })
|
||||||
|
const a = document.createElement('a')
|
||||||
|
a.href = URL.createObjectURL(blob)
|
||||||
|
a.download = 'oci_api_key.pem'
|
||||||
|
a.click()
|
||||||
|
URL.revokeObjectURL(a.href)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyIni(text: string) {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
toast.success('已复制配置')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<NModal
|
||||||
|
:show="show"
|
||||||
|
preset="card"
|
||||||
|
:style="modalStyle"
|
||||||
|
:class="modalClass"
|
||||||
|
@update:show="emit('update:show', $event)"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div>
|
||||||
|
<div class="text-[15px] leading-tight font-semibold">API Keys</div>
|
||||||
|
<div class="mt-1 text-xs font-normal text-ink-3">
|
||||||
|
{{ user?.name }}{{ user?.isCurrentUser ? ' · 当前签名用户' : '' }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div class="flex max-md:flex-col md:min-h-[300px]">
|
||||||
|
<!-- 左栏:key 列表 -->
|
||||||
|
<div
|
||||||
|
class="flex w-[218px] flex-none flex-col gap-0.5 border-r border-line-soft pr-2.5 max-md:w-full max-md:border-r-0 max-md:border-b max-md:pr-0 max-md:pb-2.5"
|
||||||
|
>
|
||||||
|
<div class="px-2.5 pb-1.5 text-[11px] text-ink-3">{{ keys.length }} / 3 把</div>
|
||||||
|
<div v-if="loading && !keys.length" class="px-2.5 py-6 text-center text-xs text-ink-3">加载中…</div>
|
||||||
|
<button
|
||||||
|
v-for="k in keys"
|
||||||
|
:key="k.fingerprint"
|
||||||
|
type="button"
|
||||||
|
class="cursor-pointer rounded-[7px] px-2.5 py-[7px] text-left hover:bg-row-hover"
|
||||||
|
:class="selectedFp === k.fingerprint ? 'bg-row-hover' : ''"
|
||||||
|
@click="selectedFp = k.fingerprint"
|
||||||
|
>
|
||||||
|
<div class="mono truncate text-xs">{{ k.fingerprint }}</div>
|
||||||
|
<div class="mt-0.5 text-[11px] text-ink-3">
|
||||||
|
{{ created?.fingerprint === k.fingerprint ? '刚刚创建' : fmtTime(k.timeCreated) }}
|
||||||
|
</div>
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="mt-auto cursor-pointer rounded-[7px] border border-dashed border-line px-2.5 py-[7px] text-xs text-ink-3 enabled:hover:border-accent enabled:hover:text-accent disabled:opacity-60 max-md:mt-2"
|
||||||
|
:disabled="busy || keys.length >= 3"
|
||||||
|
@click="doAdd"
|
||||||
|
>
|
||||||
|
{{ busy ? '创建中…' : '+ 添加 Key' }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 右栏:详情 / 创建结果 -->
|
||||||
|
<div class="min-w-0 flex-1 pt-0.5 pl-4 max-md:pt-3 max-md:pl-0">
|
||||||
|
<template v-if="isCreatedView && created">
|
||||||
|
<div class="rounded-lg bg-warn/14 px-3 py-2 text-xs leading-relaxed text-warn">
|
||||||
|
私钥仅可下载这一次,离开本页后无法再次获取
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex items-center gap-2 rounded-lg border border-line-soft bg-white px-3 py-2.5">
|
||||||
|
<span class="mono text-xs">oci_api_key.pem</span>
|
||||||
|
<span class="text-[11px] text-ink-3">RSA-2048</span>
|
||||||
|
<span class="min-w-0 flex-1"></span>
|
||||||
|
<NButton size="tiny" type="primary" @click="download">下载私钥</NButton>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 overflow-hidden rounded-lg border border-line-soft bg-white">
|
||||||
|
<div class="flex items-center px-3 pt-2">
|
||||||
|
<span class="text-[11px] text-ink-3">~/.oci/config</span>
|
||||||
|
<span class="min-w-0 flex-1"></span>
|
||||||
|
<NButton size="tiny" quaternary @click="copyIni(created.configIni)">复制</NButton>
|
||||||
|
</div>
|
||||||
|
<pre class="mono overflow-x-auto px-3 pt-1 pb-2.5 text-xs leading-relaxed">{{ created.configIni }}</pre>
|
||||||
|
</div>
|
||||||
|
<div v-if="user?.isCurrentUser" class="mt-3 flex">
|
||||||
|
<NButton size="tiny" secondary :loading="activating" :disabled="activated" @click="doActivate">
|
||||||
|
{{ activated ? '已设为面板签名 key' : '设为面板签名 key' }}
|
||||||
|
</NButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-else-if="selected">
|
||||||
|
<div class="mono text-[13px] leading-relaxed break-all">{{ selected.fingerprint }}</div>
|
||||||
|
<div class="mt-1 text-[11.5px] text-ink-3">
|
||||||
|
创建于 {{ fmtTime(selected.timeCreated) }} · RSA-2048{{ selected.isCurrent ? ' · 面板签名使用中' : '' }}
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 overflow-hidden rounded-lg border border-line-soft bg-white">
|
||||||
|
<div class="flex items-center px-3 pt-2">
|
||||||
|
<span class="text-[11px] text-ink-3">~/.oci/config · key_file 指向本机保存的私钥</span>
|
||||||
|
<span class="min-w-0 flex-1"></span>
|
||||||
|
<NButton size="tiny" quaternary @click="copyIni(selected.configIni)">复制</NButton>
|
||||||
|
</div>
|
||||||
|
<pre class="mono overflow-x-auto px-3 pt-1 pb-2.5 text-xs leading-relaxed">{{ selected.configIni }}</pre>
|
||||||
|
</div>
|
||||||
|
<div class="mt-3 flex">
|
||||||
|
<span class="min-w-0 flex-1"></span>
|
||||||
|
<ConfirmPop
|
||||||
|
v-if="!selected.isCurrent"
|
||||||
|
title="删除该 API Key?"
|
||||||
|
content="删除后使用它签名的调用立即失效。"
|
||||||
|
confirm-text="删除"
|
||||||
|
:on-confirm="() => doDelete(selectedFp)"
|
||||||
|
>
|
||||||
|
<template #trigger>
|
||||||
|
<NButton size="tiny" secondary type="error">删除此 Key</NButton>
|
||||||
|
</template>
|
||||||
|
</ConfirmPop>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-else-if="!loading" class="py-12 text-center text-xs text-ink-3">
|
||||||
|
该用户暂无 API Key,点左侧「添加 Key」创建
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</NModal>
|
||||||
|
</template>
|
||||||
@@ -2,9 +2,10 @@
|
|||||||
import { NButton, NDataTable, useDialog, type DataTableColumns } from 'naive-ui'
|
import { NButton, NDataTable, useDialog, type DataTableColumns } from 'naive-ui'
|
||||||
import { h, ref } from 'vue'
|
import { h, ref } from 'vue'
|
||||||
|
|
||||||
import { clearUserMfa, deleteUser, deleteUserApiKeys, listUsers, resetUserPassword } from '@/api/tenant'
|
import { clearUserMfa, deleteUser, listUsers, resetUserPassword } from '@/api/tenant'
|
||||||
import FootNote from '@/components/FootNote.vue'
|
import FootNote from '@/components/FootNote.vue'
|
||||||
import StatusBadge from '@/components/StatusBadge.vue'
|
import StatusBadge from '@/components/StatusBadge.vue'
|
||||||
|
import ApiKeysModal from '@/components/tenant/ApiKeysModal.vue'
|
||||||
import UserFormModal from '@/components/tenant/UserFormModal.vue'
|
import UserFormModal from '@/components/tenant/UserFormModal.vue'
|
||||||
import { fmtTime } from '@/composables/useFormat'
|
import { fmtTime } from '@/composables/useFormat'
|
||||||
import { useAsync } from '@/composables/useAsync'
|
import { useAsync } from '@/composables/useAsync'
|
||||||
@@ -19,6 +20,8 @@ const dialog = useDialog()
|
|||||||
const users = useAsync(() => listUsers(props.cfgId, props.domainId))
|
const users = useAsync(() => listUsers(props.cfgId, props.domainId))
|
||||||
const showForm = ref(false)
|
const showForm = ref(false)
|
||||||
const editingUser = ref<IamUser | null>(null)
|
const editingUser = ref<IamUser | null>(null)
|
||||||
|
const showKeys = ref(false)
|
||||||
|
const keysUser = ref<IamUser | null>(null)
|
||||||
|
|
||||||
function openCreate() {
|
function openCreate() {
|
||||||
editingUser.value = null
|
editingUser.value = null
|
||||||
@@ -30,6 +33,11 @@ function openEdit(row: IamUser) {
|
|||||||
showForm.value = true
|
showForm.value = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openKeys(row: IamUser) {
|
||||||
|
keysUser.value = row
|
||||||
|
showKeys.value = true
|
||||||
|
}
|
||||||
|
|
||||||
async function act(fn: () => Promise<unknown>, ok: string) {
|
async function act(fn: () => Promise<unknown>, ok: string) {
|
||||||
try {
|
try {
|
||||||
await fn()
|
await fn()
|
||||||
@@ -112,11 +120,23 @@ const columns: DataTableColumns<IamUser> = [
|
|||||||
{
|
{
|
||||||
title: '操作',
|
title: '操作',
|
||||||
key: 'actions',
|
key: 'actions',
|
||||||
width: 290,
|
width: 300,
|
||||||
render: (row) =>
|
render: (row) =>
|
||||||
h('div', { class: 'flex items-center gap-1' }, [
|
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: () => openEdit(row) }, { default: () => '修改' }),
|
||||||
h(NButton, { size: 'tiny', quaternary: true, onClick: () => resetPwd(row) }, { default: () => '重置密码' }),
|
h(
|
||||||
|
ConfirmPop,
|
||||||
|
{
|
||||||
|
title: '重置密码?',
|
||||||
|
content: `将为 ${row.name} 生成一次性新密码。`,
|
||||||
|
kind: 'warn',
|
||||||
|
confirmText: '重置',
|
||||||
|
onConfirm: () => resetPwd(row),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '重置密码' }),
|
||||||
|
},
|
||||||
|
),
|
||||||
h(
|
h(
|
||||||
ConfirmPop,
|
ConfirmPop,
|
||||||
{
|
{
|
||||||
@@ -131,19 +151,7 @@ const columns: DataTableColumns<IamUser> = [
|
|||||||
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '清 MFA' }),
|
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '清 MFA' }),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
h(
|
h(NButton, { size: 'tiny', quaternary: true, onClick: () => openKeys(row) }, { default: () => 'API Keys' }),
|
||||||
ConfirmPop,
|
|
||||||
{
|
|
||||||
title: '删除全部 API Key?',
|
|
||||||
content: '跳过当前配置使用中的指纹。',
|
|
||||||
confirmText: '删除',
|
|
||||||
onConfirm: () =>
|
|
||||||
act(() => deleteUserApiKeys(props.cfgId, row.id), `已删除 ${row.name} 的 API Key`),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '删 Key' }),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
row.isCurrentUser
|
row.isCurrentUser
|
||||||
? null
|
? null
|
||||||
: h(
|
: h(
|
||||||
@@ -180,5 +188,7 @@ const columns: DataTableColumns<IamUser> = [
|
|||||||
:user="editingUser"
|
:user="editingUser"
|
||||||
@created="users.run()"
|
@created="users.run()"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<ApiKeysModal v-model:show="showKeys" :cfg-id="cfgId" :user="keysUser" />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -639,6 +639,21 @@ export interface IamUser {
|
|||||||
lastLoginTime: string | null
|
lastLoginTime: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 用户 API 签名 key;isCurrent 表示当前配置正用它签名,configIni 为该 key 的 CLI 配置模板
|
||||||
|
export interface UserApiKey {
|
||||||
|
fingerprint: string
|
||||||
|
timeCreated: string | null
|
||||||
|
isCurrent: boolean
|
||||||
|
configIni: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建/轮换 key 的一次性返回;私钥仅此一次,关闭即不可再取
|
||||||
|
export interface CreatedApiKey {
|
||||||
|
fingerprint: string
|
||||||
|
privateKey: string
|
||||||
|
configIni: string
|
||||||
|
}
|
||||||
|
|
||||||
// 编辑表单用的域档案与管理员状态;inDomain 为 false 表示经典 IAM 用户
|
// 编辑表单用的域档案与管理员状态;inDomain 为 false 表示经典 IAM 用户
|
||||||
export interface IamUserDetail {
|
export interface IamUserDetail {
|
||||||
inDomain: boolean
|
inDomain: boolean
|
||||||
|
|||||||
Reference in New Issue
Block a user