dataflow/ui/src/api.js
2026-04-18 16:20:49 -04:00

135 lines
6.3 KiB
JavaScript

const BASE = '/api'
let _credentials = null // { user, pass }
export function setCredentials(user, pass) {
_credentials = { user, pass }
}
export function clearCredentials() {
_credentials = null
}
export function authHeaders() {
if (!_credentials) return {}
return { 'Authorization': `Basic ${btoa(`${_credentials.user}:${_credentials.pass}`)}` }
}
async function request(method, path, body, isFormData = false) {
const opts = { method, headers: {} }
if (_credentials) {
opts.headers['Authorization'] = `Basic ${btoa(`${_credentials.user}:${_credentials.pass}`)}`
}
if (body) {
if (isFormData) {
opts.body = body
} else {
opts.headers['Content-Type'] = 'application/json'
opts.body = JSON.stringify(body)
}
}
const res = await fetch(BASE + path, opts)
if (res.status === 401) {
clearCredentials()
const err = new Error('Unauthorized')
err.status = 401
throw err
}
const data = await res.json()
if (!res.ok) throw new Error(data.error || 'Request failed')
return data
}
export const api = {
// Sources
getSources: () => request('GET', '/sources'),
getSource: (name) => request('GET', `/sources/${name}`),
createSource: (body) => request('POST', '/sources', body),
updateSource: (name, body) => request('PUT', `/sources/${name}`, body),
deleteSource: (name) => request('DELETE', `/sources/${name}`),
suggestSource: (file) => {
const fd = new FormData()
fd.append('file', file)
return request('POST', '/sources/suggest', fd, true)
},
getImportLog: (name) => request('GET', `/sources/${name}/import-log`),
getAllImportLog: () => request('GET', '/sources/import-log'),
deleteImport: (name, id) => request('DELETE', `/sources/${name}/import-log/${id}`),
getStats: (name) => request('GET', `/sources/${name}/stats`),
importCSV: (name, file) => {
const fd = new FormData()
fd.append('file', file)
return request('POST', `/sources/${name}/import`, fd, true)
},
transform: (name) => request('POST', `/sources/${name}/transform`),
reprocess: (name) => request('POST', `/sources/${name}/reprocess`),
generateView: (name) => request('POST', `/sources/${name}/view`),
getFields: (name) => request('GET', `/sources/${name}/fields`),
getViewData: (name, limit = 100, offset = 0, sortCol = null, sortDir = 'asc', filters = null) => {
const params = new URLSearchParams({ limit, offset })
if (sortCol) { params.set('sort_col', sortCol); params.set('sort_dir', sortDir) }
if (filters && filters.length > 0) params.set('filters', JSON.stringify(filters))
return request('GET', `/sources/${name}/view-data?${params}`)
},
// Rules
getRules: (source) => request('GET', `/rules/source/${source}`),
createRule: (body) => request('POST', '/rules', body),
updateRule: (id, body) => request('PUT', `/rules/${id}`, body),
deleteRule: (id) => request('DELETE', `/rules/${id}`),
testRule: (id, limit = 20) => request('GET', `/rules/${id}/test?limit=${limit}`),
previewRule: (source, field, pattern, flags, function_type = 'extract', replace_value = '', limit = 20) =>
request('GET', `/rules/preview?source=${encodeURIComponent(source)}&field=${encodeURIComponent(field)}&pattern=${encodeURIComponent(pattern)}&flags=${encodeURIComponent(flags || '')}&function_type=${function_type}&replace_value=${encodeURIComponent(replace_value)}&limit=${limit}`),
// Mappings
getGlobalValues: () => request('GET', '/mappings/global-values'),
getMappings: (source, rule) => request('GET', `/mappings/source/${source}${rule ? `?rule_name=${rule}` : ''}`),
getMappingCounts: (source, rule) => request('GET', `/mappings/source/${source}/counts${rule ? `?rule_name=${rule}` : ''}`),
getUnmapped: (source, rule) => request('GET', `/mappings/source/${source}/unmapped${rule ? `?rule_name=${rule}` : ''}`),
getAllValues: (source, rule) => request('GET', `/mappings/source/${source}/all-values${rule ? `?rule_name=${rule}` : ''}`),
exportMappingsUrl: (source, rule) => `${BASE}/mappings/source/${source}/export.tsv${rule ? `?rule_name=${rule}` : ''}`,
importMappingsCSV: (source, file) => {
const fd = new FormData()
fd.append('file', file)
return request('POST', `/mappings/source/${source}/import-csv`, fd, true)
},
createMapping: (body) => request('POST', '/mappings', body),
bulkMappings: (mappings) => request('POST', '/mappings/bulk', { mappings }),
updateMapping: (id, body) => request('PUT', `/mappings/${id}`, body),
deleteMapping: (id) => request('DELETE', `/mappings/${id}`),
// Global remap
searchMappingOutputs: (search) => request('GET', `/mappings/outputs?search=${encodeURIComponent(search)}`),
getMappingsByOutputField: (col, val) => request('GET', `/mappings/outputs/${encodeURIComponent(col)}/${encodeURIComponent(val)}`),
remapOutputField: (col, from_val, to_val) => request('POST', '/mappings/remap-field', { col, from_val, to_val }),
// Pivot layouts
getPivotLayouts: (source) => request('GET', `/sources/${source}/layouts`),
savePivotLayout: (source, layout_name, config) => request('POST', `/sources/${source}/layouts`, { layout_name, config }),
deletePivotLayout: (source, id) => request('DELETE', `/sources/${source}/layouts/${id}`),
// Stacks
getStacks: () => request('GET', '/stacks'),
getStack: (name) => request('GET', `/stacks/${name}`),
createStack: (body) => request('POST', '/stacks', body),
updateStack: (name, body) => request('PUT', `/stacks/${name}`, body),
deleteStack: (name) => request('DELETE', `/stacks/${name}`),
upsertStackSource: (name, source, body) => request('PUT', `/stacks/${name}/sources/${source}`, body),
removeStackSource: (name, source) => request('DELETE', `/stacks/${name}/sources/${source}`),
generateStackView: (name) => request('POST', `/stacks/${name}/view`),
getStackBalance: (name) => request('GET', `/stacks/${name}/balance`),
calibrateBalance: (name, source, body) => request('POST', `/stacks/${name}/calibrate`, { ...body, source_name: source || null }),
// Records
getRecords: (source, limit = 100, offset = 0) =>
request('GET', `/records/source/${source}?limit=${limit}&offset=${offset}`),
getRecord: (id) => request('GET', `/records/${id}`),
setRecordOverrides: (id, overrides) => request('PUT', `/records/${id}/overrides`, { overrides }),
clearRecordOverrides: (id) => request('DELETE', `/records/${id}/overrides`),
}