358 lines
12 KiB
TypeScript
358 lines
12 KiB
TypeScript
import {
|
||
mockAuditEvents,
|
||
mockIdentitySetting,
|
||
mockIdps,
|
||
mockLimits,
|
||
mockPolicies,
|
||
mockRecipients,
|
||
mockSignOnRules,
|
||
mockSubscriptions,
|
||
mockUserDetails,
|
||
mockUsers,
|
||
} from './mock'
|
||
import { mockOn, mocked, request } from './request'
|
||
import type {
|
||
AuditEventsResult,
|
||
IamUser,
|
||
IdentityDomain,
|
||
IamUserDetail,
|
||
IdentityProvider,
|
||
IdentitySetting,
|
||
LimitItem,
|
||
LimitService,
|
||
NotificationRecipients,
|
||
PasswordPolicy,
|
||
SignOnRule,
|
||
SubscriptionDetail,
|
||
SubscriptionSummary,
|
||
} from '@/types/api'
|
||
|
||
// ---- 配额 ----
|
||
export interface LimitsQuery {
|
||
service?: string
|
||
region?: string
|
||
name?: string
|
||
scopeType?: 'GLOBAL' | 'REGION' | 'AD'
|
||
availabilityDomain?: string
|
||
/** 服务端过滤上限为 0 的配额项,查用量时不占并发名额 */
|
||
nonZero?: boolean
|
||
withAvailability?: boolean
|
||
}
|
||
|
||
export function listLimits(id: number, query: LimitsQuery = {}): Promise<LimitItem[]> {
|
||
if (mockOn) {
|
||
if (query.service === 'regions')
|
||
return mocked([{ name: 'subscribed-region-count', scopeType: 'GLOBAL', value: 3 }])
|
||
const needle = (query.name ?? '').toLowerCase()
|
||
return mocked(
|
||
mockLimits
|
||
.filter((l) => !needle || l.name.toLowerCase().includes(needle))
|
||
.filter((l) => !query.scopeType || l.scopeType === query.scopeType)
|
||
.filter((l) => !query.nonZero || l.value > 0),
|
||
)
|
||
}
|
||
return request(`/oci-configs/${id}/limits`, { query: { ...query } })
|
||
}
|
||
|
||
export function listLimitServices(id: number): Promise<LimitService[]> {
|
||
if (mockOn)
|
||
return mocked([
|
||
{ name: 'compute', description: 'Compute' },
|
||
{ name: 'vcn', description: 'Virtual Cloud Network' },
|
||
{ name: 'block-storage', description: 'Block Volume' },
|
||
])
|
||
return request(`/oci-configs/${id}/limits/services`)
|
||
}
|
||
|
||
// ---- 订阅 ----
|
||
export function listSubscriptions(id: number): Promise<SubscriptionSummary[]> {
|
||
if (mockOn)
|
||
return mocked(
|
||
mockSubscriptions.map((d) => ({
|
||
id: d.id,
|
||
serviceName: d.serviceName,
|
||
timeCreated: d.timeCreated,
|
||
timeUpdated: d.timeUpdated,
|
||
})),
|
||
)
|
||
return request(`/oci-configs/${id}/subscriptions`)
|
||
}
|
||
|
||
export function getSubscription(id: number, subscriptionId: string): Promise<SubscriptionDetail> {
|
||
if (mockOn)
|
||
return mocked(mockSubscriptions.find((d) => d.id === subscriptionId) ?? mockSubscriptions[0])
|
||
return request(`/oci-configs/${id}/subscriptions/${subscriptionId}`)
|
||
}
|
||
|
||
// ---- 身份域 ----
|
||
/** 租户 ACTIVE 身份域列表;空数组表示无域老租户(隐藏选择器走经典路径) */
|
||
export function listIdentityDomains(id: number): Promise<IdentityDomain[]> {
|
||
if (mockOn)
|
||
return mocked([
|
||
{ id: 'ocid1.domain.default', displayName: 'Default', homeRegion: 'us-phoenix-1', type: 'DEFAULT', licenseType: 'free' },
|
||
{ id: 'ocid1.domain.idcs', displayName: 'OracleIdentityCloudService', homeRegion: 'us-phoenix-1', type: 'SECONDARY', licenseType: 'idcs-foundation' },
|
||
])
|
||
return request(`/oci-configs/${id}/domains`)
|
||
}
|
||
|
||
// ---- 用户 ----
|
||
export function listUsers(id: number, domainId?: string): Promise<IamUser[]> {
|
||
if (mockOn) return mocked(mockUsers)
|
||
return request(`/oci-configs/${id}/users`, { query: { domainId } })
|
||
}
|
||
|
||
// 编辑表单预填充用;不在身份域的经典用户返回 inDomain: false
|
||
export function getUserDetail(id: number, userId: string, domainId?: string): Promise<IamUserDetail> {
|
||
if (mockOn)
|
||
return mocked(
|
||
mockUserDetails[userId] ?? {
|
||
inDomain: false,
|
||
givenName: '',
|
||
familyName: '',
|
||
isDomainAdmin: false,
|
||
inAdminGroup: false,
|
||
},
|
||
)
|
||
return request(`/oci-configs/${id}/users/${userId}`, { query: { domainId } })
|
||
}
|
||
|
||
export interface CreateUserBody {
|
||
name: string
|
||
givenName: string
|
||
familyName: string
|
||
description?: string
|
||
email?: string
|
||
/** 同时勾选时后端先授身份域管理员,再加入管理员组 */
|
||
grantDomainAdmin?: boolean
|
||
addToAdminGroup?: boolean
|
||
}
|
||
|
||
export function createUser(id: number, body: CreateUserBody, domainId?: string): Promise<IamUser> {
|
||
if (mockOn)
|
||
return mocked({ ...mockUsers[2], id: 'ocid1.user..new', name: body.name, email: body.email ?? '' })
|
||
return request(`/oci-configs/${id}/users`, { method: 'POST', body, query: { domainId } })
|
||
}
|
||
|
||
export interface UpdateUserBody {
|
||
description?: string
|
||
email?: string
|
||
givenName?: string
|
||
familyName?: string
|
||
/** 三态:true 授予/加入,false 撤销/移出,缺省不动 */
|
||
grantDomainAdmin?: boolean
|
||
addToAdminGroup?: boolean
|
||
}
|
||
|
||
export function updateUser(id: number, userId: string, body: UpdateUserBody, domainId?: string): Promise<IamUser> {
|
||
if (mockOn) {
|
||
const u = mockUsers.find((x) => x.id === userId) ?? mockUsers[2]
|
||
if (body.description !== undefined) u.description = body.description
|
||
if (body.email !== undefined) u.email = body.email
|
||
return mocked({ ...u })
|
||
}
|
||
return request(`/oci-configs/${id}/users/${userId}`, { method: 'PUT', body, query: { domainId } })
|
||
}
|
||
|
||
export function deleteUser(id: number, userId: string, domainId?: string): Promise<void> {
|
||
if (mockOn) return mocked(undefined)
|
||
return request(`/oci-configs/${id}/users/${userId}`, { method: 'DELETE', query: { domainId } })
|
||
}
|
||
|
||
export function resetUserPassword(id: number, userId: string, domainId?: string): Promise<{ password: string }> {
|
||
if (mockOn) return mocked({ password: 'Xy9#kQ2m$Lp8' })
|
||
return request(`/oci-configs/${id}/users/${userId}/reset-password`, { method: 'POST', query: { domainId } })
|
||
}
|
||
|
||
export function clearUserMfa(id: number, userId: string, domainId?: string): Promise<{ deletedTotpDevices: number }> {
|
||
if (mockOn) return mocked({ deletedTotpDevices: 1 })
|
||
return request(`/oci-configs/${id}/users/${userId}/mfa-devices`, { method: 'DELETE', query: { domainId } })
|
||
}
|
||
|
||
export function deleteUserApiKeys(
|
||
id: number,
|
||
userId: string,
|
||
includeCurrent = false,
|
||
): Promise<{ deletedApiKeys: number }> {
|
||
if (mockOn) return mocked({ deletedApiKeys: 1 })
|
||
return request(`/oci-configs/${id}/users/${userId}/api-keys`, {
|
||
method: 'DELETE',
|
||
query: { includeCurrent },
|
||
})
|
||
}
|
||
|
||
// ---- 审计日志 ----
|
||
/** 批式懒加载查询审计事件:cursor 空自当前时刻首查,
|
||
* 非空从上次响应游标向更早续取一批(~100 条,服务端分窗回溯) */
|
||
export function listAuditEvents(
|
||
id: number,
|
||
opts: { region?: string; cursor?: string; limit?: number } = {},
|
||
): Promise<AuditEventsResult> {
|
||
// mock:首批带游标,续查一批后到尽头,演示懒加载链路
|
||
if (mockOn)
|
||
return mocked(
|
||
{ items: mockAuditEvents, cursor: opts.cursor ? undefined : 'mock-cursor', exhausted: !!opts.cursor },
|
||
300,
|
||
)
|
||
return request(`/oci-configs/${id}/audit-events`, { query: { ...opts } })
|
||
}
|
||
|
||
/** 取回单条审计事件的原始 JSON;404 表示缓存过期且重查未找回(前端降级展示精简字段) */
|
||
export function getAuditEventDetail(
|
||
id: number,
|
||
q: { eventId: string; eventTime: string; region?: string },
|
||
): Promise<{ raw: unknown }> {
|
||
if (mockOn) return mocked({ raw: { eventId: q.eventId, mock: true, note: '演示原始事件' } }, 200)
|
||
return request(`/oci-configs/${id}/audit-events/detail`, { query: q })
|
||
}
|
||
|
||
// ---- 域设置 ----
|
||
export function getNotificationRecipients(id: number, domainId?: string): Promise<NotificationRecipients> {
|
||
if (mockOn) return mocked(mockRecipients)
|
||
return request(`/oci-configs/${id}/notification-recipients`, { query: { domainId } })
|
||
}
|
||
|
||
export function updateNotificationRecipients(
|
||
id: number,
|
||
recipients: string[],
|
||
domainId?: string,
|
||
): Promise<NotificationRecipients> {
|
||
if (mockOn) return mocked({ recipients, testModeEnabled: recipients.length > 0 })
|
||
return request(`/oci-configs/${id}/notification-recipients`, {
|
||
method: 'PUT',
|
||
body: { recipients },
|
||
query: { domainId },
|
||
})
|
||
}
|
||
|
||
export function listPasswordPolicies(id: number, domainId?: string): Promise<PasswordPolicy[]> {
|
||
if (mockOn) return mocked(mockPolicies)
|
||
return request(`/oci-configs/${id}/password-policies`, { query: { domainId } })
|
||
}
|
||
|
||
export function updatePasswordPolicy(
|
||
id: number,
|
||
policyId: string,
|
||
body: { passwordExpiresAfter?: number; minLength?: number; numPasswordsInHistory?: number },
|
||
domainId?: string,
|
||
): Promise<PasswordPolicy> {
|
||
if (mockOn) return mocked({ ...mockPolicies[0], ...body })
|
||
return request(`/oci-configs/${id}/password-policies/${policyId}`, { method: 'PUT', body, query: { domainId } })
|
||
}
|
||
|
||
// ---- 身份设置 ----
|
||
export function getIdentitySetting(id: number, domainId?: string): Promise<IdentitySetting> {
|
||
if (mockOn) return mocked({ ...mockIdentitySetting })
|
||
return request(`/oci-configs/${id}/identity-settings`, { query: { domainId } })
|
||
}
|
||
|
||
export function updateIdentitySetting(
|
||
id: number,
|
||
primaryEmailRequired: boolean,
|
||
domainId?: string,
|
||
): Promise<IdentitySetting> {
|
||
if (mockOn) {
|
||
mockIdentitySetting.primaryEmailRequired = primaryEmailRequired
|
||
return mocked({ ...mockIdentitySetting })
|
||
}
|
||
return request(`/oci-configs/${id}/identity-settings`, {
|
||
method: 'PUT',
|
||
body: { primaryEmailRequired },
|
||
query: { domainId },
|
||
})
|
||
}
|
||
|
||
export async function downloadSamlMetadata(id: number, domainId?: string): Promise<void> {
|
||
if (mockOn) {
|
||
const blob = new Blob(['<mock-saml-metadata/>'], { type: 'application/xml' })
|
||
triggerDownload(blob, 'oci-domain-saml-metadata.xml')
|
||
return
|
||
}
|
||
const { useAuthStore } = await import('@/stores/auth')
|
||
const auth = useAuthStore()
|
||
const qs = domainId ? `?domainId=${encodeURIComponent(domainId)}` : ''
|
||
const resp = await fetch(`/api/v1/oci-configs/${id}/saml-metadata${qs}`, {
|
||
headers: auth.token ? { Authorization: `Bearer ${auth.token}` } : {},
|
||
})
|
||
if (!resp.ok) throw new Error(`下载失败(${resp.status})`)
|
||
triggerDownload(await resp.blob(), 'oci-domain-saml-metadata.xml')
|
||
}
|
||
|
||
function triggerDownload(blob: Blob, filename: string) {
|
||
const url = URL.createObjectURL(blob)
|
||
const a = document.createElement('a')
|
||
a.href = url
|
||
a.download = filename
|
||
a.click()
|
||
URL.revokeObjectURL(url)
|
||
}
|
||
|
||
// ---- Federation ----
|
||
export interface CreateIdpBody {
|
||
name: string
|
||
metadata: string
|
||
description?: string
|
||
/** logo:data URI 或外链 URL */
|
||
iconUrl?: string
|
||
nameIdFormat?: string
|
||
/** 非空表示按断言属性映射;空则按 SAML NameID 映射 */
|
||
assertionAttribute?: string
|
||
userStoreAttribute?: string
|
||
jitEnabled?: boolean
|
||
jitCreate?: boolean
|
||
jitUpdate?: boolean
|
||
jitAssignAdminGroup?: boolean
|
||
jitMapEmail?: boolean
|
||
}
|
||
|
||
export function createIdp(id: number, body: CreateIdpBody, domainId?: string): Promise<IdentityProvider> {
|
||
if (mockOn) {
|
||
const created = {
|
||
...mockIdps[1],
|
||
id: `idp-${mockIdps.length + 1}`,
|
||
name: body.name,
|
||
enabled: false,
|
||
}
|
||
mockIdps.push(created)
|
||
return mocked(created, 900)
|
||
}
|
||
return request(`/oci-configs/${id}/identity-providers`, { method: 'POST', body, query: { domainId } })
|
||
}
|
||
|
||
export function listIdps(id: number, domainId?: string): Promise<IdentityProvider[]> {
|
||
if (mockOn) return mocked(mockIdps)
|
||
return request(`/oci-configs/${id}/identity-providers`, { query: { domainId } })
|
||
}
|
||
|
||
export function activateIdp(id: number, idpId: string, enabled: boolean, domainId?: string): Promise<IdentityProvider> {
|
||
if (mockOn) return mocked({ ...mockIdps[0], enabled })
|
||
return request(`/oci-configs/${id}/identity-providers/${idpId}/activate`, {
|
||
method: 'POST',
|
||
body: { enabled },
|
||
query: { domainId },
|
||
})
|
||
}
|
||
|
||
export function deleteIdp(id: number, idpId: string, domainId?: string): Promise<void> {
|
||
if (mockOn) return mocked(undefined)
|
||
return request(`/oci-configs/${id}/identity-providers/${idpId}`, { method: 'DELETE', query: { domainId } })
|
||
}
|
||
|
||
export function listSignOnRules(id: number, domainId?: string): Promise<SignOnRule[]> {
|
||
if (mockOn) return mocked(mockSignOnRules)
|
||
return request(`/oci-configs/${id}/sign-on-rules`, { query: { domainId } })
|
||
}
|
||
|
||
export function createSignOnExemption(id: number, identityProviderId: string, domainId?: string): Promise<SignOnRule> {
|
||
if (mockOn) return mocked(mockSignOnRules[0])
|
||
return request(`/oci-configs/${id}/sign-on-exemptions`, {
|
||
method: 'POST',
|
||
body: { identityProviderId },
|
||
query: { domainId },
|
||
})
|
||
}
|
||
|
||
export function deleteSignOnExemption(id: number, ruleId: string, domainId?: string): Promise<void> {
|
||
if (mockOn) return mocked(undefined)
|
||
return request(`/oci-configs/${id}/sign-on-exemptions/${ruleId}`, { method: 'DELETE', query: { domainId } })
|
||
}
|