File size: 4,412 Bytes
274bf5d 4e0bdd1 274bf5d 4e0bdd1 da45372 4e0bdd1 274bf5d 84366f3 274bf5d 84366f3 274bf5d | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 | // Admin dashboard + user-management (CMS) API client. Mirrors debugApi.ts's
// lightweight fetch wrapper (cookie-based auth, no Zod). All endpoints are
// gated server-side on the ADMIN role.
export const ADMIN_BASE_URL = import.meta.env.VITE_API_BASE_URL ||
(import.meta.env.PROD ? '' : 'http://localhost:8000')
async function adminFetch<T>(path: string, init?: RequestInit): Promise<T> {
const res = await fetch(`${ADMIN_BASE_URL}${path}`, {
credentials: 'include',
headers: {
'Content-Type': 'application/json',
...(init?.headers || {}),
},
...init,
})
const text = await res.text().catch(() => '')
const parse = () => {
try { return text ? JSON.parse(text) : {} } catch { return text }
}
if (!res.ok) {
const parsed = parse() as any
const detail = typeof parsed === 'string' ? parsed : parsed?.detail || text
// Keep the status code in the message so callers can detect 409 (stale edit).
throw new Error(`HTTP ${res.status} ${res.statusText}${detail ? `: ${detail}` : ''}`)
}
return parse() as T
}
export type AdminUser = {
id: string
kind: string
role: string
email: string | null
display_name: string | null
status: string
created_at: string | null
updated_at: string | null
last_seen_at: string | null
}
export type AdminUserList = {
items: AdminUser[]
total: number
limit: number
offset: number
}
export type AdminStats = {
tasks: { total: number; completed: number; errored: number; success_rate: number }
tokens: {
total_tokens: number
prompt_tokens: number
completion_tokens: number
cost_usd: number
last_7d_total_tokens: number
}
users: { total: number; registered: number; guests: number; new_registered_last_7d: number }
conversations: { total_created: number; active_sessions: number }
feedback: FeedbackStatsSummary & {
// Per-domain slices (restaurant / movie / music / book / product / unknown),
// most feedback first. The top-level fields above are the all-domains rollup.
domains: Array<FeedbackStatsSummary & { domain: string }>
}
generated_at: string
}
export type FeedbackStatsSummary = {
total: number
satisfied: number
unsatisfied: number
satisfaction_ratio: number | null
// `reason` is the stable code; `label` is the human-readable text for display.
reasons: Array<{ reason: string; label: string; count: number }>
}
export type AdminSessionInfo = {
ok: boolean
user: { id: string; email: string | null; display_name: string | null; role: string }
}
export type ListUsersParams = {
limit?: number
offset?: number
search?: string
role?: string
status?: string
kind?: string
}
export type CreateUserPayload = {
email: string
password: string
display_name?: string | null
role?: string
status?: string
}
export type UpdateUserPayload = {
expected_updated_at?: string | null
role?: string
status?: string
display_name?: string | null
}
export function getAdminSession(): Promise<AdminSessionInfo> {
return adminFetch<AdminSessionInfo>('/api/admin/session', { method: 'GET' })
}
export function getAdminStats(): Promise<AdminStats> {
return adminFetch<AdminStats>('/api/admin/stats', { method: 'GET' })
}
export function listUsers(params: ListUsersParams = {}): Promise<AdminUserList> {
const q = new URLSearchParams()
if (params.limit != null) q.set('limit', String(params.limit))
if (params.offset != null) q.set('offset', String(params.offset))
if (params.search) q.set('search', params.search)
if (params.role) q.set('role', params.role)
if (params.status) q.set('status', params.status)
if (params.kind) q.set('kind', params.kind)
const qs = q.toString()
return adminFetch<AdminUserList>(`/api/admin/users${qs ? `?${qs}` : ''}`, { method: 'GET' })
}
export function createUser(payload: CreateUserPayload): Promise<AdminUser> {
return adminFetch<AdminUser>('/api/admin/users', {
method: 'POST',
body: JSON.stringify(payload),
})
}
export function updateUser(userId: string, payload: UpdateUserPayload): Promise<AdminUser> {
return adminFetch<AdminUser>(`/api/admin/users/${encodeURIComponent(userId)}`, {
method: 'PATCH',
body: JSON.stringify(payload),
})
}
export function deleteUser(userId: string): Promise<AdminUser> {
return adminFetch<AdminUser>(`/api/admin/users/${encodeURIComponent(userId)}`, {
method: 'DELETE',
})
}
|