import { mockAuditEvents, mockIdentitySetting, mockIdps, mockLimits, mockPolicies, mockRecipients, mockSignOnRules, mockSubscriptions, mockUserDetails, mockUsers, } from './mock' import { mockOn, mocked, rawFetch, request } from './request' import type { AuditEventsResult, CreatedApiKey, IamUser, IdentityDomain, IamUserDetail, IdentityProvider, IdentitySetting, IdpIconUpload, LimitItem, LimitService, NotificationRecipients, PasswordPolicy, SignOnRule, SubscriptionDetail, SubscriptionSummary, UserApiKey, } 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 { 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 } }) } // ---- 用户 API 签名 Key ---- const mockCreatedKey: CreatedApiKey = { fingerprint: '11:22:33:44:55:66:77:88:99:00:aa:bb:cc:dd:ee:ff', privateKey: '-----BEGIN RSA PRIVATE KEY-----\nMOCKMOCKMOCK\n-----END RSA PRIVATE KEY-----\n', configIni: '[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 { 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 { if (mockOn) return mocked(undefined) return request(`/oci-configs/${id}/users/${userId}/api-keys/${encodeURIComponent(fingerprint)}`, { method: 'DELETE', }) } /** 把刚创建的 key 设为本配置签名凭据(验证可用后落库,不删旧 key);私钥为创建时下发的那份回传; * userId 给定且异于当前签名用户时,一并把面板签名用户切换为该用户 */ export function activateApiKey( id: number, fingerprint: string, privateKey: string, userId?: string, ): Promise { if (mockOn) return mocked(undefined) return request(`/oci-configs/${id}/activate-api-key`, { method: 'POST', body: { userId, fingerprint, privateKey }, }) } // ---- 审计日志 ---- /** 批式懒加载查询审计事件:cursor 空自当前时刻首查, * 非空从上次响应游标向更早续取一批(~200 条,服务端分窗回溯); * q 为服务端全文检索关键字,仅首查生效(续查沿用游标内嵌关键字) */ export function listAuditEvents( id: number, opts: { region?: string; cursor?: string; limit?: number; q?: string } = {}, ): Promise { // mock:首批带游标,续查一批后到尽头,演示懒加载链路;q 演示不区分大小写的包含匹配 if (mockOn) { const kw = (opts.q ?? '').replace(/\*/g, '').toLowerCase() const items = kw ? mockAuditEvents.filter((e) => [e.eventName, e.resourceName, e.principalName, e.ipAddress, e.source].some((v) => (v ?? '').toLowerCase().includes(kw), ), ) : mockAuditEvents return mocked({ items, 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 { if (mockOn) return mocked(mockRecipients) return request(`/oci-configs/${id}/notification-recipients`, { query: { domainId } }) } export function updateNotificationRecipients( id: number, recipients: string[], domainId?: string, ): Promise { 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 { 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 { 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 { if (mockOn) return mocked({ ...mockIdentitySetting }) return request(`/oci-configs/${id}/identity-settings`, { query: { domainId } }) } export function updateIdentitySetting( id: number, primaryEmailRequired: boolean, domainId?: string, ): Promise { 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 { if (mockOn) { const blob = new Blob([''], { 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:http(s) 外链 URL(可先经 IdP 图标上传接口取得) */ 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 { 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 } }) } /** 上传 IdP 图标到身份域公共图片存储,返回可填入 iconUrl 的公网地址(multipart 走 rawFetch) */ export async function uploadIdpIcon( id: number, file: File, domainId?: string, ): Promise { if (mockOn) return mocked({ url: 'https://mock.local/images/idp-icon.png', fileName: 'images/idp-icon.png' }, 600) const fd = new FormData() fd.append('file', file, file.name) const resp = await rawFetch(`/oci-configs/${id}/idp-icons`, { method: 'POST', query: { domainId }, body: fd, }) return (await resp.json()) as IdpIconUpload } /** 删除尚未被 IdP 采用的身份域公共图片 */ export function deleteIdpIcon(id: number, fileName: string, domainId?: string): Promise { if (mockOn) return mocked(undefined) return request(`/oci-configs/${id}/idp-icons`, { method: 'DELETE', query: { domainId, fileName }, }) } export function listIdps(id: number, domainId?: string): Promise { 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 { 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 { 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 { 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 { 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 { if (mockOn) return mocked(undefined) return request(`/oci-configs/${id}/sign-on-exemptions/${ruleId}`, { method: 'DELETE', query: { domainId } }) }