0863c2db94
Mirrors IP scan flow: POST /zigbee/import-pending now creates a ScanRun(kind=zigbee, status=running) and returns immediately. Networkmap fetch + pending upsert run in the background, status transitions to done/error when finished. Frontend: import modal closes on submit, scan history shows the run with a ZIG/IP kind chip and toasts on completion. Pending modal auto-refreshes when run finishes. scan_runs.kind column added (default 'ip', idempotent migration). Existing zigbee tests refactored to exercise _persist_pending_import directly (background tasks don't see the test session); route test verifies the run is created with kind=zigbee.
138 lines
4.2 KiB
TypeScript
138 lines
4.2 KiB
TypeScript
import axios from 'axios'
|
|
import { useAuthStore } from '@/stores/authStore'
|
|
|
|
export const api = axios.create({
|
|
baseURL: '/api/v1',
|
|
})
|
|
|
|
// Unauthenticated axios instance — no JWT, no 401 redirect (used for public endpoints)
|
|
const publicApi = axios.create({ baseURL: '/api/v1' })
|
|
|
|
api.interceptors.request.use((config) => {
|
|
const token = useAuthStore.getState().token
|
|
if (token) config.headers.Authorization = `Bearer ${token}`
|
|
return config
|
|
})
|
|
|
|
api.interceptors.response.use(
|
|
(r) => r,
|
|
(err) => {
|
|
if (err.response?.status === 401) useAuthStore.getState().logout()
|
|
return Promise.reject(err)
|
|
}
|
|
)
|
|
|
|
export const authApi = {
|
|
login: (username: string, password: string) =>
|
|
api.post<{ access_token: string }>('/auth/login', { username, password }),
|
|
}
|
|
|
|
export const canvasApi = {
|
|
load: () => api.get('/canvas'),
|
|
save: (payload: {
|
|
nodes: object[]
|
|
edges: object[]
|
|
viewport: object
|
|
custom_style?: object | null
|
|
}) => api.post('/canvas/save', payload),
|
|
}
|
|
|
|
export const nodesApi = {
|
|
create: (data: object) => api.post('/nodes', data),
|
|
update: (id: string, data: object) => api.patch(`/nodes/${id}`, data),
|
|
delete: (id: string) => api.delete(`/nodes/${id}`),
|
|
}
|
|
|
|
export const edgesApi = {
|
|
create: (data: object) => api.post('/edges', data),
|
|
delete: (id: string) => api.delete(`/edges/${id}`),
|
|
}
|
|
|
|
export const liveviewApi = {
|
|
load: (key: string) => publicApi.get('/liveview', { params: { key } }),
|
|
}
|
|
|
|
export const scanApi = {
|
|
trigger: () => api.post('/scan/trigger'),
|
|
pending: () => api.get('/scan/pending'),
|
|
hidden: () => api.get('/scan/hidden'),
|
|
runs: () => api.get('/scan/runs'),
|
|
clearPending: () => api.delete('/scan/pending'),
|
|
approve: (id: string, nodeData: object) =>
|
|
api.post<{
|
|
approved: boolean
|
|
node_id: string
|
|
edges_created: number
|
|
edges: { id: string; source: string; target: string }[]
|
|
}>(`/scan/pending/${id}/approve`, nodeData),
|
|
hide: (id: string) => api.post(`/scan/pending/${id}/hide`),
|
|
ignore: (id: string) => api.post(`/scan/pending/${id}/ignore`),
|
|
bulkApprove: (ids: string[]) =>
|
|
api.post<{
|
|
approved: number
|
|
node_ids: string[]
|
|
device_ids: string[]
|
|
edges_created: number
|
|
edges: { id: string; source: string; target: string }[]
|
|
skipped: number
|
|
}>('/scan/pending/bulk-approve', { device_ids: ids }),
|
|
bulkHide: (ids: string[]) => api.post<{ hidden: number; skipped: number }>('/scan/pending/bulk-hide', { device_ids: ids }),
|
|
restore: (id: string) => api.post<{ restored: boolean; device_id: string }>(`/scan/pending/${id}/restore`),
|
|
bulkRestore: (ids: string[]) => api.post<{ restored: number; skipped: number }>('/scan/pending/bulk-restore', { device_ids: ids }),
|
|
stop: (runId: string) => api.post(`/scan/${runId}/stop`),
|
|
getConfig: () => api.get<{ ranges: string[] }>('/scan/config'),
|
|
saveConfig: (data: { ranges: string[] }) => api.post('/scan/config', data),
|
|
}
|
|
|
|
export const settingsApi = {
|
|
get: () => api.get<{ interval_seconds: number }>('/settings'),
|
|
save: (data: { interval_seconds: number }) => api.post<{ interval_seconds: number }>('/settings', data),
|
|
}
|
|
|
|
export const zigbeeApi = {
|
|
testConnection: (data: {
|
|
mqtt_host: string
|
|
mqtt_port: number
|
|
mqtt_username?: string
|
|
mqtt_password?: string
|
|
mqtt_tls?: boolean
|
|
mqtt_tls_insecure?: boolean
|
|
}) =>
|
|
api.post<{ connected: boolean; message: string }>('/zigbee/test-connection', data),
|
|
|
|
importNetwork: (data: {
|
|
mqtt_host: string
|
|
mqtt_port: number
|
|
mqtt_username?: string
|
|
mqtt_password?: string
|
|
base_topic?: string
|
|
mqtt_tls?: boolean
|
|
mqtt_tls_insecure?: boolean
|
|
}) =>
|
|
api.post<{
|
|
nodes: import('@/components/zigbee/types').ZigbeeNode[]
|
|
edges: import('@/components/zigbee/types').ZigbeeEdge[]
|
|
device_count: number
|
|
}>('/zigbee/import', data),
|
|
|
|
importToPending: (data: {
|
|
mqtt_host: string
|
|
mqtt_port: number
|
|
mqtt_username?: string
|
|
mqtt_password?: string
|
|
base_topic?: string
|
|
mqtt_tls?: boolean
|
|
mqtt_tls_insecure?: boolean
|
|
}) =>
|
|
api.post<{
|
|
id: string
|
|
status: string
|
|
kind: string
|
|
ranges: string[]
|
|
devices_found: number
|
|
started_at: string
|
|
finished_at: string | null
|
|
error: string | null
|
|
}>('/zigbee/import-pending', data),
|
|
}
|