Migrate the hardcoded wechat field to the new extensible user attributes system and improve the users management UI. Migration: - Add migration 019 to move wechat data to user_attribute_values - Remove wechat field from User entity, DTOs, and API contracts - Clean up wechat-related code from backend and frontend UsersView enhancements: - Add text labels to action buttons (Filter Settings, Column Settings, Attributes Config) for better UX - Change status column to show colored dot + Chinese text instead of English text - Add dynamic attribute columns support with batch loading - Add column visibility settings with localStorage persistence - Add filter settings modal for search and filter preferences - Update i18n translations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
192 lines
4.5 KiB
TypeScript
192 lines
4.5 KiB
TypeScript
/**
|
|
* 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, 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
|
|
},
|
|
options?: {
|
|
signal?: AbortSignal
|
|
}
|
|
): Promise<PaginatedResponse<User>> {
|
|
// 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
|
|
}
|
|
|
|
// 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<User>>('/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<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')
|
|
* @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<User> {
|
|
const { data } = await apiClient.post<User>(`/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<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
|