get_pagination_params clamps perpage to MAX_PAGE_SIZE (100) and reports nothing about having done so, so a caller asking for perpage: 1000 gets the first 100 rows and a success response. Every picker built that way looked complete and was not. Found on a live site with 126 active applications: the 26 sorting last were absent from the knowledge-base topic dropdown, so an article could not be filed against them. Nothing was wrong with those application records, and editing them could never have helped. Adds fetchAllPages() to the api module, generalizing the one call site that already handled this correctly (modelsApi.listAll), and points the four application pickers at a new applicationsApi.listAll(): the KB article form, the KB list's topic filter, the notification form, and the report filter builder. Lists that render a page at a time are untouched - they page for a reason. Other callers still asking for more than 100 rows of vendors, locations, models, subnets and the rest are latent: correct only while those tables stay under 100, and silent on the day they do not.
1309 lines
34 KiB
JavaScript
1309 lines
34 KiB
JavaScript
import axios from 'axios'
|
|
import { withBase, stripBase } from './../utils/basePath'
|
|
|
|
// BASE_URL ends in '/', so this is '/api' at root or '/ops/api' under a subpath
|
|
// mount. Keeps the SPA, its API, and IIS all on the same mount path.
|
|
const api = axios.create({
|
|
baseURL: import.meta.env.BASE_URL + 'api',
|
|
headers: {
|
|
'Content-Type': 'application/json'
|
|
}
|
|
})
|
|
|
|
// Add auth token and normalize pagination params
|
|
api.interceptors.request.use(config => {
|
|
const token = localStorage.getItem('token')
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
}
|
|
// Send both perpage and per_page for backend compatibility
|
|
if (config.params?.perpage) {
|
|
config.params.per_page = config.params.perpage
|
|
}
|
|
return config
|
|
})
|
|
|
|
// Handle 401 errors (token expired) - only redirect if user was logged in
|
|
api.interceptors.response.use(
|
|
response => response,
|
|
error => {
|
|
if (error.response?.status === 401) {
|
|
const hadToken = localStorage.getItem('token')
|
|
localStorage.removeItem('token')
|
|
localStorage.removeItem('user')
|
|
// Only redirect if user was previously logged in (session expired).
|
|
// Preserve the destination so login returns the user to this page.
|
|
if (hadToken) {
|
|
const loginPath = withBase('/login')
|
|
// Router paths exclude the mount base; strip it or login's
|
|
// router.push double-prefixes under a subpath mount.
|
|
const here = stripBase(window.location.pathname) + window.location.search
|
|
const target = here && here !== '/login'
|
|
? loginPath + '?redirect=' + encodeURIComponent(here)
|
|
: loginPath
|
|
window.location.href = target
|
|
}
|
|
}
|
|
return Promise.reject(error)
|
|
}
|
|
)
|
|
|
|
export default api
|
|
|
|
// The backend clamps perpage to MAX_PAGE_SIZE (100) and says nothing about it,
|
|
// so asking for `perpage: 1000` silently returns the first 100 rows and drops
|
|
// the rest. A picker built that way looks complete and is not: with 126
|
|
// applications on a live site, the 26 sorting last were simply unselectable.
|
|
//
|
|
// Use this wherever a control needs the WHOLE list (dropdowns, pickers, label
|
|
// batches) rather than a page of it. Returns the full array directly, not an
|
|
// axios response. Anything that renders a paged table should keep calling
|
|
// list() with a real page number instead.
|
|
export async function fetchAllPages(path, params = {}) {
|
|
const first = await api.get(path, { params: { ...params, perpage: 100, page: 1 } })
|
|
let items = first.data.data || []
|
|
const totalpages = first.data.meta?.pagination?.totalpages || 1
|
|
if (totalpages > 1) {
|
|
const rest = await Promise.all(
|
|
Array.from({ length: totalpages - 1 }, (_, i) =>
|
|
api.get(path, { params: { ...params, perpage: 100, page: i + 2 } })
|
|
)
|
|
)
|
|
rest.forEach(response => { items = items.concat(response.data.data || []) })
|
|
}
|
|
return items
|
|
}
|
|
|
|
// Auth API
|
|
export const authApi = {
|
|
login(username, password) {
|
|
return api.post('/auth/login', { username, password })
|
|
},
|
|
logout() {
|
|
return api.post('/auth/logout')
|
|
},
|
|
me() {
|
|
return api.get('/auth/me')
|
|
},
|
|
refresh() {
|
|
const refreshToken = localStorage.getItem('refreshToken')
|
|
return api.post('/auth/refresh', {}, {
|
|
headers: { Authorization: `Bearer ${refreshToken}` }
|
|
})
|
|
},
|
|
changePassword(payload) {
|
|
return api.post('/auth/change-password', payload)
|
|
}
|
|
}
|
|
|
|
// Machines API (plugin)
|
|
export const machinesApi = {
|
|
list(params = {}) {
|
|
return api.get('/machines', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/machines/${id}`)
|
|
},
|
|
getByAsset(assetId) {
|
|
return api.get(`/machines/by-asset/${assetId}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/machines', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/machines/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/machines/${id}`)
|
|
},
|
|
dashboardSummary() {
|
|
return api.get('/machines/dashboard/summary')
|
|
},
|
|
// Machine types
|
|
types: {
|
|
list(params = {}) {
|
|
return api.get('/machines/types', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/machines/types/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/machines/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/machines/types/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/machines/types/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Computers API (plugin)
|
|
export const computersApi = {
|
|
list(params = {}) {
|
|
return api.get('/computers', { params })
|
|
},
|
|
displayKiosks() {
|
|
return api.get('/computers/display-kiosks')
|
|
},
|
|
get(id) {
|
|
return api.get(`/computers/${id}`)
|
|
},
|
|
getByAsset(assetId) {
|
|
return api.get(`/computers/by-asset/${assetId}`)
|
|
},
|
|
getByHostname(hostname) {
|
|
return api.get(`/computers/by-hostname/${hostname}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/computers', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/computers/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/computers/${id}`)
|
|
},
|
|
dashboardSummary() {
|
|
return api.get('/computers/dashboard/summary')
|
|
},
|
|
// Computer types
|
|
types: {
|
|
list(params = {}) {
|
|
return api.get('/computers/types', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/computers/types/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/computers/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/computers/types/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/computers/types/${id}`)
|
|
}
|
|
},
|
|
// Remote-access protocol catalog
|
|
protocols: {
|
|
list(params = {}) {
|
|
return api.get('/computers/protocols', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/computers/protocols', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/computers/protocols/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/computers/protocols/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Relationship Types API
|
|
export const relationshipTypesApi = {
|
|
list() {
|
|
return api.get('/assets/relationshiptypes')
|
|
},
|
|
create(data) {
|
|
return api.post('/assets/relationshiptypes', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/assets/relationshiptypes/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/assets/relationshiptypes/${id}`)
|
|
}
|
|
}
|
|
|
|
// Model Types API (the vendor models catalog: modeltypeid)
|
|
export const modeltypesApi = {
|
|
list(params = {}) {
|
|
return api.get('/modeltypes', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/modeltypes', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/modeltypes/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/modeltypes/${id}`)
|
|
}
|
|
}
|
|
|
|
// Vendors API
|
|
export const vendorsApi = {
|
|
list(params = {}) {
|
|
return api.get('/vendors', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/vendors/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/vendors', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/vendors/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/vendors/${id}`)
|
|
}
|
|
}
|
|
|
|
// Locations API
|
|
export const locationsApi = {
|
|
list(params = {}) {
|
|
return api.get('/locations', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/locations/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/locations', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/locations/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/locations/${id}`)
|
|
},
|
|
types: {
|
|
list(params = {}) {
|
|
return api.get('/locations/types', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/locations/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/locations/types/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/locations/types/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Printers API
|
|
export const printersApi = {
|
|
list(params = {}) {
|
|
return api.get('/printers', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/printers/${id}`)
|
|
},
|
|
// create/update write asset core + printer extension in one call (the
|
|
// printers plugin owns both). Use these instead of the legacy machinesApi.
|
|
create(data) {
|
|
return api.post('/printers', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/printers/${id}`, data)
|
|
},
|
|
updateExtension(id, data) {
|
|
return api.put(`/printers/${id}/printerdata`, data)
|
|
},
|
|
// printer sub-types (Laser, Inkjet, Label, Card, Wide Format, ...)
|
|
types: {
|
|
list(params = {}) {
|
|
return api.get('/printers/types', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/printers/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/printers/types/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/printers/types/${id}`)
|
|
}
|
|
},
|
|
updateCommunication(id, data) {
|
|
return api.put(`/printers/${id}/communication`, data)
|
|
},
|
|
getSupplies(id) {
|
|
return api.get(`/printers/${id}/supplies`)
|
|
},
|
|
getDrivers(id) {
|
|
return api.get(`/printers/${id}/drivers`)
|
|
},
|
|
supplyForecast(days = 90) {
|
|
return api.get('/printers/supplies/forecast', { params: { days } })
|
|
},
|
|
lowSupplies() {
|
|
return api.get('/printers/lowsupplies')
|
|
},
|
|
refreshSupplies() {
|
|
return api.post('/printers/supplies/refresh')
|
|
},
|
|
lookup({ ip, fqdn } = {}) {
|
|
return api.get('/printers/lookup', { params: { ip, fqdn } })
|
|
},
|
|
dashboardSummary() {
|
|
return api.get('/printers/dashboard/summary')
|
|
},
|
|
// Flat network-printer list (with mapx/mapy) for the installer map.
|
|
installList() {
|
|
return api.get('/printers/install-list')
|
|
},
|
|
drivers: {
|
|
list(params = {}) {
|
|
return api.get('/printers/drivers', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/printers/drivers', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/printers/drivers/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/printers/drivers/${id}`)
|
|
}
|
|
},
|
|
supplyTypes: {
|
|
list() {
|
|
return api.get('/printers/supplytypes')
|
|
},
|
|
create(data) {
|
|
return api.post('/printers/supplytypes', data)
|
|
}
|
|
},
|
|
// model -> toner/drum/waste part-number management
|
|
modelSupplies: {
|
|
meta() {
|
|
return api.get('/printers/supplies/meta')
|
|
},
|
|
listModels(params = {}) {
|
|
return api.get('/printers/models', { params })
|
|
},
|
|
list(modelnumberid) {
|
|
return api.get(`/printers/models/${modelnumberid}/supplies`)
|
|
},
|
|
create(modelnumberid, data) {
|
|
return api.post(`/printers/models/${modelnumberid}/supplies`, data)
|
|
},
|
|
update(modelsupplyid, data) {
|
|
return api.put(`/printers/supplies/${modelsupplyid}`, data)
|
|
},
|
|
delete(modelsupplyid) {
|
|
return api.delete(`/printers/supplies/${modelsupplyid}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dashboard API
|
|
export const dashboardApi = {
|
|
summary() {
|
|
return api.get('/dashboard/summary')
|
|
},
|
|
navigation() {
|
|
return api.get('/dashboard/navigation')
|
|
}
|
|
}
|
|
|
|
// Models API
|
|
export const modelsApi = {
|
|
list(params = {}) {
|
|
return api.get('/models', { params })
|
|
},
|
|
// Backend caps perpage at 100, so page through every model. Returns the
|
|
// full array directly (not an axios response). Use in forms whose model
|
|
// dropdown must include the editing record's model regardless of page.
|
|
listAll(params = {}) {
|
|
return fetchAllPages('/models', params)
|
|
},
|
|
get(id) {
|
|
return api.get(`/models/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/models', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/models/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/models/${id}`)
|
|
},
|
|
uploadImage(id, file) {
|
|
// multipart photo upload; backend sets imageurl to the served URL
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
return api.post(`/models/${id}/image`, form, { headers: { 'Content-Type': 'multipart/form-data' } })
|
|
},
|
|
removeImage(id) {
|
|
return api.delete(`/models/${id}/image`)
|
|
}
|
|
}
|
|
|
|
// Operating Systems API
|
|
export const operatingsystemsApi = {
|
|
list(params = {}) {
|
|
return api.get('/operatingsystems', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/operatingsystems/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/operatingsystems', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/operatingsystems/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/operatingsystems/${id}`)
|
|
}
|
|
}
|
|
|
|
// Business Units API
|
|
export const businessunitsApi = {
|
|
list(params = {}) {
|
|
return api.get('/businessunits', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/businessunits/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/businessunits', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/businessunits/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/businessunits/${id}`)
|
|
}
|
|
}
|
|
|
|
// Applications API
|
|
export const applicationsApi = {
|
|
list(params = {}) {
|
|
return api.get('/applications', { params })
|
|
},
|
|
// Every application, paged past the backend's 100-row cap. The catalogue is
|
|
// already over 100 entries on a live site, so any picker offering "all
|
|
// applications" must use this and not list({ perpage: <big number> }).
|
|
listAll(params = {}) {
|
|
return fetchAllPages('/applications', params)
|
|
},
|
|
get(id) {
|
|
return api.get(`/applications/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/applications', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/applications/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/applications/${id}`)
|
|
},
|
|
// multipart image upload; backend sets image to the served URL
|
|
uploadImage(id, file) {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
return api.post(`/applications/${id}/image`, form, { headers: { 'Content-Type': 'multipart/form-data' } })
|
|
},
|
|
removeImage(id) {
|
|
return api.delete(`/applications/${id}/image`)
|
|
},
|
|
// installer upload; backend sets installpath to the download URL.
|
|
// onProgress gets 0-100 - an installer is big enough that a silent wait
|
|
// reads as a hang.
|
|
uploadPackage(id, file, onProgress) {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
return api.post(`/applications/${id}/package`, form, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
onUploadProgress: event => {
|
|
if (onProgress && event.total) {
|
|
onProgress(Math.round((event.loaded * 100) / event.total))
|
|
}
|
|
}
|
|
})
|
|
},
|
|
removePackage(id) {
|
|
return api.delete(`/applications/${id}/package`)
|
|
},
|
|
// Versions
|
|
getVersions(appId) {
|
|
return api.get(`/applications/${appId}/versions`)
|
|
},
|
|
createVersion(appId, data) {
|
|
return api.post(`/applications/${appId}/versions`, data)
|
|
},
|
|
// Get PCs that have this app installed
|
|
getInstalledOn(appId) {
|
|
return api.get(`/applications/${appId}/installed`)
|
|
},
|
|
// Machine applications (installed apps)
|
|
getMachineApps(machineId) {
|
|
return api.get(`/applications/machines/${machineId}`)
|
|
},
|
|
installApp(machineId, data) {
|
|
return api.post(`/applications/machines/${machineId}`, data)
|
|
},
|
|
uninstallApp(machineId, appId) {
|
|
return api.delete(`/applications/machines/${machineId}/${appId}`)
|
|
},
|
|
updateInstalledApp(machineId, appId, data) {
|
|
return api.put(`/applications/machines/${machineId}/${appId}`, data)
|
|
}
|
|
}
|
|
|
|
// Support Teams API (teams + nested contacts)
|
|
export const supportteamsApi = {
|
|
list(params = {}) {
|
|
return api.get('/supportteams', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/supportteams/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/supportteams', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/supportteams/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/supportteams/${id}`)
|
|
},
|
|
contacts: {
|
|
add(teamId, data) {
|
|
return api.post(`/supportteams/${teamId}/contacts`, data)
|
|
},
|
|
update(teamId, contactId, data) {
|
|
return api.put(`/supportteams/${teamId}/contacts/${contactId}`, data)
|
|
},
|
|
remove(teamId, contactId) {
|
|
return api.delete(`/supportteams/${teamId}/contacts/${contactId}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Search API
|
|
export const searchApi = {
|
|
search(query, params = {}) {
|
|
return api.get('/search', { params: { q: query, ...params } })
|
|
}
|
|
}
|
|
|
|
// Knowledge Base API
|
|
export const knowledgebaseApi = {
|
|
list(params = {}) {
|
|
return api.get('/knowledgebase', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/knowledgebase/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/knowledgebase', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/knowledgebase/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/knowledgebase/${id}`)
|
|
},
|
|
trackClick(id) {
|
|
return api.post(`/knowledgebase/${id}/click`)
|
|
},
|
|
getStats() {
|
|
return api.get('/knowledgebase/stats')
|
|
}
|
|
}
|
|
|
|
// Assets API (unified)
|
|
export const assetsApi = {
|
|
list(params = {}) {
|
|
return api.get('/assets', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/assets/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/assets', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/assets/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/assets/${id}`)
|
|
},
|
|
getMap(params = {}) {
|
|
return api.get('/assets/map', { params })
|
|
},
|
|
// Relationships
|
|
getRelationships(id) {
|
|
return api.get(`/assets/${id}/relationships`)
|
|
},
|
|
createRelationship(data) {
|
|
return api.post('/assets/relationships', data)
|
|
},
|
|
deleteRelationship(relationshipId) {
|
|
return api.delete(`/assets/relationships/${relationshipId}`)
|
|
},
|
|
// Search assets (for relationship picker)
|
|
search(query, params = {}) {
|
|
return api.get('/assets', { params: { search: query, ...params } })
|
|
},
|
|
// Lookup asset by asset/machine number
|
|
lookup(assetnumber) {
|
|
return api.get(`/assets/lookup/${encodeURIComponent(assetnumber)}`)
|
|
},
|
|
types: {
|
|
list() {
|
|
return api.get('/assets/types')
|
|
},
|
|
get(id) {
|
|
return api.get(`/assets/types/${id}`)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/assets/types/${id}`, data)
|
|
}
|
|
},
|
|
statuses: {
|
|
list(params = {}) {
|
|
return api.get('/assets/statuses', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/assets/statuses', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/assets/statuses/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/assets/statuses/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Notifications API
|
|
export const notificationsApi = {
|
|
list(params = {}) {
|
|
return api.get('/notifications', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/notifications/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/notifications', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/notifications/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/notifications/${id}`)
|
|
},
|
|
getActive() {
|
|
return api.get('/notifications/active')
|
|
},
|
|
getCalendar(params = {}) {
|
|
return api.get('/notifications/calendar', { params })
|
|
},
|
|
dashboardSummary() {
|
|
return api.get('/notifications/dashboard/summary')
|
|
},
|
|
getShopfloor(params = {}) {
|
|
return api.get('/notifications/shopfloor', { params })
|
|
},
|
|
getEmployeeRecognitions(sso) {
|
|
return api.get(`/notifications/employee/${sso}`)
|
|
},
|
|
types: {
|
|
list() {
|
|
return api.get('/notifications/types')
|
|
},
|
|
get(id) {
|
|
return api.get(`/notifications/types/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/notifications/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/notifications/types/${id}`, data)
|
|
}
|
|
}
|
|
}
|
|
|
|
// USB Devices API
|
|
export const usbApi = {
|
|
list(params = {}) {
|
|
return api.get('/usb', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/usb/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/usb', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/usb/${id}`, data)
|
|
},
|
|
checkout(id, data) {
|
|
return api.post(`/usb/${id}/checkout`, data)
|
|
},
|
|
checkin(id, data = {}) {
|
|
return api.post(`/usb/${id}/checkin`, data)
|
|
},
|
|
getHistory(id, params = {}) {
|
|
return api.get(`/usb/${id}/history`, { params })
|
|
},
|
|
// check-out log rows for one badge; activeonly = only currently-held devices
|
|
getUserCheckouts(badge, activeonly = true) {
|
|
const path = activeonly ? '/usb/checkouts/active' : '/usb/checkouts'
|
|
return api.get(path, { params: { badge } })
|
|
}
|
|
}
|
|
|
|
// Reports API
|
|
export const reportsApi = {
|
|
list() {
|
|
return api.get('/reports')
|
|
},
|
|
machinesByType(params = {}) {
|
|
return api.get('/reports/machines-by-type', { params })
|
|
},
|
|
assetsByStatus(params = {}) {
|
|
return api.get('/reports/assets-by-status', { params })
|
|
},
|
|
kbPopularity(params = {}) {
|
|
return api.get('/reports/kb-popularity', { params })
|
|
},
|
|
softwareCompliance(params = {}) {
|
|
return api.get('/reports/software-compliance', { params })
|
|
},
|
|
assetInventory(params = {}) {
|
|
return api.get('/reports/asset-inventory', { params })
|
|
},
|
|
pcRelationships(params = {}) {
|
|
return api.get('/reports/pc-relationships', { params })
|
|
},
|
|
// On-demand report delivery: email the given rows as an HTML table.
|
|
// Recipients default to the site Alert Recipients when `to` is omitted.
|
|
email(payload) {
|
|
return api.post('/reports/email', payload)
|
|
}
|
|
}
|
|
|
|
// Employees API (wjf_employees database)
|
|
export const employeesApi = {
|
|
search(query, limit = 10) {
|
|
return api.get('/employees/search', { params: { q: query, limit } })
|
|
},
|
|
lookup(sso) {
|
|
return api.get(`/employees/lookup/${sso}`)
|
|
},
|
|
lookupMultiple(ssoList) {
|
|
return api.get('/employees/lookup', { params: { sso: ssoList } })
|
|
},
|
|
// Self-hosted directory management (directory_mode=selfhosted)
|
|
directory: {
|
|
list() {
|
|
return api.get('/employees/directory')
|
|
},
|
|
create(data) {
|
|
return api.post('/employees/directory', data)
|
|
},
|
|
update(sso, data) {
|
|
return api.put(`/employees/directory/${sso}`, data)
|
|
},
|
|
remove(sso) {
|
|
return api.delete(`/employees/directory/${sso}`)
|
|
},
|
|
importCsv(csv) {
|
|
return api.post('/employees/directory/import', { csv })
|
|
},
|
|
// multipart photo upload; backend sets photofilename + returns photourl
|
|
uploadPhoto(sso, file) {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
return api.post(`/employees/${sso}/photo`, form, { headers: { 'Content-Type': 'multipart/form-data' } })
|
|
},
|
|
removePhoto(sso) {
|
|
return api.delete(`/employees/${sso}/photo`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Alias for different casing
|
|
export const businessUnitsApi = businessunitsApi
|
|
|
|
// Dashboard defaults: visitor-IP -> business-unit mapping for kiosks
|
|
export const dashboardDefaultsApi = {
|
|
list() {
|
|
return api.get('/dashboarddefaults')
|
|
},
|
|
create(data) {
|
|
return api.post('/dashboarddefaults', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/dashboarddefaults/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/dashboarddefaults/${id}`)
|
|
},
|
|
// Resolve the calling display's business unit by its IP
|
|
visitorLocation() {
|
|
return api.get('/dashboarddefaults/visitor-location')
|
|
}
|
|
}
|
|
|
|
// System Settings API
|
|
export const pluginsApi = {
|
|
list() {
|
|
return api.get('/plugins')
|
|
},
|
|
// Flat array of enabled plugin names. jwt-optional, safe for kiosk routes.
|
|
enabled() {
|
|
return api.get('/plugins/enabled')
|
|
},
|
|
setEnabled(name, enabled) {
|
|
return api.put(`/plugins/${name}`, { enabled })
|
|
}
|
|
}
|
|
|
|
export const setupApi = {
|
|
needsAdmin() {
|
|
return api.get('/setup/needs-admin')
|
|
},
|
|
createAdmin(data) {
|
|
return api.post('/setup/create-admin', data)
|
|
},
|
|
seedReference() {
|
|
return api.post('/setup/seed-reference')
|
|
},
|
|
seedStarter() {
|
|
return api.post('/setup/seed-starter')
|
|
},
|
|
complete() {
|
|
return api.post('/setup/complete')
|
|
}
|
|
}
|
|
|
|
// Buildings and the levels within them (ADR-017). Reads are public - the
|
|
// printer installer map draws a blueprint before anyone logs in.
|
|
export const mapLevelsApi = {
|
|
list() {
|
|
return api.get('/maplevels')
|
|
},
|
|
get(levelid) {
|
|
return api.get(`/maplevels/${levelid}`)
|
|
},
|
|
createBuilding(payload) {
|
|
return api.post('/maplevels/buildings', payload)
|
|
},
|
|
updateBuilding(buildingid, payload) {
|
|
return api.patch(`/maplevels/buildings/${buildingid}`, payload)
|
|
},
|
|
create(payload) {
|
|
return api.post('/maplevels', payload)
|
|
},
|
|
update(levelid, payload) {
|
|
return api.patch(`/maplevels/${levelid}`, payload)
|
|
},
|
|
remove(levelid) {
|
|
return api.delete(`/maplevels/${levelid}`)
|
|
},
|
|
// Returns the image's real pixel size alongside the stored dimensions. On an
|
|
// empty level the server adopts them; on a populated one it refuses and says
|
|
// so, because changing the coordinate space moves every marker on it.
|
|
uploadBlueprint(levelid, theme, file) {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
form.append('theme', theme)
|
|
return api.post(`/maplevels/${levelid}/blueprint`, form, {
|
|
headers: { 'Content-Type': 'multipart/form-data' },
|
|
})
|
|
},
|
|
}
|
|
|
|
// Bulk marker positions: the landmark transform, bulk placement, review state
|
|
// and undo. Every write snapshots first.
|
|
export const mapPositionsApi = {
|
|
setPositions(positions, verified = true) {
|
|
return api.post('/mappositions/positions', { positions, verified })
|
|
},
|
|
transform(payload) {
|
|
return api.post('/mappositions/transform', payload)
|
|
},
|
|
verify(assetids, unverify = false) {
|
|
return api.post('/mappositions/verify', { assetids, unverify })
|
|
},
|
|
snapshots() {
|
|
return api.get('/mappositions/snapshots')
|
|
},
|
|
restore(snapshotid) {
|
|
return api.post(`/mappositions/snapshots/${snapshotid}/restore`)
|
|
},
|
|
}
|
|
|
|
export const settingsApi = {
|
|
list(params = {}) {
|
|
return api.get('/settings', { params })
|
|
},
|
|
get(key) {
|
|
return api.get(`/settings/${key}`)
|
|
},
|
|
update(key, value) {
|
|
return api.put(`/settings/${key}`, { value })
|
|
},
|
|
testEmail(to) {
|
|
return api.post('/settings/test-email', { to })
|
|
},
|
|
create(data) {
|
|
return api.post('/settings', data)
|
|
},
|
|
uploadMapBlueprint(theme, file) {
|
|
const form = new FormData()
|
|
form.append('theme', theme)
|
|
form.append('file', file)
|
|
return api.post('/settings/map-blueprint', form, { headers: { 'Content-Type': 'multipart/form-data' } })
|
|
},
|
|
uploadBrandingLogo(kind, file) {
|
|
// kind is one of site/qr/badge/favicon; backend writes the matching setting
|
|
const form = new FormData()
|
|
form.append('kind', kind)
|
|
form.append('file', file)
|
|
return api.post('/settings/branding-logo', form, { headers: { 'Content-Type': 'multipart/form-data' } })
|
|
}
|
|
}
|
|
|
|
// Slide manager (lobby display + shopfloor screensaver)
|
|
export const slidesApi = {
|
|
list(surface) {
|
|
return api.get(`/slides/${surface}`)
|
|
},
|
|
upload(surface, formData) {
|
|
return api.post(`/slides/${surface}/upload`, formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
})
|
|
},
|
|
reorder(surface, order) {
|
|
return api.post(`/slides/${surface}/order`, { order })
|
|
},
|
|
remove(surface, files) {
|
|
return api.post(`/slides/${surface}/delete`, { files })
|
|
}
|
|
}
|
|
|
|
// Audit Logs API
|
|
export const auditLogsApi = {
|
|
list(params = {}) {
|
|
return api.get('/auditlogs', { params })
|
|
},
|
|
getEntityHistory(entitytype, entityid) {
|
|
return api.get(`/auditlogs/entity/${entitytype}/${entityid}`)
|
|
},
|
|
getStats() {
|
|
return api.get('/auditlogs/stats')
|
|
}
|
|
}
|
|
|
|
// Users API
|
|
export const usersApi = {
|
|
list() {
|
|
return api.get('/users')
|
|
},
|
|
get(id) {
|
|
return api.get(`/users/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/users', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/users/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/users/${id}`)
|
|
},
|
|
// Permissions
|
|
permissions: {
|
|
list() {
|
|
return api.get('/users/permissions')
|
|
}
|
|
},
|
|
// Roles
|
|
roles: {
|
|
list() {
|
|
return api.get('/users/roles')
|
|
},
|
|
create(data) {
|
|
return api.post('/users/roles', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/users/roles/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/users/roles/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Personal API tokens: authenticate scripts/integrations as a user without
|
|
// the hourly-expiring login JWT. The secret is returned ONCE, on create.
|
|
export const apitokensApi = {
|
|
// all=true (admin) lists everyone's tokens; otherwise just the caller's.
|
|
list(params = {}) {
|
|
return api.get('/apitokens', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/apitokens', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/apitokens/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/apitokens/${id}`)
|
|
}
|
|
}
|
|
|
|
// Network API (devices, subnets, and VLANs)
|
|
export const networkApi = {
|
|
// Network devices
|
|
list(params = {}) {
|
|
return api.get('/network', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/network/${id}`)
|
|
},
|
|
getByAsset(assetId) {
|
|
return api.get(`/network/by-asset/${assetId}`)
|
|
},
|
|
getByHostname(hostname) {
|
|
return api.get(`/network/by-hostname/${hostname}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/network', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/network/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/network/${id}`)
|
|
},
|
|
dashboardSummary() {
|
|
return api.get('/network/dashboard/summary')
|
|
},
|
|
// Network device types
|
|
types: {
|
|
list(params = {}) {
|
|
return api.get('/network/types', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/network/types/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/network/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/network/types/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/network/types/${id}`)
|
|
}
|
|
},
|
|
// VLANs
|
|
vlans: {
|
|
list(params = {}) {
|
|
return api.get('/network/vlans', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/network/vlans/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/network/vlans', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/network/vlans/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/network/vlans/${id}`)
|
|
}
|
|
},
|
|
// Subnets
|
|
subnets: {
|
|
list(params = {}) {
|
|
return api.get('/network/subnets', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/network/subnets/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/network/subnets', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/network/subnets/${id}`, data)
|
|
},
|
|
delete(id) {
|
|
return api.delete(`/network/subnets/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Custom fields: site-defined attributes per asset type.
|
|
export const customFieldsApi = {
|
|
// Definitions
|
|
list(params = {}) {
|
|
return api.get('/customfields', { params })
|
|
},
|
|
create(data) {
|
|
return api.post('/customfields', data)
|
|
},
|
|
update(fieldid, data) {
|
|
return api.put(`/customfields/${fieldid}`, data)
|
|
},
|
|
remove(fieldid) {
|
|
return api.delete(`/customfields/${fieldid}`)
|
|
},
|
|
// Per-asset values (defs merged with the asset's stored values)
|
|
forAsset(assetid) {
|
|
return api.get(`/customfields/asset/${assetid}`)
|
|
},
|
|
saveForAsset(assetid, values) {
|
|
return api.put(`/customfields/asset/${assetid}`, { values })
|
|
}
|
|
}
|
|
|
|
// Warranty plugin: asset warranty tracking (manual + provider lookups).
|
|
export const warrantyApi = {
|
|
list(params = {}) {
|
|
return api.get('/warranty', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/warranty/${id}`)
|
|
},
|
|
forAsset(assetid) {
|
|
return api.get(`/warranty/asset/${assetid}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/warranty', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/warranty/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/warranty/${id}`)
|
|
},
|
|
refresh(id) {
|
|
return api.post(`/warranty/${id}/refresh`)
|
|
},
|
|
syncDell(all = false) {
|
|
return api.post('/warranty/sync/dell', null, { params: all ? { all: 'true' } : {} })
|
|
},
|
|
report() {
|
|
return api.get('/warranty/report')
|
|
},
|
|
// proof of cover: invoice, certificate, whatever the vendor sent
|
|
uploadProof(id, file) {
|
|
const form = new FormData()
|
|
form.append('file', file)
|
|
return api.post(`/warranty/${id}/proof`, form, { headers: { 'Content-Type': 'multipart/form-data' } })
|
|
},
|
|
removeProof(id) {
|
|
return api.delete(`/warranty/${id}/proof`)
|
|
}
|
|
}
|
|
|
|
// Measuring tools API (plugin)
|
|
export const measuringtoolsApi = {
|
|
list(params = {}) {
|
|
return api.get('/measuringtools', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/measuringtools/${id}`)
|
|
},
|
|
getByAsset(assetid) {
|
|
return api.get(`/measuringtools/by-asset/${assetid}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/measuringtools', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/measuringtools/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/measuringtools/${id}`)
|
|
},
|
|
calibrationReport() {
|
|
return api.get('/measuringtools/report/calibration')
|
|
},
|
|
// Measuring-tool types
|
|
types: {
|
|
list(params = {}) {
|
|
return api.get('/measuringtools/types', { params })
|
|
},
|
|
get(id) {
|
|
return api.get(`/measuringtools/types/${id}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/measuringtools/types', data)
|
|
},
|
|
update(id, data) {
|
|
return api.put(`/measuringtools/types/${id}`, data)
|
|
},
|
|
remove(id) {
|
|
return api.delete(`/measuringtools/types/${id}`)
|
|
}
|
|
}
|
|
}
|
|
|
|
// 3D printed parts (printedparts plugin)
|
|
export const printedpartsApi = {
|
|
list(params = {}) {
|
|
return api.get('/printedparts/items', { params })
|
|
},
|
|
get(printeditemid) {
|
|
return api.get(`/printedparts/items/${printeditemid}`)
|
|
},
|
|
create(data) {
|
|
return api.post('/printedparts/items', data)
|
|
},
|
|
update(printeditemid, data) {
|
|
return api.put(`/printedparts/items/${printeditemid}`, data)
|
|
},
|
|
remove(printeditemid) {
|
|
return api.delete(`/printedparts/items/${printeditemid}`)
|
|
},
|
|
restore(printeditemid) {
|
|
return api.post(`/printedparts/items/${printeditemid}/restore`)
|
|
},
|
|
uploadImage(printeditemid, file) {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
return api.post(`/printedparts/items/${printeditemid}/image`, formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
})
|
|
},
|
|
deleteImage(printeditemid) {
|
|
return api.delete(`/printedparts/items/${printeditemid}/image`)
|
|
},
|
|
restock(printeditemid, data) {
|
|
return api.post(`/printedparts/items/${printeditemid}/restock`, data)
|
|
},
|
|
adjust(printeditemid, data) {
|
|
return api.post(`/printedparts/items/${printeditemid}/adjust`, data)
|
|
},
|
|
kioskItem(itemcode) {
|
|
return api.get(`/printedparts/kiosk/item/${encodeURIComponent(itemcode)}`)
|
|
},
|
|
kioskTake(data) {
|
|
return api.post('/printedparts/kiosk/take', data)
|
|
},
|
|
listFiles(printeditemid) {
|
|
return api.get(`/printedparts/items/${printeditemid}/files`)
|
|
},
|
|
uploadFile(printeditemid, file, note) {
|
|
const formData = new FormData()
|
|
formData.append('file', file)
|
|
if (note) formData.append('note', note)
|
|
return api.post(`/printedparts/items/${printeditemid}/files`, formData, {
|
|
headers: { 'Content-Type': 'multipart/form-data' }
|
|
})
|
|
},
|
|
removeFile(fileid) {
|
|
return api.delete(`/printedparts/files/${fileid}`)
|
|
}
|
|
}
|