chore: initial snapshot for gitea/github upload
This commit is contained in:
@@ -0,0 +1,660 @@
|
||||
/**
|
||||
* Admin Accounts API endpoints
|
||||
* Handles AI platform account management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
Account,
|
||||
CreateAccountRequest,
|
||||
UpdateAccountRequest,
|
||||
PaginatedResponse,
|
||||
AccountUsageInfo,
|
||||
WindowStats,
|
||||
ClaudeModel,
|
||||
AccountUsageStatsResponse,
|
||||
TempUnschedulableStatus,
|
||||
AdminDataPayload,
|
||||
AdminDataImportResult,
|
||||
CheckMixedChannelRequest,
|
||||
CheckMixedChannelResponse
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* List all accounts with pagination
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param filters - Optional filters
|
||||
* @returns Paginated list of accounts
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
platform?: string
|
||||
type?: string
|
||||
status?: string
|
||||
group?: string
|
||||
search?: string
|
||||
lite?: string
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<Account>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters
|
||||
},
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export interface AccountListWithEtagResult {
|
||||
notModified: boolean
|
||||
etag: string | null
|
||||
data: PaginatedResponse<Account> | null
|
||||
}
|
||||
|
||||
export async function listWithEtag(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
platform?: string
|
||||
type?: string
|
||||
status?: string
|
||||
search?: string
|
||||
lite?: string
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
etag?: string | null
|
||||
}
|
||||
): Promise<AccountListWithEtagResult> {
|
||||
const headers: Record<string, string> = {}
|
||||
if (options?.etag) {
|
||||
headers['If-None-Match'] = options.etag
|
||||
}
|
||||
|
||||
const response = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters
|
||||
},
|
||||
headers,
|
||||
signal: options?.signal,
|
||||
validateStatus: (status) => (status >= 200 && status < 300) || status === 304
|
||||
})
|
||||
|
||||
const etagHeader = typeof response.headers?.etag === 'string' ? response.headers.etag : null
|
||||
if (response.status === 304) {
|
||||
return {
|
||||
notModified: true,
|
||||
etag: etagHeader,
|
||||
data: null
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
notModified: false,
|
||||
etag: etagHeader,
|
||||
data: response.data
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account by ID
|
||||
* @param id - Account ID
|
||||
* @returns Account details
|
||||
*/
|
||||
export async function getById(id: number): Promise<Account> {
|
||||
const { data } = await apiClient.get<Account>(`/admin/accounts/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new account
|
||||
* @param accountData - Account data
|
||||
* @returns Created account
|
||||
*/
|
||||
export async function create(accountData: CreateAccountRequest): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>('/admin/accounts', accountData)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update account
|
||||
* @param id - Account ID
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateAccountRequest): Promise<Account> {
|
||||
const { data } = await apiClient.put<Account>(`/admin/accounts/${id}`, updates)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Check mixed-channel risk for account-group binding.
|
||||
*/
|
||||
export async function checkMixedChannelRisk(
|
||||
payload: CheckMixedChannelRequest
|
||||
): Promise<CheckMixedChannelResponse> {
|
||||
const { data } = await apiClient.post<CheckMixedChannelResponse>('/admin/accounts/check-mixed-channel', payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete account
|
||||
* @param id - Account ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteAccount(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/accounts/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle account status
|
||||
* @param id - Account ID
|
||||
* @param status - New status
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<Account> {
|
||||
return update(id, { status })
|
||||
}
|
||||
|
||||
/**
|
||||
* Test account connectivity
|
||||
* @param id - Account ID
|
||||
* @returns Test result
|
||||
*/
|
||||
export async function testAccount(id: number): Promise<{
|
||||
success: boolean
|
||||
message: string
|
||||
latency_ms?: number
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
success: boolean
|
||||
message: string
|
||||
latency_ms?: number
|
||||
}>(`/admin/accounts/${id}/test`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh account credentials
|
||||
* @param id - Account ID
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function refreshCredentials(id: number): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/refresh`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account usage statistics
|
||||
* @param id - Account ID
|
||||
* @param days - Number of days (default: 30)
|
||||
* @returns Account usage statistics with history, summary, and models
|
||||
*/
|
||||
export async function getStats(id: number, days: number = 30): Promise<AccountUsageStatsResponse> {
|
||||
const { data } = await apiClient.get<AccountUsageStatsResponse>(`/admin/accounts/${id}/stats`, {
|
||||
params: { days }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear account error
|
||||
* @param id - Account ID
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function clearError(id: number): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/clear-error`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account usage information (5h/7d window)
|
||||
* @param id - Account ID
|
||||
* @returns Account usage info
|
||||
*/
|
||||
export async function getUsage(id: number): Promise<AccountUsageInfo> {
|
||||
const { data } = await apiClient.get<AccountUsageInfo>(`/admin/accounts/${id}/usage`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear account rate limit status
|
||||
* @param id - Account ID
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function clearRateLimit(id: number): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>(
|
||||
`/admin/accounts/${id}/clear-rate-limit`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Recover account runtime state in one call
|
||||
* @param id - Account ID
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function recoverState(id: number): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/recover-state`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset account quota usage
|
||||
* @param id - Account ID
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function resetAccountQuota(id: number): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>(
|
||||
`/admin/accounts/${id}/reset-quota`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get temporary unschedulable status
|
||||
* @param id - Account ID
|
||||
* @returns Status with detail state if active
|
||||
*/
|
||||
export async function getTempUnschedulableStatus(id: number): Promise<TempUnschedulableStatus> {
|
||||
const { data } = await apiClient.get<TempUnschedulableStatus>(
|
||||
`/admin/accounts/${id}/temp-unschedulable`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset temporary unschedulable status
|
||||
* @param id - Account ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function resetTempUnschedulable(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(
|
||||
`/admin/accounts/${id}/temp-unschedulable`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate OAuth authorization URL
|
||||
* @param endpoint - API endpoint path
|
||||
* @param config - Proxy configuration
|
||||
* @returns Auth URL and session ID
|
||||
*/
|
||||
export async function generateAuthUrl(
|
||||
endpoint: string,
|
||||
config: { proxy_id?: number }
|
||||
): Promise<{ auth_url: string; session_id: string }> {
|
||||
const { data } = await apiClient.post<{ auth_url: string; session_id: string }>(endpoint, config)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Exchange authorization code for tokens
|
||||
* @param endpoint - API endpoint path
|
||||
* @param exchangeData - Session ID, code, and optional proxy config
|
||||
* @returns Token information
|
||||
*/
|
||||
export async function exchangeCode(
|
||||
endpoint: string,
|
||||
exchangeData: { session_id: string; code: string; state?: string; proxy_id?: number }
|
||||
): Promise<Record<string, unknown>> {
|
||||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, exchangeData)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create accounts
|
||||
* @param accounts - Array of account data
|
||||
* @returns Results of batch creation
|
||||
*/
|
||||
export async function batchCreate(accounts: CreateAccountRequest[]): Promise<{
|
||||
success: number
|
||||
failed: number
|
||||
results: Array<{ success: boolean; account?: Account; error?: string }>
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
success: number
|
||||
failed: number
|
||||
results: Array<{ success: boolean; account?: Account; error?: string }>
|
||||
}>('/admin/accounts/batch', { accounts })
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch update credentials fields for multiple accounts
|
||||
* @param request - Batch update request containing account IDs, field name, and value
|
||||
* @returns Results of batch update
|
||||
*/
|
||||
export async function batchUpdateCredentials(request: {
|
||||
account_ids: number[]
|
||||
field: string
|
||||
value: any
|
||||
}): Promise<{
|
||||
success: number
|
||||
failed: number
|
||||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
success: number
|
||||
failed: number
|
||||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||
}>('/admin/accounts/batch-update-credentials', request)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk update multiple accounts
|
||||
* @param accountIds - Array of account IDs
|
||||
* @param updates - Fields to update
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function bulkUpdate(
|
||||
accountIds: number[],
|
||||
updates: Record<string, unknown>
|
||||
): Promise<{
|
||||
success: number
|
||||
failed: number
|
||||
success_ids?: number[]
|
||||
failed_ids?: number[]
|
||||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
success: number
|
||||
failed: number
|
||||
success_ids?: number[]
|
||||
failed_ids?: number[]
|
||||
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||
}>('/admin/accounts/bulk-update', {
|
||||
account_ids: accountIds,
|
||||
...updates
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get account today statistics
|
||||
* @param id - Account ID
|
||||
* @returns Today's stats (requests, tokens, cost)
|
||||
*/
|
||||
export async function getTodayStats(id: number): Promise<WindowStats> {
|
||||
const { data } = await apiClient.get<WindowStats>(`/admin/accounts/${id}/today-stats`)
|
||||
return data
|
||||
}
|
||||
|
||||
export interface BatchTodayStatsResponse {
|
||||
stats: Record<string, WindowStats>
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量获取多个账号的今日统计
|
||||
* @param accountIds - 账号 ID 列表
|
||||
* @returns 以账号 ID(字符串)为键的统计映射
|
||||
*/
|
||||
export async function getBatchTodayStats(accountIds: number[]): Promise<BatchTodayStatsResponse> {
|
||||
const { data } = await apiClient.post<BatchTodayStatsResponse>('/admin/accounts/today-stats/batch', {
|
||||
account_ids: accountIds
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Set account schedulable status
|
||||
* @param id - Account ID
|
||||
* @param schedulable - Whether the account should participate in scheduling
|
||||
* @returns Updated account
|
||||
*/
|
||||
export async function setSchedulable(id: number, schedulable: boolean): Promise<Account> {
|
||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/schedulable`, {
|
||||
schedulable
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available models for an account
|
||||
* @param id - Account ID
|
||||
* @returns List of available models for this account
|
||||
*/
|
||||
export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
|
||||
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/models`)
|
||||
return data
|
||||
}
|
||||
|
||||
export interface CRSPreviewAccount {
|
||||
crs_account_id: string
|
||||
kind: string
|
||||
name: string
|
||||
platform: string
|
||||
type: string
|
||||
}
|
||||
|
||||
export interface PreviewFromCRSResult {
|
||||
new_accounts: CRSPreviewAccount[]
|
||||
existing_accounts: CRSPreviewAccount[]
|
||||
}
|
||||
|
||||
export async function previewFromCrs(params: {
|
||||
base_url: string
|
||||
username: string
|
||||
password: string
|
||||
}): Promise<PreviewFromCRSResult> {
|
||||
const { data } = await apiClient.post<PreviewFromCRSResult>('/admin/accounts/sync/crs/preview', params)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function syncFromCrs(params: {
|
||||
base_url: string
|
||||
username: string
|
||||
password: string
|
||||
sync_proxies?: boolean
|
||||
selected_account_ids?: string[]
|
||||
}): Promise<{
|
||||
created: number
|
||||
updated: number
|
||||
skipped: number
|
||||
failed: number
|
||||
items: Array<{
|
||||
crs_account_id: string
|
||||
kind: string
|
||||
name: string
|
||||
action: string
|
||||
error?: string
|
||||
}>
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
created: number
|
||||
updated: number
|
||||
skipped: number
|
||||
failed: number
|
||||
items: Array<{
|
||||
crs_account_id: string
|
||||
kind: string
|
||||
name: string
|
||||
action: string
|
||||
error?: string
|
||||
}>
|
||||
}>('/admin/accounts/sync/crs', params)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function exportData(options?: {
|
||||
ids?: number[]
|
||||
filters?: {
|
||||
platform?: string
|
||||
type?: string
|
||||
status?: string
|
||||
search?: string
|
||||
}
|
||||
includeProxies?: boolean
|
||||
}): Promise<AdminDataPayload> {
|
||||
const params: Record<string, string> = {}
|
||||
if (options?.ids && options.ids.length > 0) {
|
||||
params.ids = options.ids.join(',')
|
||||
} else if (options?.filters) {
|
||||
const { platform, type, status, search } = options.filters
|
||||
if (platform) params.platform = platform
|
||||
if (type) params.type = type
|
||||
if (status) params.status = status
|
||||
if (search) params.search = search
|
||||
}
|
||||
if (options?.includeProxies === false) {
|
||||
params.include_proxies = 'false'
|
||||
}
|
||||
const { data } = await apiClient.get<AdminDataPayload>('/admin/accounts/data', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function importData(payload: {
|
||||
data: AdminDataPayload
|
||||
skip_default_group_bind?: boolean
|
||||
}): Promise<AdminDataImportResult> {
|
||||
const { data } = await apiClient.post<AdminDataImportResult>('/admin/accounts/data', {
|
||||
data: payload.data,
|
||||
skip_default_group_bind: payload.skip_default_group_bind
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get Antigravity default model mapping from backend
|
||||
* @returns Default model mapping (from -> to)
|
||||
*/
|
||||
export async function getAntigravityDefaultModelMapping(): Promise<Record<string, string>> {
|
||||
const { data } = await apiClient.get<Record<string, string>>(
|
||||
'/admin/accounts/antigravity/default-model-mapping'
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh OpenAI token using refresh token
|
||||
* @param refreshToken - The refresh token
|
||||
* @param proxyId - Optional proxy ID
|
||||
* @returns Token information including access_token, email, etc.
|
||||
*/
|
||||
export async function refreshOpenAIToken(
|
||||
refreshToken: string,
|
||||
proxyId?: number | null,
|
||||
endpoint: string = '/admin/openai/refresh-token'
|
||||
): Promise<Record<string, unknown>> {
|
||||
const payload: { refresh_token: string; proxy_id?: number } = {
|
||||
refresh_token: refreshToken
|
||||
}
|
||||
if (proxyId) {
|
||||
payload.proxy_id = proxyId
|
||||
}
|
||||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate Sora session token and exchange to access token
|
||||
* @param sessionToken - Sora session token
|
||||
* @param proxyId - Optional proxy ID
|
||||
* @param endpoint - API endpoint path
|
||||
* @returns Token information including access_token
|
||||
*/
|
||||
export async function validateSoraSessionToken(
|
||||
sessionToken: string,
|
||||
proxyId?: number | null,
|
||||
endpoint: string = '/admin/sora/st2at'
|
||||
): Promise<Record<string, unknown>> {
|
||||
const payload: { session_token: string; proxy_id?: number } = {
|
||||
session_token: sessionToken
|
||||
}
|
||||
if (proxyId) {
|
||||
payload.proxy_id = proxyId
|
||||
}
|
||||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch operation result type
|
||||
*/
|
||||
export interface BatchOperationResult {
|
||||
total: number
|
||||
success: number
|
||||
failed: number
|
||||
errors?: Array<{ account_id: number; error: string }>
|
||||
warnings?: Array<{ account_id: number; warning: string }>
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch clear account errors
|
||||
* @param accountIds - Array of account IDs
|
||||
* @returns Batch operation result
|
||||
*/
|
||||
export async function batchClearError(accountIds: number[]): Promise<BatchOperationResult> {
|
||||
const { data } = await apiClient.post<BatchOperationResult>('/admin/accounts/batch-clear-error', {
|
||||
account_ids: accountIds
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch refresh account credentials
|
||||
* @param accountIds - Array of account IDs
|
||||
* @returns Batch operation result
|
||||
*/
|
||||
export async function batchRefresh(accountIds: number[]): Promise<BatchOperationResult> {
|
||||
const { data } = await apiClient.post<BatchOperationResult>('/admin/accounts/batch-refresh', {
|
||||
account_ids: accountIds,
|
||||
}, {
|
||||
timeout: 120000 // 120s timeout for large batch refreshes
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export const accountsAPI = {
|
||||
list,
|
||||
listWithEtag,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
checkMixedChannelRisk,
|
||||
delete: deleteAccount,
|
||||
toggleStatus,
|
||||
testAccount,
|
||||
refreshCredentials,
|
||||
getStats,
|
||||
clearError,
|
||||
getUsage,
|
||||
getTodayStats,
|
||||
getBatchTodayStats,
|
||||
clearRateLimit,
|
||||
recoverState,
|
||||
resetAccountQuota,
|
||||
getTempUnschedulableStatus,
|
||||
resetTempUnschedulable,
|
||||
setSchedulable,
|
||||
getAvailableModels,
|
||||
generateAuthUrl,
|
||||
exchangeCode,
|
||||
refreshOpenAIToken,
|
||||
validateSoraSessionToken,
|
||||
batchCreate,
|
||||
batchUpdateCredentials,
|
||||
bulkUpdate,
|
||||
previewFromCrs,
|
||||
syncFromCrs,
|
||||
exportData,
|
||||
importData,
|
||||
getAntigravityDefaultModelMapping,
|
||||
batchClearError,
|
||||
batchRefresh
|
||||
}
|
||||
|
||||
export default accountsAPI
|
||||
@@ -0,0 +1,71 @@
|
||||
/**
|
||||
* Admin Announcements API endpoints
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
Announcement,
|
||||
AnnouncementUserReadStatus,
|
||||
BasePaginationResponse,
|
||||
CreateAnnouncementRequest,
|
||||
UpdateAnnouncementRequest
|
||||
} from '@/types'
|
||||
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
status?: string
|
||||
search?: string
|
||||
}
|
||||
): Promise<BasePaginationResponse<Announcement>> {
|
||||
const { data } = await apiClient.get<BasePaginationResponse<Announcement>>('/admin/announcements', {
|
||||
params: { page, page_size: pageSize, ...filters }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getById(id: number): Promise<Announcement> {
|
||||
const { data } = await apiClient.get<Announcement>(`/admin/announcements/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function create(request: CreateAnnouncementRequest): Promise<Announcement> {
|
||||
const { data } = await apiClient.post<Announcement>('/admin/announcements', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function update(id: number, request: UpdateAnnouncementRequest): Promise<Announcement> {
|
||||
const { data } = await apiClient.put<Announcement>(`/admin/announcements/${id}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteAnnouncement(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/announcements/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getReadStatus(
|
||||
id: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
search: string = ''
|
||||
): Promise<BasePaginationResponse<AnnouncementUserReadStatus>> {
|
||||
const { data } = await apiClient.get<BasePaginationResponse<AnnouncementUserReadStatus>>(
|
||||
`/admin/announcements/${id}/read-status`,
|
||||
{ params: { page, page_size: pageSize, search } }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
const announcementsAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteAnnouncement,
|
||||
getReadStatus
|
||||
}
|
||||
|
||||
export default announcementsAPI
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Admin Antigravity API endpoints
|
||||
* Handles Antigravity (Google Cloud AI Companion) OAuth flows for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export interface AntigravityAuthUrlResponse {
|
||||
auth_url: string
|
||||
session_id: string
|
||||
state: string
|
||||
}
|
||||
|
||||
export interface AntigravityAuthUrlRequest {
|
||||
proxy_id?: number
|
||||
}
|
||||
|
||||
export interface AntigravityExchangeCodeRequest {
|
||||
session_id: string
|
||||
state: string
|
||||
code: string
|
||||
proxy_id?: number
|
||||
}
|
||||
|
||||
export interface AntigravityTokenInfo {
|
||||
access_token?: string
|
||||
refresh_token?: string
|
||||
token_type?: string
|
||||
expires_at?: number | string
|
||||
expires_in?: number
|
||||
project_id?: string
|
||||
email?: string
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export async function generateAuthUrl(
|
||||
payload: AntigravityAuthUrlRequest
|
||||
): Promise<AntigravityAuthUrlResponse> {
|
||||
const { data } = await apiClient.post<AntigravityAuthUrlResponse>(
|
||||
'/admin/antigravity/oauth/auth-url',
|
||||
payload
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function exchangeCode(
|
||||
payload: AntigravityExchangeCodeRequest
|
||||
): Promise<AntigravityTokenInfo> {
|
||||
const { data } = await apiClient.post<AntigravityTokenInfo>(
|
||||
'/admin/antigravity/oauth/exchange-code',
|
||||
payload
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function refreshAntigravityToken(
|
||||
refreshToken: string,
|
||||
proxyId?: number | null
|
||||
): Promise<AntigravityTokenInfo> {
|
||||
const payload: Record<string, any> = { refresh_token: refreshToken }
|
||||
if (proxyId) payload.proxy_id = proxyId
|
||||
|
||||
const { data } = await apiClient.post<AntigravityTokenInfo>(
|
||||
'/admin/antigravity/oauth/refresh-token',
|
||||
payload
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export default { generateAuthUrl, exchangeCode, refreshAntigravityToken }
|
||||
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* Admin API Keys API endpoints
|
||||
* Handles API key management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { ApiKey } from '@/types'
|
||||
|
||||
export interface UpdateApiKeyGroupResult {
|
||||
api_key: ApiKey
|
||||
auto_granted_group_access: boolean
|
||||
granted_group_id?: number
|
||||
granted_group_name?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an API key's group binding
|
||||
* @param id - API Key ID
|
||||
* @param groupId - Group ID (0 to unbind, positive to bind, null/undefined to skip)
|
||||
* @returns Updated API key with auto-grant info
|
||||
*/
|
||||
export async function updateApiKeyGroup(id: number, groupId: number | null): Promise<UpdateApiKeyGroupResult> {
|
||||
const { data } = await apiClient.put<UpdateApiKeyGroupResult>(`/admin/api-keys/${id}`, {
|
||||
group_id: groupId === null ? 0 : groupId
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export const apiKeysAPI = {
|
||||
updateApiKeyGroup
|
||||
}
|
||||
|
||||
export default apiKeysAPI
|
||||
@@ -0,0 +1,114 @@
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export interface BackupS3Config {
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
}
|
||||
|
||||
export interface BackupScheduleConfig {
|
||||
enabled: boolean
|
||||
cron_expr: string
|
||||
retain_days: number
|
||||
retain_count: number
|
||||
}
|
||||
|
||||
export interface BackupRecord {
|
||||
id: string
|
||||
status: 'pending' | 'running' | 'completed' | 'failed'
|
||||
backup_type: string
|
||||
file_name: string
|
||||
s3_key: string
|
||||
size_bytes: number
|
||||
triggered_by: string
|
||||
error_message?: string
|
||||
started_at: string
|
||||
finished_at?: string
|
||||
expires_at?: string
|
||||
}
|
||||
|
||||
export interface CreateBackupRequest {
|
||||
expire_days?: number
|
||||
}
|
||||
|
||||
export interface TestS3Response {
|
||||
ok: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
// S3 Config
|
||||
export async function getS3Config(): Promise<BackupS3Config> {
|
||||
const { data } = await apiClient.get<BackupS3Config>('/admin/backups/s3-config')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateS3Config(config: BackupS3Config): Promise<BackupS3Config> {
|
||||
const { data } = await apiClient.put<BackupS3Config>('/admin/backups/s3-config', config)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function testS3Connection(config: BackupS3Config): Promise<TestS3Response> {
|
||||
const { data } = await apiClient.post<TestS3Response>('/admin/backups/s3-config/test', config)
|
||||
return data
|
||||
}
|
||||
|
||||
// Schedule
|
||||
export async function getSchedule(): Promise<BackupScheduleConfig> {
|
||||
const { data } = await apiClient.get<BackupScheduleConfig>('/admin/backups/schedule')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateSchedule(config: BackupScheduleConfig): Promise<BackupScheduleConfig> {
|
||||
const { data } = await apiClient.put<BackupScheduleConfig>('/admin/backups/schedule', config)
|
||||
return data
|
||||
}
|
||||
|
||||
// Backup operations
|
||||
export async function createBackup(req?: CreateBackupRequest): Promise<BackupRecord> {
|
||||
const { data } = await apiClient.post<BackupRecord>('/admin/backups', req || {}, { timeout: 600000 })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listBackups(): Promise<{ items: BackupRecord[] }> {
|
||||
const { data } = await apiClient.get<{ items: BackupRecord[] }>('/admin/backups')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getBackup(id: string): Promise<BackupRecord> {
|
||||
const { data } = await apiClient.get<BackupRecord>(`/admin/backups/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteBackup(id: string): Promise<void> {
|
||||
await apiClient.delete(`/admin/backups/${id}`)
|
||||
}
|
||||
|
||||
export async function getDownloadURL(id: string): Promise<{ url: string }> {
|
||||
const { data } = await apiClient.get<{ url: string }>(`/admin/backups/${id}/download-url`)
|
||||
return data
|
||||
}
|
||||
|
||||
// Restore
|
||||
export async function restoreBackup(id: string, password: string): Promise<void> {
|
||||
await apiClient.post(`/admin/backups/${id}/restore`, { password }, { timeout: 600000 })
|
||||
}
|
||||
|
||||
export const backupAPI = {
|
||||
getS3Config,
|
||||
updateS3Config,
|
||||
testS3Connection,
|
||||
getSchedule,
|
||||
updateSchedule,
|
||||
createBackup,
|
||||
listBackups,
|
||||
getBackup,
|
||||
deleteBackup,
|
||||
getDownloadURL,
|
||||
restoreBackup,
|
||||
}
|
||||
|
||||
export default backupAPI
|
||||
@@ -0,0 +1,299 @@
|
||||
/**
|
||||
* Admin Dashboard API endpoints
|
||||
* Provides system-wide statistics and metrics
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
DashboardStats,
|
||||
TrendDataPoint,
|
||||
ModelStat,
|
||||
GroupStat,
|
||||
ApiKeyUsageTrendPoint,
|
||||
UserUsageTrendPoint,
|
||||
UserSpendingRankingResponse,
|
||||
UsageRequestType
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* Get dashboard statistics
|
||||
* @returns Dashboard statistics including users, keys, accounts, and token usage
|
||||
*/
|
||||
export async function getStats(): Promise<DashboardStats> {
|
||||
const { data } = await apiClient.get<DashboardStats>('/admin/dashboard/stats')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get real-time metrics
|
||||
* @returns Real-time system metrics
|
||||
*/
|
||||
export async function getRealtimeMetrics(): Promise<{
|
||||
active_requests: number
|
||||
requests_per_minute: number
|
||||
average_response_time: number
|
||||
error_rate: number
|
||||
}> {
|
||||
const { data } = await apiClient.get<{
|
||||
active_requests: number
|
||||
requests_per_minute: number
|
||||
average_response_time: number
|
||||
error_rate: number
|
||||
}>('/admin/dashboard/realtime')
|
||||
return data
|
||||
}
|
||||
|
||||
export interface TrendParams {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
granularity?: 'day' | 'hour'
|
||||
user_id?: number
|
||||
api_key_id?: number
|
||||
model?: string
|
||||
account_id?: number
|
||||
group_id?: number
|
||||
request_type?: UsageRequestType
|
||||
stream?: boolean
|
||||
billing_type?: number | null
|
||||
}
|
||||
|
||||
export interface TrendResponse {
|
||||
trend: TrendDataPoint[]
|
||||
start_date: string
|
||||
end_date: string
|
||||
granularity: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage trend data
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns Usage trend data
|
||||
*/
|
||||
export async function getUsageTrend(params?: TrendParams): Promise<TrendResponse> {
|
||||
const { data } = await apiClient.get<TrendResponse>('/admin/dashboard/trend', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
export interface ModelStatsParams {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
user_id?: number
|
||||
api_key_id?: number
|
||||
model?: string
|
||||
account_id?: number
|
||||
group_id?: number
|
||||
request_type?: UsageRequestType
|
||||
stream?: boolean
|
||||
billing_type?: number | null
|
||||
}
|
||||
|
||||
export interface ModelStatsResponse {
|
||||
models: ModelStat[]
|
||||
start_date: string
|
||||
end_date: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get model usage statistics
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns Model usage statistics
|
||||
*/
|
||||
export async function getModelStats(params?: ModelStatsParams): Promise<ModelStatsResponse> {
|
||||
const { data } = await apiClient.get<ModelStatsResponse>('/admin/dashboard/models', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
export interface GroupStatsParams {
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
user_id?: number
|
||||
api_key_id?: number
|
||||
account_id?: number
|
||||
group_id?: number
|
||||
request_type?: UsageRequestType
|
||||
stream?: boolean
|
||||
billing_type?: number | null
|
||||
}
|
||||
|
||||
export interface GroupStatsResponse {
|
||||
groups: GroupStat[]
|
||||
start_date: string
|
||||
end_date: string
|
||||
}
|
||||
|
||||
export interface DashboardSnapshotV2Params extends TrendParams {
|
||||
include_stats?: boolean
|
||||
include_trend?: boolean
|
||||
include_model_stats?: boolean
|
||||
include_group_stats?: boolean
|
||||
include_users_trend?: boolean
|
||||
users_trend_limit?: number
|
||||
}
|
||||
|
||||
export interface DashboardSnapshotV2Stats extends DashboardStats {
|
||||
uptime: number
|
||||
}
|
||||
|
||||
export interface DashboardSnapshotV2Response {
|
||||
generated_at: string
|
||||
start_date: string
|
||||
end_date: string
|
||||
granularity: string
|
||||
stats?: DashboardSnapshotV2Stats
|
||||
trend?: TrendDataPoint[]
|
||||
models?: ModelStat[]
|
||||
groups?: GroupStat[]
|
||||
users_trend?: UserUsageTrendPoint[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group usage statistics
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns Group usage statistics
|
||||
*/
|
||||
export async function getGroupStats(params?: GroupStatsParams): Promise<GroupStatsResponse> {
|
||||
const { data } = await apiClient.get<GroupStatsResponse>('/admin/dashboard/groups', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get dashboard snapshot v2 (aggregated response for heavy admin pages).
|
||||
*/
|
||||
export async function getSnapshotV2(params?: DashboardSnapshotV2Params): Promise<DashboardSnapshotV2Response> {
|
||||
const { data } = await apiClient.get<DashboardSnapshotV2Response>('/admin/dashboard/snapshot-v2', {
|
||||
params
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export interface ApiKeyTrendParams extends TrendParams {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface ApiKeyTrendResponse {
|
||||
trend: ApiKeyUsageTrendPoint[]
|
||||
start_date: string
|
||||
end_date: string
|
||||
granularity: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API key usage trend data
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns API key usage trend data
|
||||
*/
|
||||
export async function getApiKeyUsageTrend(
|
||||
params?: ApiKeyTrendParams
|
||||
): Promise<ApiKeyTrendResponse> {
|
||||
const { data } = await apiClient.get<ApiKeyTrendResponse>('/admin/dashboard/api-keys-trend', {
|
||||
params
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export interface UserTrendParams extends TrendParams {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
export interface UserTrendResponse {
|
||||
trend: UserUsageTrendPoint[]
|
||||
start_date: string
|
||||
end_date: string
|
||||
granularity: string
|
||||
}
|
||||
|
||||
export interface UserSpendingRankingParams
|
||||
extends Pick<TrendParams, 'start_date' | 'end_date'> {
|
||||
limit?: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user usage trend data
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns User usage trend data
|
||||
*/
|
||||
export async function getUserUsageTrend(params?: UserTrendParams): Promise<UserTrendResponse> {
|
||||
const { data } = await apiClient.get<UserTrendResponse>('/admin/dashboard/users-trend', {
|
||||
params
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user spending ranking data
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns User spending ranking data
|
||||
*/
|
||||
export async function getUserSpendingRanking(
|
||||
params?: UserSpendingRankingParams
|
||||
): Promise<UserSpendingRankingResponse> {
|
||||
const { data } = await apiClient.get<UserSpendingRankingResponse>('/admin/dashboard/users-ranking', {
|
||||
params
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export interface BatchUserUsageStats {
|
||||
user_id: number
|
||||
today_actual_cost: number
|
||||
total_actual_cost: number
|
||||
}
|
||||
|
||||
export interface BatchUsersUsageResponse {
|
||||
stats: Record<string, BatchUserUsageStats>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch usage stats for multiple users
|
||||
* @param userIds - Array of user IDs
|
||||
* @returns Usage stats map keyed by user ID
|
||||
*/
|
||||
export async function getBatchUsersUsage(userIds: number[]): Promise<BatchUsersUsageResponse> {
|
||||
const { data } = await apiClient.post<BatchUsersUsageResponse>('/admin/dashboard/users-usage', {
|
||||
user_ids: userIds
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export interface BatchApiKeyUsageStats {
|
||||
api_key_id: number
|
||||
today_actual_cost: number
|
||||
total_actual_cost: number
|
||||
}
|
||||
|
||||
export interface BatchApiKeysUsageResponse {
|
||||
stats: Record<string, BatchApiKeyUsageStats>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get batch usage stats for multiple API keys
|
||||
* @param apiKeyIds - Array of API key IDs
|
||||
* @returns Usage stats map keyed by API key ID
|
||||
*/
|
||||
export async function getBatchApiKeysUsage(
|
||||
apiKeyIds: number[]
|
||||
): Promise<BatchApiKeysUsageResponse> {
|
||||
const { data } = await apiClient.post<BatchApiKeysUsageResponse>(
|
||||
'/admin/dashboard/api-keys-usage',
|
||||
{
|
||||
api_key_ids: apiKeyIds
|
||||
}
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const dashboardAPI = {
|
||||
getStats,
|
||||
getRealtimeMetrics,
|
||||
getUsageTrend,
|
||||
getModelStats,
|
||||
getGroupStats,
|
||||
getSnapshotV2,
|
||||
getApiKeyUsageTrend,
|
||||
getUserUsageTrend,
|
||||
getUserSpendingRanking,
|
||||
getBatchUsersUsage,
|
||||
getBatchApiKeysUsage
|
||||
}
|
||||
|
||||
export default dashboardAPI
|
||||
@@ -0,0 +1,332 @@
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export type BackupType = 'postgres' | 'redis' | 'full'
|
||||
export type BackupJobStatus = 'queued' | 'running' | 'succeeded' | 'failed' | 'partial_succeeded'
|
||||
|
||||
export interface BackupAgentInfo {
|
||||
status: string
|
||||
version: string
|
||||
uptime_seconds: number
|
||||
}
|
||||
|
||||
export interface BackupAgentHealth {
|
||||
enabled: boolean
|
||||
reason: string
|
||||
socket_path: string
|
||||
agent?: BackupAgentInfo
|
||||
}
|
||||
|
||||
export interface DataManagementPostgresConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password?: string
|
||||
password_configured?: boolean
|
||||
database: string
|
||||
ssl_mode: string
|
||||
container_name: string
|
||||
}
|
||||
|
||||
export interface DataManagementRedisConfig {
|
||||
addr: string
|
||||
username: string
|
||||
password?: string
|
||||
password_configured?: boolean
|
||||
db: number
|
||||
container_name: string
|
||||
}
|
||||
|
||||
export interface DataManagementS3Config {
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
secret_access_key_configured?: boolean
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
use_ssl: boolean
|
||||
}
|
||||
|
||||
export interface DataManagementConfig {
|
||||
source_mode: 'direct' | 'docker_exec'
|
||||
backup_root: string
|
||||
sqlite_path?: string
|
||||
retention_days: number
|
||||
keep_last: number
|
||||
active_postgres_profile_id?: string
|
||||
active_redis_profile_id?: string
|
||||
active_s3_profile_id?: string
|
||||
postgres: DataManagementPostgresConfig
|
||||
redis: DataManagementRedisConfig
|
||||
s3: DataManagementS3Config
|
||||
}
|
||||
|
||||
export type SourceType = 'postgres' | 'redis'
|
||||
|
||||
export interface DataManagementSourceConfig {
|
||||
host: string
|
||||
port: number
|
||||
user: string
|
||||
password?: string
|
||||
database: string
|
||||
ssl_mode: string
|
||||
addr: string
|
||||
username: string
|
||||
db: number
|
||||
container_name: string
|
||||
}
|
||||
|
||||
export interface DataManagementSourceProfile {
|
||||
source_type: SourceType
|
||||
profile_id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
password_configured?: boolean
|
||||
config: DataManagementSourceConfig
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface TestS3Request {
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key: string
|
||||
prefix?: string
|
||||
force_path_style?: boolean
|
||||
use_ssl?: boolean
|
||||
}
|
||||
|
||||
export interface TestS3Response {
|
||||
ok: boolean
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface CreateBackupJobRequest {
|
||||
backup_type: BackupType
|
||||
upload_to_s3?: boolean
|
||||
s3_profile_id?: string
|
||||
postgres_profile_id?: string
|
||||
redis_profile_id?: string
|
||||
idempotency_key?: string
|
||||
}
|
||||
|
||||
export interface CreateBackupJobResponse {
|
||||
job_id: string
|
||||
status: BackupJobStatus
|
||||
}
|
||||
|
||||
export interface BackupArtifactInfo {
|
||||
local_path: string
|
||||
size_bytes: number
|
||||
sha256: string
|
||||
}
|
||||
|
||||
export interface BackupS3Info {
|
||||
bucket: string
|
||||
key: string
|
||||
etag: string
|
||||
}
|
||||
|
||||
export interface BackupJob {
|
||||
job_id: string
|
||||
backup_type: BackupType
|
||||
status: BackupJobStatus
|
||||
triggered_by: string
|
||||
s3_profile_id?: string
|
||||
postgres_profile_id?: string
|
||||
redis_profile_id?: string
|
||||
started_at?: string
|
||||
finished_at?: string
|
||||
error_message?: string
|
||||
artifact?: BackupArtifactInfo
|
||||
s3?: BackupS3Info
|
||||
}
|
||||
|
||||
export interface ListSourceProfilesResponse {
|
||||
items: DataManagementSourceProfile[]
|
||||
}
|
||||
|
||||
export interface CreateSourceProfileRequest {
|
||||
profile_id: string
|
||||
name: string
|
||||
config: DataManagementSourceConfig
|
||||
set_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateSourceProfileRequest {
|
||||
name: string
|
||||
config: DataManagementSourceConfig
|
||||
}
|
||||
|
||||
export interface DataManagementS3Profile {
|
||||
profile_id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
s3: DataManagementS3Config
|
||||
secret_access_key_configured?: boolean
|
||||
created_at?: string
|
||||
updated_at?: string
|
||||
}
|
||||
|
||||
export interface ListS3ProfilesResponse {
|
||||
items: DataManagementS3Profile[]
|
||||
}
|
||||
|
||||
export interface CreateS3ProfileRequest {
|
||||
profile_id: string
|
||||
name: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix?: string
|
||||
force_path_style?: boolean
|
||||
use_ssl?: boolean
|
||||
set_active?: boolean
|
||||
}
|
||||
|
||||
export interface UpdateS3ProfileRequest {
|
||||
name: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix?: string
|
||||
force_path_style?: boolean
|
||||
use_ssl?: boolean
|
||||
}
|
||||
|
||||
export interface ListBackupJobsRequest {
|
||||
page_size?: number
|
||||
page_token?: string
|
||||
status?: BackupJobStatus
|
||||
backup_type?: BackupType
|
||||
}
|
||||
|
||||
export interface ListBackupJobsResponse {
|
||||
items: BackupJob[]
|
||||
next_page_token?: string
|
||||
}
|
||||
|
||||
export async function getAgentHealth(): Promise<BackupAgentHealth> {
|
||||
const { data } = await apiClient.get<BackupAgentHealth>('/admin/data-management/agent/health')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getConfig(): Promise<DataManagementConfig> {
|
||||
const { data } = await apiClient.get<DataManagementConfig>('/admin/data-management/config')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateConfig(request: DataManagementConfig): Promise<DataManagementConfig> {
|
||||
const { data } = await apiClient.put<DataManagementConfig>('/admin/data-management/config', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function testS3(request: TestS3Request): Promise<TestS3Response> {
|
||||
const { data } = await apiClient.post<TestS3Response>('/admin/data-management/s3/test', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listSourceProfiles(sourceType: SourceType): Promise<ListSourceProfilesResponse> {
|
||||
const { data } = await apiClient.get<ListSourceProfilesResponse>(`/admin/data-management/sources/${sourceType}/profiles`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createSourceProfile(sourceType: SourceType, request: CreateSourceProfileRequest): Promise<DataManagementSourceProfile> {
|
||||
const { data } = await apiClient.post<DataManagementSourceProfile>(`/admin/data-management/sources/${sourceType}/profiles`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateSourceProfile(sourceType: SourceType, profileID: string, request: UpdateSourceProfileRequest): Promise<DataManagementSourceProfile> {
|
||||
const { data } = await apiClient.put<DataManagementSourceProfile>(`/admin/data-management/sources/${sourceType}/profiles/${profileID}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteSourceProfile(sourceType: SourceType, profileID: string): Promise<void> {
|
||||
await apiClient.delete(`/admin/data-management/sources/${sourceType}/profiles/${profileID}`)
|
||||
}
|
||||
|
||||
export async function setActiveSourceProfile(sourceType: SourceType, profileID: string): Promise<DataManagementSourceProfile> {
|
||||
const { data } = await apiClient.post<DataManagementSourceProfile>(`/admin/data-management/sources/${sourceType}/profiles/${profileID}/activate`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listS3Profiles(): Promise<ListS3ProfilesResponse> {
|
||||
const { data } = await apiClient.get<ListS3ProfilesResponse>('/admin/data-management/s3/profiles')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createS3Profile(request: CreateS3ProfileRequest): Promise<DataManagementS3Profile> {
|
||||
const { data } = await apiClient.post<DataManagementS3Profile>('/admin/data-management/s3/profiles', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateS3Profile(profileID: string, request: UpdateS3ProfileRequest): Promise<DataManagementS3Profile> {
|
||||
const { data } = await apiClient.put<DataManagementS3Profile>(`/admin/data-management/s3/profiles/${profileID}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteS3Profile(profileID: string): Promise<void> {
|
||||
await apiClient.delete(`/admin/data-management/s3/profiles/${profileID}`)
|
||||
}
|
||||
|
||||
export async function setActiveS3Profile(profileID: string): Promise<DataManagementS3Profile> {
|
||||
const { data } = await apiClient.post<DataManagementS3Profile>(`/admin/data-management/s3/profiles/${profileID}/activate`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createBackupJob(request: CreateBackupJobRequest): Promise<CreateBackupJobResponse> {
|
||||
const headers = request.idempotency_key
|
||||
? { 'X-Idempotency-Key': request.idempotency_key }
|
||||
: undefined
|
||||
|
||||
const { data } = await apiClient.post<CreateBackupJobResponse>(
|
||||
'/admin/data-management/backups',
|
||||
request,
|
||||
{ headers }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listBackupJobs(request?: ListBackupJobsRequest): Promise<ListBackupJobsResponse> {
|
||||
const { data } = await apiClient.get<ListBackupJobsResponse>('/admin/data-management/backups', {
|
||||
params: request
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getBackupJob(jobID: string): Promise<BackupJob> {
|
||||
const { data } = await apiClient.get<BackupJob>(`/admin/data-management/backups/${jobID}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export const dataManagementAPI = {
|
||||
getAgentHealth,
|
||||
getConfig,
|
||||
updateConfig,
|
||||
listSourceProfiles,
|
||||
createSourceProfile,
|
||||
updateSourceProfile,
|
||||
deleteSourceProfile,
|
||||
setActiveSourceProfile,
|
||||
testS3,
|
||||
listS3Profiles,
|
||||
createS3Profile,
|
||||
updateS3Profile,
|
||||
deleteS3Profile,
|
||||
setActiveS3Profile,
|
||||
createBackupJob,
|
||||
listBackupJobs,
|
||||
getBackupJob
|
||||
}
|
||||
|
||||
export default dataManagementAPI
|
||||
@@ -0,0 +1,137 @@
|
||||
/**
|
||||
* Admin Error Passthrough Rules API endpoints
|
||||
* Handles error passthrough rule management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
|
||||
/**
|
||||
* Error passthrough rule interface
|
||||
*/
|
||||
export interface ErrorPassthroughRule {
|
||||
id: number
|
||||
name: string
|
||||
enabled: boolean
|
||||
priority: number
|
||||
error_codes: number[]
|
||||
keywords: string[]
|
||||
match_mode: 'any' | 'all'
|
||||
platforms: string[]
|
||||
passthrough_code: boolean
|
||||
response_code: number | null
|
||||
passthrough_body: boolean
|
||||
custom_message: string | null
|
||||
skip_monitoring: boolean
|
||||
description: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Create rule request
|
||||
*/
|
||||
export interface CreateRuleRequest {
|
||||
name: string
|
||||
enabled?: boolean
|
||||
priority?: number
|
||||
error_codes?: number[]
|
||||
keywords?: string[]
|
||||
match_mode?: 'any' | 'all'
|
||||
platforms?: string[]
|
||||
passthrough_code?: boolean
|
||||
response_code?: number | null
|
||||
passthrough_body?: boolean
|
||||
custom_message?: string | null
|
||||
skip_monitoring?: boolean
|
||||
description?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rule request
|
||||
*/
|
||||
export interface UpdateRuleRequest {
|
||||
name?: string
|
||||
enabled?: boolean
|
||||
priority?: number
|
||||
error_codes?: number[]
|
||||
keywords?: string[]
|
||||
match_mode?: 'any' | 'all'
|
||||
platforms?: string[]
|
||||
passthrough_code?: boolean
|
||||
response_code?: number | null
|
||||
passthrough_body?: boolean
|
||||
custom_message?: string | null
|
||||
skip_monitoring?: boolean
|
||||
description?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* List all error passthrough rules
|
||||
* @returns List of all rules sorted by priority
|
||||
*/
|
||||
export async function list(): Promise<ErrorPassthroughRule[]> {
|
||||
const { data } = await apiClient.get<ErrorPassthroughRule[]>('/admin/error-passthrough-rules')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rule by ID
|
||||
* @param id - Rule ID
|
||||
* @returns Rule details
|
||||
*/
|
||||
export async function getById(id: number): Promise<ErrorPassthroughRule> {
|
||||
const { data } = await apiClient.get<ErrorPassthroughRule>(`/admin/error-passthrough-rules/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new rule
|
||||
* @param ruleData - Rule data
|
||||
* @returns Created rule
|
||||
*/
|
||||
export async function create(ruleData: CreateRuleRequest): Promise<ErrorPassthroughRule> {
|
||||
const { data } = await apiClient.post<ErrorPassthroughRule>('/admin/error-passthrough-rules', ruleData)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rule
|
||||
* @param id - Rule ID
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated rule
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateRuleRequest): Promise<ErrorPassthroughRule> {
|
||||
const { data } = await apiClient.put<ErrorPassthroughRule>(`/admin/error-passthrough-rules/${id}`, updates)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete rule
|
||||
* @param id - Rule ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteRule(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/error-passthrough-rules/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle rule enabled status
|
||||
* @param id - Rule ID
|
||||
* @param enabled - New enabled status
|
||||
* @returns Updated rule
|
||||
*/
|
||||
export async function toggleEnabled(id: number, enabled: boolean): Promise<ErrorPassthroughRule> {
|
||||
return update(id, { enabled })
|
||||
}
|
||||
|
||||
export const errorPassthroughAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteRule,
|
||||
toggleEnabled
|
||||
}
|
||||
|
||||
export default errorPassthroughAPI
|
||||
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Admin Gemini API endpoints
|
||||
* Handles Gemini OAuth flows for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export interface GeminiAuthUrlResponse {
|
||||
auth_url: string
|
||||
session_id: string
|
||||
state: string
|
||||
}
|
||||
|
||||
export interface GeminiOAuthCapabilities {
|
||||
ai_studio_oauth_enabled: boolean
|
||||
required_redirect_uris: string[]
|
||||
}
|
||||
|
||||
export interface GeminiAuthUrlRequest {
|
||||
proxy_id?: number
|
||||
project_id?: string
|
||||
oauth_type?: 'code_assist' | 'google_one' | 'ai_studio'
|
||||
tier_id?: string
|
||||
}
|
||||
|
||||
export interface GeminiExchangeCodeRequest {
|
||||
session_id: string
|
||||
state: string
|
||||
code: string
|
||||
proxy_id?: number
|
||||
oauth_type?: 'code_assist' | 'google_one' | 'ai_studio'
|
||||
tier_id?: string
|
||||
}
|
||||
|
||||
export type GeminiTokenInfo = {
|
||||
access_token?: string
|
||||
refresh_token?: string
|
||||
token_type?: string
|
||||
scope?: string
|
||||
expires_in?: number
|
||||
expires_at?: number
|
||||
project_id?: string
|
||||
oauth_type?: string
|
||||
tier_id?: string
|
||||
extra?: Record<string, unknown>
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
export async function generateAuthUrl(
|
||||
payload: GeminiAuthUrlRequest
|
||||
): Promise<GeminiAuthUrlResponse> {
|
||||
const { data } = await apiClient.post<GeminiAuthUrlResponse>(
|
||||
'/admin/gemini/oauth/auth-url',
|
||||
payload
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function exchangeCode(payload: GeminiExchangeCodeRequest): Promise<GeminiTokenInfo> {
|
||||
const { data } = await apiClient.post<GeminiTokenInfo>(
|
||||
'/admin/gemini/oauth/exchange-code',
|
||||
payload
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getCapabilities(): Promise<GeminiOAuthCapabilities> {
|
||||
const { data } = await apiClient.get<GeminiOAuthCapabilities>('/admin/gemini/oauth/capabilities')
|
||||
return data
|
||||
}
|
||||
|
||||
export default { generateAuthUrl, exchangeCode, getCapabilities }
|
||||
@@ -0,0 +1,238 @@
|
||||
/**
|
||||
* Admin Groups API endpoints
|
||||
* Handles API key group management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
AdminGroup,
|
||||
GroupPlatform,
|
||||
CreateGroupRequest,
|
||||
UpdateGroupRequest,
|
||||
PaginatedResponse
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* List all groups with pagination
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param filters - Optional filters (platform, status, is_exclusive, search)
|
||||
* @returns Paginated list of groups
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
platform?: GroupPlatform
|
||||
status?: 'active' | 'inactive'
|
||||
is_exclusive?: boolean
|
||||
search?: string
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<AdminGroup>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<AdminGroup>>('/admin/groups', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters
|
||||
},
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active groups (without pagination)
|
||||
* @param platform - Optional platform filter
|
||||
* @returns List of all active groups
|
||||
*/
|
||||
export async function getAll(platform?: GroupPlatform): Promise<AdminGroup[]> {
|
||||
const { data } = await apiClient.get<AdminGroup[]>('/admin/groups/all', {
|
||||
params: platform ? { platform } : undefined
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get active groups by platform
|
||||
* @param platform - Platform to filter by
|
||||
* @returns List of groups for the specified platform
|
||||
*/
|
||||
export async function getByPlatform(platform: GroupPlatform): Promise<AdminGroup[]> {
|
||||
return getAll(platform)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group by ID
|
||||
* @param id - Group ID
|
||||
* @returns Group details
|
||||
*/
|
||||
export async function getById(id: number): Promise<AdminGroup> {
|
||||
const { data } = await apiClient.get<AdminGroup>(`/admin/groups/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new group
|
||||
* @param groupData - Group data
|
||||
* @returns Created group
|
||||
*/
|
||||
export async function create(groupData: CreateGroupRequest): Promise<AdminGroup> {
|
||||
const { data } = await apiClient.post<AdminGroup>('/admin/groups', groupData)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group
|
||||
* @param id - Group ID
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated group
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateGroupRequest): Promise<AdminGroup> {
|
||||
const { data } = await apiClient.put<AdminGroup>(`/admin/groups/${id}`, updates)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete group
|
||||
* @param id - Group ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteGroup(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/groups/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle group status
|
||||
* @param id - Group ID
|
||||
* @param status - New status
|
||||
* @returns Updated group
|
||||
*/
|
||||
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<AdminGroup> {
|
||||
return update(id, { status })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group statistics
|
||||
* @param id - Group ID
|
||||
* @returns Group usage statistics
|
||||
*/
|
||||
export async function getStats(id: number): Promise<{
|
||||
total_api_keys: number
|
||||
active_api_keys: number
|
||||
total_requests: number
|
||||
total_cost: number
|
||||
}> {
|
||||
const { data } = await apiClient.get<{
|
||||
total_api_keys: number
|
||||
active_api_keys: number
|
||||
total_requests: number
|
||||
total_cost: number
|
||||
}>(`/admin/groups/${id}/stats`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API keys in a group
|
||||
* @param id - Group ID
|
||||
* @param page - Page number
|
||||
* @param pageSize - Items per page
|
||||
* @returns Paginated list of API keys in the group
|
||||
*/
|
||||
export async function getGroupApiKeys(
|
||||
id: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
): Promise<PaginatedResponse<any>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<any>>(`/admin/groups/${id}/api-keys`, {
|
||||
params: { page, page_size: pageSize }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Rate multiplier entry for a user in a group
|
||||
*/
|
||||
export interface GroupRateMultiplierEntry {
|
||||
user_id: number
|
||||
user_name: string
|
||||
user_email: string
|
||||
user_notes: string
|
||||
user_status: string
|
||||
rate_multiplier: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rate multipliers for users in a group
|
||||
* @param id - Group ID
|
||||
* @returns List of user rate multiplier entries
|
||||
*/
|
||||
export async function getGroupRateMultipliers(id: number): Promise<GroupRateMultiplierEntry[]> {
|
||||
const { data } = await apiClient.get<GroupRateMultiplierEntry[]>(
|
||||
`/admin/groups/${id}/rate-multipliers`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update group sort orders
|
||||
* @param updates - Array of { id, sort_order } objects
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function updateSortOrder(
|
||||
updates: Array<{ id: number; sort_order: number }>
|
||||
): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.put<{ message: string }>('/admin/groups/sort-order', {
|
||||
updates
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all rate multipliers for a group
|
||||
* @param id - Group ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function clearGroupRateMultipliers(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/groups/${id}/rate-multipliers`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch set rate multipliers for users in a group
|
||||
* @param id - Group ID
|
||||
* @param entries - Array of { user_id, rate_multiplier }
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function batchSetGroupRateMultipliers(
|
||||
id: number,
|
||||
entries: Array<{ user_id: number; rate_multiplier: number }>
|
||||
): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.put<{ message: string }>(
|
||||
`/admin/groups/${id}/rate-multipliers`,
|
||||
{ entries }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const groupsAPI = {
|
||||
list,
|
||||
getAll,
|
||||
getByPlatform,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteGroup,
|
||||
toggleStatus,
|
||||
getStats,
|
||||
getGroupApiKeys,
|
||||
getGroupRateMultipliers,
|
||||
clearGroupRateMultipliers,
|
||||
batchSetGroupRateMultipliers,
|
||||
updateSortOrder
|
||||
}
|
||||
|
||||
export default groupsAPI
|
||||
@@ -0,0 +1,84 @@
|
||||
/**
|
||||
* Admin API barrel export
|
||||
* Centralized exports for all admin API modules
|
||||
*/
|
||||
|
||||
import dashboardAPI from './dashboard'
|
||||
import usersAPI from './users'
|
||||
import groupsAPI from './groups'
|
||||
import accountsAPI from './accounts'
|
||||
import proxiesAPI from './proxies'
|
||||
import redeemAPI from './redeem'
|
||||
import promoAPI from './promo'
|
||||
import announcementsAPI from './announcements'
|
||||
import settingsAPI from './settings'
|
||||
import systemAPI from './system'
|
||||
import subscriptionsAPI from './subscriptions'
|
||||
import usageAPI from './usage'
|
||||
import geminiAPI from './gemini'
|
||||
import antigravityAPI from './antigravity'
|
||||
import userAttributesAPI from './userAttributes'
|
||||
import opsAPI from './ops'
|
||||
import errorPassthroughAPI from './errorPassthrough'
|
||||
import dataManagementAPI from './dataManagement'
|
||||
import apiKeysAPI from './apiKeys'
|
||||
import scheduledTestsAPI from './scheduledTests'
|
||||
import backupAPI from './backup'
|
||||
|
||||
/**
|
||||
* Unified admin API object for convenient access
|
||||
*/
|
||||
export const adminAPI = {
|
||||
dashboard: dashboardAPI,
|
||||
users: usersAPI,
|
||||
groups: groupsAPI,
|
||||
accounts: accountsAPI,
|
||||
proxies: proxiesAPI,
|
||||
redeem: redeemAPI,
|
||||
promo: promoAPI,
|
||||
announcements: announcementsAPI,
|
||||
settings: settingsAPI,
|
||||
system: systemAPI,
|
||||
subscriptions: subscriptionsAPI,
|
||||
usage: usageAPI,
|
||||
gemini: geminiAPI,
|
||||
antigravity: antigravityAPI,
|
||||
userAttributes: userAttributesAPI,
|
||||
ops: opsAPI,
|
||||
errorPassthrough: errorPassthroughAPI,
|
||||
dataManagement: dataManagementAPI,
|
||||
apiKeys: apiKeysAPI,
|
||||
scheduledTests: scheduledTestsAPI,
|
||||
backup: backupAPI
|
||||
}
|
||||
|
||||
export {
|
||||
dashboardAPI,
|
||||
usersAPI,
|
||||
groupsAPI,
|
||||
accountsAPI,
|
||||
proxiesAPI,
|
||||
redeemAPI,
|
||||
promoAPI,
|
||||
announcementsAPI,
|
||||
settingsAPI,
|
||||
systemAPI,
|
||||
subscriptionsAPI,
|
||||
usageAPI,
|
||||
geminiAPI,
|
||||
antigravityAPI,
|
||||
userAttributesAPI,
|
||||
opsAPI,
|
||||
errorPassthroughAPI,
|
||||
dataManagementAPI,
|
||||
apiKeysAPI,
|
||||
scheduledTestsAPI,
|
||||
backupAPI
|
||||
}
|
||||
|
||||
export default adminAPI
|
||||
|
||||
// Re-export types used by components
|
||||
export type { BalanceHistoryItem } from './users'
|
||||
export type { ErrorPassthroughRule, CreateRuleRequest, UpdateRuleRequest } from './errorPassthrough'
|
||||
export type { BackupAgentHealth, DataManagementConfig } from './dataManagement'
|
||||
1417
llm-gateway-competitors/sub2api-tar/frontend/src/api/admin/ops.ts
Normal file
1417
llm-gateway-competitors/sub2api-tar/frontend/src/api/admin/ops.ts
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Admin Promo Codes API endpoints
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
PromoCode,
|
||||
PromoCodeUsage,
|
||||
CreatePromoCodeRequest,
|
||||
UpdatePromoCodeRequest,
|
||||
BasePaginationResponse
|
||||
} from '@/types'
|
||||
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
status?: string
|
||||
search?: string
|
||||
}
|
||||
): Promise<BasePaginationResponse<PromoCode>> {
|
||||
const { data } = await apiClient.get<BasePaginationResponse<PromoCode>>('/admin/promo-codes', {
|
||||
params: { page, page_size: pageSize, ...filters }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getById(id: number): Promise<PromoCode> {
|
||||
const { data } = await apiClient.get<PromoCode>(`/admin/promo-codes/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function create(request: CreatePromoCodeRequest): Promise<PromoCode> {
|
||||
const { data } = await apiClient.post<PromoCode>('/admin/promo-codes', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function update(id: number, request: UpdatePromoCodeRequest): Promise<PromoCode> {
|
||||
const { data } = await apiClient.put<PromoCode>(`/admin/promo-codes/${id}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteCode(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/promo-codes/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function getUsages(
|
||||
id: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
): Promise<BasePaginationResponse<PromoCodeUsage>> {
|
||||
const { data } = await apiClient.get<BasePaginationResponse<PromoCodeUsage>>(
|
||||
`/admin/promo-codes/${id}/usages`,
|
||||
{ params: { page, page_size: pageSize } }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
const promoAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteCode,
|
||||
getUsages
|
||||
}
|
||||
|
||||
export default promoAPI
|
||||
@@ -0,0 +1,271 @@
|
||||
/**
|
||||
* Admin Proxies API endpoints
|
||||
* Handles proxy server management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
Proxy,
|
||||
ProxyAccountSummary,
|
||||
ProxyQualityCheckResult,
|
||||
CreateProxyRequest,
|
||||
UpdateProxyRequest,
|
||||
PaginatedResponse,
|
||||
AdminDataPayload,
|
||||
AdminDataImportResult
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* List all proxies with pagination
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param filters - Optional filters
|
||||
* @returns Paginated list of proxies
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
protocol?: string
|
||||
status?: 'active' | 'inactive'
|
||||
search?: string
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<Proxy>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<Proxy>>('/admin/proxies', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters
|
||||
},
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active proxies (without pagination)
|
||||
* @returns List of all active proxies
|
||||
*/
|
||||
export async function getAll(): Promise<Proxy[]> {
|
||||
const { data } = await apiClient.get<Proxy[]>('/admin/proxies/all')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all active proxies with account count (sorted by creation time desc)
|
||||
* @returns List of all active proxies with account count
|
||||
*/
|
||||
export async function getAllWithCount(): Promise<Proxy[]> {
|
||||
const { data } = await apiClient.get<Proxy[]>('/admin/proxies/all', {
|
||||
params: { with_count: 'true' }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy by ID
|
||||
* @param id - Proxy ID
|
||||
* @returns Proxy details
|
||||
*/
|
||||
export async function getById(id: number): Promise<Proxy> {
|
||||
const { data } = await apiClient.get<Proxy>(`/admin/proxies/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new proxy
|
||||
* @param proxyData - Proxy data
|
||||
* @returns Created proxy
|
||||
*/
|
||||
export async function create(proxyData: CreateProxyRequest): Promise<Proxy> {
|
||||
const { data } = await apiClient.post<Proxy>('/admin/proxies', proxyData)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update proxy
|
||||
* @param id - Proxy ID
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated proxy
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateProxyRequest): Promise<Proxy> {
|
||||
const { data } = await apiClient.put<Proxy>(`/admin/proxies/${id}`, updates)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete proxy
|
||||
* @param id - Proxy ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteProxy(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/proxies/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle proxy status
|
||||
* @param id - Proxy ID
|
||||
* @param status - New status
|
||||
* @returns Updated proxy
|
||||
*/
|
||||
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<Proxy> {
|
||||
return update(id, { status })
|
||||
}
|
||||
|
||||
/**
|
||||
* Test proxy connectivity
|
||||
* @param id - Proxy ID
|
||||
* @returns Test result with IP info
|
||||
*/
|
||||
export async function testProxy(id: number): Promise<{
|
||||
success: boolean
|
||||
message: string
|
||||
latency_ms?: number
|
||||
ip_address?: string
|
||||
city?: string
|
||||
region?: string
|
||||
country?: string
|
||||
country_code?: string
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
success: boolean
|
||||
message: string
|
||||
latency_ms?: number
|
||||
ip_address?: string
|
||||
city?: string
|
||||
region?: string
|
||||
country?: string
|
||||
country_code?: string
|
||||
}>(`/admin/proxies/${id}/test`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Check proxy quality across common AI targets
|
||||
* @param id - Proxy ID
|
||||
* @returns Quality check result
|
||||
*/
|
||||
export async function checkProxyQuality(id: number): Promise<ProxyQualityCheckResult> {
|
||||
const { data } = await apiClient.post<ProxyQualityCheckResult>(`/admin/proxies/${id}/quality-check`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy usage statistics
|
||||
* @param id - Proxy ID
|
||||
* @returns Proxy usage statistics
|
||||
*/
|
||||
export async function getStats(id: number): Promise<{
|
||||
total_accounts: number
|
||||
active_accounts: number
|
||||
total_requests: number
|
||||
success_rate: number
|
||||
average_latency: number
|
||||
}> {
|
||||
const { data } = await apiClient.get<{
|
||||
total_accounts: number
|
||||
active_accounts: number
|
||||
total_requests: number
|
||||
success_rate: number
|
||||
average_latency: number
|
||||
}>(`/admin/proxies/${id}/stats`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get accounts using a proxy
|
||||
* @param id - Proxy ID
|
||||
* @returns List of accounts using the proxy
|
||||
*/
|
||||
export async function getProxyAccounts(id: number): Promise<ProxyAccountSummary[]> {
|
||||
const { data } = await apiClient.get<ProxyAccountSummary[]>(`/admin/proxies/${id}/accounts`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch create proxies
|
||||
* @param proxies - Array of proxy data to create
|
||||
* @returns Creation result with count of created and skipped
|
||||
*/
|
||||
export async function batchCreate(
|
||||
proxies: Array<{
|
||||
protocol: string
|
||||
host: string
|
||||
port: number
|
||||
username?: string
|
||||
password?: string
|
||||
}>
|
||||
): Promise<{
|
||||
created: number
|
||||
skipped: number
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
created: number
|
||||
skipped: number
|
||||
}>('/admin/proxies/batch', { proxies })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function batchDelete(ids: number[]): Promise<{
|
||||
deleted_ids: number[]
|
||||
skipped: Array<{ id: number; reason: string }>
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
deleted_ids: number[]
|
||||
skipped: Array<{ id: number; reason: string }>
|
||||
}>('/admin/proxies/batch-delete', { ids })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function exportData(options?: {
|
||||
ids?: number[]
|
||||
filters?: {
|
||||
protocol?: string
|
||||
status?: 'active' | 'inactive'
|
||||
search?: string
|
||||
}
|
||||
}): Promise<AdminDataPayload> {
|
||||
const params: Record<string, string> = {}
|
||||
if (options?.ids && options.ids.length > 0) {
|
||||
params.ids = options.ids.join(',')
|
||||
} else if (options?.filters) {
|
||||
const { protocol, status, search } = options.filters
|
||||
if (protocol) params.protocol = protocol
|
||||
if (status) params.status = status
|
||||
if (search) params.search = search
|
||||
}
|
||||
const { data } = await apiClient.get<AdminDataPayload>('/admin/proxies/data', { params })
|
||||
return data
|
||||
}
|
||||
|
||||
export async function importData(payload: {
|
||||
data: AdminDataPayload
|
||||
}): Promise<AdminDataImportResult> {
|
||||
const { data } = await apiClient.post<AdminDataImportResult>('/admin/proxies/data', payload)
|
||||
return data
|
||||
}
|
||||
|
||||
export const proxiesAPI = {
|
||||
list,
|
||||
getAll,
|
||||
getAllWithCount,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteProxy,
|
||||
toggleStatus,
|
||||
testProxy,
|
||||
checkProxyQuality,
|
||||
getStats,
|
||||
getProxyAccounts,
|
||||
batchCreate,
|
||||
batchDelete,
|
||||
exportData,
|
||||
importData
|
||||
}
|
||||
|
||||
export default proxiesAPI
|
||||
@@ -0,0 +1,174 @@
|
||||
/**
|
||||
* Admin Redeem Codes API endpoints
|
||||
* Handles redeem code generation and management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
RedeemCode,
|
||||
GenerateRedeemCodesRequest,
|
||||
RedeemCodeType,
|
||||
PaginatedResponse
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* List all redeem codes with pagination
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param filters - Optional filters
|
||||
* @returns Paginated list of redeem codes
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
type?: RedeemCodeType
|
||||
status?: 'active' | 'used' | 'expired' | 'unused'
|
||||
search?: string
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<RedeemCode>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<RedeemCode>>('/admin/redeem-codes', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters
|
||||
},
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redeem code by ID
|
||||
* @param id - Redeem code ID
|
||||
* @returns Redeem code details
|
||||
*/
|
||||
export async function getById(id: number): Promise<RedeemCode> {
|
||||
const { data } = await apiClient.get<RedeemCode>(`/admin/redeem-codes/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate new redeem codes
|
||||
* @param count - Number of codes to generate
|
||||
* @param type - Type of redeem code
|
||||
* @param value - Value of the code
|
||||
* @param groupId - Group ID (required for subscription type)
|
||||
* @param validityDays - Validity days (for subscription type)
|
||||
* @returns Array of generated redeem codes
|
||||
*/
|
||||
export async function generate(
|
||||
count: number,
|
||||
type: RedeemCodeType,
|
||||
value: number,
|
||||
groupId?: number | null,
|
||||
validityDays?: number
|
||||
): Promise<RedeemCode[]> {
|
||||
const payload: GenerateRedeemCodesRequest = {
|
||||
count,
|
||||
type,
|
||||
value
|
||||
}
|
||||
|
||||
// 订阅类型专用字段
|
||||
if (type === 'subscription') {
|
||||
payload.group_id = groupId
|
||||
if (validityDays && validityDays > 0) {
|
||||
payload.validity_days = validityDays
|
||||
}
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post<RedeemCode[]>('/admin/redeem-codes/generate', payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete redeem code
|
||||
* @param id - Redeem code ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteCode(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/redeem-codes/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch delete redeem codes
|
||||
* @param ids - Array of redeem code IDs
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function batchDelete(ids: number[]): Promise<{
|
||||
deleted: number
|
||||
message: string
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
deleted: number
|
||||
message: string
|
||||
}>('/admin/redeem-codes/batch-delete', { ids })
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Expire redeem code
|
||||
* @param id - Redeem code ID
|
||||
* @returns Updated redeem code
|
||||
*/
|
||||
export async function expire(id: number): Promise<RedeemCode> {
|
||||
const { data } = await apiClient.post<RedeemCode>(`/admin/redeem-codes/${id}/expire`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get redeem code statistics
|
||||
* @returns Statistics about redeem codes
|
||||
*/
|
||||
export async function getStats(): Promise<{
|
||||
total_codes: number
|
||||
active_codes: number
|
||||
used_codes: number
|
||||
expired_codes: number
|
||||
total_value_distributed: number
|
||||
by_type: Record<RedeemCodeType, number>
|
||||
}> {
|
||||
const { data } = await apiClient.get<{
|
||||
total_codes: number
|
||||
active_codes: number
|
||||
used_codes: number
|
||||
expired_codes: number
|
||||
total_value_distributed: number
|
||||
by_type: Record<RedeemCodeType, number>
|
||||
}>('/admin/redeem-codes/stats')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Export redeem codes to CSV
|
||||
* @param filters - Optional filters
|
||||
* @returns CSV data as blob
|
||||
*/
|
||||
export async function exportCodes(filters?: {
|
||||
type?: RedeemCodeType
|
||||
status?: 'active' | 'used' | 'expired'
|
||||
}): Promise<Blob> {
|
||||
const response = await apiClient.get('/admin/redeem-codes/export', {
|
||||
params: filters,
|
||||
responseType: 'blob'
|
||||
})
|
||||
return response.data
|
||||
}
|
||||
|
||||
export const redeemAPI = {
|
||||
list,
|
||||
getById,
|
||||
generate,
|
||||
delete: deleteCode,
|
||||
batchDelete,
|
||||
expire,
|
||||
getStats,
|
||||
exportCodes
|
||||
}
|
||||
|
||||
export default redeemAPI
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Admin Scheduled Tests API endpoints
|
||||
* Handles scheduled test plan management for account connectivity monitoring
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
ScheduledTestPlan,
|
||||
ScheduledTestResult,
|
||||
CreateScheduledTestPlanRequest,
|
||||
UpdateScheduledTestPlanRequest
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* List all scheduled test plans for an account
|
||||
* @param accountId - Account ID
|
||||
* @returns List of scheduled test plans
|
||||
*/
|
||||
export async function listByAccount(accountId: number): Promise<ScheduledTestPlan[]> {
|
||||
const { data } = await apiClient.get<ScheduledTestPlan[]>(
|
||||
`/admin/accounts/${accountId}/scheduled-test-plans`
|
||||
)
|
||||
return data ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new scheduled test plan
|
||||
* @param req - Plan creation request
|
||||
* @returns Created plan
|
||||
*/
|
||||
export async function create(req: CreateScheduledTestPlanRequest): Promise<ScheduledTestPlan> {
|
||||
const { data } = await apiClient.post<ScheduledTestPlan>(
|
||||
'/admin/scheduled-test-plans',
|
||||
req
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing scheduled test plan
|
||||
* @param id - Plan ID
|
||||
* @param req - Fields to update
|
||||
* @returns Updated plan
|
||||
*/
|
||||
export async function update(id: number, req: UpdateScheduledTestPlanRequest): Promise<ScheduledTestPlan> {
|
||||
const { data } = await apiClient.put<ScheduledTestPlan>(
|
||||
`/admin/scheduled-test-plans/${id}`,
|
||||
req
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a scheduled test plan
|
||||
* @param id - Plan ID
|
||||
*/
|
||||
export async function deletePlan(id: number): Promise<void> {
|
||||
await apiClient.delete(`/admin/scheduled-test-plans/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* List test results for a plan
|
||||
* @param planId - Plan ID
|
||||
* @param limit - Optional max number of results to return
|
||||
* @returns List of test results
|
||||
*/
|
||||
export async function listResults(planId: number, limit?: number): Promise<ScheduledTestResult[]> {
|
||||
const { data } = await apiClient.get<ScheduledTestResult[]>(
|
||||
`/admin/scheduled-test-plans/${planId}/results`,
|
||||
{
|
||||
params: limit ? { limit } : undefined
|
||||
}
|
||||
)
|
||||
return data ?? []
|
||||
}
|
||||
|
||||
export const scheduledTestsAPI = {
|
||||
listByAccount,
|
||||
create,
|
||||
update,
|
||||
delete: deletePlan,
|
||||
listResults
|
||||
}
|
||||
|
||||
export default scheduledTestsAPI
|
||||
@@ -0,0 +1,518 @@
|
||||
/**
|
||||
* Admin Settings API endpoints
|
||||
* Handles system settings management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { CustomMenuItem } from '@/types'
|
||||
|
||||
export interface DefaultSubscriptionSetting {
|
||||
group_id: number
|
||||
validity_days: number
|
||||
}
|
||||
|
||||
/**
|
||||
* System settings interface
|
||||
*/
|
||||
export interface SystemSettings {
|
||||
// Registration settings
|
||||
registration_enabled: boolean
|
||||
email_verify_enabled: boolean
|
||||
registration_email_suffix_whitelist: string[]
|
||||
promo_code_enabled: boolean
|
||||
password_reset_enabled: boolean
|
||||
frontend_url: string
|
||||
invitation_code_enabled: boolean
|
||||
totp_enabled: boolean // TOTP 双因素认证
|
||||
totp_encryption_key_configured: boolean // TOTP 加密密钥是否已配置
|
||||
// Default settings
|
||||
default_balance: number
|
||||
default_concurrency: number
|
||||
default_subscriptions: DefaultSubscriptionSetting[]
|
||||
// OEM settings
|
||||
site_name: string
|
||||
site_logo: string
|
||||
site_subtitle: string
|
||||
api_base_url: string
|
||||
contact_info: string
|
||||
doc_url: string
|
||||
home_content: string
|
||||
hide_ccs_import_button: boolean
|
||||
purchase_subscription_enabled: boolean
|
||||
purchase_subscription_url: string
|
||||
sora_client_enabled: boolean
|
||||
backend_mode_enabled: boolean
|
||||
custom_menu_items: CustomMenuItem[]
|
||||
// SMTP settings
|
||||
smtp_host: string
|
||||
smtp_port: number
|
||||
smtp_username: string
|
||||
smtp_password_configured: boolean
|
||||
smtp_from_email: string
|
||||
smtp_from_name: string
|
||||
smtp_use_tls: boolean
|
||||
// Cloudflare Turnstile settings
|
||||
turnstile_enabled: boolean
|
||||
turnstile_site_key: string
|
||||
turnstile_secret_key_configured: boolean
|
||||
|
||||
// LinuxDo Connect OAuth settings
|
||||
linuxdo_connect_enabled: boolean
|
||||
linuxdo_connect_client_id: string
|
||||
linuxdo_connect_client_secret_configured: boolean
|
||||
linuxdo_connect_redirect_url: string
|
||||
|
||||
// Model fallback configuration
|
||||
enable_model_fallback: boolean
|
||||
fallback_model_anthropic: string
|
||||
fallback_model_openai: string
|
||||
fallback_model_gemini: string
|
||||
fallback_model_antigravity: string
|
||||
|
||||
// Identity patch configuration (Claude -> Gemini)
|
||||
enable_identity_patch: boolean
|
||||
identity_patch_prompt: string
|
||||
|
||||
// Ops Monitoring (vNext)
|
||||
ops_monitoring_enabled: boolean
|
||||
ops_realtime_monitoring_enabled: boolean
|
||||
ops_query_mode_default: 'auto' | 'raw' | 'preagg' | string
|
||||
ops_metrics_interval_seconds: number
|
||||
|
||||
// Claude Code version check
|
||||
min_claude_code_version: string
|
||||
|
||||
// 分组隔离
|
||||
allow_ungrouped_key_scheduling: boolean
|
||||
}
|
||||
|
||||
export interface UpdateSettingsRequest {
|
||||
registration_enabled?: boolean
|
||||
email_verify_enabled?: boolean
|
||||
registration_email_suffix_whitelist?: string[]
|
||||
promo_code_enabled?: boolean
|
||||
password_reset_enabled?: boolean
|
||||
frontend_url?: string
|
||||
invitation_code_enabled?: boolean
|
||||
totp_enabled?: boolean // TOTP 双因素认证
|
||||
default_balance?: number
|
||||
default_concurrency?: number
|
||||
default_subscriptions?: DefaultSubscriptionSetting[]
|
||||
site_name?: string
|
||||
site_logo?: string
|
||||
site_subtitle?: string
|
||||
api_base_url?: string
|
||||
contact_info?: string
|
||||
doc_url?: string
|
||||
home_content?: string
|
||||
hide_ccs_import_button?: boolean
|
||||
purchase_subscription_enabled?: boolean
|
||||
purchase_subscription_url?: string
|
||||
sora_client_enabled?: boolean
|
||||
backend_mode_enabled?: boolean
|
||||
custom_menu_items?: CustomMenuItem[]
|
||||
smtp_host?: string
|
||||
smtp_port?: number
|
||||
smtp_username?: string
|
||||
smtp_password?: string
|
||||
smtp_from_email?: string
|
||||
smtp_from_name?: string
|
||||
smtp_use_tls?: boolean
|
||||
turnstile_enabled?: boolean
|
||||
turnstile_site_key?: string
|
||||
turnstile_secret_key?: string
|
||||
linuxdo_connect_enabled?: boolean
|
||||
linuxdo_connect_client_id?: string
|
||||
linuxdo_connect_client_secret?: string
|
||||
linuxdo_connect_redirect_url?: string
|
||||
enable_model_fallback?: boolean
|
||||
fallback_model_anthropic?: string
|
||||
fallback_model_openai?: string
|
||||
fallback_model_gemini?: string
|
||||
fallback_model_antigravity?: string
|
||||
enable_identity_patch?: boolean
|
||||
identity_patch_prompt?: string
|
||||
ops_monitoring_enabled?: boolean
|
||||
ops_realtime_monitoring_enabled?: boolean
|
||||
ops_query_mode_default?: 'auto' | 'raw' | 'preagg' | string
|
||||
ops_metrics_interval_seconds?: number
|
||||
min_claude_code_version?: string
|
||||
allow_ungrouped_key_scheduling?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all system settings
|
||||
* @returns System settings
|
||||
*/
|
||||
export async function getSettings(): Promise<SystemSettings> {
|
||||
const { data } = await apiClient.get<SystemSettings>('/admin/settings')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update system settings
|
||||
* @param settings - Partial settings to update
|
||||
* @returns Updated settings
|
||||
*/
|
||||
export async function updateSettings(settings: UpdateSettingsRequest): Promise<SystemSettings> {
|
||||
const { data } = await apiClient.put<SystemSettings>('/admin/settings', settings)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SMTP connection request
|
||||
*/
|
||||
export interface TestSmtpRequest {
|
||||
smtp_host: string
|
||||
smtp_port: number
|
||||
smtp_username: string
|
||||
smtp_password: string
|
||||
smtp_use_tls: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Test SMTP connection with provided config
|
||||
* @param config - SMTP configuration to test
|
||||
* @returns Test result message
|
||||
*/
|
||||
export async function testSmtpConnection(config: TestSmtpRequest): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.post<{ message: string }>('/admin/settings/test-smtp', config)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email request
|
||||
*/
|
||||
export interface SendTestEmailRequest {
|
||||
email: string
|
||||
smtp_host: string
|
||||
smtp_port: number
|
||||
smtp_username: string
|
||||
smtp_password: string
|
||||
smtp_from_email: string
|
||||
smtp_from_name: string
|
||||
smtp_use_tls: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Send test email with provided SMTP config
|
||||
* @param request - Email address and SMTP config
|
||||
* @returns Test result message
|
||||
*/
|
||||
export async function sendTestEmail(request: SendTestEmailRequest): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.post<{ message: string }>(
|
||||
'/admin/settings/send-test-email',
|
||||
request
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Admin API Key status response
|
||||
*/
|
||||
export interface AdminApiKeyStatus {
|
||||
exists: boolean
|
||||
masked_key: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Get admin API key status
|
||||
* @returns Status indicating if key exists and masked version
|
||||
*/
|
||||
export async function getAdminApiKey(): Promise<AdminApiKeyStatus> {
|
||||
const { data } = await apiClient.get<AdminApiKeyStatus>('/admin/settings/admin-api-key')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Regenerate admin API key
|
||||
* @returns The new full API key (only shown once)
|
||||
*/
|
||||
export async function regenerateAdminApiKey(): Promise<{ key: string }> {
|
||||
const { data } = await apiClient.post<{ key: string }>('/admin/settings/admin-api-key/regenerate')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete admin API key
|
||||
* @returns Success message
|
||||
*/
|
||||
export async function deleteAdminApiKey(): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>('/admin/settings/admin-api-key')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Stream timeout settings interface
|
||||
*/
|
||||
export interface StreamTimeoutSettings {
|
||||
enabled: boolean
|
||||
action: 'temp_unsched' | 'error' | 'none'
|
||||
temp_unsched_minutes: number
|
||||
threshold_count: number
|
||||
threshold_window_minutes: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream timeout settings
|
||||
* @returns Stream timeout settings
|
||||
*/
|
||||
export async function getStreamTimeoutSettings(): Promise<StreamTimeoutSettings> {
|
||||
const { data } = await apiClient.get<StreamTimeoutSettings>('/admin/settings/stream-timeout')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update stream timeout settings
|
||||
* @param settings - Stream timeout settings to update
|
||||
* @returns Updated settings
|
||||
*/
|
||||
export async function updateStreamTimeoutSettings(
|
||||
settings: StreamTimeoutSettings
|
||||
): Promise<StreamTimeoutSettings> {
|
||||
const { data } = await apiClient.put<StreamTimeoutSettings>(
|
||||
'/admin/settings/stream-timeout',
|
||||
settings
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
// ==================== Rectifier Settings ====================
|
||||
|
||||
/**
|
||||
* Rectifier settings interface
|
||||
*/
|
||||
export interface RectifierSettings {
|
||||
enabled: boolean
|
||||
thinking_signature_enabled: boolean
|
||||
thinking_budget_enabled: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Get rectifier settings
|
||||
* @returns Rectifier settings
|
||||
*/
|
||||
export async function getRectifierSettings(): Promise<RectifierSettings> {
|
||||
const { data } = await apiClient.get<RectifierSettings>('/admin/settings/rectifier')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update rectifier settings
|
||||
* @param settings - Rectifier settings to update
|
||||
* @returns Updated settings
|
||||
*/
|
||||
export async function updateRectifierSettings(
|
||||
settings: RectifierSettings
|
||||
): Promise<RectifierSettings> {
|
||||
const { data } = await apiClient.put<RectifierSettings>(
|
||||
'/admin/settings/rectifier',
|
||||
settings
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
// ==================== Beta Policy Settings ====================
|
||||
|
||||
/**
|
||||
* Beta policy rule interface
|
||||
*/
|
||||
export interface BetaPolicyRule {
|
||||
beta_token: string
|
||||
action: 'pass' | 'filter' | 'block'
|
||||
scope: 'all' | 'oauth' | 'apikey' | 'bedrock'
|
||||
error_message?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Beta policy settings interface
|
||||
*/
|
||||
export interface BetaPolicySettings {
|
||||
rules: BetaPolicyRule[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Get beta policy settings
|
||||
* @returns Beta policy settings
|
||||
*/
|
||||
export async function getBetaPolicySettings(): Promise<BetaPolicySettings> {
|
||||
const { data } = await apiClient.get<BetaPolicySettings>('/admin/settings/beta-policy')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update beta policy settings
|
||||
* @param settings - Beta policy settings to update
|
||||
* @returns Updated settings
|
||||
*/
|
||||
export async function updateBetaPolicySettings(
|
||||
settings: BetaPolicySettings
|
||||
): Promise<BetaPolicySettings> {
|
||||
const { data } = await apiClient.put<BetaPolicySettings>(
|
||||
'/admin/settings/beta-policy',
|
||||
settings
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
// ==================== Sora S3 Settings ====================
|
||||
|
||||
export interface SoraS3Settings {
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key_configured: boolean
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
cdn_url: string
|
||||
default_storage_quota_bytes: number
|
||||
}
|
||||
|
||||
export interface SoraS3Profile {
|
||||
profile_id: string
|
||||
name: string
|
||||
is_active: boolean
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key_configured: boolean
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
cdn_url: string
|
||||
default_storage_quota_bytes: number
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface ListSoraS3ProfilesResponse {
|
||||
active_profile_id: string
|
||||
items: SoraS3Profile[]
|
||||
}
|
||||
|
||||
export interface UpdateSoraS3SettingsRequest {
|
||||
profile_id?: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
cdn_url: string
|
||||
default_storage_quota_bytes: number
|
||||
}
|
||||
|
||||
export interface CreateSoraS3ProfileRequest {
|
||||
profile_id: string
|
||||
name: string
|
||||
set_active?: boolean
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
cdn_url: string
|
||||
default_storage_quota_bytes: number
|
||||
}
|
||||
|
||||
export interface UpdateSoraS3ProfileRequest {
|
||||
name: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
cdn_url: string
|
||||
default_storage_quota_bytes: number
|
||||
}
|
||||
|
||||
export interface TestSoraS3ConnectionRequest {
|
||||
profile_id?: string
|
||||
enabled: boolean
|
||||
endpoint: string
|
||||
region: string
|
||||
bucket: string
|
||||
access_key_id: string
|
||||
secret_access_key?: string
|
||||
prefix: string
|
||||
force_path_style: boolean
|
||||
cdn_url: string
|
||||
default_storage_quota_bytes?: number
|
||||
}
|
||||
|
||||
export async function getSoraS3Settings(): Promise<SoraS3Settings> {
|
||||
const { data } = await apiClient.get<SoraS3Settings>('/admin/settings/sora-s3')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateSoraS3Settings(settings: UpdateSoraS3SettingsRequest): Promise<SoraS3Settings> {
|
||||
const { data } = await apiClient.put<SoraS3Settings>('/admin/settings/sora-s3', settings)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function testSoraS3Connection(
|
||||
settings: TestSoraS3ConnectionRequest
|
||||
): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.post<{ message: string }>('/admin/settings/sora-s3/test', settings)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function listSoraS3Profiles(): Promise<ListSoraS3ProfilesResponse> {
|
||||
const { data } = await apiClient.get<ListSoraS3ProfilesResponse>('/admin/settings/sora-s3/profiles')
|
||||
return data
|
||||
}
|
||||
|
||||
export async function createSoraS3Profile(request: CreateSoraS3ProfileRequest): Promise<SoraS3Profile> {
|
||||
const { data } = await apiClient.post<SoraS3Profile>('/admin/settings/sora-s3/profiles', request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function updateSoraS3Profile(profileID: string, request: UpdateSoraS3ProfileRequest): Promise<SoraS3Profile> {
|
||||
const { data } = await apiClient.put<SoraS3Profile>(`/admin/settings/sora-s3/profiles/${profileID}`, request)
|
||||
return data
|
||||
}
|
||||
|
||||
export async function deleteSoraS3Profile(profileID: string): Promise<void> {
|
||||
await apiClient.delete(`/admin/settings/sora-s3/profiles/${profileID}`)
|
||||
}
|
||||
|
||||
export async function setActiveSoraS3Profile(profileID: string): Promise<SoraS3Profile> {
|
||||
const { data } = await apiClient.post<SoraS3Profile>(`/admin/settings/sora-s3/profiles/${profileID}/activate`)
|
||||
return data
|
||||
}
|
||||
|
||||
export const settingsAPI = {
|
||||
getSettings,
|
||||
updateSettings,
|
||||
testSmtpConnection,
|
||||
sendTestEmail,
|
||||
getAdminApiKey,
|
||||
regenerateAdminApiKey,
|
||||
deleteAdminApiKey,
|
||||
getStreamTimeoutSettings,
|
||||
updateStreamTimeoutSettings,
|
||||
getRectifierSettings,
|
||||
updateRectifierSettings,
|
||||
getBetaPolicySettings,
|
||||
updateBetaPolicySettings,
|
||||
getSoraS3Settings,
|
||||
updateSoraS3Settings,
|
||||
testSoraS3Connection,
|
||||
listSoraS3Profiles,
|
||||
createSoraS3Profile,
|
||||
updateSoraS3Profile,
|
||||
deleteSoraS3Profile,
|
||||
setActiveSoraS3Profile
|
||||
}
|
||||
|
||||
export default settingsAPI
|
||||
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Admin Subscriptions API endpoints
|
||||
* Handles user subscription management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
UserSubscription,
|
||||
SubscriptionProgress,
|
||||
AssignSubscriptionRequest,
|
||||
BulkAssignSubscriptionRequest,
|
||||
ExtendSubscriptionRequest,
|
||||
PaginatedResponse
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* List all subscriptions with pagination
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param filters - Optional filters (status, user_id, group_id, sort_by, sort_order)
|
||||
* @returns Paginated list of subscriptions
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
status?: 'active' | 'expired' | 'revoked'
|
||||
user_id?: number
|
||||
group_id?: number
|
||||
sort_by?: string
|
||||
sort_order?: 'asc' | 'desc'
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<UserSubscription>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UserSubscription>>(
|
||||
'/admin/subscriptions',
|
||||
{
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters
|
||||
},
|
||||
signal: options?.signal
|
||||
}
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription by ID
|
||||
* @param id - Subscription ID
|
||||
* @returns Subscription details
|
||||
*/
|
||||
export async function getById(id: number): Promise<UserSubscription> {
|
||||
const { data } = await apiClient.get<UserSubscription>(`/admin/subscriptions/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription progress
|
||||
* @param id - Subscription ID
|
||||
* @returns Subscription progress with usage stats
|
||||
*/
|
||||
export async function getProgress(id: number): Promise<SubscriptionProgress> {
|
||||
const { data } = await apiClient.get<SubscriptionProgress>(`/admin/subscriptions/${id}/progress`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Assign subscription to user
|
||||
* @param request - Assignment request
|
||||
* @returns Created subscription
|
||||
*/
|
||||
export async function assign(request: AssignSubscriptionRequest): Promise<UserSubscription> {
|
||||
const { data } = await apiClient.post<UserSubscription>('/admin/subscriptions/assign', request)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Bulk assign subscriptions to multiple users
|
||||
* @param request - Bulk assignment request
|
||||
* @returns Created subscriptions
|
||||
*/
|
||||
export async function bulkAssign(
|
||||
request: BulkAssignSubscriptionRequest
|
||||
): Promise<UserSubscription[]> {
|
||||
const { data } = await apiClient.post<UserSubscription[]>(
|
||||
'/admin/subscriptions/bulk-assign',
|
||||
request
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend subscription validity
|
||||
* @param id - Subscription ID
|
||||
* @param request - Extension request with days
|
||||
* @returns Updated subscription
|
||||
*/
|
||||
export async function extend(
|
||||
id: number,
|
||||
request: ExtendSubscriptionRequest
|
||||
): Promise<UserSubscription> {
|
||||
const { data } = await apiClient.post<UserSubscription>(
|
||||
`/admin/subscriptions/${id}/extend`,
|
||||
request
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Revoke subscription
|
||||
* @param id - Subscription ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function revoke(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/subscriptions/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Reset daily, weekly, and/or monthly usage quota for a subscription
|
||||
* @param id - Subscription ID
|
||||
* @param options - Which windows to reset
|
||||
* @returns Updated subscription
|
||||
*/
|
||||
export async function resetQuota(
|
||||
id: number,
|
||||
options: { daily: boolean; weekly: boolean; monthly: boolean }
|
||||
): Promise<UserSubscription> {
|
||||
const { data } = await apiClient.post<UserSubscription>(
|
||||
`/admin/subscriptions/${id}/reset-quota`,
|
||||
options
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* List subscriptions by group
|
||||
* @param groupId - Group ID
|
||||
* @param page - Page number
|
||||
* @param pageSize - Items per page
|
||||
* @returns Paginated list of subscriptions in the group
|
||||
*/
|
||||
export async function listByGroup(
|
||||
groupId: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
): Promise<PaginatedResponse<UserSubscription>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UserSubscription>>(
|
||||
`/admin/groups/${groupId}/subscriptions`,
|
||||
{
|
||||
params: { page, page_size: pageSize }
|
||||
}
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* List subscriptions by user
|
||||
* @param userId - User ID
|
||||
* @param page - Page number
|
||||
* @param pageSize - Items per page
|
||||
* @returns Paginated list of user's subscriptions
|
||||
*/
|
||||
export async function listByUser(
|
||||
userId: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 20
|
||||
): Promise<PaginatedResponse<UserSubscription>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UserSubscription>>(
|
||||
`/admin/users/${userId}/subscriptions`,
|
||||
{
|
||||
params: { page, page_size: pageSize }
|
||||
}
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const subscriptionsAPI = {
|
||||
list,
|
||||
getById,
|
||||
getProgress,
|
||||
assign,
|
||||
bulkAssign,
|
||||
extend,
|
||||
revoke,
|
||||
resetQuota,
|
||||
listByGroup,
|
||||
listByUser
|
||||
}
|
||||
|
||||
export default subscriptionsAPI
|
||||
@@ -0,0 +1,81 @@
|
||||
/**
|
||||
* System API endpoints for admin operations
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
|
||||
export interface ReleaseInfo {
|
||||
name: string
|
||||
body: string
|
||||
published_at: string
|
||||
html_url: string
|
||||
}
|
||||
|
||||
export interface VersionInfo {
|
||||
current_version: string
|
||||
latest_version: string
|
||||
has_update: boolean
|
||||
release_info?: ReleaseInfo
|
||||
cached: boolean
|
||||
warning?: string
|
||||
build_type: string // "source" for manual builds, "release" for CI builds
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current version
|
||||
*/
|
||||
export async function getVersion(): Promise<{ version: string }> {
|
||||
const { data } = await apiClient.get<{ version: string }>('/admin/system/version')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Check for updates
|
||||
* @param force - Force refresh from GitHub API
|
||||
*/
|
||||
export async function checkUpdates(force = false): Promise<VersionInfo> {
|
||||
const { data } = await apiClient.get<VersionInfo>('/admin/system/check-updates', {
|
||||
params: force ? { force: 'true' } : undefined
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
export interface UpdateResult {
|
||||
message: string
|
||||
need_restart: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform system update
|
||||
* Downloads and applies the latest version
|
||||
*/
|
||||
export async function performUpdate(): Promise<UpdateResult> {
|
||||
const { data } = await apiClient.post<UpdateResult>('/admin/system/update')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Rollback to previous version
|
||||
*/
|
||||
export async function rollback(): Promise<UpdateResult> {
|
||||
const { data } = await apiClient.post<UpdateResult>('/admin/system/rollback')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart the service
|
||||
*/
|
||||
export async function restartService(): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.post<{ message: string }>('/admin/system/restart')
|
||||
return data
|
||||
}
|
||||
|
||||
export const systemAPI = {
|
||||
getVersion,
|
||||
checkUpdates,
|
||||
performUpdate,
|
||||
rollback,
|
||||
restartService
|
||||
}
|
||||
|
||||
export default systemAPI
|
||||
@@ -0,0 +1,206 @@
|
||||
/**
|
||||
* Admin Usage API endpoints
|
||||
* Handles admin-level usage logs and statistics retrieval
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { AdminUsageLog, UsageQueryParams, PaginatedResponse, UsageRequestType } from '@/types'
|
||||
import type { EndpointStat } from '@/types'
|
||||
|
||||
// ==================== Types ====================
|
||||
|
||||
export interface AdminUsageStatsResponse {
|
||||
total_requests: number
|
||||
total_input_tokens: number
|
||||
total_output_tokens: number
|
||||
total_cache_tokens: number
|
||||
total_tokens: number
|
||||
total_cost: number
|
||||
total_actual_cost: number
|
||||
total_account_cost?: number
|
||||
average_duration_ms: number
|
||||
endpoints?: EndpointStat[]
|
||||
upstream_endpoints?: EndpointStat[]
|
||||
endpoint_paths?: EndpointStat[]
|
||||
}
|
||||
|
||||
export interface SimpleUser {
|
||||
id: number
|
||||
email: string
|
||||
}
|
||||
|
||||
export interface SimpleApiKey {
|
||||
id: number
|
||||
name: string
|
||||
user_id: number
|
||||
}
|
||||
|
||||
export interface UsageCleanupFilters {
|
||||
start_time: string
|
||||
end_time: string
|
||||
user_id?: number
|
||||
api_key_id?: number
|
||||
account_id?: number
|
||||
group_id?: number
|
||||
model?: string | null
|
||||
request_type?: UsageRequestType | null
|
||||
stream?: boolean | null
|
||||
billing_type?: number | null
|
||||
}
|
||||
|
||||
export interface UsageCleanupTask {
|
||||
id: number
|
||||
status: string
|
||||
filters: UsageCleanupFilters
|
||||
created_by: number
|
||||
deleted_rows: number
|
||||
error_message?: string | null
|
||||
canceled_by?: number | null
|
||||
canceled_at?: string | null
|
||||
started_at?: string | null
|
||||
finished_at?: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreateUsageCleanupTaskRequest {
|
||||
start_date: string
|
||||
end_date: string
|
||||
user_id?: number
|
||||
api_key_id?: number
|
||||
account_id?: number
|
||||
group_id?: number
|
||||
model?: string | null
|
||||
request_type?: UsageRequestType | null
|
||||
stream?: boolean | null
|
||||
billing_type?: number | null
|
||||
timezone?: string
|
||||
}
|
||||
|
||||
export interface AdminUsageQueryParams extends UsageQueryParams {
|
||||
user_id?: number
|
||||
exact_total?: boolean
|
||||
}
|
||||
|
||||
// ==================== API Functions ====================
|
||||
|
||||
/**
|
||||
* List all usage logs with optional filters (admin only)
|
||||
* @param params - Query parameters for filtering and pagination
|
||||
* @returns Paginated list of usage logs
|
||||
*/
|
||||
export async function list(
|
||||
params: AdminUsageQueryParams,
|
||||
options?: { signal?: AbortSignal }
|
||||
): Promise<PaginatedResponse<AdminUsageLog>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<AdminUsageLog>>('/admin/usage', {
|
||||
params,
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage statistics with optional filters (admin only)
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns Usage statistics
|
||||
*/
|
||||
export async function getStats(params: {
|
||||
user_id?: number
|
||||
api_key_id?: number
|
||||
account_id?: number
|
||||
group_id?: number
|
||||
model?: string
|
||||
request_type?: UsageRequestType
|
||||
stream?: boolean
|
||||
period?: string
|
||||
start_date?: string
|
||||
end_date?: string
|
||||
timezone?: string
|
||||
}): Promise<AdminUsageStatsResponse> {
|
||||
const { data } = await apiClient.get<AdminUsageStatsResponse>('/admin/usage/stats', {
|
||||
params
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Search users by email keyword (admin only)
|
||||
* @param keyword - Email keyword to search
|
||||
* @returns List of matching users (max 30)
|
||||
*/
|
||||
export async function searchUsers(keyword: string): Promise<SimpleUser[]> {
|
||||
const { data } = await apiClient.get<SimpleUser[]>('/admin/usage/search-users', {
|
||||
params: { q: keyword }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Search API keys by user ID and/or keyword (admin only)
|
||||
* @param userId - Optional user ID to filter by
|
||||
* @param keyword - Optional keyword to search in key name
|
||||
* @returns List of matching API keys (max 30)
|
||||
*/
|
||||
export async function searchApiKeys(userId?: number, keyword?: string): Promise<SimpleApiKey[]> {
|
||||
const params: Record<string, unknown> = {}
|
||||
if (userId !== undefined) {
|
||||
params.user_id = userId
|
||||
}
|
||||
if (keyword) {
|
||||
params.q = keyword
|
||||
}
|
||||
const { data } = await apiClient.get<SimpleApiKey[]>('/admin/usage/search-api-keys', {
|
||||
params
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* List usage cleanup tasks (admin only)
|
||||
* @param params - Query parameters for pagination
|
||||
* @returns Paginated list of cleanup tasks
|
||||
*/
|
||||
export async function listCleanupTasks(
|
||||
params: { page?: number; page_size?: number },
|
||||
options?: { signal?: AbortSignal }
|
||||
): Promise<PaginatedResponse<UsageCleanupTask>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UsageCleanupTask>>('/admin/usage/cleanup-tasks', {
|
||||
params,
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a usage cleanup task (admin only)
|
||||
* @param payload - Cleanup task parameters
|
||||
* @returns Created cleanup task
|
||||
*/
|
||||
export async function createCleanupTask(payload: CreateUsageCleanupTaskRequest): Promise<UsageCleanupTask> {
|
||||
const { data } = await apiClient.post<UsageCleanupTask>('/admin/usage/cleanup-tasks', payload)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Cancel a usage cleanup task (admin only)
|
||||
* @param taskId - Task ID to cancel
|
||||
*/
|
||||
export async function cancelCleanupTask(taskId: number): Promise<{ id: number; status: string }> {
|
||||
const { data } = await apiClient.post<{ id: number; status: string }>(
|
||||
`/admin/usage/cleanup-tasks/${taskId}/cancel`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const adminUsageAPI = {
|
||||
list,
|
||||
getStats,
|
||||
searchUsers,
|
||||
searchApiKeys,
|
||||
listCleanupTasks,
|
||||
createCleanupTask,
|
||||
cancelCleanupTask
|
||||
}
|
||||
|
||||
export default adminUsageAPI
|
||||
@@ -0,0 +1,131 @@
|
||||
/**
|
||||
* Admin User Attributes API endpoints
|
||||
* Handles user custom attribute definitions and values
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type {
|
||||
UserAttributeDefinition,
|
||||
UserAttributeValue,
|
||||
CreateUserAttributeRequest,
|
||||
UpdateUserAttributeRequest,
|
||||
UserAttributeValuesMap
|
||||
} from '@/types'
|
||||
|
||||
/**
|
||||
* Get all attribute definitions
|
||||
*/
|
||||
export async function listDefinitions(): Promise<UserAttributeDefinition[]> {
|
||||
const { data } = await apiClient.get<UserAttributeDefinition[]>('/admin/user-attributes')
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get enabled attribute definitions only
|
||||
*/
|
||||
export async function listEnabledDefinitions(): Promise<UserAttributeDefinition[]> {
|
||||
const { data } = await apiClient.get<UserAttributeDefinition[]>('/admin/user-attributes', {
|
||||
params: { enabled: true }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new attribute definition
|
||||
*/
|
||||
export async function createDefinition(
|
||||
request: CreateUserAttributeRequest
|
||||
): Promise<UserAttributeDefinition> {
|
||||
const { data } = await apiClient.post<UserAttributeDefinition>('/admin/user-attributes', request)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an attribute definition
|
||||
*/
|
||||
export async function updateDefinition(
|
||||
id: number,
|
||||
request: UpdateUserAttributeRequest
|
||||
): Promise<UserAttributeDefinition> {
|
||||
const { data } = await apiClient.put<UserAttributeDefinition>(
|
||||
`/admin/user-attributes/${id}`,
|
||||
request
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete an attribute definition
|
||||
*/
|
||||
export async function deleteDefinition(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/user-attributes/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder attribute definitions
|
||||
*/
|
||||
export async function reorderDefinitions(ids: number[]): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.put<{ message: string }>('/admin/user-attributes/reorder', {
|
||||
ids
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's attribute values
|
||||
*/
|
||||
export async function getUserAttributeValues(userId: number): Promise<UserAttributeValue[]> {
|
||||
const { data } = await apiClient.get<UserAttributeValue[]>(
|
||||
`/admin/users/${userId}/attributes`
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user's attribute values (batch)
|
||||
*/
|
||||
export async function updateUserAttributeValues(
|
||||
userId: number,
|
||||
values: UserAttributeValuesMap
|
||||
): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.put<{ message: string }>(
|
||||
`/admin/users/${userId}/attributes`,
|
||||
{ values }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch response type
|
||||
*/
|
||||
export interface BatchUserAttributesResponse {
|
||||
attributes: Record<number, Record<number, string>>
|
||||
}
|
||||
|
||||
/**
|
||||
* Get attribute values for multiple users
|
||||
*/
|
||||
export async function getBatchUserAttributes(
|
||||
userIds: number[]
|
||||
): Promise<BatchUserAttributesResponse> {
|
||||
const { data } = await apiClient.post<BatchUserAttributesResponse>(
|
||||
'/admin/user-attributes/batch',
|
||||
{ user_ids: userIds }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const userAttributesAPI = {
|
||||
listDefinitions,
|
||||
listEnabledDefinitions,
|
||||
createDefinition,
|
||||
updateDefinition,
|
||||
deleteDefinition,
|
||||
reorderDefinitions,
|
||||
getUserAttributeValues,
|
||||
updateUserAttributeValues,
|
||||
getBatchUserAttributes
|
||||
}
|
||||
|
||||
export default userAttributesAPI
|
||||
@@ -0,0 +1,240 @@
|
||||
/**
|
||||
* Admin Users API endpoints
|
||||
* Handles user management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client'
|
||||
import type { AdminUser, UpdateUserRequest, PaginatedResponse, ApiKey } from '@/types'
|
||||
|
||||
/**
|
||||
* List all users with pagination
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param filters - Optional filters (status, role, search, attributes)
|
||||
* @param options - Optional request options (signal)
|
||||
* @returns Paginated list of users
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
status?: 'active' | 'disabled'
|
||||
role?: 'admin' | 'user'
|
||||
search?: string
|
||||
attributes?: Record<number, string> // attributeId -> value
|
||||
include_subscriptions?: boolean
|
||||
},
|
||||
options?: {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
): Promise<PaginatedResponse<AdminUser>> {
|
||||
// Build params with attribute filters in attr[id]=value format
|
||||
const params: Record<string, any> = {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
status: filters?.status,
|
||||
role: filters?.role,
|
||||
search: filters?.search,
|
||||
include_subscriptions: filters?.include_subscriptions
|
||||
}
|
||||
|
||||
// Add attribute filters as attr[id]=value
|
||||
if (filters?.attributes) {
|
||||
for (const [attrId, value] of Object.entries(filters.attributes)) {
|
||||
if (value) {
|
||||
params[`attr[${attrId}]`] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
const { data } = await apiClient.get<PaginatedResponse<AdminUser>>('/admin/users', {
|
||||
params,
|
||||
signal: options?.signal
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by ID
|
||||
* @param id - User ID
|
||||
* @returns User details
|
||||
*/
|
||||
export async function getById(id: number): Promise<AdminUser> {
|
||||
const { data } = await apiClient.get<AdminUser>(`/admin/users/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new user
|
||||
* @param userData - User data (email, password, etc.)
|
||||
* @returns Created user
|
||||
*/
|
||||
export async function create(userData: {
|
||||
email: string
|
||||
password: string
|
||||
balance?: number
|
||||
concurrency?: number
|
||||
allowed_groups?: number[] | null
|
||||
}): Promise<AdminUser> {
|
||||
const { data } = await apiClient.post<AdminUser>('/admin/users', userData)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user
|
||||
* @param id - User ID
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated user
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateUserRequest): Promise<AdminUser> {
|
||||
const { data } = await apiClient.put<AdminUser>(`/admin/users/${id}`, updates)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete user
|
||||
* @param id - User ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteUser(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/users/${id}`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user balance
|
||||
* @param id - User ID
|
||||
* @param balance - New balance
|
||||
* @param operation - Operation type ('set', 'add', 'subtract')
|
||||
* @param notes - Optional notes for the balance adjustment
|
||||
* @returns Updated user
|
||||
*/
|
||||
export async function updateBalance(
|
||||
id: number,
|
||||
balance: number,
|
||||
operation: 'set' | 'add' | 'subtract' = 'set',
|
||||
notes?: string
|
||||
): Promise<AdminUser> {
|
||||
const { data } = await apiClient.post<AdminUser>(`/admin/users/${id}/balance`, {
|
||||
balance,
|
||||
operation,
|
||||
notes: notes || ''
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user concurrency
|
||||
* @param id - User ID
|
||||
* @param concurrency - New concurrency limit
|
||||
* @returns Updated user
|
||||
*/
|
||||
export async function updateConcurrency(id: number, concurrency: number): Promise<AdminUser> {
|
||||
return update(id, { concurrency })
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle user status
|
||||
* @param id - User ID
|
||||
* @param status - New status
|
||||
* @returns Updated user
|
||||
*/
|
||||
export async function toggleStatus(id: number, status: 'active' | 'disabled'): Promise<AdminUser> {
|
||||
return update(id, { status })
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's API keys
|
||||
* @param id - User ID
|
||||
* @returns List of user's API keys
|
||||
*/
|
||||
export async function getUserApiKeys(id: number): Promise<PaginatedResponse<ApiKey>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<ApiKey>>(`/admin/users/${id}/api-keys`)
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's usage statistics
|
||||
* @param id - User ID
|
||||
* @param period - Time period
|
||||
* @returns User usage statistics
|
||||
*/
|
||||
export async function getUserUsageStats(
|
||||
id: number,
|
||||
period: string = 'month'
|
||||
): Promise<{
|
||||
total_requests: number
|
||||
total_cost: number
|
||||
total_tokens: number
|
||||
}> {
|
||||
const { data } = await apiClient.get<{
|
||||
total_requests: number
|
||||
total_cost: number
|
||||
total_tokens: number
|
||||
}>(`/admin/users/${id}/usage`, {
|
||||
params: { period }
|
||||
})
|
||||
return data
|
||||
}
|
||||
|
||||
/**
|
||||
* Balance history item returned from the API
|
||||
*/
|
||||
export interface BalanceHistoryItem {
|
||||
id: number
|
||||
code: string
|
||||
type: string
|
||||
value: number
|
||||
status: string
|
||||
used_by: number | null
|
||||
used_at: string | null
|
||||
created_at: string
|
||||
group_id: number | null
|
||||
validity_days: number
|
||||
notes: string
|
||||
user?: { id: number; email: string } | null
|
||||
group?: { id: number; name: string } | null
|
||||
}
|
||||
|
||||
// Balance history response extends pagination with total_recharged summary
|
||||
export interface BalanceHistoryResponse extends PaginatedResponse<BalanceHistoryItem> {
|
||||
total_recharged: number
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's balance/concurrency change history
|
||||
* @param id - User ID
|
||||
* @param page - Page number
|
||||
* @param pageSize - Items per page
|
||||
* @param type - Optional type filter (balance, admin_balance, concurrency, admin_concurrency, subscription)
|
||||
* @returns Paginated balance history with total_recharged
|
||||
*/
|
||||
export async function getUserBalanceHistory(
|
||||
id: number,
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
type?: string
|
||||
): Promise<BalanceHistoryResponse> {
|
||||
const params: Record<string, any> = { page, page_size: pageSize }
|
||||
if (type) params.type = type
|
||||
const { data } = await apiClient.get<BalanceHistoryResponse>(
|
||||
`/admin/users/${id}/balance-history`,
|
||||
{ params }
|
||||
)
|
||||
return data
|
||||
}
|
||||
|
||||
export const usersAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteUser,
|
||||
updateBalance,
|
||||
updateConcurrency,
|
||||
toggleStatus,
|
||||
getUserApiKeys,
|
||||
getUserUsageStats,
|
||||
getUserBalanceHistory
|
||||
}
|
||||
|
||||
export default usersAPI
|
||||
Reference in New Issue
Block a user