Files
homelable/frontend/src/api/client.ts
T
Pouzor babbcb1dc5 feat: add custom style theme with per-type node/edge style editor
- New 'custom' ThemeId with dedicated ThemeCard + pencil edit button
- CustomStyleModal: two-column editor for all node types (excl. groupRect/group) and edge types
  - Per node: border, background, icon color + opacity, default size (w/h)
  - Per edge: color + opacity, path style, animation
  - 'Apply to existing [Type]' per type, 'Apply All to Canvas' footer action
- canvasStore: applyTypeNodeStyle, applyTypeEdgeStyle, applyAllCustomStyles actions
- themeStore: customStyle state + setCustomStyle action
- Backend: custom_style JSON column on canvas_state (ALTER TABLE migration), saved/loaded with canvas
- App.tsx: custom_style included in save payload, restored on load (API + standalone)
- Tests: +8 frontend (themeStore + canvasStore), +3 backend (canvas API)
2026-04-24 11:42:31 +02:00

75 lines
2.6 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(`/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[]; 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 }),
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),
}