初始提交:OCI 面板前端(含 GenAI 网关一期)
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
import { request } from './request'
|
||||
import type { AiCallLog, AiChannel, AiContentLog, AiKey, AiModel, AiModelCacheItem } from '@/types/api'
|
||||
|
||||
// ---- 网关密钥 ----
|
||||
|
||||
export function listAiKeys(): Promise<AiKey[]> {
|
||||
return request<{ items: AiKey[] }>('/ai-keys').then((r) => r.items)
|
||||
}
|
||||
|
||||
export interface CreateAiKeyRequest {
|
||||
name: string
|
||||
value?: string
|
||||
group?: string
|
||||
}
|
||||
|
||||
/** 创建密钥;key 为明文,仅本次响应返回 */
|
||||
export function createAiKey(body: CreateAiKeyRequest): Promise<{ key: string; item: AiKey }> {
|
||||
return request('/ai-keys', { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function updateAiKey(
|
||||
id: number,
|
||||
body: { name?: string; enabled?: boolean; group?: string },
|
||||
): Promise<void> {
|
||||
return request(`/ai-keys/${id}`, { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function deleteAiKey(id: number): Promise<void> {
|
||||
return request(`/ai-keys/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** 设置密钥内容日志窗口:0 关闭,>0 从现在起开启 N 小时(上限 168 = 7 天),返回最新密钥 */
|
||||
export function updateAiKeyContentLog(id: number, hours: number): Promise<AiKey> {
|
||||
return request(`/ai-keys/${id}/content-log`, { method: 'PUT', body: { hours } })
|
||||
}
|
||||
|
||||
// ---- 渠道(号池)----
|
||||
|
||||
export function listAiChannels(): Promise<AiChannel[]> {
|
||||
return request<{ items: AiChannel[] }>('/ai-channels').then((r) => r.items)
|
||||
}
|
||||
|
||||
export interface AiChannelInput {
|
||||
ociConfigId?: number
|
||||
region?: string
|
||||
name?: string
|
||||
group?: string
|
||||
enabled?: boolean
|
||||
priority?: number
|
||||
weight?: number
|
||||
}
|
||||
|
||||
export function createAiChannel(body: AiChannelInput): Promise<AiChannel> {
|
||||
return request('/ai-channels', { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function updateAiChannel(id: number, body: AiChannelInput): Promise<void> {
|
||||
return request(`/ai-channels/${id}`, { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function deleteAiChannel(id: number): Promise<void> {
|
||||
return request(`/ai-channels/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function probeAiChannel(id: number): Promise<AiChannel> {
|
||||
return request(`/ai-channels/${id}/probe`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function syncAiChannelModels(id: number): Promise<AiModelCacheItem[]> {
|
||||
return request<{ items: AiModelCacheItem[] }>(`/ai-channels/${id}/sync-models`, {
|
||||
method: 'POST',
|
||||
}).then((r) => r.items)
|
||||
}
|
||||
|
||||
// ---- 聚合模型与调用日志 ----
|
||||
|
||||
export function listAiModels(): Promise<AiModel[]> {
|
||||
return request<{ items: AiModel[] }>('/ai-models').then((r) => r.items)
|
||||
}
|
||||
|
||||
export function listAiCallLogs(params: {
|
||||
page: number
|
||||
size: number
|
||||
}): Promise<{ items: AiCallLog[]; total: number }> {
|
||||
return request('/ai-logs', { query: params })
|
||||
}
|
||||
|
||||
/** 内容日志(红线例外);keyId / callLogId 可选过滤 */
|
||||
export function listAiContentLogs(params: {
|
||||
page: number
|
||||
size: number
|
||||
keyId?: number
|
||||
callLogId?: number
|
||||
}): Promise<{ items: AiContentLog[]; total: number }> {
|
||||
return request('/ai-content-logs', { query: params })
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import { mockCosts, mockTraffic } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type { CostItem, InstanceTraffic } from '@/types/api'
|
||||
|
||||
export function getInstanceTraffic(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
days = 30,
|
||||
region?: string,
|
||||
): Promise<InstanceTraffic> {
|
||||
if (mockOn) return mocked(mockTraffic, 800)
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/traffic`, {
|
||||
query: { days, region },
|
||||
})
|
||||
}
|
||||
|
||||
export interface CostQuery {
|
||||
startTime?: string
|
||||
endTime?: string
|
||||
granularity?: 'DAILY' | 'MONTHLY'
|
||||
queryType?: 'COST' | 'USAGE'
|
||||
groupBy?: 'service' | 'skuName' | 'region' | 'compartmentName'
|
||||
}
|
||||
|
||||
export function getCosts(cfgId: number, query: CostQuery = {}): Promise<CostItem[]> {
|
||||
if (mockOn) return mocked(mockCosts)
|
||||
return request(`/oci-configs/${cfgId}/costs`, { query: { ...query } })
|
||||
}
|
||||
+151
@@ -0,0 +1,151 @@
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type {
|
||||
CredentialsInfo,
|
||||
OauthProviderInfo,
|
||||
LoginRequest,
|
||||
LoginResponse,
|
||||
OAuthSettings,
|
||||
TotpSetup,
|
||||
TotpStatus,
|
||||
UpdateCredentialsRequest,
|
||||
UpdateOAuthRequest,
|
||||
UserIdentityInfo,
|
||||
} from '@/types/api'
|
||||
|
||||
export function login(body: LoginRequest): Promise<LoginResponse> {
|
||||
if (mockOn) {
|
||||
if (body.password === 'wrong') return Promise.reject(new Error('用户名或密码错误'))
|
||||
return mocked({ token: 'mock-token', expiresAt: '2099-01-01T00:00:00Z' }, 400)
|
||||
}
|
||||
return request('/auth/login', { method: 'POST', body })
|
||||
}
|
||||
|
||||
/** 服务端登出:当前令牌拉黑至自然过期;调用方无论成败都应清本地会话 */
|
||||
export function logout(): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 100)
|
||||
return request('/auth/logout', { method: 'POST' })
|
||||
}
|
||||
|
||||
// ---- 登录凭据 ----
|
||||
|
||||
export function getCredentials(): Promise<CredentialsInfo> {
|
||||
if (mockOn) return mocked({ username: 'admin', passwordLoginDisabled: false })
|
||||
return request('/auth/credentials')
|
||||
}
|
||||
|
||||
/** 修改用户名 / 密码;成功后旧 token 随即失效,调用方应登出重登 */
|
||||
export function updateCredentials(body: UpdateCredentialsRequest): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 400)
|
||||
return request('/auth/credentials', { method: 'PUT', body })
|
||||
}
|
||||
|
||||
/** 保存密码登录禁用开关;开启要求至少绑定一个外部身份(后端 409) */
|
||||
export function updatePasswordLogin(disabled: boolean): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 300)
|
||||
return request('/auth/password-login', { method: 'PUT', body: { disabled } })
|
||||
}
|
||||
|
||||
// ---- 两步验证(TOTP) ----
|
||||
|
||||
export function getTotpStatus(): Promise<TotpStatus> {
|
||||
if (mockOn) return mocked({ enabled: false })
|
||||
return request('/auth/totp')
|
||||
}
|
||||
|
||||
/** 生成待激活密钥;10 分钟内输入验证码激活,重复调用覆盖旧暂存 */
|
||||
export function setupTotp(): Promise<TotpSetup> {
|
||||
if (mockOn)
|
||||
return mocked({
|
||||
secret: 'JBSWY3DPEHPK3PXP',
|
||||
otpauthUri: 'otpauth://totp/oci-portal:admin?secret=JBSWY3DPEHPK3PXP&issuer=oci-portal',
|
||||
})
|
||||
return request('/auth/totp/setup', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function activateTotp(code: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 300)
|
||||
return request('/auth/totp/activate', { method: 'POST', body: { code } })
|
||||
}
|
||||
|
||||
/** 停用两步验证;密码或当前验证码任一确认 */
|
||||
export function disableTotp(body: { password?: string; code?: string }): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 300)
|
||||
return request('/auth/totp/disable', { method: 'POST', body })
|
||||
}
|
||||
|
||||
// ---- 外部身份(OAuth) ----
|
||||
|
||||
/** 可登录的 provider 列表(登录页公开接口;已禁用的不返回);
|
||||
* passwordLoginDisabled 为 true 且有可用 provider 时,登录页隐藏密码表单 */
|
||||
export function getOauthProviders(): Promise<{
|
||||
providers: OauthProviderInfo[]
|
||||
passwordLoginDisabled: boolean
|
||||
}> {
|
||||
if (mockOn)
|
||||
return mocked({
|
||||
providers: [
|
||||
{ provider: 'github', displayName: 'GitHub' },
|
||||
{ provider: 'oidc', displayName: 'OIDC SSO' },
|
||||
],
|
||||
passwordLoginDisabled: false,
|
||||
})
|
||||
return request('/auth/oauth/providers')
|
||||
}
|
||||
|
||||
/** 获取授权跳转 URL;bind 模式要求已登录(带 JWT) */
|
||||
export function getOauthAuthorizeUrl(
|
||||
provider: string,
|
||||
mode: 'login' | 'bind',
|
||||
): Promise<{ url: string }> {
|
||||
if (mockOn) return mocked({ url: 'https://example.com/oauth/authorize?mock=1' })
|
||||
return request(`/auth/oauth/${provider}/authorize`, { query: { mode } })
|
||||
}
|
||||
|
||||
export function listIdentities(): Promise<{ items: UserIdentityInfo[] }> {
|
||||
if (mockOn)
|
||||
return mocked({
|
||||
items: [
|
||||
{ id: 1, provider: 'github', display: 'octocat', createdAt: '2026-07-07T10:00:00+08:00' },
|
||||
],
|
||||
})
|
||||
return request('/auth/identities')
|
||||
}
|
||||
|
||||
export function unbindIdentity(id: number): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 300)
|
||||
return request(`/auth/identities/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
// ---- OAuth provider 配置(设置页) ----
|
||||
|
||||
export function getOAuthSettings(): Promise<OAuthSettings> {
|
||||
if (mockOn)
|
||||
return mocked({
|
||||
oidcIssuer: '',
|
||||
oidcClientId: '',
|
||||
oidcSecretSet: false,
|
||||
oidcDisplayName: '',
|
||||
oidcDisabled: false,
|
||||
githubClientId: '',
|
||||
githubSecretSet: false,
|
||||
githubDisplayName: '',
|
||||
githubDisabled: false,
|
||||
})
|
||||
return request('/settings/oauth')
|
||||
}
|
||||
|
||||
export function updateOAuthSettings(body: UpdateOAuthRequest): Promise<OAuthSettings> {
|
||||
if (mockOn)
|
||||
return mocked({
|
||||
oidcIssuer: body.oidcIssuer,
|
||||
oidcClientId: body.oidcClientId,
|
||||
oidcSecretSet: !!body.oidcClientSecret,
|
||||
oidcDisplayName: body.oidcDisplayName,
|
||||
oidcDisabled: body.oidcDisabled,
|
||||
githubClientId: body.githubClientId,
|
||||
githubSecretSet: !!body.githubClientSecret,
|
||||
githubDisplayName: body.githubDisplayName,
|
||||
githubDisabled: body.githubDisabled,
|
||||
})
|
||||
return request('/settings/oauth', { method: 'PUT', body })
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
import { mockBootVolumes } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type { BootVolume, UpdateBootVolumeRequest } from '@/types/api'
|
||||
|
||||
export function listBootVolumes(
|
||||
cfgId: number,
|
||||
availabilityDomain?: string,
|
||||
compartmentId?: string,
|
||||
region?: string,
|
||||
): Promise<BootVolume[]> {
|
||||
if (mockOn) return mocked(compartmentId ? [] : (mockBootVolumes[cfgId] ?? []))
|
||||
return request(`/oci-configs/${cfgId}/boot-volumes`, {
|
||||
query: { availabilityDomain, compartmentId, region },
|
||||
})
|
||||
}
|
||||
|
||||
export function getBootVolume(
|
||||
cfgId: number,
|
||||
bootVolumeId: string,
|
||||
region?: string,
|
||||
): Promise<BootVolume> {
|
||||
if (mockOn) {
|
||||
const found = Object.values(mockBootVolumes)
|
||||
.flat()
|
||||
.find((b) => b.id === bootVolumeId)
|
||||
return found ? mocked(found) : Promise.reject(new Error('引导卷不存在'))
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/boot-volumes/${encodeURIComponent(bootVolumeId)}`, {
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
export function updateBootVolume(
|
||||
cfgId: number,
|
||||
bootVolumeId: string,
|
||||
body: UpdateBootVolumeRequest,
|
||||
region?: string,
|
||||
): Promise<BootVolume> {
|
||||
if (mockOn) {
|
||||
const base = Object.values(mockBootVolumes).flat()[0]
|
||||
return mocked({ ...base, ...body }, 700)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/boot-volumes/${encodeURIComponent(bootVolumeId)}`, {
|
||||
method: 'PUT',
|
||||
body: { region: region ?? '', ...body },
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteBootVolume(
|
||||
cfgId: number,
|
||||
bootVolumeId: string,
|
||||
region?: string,
|
||||
): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/boot-volumes/${encodeURIComponent(bootVolumeId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
import { mockCompartments, mockConfigs, mockOverview, mockRegionSubs, mockRegions } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type {
|
||||
Compartment,
|
||||
ImportConfigRequest,
|
||||
OciConfig,
|
||||
OciConfigSummary,
|
||||
Overview,
|
||||
RegionInfo,
|
||||
RegionSubscription,
|
||||
UpdateConfigRequest,
|
||||
VerifyResult,
|
||||
} from '@/types/api'
|
||||
|
||||
export function getOverview(): Promise<Overview> {
|
||||
if (mockOn) return mocked(mockOverview)
|
||||
return request('/overview')
|
||||
}
|
||||
|
||||
export function listCompartments(id: number): Promise<Compartment[]> {
|
||||
if (mockOn) return mocked(mockCompartments[id] ?? [])
|
||||
return request(`/oci-configs/${id}/compartments`)
|
||||
}
|
||||
|
||||
/** 列表返回瘦身摘要,订阅 / 促销等详情字段走 getConfig 单查 */
|
||||
export function listConfigs(): Promise<OciConfigSummary[]> {
|
||||
if (mockOn) return mocked(mockConfigs)
|
||||
return request('/oci-configs')
|
||||
}
|
||||
|
||||
export function getConfig(id: number): Promise<OciConfig> {
|
||||
if (mockOn) {
|
||||
const found = mockConfigs.find((c) => c.id === id)
|
||||
return found ? mocked(found) : Promise.reject(new Error('配置不存在'))
|
||||
}
|
||||
return request(`/oci-configs/${id}`)
|
||||
}
|
||||
|
||||
export function importConfig(body: ImportConfigRequest): Promise<OciConfig> {
|
||||
if (mockOn) {
|
||||
const id = Math.max(...mockConfigs.map((c) => c.id)) + 1
|
||||
const created = { ...mockConfigs[0], id, alias: body.alias, accountType: 'unknown' as const }
|
||||
mockConfigs.push(created)
|
||||
return mocked(created, 1200)
|
||||
}
|
||||
return request('/oci-configs', { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function updateConfig(id: number, body: UpdateConfigRequest): Promise<VerifyResult> {
|
||||
if (mockOn) {
|
||||
const found = mockConfigs.find((c) => c.id === id)
|
||||
if (!found) return Promise.reject(new Error('配置不存在'))
|
||||
if (body.alias) found.alias = body.alias
|
||||
if (body.group !== undefined) found.group = body.group
|
||||
if (body.region) found.region = body.region
|
||||
if (body.multiRegion !== undefined) found.multiRegion = body.multiRegion
|
||||
if (body.multiCompartment !== undefined) found.multiCompartment = body.multiCompartment
|
||||
return mocked({ config: found, changes: {} }, 900)
|
||||
}
|
||||
return request(`/oci-configs/${id}`, { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function verifyConfig(id: number): Promise<VerifyResult> {
|
||||
if (mockOn) return mocked({ config: mockConfigs.find((c) => c.id === id) ?? mockConfigs[0], changes: {} }, 800)
|
||||
return request(`/oci-configs/${id}/verify`, { method: 'POST' })
|
||||
}
|
||||
|
||||
export function deleteConfig(id: number): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function listRegions(): Promise<RegionInfo[]> {
|
||||
if (mockOn) return mocked(mockRegions)
|
||||
return request('/regions')
|
||||
}
|
||||
|
||||
export function listRegionSubscriptions(id: number): Promise<RegionSubscription[]> {
|
||||
if (mockOn) return mocked(mockRegionSubs)
|
||||
return request(`/oci-configs/${id}/region-subscriptions`)
|
||||
}
|
||||
|
||||
export function subscribeRegion(id: number, regionKey: string): Promise<RegionSubscription[]> {
|
||||
if (mockOn)
|
||||
return mocked([
|
||||
...mockRegionSubs,
|
||||
{ key: regionKey, name: '', status: 'IN_PROGRESS', isHomeRegion: false, alias: '' },
|
||||
])
|
||||
return request(`/oci-configs/${id}/region-subscriptions`, { method: 'POST', body: { regionKey } })
|
||||
}
|
||||
|
||||
/** 筛选器用区域列表(缓存):未开多区域支持只含默认区域;有非 READY 时服务端实时刷新 */
|
||||
export function listCachedRegions(id: number): Promise<RegionSubscription[]> {
|
||||
if (mockOn) {
|
||||
const cfg = mockConfigs.find((c) => c.id === id)
|
||||
if (!cfg) return mocked([])
|
||||
if (!cfg.multiRegion) {
|
||||
const info = mockRegions.find((r) => r.name === cfg.region)
|
||||
return mocked([
|
||||
{
|
||||
key: info?.key ?? cfg.homeRegionKey,
|
||||
name: cfg.region,
|
||||
status: 'READY',
|
||||
isHomeRegion: true,
|
||||
alias: info?.alias ?? '',
|
||||
},
|
||||
])
|
||||
}
|
||||
return mocked(mockRegionSubs)
|
||||
}
|
||||
return request(`/oci-configs/${id}/cached-regions`)
|
||||
}
|
||||
|
||||
/** 筛选器用区间列表(缓存):未开多区间支持返回空数组(前端锁定根) */
|
||||
export function listCachedCompartments(id: number): Promise<Compartment[]> {
|
||||
if (mockOn) {
|
||||
const cfg = mockConfigs.find((c) => c.id === id)
|
||||
return mocked(cfg?.multiCompartment ? (mockCompartments[id] ?? []) : [])
|
||||
}
|
||||
return request(`/oci-configs/${id}/cached-compartments`)
|
||||
}
|
||||
@@ -0,0 +1,380 @@
|
||||
import {
|
||||
mockAds,
|
||||
mockBvAttachments,
|
||||
mockConsoleConns,
|
||||
mockImages,
|
||||
mockInstances,
|
||||
mockShapes,
|
||||
mockVolAttachments,
|
||||
mockVolumes,
|
||||
} from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type {
|
||||
AttachVnicRequest,
|
||||
BootVolumeAttachment,
|
||||
ComputeShape,
|
||||
ConsoleConnection,
|
||||
CreateInstanceRequest,
|
||||
CreateInstancesResponse,
|
||||
ImageInfo,
|
||||
Instance,
|
||||
PowerAction,
|
||||
UpdateInstanceRequest,
|
||||
Vnic,
|
||||
Volume,
|
||||
VolumeAttachment,
|
||||
} from '@/types/api'
|
||||
|
||||
// ---- 创建实例前置查询 ----
|
||||
export interface ImagesQuery {
|
||||
region?: string
|
||||
operatingSystem?: string
|
||||
shape?: string
|
||||
}
|
||||
|
||||
export function listImages(cfgId: number, query: ImagesQuery = {}): Promise<ImageInfo[]> {
|
||||
if (mockOn) return mocked(mockImages)
|
||||
return request(`/oci-configs/${cfgId}/images`, { query: { ...query } })
|
||||
}
|
||||
|
||||
export function getImage(cfgId: number, imageId: string, region?: string): Promise<ImageInfo> {
|
||||
if (mockOn) {
|
||||
const found = mockImages.find((i) => i.id === imageId) ?? {
|
||||
...mockImages[0],
|
||||
id: imageId,
|
||||
displayName: 'Canonical-Ubuntu-24.04-aarch64-2026.05.20-0',
|
||||
}
|
||||
return mocked(found)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/images/${encodeURIComponent(imageId)}`, {
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
export function listAvailabilityDomains(cfgId: number, region?: string): Promise<string[]> {
|
||||
if (mockOn) return mocked(mockAds)
|
||||
return request(`/oci-configs/${cfgId}/availability-domains`, { query: { region } })
|
||||
}
|
||||
|
||||
export function listShapes(
|
||||
cfgId: number,
|
||||
region?: string,
|
||||
availabilityDomain?: string,
|
||||
): Promise<ComputeShape[]> {
|
||||
if (mockOn) return mocked(mockShapes)
|
||||
return request(`/oci-configs/${cfgId}/shapes`, { query: { region, availabilityDomain } })
|
||||
}
|
||||
|
||||
export function listVolumes(
|
||||
cfgId: number,
|
||||
availabilityDomain?: string,
|
||||
compartmentId?: string,
|
||||
region?: string,
|
||||
): Promise<Volume[]> {
|
||||
if (mockOn) return mocked(mockVolumes[cfgId] ?? [])
|
||||
return request(`/oci-configs/${cfgId}/volumes`, {
|
||||
query: { availabilityDomain, compartmentId, region },
|
||||
})
|
||||
}
|
||||
|
||||
export function listInstances(
|
||||
cfgId: number,
|
||||
region?: string,
|
||||
compartmentId?: string,
|
||||
): Promise<Instance[]> {
|
||||
if (mockOn) return mocked(compartmentId ? [] : (mockInstances[cfgId] ?? []))
|
||||
return request(`/oci-configs/${cfgId}/instances`, { query: { region, compartmentId } })
|
||||
}
|
||||
|
||||
export function getInstance(cfgId: number, instanceId: string, region?: string): Promise<Instance> {
|
||||
if (mockOn) {
|
||||
const all = Object.values(mockInstances).flat()
|
||||
const found = all.find((i) => i.id === instanceId)
|
||||
return found ? mocked(found) : Promise.reject(new Error('实例不存在'))
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}`, {
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
export function createInstances(
|
||||
cfgId: number,
|
||||
body: CreateInstanceRequest,
|
||||
): Promise<CreateInstancesResponse> {
|
||||
if (mockOn) return mocked({ instances: [mockInstances[1][0]], errors: [] }, 1200)
|
||||
return request(`/oci-configs/${cfgId}/instances`, { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function updateInstance(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
body: UpdateInstanceRequest,
|
||||
region?: string,
|
||||
): Promise<Instance> {
|
||||
if (mockOn) return mocked({ ...mockInstances[1][0], ...body })
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}`, {
|
||||
method: 'PUT',
|
||||
body: { region: region ?? '', ...body },
|
||||
})
|
||||
}
|
||||
|
||||
export function terminateInstance(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
preserveBootVolume: boolean,
|
||||
region?: string,
|
||||
): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 600)
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { preserveBootVolume, region },
|
||||
})
|
||||
}
|
||||
|
||||
export function instanceAction(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
action: PowerAction,
|
||||
region?: string,
|
||||
): Promise<Instance> {
|
||||
if (mockOn) {
|
||||
const next = action === 'STOP' || action === 'SOFTSTOP' ? 'STOPPING' : 'STARTING'
|
||||
return mocked({ ...mockInstances[1][0], lifecycleState: next }, 600)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/action`, {
|
||||
method: 'POST',
|
||||
body: { region: region ?? '', action },
|
||||
})
|
||||
}
|
||||
|
||||
export function changePublicIp(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
region?: string,
|
||||
): Promise<{ publicIp: string }> {
|
||||
if (mockOn) return mocked({ publicIp: '130.61.99.201' }, 900)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/change-public-ip`,
|
||||
{ method: 'POST', body: { region: region ?? '' } },
|
||||
)
|
||||
}
|
||||
|
||||
export function addIpv6(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
address = '',
|
||||
region?: string,
|
||||
): Promise<{ ipv6Address: string }> {
|
||||
if (mockOn) return mocked({ ipv6Address: address || '2603:c020:400d:aa01::2' }, 700)
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/ipv6-addresses`, {
|
||||
method: 'POST',
|
||||
body: { region: region ?? '', address },
|
||||
})
|
||||
}
|
||||
|
||||
export function removeIpv6(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
address: string,
|
||||
region?: string,
|
||||
): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/ipv6-addresses`, {
|
||||
method: 'DELETE',
|
||||
query: { address, region },
|
||||
})
|
||||
}
|
||||
|
||||
// ---- VNIC 管理 ----
|
||||
export function listVnics(cfgId: number, instanceId: string, region?: string): Promise<Vnic[]> {
|
||||
if (mockOn) return mocked([])
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/vnics`, {
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
export function attachVnic(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
body: AttachVnicRequest,
|
||||
): Promise<Vnic> {
|
||||
if (mockOn) return Promise.reject(new Error('mock 环境不支持附加 VNIC'))
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/vnics`, {
|
||||
method: 'POST',
|
||||
body: { ...body, region: body.region ?? '' },
|
||||
})
|
||||
}
|
||||
|
||||
export function detachVnic(cfgId: number, attachmentId: string, region?: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/vnic-attachments/${encodeURIComponent(attachmentId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
/** 为指定 VNIC 添加 IPv6;address 留空自动分配。删除复用实例级 removeIpv6(按地址遍历全部网卡) */
|
||||
export function addVnicIpv6(
|
||||
cfgId: number,
|
||||
vnicId: string,
|
||||
address = '',
|
||||
region?: string,
|
||||
): Promise<{ address: string }> {
|
||||
if (mockOn) return mocked({ address: address || '2603:c020:400d:aa01::3' }, 700)
|
||||
return request(`/oci-configs/${cfgId}/vnics/${encodeURIComponent(vnicId)}/ipv6-addresses`, {
|
||||
method: 'POST',
|
||||
body: { region: region ?? '', address },
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 网页控制台(串行 / VNC)----
|
||||
export type ConsoleSessionType = 'serial' | 'vnc'
|
||||
|
||||
export function createConsoleSession(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
type: ConsoleSessionType,
|
||||
region?: string,
|
||||
): Promise<{ sessionId: string; type: ConsoleSessionType }> {
|
||||
if (mockOn) return Promise.reject(new Error('mock 环境不支持网页控制台'))
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/console-sessions`,
|
||||
{ method: 'POST', body: { type, region: region ?? '' } },
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteConsoleSession(sessionId: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/console-sessions/${encodeURIComponent(sessionId)}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** 控制台数据面 WS 地址:浏览器 WS 无法带自定义头,token 走 query */
|
||||
export function consoleSessionWsUrl(sessionId: string, token: string, extra?: string): string {
|
||||
const proto = location.protocol === 'https:' ? 'wss' : 'ws'
|
||||
const q = extra ? `&${extra}` : ''
|
||||
return `${proto}://${location.host}/api/v1/console-sessions/${encodeURIComponent(sessionId)}/ws?token=${encodeURIComponent(token)}${q}`
|
||||
}
|
||||
|
||||
// ---- 控制台连接 ----
|
||||
export function listConsoleConnections(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
region?: string,
|
||||
): Promise<ConsoleConnection[]> {
|
||||
if (mockOn) return mocked(mockConsoleConns)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/console-connections`,
|
||||
{ query: { region } },
|
||||
)
|
||||
}
|
||||
|
||||
export function createConsoleConnection(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
sshPublicKey: string,
|
||||
region?: string,
|
||||
): Promise<ConsoleConnection> {
|
||||
if (mockOn) return mocked(mockConsoleConns[0], 1500)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/console-connections`,
|
||||
{ method: 'POST', body: { region: region ?? '', sshPublicKey } },
|
||||
)
|
||||
}
|
||||
|
||||
export function deleteConsoleConnection(
|
||||
cfgId: number,
|
||||
connectionId: string,
|
||||
region?: string,
|
||||
): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/console-connections/${encodeURIComponent(connectionId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 引导卷挂载 ----
|
||||
export function listBootVolumeAttachments(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
region?: string,
|
||||
): Promise<BootVolumeAttachment[]> {
|
||||
if (mockOn) return mocked(mockBvAttachments)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/boot-volume-attachments`,
|
||||
{ query: { region } },
|
||||
)
|
||||
}
|
||||
|
||||
export function attachBootVolume(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
bootVolumeId: string,
|
||||
region?: string,
|
||||
): Promise<BootVolumeAttachment> {
|
||||
if (mockOn) return mocked(mockBvAttachments[0], 1200)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/boot-volume-attachments`,
|
||||
{ method: 'POST', body: { region: region ?? '', bootVolumeId } },
|
||||
)
|
||||
}
|
||||
|
||||
export function replaceBootVolume(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
bootVolumeId: string,
|
||||
region?: string,
|
||||
): Promise<BootVolumeAttachment> {
|
||||
if (mockOn) return mocked(mockBvAttachments[0], 1500)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/replace-boot-volume`,
|
||||
{ method: 'POST', body: { region: region ?? '', bootVolumeId } },
|
||||
)
|
||||
}
|
||||
|
||||
export function detachBootVolume(
|
||||
cfgId: number,
|
||||
attachmentId: string,
|
||||
region?: string,
|
||||
): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/boot-volume-attachments/${encodeURIComponent(attachmentId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 块卷挂载 ----
|
||||
export function listVolumeAttachments(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
region?: string,
|
||||
): Promise<VolumeAttachment[]> {
|
||||
if (mockOn) return mocked(mockVolAttachments)
|
||||
return request(
|
||||
`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/volume-attachments`,
|
||||
{ query: { region } },
|
||||
)
|
||||
}
|
||||
|
||||
export function attachVolume(
|
||||
cfgId: number,
|
||||
instanceId: string,
|
||||
volumeId: string,
|
||||
readOnly = false,
|
||||
region?: string,
|
||||
): Promise<VolumeAttachment> {
|
||||
if (mockOn) return mocked(mockVolAttachments[0], 1000)
|
||||
return request(`/oci-configs/${cfgId}/instances/${encodeURIComponent(instanceId)}/volume-attachments`, {
|
||||
method: 'POST',
|
||||
body: { region: region ?? '', volumeId, readOnly },
|
||||
})
|
||||
}
|
||||
|
||||
export function detachVolume(cfgId: number, attachmentId: string, region?: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/volume-attachments/${encodeURIComponent(attachmentId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { mockLogEvents, mockLogRelays, mockLogWebhooks } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type { LogEventPage, LogRelayView, LogWebhookInfo, LogWebhookState } from '@/types/api'
|
||||
|
||||
export interface LogEventParams {
|
||||
page: number
|
||||
pageSize: number
|
||||
/** 按租户过滤,缺省为全部 */
|
||||
cfgId?: number
|
||||
}
|
||||
|
||||
export function listLogEvents(params: LogEventParams): Promise<LogEventPage> {
|
||||
if (mockOn) {
|
||||
const filtered = params.cfgId
|
||||
? mockLogEvents.filter((e) => e.ociConfigId === params.cfgId)
|
||||
: mockLogEvents
|
||||
const start = (params.page - 1) * params.pageSize
|
||||
return mocked({
|
||||
items: filtered.slice(start, start + params.pageSize),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
return request('/log-events', { query: { ...params } })
|
||||
}
|
||||
|
||||
export function getLogWebhook(cfgId: number): Promise<LogWebhookState> {
|
||||
if (mockOn) {
|
||||
const info = mockLogWebhooks.get(cfgId)
|
||||
return mocked(info ? { exists: true, webhook: { ...info } } : { exists: false })
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/log-webhook`)
|
||||
}
|
||||
|
||||
/** 生成(或幂等返回)回传回调地址 */
|
||||
export function ensureLogWebhook(cfgId: number): Promise<LogWebhookInfo> {
|
||||
if (mockOn) {
|
||||
let info = mockLogWebhooks.get(cfgId)
|
||||
if (!info) {
|
||||
const secret = `mock${cfgId}`.padEnd(64, '0')
|
||||
info = {
|
||||
path: `/api/v1/webhooks/oci-logs/${secret}`,
|
||||
secret,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
mockLogWebhooks.set(cfgId, info)
|
||||
}
|
||||
return mocked({ ...info }, 400)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/log-webhook`, { method: 'POST' })
|
||||
}
|
||||
|
||||
/** 撤销回调地址,旧 URL 随即 404 */
|
||||
export function revokeLogWebhook(cfgId: number): Promise<void> {
|
||||
if (mockOn) {
|
||||
mockLogWebhooks.delete(cfgId)
|
||||
return mocked(undefined, 300)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/log-webhook`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** 查询 OCI 侧链路状态与关键事件清单 */
|
||||
export function getLogRelay(cfgId: number): Promise<LogRelayView> {
|
||||
if (mockOn) return mocked(structuredCloneRelay(cfgId))
|
||||
return request(`/oci-configs/${cfgId}/log-relay`)
|
||||
}
|
||||
|
||||
/** 一键建立回传链路(P1):同步执行,含订阅确认与 Connector 就绪等待,可达 2 分钟 */
|
||||
export function setupLogRelay(cfgId: number): Promise<LogRelayView> {
|
||||
if (mockOn) {
|
||||
mockLogRelays.set(cfgId, true)
|
||||
return mocked(structuredCloneRelay(cfgId), 1500)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/log-relay`, { method: 'POST' })
|
||||
}
|
||||
|
||||
/** 销毁 OCI 侧链路并撤销回调地址 */
|
||||
export function teardownLogRelay(cfgId: number): Promise<void> {
|
||||
if (mockOn) {
|
||||
mockLogRelays.delete(cfgId)
|
||||
mockLogWebhooks.delete(cfgId)
|
||||
return mocked(undefined, 800)
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/log-relay`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** mock 模式下按已建状态拼一份链路视图 */
|
||||
function structuredCloneRelay(cfgId: number): LogRelayView {
|
||||
const built = mockLogRelays.get(cfgId) === true
|
||||
const res = (suffix: string) => (built ? { id: `ocid1.mock.${suffix}`, state: 'ACTIVE' } : { id: '', state: '' })
|
||||
return {
|
||||
endpoint: built ? `https://demo.example.com/api/v1/webhooks/oci-logs/mock${cfgId}` : undefined,
|
||||
topic: res('topic'),
|
||||
subscription: res('sub'),
|
||||
connector: res('conn'),
|
||||
policy: res('policy'),
|
||||
ready: built,
|
||||
events: [
|
||||
'LaunchInstance', 'TerminateInstance', 'InstanceAction',
|
||||
'CreateUser', 'DeleteUser', 'UpdateUser',
|
||||
'CreateApiKey', 'DeleteApiKey', 'UpdateUserCapabilities',
|
||||
'CreateRegionSubscription', 'CreatePolicy', 'UpdatePolicy', 'DeletePolicy',
|
||||
],
|
||||
}
|
||||
}
|
||||
+1716
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,66 @@
|
||||
import { mockIpv6Report, mockSecurityLists, mockSubnets, mockVcns } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type { CreateVcnRequest, Ipv6Report, SecurityList, Subnet, Vcn } from '@/types/api'
|
||||
|
||||
export function listVcns(cfgId: number, region?: string, compartmentId?: string): Promise<Vcn[]> {
|
||||
if (mockOn) return mocked(compartmentId ? [] : (mockVcns[cfgId] ?? []))
|
||||
return request(`/oci-configs/${cfgId}/vcns`, { query: { region, compartmentId } })
|
||||
}
|
||||
|
||||
export function getVcn(cfgId: number, vcnId: string, region?: string): Promise<Vcn> {
|
||||
if (mockOn) {
|
||||
const found = Object.values(mockVcns)
|
||||
.flat()
|
||||
.find((v) => v.id === vcnId)
|
||||
return found ? mocked(found) : Promise.reject(new Error('VCN 不存在'))
|
||||
}
|
||||
return request(`/oci-configs/${cfgId}/vcns/${encodeURIComponent(vcnId)}`, { query: { region } })
|
||||
}
|
||||
|
||||
export function createVcn(cfgId: number, body: CreateVcnRequest): Promise<Vcn> {
|
||||
if (mockOn) return mocked({ ...mockVcns[1][0], id: 'ocid1.vcn..new', displayName: body.displayName }, 900)
|
||||
return request(`/oci-configs/${cfgId}/vcns`, { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function renameVcn(
|
||||
cfgId: number,
|
||||
vcnId: string,
|
||||
displayName: string,
|
||||
region?: string,
|
||||
): Promise<Vcn> {
|
||||
if (mockOn) return mocked({ ...mockVcns[1][0], displayName })
|
||||
return request(`/oci-configs/${cfgId}/vcns/${encodeURIComponent(vcnId)}`, {
|
||||
method: 'PUT',
|
||||
body: { region: region ?? '', displayName },
|
||||
})
|
||||
}
|
||||
|
||||
export function deleteVcn(cfgId: number, vcnId: string, region?: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/oci-configs/${cfgId}/vcns/${encodeURIComponent(vcnId)}`, {
|
||||
method: 'DELETE',
|
||||
query: { region },
|
||||
})
|
||||
}
|
||||
|
||||
export function enableVcnIpv6(cfgId: number, vcnId: string, region?: string): Promise<Ipv6Report> {
|
||||
if (mockOn) return mocked(mockIpv6Report, 1500)
|
||||
return request(`/oci-configs/${cfgId}/vcns/${encodeURIComponent(vcnId)}/enable-ipv6`, {
|
||||
method: 'POST',
|
||||
body: { region: region ?? '' },
|
||||
})
|
||||
}
|
||||
|
||||
export function listSubnets(cfgId: number, vcnId: string, region?: string): Promise<Subnet[]> {
|
||||
if (mockOn) return mocked(mockSubnets.filter((s) => s.vcnId === vcnId || vcnId === ''))
|
||||
return request(`/oci-configs/${cfgId}/subnets`, { query: { vcnId, region } })
|
||||
}
|
||||
|
||||
export function listSecurityLists(
|
||||
cfgId: number,
|
||||
vcnId: string,
|
||||
region?: string,
|
||||
): Promise<SecurityList[]> {
|
||||
if (mockOn) return mocked(mockSecurityLists.filter((s) => s.vcnId === vcnId || vcnId === ''))
|
||||
return request(`/oci-configs/${cfgId}/security-lists`, { query: { vcnId, region } })
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
import { mockProxies } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type { ProxyImportResult, ProxyInfo, ProxyInput } from '@/types/api'
|
||||
|
||||
export function listProxies(): Promise<{ items: ProxyInfo[] }> {
|
||||
if (mockOn) return mocked({ items: mockProxies })
|
||||
return request('/proxies')
|
||||
}
|
||||
|
||||
/** mock 端复刻后端自动命名:留空生成 {type}-{host}:{port},冲突加序号 */
|
||||
function mockAutoName(body: ProxyInput): string {
|
||||
const base = `${body.type}-${body.host}:${body.port}`
|
||||
let name = base
|
||||
for (let i = 2; mockProxies.some((p) => p.name === name); i++) name = `${base}-${i}`
|
||||
return name
|
||||
}
|
||||
|
||||
function mockCreate(body: ProxyInput): ProxyInfo {
|
||||
const created: ProxyInfo = {
|
||||
id: Math.max(0, ...mockProxies.map((p) => p.id)) + 1,
|
||||
name: body.name?.trim() || mockAutoName(body),
|
||||
type: body.type,
|
||||
host: body.host,
|
||||
port: body.port,
|
||||
username: body.username ?? '',
|
||||
passwordSet: !!body.password,
|
||||
country: '',
|
||||
city: '',
|
||||
geoAt: '',
|
||||
usedBy: 0,
|
||||
createdAt: new Date().toISOString(),
|
||||
}
|
||||
mockProxies.push(created)
|
||||
return created
|
||||
}
|
||||
|
||||
export function createProxy(body: ProxyInput): Promise<ProxyInfo> {
|
||||
if (mockOn) return mocked(mockCreate(body))
|
||||
return request('/proxies', { method: 'POST', body })
|
||||
}
|
||||
|
||||
export function updateProxy(id: number, body: ProxyInput): Promise<ProxyInfo> {
|
||||
if (mockOn) {
|
||||
const found = mockProxies.find((p) => p.id === id)
|
||||
if (!found) return Promise.reject(new Error('代理不存在'))
|
||||
Object.assign(found, body, {
|
||||
name: body.name?.trim() || found.name,
|
||||
passwordSet: body.password ? true : found.passwordSet,
|
||||
})
|
||||
return mocked({ ...found })
|
||||
}
|
||||
return request(`/proxies/${id}`, { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function deleteProxy(id: number): Promise<void> {
|
||||
if (mockOn) {
|
||||
const i = mockProxies.findIndex((p) => p.id === id)
|
||||
if (i >= 0) mockProxies.splice(i, 1)
|
||||
return mocked(undefined)
|
||||
}
|
||||
return request(`/proxies/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
/** 批量导入:每行 scheme://[user:pass@]host:port 或 host:port[:user:pass] */
|
||||
export function importProxies(text: string): Promise<ProxyImportResult> {
|
||||
if (mockOn) {
|
||||
const created = text
|
||||
.split('\n')
|
||||
.map((l) => l.trim())
|
||||
.filter((l) => l && !l.startsWith('#'))
|
||||
.map((l) => {
|
||||
const m = /^(?:(socks5|http|https):\/\/)?(?:[^@]+@)?([^:@/]+):(\d+)/.exec(l)
|
||||
return m ? mockCreate({ type: m[1] ?? 'socks5', host: m[2], port: Number(m[3]) }) : null
|
||||
})
|
||||
.filter((p): p is ProxyInfo => !!p)
|
||||
return mocked({ created, failed: [] })
|
||||
}
|
||||
return request('/proxies/import', { method: 'POST', body: { text } })
|
||||
}
|
||||
|
||||
/** 手动重测出口地区,同步返回最新视图 */
|
||||
export function probeProxy(id: number): Promise<ProxyInfo> {
|
||||
if (mockOn) {
|
||||
const found = mockProxies.find((p) => p.id === id)
|
||||
if (!found) return Promise.reject(new Error('代理不存在'))
|
||||
Object.assign(found, { country: '日本', city: '大阪市', geoAt: new Date().toISOString() })
|
||||
return mocked({ ...found })
|
||||
}
|
||||
return request(`/proxies/${id}/probe`, { method: 'POST' })
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
import { useAuthStore } from '@/stores/auth'
|
||||
|
||||
const BASE = '/api/v1'
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
|
||||
constructor(status: number, message: string) {
|
||||
super(message)
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
method?: string
|
||||
body?: unknown
|
||||
query?: Record<string, string | number | boolean | undefined>
|
||||
}
|
||||
|
||||
function buildUrl(path: string, query?: RequestOptions['query']): string {
|
||||
if (!query) return BASE + path
|
||||
const qs = new URLSearchParams()
|
||||
for (const [k, v] of Object.entries(query)) {
|
||||
if (v !== undefined && v !== '') qs.set(k, String(v))
|
||||
}
|
||||
const s = qs.toString()
|
||||
return s ? `${BASE}${path}?${s}` : BASE + path
|
||||
}
|
||||
|
||||
async function parseError(resp: Response): Promise<never> {
|
||||
let message = `请求失败(${resp.status})`
|
||||
try {
|
||||
const body = (await resp.json()) as { error?: string; hint?: string }
|
||||
if (body.error) message = body.hint ? `${body.hint}|${body.error}` : body.error
|
||||
} catch {
|
||||
/* 非 JSON 响应体,保留默认消息 */
|
||||
}
|
||||
if (resp.status === 401) useAuthStore().logout()
|
||||
// 全局限速(429)整页拦截;登录接口的 429 是账号锁定,留在表单内提示
|
||||
if (resp.status === 429 && !resp.url.includes('/auth/login') && location.pathname !== '/blocked') {
|
||||
location.assign('/blocked')
|
||||
}
|
||||
throw new ApiError(resp.status, message)
|
||||
}
|
||||
|
||||
/** 统一请求封装:注入 JWT、401 登出、错误转 ApiError */
|
||||
export async function request<T>(path: string, opts: RequestOptions = {}): Promise<T> {
|
||||
const auth = useAuthStore()
|
||||
const headers: Record<string, string> = { 'Content-Type': 'application/json' }
|
||||
if (auth.token) headers.Authorization = `Bearer ${auth.token}`
|
||||
const resp = await fetch(buildUrl(path, opts.query), {
|
||||
method: opts.method ?? 'GET',
|
||||
headers,
|
||||
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
||||
})
|
||||
if (!resp.ok) await parseError(resp)
|
||||
if (resp.status === 204) return undefined as T
|
||||
return (await resp.json()) as T
|
||||
}
|
||||
|
||||
export const mockOn = import.meta.env.VITE_MOCK === '1'
|
||||
|
||||
/** mock 数据延时返回,模拟网络延迟 */
|
||||
export function mocked<T>(data: T, ms = 200): Promise<T> {
|
||||
return new Promise((resolve) => setTimeout(() => resolve(structuredClone(data)), ms))
|
||||
}
|
||||
@@ -0,0 +1,152 @@
|
||||
import {
|
||||
mockNotifyEvents,
|
||||
mockSecuritySetting,
|
||||
mockSystemLogs,
|
||||
mockTaskSetting,
|
||||
mockTelegramSetting,
|
||||
} from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type {
|
||||
AboutInfo,
|
||||
NotifyEventsSetting,
|
||||
NotifyTemplateItem,
|
||||
SecuritySetting,
|
||||
SystemLogPage,
|
||||
TaskSetting,
|
||||
TelegramSetting,
|
||||
} from '@/types/api'
|
||||
|
||||
export function getTelegramSetting(): Promise<TelegramSetting> {
|
||||
if (mockOn) return mocked({ ...mockTelegramSetting })
|
||||
return request('/settings/telegram')
|
||||
}
|
||||
|
||||
export interface UpdateTelegramBody {
|
||||
enabled: boolean
|
||||
/** 缺省表示沿用已保存的 token,空串表示清除 */
|
||||
botToken?: string
|
||||
chatId: string
|
||||
}
|
||||
|
||||
export function updateTelegramSetting(body: UpdateTelegramBody): Promise<TelegramSetting> {
|
||||
if (mockOn) {
|
||||
mockTelegramSetting.enabled = body.enabled
|
||||
mockTelegramSetting.chatId = body.chatId
|
||||
if (body.botToken !== undefined) {
|
||||
mockTelegramSetting.tokenSet = body.botToken !== ''
|
||||
mockTelegramSetting.tokenTail = body.botToken.length >= 8 ? body.botToken.slice(-4) : ''
|
||||
}
|
||||
return mocked({ ...mockTelegramSetting }, 400)
|
||||
}
|
||||
return request('/settings/telegram', { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function testTelegram(): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 800)
|
||||
return request('/settings/telegram/test', { method: 'POST' })
|
||||
}
|
||||
|
||||
export function getNotifyEvents(): Promise<NotifyEventsSetting> {
|
||||
if (mockOn) return mocked({ ...mockNotifyEvents })
|
||||
return request('/settings/notify-events')
|
||||
}
|
||||
|
||||
/** 全量保存五个事件开关,返回最新值 */
|
||||
export function updateNotifyEvents(body: NotifyEventsSetting): Promise<NotifyEventsSetting> {
|
||||
if (mockOn) {
|
||||
Object.assign(mockNotifyEvents, body)
|
||||
return mocked({ ...mockNotifyEvents }, 400)
|
||||
}
|
||||
return request('/settings/notify-events', { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function listNotifyTemplates(): Promise<NotifyTemplateItem[]> {
|
||||
if (mockOn)
|
||||
return mocked([
|
||||
{
|
||||
kind: 'task_fail',
|
||||
label: '任务失败',
|
||||
vars: ['task_name', 'error'],
|
||||
defaultTemplate: '❌ 任务失败:{{task_name}}\n{{error}}',
|
||||
template: '',
|
||||
},
|
||||
])
|
||||
return request<{ items: NotifyTemplateItem[] }>('/settings/notify-templates').then((r) => r.items)
|
||||
}
|
||||
|
||||
/** 保存单个通知模板;空串清除自定义(恢复默认) */
|
||||
export function updateNotifyTemplate(kind: string, template: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 300)
|
||||
return request(`/settings/notify-templates/${kind}`, { method: 'PUT', body: { template } })
|
||||
}
|
||||
|
||||
/** 用示例变量渲染并发送测试消息;template 非空时按编辑中内容渲染(不落库) */
|
||||
export function testNotifyTemplate(kind: string, template: string): Promise<void> {
|
||||
if (mockOn) return mocked(undefined, 600)
|
||||
return request(`/settings/notify-templates/${kind}/test`, { method: 'POST', body: { template } })
|
||||
}
|
||||
|
||||
export function getTaskSetting(): Promise<TaskSetting> {
|
||||
if (mockOn) return mocked({ ...mockTaskSetting })
|
||||
return request('/settings/task')
|
||||
}
|
||||
|
||||
/** 保存任务行为设置,返回最新值;阈值越界(1-10 之外)后端返回 400 */
|
||||
export function updateTaskSetting(body: TaskSetting): Promise<TaskSetting> {
|
||||
if (mockOn) {
|
||||
Object.assign(mockTaskSetting, body)
|
||||
return mocked({ ...mockTaskSetting }, 400)
|
||||
}
|
||||
return request('/settings/task', { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function getSecuritySetting(): Promise<SecuritySetting> {
|
||||
if (mockOn) return mocked({ ...mockSecuritySetting })
|
||||
return request('/settings/security')
|
||||
}
|
||||
|
||||
/** 保存安全设置,返回最新值;越界或非法后端返回 400,保存后立即生效 */
|
||||
export function updateSecuritySetting(body: SecuritySetting): Promise<SecuritySetting> {
|
||||
if (mockOn) {
|
||||
Object.assign(mockSecuritySetting, body)
|
||||
return mocked({ ...mockSecuritySetting }, 400)
|
||||
}
|
||||
return request('/settings/security', { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export interface SystemLogParams {
|
||||
page: number
|
||||
pageSize: number
|
||||
/** 关键字,对路径与用户名模糊匹配 */
|
||||
keyword?: string
|
||||
}
|
||||
|
||||
export function listSystemLogs(params: SystemLogParams): Promise<SystemLogPage> {
|
||||
if (mockOn) {
|
||||
const kw = (params.keyword ?? '').toLowerCase()
|
||||
const filtered = kw
|
||||
? mockSystemLogs.filter(
|
||||
(l) => l.path.toLowerCase().includes(kw) || l.username.toLowerCase().includes(kw),
|
||||
)
|
||||
: mockSystemLogs
|
||||
const start = (params.page - 1) * params.pageSize
|
||||
return mocked({
|
||||
items: filtered.slice(start, start + params.pageSize),
|
||||
total: filtered.length,
|
||||
})
|
||||
}
|
||||
return request('/system-logs', { query: { ...params } })
|
||||
}
|
||||
|
||||
// ---- 关于 ----
|
||||
/** 构建与运行环境信息(设置 · 关于) */
|
||||
export function getAbout(): Promise<AboutInfo> {
|
||||
if (mockOn)
|
||||
return mocked({
|
||||
version: 'v1.0.0',
|
||||
buildTime: '2026-07-09T12:00Z',
|
||||
goVersion: 'go1.26.4',
|
||||
platform: 'linux/amd64',
|
||||
})
|
||||
return request('/about')
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
import { mockTaskLogs, mockTasks } from './mock'
|
||||
import { mockOn, mocked, request } from './request'
|
||||
import type { Task, TaskLog, TaskStatus } from '@/types/api'
|
||||
|
||||
export function listTasks(): Promise<Task[]> {
|
||||
if (mockOn) return mocked(mockTasks)
|
||||
return request('/tasks')
|
||||
}
|
||||
|
||||
export function getTask(id: number): Promise<Task> {
|
||||
if (mockOn) {
|
||||
const found = mockTasks.find((t) => t.id === id)
|
||||
return found ? mocked(found) : Promise.reject(new Error('任务不存在'))
|
||||
}
|
||||
return request(`/tasks/${id}`)
|
||||
}
|
||||
|
||||
export interface CreateTaskRequest {
|
||||
name: string
|
||||
type: string
|
||||
cronExpr: string
|
||||
payload: Record<string, unknown>
|
||||
}
|
||||
|
||||
export function createTask(body: CreateTaskRequest): Promise<Task> {
|
||||
if (mockOn) {
|
||||
const created: Task = {
|
||||
...mockTasks[0],
|
||||
id: Math.max(...mockTasks.map((t) => t.id)) + 1,
|
||||
name: body.name,
|
||||
type: body.type as Task['type'],
|
||||
cronExpr: body.cronExpr,
|
||||
payload: JSON.stringify(body.payload),
|
||||
status: 'active',
|
||||
lastRunAt: null,
|
||||
runCount: 0,
|
||||
}
|
||||
mockTasks.push(created)
|
||||
return mocked(created, 600)
|
||||
}
|
||||
return request('/tasks', { method: 'POST', body })
|
||||
}
|
||||
|
||||
export interface UpdateTaskRequest {
|
||||
name?: string
|
||||
cronExpr?: string
|
||||
payload?: Record<string, unknown>
|
||||
status?: TaskStatus
|
||||
}
|
||||
|
||||
export function updateTask(id: number, body: UpdateTaskRequest): Promise<Task> {
|
||||
if (mockOn) {
|
||||
const base = mockTasks.find((t) => t.id === id) ?? mockTasks[0]
|
||||
return mocked({ ...base, status: body.status ?? base.status })
|
||||
}
|
||||
return request(`/tasks/${id}`, { method: 'PUT', body })
|
||||
}
|
||||
|
||||
export function deleteTask(id: number): Promise<void> {
|
||||
if (mockOn) return mocked(undefined)
|
||||
return request(`/tasks/${id}`, { method: 'DELETE' })
|
||||
}
|
||||
|
||||
export function listTaskLogs(id: number, limit = 50): Promise<TaskLog[]> {
|
||||
if (mockOn) return mocked(mockTaskLogs[id] ?? [])
|
||||
return request(`/tasks/${id}/logs`, { query: { limit } })
|
||||
}
|
||||
|
||||
export function runTask(id: number): Promise<TaskLog> {
|
||||
if (mockOn) {
|
||||
const logs = mockTaskLogs[id]
|
||||
return mocked(logs?.[0] ?? { id: 0, taskId: id, success: true, message: 'ok', durationMs: 100, createdAt: '' }, 1000)
|
||||
}
|
||||
return request(`/tasks/${id}/run`, { method: 'POST' })
|
||||
}
|
||||
@@ -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
|
||||
/** 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): 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' })
|
||||
}
|
||||
Reference in New Issue
Block a user