初始提交:OCI 面板前端(含 GenAI 网关一期)

This commit is contained in:
Wang Defa
2026-07-09 15:31:29 +08:00
commit db45a669e3
135 changed files with 25220 additions and 0 deletions
+344
View File
@@ -0,0 +1,344 @@
import {
mockAuditEvents,
mockIdentitySetting,
mockIdps,
mockLimits,
mockPolicies,
mockRecipients,
mockSignOnRules,
mockSubscriptions,
mockUserDetails,
mockUsers,
} from './mock'
import { mockOn, mocked, request } from './request'
import type {
AuditEventsResult,
IamUser,
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}`)
}
// ---- 用户 ----
export function listUsers(id: number): Promise<IamUser[]> {
if (mockOn) return mocked(mockUsers)
return request(`/oci-configs/${id}/users`)
}
// 编辑表单预填充用;不在身份域的经典用户返回 inDomain: false
export function getUserDetail(id: number, userId: string): Promise<IamUserDetail> {
if (mockOn)
return mocked(
mockUserDetails[userId] ?? {
inDomain: false,
givenName: '',
familyName: '',
isDomainAdmin: false,
inAdminGroup: false,
},
)
return request(`/oci-configs/${id}/users/${userId}`)
}
export interface CreateUserBody {
name: string
givenName: string
familyName: string
description?: string
email?: string
/** 同时勾选时后端先授身份域管理员,再加入管理员组 */
grantDomainAdmin?: boolean
addToAdminGroup?: boolean
}
export function createUser(id: number, body: CreateUserBody): 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 })
}
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): 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 })
}
export function deleteUser(id: number, userId: string): Promise<void> {
if (mockOn) return mocked(undefined)
return request(`/oci-configs/${id}/users/${userId}`, { method: 'DELETE' })
}
export function resetUserPassword(id: number, userId: string): Promise<{ password: string }> {
if (mockOn) return mocked({ password: 'Xy9#kQ2m$Lp8' })
return request(`/oci-configs/${id}/users/${userId}/reset-password`, { method: 'POST' })
}
export function clearUserMfa(id: number, userId: string): Promise<{ deletedTotpDevices: number }> {
if (mockOn) return mocked({ deletedTotpDevices: 1 })
return request(`/oci-configs/${id}/users/${userId}/mfa-devices`, { method: 'DELETE' })
}
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 },
})
}
// ---- 审计日志 ----
/** 实时查询租户 OCI 审计事件:hours ∈ [1,72] 首查;
* 截断续查传上次响应的 start/end 与 nextPage(绝对窗+游标断点续翻) */
export function listAuditEvents(
id: number,
opts: { hours?: number; region?: string; start?: string; end?: string; page?: string } = {},
): Promise<AuditEventsResult> {
// mock 下选 1 小时窗演示截断提示
if (mockOn)
return mocked(
{
items: mockAuditEvents,
truncated: opts.hours === 1,
start: '2026-07-06T10:00:00Z',
end: '2026-07-07T10:00:00Z',
},
300,
)
const { hours = 24, region, start, end, page } = opts
const query = start && end ? { region, start, end, page } : { hours, region }
return request(`/oci-configs/${id}/audit-events`, { query })
}
/** 取回单条审计事件的原始 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): Promise<NotificationRecipients> {
if (mockOn) return mocked(mockRecipients)
return request(`/oci-configs/${id}/notification-recipients`)
}
export function updateNotificationRecipients(
id: number,
recipients: string[],
): Promise<NotificationRecipients> {
if (mockOn) return mocked({ recipients, testModeEnabled: recipients.length > 0 })
return request(`/oci-configs/${id}/notification-recipients`, {
method: 'PUT',
body: { recipients },
})
}
export function listPasswordPolicies(id: number): Promise<PasswordPolicy[]> {
if (mockOn) return mocked(mockPolicies)
return request(`/oci-configs/${id}/password-policies`)
}
export function updatePasswordPolicy(
id: number,
policyId: string,
body: { passwordExpiresAfter?: number; minLength?: number; numPasswordsInHistory?: number },
): Promise<PasswordPolicy> {
if (mockOn) return mocked({ ...mockPolicies[0], ...body })
return request(`/oci-configs/${id}/password-policies/${policyId}`, { method: 'PUT', body })
}
// ---- 身份设置 ----
export function getIdentitySetting(id: number): Promise<IdentitySetting> {
if (mockOn) return mocked({ ...mockIdentitySetting })
return request(`/oci-configs/${id}/identity-settings`)
}
export function updateIdentitySetting(
id: number,
primaryEmailRequired: boolean,
): Promise<IdentitySetting> {
if (mockOn) {
mockIdentitySetting.primaryEmailRequired = primaryEmailRequired
return mocked({ ...mockIdentitySetting })
}
return request(`/oci-configs/${id}/identity-settings`, {
method: 'PUT',
body: { primaryEmailRequired },
})
}
export async function downloadSamlMetadata(id: number): 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 resp = await fetch(`/api/v1/oci-configs/${id}/saml-metadata`, {
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
/** logodata 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): 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 })
}
export function listIdps(id: number): Promise<IdentityProvider[]> {
if (mockOn) return mocked(mockIdps)
return request(`/oci-configs/${id}/identity-providers`)
}
export function activateIdp(id: number, idpId: string, enabled: boolean): Promise<IdentityProvider> {
if (mockOn) return mocked({ ...mockIdps[0], enabled })
return request(`/oci-configs/${id}/identity-providers/${idpId}/activate`, {
method: 'POST',
body: { enabled },
})
}
export function deleteIdp(id: number, idpId: string): Promise<void> {
if (mockOn) return mocked(undefined)
return request(`/oci-configs/${id}/identity-providers/${idpId}`, { method: 'DELETE' })
}
export function listSignOnRules(id: number): Promise<SignOnRule[]> {
if (mockOn) return mocked(mockSignOnRules)
return request(`/oci-configs/${id}/sign-on-rules`)
}
export function createSignOnExemption(id: number, identityProviderId: string): Promise<SignOnRule> {
if (mockOn) return mocked(mockSignOnRules[0])
return request(`/oci-configs/${id}/sign-on-exemptions`, {
method: 'POST',
body: { identityProviderId },
})
}
export function deleteSignOnExemption(id: number, ruleId: string): Promise<void> {
if (mockOn) return mocked(undefined)
return request(`/oci-configs/${id}/sign-on-exemptions/${ruleId}`, { method: 'DELETE' })
}