Add remote browser pages and website sync
Enable managed remote browser custom pages with login autofill and add website sync workflows so external admin surfaces can be handled inside SmartUp. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -70,6 +70,111 @@ export const upstreamsApi = {
|
||||
api.get<any[]>(`/api/upstreams/${id}/snapshots`, { params: { limit, offset } }),
|
||||
}
|
||||
|
||||
// ——— Websites ———
|
||||
export interface WebsiteData {
|
||||
id: number
|
||||
name: string
|
||||
site_type: string
|
||||
base_url: string
|
||||
api_prefix: string
|
||||
auth_type: string
|
||||
auth_config_masked: Record<string, any>
|
||||
groups_endpoint: string
|
||||
group_update_endpoint: string
|
||||
enabled: boolean
|
||||
auto_sync_enabled: boolean
|
||||
timeout_seconds: number
|
||||
last_status: string
|
||||
last_checked_at: string | null
|
||||
last_error: string | null
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface WebsiteForm {
|
||||
name: string
|
||||
site_type: string
|
||||
base_url: string
|
||||
api_prefix: string
|
||||
auth_type: string
|
||||
auth_config: Record<string, any>
|
||||
groups_endpoint: string
|
||||
group_update_endpoint: string
|
||||
enabled: boolean
|
||||
auto_sync_enabled: boolean
|
||||
timeout_seconds: number
|
||||
}
|
||||
|
||||
export interface WebsiteGroup {
|
||||
id: string
|
||||
name: string
|
||||
rate_multiplier: string | null
|
||||
raw: Record<string, any>
|
||||
}
|
||||
|
||||
export interface BindingSourceGroup {
|
||||
upstream_id: number
|
||||
group_id: string
|
||||
upstream_name: string
|
||||
group_name: string
|
||||
}
|
||||
|
||||
export interface GroupBindingData {
|
||||
id: number
|
||||
website_id: number
|
||||
website_name: string
|
||||
target_group_id: string
|
||||
target_group_name: string
|
||||
source_groups: BindingSourceGroup[]
|
||||
percent: number
|
||||
algorithm: string
|
||||
enabled: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface GroupBindingForm {
|
||||
website_id: number
|
||||
target_group_id: string
|
||||
target_group_name: string
|
||||
source_groups: BindingSourceGroup[]
|
||||
percent: number
|
||||
algorithm: string
|
||||
enabled: boolean
|
||||
}
|
||||
|
||||
export interface WebsiteSyncLog {
|
||||
id: number
|
||||
website_id: number
|
||||
binding_id: number | null
|
||||
target_group_id: string
|
||||
target_group_name: string
|
||||
algorithm: string
|
||||
percent: number
|
||||
source_rates: Array<Record<string, any>>
|
||||
old_rate: string | null
|
||||
new_rate: string | null
|
||||
status: string
|
||||
message: string
|
||||
created_at: string
|
||||
}
|
||||
|
||||
export const websitesApi = {
|
||||
list: () => api.get<WebsiteData[]>('/api/websites'),
|
||||
create: (data: WebsiteForm) => api.post<WebsiteData>('/api/websites', data),
|
||||
update: (id: number, data: Partial<WebsiteForm>) => api.put<WebsiteData>(`/api/websites/${id}`, data),
|
||||
delete: (id: number) => api.delete(`/api/websites/${id}`),
|
||||
test: (id: number) => api.post<{ success: boolean; message: string; detail?: string }>(`/api/websites/${id}/test`),
|
||||
groups: (id: number) => api.get<WebsiteGroup[]>(`/api/websites/${id}/groups`),
|
||||
listBindings: () => api.get<GroupBindingData[]>('/api/group-bindings'),
|
||||
createBinding: (data: GroupBindingForm) => api.post<GroupBindingData>('/api/group-bindings', data),
|
||||
updateBinding: (id: number, data: Partial<GroupBindingForm>) => api.put<GroupBindingData>(`/api/group-bindings/${id}`, data),
|
||||
deleteBinding: (id: number) => api.delete(`/api/group-bindings/${id}`),
|
||||
syncNow: (id: number) => api.post<WebsiteSyncLog>(`/api/group-bindings/${id}/sync-now`),
|
||||
logs: (params?: { website_id?: number; binding_id?: number; limit?: number; offset?: number }) =>
|
||||
api.get<WebsiteSyncLog[]>('/api/website-sync-logs', { params }),
|
||||
}
|
||||
|
||||
// ——— Webhooks ———
|
||||
export interface WebhookData {
|
||||
id: number
|
||||
@@ -118,6 +223,8 @@ export const logsApi = {
|
||||
}
|
||||
|
||||
// ——— Custom Pages ———
|
||||
export type CustomPageAccessMode = 'direct' | 'proxy' | 'remote_browser'
|
||||
|
||||
export interface CustomPageData {
|
||||
id: number
|
||||
name: string
|
||||
@@ -126,7 +233,14 @@ export interface CustomPageData {
|
||||
sort_order: number
|
||||
enabled: boolean
|
||||
use_proxy: boolean
|
||||
access_mode: CustomPageAccessMode
|
||||
description: string | null
|
||||
login_username: string | null
|
||||
login_username_selector: string | null
|
||||
login_password_selector: string | null
|
||||
login_submit_selector: string | null
|
||||
login_autofill_enabled: boolean
|
||||
login_password_configured: boolean
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
@@ -138,7 +252,15 @@ export interface CustomPageForm {
|
||||
sort_order: number
|
||||
enabled: boolean
|
||||
use_proxy: boolean
|
||||
access_mode: CustomPageAccessMode
|
||||
description?: string
|
||||
login_username?: string
|
||||
login_password?: string
|
||||
login_username_selector?: string
|
||||
login_password_selector?: string
|
||||
login_submit_selector?: string
|
||||
login_autofill_enabled?: boolean
|
||||
login_password_clear?: boolean
|
||||
}
|
||||
|
||||
export const customPagesApi = {
|
||||
@@ -148,3 +270,40 @@ export const customPagesApi = {
|
||||
update: (id: number, data: Partial<CustomPageForm>) => api.put<CustomPageData>(`/api/custom-pages/${id}`, data),
|
||||
delete: (id: number) => api.delete(`/api/custom-pages/${id}`),
|
||||
}
|
||||
|
||||
// ——— Remote browser sessions ———
|
||||
export interface BrowserSessionData {
|
||||
id: string
|
||||
custom_page_id: number
|
||||
url: string
|
||||
title: string
|
||||
}
|
||||
|
||||
export type BrowserEventPayload =
|
||||
| { type: 'click' | 'dblclick' | 'mousemove' | 'mousedown' | 'mouseup'; x: number; y: number; button?: 'left' | 'right' | 'middle' }
|
||||
| { type: 'type'; text: string }
|
||||
| { type: 'key'; key: string }
|
||||
| { type: 'scroll'; delta_x: number; delta_y: number; x?: number; y?: number }
|
||||
| { type: 'reload' | 'back' | 'forward' }
|
||||
| { type: 'resize'; width: number; height: number }
|
||||
|
||||
export const browserSessionsApi = {
|
||||
create: (data: { custom_page_id: number; width: number; height: number }) =>
|
||||
api.post<BrowserSessionData>('/api/browser-sessions', data),
|
||||
get: (id: string) => api.get<BrowserSessionData>(`/api/browser-sessions/${id}`),
|
||||
event: (id: string, data: BrowserEventPayload) =>
|
||||
api.post<BrowserSessionData>(`/api/browser-sessions/${id}/events`, data),
|
||||
close: (id: string) => api.delete(`/api/browser-sessions/${id}`),
|
||||
screenshotUrl: (id: string, token?: string) => {
|
||||
const params = new URLSearchParams({ t: String(Date.now()) })
|
||||
if (token) params.set('token', token)
|
||||
return `/api/browser-sessions/${id}/screenshot?${params.toString()}`
|
||||
},
|
||||
/** Build a WebSocket URL for the streaming endpoint. */
|
||||
wsUrl: (id: string, token?: string) => {
|
||||
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:'
|
||||
const params = new URLSearchParams()
|
||||
if (token) params.set('token', token)
|
||||
return `${proto}//${location.host}/api/browser-sessions/${id}/ws?${params.toString()}`
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user