|
|
@@ -1,11 +1,4 @@
|
|
|
-import axios from 'axios'
|
|
|
-
|
|
|
-const api = axios.create({ baseURL: '/api/v1' })
|
|
|
-
|
|
|
-api.interceptors.response.use(
|
|
|
- (res) => res.data,
|
|
|
- (error) => Promise.reject(error)
|
|
|
-)
|
|
|
+import api from './client'
|
|
|
|
|
|
// Types
|
|
|
export interface ApiResponse<T> {
|
|
|
@@ -24,6 +17,9 @@ export interface PagedResponse<T> {
|
|
|
export interface StartTaskRequest {
|
|
|
plugin_name: string
|
|
|
auto_clean?: boolean
|
|
|
+ target_group?: string
|
|
|
+ proxy_id?: number
|
|
|
+ proxy_mode?: 'single' | 'pool'
|
|
|
}
|
|
|
|
|
|
export interface TaskLog {
|
|
|
@@ -34,6 +30,9 @@ export interface TaskLog {
|
|
|
items_processed: number
|
|
|
merchants_added: number
|
|
|
errors_count: number
|
|
|
+ proxy_id: number | null
|
|
|
+ proxy_name: string
|
|
|
+ proxy_mode: string
|
|
|
started_at: string | null
|
|
|
finished_at: string | null
|
|
|
detail: string
|
|
|
@@ -70,11 +69,28 @@ export interface MerchantClean {
|
|
|
level: string // Hot / Warm / Cold
|
|
|
status: string
|
|
|
is_alive: boolean
|
|
|
+ follow_status: string
|
|
|
+ assigned_to: string
|
|
|
+ remark: string
|
|
|
last_checked_at: string | null
|
|
|
created_at: string
|
|
|
updated_at: string
|
|
|
}
|
|
|
|
|
|
+export interface AssignableUser {
|
|
|
+ username: string
|
|
|
+ nickname: string
|
|
|
+}
|
|
|
+
|
|
|
+export interface MerchantNote {
|
|
|
+ id: number
|
|
|
+ merchant_id: number
|
|
|
+ tg_username: string
|
|
|
+ content: string
|
|
|
+ created_by: string
|
|
|
+ created_at: string
|
|
|
+}
|
|
|
+
|
|
|
export interface Keyword {
|
|
|
id: number
|
|
|
keyword: string
|
|
|
@@ -91,6 +107,53 @@ export interface MerchantStats {
|
|
|
by_source: Record<string, number>
|
|
|
}
|
|
|
|
|
|
+// Dashboard
|
|
|
+export interface DailyTrend {
|
|
|
+ date: string
|
|
|
+ count: number
|
|
|
+}
|
|
|
+
|
|
|
+export interface DashboardData {
|
|
|
+ raw_total: number
|
|
|
+ clean_total: number
|
|
|
+ valid_total: number
|
|
|
+ by_level: Record<string, number>
|
|
|
+ by_status: Record<string, number>
|
|
|
+ by_source: Record<string, number>
|
|
|
+ by_industry: Record<string, number>
|
|
|
+ today_added: number
|
|
|
+ week_added: number
|
|
|
+ recent_tasks: TaskLog[]
|
|
|
+ daily_trend: DailyTrend[]
|
|
|
+}
|
|
|
+
|
|
|
+export const getDashboard = () => api.get<unknown, ApiResponse<DashboardData>>('/dashboard')
|
|
|
+export const logoutApi = () => api.post<unknown, ApiResponse<null>>('/auth/logout')
|
|
|
+export const updateProfile = (data: { nickname?: string }) =>
|
|
|
+ api.put<unknown, ApiResponse<{ id: number; username: string; nickname: string; role: string }>>('/auth/profile', data)
|
|
|
+export const getMyPermissions = () =>
|
|
|
+ api.get<unknown, ApiResponse<{ role: string; menus: string[]; actions: string[] }>>('/auth/permissions')
|
|
|
+
|
|
|
+// Permissions management (admin)
|
|
|
+export interface RolePermission {
|
|
|
+ id: number
|
|
|
+ role: string
|
|
|
+ menus: string
|
|
|
+ actions: string
|
|
|
+}
|
|
|
+
|
|
|
+export interface PermissionMeta {
|
|
|
+ key: string
|
|
|
+ label: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getAllPermissions = () =>
|
|
|
+ api.get<unknown, ApiResponse<{ roles: RolePermission[]; all_menus: PermissionMeta[]; all_actions: PermissionMeta[] }>>('/permissions')
|
|
|
+export const updateRolePermission = (role: string, menus: string[], actions: string[]) =>
|
|
|
+ api.put<unknown, ApiResponse<RolePermission>>(`/permissions/${role}`, { menus, actions })
|
|
|
+export const resetPermissions = () =>
|
|
|
+ api.post<unknown, ApiResponse<RolePermission[]>>('/permissions/reset')
|
|
|
+
|
|
|
// Tasks
|
|
|
export const getTasks = (params?: Record<string, unknown>) =>
|
|
|
api.get<unknown, ApiResponse<PagedResponse<TaskLog>>>('/tasks', { params })
|
|
|
@@ -99,6 +162,8 @@ export const startTask = (data: StartTaskRequest) =>
|
|
|
api.post<unknown, ApiResponse<TaskLog>>('/tasks/start', data)
|
|
|
export const stopTask = (id: number) =>
|
|
|
api.post<unknown, ApiResponse<null>>(`/tasks/${id}/stop`)
|
|
|
+export const retryTask = (id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<TaskLog>>(`/tasks/${id}/retry`)
|
|
|
|
|
|
// Merchants
|
|
|
export const getMerchantsStats = () => api.get<unknown, ApiResponse<MerchantStats>>('/merchants/stats')
|
|
|
@@ -107,6 +172,43 @@ export const getMerchantsRaw = (params?: Record<string, unknown>) =>
|
|
|
export const getMerchantsClean = (params?: Record<string, unknown>) =>
|
|
|
api.get<unknown, ApiResponse<PagedResponse<MerchantClean>>>('/merchants/clean', { params })
|
|
|
export const getMerchant = (id: number) => api.get<unknown, ApiResponse<unknown>>(`/merchants/${id}`)
|
|
|
+export const updateMerchantClean = (id: number, data: Record<string, unknown>) =>
|
|
|
+ api.put<unknown, ApiResponse<MerchantClean>>(`/merchants/clean/${id}`, data)
|
|
|
+export const updateFollowStatus = (id: number, follow_status: string) =>
|
|
|
+ api.put<unknown, ApiResponse<null>>(`/merchants/clean/${id}/follow-status`, { follow_status })
|
|
|
+export const getMerchantNotes = (id: number) =>
|
|
|
+ api.get<unknown, ApiResponse<MerchantNote[]>>(`/merchants/clean/${id}/notes`)
|
|
|
+export const addMerchantNote = (id: number, content: string) =>
|
|
|
+ api.post<unknown, ApiResponse<MerchantNote>>(`/merchants/clean/${id}/notes`, { content })
|
|
|
+export const batchDeleteRaw = (ids: number[]) =>
|
|
|
+ api.delete<unknown, ApiResponse<{ deleted: number }>>('/merchants/raw/batch', { data: { ids } })
|
|
|
+export const batchDeleteClean = (ids: number[]) =>
|
|
|
+ api.delete<unknown, ApiResponse<{ deleted: number }>>('/merchants/clean/batch', { data: { ids } })
|
|
|
+export const assignMerchant = (id: number, assigned_to: string) =>
|
|
|
+ api.put<unknown, ApiResponse<MerchantClean>>(`/merchants/clean/${id}/assign`, { assigned_to })
|
|
|
+export const batchAssign = (ids: number[], assigned_to: string) =>
|
|
|
+ api.put<unknown, ApiResponse<{ updated: number }>>('/merchants/clean/batch-assign', { ids, assigned_to })
|
|
|
+export const batchFollowStatus = (ids: number[], follow_status: string) =>
|
|
|
+ api.put<unknown, ApiResponse<{ updated: number }>>('/merchants/clean/batch-follow-status', { ids, follow_status })
|
|
|
+export const batchLevel = (ids: number[], level: string) =>
|
|
|
+ api.put<unknown, ApiResponse<{ updated: number }>>('/merchants/clean/batch-level', { ids, level })
|
|
|
+export const getAssignableUsers = () =>
|
|
|
+ api.get<unknown, ApiResponse<AssignableUser[]>>('/merchants/clean/users')
|
|
|
+export const getIndustryTags = () =>
|
|
|
+ api.get<unknown, ApiResponse<string[]>>('/merchants/clean/industry-tags')
|
|
|
+export const mergeMerchants = (primary_id: number, secondary_id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<MerchantClean>>('/merchants/clean/merge', { primary_id, secondary_id })
|
|
|
+export const recheckMerchant = (id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<{ message: string }>>(`/merchants/clean/${id}/recheck`)
|
|
|
+export const batchRecheck = (ids: number[]) =>
|
|
|
+ api.post<unknown, ApiResponse<{ updated: number }>>('/merchants/clean/batch-recheck', { ids })
|
|
|
+export const importMerchantsCSV = (file: File) => {
|
|
|
+ const form = new FormData()
|
|
|
+ form.append('file', file)
|
|
|
+ return api.post<unknown, ApiResponse<{ imported: number; skipped: number; failed: number; errors: string[] }>>('/merchants/clean/import', form, {
|
|
|
+ headers: { 'Content-Type': 'multipart/form-data' },
|
|
|
+ })
|
|
|
+}
|
|
|
|
|
|
// Keywords
|
|
|
export const getKeywords = (params?: Record<string, unknown>) =>
|
|
|
@@ -116,3 +218,332 @@ export const createKeywords = (data: { keywords: string[]; industry_tag: string
|
|
|
export const updateKeyword = (id: number, data: Partial<Keyword>) =>
|
|
|
api.put<unknown, ApiResponse<Keyword>>(`/keywords/${id}`, data)
|
|
|
export const deleteKeyword = (id: number) => api.delete<unknown, ApiResponse<null>>(`/keywords/${id}`)
|
|
|
+export const importKeywordsCSV = (file: File, industryTag?: string) => {
|
|
|
+ const form = new FormData()
|
|
|
+ form.append('file', file)
|
|
|
+ if (industryTag) form.append('industry_tag', industryTag)
|
|
|
+ return api.post<unknown, ApiResponse<{ imported: number; skipped: number; errors: string[] }>>('/keywords/import', form, {
|
|
|
+ headers: { 'Content-Type': 'multipart/form-data' },
|
|
|
+ })
|
|
|
+}
|
|
|
+
|
|
|
+// Settings
|
|
|
+export interface LevelDef {
|
|
|
+ key: string
|
|
|
+ label: string
|
|
|
+ color: string
|
|
|
+ description: string
|
|
|
+ rules: GradeRule[]
|
|
|
+}
|
|
|
+
|
|
|
+export interface GradeRule {
|
|
|
+ has_industry?: boolean
|
|
|
+ has_website?: boolean
|
|
|
+ has_email?: boolean
|
|
|
+ has_phone?: boolean
|
|
|
+ min_source_count?: number
|
|
|
+}
|
|
|
+
|
|
|
+export interface GradingConfig {
|
|
|
+ levels: LevelDef[]
|
|
|
+ industry_keywords: Record<string, string[]>
|
|
|
+}
|
|
|
+
|
|
|
+export const getGradingConfig = () => api.get<unknown, ApiResponse<GradingConfig>>('/settings/grading')
|
|
|
+export const updateGradingConfig = (data: GradingConfig) => api.put<unknown, ApiResponse<GradingConfig>>('/settings/grading', data)
|
|
|
+export const resetGradingConfig = () => api.post<unknown, ApiResponse<GradingConfig>>('/settings/grading/reset')
|
|
|
+export const getLevelMap = () => api.get<unknown, ApiResponse<Record<string, { label: string; color: string; description: string }>>>('/settings/level-map')
|
|
|
+
|
|
|
+// Groups
|
|
|
+export interface GroupSummary {
|
|
|
+ group_username: string
|
|
|
+ group_title: string
|
|
|
+ member_count: number
|
|
|
+ source_type: string
|
|
|
+}
|
|
|
+
|
|
|
+export interface GroupMember {
|
|
|
+ id: number
|
|
|
+ group_username: string
|
|
|
+ member_username: string
|
|
|
+ group_title: string
|
|
|
+ source_type: string
|
|
|
+ task_id: number
|
|
|
+ discovered_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getGroups = (params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<GroupSummary>>>('/groups', { params })
|
|
|
+export const getGroupMembers = (username: string, params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<GroupMember>>>(`/groups/${username}/members`, { params })
|
|
|
+export const getMemberGroups = (username: string) =>
|
|
|
+ api.get<unknown, ApiResponse<GroupMember[]>>(`/member-groups/${username}`)
|
|
|
+
|
|
|
+export interface CloneMembersResult {
|
|
|
+ group_username: string
|
|
|
+ group_title: string
|
|
|
+ total_participants: number
|
|
|
+ with_username: number
|
|
|
+ new_saved: number
|
|
|
+ partial: boolean
|
|
|
+ status: string
|
|
|
+ progress: {
|
|
|
+ collected: number
|
|
|
+ total: number
|
|
|
+ queries_done: number
|
|
|
+ queries_total: number
|
|
|
+ }
|
|
|
+}
|
|
|
+export const cloneGroupMembers = (username: string, reset = false) =>
|
|
|
+ api.post<unknown, ApiResponse<CloneMembersResult>>(
|
|
|
+ `/groups/${username}/clone-members${reset ? '?reset=1' : ''}`
|
|
|
+ )
|
|
|
+
|
|
|
+export interface MemberSummary {
|
|
|
+ member_username: string
|
|
|
+ group_count: number
|
|
|
+ last_seen: string
|
|
|
+}
|
|
|
+export const searchMembers = (q: string, params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<MemberSummary>>>('/members/search', { params: { q, ...params } })
|
|
|
+
|
|
|
+// Task details (per-operation execution logs)
|
|
|
+export interface TaskDetail {
|
|
|
+ id: number
|
|
|
+ task_id: number
|
|
|
+ seq: number
|
|
|
+ action: string
|
|
|
+ url: string
|
|
|
+ parent_url: string
|
|
|
+ depth: number
|
|
|
+ input: string
|
|
|
+ output: string
|
|
|
+ status: string
|
|
|
+ duration_ms: number
|
|
|
+ extra: string
|
|
|
+ created_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export interface TaskDetailResponse {
|
|
|
+ items: TaskDetail[]
|
|
|
+ total: number
|
|
|
+ page: number
|
|
|
+ page_size: number
|
|
|
+ summary: Record<string, Record<string, number>>
|
|
|
+}
|
|
|
+
|
|
|
+export const getTaskDetails = (id: number, params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<TaskDetailResponse>>(`/tasks/${id}/details`, { params })
|
|
|
+
|
|
|
+// Schedules
|
|
|
+export interface ScheduleJob {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ plugin_name: string
|
|
|
+ cron_expr: string
|
|
|
+ enabled: boolean
|
|
|
+ last_run_at: string | null
|
|
|
+ next_run_at: string | null
|
|
|
+ created_at: string
|
|
|
+ updated_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getSchedules = () =>
|
|
|
+ api.get<unknown, ApiResponse<ScheduleJob[]>>('/schedules')
|
|
|
+export const createSchedule = (data: { name: string; plugin_name: string; cron_expr: string }) =>
|
|
|
+ api.post<unknown, ApiResponse<ScheduleJob>>('/schedules', data)
|
|
|
+export const updateSchedule = (id: number, data: Partial<Pick<ScheduleJob, 'name' | 'cron_expr' | 'enabled'>>) =>
|
|
|
+ api.put<unknown, ApiResponse<ScheduleJob>>(`/schedules/${id}`, data)
|
|
|
+export const deleteSchedule = (id: number) =>
|
|
|
+ api.delete<unknown, ApiResponse<null>>(`/schedules/${id}`)
|
|
|
+export const runScheduleNow = (id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<TaskLog>>(`/schedules/${id}/run`)
|
|
|
+
|
|
|
+// Audit logs
|
|
|
+export interface AuditLog {
|
|
|
+ id: number
|
|
|
+ username: string
|
|
|
+ action: string
|
|
|
+ target_type: string
|
|
|
+ target_id: string
|
|
|
+ detail: Record<string, unknown>
|
|
|
+ ip: string
|
|
|
+ created_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getAuditLogs = (params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<AuditLog>>>('/audit-logs', { params })
|
|
|
+
|
|
|
+// Notification configs
|
|
|
+export interface NotificationConfig {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ event_type: string
|
|
|
+ channel: string
|
|
|
+ config: Record<string, string>
|
|
|
+ enabled: boolean
|
|
|
+ created_at: string
|
|
|
+ updated_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getNotificationConfigs = () =>
|
|
|
+ api.get<unknown, ApiResponse<NotificationConfig[]>>('/notification-configs')
|
|
|
+export const createNotificationConfig = (data: Partial<NotificationConfig>) =>
|
|
|
+ api.post<unknown, ApiResponse<NotificationConfig>>('/notification-configs', data)
|
|
|
+export const updateNotificationConfig = (id: number, data: Partial<NotificationConfig>) =>
|
|
|
+ api.put<unknown, ApiResponse<NotificationConfig>>(`/notification-configs/${id}`, data)
|
|
|
+export const deleteNotificationConfig = (id: number) =>
|
|
|
+ api.delete<unknown, ApiResponse<null>>(`/notification-configs/${id}`)
|
|
|
+export const testNotificationConfig = (id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<{ message: string }>>(`/notification-configs/${id}/test`)
|
|
|
+
|
|
|
+// System health
|
|
|
+export interface SystemHealth {
|
|
|
+ mysql: { status: string; open_conns?: number; in_use?: number; idle?: number; max_open?: number; error?: string }
|
|
|
+ redis: { status: string; error?: string }
|
|
|
+ tg_accounts: Record<string, number>
|
|
|
+ tasks_24h: Record<string, number>
|
|
|
+ data: { merchants_raw: number; merchants_clean: number; task_details: number }
|
|
|
+}
|
|
|
+
|
|
|
+export const getSystemHealth = () =>
|
|
|
+ api.get<unknown, ApiResponse<SystemHealth>>('/system/health')
|
|
|
+
|
|
|
+// Archived merchants
|
|
|
+export interface MerchantArchived {
|
|
|
+ id: number
|
|
|
+ original_id: number
|
|
|
+ tg_username: string
|
|
|
+ merchant_name: string
|
|
|
+ level: string
|
|
|
+ status: string
|
|
|
+ follow_status: string
|
|
|
+ archive_reason: string
|
|
|
+ archived_at: string
|
|
|
+ created_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const archiveMerchants = (params?: { max_days_invalid?: number; max_days_rejected?: number }) =>
|
|
|
+ api.post<unknown, ApiResponse<{ archived: number; candidates: number }>>('/merchants/archive', params)
|
|
|
+export const getArchivedMerchants = (params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<MerchantArchived>>>('/merchants/archived', { params })
|
|
|
+export const restoreArchived = (id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<MerchantClean>>(`/merchants/archived/${id}/restore`)
|
|
|
+
|
|
|
+// Analytics
|
|
|
+export interface FunnelData {
|
|
|
+ raw_total: number
|
|
|
+ clean_total: number
|
|
|
+ valid_total: number
|
|
|
+ contacted: number
|
|
|
+ cooperating: number
|
|
|
+ rejected: number
|
|
|
+ conversion_rates: Record<string, number>
|
|
|
+}
|
|
|
+
|
|
|
+export interface SourceEfficiency {
|
|
|
+ by_source_type: { source_type: string; raw_count: number; clean_count: number; hot_count: number; efficiency: number }[]
|
|
|
+ top_keywords: { keyword: string; merchants_found: number }[]
|
|
|
+ top_groups: { group_username: string; members_found: number }[]
|
|
|
+}
|
|
|
+
|
|
|
+export interface TrendPeriod {
|
|
|
+ period_label: string
|
|
|
+ raw_added: number
|
|
|
+ clean_added: number
|
|
|
+}
|
|
|
+
|
|
|
+export const getAnalyticsFunnel = () =>
|
|
|
+ api.get<unknown, ApiResponse<FunnelData>>('/analytics/funnel')
|
|
|
+export const getAnalyticsSourceEfficiency = () =>
|
|
|
+ api.get<unknown, ApiResponse<SourceEfficiency>>('/analytics/source-efficiency')
|
|
|
+export const getAnalyticsTrends = (params?: { period?: string; range?: string }) =>
|
|
|
+ api.get<unknown, ApiResponse<{ period: string; data: TrendPeriod[] }>>('/analytics/trends', { params })
|
|
|
+
|
|
|
+// Proxies
|
|
|
+export interface Proxy {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ protocol: string
|
|
|
+ host: string
|
|
|
+ port: number
|
|
|
+ username: string
|
|
|
+ password: string
|
|
|
+ region: string
|
|
|
+ enabled: boolean
|
|
|
+ status: string
|
|
|
+ last_checked_at: string | null
|
|
|
+ remark: string
|
|
|
+ created_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getProxies = (params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<Proxy>>>('/proxies', { params })
|
|
|
+export const getEnabledProxies = () =>
|
|
|
+ api.get<unknown, ApiResponse<Proxy[]>>('/proxies/enabled')
|
|
|
+
|
|
|
+export interface ProxyPoolEntry {
|
|
|
+ id: number
|
|
|
+ name: string
|
|
|
+ url: string
|
|
|
+ region: string
|
|
|
+ failures: number
|
|
|
+ disabled: boolean
|
|
|
+ cool_down: string
|
|
|
+}
|
|
|
+export interface ProxyPoolStatus {
|
|
|
+ active: boolean
|
|
|
+ message?: string
|
|
|
+ total?: number
|
|
|
+ active_count?: number
|
|
|
+ proxies?: ProxyPoolEntry[]
|
|
|
+}
|
|
|
+export const getProxyPoolStatus = () =>
|
|
|
+ api.get<unknown, ApiResponse<ProxyPoolStatus>>('/proxies/pool-status')
|
|
|
+export const createProxy = (data: Partial<Proxy>) =>
|
|
|
+ api.post<unknown, ApiResponse<Proxy>>('/proxies', data)
|
|
|
+export const updateProxy = (id: number, data: Partial<Proxy>) =>
|
|
|
+ api.put<unknown, ApiResponse<Proxy>>(`/proxies/${id}`, data)
|
|
|
+export const deleteProxy = (id: number) =>
|
|
|
+ api.delete<unknown, ApiResponse<null>>(`/proxies/${id}`)
|
|
|
+export const testProxy = (id: number) =>
|
|
|
+ api.post<unknown, ApiResponse<{ status: string; proxy_url: string; error?: string }>>(`/proxies/${id}/test`)
|
|
|
+
|
|
|
+export interface TestAllResult {
|
|
|
+ total: number
|
|
|
+ ok: number
|
|
|
+ fail: number
|
|
|
+ results: { id: number; name: string; status: string; error?: string }[]
|
|
|
+}
|
|
|
+export const testAllProxies = () =>
|
|
|
+ api.post<unknown, ApiResponse<TestAllResult>>('/proxies/test-all')
|
|
|
+
|
|
|
+// Backup
|
|
|
+export const getBackupStats = () =>
|
|
|
+ api.get<unknown, ApiResponse<{ table: string; rows: number }[]>>('/backup/stats')
|
|
|
+
|
|
|
+// Channels
|
|
|
+export interface Channel {
|
|
|
+ id: number
|
|
|
+ username: string
|
|
|
+ status: string
|
|
|
+ merchants_found: number
|
|
|
+ source: string
|
|
|
+ created_at: string
|
|
|
+ updated_at: string
|
|
|
+}
|
|
|
+
|
|
|
+export const getChannels = (params?: Record<string, unknown>) =>
|
|
|
+ api.get<unknown, ApiResponse<PagedResponse<Channel>>>('/channels', { params })
|
|
|
+export const getChannelStats = () =>
|
|
|
+ api.get<unknown, ApiResponse<{ total: number; by_status: { key: string; count: number }[]; by_source: { key: string; count: number }[] }>>('/channels/stats')
|
|
|
+export const updateChannelStatus = (id: number, status: string) =>
|
|
|
+ api.put<unknown, ApiResponse<Channel>>(`/channels/${id}/status`, { status })
|
|
|
+export const deleteChannel = (id: number) =>
|
|
|
+ api.delete<unknown, ApiResponse<null>>(`/channels/${id}`)
|
|
|
+
|
|
|
+// Build WebSocket URL for task logs
|
|
|
+export function buildTaskLogWsUrl(taskId: number): string {
|
|
|
+ const proto = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
|
+ return `${proto}//${window.location.host}/api/v1/tasks/${taskId}/logs`
|
|
|
+}
|