发布 0.1.0:通知渠道、告警规则与多项体验修复
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NDataTable, NModal, NSelect, useMessage, type DataTableColumns } from 'naive-ui'
|
||||
import { computed, h, onMounted, reactive, ref, watch } from 'vue'
|
||||
import { NButton, NDataTable, NModal, type DataTableColumns } from 'naive-ui'
|
||||
import { computed, h, onMounted, reactive, ref } from 'vue'
|
||||
|
||||
import { getAuditEventDetail, listAuditEvents } from '@/api/tenant'
|
||||
import DetailFieldList, { type DetailRow } from '@/components/DetailFieldList.vue'
|
||||
@@ -11,17 +11,21 @@ import StatusBadge from '@/components/StatusBadge.vue'
|
||||
import { eventLabel } from '@/composables/useEventLabel'
|
||||
import { fmtTime } from '@/composables/useFormat'
|
||||
import type { AuditEvent } from '@/types/api'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{ cfgId: number }>()
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
|
||||
const hours = ref(24)
|
||||
const hourOptions = [
|
||||
{ label: '近 1 小时', value: 1 },
|
||||
{ label: '近 6 小时', value: 6 },
|
||||
{ label: '近 24 小时', value: 24 },
|
||||
{ label: '近 72 小时', value: 72 },
|
||||
]
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const error = ref('')
|
||||
const rows = ref<AuditEvent[]>([])
|
||||
// 服务端分窗回溯游标:空且 exhausted 表示已到 365 天保留期尽头
|
||||
const cursor = ref('')
|
||||
const exhausted = ref(false)
|
||||
|
||||
const pageCount = computed(() => Math.max(1, Math.ceil(rows.value.length / pagination.pageSize)))
|
||||
const hasMore = computed(() => cursor.value !== '' && !exhausted.value)
|
||||
|
||||
/** 受控分页:字面量对象会在重渲染时被重建导致页大小切换失效 */
|
||||
const pagination = reactive({
|
||||
@@ -29,8 +33,13 @@ const pagination = reactive({
|
||||
pageSize: 20,
|
||||
showSizePicker: true,
|
||||
pageSizes: [20, 50, 100],
|
||||
prefix: () =>
|
||||
`已加载 ${rows.value.length} 条 · ` +
|
||||
(loadingMore.value ? '正在加载更早…' : exhausted.value ? '已到 365 天保留期尽头' : '翻到末页自动加载更早'),
|
||||
onUpdatePage: (p: number) => {
|
||||
pagination.page = p
|
||||
// 懒加载触发点:翻到已加载数据的最后一页即向后端续取一批
|
||||
if (p >= pageCount.value) void fetchBatch()
|
||||
},
|
||||
onUpdatePageSize: (ps: number) => {
|
||||
pagination.pageSize = ps
|
||||
@@ -38,55 +47,48 @@ const pagination = reactive({
|
||||
},
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const loadingMore = ref(false)
|
||||
const error = ref('')
|
||||
const rows = ref<AuditEvent[]>([])
|
||||
const truncated = ref(false)
|
||||
const nextPage = ref('')
|
||||
// 本次查询的绝对时间窗:续查游标绑定查询参数,必须原样带回
|
||||
const win = ref<{ start: string; end: string } | null>(null)
|
||||
|
||||
/** 重置信息流:回到最新一批 */
|
||||
async function load() {
|
||||
loading.value = true
|
||||
error.value = ''
|
||||
rows.value = []
|
||||
cursor.value = ''
|
||||
exhausted.value = false
|
||||
pagination.page = 1
|
||||
try {
|
||||
const r = await listAuditEvents(props.cfgId, { hours: hours.value })
|
||||
rows.value = r.items
|
||||
truncated.value = r.truncated
|
||||
nextPage.value = r.nextPage ?? ''
|
||||
win.value = { start: r.start, end: r.end }
|
||||
pagination.page = 1
|
||||
} catch (e) {
|
||||
error.value = e instanceof Error ? e.message : '查询失败'
|
||||
await fetchBatch(true)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
/** 截断续查:同一绝对窗带游标断点续翻,追加去重后按时间重排 */
|
||||
async function loadMore() {
|
||||
if (!win.value || !nextPage.value || loadingMore.value) return
|
||||
/** 向更早方向续取一批(~100 条):追加去重后按时间重排;
|
||||
* 末页不满且还有更早数据时自动补一批,避免首屏残页 */
|
||||
async function fetchBatch(first = false) {
|
||||
if (loadingMore.value || (!first && !hasMore.value)) return
|
||||
loadingMore.value = true
|
||||
try {
|
||||
const r = await listAuditEvents(props.cfgId, { ...win.value, page: nextPage.value })
|
||||
const r = await listAuditEvents(props.cfgId, { cursor: cursor.value || undefined })
|
||||
const seen = new Set(rows.value.map((e) => e.eventId))
|
||||
rows.value = [...rows.value, ...r.items.filter((e) => !seen.has(e.eventId))].sort((a, b) =>
|
||||
(b.eventTime ?? '').localeCompare(a.eventTime ?? ''),
|
||||
)
|
||||
truncated.value = r.truncated
|
||||
nextPage.value = r.nextPage ?? ''
|
||||
cursor.value = r.cursor ?? ''
|
||||
exhausted.value = r.exhausted
|
||||
} catch (e) {
|
||||
// 追加失败不清空已有列表;游标过期时提示重新查询
|
||||
message.error(e instanceof Error ? e.message : '继续加载失败,请刷新重新查询')
|
||||
if (first) {
|
||||
error.value = e instanceof Error ? e.message : '查询失败'
|
||||
} else {
|
||||
// 追加失败不清空已有列表;游标过期时提示刷新重来
|
||||
message.error(e instanceof Error ? e.message : '继续加载失败,请刷新重新查询')
|
||||
}
|
||||
} finally {
|
||||
loadingMore.value = false
|
||||
}
|
||||
if (hasMore.value && rows.value.length < pagination.pageSize) void fetchBatch()
|
||||
}
|
||||
|
||||
onMounted(() => void load())
|
||||
// 切换时间窗自动重查并回到第一页
|
||||
watch(hours, () => void load())
|
||||
|
||||
// ---- 行点击详情:原始 JSON 懒取(命中服务端缓存秒开,过期走小窗重查) ----
|
||||
const detail = ref<AuditEvent | null>(null)
|
||||
@@ -202,16 +204,8 @@ const columns: DataTableColumns<AuditEvent> = [
|
||||
<div class="panel">
|
||||
<div class="flex flex-wrap items-center gap-2.5 border-b border-line-soft px-4.5 py-3">
|
||||
<div class="text-sm font-semibold">审计日志</div>
|
||||
<template v-if="truncated">
|
||||
<StatusBadge kind="warn" label="结果已截断" />
|
||||
<NButton v-if="nextPage" size="tiny" :loading="loadingMore" @click="loadMore">
|
||||
继续加载更早
|
||||
</NButton>
|
||||
</template>
|
||||
<StatusBadge v-if="exhausted" kind="run" label="已加载全部保留期数据" :dot="false" />
|
||||
<div class="flex-1" />
|
||||
<div class="w-32 max-md:w-full">
|
||||
<NSelect v-model:value="hours" size="small" :options="hourOptions" />
|
||||
</div>
|
||||
<NButton size="small" :loading="loading" @click="load()">刷新</NButton>
|
||||
</div>
|
||||
|
||||
@@ -235,7 +229,7 @@ const columns: DataTableColumns<AuditEvent> = [
|
||||
OCI Audit 免费且默认开启,事件保留 365 天,无需在云端做任何配置 ·
|
||||
实时查询不入库,事件通常延迟数分钟可见 ·
|
||||
已默认过滤遥测噪声(SummarizeMetricsData)与内网地址发起的服务互调 ·
|
||||
单次最多翻 10 页,截断时点「继续加载更早」断点续查 · 点击行查看完整详情
|
||||
从最新事件起每批约 100 条,翻到末页自动向更早加载,空档期自动扩窗回溯 · 点击行查看完整详情
|
||||
</FootNote>
|
||||
|
||||
<NModal v-model:show="showDetail" preset="card" closable :style="{ width: '720px', maxWidth: 'calc(100vw - 24px)' }">
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
<script setup lang="ts">
|
||||
import { NCheckbox, NInput, NSelect, NSwitch, useMessage } from 'naive-ui'
|
||||
import { NCheckbox, NInput, NSelect, NSwitch } from 'naive-ui'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
import { createIdp, getIdentitySetting, type CreateIdpBody } from '@/api/tenant'
|
||||
import FilePicker from '@/components/FilePicker.vue'
|
||||
import FormField from '@/components/FormField.vue'
|
||||
import FormModal from '@/components/FormModal.vue'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{ show: boolean; cfgId: number }>()
|
||||
const props = defineProps<{ show: boolean; cfgId: number; domainId?: string }>()
|
||||
const emit = defineEmits<{ 'update:show': [boolean]; created: [] }>()
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
const submitting = ref(false)
|
||||
// 「用户需要提供主电子邮件地址」开启时 JIT 必须映射邮箱
|
||||
const primaryEmailRequired = ref(false)
|
||||
@@ -32,7 +33,7 @@ const userAttrOptions = [
|
||||
]
|
||||
|
||||
// 默认值与控制台一致:名称 ID 格式「无」、NameID 映射用户名、
|
||||
// JIT 开启建用户不更新、分配 Administrators 组
|
||||
// JIT 开启建用户不更新、分配租户管理员组
|
||||
const blank = {
|
||||
name: '',
|
||||
metadata: '',
|
||||
@@ -103,7 +104,7 @@ function buildBody(): CreateIdpBody {
|
||||
async function submit() {
|
||||
submitting.value = true
|
||||
try {
|
||||
const created = await createIdp(props.cfgId, buildBody())
|
||||
const created = await createIdp(props.cfgId, buildBody(), props.domainId)
|
||||
message.success(`IdP「${created.name}」已创建(禁用态),激活后显示到登录页`)
|
||||
emit('update:show', false)
|
||||
emit('created')
|
||||
@@ -199,7 +200,7 @@ async function submit() {
|
||||
<NCheckbox v-model:checked="form.jitCreate">首次联邦登录时自动创建用户</NCheckbox>
|
||||
<NCheckbox v-model:checked="form.jitUpdate">每次登录时按断言更新既有用户</NCheckbox>
|
||||
<NCheckbox v-model:checked="form.jitAssignAdminGroup">
|
||||
将 JIT 用户加入 Administrators 组
|
||||
将 JIT 用户加入租户管理员组
|
||||
</NCheckbox>
|
||||
<NCheckbox
|
||||
v-model:checked="form.jitMapEmail"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NDataTable, NPopconfirm, NSwitch, useDialog, useMessage, type DataTableColumns } from 'naive-ui'
|
||||
import { NButton, NDataTable, NPopconfirm, NSwitch, useDialog, type DataTableColumns } from 'naive-ui'
|
||||
import { h, ref } from 'vue'
|
||||
|
||||
import {
|
||||
@@ -16,13 +16,14 @@ 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'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{ cfgId: number }>()
|
||||
const props = defineProps<{ cfgId: number; domainId?: string }>()
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
const dialog = useDialog()
|
||||
const idps = useAsync(() => listIdps(props.cfgId))
|
||||
const rules = useAsync(() => listSignOnRules(props.cfgId))
|
||||
const idps = useAsync(() => listIdps(props.cfgId, props.domainId))
|
||||
const rules = useAsync(() => listSignOnRules(props.cfgId, props.domainId))
|
||||
const showCreate = ref(false)
|
||||
|
||||
async function act(fn: () => Promise<unknown>, ok: string) {
|
||||
@@ -74,7 +75,7 @@ const idpColumns: DataTableColumns<IdentityProvider> = [
|
||||
size: 'small',
|
||||
value: row.enabled,
|
||||
onUpdateValue: (v: boolean) =>
|
||||
act(() => activateIdp(props.cfgId, row.id, v), v ? '已激活并显示到登录页' : '已停用'),
|
||||
act(() => activateIdp(props.cfgId, row.id, v, props.domainId), v ? '已激活并显示到登录页' : '已停用'),
|
||||
}),
|
||||
h(
|
||||
NButton,
|
||||
@@ -123,7 +124,7 @@ const ruleColumns: DataTableColumns<SignOnRule> = [
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () =>
|
||||
act(() => deleteSignOnExemption(props.cfgId, row.id), '已删除免 MFA 规则并恢复优先级'),
|
||||
act(() => deleteSignOnExemption(props.cfgId, row.id, props.domainId), '已删除免 MFA 规则并恢复优先级'),
|
||||
},
|
||||
{
|
||||
trigger: () =>
|
||||
@@ -136,7 +137,7 @@ const ruleColumns: DataTableColumns<SignOnRule> = [
|
||||
|
||||
async function doDownloadMetadata() {
|
||||
try {
|
||||
await downloadSamlMetadata(props.cfgId)
|
||||
await downloadSamlMetadata(props.cfgId, props.domainId)
|
||||
message.success('SAML 元数据已下载')
|
||||
} catch (e) {
|
||||
message.error(e instanceof Error ? e.message : '下载失败')
|
||||
@@ -154,7 +155,7 @@ function confirmDeleteIdp(row: IdentityProvider) {
|
||||
positiveText: '确认删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () =>
|
||||
act(() => deleteIdp(props.cfgId, row.id), `已删除 IdP「${row.name}」及其关联规则`),
|
||||
act(() => deleteIdp(props.cfgId, row.id, props.domainId), `已删除 IdP「${row.name}」及其关联规则`),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -165,7 +166,7 @@ function addExemption() {
|
||||
return
|
||||
}
|
||||
void act(
|
||||
() => createSignOnExemption(props.cfgId, enabled.id),
|
||||
() => createSignOnExemption(props.cfgId, enabled.id, props.domainId),
|
||||
`已为 ${enabled.name} 创建免 MFA 规则并置顶`,
|
||||
)
|
||||
}
|
||||
@@ -211,6 +212,6 @@ function addExemption() {
|
||||
</FootNote>
|
||||
</div>
|
||||
|
||||
<IdpFormModal v-model:show="showCreate" :cfg-id="cfgId" @created="idps.run()" />
|
||||
<IdpFormModal v-model:show="showCreate" :cfg-id="cfgId" :domain-id="domainId" @created="idps.run()" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NDynamicTags, NPopconfirm, NSpin, NSwitch, useMessage } from 'naive-ui'
|
||||
import { NButton, NDynamicTags, NPopconfirm, NSpin, NSwitch } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
@@ -22,14 +22,15 @@ import StatusBadge from '@/components/StatusBadge.vue'
|
||||
import PolicyEditModal from '@/components/tenant/PolicyEditModal.vue'
|
||||
import { useAsync } from '@/composables/useAsync'
|
||||
import type { PasswordPolicy } from '@/types/api'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{ cfgId: number }>()
|
||||
const props = defineProps<{ cfgId: number; domainId?: string }>()
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
|
||||
const policies = useAsync(() => listPasswordPolicies(props.cfgId))
|
||||
const recipients = useAsync(() => getNotificationRecipients(props.cfgId))
|
||||
const identity = useAsync(() => getIdentitySetting(props.cfgId))
|
||||
const policies = useAsync(() => listPasswordPolicies(props.cfgId, props.domainId))
|
||||
const recipients = useAsync(() => getNotificationRecipients(props.cfgId, props.domainId))
|
||||
const identity = useAsync(() => getIdentitySetting(props.cfgId, props.domainId))
|
||||
const webhook = useAsync(() => getLogWebhook(props.cfgId))
|
||||
const relay = useAsync(() => getLogRelay(props.cfgId))
|
||||
const savingIdentity = ref(false)
|
||||
@@ -114,7 +115,7 @@ watch(
|
||||
async function saveRecipients() {
|
||||
saving.value = true
|
||||
try {
|
||||
await updateNotificationRecipients(props.cfgId, emails.value)
|
||||
await updateNotificationRecipients(props.cfgId, emails.value, props.domainId)
|
||||
message.success(emails.value.length ? '通知收件人已保存' : '已关闭 test mode,恢复默认发送')
|
||||
void recipients.run()
|
||||
} catch (e) {
|
||||
@@ -127,7 +128,7 @@ async function saveRecipients() {
|
||||
async function toggleEmailRequired(v: boolean) {
|
||||
savingIdentity.value = true
|
||||
try {
|
||||
await updateIdentitySetting(props.cfgId, v)
|
||||
await updateIdentitySetting(props.cfgId, v, props.domainId)
|
||||
message.success(v ? '已开启:添加用户 / JIT 预配必须提供邮箱' : '已关闭:邮箱变为可选')
|
||||
void identity.run()
|
||||
} catch (e) {
|
||||
@@ -402,6 +403,7 @@ function policyDesc(p: PasswordPolicy): string {
|
||||
<PolicyEditModal
|
||||
v-model:show="showPolicyEdit"
|
||||
:cfg-id="cfgId"
|
||||
:domain-id="domainId"
|
||||
:policy="editingPolicy"
|
||||
@updated="policies.run()"
|
||||
/>
|
||||
|
||||
@@ -1,19 +1,20 @@
|
||||
<script setup lang="ts">
|
||||
import { NSwitch, useMessage } from 'naive-ui'
|
||||
import { NSwitch } from 'naive-ui'
|
||||
import { reactive, ref, watch } from 'vue'
|
||||
|
||||
import { updatePasswordPolicy } from '@/api/tenant'
|
||||
import FormModal from '@/components/FormModal.vue'
|
||||
import type { PasswordPolicy } from '@/types/api'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{ show: boolean; cfgId: number; policy: PasswordPolicy | null }>()
|
||||
const props = defineProps<{ show: boolean; cfgId: number; domainId?: string; policy: PasswordPolicy | null }>()
|
||||
const emit = defineEmits<{ 'update:show': [boolean]; updated: [] }>()
|
||||
|
||||
// 关闭开关时写回的控制台默认值
|
||||
const DEFAULT_EXPIRES_DAYS = 120
|
||||
const DEFAULT_HISTORY_COUNT = 4
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
const submitting = ref(false)
|
||||
const form = reactive({ neverExpires: true, allowReuse: true })
|
||||
|
||||
@@ -45,7 +46,7 @@ async function submit() {
|
||||
}
|
||||
submitting.value = true
|
||||
try {
|
||||
await updatePasswordPolicy(props.cfgId, props.policy.id, body)
|
||||
await updatePasswordPolicy(props.cfgId, props.policy.id, body, props.domainId)
|
||||
message.success('密码策略已更新')
|
||||
emit('update:show', false)
|
||||
emit('updated')
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { NCheckbox, NSelect, useMessage } from 'naive-ui'
|
||||
import { NCheckbox, NSelect } from 'naive-ui'
|
||||
import { computed, ref, watch } from 'vue'
|
||||
|
||||
import { listRegions, subscribeRegion } from '@/api/configs'
|
||||
import FormField from '@/components/FormField.vue'
|
||||
import FormModal from '@/components/FormModal.vue'
|
||||
import { useAsync } from '@/composables/useAsync'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{
|
||||
show: boolean
|
||||
@@ -16,7 +17,7 @@ const props = defineProps<{
|
||||
}>()
|
||||
const emit = defineEmits<{ 'update:show': [boolean]; subscribed: [] }>()
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
const submitting = ref(false)
|
||||
const regionKey = ref<string | null>(null)
|
||||
const acknowledged = ref(false)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { NCheckbox, NInput, useMessage } from 'naive-ui'
|
||||
import { NCheckbox, NInput } from 'naive-ui'
|
||||
import { computed, reactive, ref, watch } from 'vue'
|
||||
|
||||
import {
|
||||
@@ -12,12 +12,13 @@ import {
|
||||
import FormField from '@/components/FormField.vue'
|
||||
import FormModal from '@/components/FormModal.vue'
|
||||
import type { IamUser, IamUserDetail } from '@/types/api'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
// user 非空即编辑模式:打开时拉取域档案预填充,提交只发送与原值不同的字段
|
||||
const props = defineProps<{ show: boolean; cfgId: number; user?: IamUser | null }>()
|
||||
const props = defineProps<{ show: boolean; cfgId: number; domainId?: string; user?: IamUser | null }>()
|
||||
const emit = defineEmits<{ 'update:show': [boolean]; created: [] }>()
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
const submitting = ref(false)
|
||||
// 域开启「用户需要提供主电子邮件地址」时邮箱必填
|
||||
const emailRequired = ref(false)
|
||||
@@ -57,7 +58,7 @@ watch(
|
||||
|
||||
async function loadIdentitySetting() {
|
||||
try {
|
||||
emailRequired.value = (await getIdentitySetting(props.cfgId)).primaryEmailRequired
|
||||
emailRequired.value = (await getIdentitySetting(props.cfgId, props.domainId)).primaryEmailRequired
|
||||
} catch {
|
||||
emailRequired.value = false
|
||||
}
|
||||
@@ -67,7 +68,7 @@ async function loadIdentitySetting() {
|
||||
async function loadDetail(userId: string) {
|
||||
detailLoading.value = true
|
||||
try {
|
||||
const d = await getUserDetail(props.cfgId, userId)
|
||||
const d = await getUserDetail(props.cfgId, userId, props.domainId)
|
||||
if (props.user?.id !== userId) return
|
||||
detail.value = d
|
||||
if (!d.inDomain) return
|
||||
@@ -139,20 +140,24 @@ async function submitEdit(u: IamUser) {
|
||||
emit('update:show', false)
|
||||
return
|
||||
}
|
||||
await updateUser(props.cfgId, u.id, body)
|
||||
await updateUser(props.cfgId, u.id, body, props.domainId)
|
||||
message.success(`用户「${u.name}」已更新`)
|
||||
}
|
||||
|
||||
async function submitCreate() {
|
||||
const created = await createUser(props.cfgId, {
|
||||
name: form.name.trim(),
|
||||
givenName: form.givenName.trim(),
|
||||
familyName: form.familyName.trim(),
|
||||
email: form.email.trim() || undefined,
|
||||
description: form.description.trim() || undefined,
|
||||
grantDomainAdmin: form.grantDomainAdmin,
|
||||
addToAdminGroup: form.addToAdminGroup,
|
||||
})
|
||||
const created = await createUser(
|
||||
props.cfgId,
|
||||
{
|
||||
name: form.name.trim(),
|
||||
givenName: form.givenName.trim(),
|
||||
familyName: form.familyName.trim(),
|
||||
email: form.email.trim() || undefined,
|
||||
description: form.description.trim() || undefined,
|
||||
grantDomainAdmin: form.grantDomainAdmin,
|
||||
addToAdminGroup: form.addToAdminGroup,
|
||||
},
|
||||
props.domainId,
|
||||
)
|
||||
message.success(`用户「${created.name}」已创建,可再执行「重置密码」下发初始密码`)
|
||||
}
|
||||
|
||||
@@ -225,7 +230,7 @@ async function submit() {
|
||||
{{ editing ? '身份域管理员' : '添加到身份域管理员' }}
|
||||
</NCheckbox>
|
||||
<NCheckbox v-model:checked="form.addToAdminGroup" :disabled="classicUser || detailLoading">
|
||||
{{ editing ? '管理员组(Administrators)成员' : '添加到管理员组(Administrators)' }}
|
||||
{{ editing ? '租户管理员组成员' : '添加到租户管理员组' }}
|
||||
</NCheckbox>
|
||||
<div v-if="adminHint" class="text-xs text-ink-3">{{ adminHint }}</div>
|
||||
</div>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { NButton, NDataTable, NPopconfirm, useDialog, useMessage, type DataTableColumns } from 'naive-ui'
|
||||
import { NButton, NDataTable, NPopconfirm, useDialog, type DataTableColumns } from 'naive-ui'
|
||||
import { h, ref } from 'vue'
|
||||
|
||||
import { clearUserMfa, deleteUser, deleteUserApiKeys, listUsers, resetUserPassword } from '@/api/tenant'
|
||||
@@ -9,12 +9,13 @@ import UserFormModal from '@/components/tenant/UserFormModal.vue'
|
||||
import { fmtTime } from '@/composables/useFormat'
|
||||
import { useAsync } from '@/composables/useAsync'
|
||||
import type { IamUser } from '@/types/api'
|
||||
import { useToast } from '@/composables/useToast'
|
||||
|
||||
const props = defineProps<{ cfgId: number }>()
|
||||
const props = defineProps<{ cfgId: number; domainId?: string }>()
|
||||
|
||||
const message = useMessage()
|
||||
const message = useToast()
|
||||
const dialog = useDialog()
|
||||
const users = useAsync(() => listUsers(props.cfgId))
|
||||
const users = useAsync(() => listUsers(props.cfgId, props.domainId))
|
||||
const showForm = ref(false)
|
||||
const editingUser = ref<IamUser | null>(null)
|
||||
|
||||
@@ -48,13 +49,13 @@ function confirmDelete(row: IamUser) {
|
||||
]),
|
||||
positiveText: '确认删除',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: () => act(() => deleteUser(props.cfgId, row.id), `已删除用户 ${row.name}`),
|
||||
onPositiveClick: () => act(() => deleteUser(props.cfgId, row.id, props.domainId), `已删除用户 ${row.name}`),
|
||||
})
|
||||
}
|
||||
|
||||
async function resetPwd(row: IamUser) {
|
||||
try {
|
||||
const { password } = await resetUserPassword(props.cfgId, row.id)
|
||||
const { password } = await resetUserPassword(props.cfgId, row.id, props.domainId)
|
||||
dialog.success({
|
||||
title: '一次性密码',
|
||||
content: () =>
|
||||
@@ -119,7 +120,7 @@ const columns: DataTableColumns<IamUser> = [
|
||||
NPopconfirm,
|
||||
{
|
||||
onPositiveClick: () =>
|
||||
act(() => clearUserMfa(props.cfgId, row.id), `已清除 ${row.name} 的全部 MFA`),
|
||||
act(() => clearUserMfa(props.cfgId, row.id, props.domainId), `已清除 ${row.name} 的全部 MFA`),
|
||||
},
|
||||
{
|
||||
trigger: () => h(NButton, { size: 'tiny', quaternary: true }, { default: () => '清 MFA' }),
|
||||
@@ -169,6 +170,7 @@ const columns: DataTableColumns<IamUser> = [
|
||||
<UserFormModal
|
||||
v-model:show="showForm"
|
||||
:cfg-id="cfgId"
|
||||
:domain-id="domainId"
|
||||
:user="editingUser"
|
||||
@created="users.run()"
|
||||
/>
|
||||
|
||||
Reference in New Issue
Block a user