First commit
This commit is contained in:
270
frontend/src/api/admin/accounts.ts
Normal file
270
frontend/src/api/admin/accounts.ts
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* Admin Accounts API endpoints
|
||||
* Handles AI platform account management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type {
|
||||
Account,
|
||||
CreateAccountRequest,
|
||||
UpdateAccountRequest,
|
||||
PaginatedResponse,
|
||||
AccountUsageInfo,
|
||||
WindowStats,
|
||||
} 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;
|
||||
search?: string;
|
||||
}
|
||||
): Promise<PaginatedResponse<Account>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
return 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 period - Time period
|
||||
* @returns Account usage statistics
|
||||
*/
|
||||
export async function getStats(
|
||||
id: number,
|
||||
period: string = 'month'
|
||||
): Promise<{
|
||||
total_requests: number;
|
||||
successful_requests: number;
|
||||
failed_requests: number;
|
||||
total_tokens: number;
|
||||
average_response_time: number;
|
||||
}> {
|
||||
const { data } = await apiClient.get<{
|
||||
total_requests: number;
|
||||
successful_requests: number;
|
||||
failed_requests: number;
|
||||
total_tokens: number;
|
||||
average_response_time: number;
|
||||
}>(`/admin/accounts/${id}/stats`, {
|
||||
params: { period },
|
||||
});
|
||||
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 Success confirmation
|
||||
*/
|
||||
export async function clearRateLimit(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.post<{ message: string }>(`/admin/accounts/${id}/clear-rate-limit`);
|
||||
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; 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
export const accountsAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteAccount,
|
||||
toggleStatus,
|
||||
testAccount,
|
||||
refreshCredentials,
|
||||
getStats,
|
||||
clearError,
|
||||
getUsage,
|
||||
getTodayStats,
|
||||
clearRateLimit,
|
||||
setSchedulable,
|
||||
generateAuthUrl,
|
||||
exchangeCode,
|
||||
batchCreate,
|
||||
};
|
||||
|
||||
export default accountsAPI;
|
||||
173
frontend/src/api/admin/dashboard.ts
Normal file
173
frontend/src/api/admin/dashboard.ts
Normal file
@@ -0,0 +1,173 @@
|
||||
/**
|
||||
* Admin Dashboard API endpoints
|
||||
* Provides system-wide statistics and metrics
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type { DashboardStats, TrendDataPoint, ModelStat, ApiKeyUsageTrendPoint, UserUsageTrendPoint } 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';
|
||||
}
|
||||
|
||||
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 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?: { start_date?: string; end_date?: string }): Promise<ModelStatsResponse> {
|
||||
const { data } = await apiClient.get<ModelStatsResponse>('/admin/dashboard/models', { 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
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,
|
||||
getApiKeyUsageTrend,
|
||||
getUserUsageTrend,
|
||||
getBatchUsersUsage,
|
||||
getBatchApiKeysUsage,
|
||||
};
|
||||
|
||||
export default dashboardAPI;
|
||||
170
frontend/src/api/admin/groups.ts
Normal file
170
frontend/src/api/admin/groups.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* Admin Groups API endpoints
|
||||
* Handles API key group management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type {
|
||||
Group,
|
||||
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)
|
||||
* @returns Paginated list of groups
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
platform?: GroupPlatform;
|
||||
status?: 'active' | 'inactive';
|
||||
is_exclusive?: boolean;
|
||||
}
|
||||
): Promise<PaginatedResponse<Group>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<Group>>('/admin/groups', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
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<Group[]> {
|
||||
const { data } = await apiClient.get<Group[]>('/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<Group[]> {
|
||||
return getAll(platform);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get group by ID
|
||||
* @param id - Group ID
|
||||
* @returns Group details
|
||||
*/
|
||||
export async function getById(id: number): Promise<Group> {
|
||||
const { data } = await apiClient.get<Group>(`/admin/groups/${id}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new group
|
||||
* @param groupData - Group data
|
||||
* @returns Created group
|
||||
*/
|
||||
export async function create(groupData: CreateGroupRequest): Promise<Group> {
|
||||
const { data } = await apiClient.post<Group>('/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<Group> {
|
||||
const { data } = await apiClient.put<Group>(`/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<Group> {
|
||||
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;
|
||||
}
|
||||
|
||||
export const groupsAPI = {
|
||||
list,
|
||||
getAll,
|
||||
getByPlatform,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteGroup,
|
||||
toggleStatus,
|
||||
getStats,
|
||||
getGroupApiKeys,
|
||||
};
|
||||
|
||||
export default groupsAPI;
|
||||
35
frontend/src/api/admin/index.ts
Normal file
35
frontend/src/api/admin/index.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
/**
|
||||
* 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 settingsAPI from './settings';
|
||||
import systemAPI from './system';
|
||||
import subscriptionsAPI from './subscriptions';
|
||||
import usageAPI from './usage';
|
||||
|
||||
/**
|
||||
* Unified admin API object for convenient access
|
||||
*/
|
||||
export const adminAPI = {
|
||||
dashboard: dashboardAPI,
|
||||
users: usersAPI,
|
||||
groups: groupsAPI,
|
||||
accounts: accountsAPI,
|
||||
proxies: proxiesAPI,
|
||||
redeem: redeemAPI,
|
||||
settings: settingsAPI,
|
||||
system: systemAPI,
|
||||
subscriptions: subscriptionsAPI,
|
||||
usage: usageAPI,
|
||||
};
|
||||
|
||||
export { dashboardAPI, usersAPI, groupsAPI, accountsAPI, proxiesAPI, redeemAPI, settingsAPI, systemAPI, subscriptionsAPI, usageAPI };
|
||||
|
||||
export default adminAPI;
|
||||
211
frontend/src/api/admin/proxies.ts
Normal file
211
frontend/src/api/admin/proxies.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
/**
|
||||
* Admin Proxies API endpoints
|
||||
* Handles proxy server management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type {
|
||||
Proxy,
|
||||
CreateProxyRequest,
|
||||
UpdateProxyRequest,
|
||||
PaginatedResponse,
|
||||
} 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;
|
||||
}
|
||||
): Promise<PaginatedResponse<Proxy>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<Proxy>>('/admin/proxies', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
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;
|
||||
}> {
|
||||
const { data } = await apiClient.post<{
|
||||
success: boolean;
|
||||
message: string;
|
||||
latency_ms?: number;
|
||||
ip_address?: string;
|
||||
city?: string;
|
||||
region?: string;
|
||||
country?: string;
|
||||
}>(`/admin/proxies/${id}/test`);
|
||||
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<PaginatedResponse<any>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<any>>(
|
||||
`/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 const proxiesAPI = {
|
||||
list,
|
||||
getAll,
|
||||
getAllWithCount,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteProxy,
|
||||
toggleStatus,
|
||||
testProxy,
|
||||
getStats,
|
||||
getProxyAccounts,
|
||||
batchCreate,
|
||||
};
|
||||
|
||||
export default proxiesAPI;
|
||||
170
frontend/src/api/admin/redeem.ts
Normal file
170
frontend/src/api/admin/redeem.ts
Normal file
@@ -0,0 +1,170 @@
|
||||
/**
|
||||
* 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';
|
||||
search?: string;
|
||||
}
|
||||
): Promise<PaginatedResponse<RedeemCode>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<RedeemCode>>('/admin/redeem-codes', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
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;
|
||||
109
frontend/src/api/admin/settings.ts
Normal file
109
frontend/src/api/admin/settings.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
/**
|
||||
* Admin Settings API endpoints
|
||||
* Handles system settings management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
|
||||
/**
|
||||
* System settings interface
|
||||
*/
|
||||
export interface SystemSettings {
|
||||
// Registration settings
|
||||
registration_enabled: boolean;
|
||||
email_verify_enabled: boolean;
|
||||
// Default settings
|
||||
default_balance: number;
|
||||
default_concurrency: number;
|
||||
// OEM settings
|
||||
site_name: string;
|
||||
site_logo: string;
|
||||
site_subtitle: string;
|
||||
api_base_url: string;
|
||||
contact_info: string;
|
||||
// SMTP settings
|
||||
smtp_host: string;
|
||||
smtp_port: number;
|
||||
smtp_username: string;
|
||||
smtp_password: string;
|
||||
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: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: Partial<SystemSettings>): 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;
|
||||
}
|
||||
|
||||
export const settingsAPI = {
|
||||
getSettings,
|
||||
updateSettings,
|
||||
testSmtpConnection,
|
||||
sendTestEmail,
|
||||
};
|
||||
|
||||
export default settingsAPI;
|
||||
157
frontend/src/api/admin/subscriptions.ts
Normal file
157
frontend/src/api/admin/subscriptions.ts
Normal file
@@ -0,0 +1,157 @@
|
||||
/**
|
||||
* 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)
|
||||
* @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;
|
||||
}
|
||||
): Promise<PaginatedResponse<UserSubscription>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UserSubscription>>('/admin/subscriptions', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
listByGroup,
|
||||
listByUser,
|
||||
};
|
||||
|
||||
export default subscriptionsAPI;
|
||||
48
frontend/src/api/admin/system.ts
Normal file
48
frontend/src/api/admin/system.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
/**
|
||||
* 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 const systemAPI = {
|
||||
getVersion,
|
||||
checkUpdates,
|
||||
};
|
||||
|
||||
export default systemAPI;
|
||||
112
frontend/src/api/admin/usage.ts
Normal file
112
frontend/src/api/admin/usage.ts
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* Admin Usage API endpoints
|
||||
* Handles admin-level usage logs and statistics retrieval
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type {
|
||||
UsageLog,
|
||||
UsageQueryParams,
|
||||
PaginatedResponse,
|
||||
} 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;
|
||||
average_duration_ms: number;
|
||||
}
|
||||
|
||||
export interface SimpleUser {
|
||||
id: number;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface SimpleApiKey {
|
||||
id: number;
|
||||
name: string;
|
||||
user_id: number;
|
||||
}
|
||||
|
||||
export interface AdminUsageQueryParams extends UsageQueryParams {
|
||||
user_id?: number;
|
||||
}
|
||||
|
||||
// ==================== 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): Promise<PaginatedResponse<UsageLog>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/admin/usage', {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage statistics with optional filters (admin only)
|
||||
* @param params - Query parameters (user_id, api_key_id, period/date range)
|
||||
* @returns Usage statistics
|
||||
*/
|
||||
export async function getStats(params: {
|
||||
user_id?: number;
|
||||
api_key_id?: number;
|
||||
period?: string;
|
||||
start_date?: string;
|
||||
end_date?: 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;
|
||||
}
|
||||
|
||||
export const adminUsageAPI = {
|
||||
list,
|
||||
getStats,
|
||||
searchUsers,
|
||||
searchApiKeys,
|
||||
};
|
||||
|
||||
export default adminUsageAPI;
|
||||
168
frontend/src/api/admin/users.ts
Normal file
168
frontend/src/api/admin/users.ts
Normal file
@@ -0,0 +1,168 @@
|
||||
/**
|
||||
* Admin Users API endpoints
|
||||
* Handles user management for administrators
|
||||
*/
|
||||
|
||||
import { apiClient } from '../client';
|
||||
import type { User, UpdateUserRequest, PaginatedResponse } 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)
|
||||
* @returns Paginated list of users
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
filters?: {
|
||||
status?: 'active' | 'disabled';
|
||||
role?: 'admin' | 'user';
|
||||
search?: string;
|
||||
}
|
||||
): Promise<PaginatedResponse<User>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<User>>('/admin/users', {
|
||||
params: {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
...filters,
|
||||
},
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user by ID
|
||||
* @param id - User ID
|
||||
* @returns User details
|
||||
*/
|
||||
export async function getById(id: number): Promise<User> {
|
||||
const { data } = await apiClient.get<User>(`/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<User> {
|
||||
const { data } = await apiClient.post<User>('/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<User> {
|
||||
const { data } = await apiClient.put<User>(`/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')
|
||||
* @returns Updated user
|
||||
*/
|
||||
export async function updateBalance(
|
||||
id: number,
|
||||
balance: number,
|
||||
operation: 'set' | 'add' | 'subtract' = 'set'
|
||||
): Promise<User> {
|
||||
const { data } = await apiClient.post<User>(`/admin/users/${id}/balance`, {
|
||||
balance,
|
||||
operation,
|
||||
});
|
||||
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<User> {
|
||||
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<User> {
|
||||
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<any>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<any>>(`/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;
|
||||
}
|
||||
|
||||
export const usersAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteUser,
|
||||
updateBalance,
|
||||
updateConcurrency,
|
||||
toggleStatus,
|
||||
getUserApiKeys,
|
||||
getUserUsageStats,
|
||||
};
|
||||
|
||||
export default usersAPI;
|
||||
120
frontend/src/api/auth.ts
Normal file
120
frontend/src/api/auth.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
/**
|
||||
* Authentication API endpoints
|
||||
* Handles user login, registration, and logout operations
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type { LoginRequest, RegisterRequest, AuthResponse, User, SendVerifyCodeRequest, SendVerifyCodeResponse, PublicSettings } from '@/types';
|
||||
|
||||
/**
|
||||
* Store authentication token in localStorage
|
||||
*/
|
||||
export function setAuthToken(token: string): void {
|
||||
localStorage.setItem('auth_token', token);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get authentication token from localStorage
|
||||
*/
|
||||
export function getAuthToken(): string | null {
|
||||
return localStorage.getItem('auth_token');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear authentication token from localStorage
|
||||
*/
|
||||
export function clearAuthToken(): void {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
}
|
||||
|
||||
/**
|
||||
* User login
|
||||
* @param credentials - Username and password
|
||||
* @returns Authentication response with token and user data
|
||||
*/
|
||||
export async function login(credentials: LoginRequest): Promise<AuthResponse> {
|
||||
const { data } = await apiClient.post<AuthResponse>('/auth/login', credentials);
|
||||
|
||||
// Store token and user data
|
||||
setAuthToken(data.access_token);
|
||||
localStorage.setItem('auth_user', JSON.stringify(data.user));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* User registration
|
||||
* @param userData - Registration data (username, email, password)
|
||||
* @returns Authentication response with token and user data
|
||||
*/
|
||||
export async function register(userData: RegisterRequest): Promise<AuthResponse> {
|
||||
const { data } = await apiClient.post<AuthResponse>('/auth/register', userData);
|
||||
|
||||
// Store token and user data
|
||||
setAuthToken(data.access_token);
|
||||
localStorage.setItem('auth_user', JSON.stringify(data.user));
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current authenticated user
|
||||
* @returns User profile data
|
||||
*/
|
||||
export async function getCurrentUser(): Promise<User> {
|
||||
const { data } = await apiClient.get<User>('/auth/me');
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* User logout
|
||||
* Clears authentication token and user data from localStorage
|
||||
*/
|
||||
export function logout(): void {
|
||||
clearAuthToken();
|
||||
// Optionally redirect to login page
|
||||
// window.location.href = '/login';
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if user is authenticated
|
||||
* @returns True if user has valid token
|
||||
*/
|
||||
export function isAuthenticated(): boolean {
|
||||
return getAuthToken() !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get public settings (no auth required)
|
||||
* @returns Public settings including registration and Turnstile config
|
||||
*/
|
||||
export async function getPublicSettings(): Promise<PublicSettings> {
|
||||
const { data } = await apiClient.get<PublicSettings>('/settings/public');
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send verification code to email
|
||||
* @param request - Email and optional Turnstile token
|
||||
* @returns Response with countdown seconds
|
||||
*/
|
||||
export async function sendVerifyCode(request: SendVerifyCodeRequest): Promise<SendVerifyCodeResponse> {
|
||||
const { data } = await apiClient.post<SendVerifyCodeResponse>('/auth/send-verify-code', request);
|
||||
return data;
|
||||
}
|
||||
|
||||
export const authAPI = {
|
||||
login,
|
||||
register,
|
||||
getCurrentUser,
|
||||
logout,
|
||||
isAuthenticated,
|
||||
setAuthToken,
|
||||
getAuthToken,
|
||||
clearAuthToken,
|
||||
getPublicSettings,
|
||||
sendVerifyCode,
|
||||
};
|
||||
|
||||
export default authAPI;
|
||||
89
frontend/src/api/client.ts
Normal file
89
frontend/src/api/client.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
/**
|
||||
* Axios HTTP Client Configuration
|
||||
* Base client with interceptors for authentication and error handling
|
||||
*/
|
||||
|
||||
import axios, { AxiosInstance, AxiosError, InternalAxiosRequestConfig } from 'axios';
|
||||
import type { ApiResponse } from '@/types';
|
||||
|
||||
// ==================== Axios Instance Configuration ====================
|
||||
|
||||
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || '/api/v1';
|
||||
|
||||
export const apiClient: AxiosInstance = axios.create({
|
||||
baseURL: API_BASE_URL,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// ==================== Request Interceptor ====================
|
||||
|
||||
apiClient.interceptors.request.use(
|
||||
(config: InternalAxiosRequestConfig) => {
|
||||
// Attach token from localStorage
|
||||
const token = localStorage.getItem('auth_token');
|
||||
if (token && config.headers) {
|
||||
config.headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// ==================== Response Interceptor ====================
|
||||
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
// Unwrap standard API response format { code, message, data }
|
||||
const apiResponse = response.data as ApiResponse<unknown>;
|
||||
if (apiResponse && typeof apiResponse === 'object' && 'code' in apiResponse) {
|
||||
if (apiResponse.code === 0) {
|
||||
// Success - return the data portion
|
||||
response.data = apiResponse.data;
|
||||
} else {
|
||||
// API error
|
||||
return Promise.reject({
|
||||
status: response.status,
|
||||
code: apiResponse.code,
|
||||
message: apiResponse.message || 'Unknown error',
|
||||
});
|
||||
}
|
||||
}
|
||||
return response;
|
||||
},
|
||||
(error: AxiosError<ApiResponse<unknown>>) => {
|
||||
// Handle common errors
|
||||
if (error.response) {
|
||||
const { status, data } = error.response;
|
||||
|
||||
// 401: Unauthorized - clear token and redirect to login
|
||||
if (status === 401) {
|
||||
localStorage.removeItem('auth_token');
|
||||
localStorage.removeItem('auth_user');
|
||||
// Only redirect if not already on login page
|
||||
if (!window.location.pathname.includes('/login')) {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
|
||||
// Return structured error
|
||||
return Promise.reject({
|
||||
status,
|
||||
code: data?.code,
|
||||
message: data?.message || error.message,
|
||||
});
|
||||
}
|
||||
|
||||
// Network error
|
||||
return Promise.reject({
|
||||
status: 0,
|
||||
message: 'Network error. Please check your connection.',
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export default apiClient;
|
||||
25
frontend/src/api/groups.ts
Normal file
25
frontend/src/api/groups.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* User Groups API endpoints (non-admin)
|
||||
* Handles group-related operations for regular users
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type { Group } from '@/types';
|
||||
|
||||
/**
|
||||
* Get available groups that the current user can bind to API keys
|
||||
* This returns groups based on user's permissions:
|
||||
* - Standard groups: public (non-exclusive) or explicitly allowed
|
||||
* - Subscription groups: user has active subscription
|
||||
* @returns List of available groups
|
||||
*/
|
||||
export async function getAvailable(): Promise<Group[]> {
|
||||
const { data } = await apiClient.get<Group[]>('/groups/available');
|
||||
return data;
|
||||
}
|
||||
|
||||
export const userGroupsAPI = {
|
||||
getAvailable,
|
||||
};
|
||||
|
||||
export default userGroupsAPI;
|
||||
23
frontend/src/api/index.ts
Normal file
23
frontend/src/api/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
/**
|
||||
* API Client for Sub2API Backend
|
||||
* Central export point for all API modules
|
||||
*/
|
||||
|
||||
// Re-export the HTTP client
|
||||
export { apiClient } from './client';
|
||||
|
||||
// Auth API
|
||||
export { authAPI } from './auth';
|
||||
|
||||
// User APIs
|
||||
export { keysAPI } from './keys';
|
||||
export { usageAPI } from './usage';
|
||||
export { userAPI } from './user';
|
||||
export { redeemAPI, type RedeemHistoryItem } from './redeem';
|
||||
export { userGroupsAPI } from './groups';
|
||||
|
||||
// Admin APIs
|
||||
export { adminAPI } from './admin';
|
||||
|
||||
// Default export
|
||||
export { default } from './client';
|
||||
100
frontend/src/api/keys.ts
Normal file
100
frontend/src/api/keys.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* API Keys management endpoints
|
||||
* Handles CRUD operations for user API keys
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type {
|
||||
ApiKey,
|
||||
CreateApiKeyRequest,
|
||||
UpdateApiKeyRequest,
|
||||
PaginatedResponse,
|
||||
} from '@/types';
|
||||
|
||||
/**
|
||||
* List all API keys for current user
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 10)
|
||||
* @returns Paginated list of API keys
|
||||
*/
|
||||
export async function list(page: number = 1, pageSize: number = 10): Promise<PaginatedResponse<ApiKey>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<ApiKey>>('/keys', {
|
||||
params: { page, page_size: pageSize },
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get API key by ID
|
||||
* @param id - API key ID
|
||||
* @returns API key details
|
||||
*/
|
||||
export async function getById(id: number): Promise<ApiKey> {
|
||||
const { data } = await apiClient.get<ApiKey>(`/keys/${id}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new API key
|
||||
* @param name - Key name
|
||||
* @param groupId - Optional group ID
|
||||
* @param customKey - Optional custom key value
|
||||
* @returns Created API key
|
||||
*/
|
||||
export async function create(name: string, groupId?: number | null, customKey?: string): Promise<ApiKey> {
|
||||
const payload: CreateApiKeyRequest = { name };
|
||||
if (groupId !== undefined) {
|
||||
payload.group_id = groupId;
|
||||
}
|
||||
if (customKey) {
|
||||
payload.custom_key = customKey;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.post<ApiKey>('/keys', payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update API key
|
||||
* @param id - API key ID
|
||||
* @param updates - Fields to update
|
||||
* @returns Updated API key
|
||||
*/
|
||||
export async function update(id: number, updates: UpdateApiKeyRequest): Promise<ApiKey> {
|
||||
const { data } = await apiClient.put<ApiKey>(`/keys/${id}`, updates);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete API key
|
||||
* @param id - API key ID
|
||||
* @returns Success confirmation
|
||||
*/
|
||||
export async function deleteKey(id: number): Promise<{ message: string }> {
|
||||
const { data } = await apiClient.delete<{ message: string }>(`/keys/${id}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle API key status (active/inactive)
|
||||
* @param id - API key ID
|
||||
* @param status - New status
|
||||
* @returns Updated API key
|
||||
*/
|
||||
export async function toggleStatus(
|
||||
id: number,
|
||||
status: 'active' | 'inactive'
|
||||
): Promise<ApiKey> {
|
||||
return update(id, { status });
|
||||
}
|
||||
|
||||
export const keysAPI = {
|
||||
list,
|
||||
getById,
|
||||
create,
|
||||
update,
|
||||
delete: deleteKey,
|
||||
toggleStatus,
|
||||
};
|
||||
|
||||
export default keysAPI;
|
||||
65
frontend/src/api/redeem.ts
Normal file
65
frontend/src/api/redeem.ts
Normal file
@@ -0,0 +1,65 @@
|
||||
/**
|
||||
* Redeem code API endpoints
|
||||
* Handles redeem code redemption for users
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type { RedeemCodeRequest } from '@/types';
|
||||
|
||||
export interface RedeemHistoryItem {
|
||||
id: number;
|
||||
code: string;
|
||||
type: string;
|
||||
value: number;
|
||||
status: string;
|
||||
used_at: string;
|
||||
created_at: string;
|
||||
// 订阅类型专用字段
|
||||
group_id?: number;
|
||||
validity_days?: number;
|
||||
group?: {
|
||||
id: number;
|
||||
name: string;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Redeem a code
|
||||
* @param code - Redeem code string
|
||||
* @returns Redemption result with updated balance or concurrency
|
||||
*/
|
||||
export async function redeem(code: string): Promise<{
|
||||
message: string;
|
||||
type: string;
|
||||
value: number;
|
||||
new_balance?: number;
|
||||
new_concurrency?: number;
|
||||
}> {
|
||||
const payload: RedeemCodeRequest = { code };
|
||||
|
||||
const { data } = await apiClient.post<{
|
||||
message: string;
|
||||
type: string;
|
||||
value: number;
|
||||
new_balance?: number;
|
||||
new_concurrency?: number;
|
||||
}>('/redeem', payload);
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's redemption history
|
||||
* @returns List of redeemed codes
|
||||
*/
|
||||
export async function getHistory(): Promise<RedeemHistoryItem[]> {
|
||||
const { data } = await apiClient.get<RedeemHistoryItem[]>('/redeem/history');
|
||||
return data;
|
||||
}
|
||||
|
||||
export const redeemAPI = {
|
||||
redeem,
|
||||
getHistory,
|
||||
};
|
||||
|
||||
export default redeemAPI;
|
||||
87
frontend/src/api/setup.ts
Normal file
87
frontend/src/api/setup.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
/**
|
||||
* Setup API endpoints
|
||||
*/
|
||||
import axios from 'axios';
|
||||
|
||||
// Create a separate client for setup endpoints (not under /api/v1)
|
||||
const setupClient = axios.create({
|
||||
baseURL: '',
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
export interface SetupStatus {
|
||||
needs_setup: boolean;
|
||||
step: string;
|
||||
}
|
||||
|
||||
export interface DatabaseConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
user: string;
|
||||
password: string;
|
||||
dbname: string;
|
||||
sslmode: string;
|
||||
}
|
||||
|
||||
export interface RedisConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
password: string;
|
||||
db: number;
|
||||
}
|
||||
|
||||
export interface AdminConfig {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface ServerConfig {
|
||||
host: string;
|
||||
port: number;
|
||||
mode: string;
|
||||
}
|
||||
|
||||
export interface InstallRequest {
|
||||
database: DatabaseConfig;
|
||||
redis: RedisConfig;
|
||||
admin: AdminConfig;
|
||||
server: ServerConfig;
|
||||
}
|
||||
|
||||
export interface InstallResponse {
|
||||
message: string;
|
||||
restart: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get setup status
|
||||
*/
|
||||
export async function getSetupStatus(): Promise<SetupStatus> {
|
||||
const response = await setupClient.get('/setup/status');
|
||||
return response.data.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database connection
|
||||
*/
|
||||
export async function testDatabase(config: DatabaseConfig): Promise<void> {
|
||||
await setupClient.post('/setup/test-db', config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test Redis connection
|
||||
*/
|
||||
export async function testRedis(config: RedisConfig): Promise<void> {
|
||||
await setupClient.post('/setup/test-redis', config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform installation
|
||||
*/
|
||||
export async function install(config: InstallRequest): Promise<InstallResponse> {
|
||||
const response = await setupClient.post('/setup/install', config);
|
||||
return response.data.data;
|
||||
}
|
||||
72
frontend/src/api/subscriptions.ts
Normal file
72
frontend/src/api/subscriptions.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* User Subscription API
|
||||
* API for regular users to view their own subscriptions and progress
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type { UserSubscription, SubscriptionProgress } from '@/types';
|
||||
|
||||
/**
|
||||
* Subscription summary for user dashboard
|
||||
*/
|
||||
export interface SubscriptionSummary {
|
||||
active_count: number;
|
||||
subscriptions: Array<{
|
||||
id: number;
|
||||
group_name: string;
|
||||
status: string;
|
||||
daily_progress: number | null;
|
||||
weekly_progress: number | null;
|
||||
monthly_progress: number | null;
|
||||
expires_at: string | null;
|
||||
days_remaining: number | null;
|
||||
}>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of current user's subscriptions
|
||||
*/
|
||||
export async function getMySubscriptions(): Promise<UserSubscription[]> {
|
||||
const response = await apiClient.get<UserSubscription[]>('/subscriptions');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current user's active subscriptions
|
||||
*/
|
||||
export async function getActiveSubscriptions(): Promise<UserSubscription[]> {
|
||||
const response = await apiClient.get<UserSubscription[]>('/subscriptions/active');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress for all user's active subscriptions
|
||||
*/
|
||||
export async function getSubscriptionsProgress(): Promise<SubscriptionProgress[]> {
|
||||
const response = await apiClient.get<SubscriptionProgress[]>('/subscriptions/progress');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get subscription summary for dashboard display
|
||||
*/
|
||||
export async function getSubscriptionSummary(): Promise<SubscriptionSummary> {
|
||||
const response = await apiClient.get<SubscriptionSummary>('/subscriptions/summary');
|
||||
return response.data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get progress for a specific subscription
|
||||
*/
|
||||
export async function getSubscriptionProgress(subscriptionId: number): Promise<SubscriptionProgress> {
|
||||
const response = await apiClient.get<SubscriptionProgress>(`/subscriptions/${subscriptionId}/progress`);
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export default {
|
||||
getMySubscriptions,
|
||||
getActiveSubscriptions,
|
||||
getSubscriptionsProgress,
|
||||
getSubscriptionSummary,
|
||||
getSubscriptionProgress,
|
||||
};
|
||||
253
frontend/src/api/usage.ts
Normal file
253
frontend/src/api/usage.ts
Normal file
@@ -0,0 +1,253 @@
|
||||
/**
|
||||
* Usage tracking API endpoints
|
||||
* Handles usage logs and statistics retrieval
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type {
|
||||
UsageLog,
|
||||
UsageQueryParams,
|
||||
UsageStatsResponse,
|
||||
PaginatedResponse,
|
||||
TrendDataPoint,
|
||||
ModelStat,
|
||||
} from '@/types';
|
||||
|
||||
// ==================== Dashboard Types ====================
|
||||
|
||||
export interface UserDashboardStats {
|
||||
total_api_keys: number;
|
||||
active_api_keys: number;
|
||||
total_requests: number;
|
||||
total_input_tokens: number;
|
||||
total_output_tokens: number;
|
||||
total_cache_creation_tokens: number;
|
||||
total_cache_read_tokens: number;
|
||||
total_tokens: number;
|
||||
total_cost: number; // 标准计费
|
||||
total_actual_cost: number; // 实际扣除
|
||||
today_requests: number;
|
||||
today_input_tokens: number;
|
||||
today_output_tokens: number;
|
||||
today_cache_creation_tokens: number;
|
||||
today_cache_read_tokens: number;
|
||||
today_tokens: number;
|
||||
today_cost: number; // 今日标准计费
|
||||
today_actual_cost: number; // 今日实际扣除
|
||||
average_duration_ms: number;
|
||||
}
|
||||
|
||||
export interface TrendParams {
|
||||
start_date?: string;
|
||||
end_date?: string;
|
||||
granularity?: 'day' | 'hour';
|
||||
}
|
||||
|
||||
export interface TrendResponse {
|
||||
trend: TrendDataPoint[];
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
granularity: string;
|
||||
}
|
||||
|
||||
export interface ModelStatsResponse {
|
||||
models: ModelStat[];
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* List usage logs with optional filters
|
||||
* @param page - Page number (default: 1)
|
||||
* @param pageSize - Items per page (default: 20)
|
||||
* @param apiKeyId - Filter by API key ID
|
||||
* @returns Paginated list of usage logs
|
||||
*/
|
||||
export async function list(
|
||||
page: number = 1,
|
||||
pageSize: number = 20,
|
||||
apiKeyId?: number
|
||||
): Promise<PaginatedResponse<UsageLog>> {
|
||||
const params: UsageQueryParams = {
|
||||
page,
|
||||
page_size: pageSize,
|
||||
};
|
||||
|
||||
if (apiKeyId !== undefined) {
|
||||
params.api_key_id = apiKeyId;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/usage', {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage logs with advanced query parameters
|
||||
* @param params - Query parameters for filtering and pagination
|
||||
* @returns Paginated list of usage logs
|
||||
*/
|
||||
export async function query(params: UsageQueryParams): Promise<PaginatedResponse<UsageLog>> {
|
||||
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/usage', {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage statistics for a specific period
|
||||
* @param period - Time period ('today', 'week', 'month', 'year')
|
||||
* @param apiKeyId - Optional API key ID filter
|
||||
* @returns Usage statistics
|
||||
*/
|
||||
export async function getStats(
|
||||
period: string = 'today',
|
||||
apiKeyId?: number
|
||||
): Promise<UsageStatsResponse> {
|
||||
const params: Record<string, unknown> = { period };
|
||||
|
||||
if (apiKeyId !== undefined) {
|
||||
params.api_key_id = apiKeyId;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get<UsageStatsResponse>('/usage/stats', {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage statistics for a date range
|
||||
* @param startDate - Start date (YYYY-MM-DD format)
|
||||
* @param endDate - End date (YYYY-MM-DD format)
|
||||
* @param apiKeyId - Optional API key ID filter
|
||||
* @returns Usage statistics
|
||||
*/
|
||||
export async function getStatsByDateRange(
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
apiKeyId?: number
|
||||
): Promise<UsageStatsResponse> {
|
||||
const params: Record<string, unknown> = {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
};
|
||||
|
||||
if (apiKeyId !== undefined) {
|
||||
params.api_key_id = apiKeyId;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get<UsageStatsResponse>('/usage/stats', {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get usage by date range
|
||||
* @param startDate - Start date (ISO format)
|
||||
* @param endDate - End date (ISO format)
|
||||
* @param apiKeyId - Optional API key ID filter
|
||||
* @returns Usage logs within date range
|
||||
*/
|
||||
export async function getByDateRange(
|
||||
startDate: string,
|
||||
endDate: string,
|
||||
apiKeyId?: number
|
||||
): Promise<PaginatedResponse<UsageLog>> {
|
||||
const params: UsageQueryParams = {
|
||||
start_date: startDate,
|
||||
end_date: endDate,
|
||||
page: 1,
|
||||
page_size: 100,
|
||||
};
|
||||
|
||||
if (apiKeyId !== undefined) {
|
||||
params.api_key_id = apiKeyId;
|
||||
}
|
||||
|
||||
const { data } = await apiClient.get<PaginatedResponse<UsageLog>>('/usage', {
|
||||
params,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get detailed usage log by ID
|
||||
* @param id - Usage log ID
|
||||
* @returns Usage log details
|
||||
*/
|
||||
export async function getById(id: number): Promise<UsageLog> {
|
||||
const { data } = await apiClient.get<UsageLog>(`/usage/${id}`);
|
||||
return data;
|
||||
}
|
||||
|
||||
// ==================== Dashboard API ====================
|
||||
|
||||
/**
|
||||
* Get user dashboard statistics
|
||||
* @returns Dashboard statistics for current user
|
||||
*/
|
||||
export async function getDashboardStats(): Promise<UserDashboardStats> {
|
||||
const { data } = await apiClient.get<UserDashboardStats>('/usage/dashboard/stats');
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user usage trend data
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns Usage trend data for current user
|
||||
*/
|
||||
export async function getDashboardTrend(params?: TrendParams): Promise<TrendResponse> {
|
||||
const { data } = await apiClient.get<TrendResponse>('/usage/dashboard/trend', { params });
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user model usage statistics
|
||||
* @param params - Query parameters for filtering
|
||||
* @returns Model usage statistics for current user
|
||||
*/
|
||||
export async function getDashboardModels(params?: { start_date?: string; end_date?: string }): Promise<ModelStatsResponse> {
|
||||
const { data } = await apiClient.get<ModelStatsResponse>('/usage/dashboard/models', { params });
|
||||
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 user's own API keys
|
||||
* @param apiKeyIds - Array of API key IDs
|
||||
* @returns Usage stats map keyed by API key ID
|
||||
*/
|
||||
export async function getDashboardApiKeysUsage(apiKeyIds: number[]): Promise<BatchApiKeysUsageResponse> {
|
||||
const { data } = await apiClient.post<BatchApiKeysUsageResponse>('/usage/dashboard/api-keys-usage', {
|
||||
api_key_ids: apiKeyIds,
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
export const usageAPI = {
|
||||
list,
|
||||
query,
|
||||
getStats,
|
||||
getStatsByDateRange,
|
||||
getByDateRange,
|
||||
getById,
|
||||
// Dashboard
|
||||
getDashboardStats,
|
||||
getDashboardTrend,
|
||||
getDashboardModels,
|
||||
getDashboardApiKeysUsage,
|
||||
};
|
||||
|
||||
export default usageAPI;
|
||||
41
frontend/src/api/user.ts
Normal file
41
frontend/src/api/user.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
/**
|
||||
* User API endpoints
|
||||
* Handles user profile management and password changes
|
||||
*/
|
||||
|
||||
import { apiClient } from './client';
|
||||
import type { User, ChangePasswordRequest } from '@/types';
|
||||
|
||||
/**
|
||||
* Get current user profile
|
||||
* @returns User profile data
|
||||
*/
|
||||
export async function getProfile(): Promise<User> {
|
||||
const { data } = await apiClient.get<User>('/users/me');
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change current user password
|
||||
* @param passwords - Old and new password
|
||||
* @returns Success message
|
||||
*/
|
||||
export async function changePassword(
|
||||
oldPassword: string,
|
||||
newPassword: string
|
||||
): Promise<{ message: string }> {
|
||||
const payload: ChangePasswordRequest = {
|
||||
old_password: oldPassword,
|
||||
new_password: newPassword,
|
||||
};
|
||||
|
||||
const { data } = await apiClient.post<{ message: string }>('/users/me/password', payload);
|
||||
return data;
|
||||
}
|
||||
|
||||
export const userAPI = {
|
||||
getProfile,
|
||||
changePassword,
|
||||
};
|
||||
|
||||
export default userAPI;
|
||||
Reference in New Issue
Block a user