发布 0.1.0:通知渠道、告警规则与多项体验修复
CI / test (push) Successful in 27s
Release / release (push) Successful in 26s

This commit is contained in:
Wang Defa
2026-07-10 17:31:19 +08:00
parent 9d0c116ce3
commit 5464d2dfea
68 changed files with 1604 additions and 359 deletions
+58 -15
View File
@@ -1,9 +1,10 @@
<script setup lang="ts">
import { NButton, NPopconfirm, NSpin, NTabPane, NTabs, NTooltip, useMessage } from 'naive-ui'
import { computed, ref } from 'vue'
import { NButton, NPopconfirm, NSelect, NSpin, NTabPane, NTabs, NTooltip } from 'naive-ui'
import { computed, ref, watch } from 'vue'
import { RouterLink, useRoute, useRouter } from 'vue-router'
import { deleteConfig, getConfig, verifyConfig } from '@/api/configs'
import { listIdentityDomains } from '@/api/tenant'
import AccountTypeBadge from '@/components/AccountTypeBadge.vue'
import AuditLogTab from '@/components/tenant/AuditLogTab.vue'
import CostTab from '@/components/tenant/CostTab.vue'
@@ -15,15 +16,17 @@ import QuotaTab from '@/components/tenant/QuotaTab.vue'
import StatusBadge from '@/components/StatusBadge.vue'
import SubsTab from '@/components/tenant/SubsTab.vue'
import UsersTab from '@/components/tenant/UsersTab.vue'
import { aliveMeta } from '@/composables/useAliveStatus'
import { fmtTime } from '@/composables/useFormat'
import { useRegionAlias } from '@/composables/useRegionAlias'
import { useAsync } from '@/composables/useAsync'
import { useScopeStore } from '@/stores/scope'
import { useToast } from '@/composables/useToast'
const scope = useScopeStore()
const route = useRoute()
const router = useRouter()
const message = useMessage()
const message = useToast()
const { alias: regionAlias } = useRegionAlias()
const cfgId = computed(() => Number(route.params.id))
@@ -32,8 +35,33 @@ const verifying = ref(false)
const config = useAsync(() => getConfig(cfgId.value))
/** 失联租户各 tab 拉不到云端数据,内容区整体换失联占位 */
const isDead = computed(() => config.data.value?.aliveStatus === 'dead')
/** 失联/暂停租户各 tab 拉不到云端数据,内容区整体换占位 */
const isBlocked = computed(() =>
['dead', 'suspended'].includes(config.data.value?.aliveStatus ?? ''),
)
// ---- 身份域:用户 / IDP / 其他 三个 tab 共享的域上下文 ----
// 拉取失败(无权限/老租户)静默降级:不显示选择器,各 tab 走缺省 Default 路径
const domains = useAsync(async () => {
try {
return await listIdentityDomains(cfgId.value)
} catch {
return []
}
})
const domainId = ref<string | undefined>(undefined)
watch(domains.data, (list) => {
if (!list?.length) return
const preferred = list.find((d) => d.displayName === 'Default') ?? list[0]
domainId.value ??= preferred.id
})
const domainOptions = computed(
() => domains.data.value?.map((d) => ({ label: d.displayName, value: d.id })) ?? [],
)
/** 域选择器只在多域租户的域相关 tab 显示 */
const showDomainPicker = computed(
() => (domains.data.value?.length ?? 0) > 1 && ['users', 'idp', 'misc'].includes(tab.value),
)
async function verify() {
verifying.value = true
@@ -53,7 +81,7 @@ async function verify() {
async function remove() {
try {
await deleteConfig(cfgId.value)
message.success('已删除(仅移除本面板配置,不影响云端资源)')
message.success('已删除租户及关联本地数据,OCI 云端资源未受影响')
void scope.refreshConfigs()
void router.push({ name: 'tenants' })
} catch (e) {
@@ -88,19 +116,22 @@ async function remove() {
<div class="flex flex-wrap items-center gap-3">
<h1 class="page-title">{{ config.data.value.alias }}</h1>
<NTooltip
v-if="isDead && config.data.value.lastError"
v-if="isBlocked && config.data.value.lastError"
trigger="hover"
placement="top-start"
>
<template #trigger>
<StatusBadge kind="term" label="失联" />
<StatusBadge
:kind="aliveMeta(config.data.value.aliveStatus).kind"
:label="aliveMeta(config.data.value.aliveStatus).label"
/>
</template>
<div class="max-w-80 text-xs">{{ config.data.value.lastError }}</div>
</NTooltip>
<StatusBadge
v-else
:kind="config.data.value.aliveStatus === 'alive' ? 'run' : 'term'"
:label="config.data.value.aliveStatus === 'alive' ? '存活' : '失联'"
:kind="aliveMeta(config.data.value.aliveStatus).kind"
:label="aliveMeta(config.data.value.aliveStatus).label"
/>
<AccountTypeBadge :type="config.data.value.accountType" />
<div class="flex-1" />
@@ -109,7 +140,8 @@ async function remove() {
<template #trigger>
<NButton size="small" type="error" ghost>删除租户</NButton>
</template>
删除{{ config.data.value.alias }}仅移除本面板配置与快照不影响云端资源
删除{{ config.data.value.alias }}将清理本面板中的关联任务快照回传事件与 Webhook
密钥告警及 AI 渠道数据OCI 云端资源不会被删除云端日志回传链路请先在其他页销毁
</NPopconfirm>
</div>
@@ -140,19 +172,30 @@ async function remove() {
<NTabPane name="subs" tab="订阅" />
<NTabPane name="audit" tab="审计日志" />
<NTabPane name="misc" tab="其他" />
<template #suffix>
<!-- 多域租户:用户/IDP/其他 tab 按选中域读写 -->
<div v-if="showDomainPicker" class="w-64 max-md:w-44">
<NSelect v-model:value="domainId" size="small" :options="domainOptions" placeholder="身份域" />
</div>
</template>
</NTabs>
</div>
<template v-if="config.data.value">
<DeadPlaceholder v-if="isDead" :last-error="config.data.value.lastError" />
<DeadPlaceholder
v-if="isBlocked"
:last-error="config.data.value.lastError"
:suspended="config.data.value.aliveStatus === 'suspended'"
/>
<template v-else>
<QuotaTab v-if="tab === 'quota'" :cfg-id="cfgId" />
<IdpTab v-else-if="tab === 'idp'" :cfg-id="cfgId" />
<UsersTab v-else-if="tab === 'users'" :cfg-id="cfgId" />
<!-- 域相关 tab domainId key:切域整组件重挂载,状态与数据全量刷新 -->
<IdpTab v-else-if="tab === 'idp'" :key="`idp-${domainId ?? ''}`" :cfg-id="cfgId" :domain-id="domainId" />
<UsersTab v-else-if="tab === 'users'" :key="`users-${domainId ?? ''}`" :cfg-id="cfgId" :domain-id="domainId" />
<CostTab v-else-if="tab === 'cost'" :cfg-id="cfgId" />
<SubsTab v-else-if="tab === 'subs'" :cfg-id="cfgId" :account-type="config.data.value.accountType" />
<AuditLogTab v-else-if="tab === 'audit'" :cfg-id="cfgId" />
<MiscTab v-else :cfg-id="cfgId" />
<MiscTab v-else :key="`misc-${domainId ?? ''}`" :cfg-id="cfgId" :domain-id="domainId" />
</template>
</template>
</div>