diff --git a/netbox/project-static/dist/config.js b/netbox/project-static/dist/config.js index 0e701fc85..7cf3ccb30 100644 Binary files a/netbox/project-static/dist/config.js and b/netbox/project-static/dist/config.js differ diff --git a/netbox/project-static/dist/config.js.map b/netbox/project-static/dist/config.js.map index 63379ab42..d070a0451 100644 Binary files a/netbox/project-static/dist/config.js.map and b/netbox/project-static/dist/config.js.map differ diff --git a/netbox/project-static/dist/jobs.js b/netbox/project-static/dist/jobs.js index a2d9ead62..2aedf1219 100644 Binary files a/netbox/project-static/dist/jobs.js and b/netbox/project-static/dist/jobs.js differ diff --git a/netbox/project-static/dist/jobs.js.map b/netbox/project-static/dist/jobs.js.map index c1e2ac34b..d7c1dbcbf 100644 Binary files a/netbox/project-static/dist/jobs.js.map and b/netbox/project-static/dist/jobs.js.map differ diff --git a/netbox/project-static/dist/lldp.js b/netbox/project-static/dist/lldp.js index a704b158d..7fac1012a 100644 Binary files a/netbox/project-static/dist/lldp.js and b/netbox/project-static/dist/lldp.js differ diff --git a/netbox/project-static/dist/lldp.js.map b/netbox/project-static/dist/lldp.js.map index ab5d4295a..911cd77c3 100644 Binary files a/netbox/project-static/dist/lldp.js.map and b/netbox/project-static/dist/lldp.js.map differ diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index a60f4eaa8..33d004108 100644 Binary files a/netbox/project-static/dist/netbox.js and b/netbox/project-static/dist/netbox.js differ diff --git a/netbox/project-static/dist/netbox.js.map b/netbox/project-static/dist/netbox.js.map index 984391b01..8bee87505 100644 Binary files a/netbox/project-static/dist/netbox.js.map and b/netbox/project-static/dist/netbox.js.map differ diff --git a/netbox/project-static/dist/status.js b/netbox/project-static/dist/status.js index f4e64b5ef..c817efb4c 100644 Binary files a/netbox/project-static/dist/status.js and b/netbox/project-static/dist/status.js differ diff --git a/netbox/project-static/dist/status.js.map b/netbox/project-static/dist/status.js.map index d2c1a9c49..53b01423e 100644 Binary files a/netbox/project-static/dist/status.js.map and b/netbox/project-static/dist/status.js.map differ diff --git a/netbox/project-static/src/buttons/connectionToggle.ts b/netbox/project-static/src/buttons/connectionToggle.ts index 6485bbb50..74b32dc3a 100644 --- a/netbox/project-static/src/buttons/connectionToggle.ts +++ b/netbox/project-static/src/buttons/connectionToggle.ts @@ -8,12 +8,12 @@ import { isTruthy, apiPatch, hasError, getElements } from '../util'; * @param element Connection Toggle Button Element */ function toggleConnection(element: HTMLButtonElement): void { - const id = element.getAttribute('data'); + const url = element.getAttribute('data-url'); const connected = element.classList.contains('connected'); const status = connected ? 'planned' : 'connected'; - if (isTruthy(id)) { - apiPatch(`/api/dcim/cables/${id}/`, { status }).then(res => { + if (isTruthy(url)) { + apiPatch(url, { status }).then(res => { if (hasError(res)) { // If the API responds with an error, show it to the user. createToast('danger', 'Error', res.error).show(); diff --git a/netbox/project-static/src/jobs.ts b/netbox/project-static/src/jobs.ts index 8a8a3fd12..dedf0706d 100644 --- a/netbox/project-static/src/jobs.ts +++ b/netbox/project-static/src/jobs.ts @@ -4,7 +4,7 @@ import { apiGetBase, hasError, getNetboxData } from './util'; let timeout: number = 1000; interface JobInfo { - id: Nullable; + url: Nullable; complete: boolean; } @@ -23,15 +23,16 @@ function asyncTimeout(ms: number) { function getJobInfo(): JobInfo { let complete = false; - const id = getNetboxData('data-job-id'); - const jobComplete = getNetboxData('data-job-complete'); + // Determine the API URL for the job status + const url = getNetboxData('data-job-url'); // Determine the job completion status, if present. If the job is not complete, the value will be // "None". Otherwise, it will be a stringified date. + const jobComplete = getNetboxData('data-job-complete'); if (typeof jobComplete === 'string' && jobComplete.toLowerCase() !== 'none') { complete = true; } - return { id, complete }; + return { url, complete }; } /** @@ -59,10 +60,10 @@ function updateLabel(status: JobStatus) { /** * Recursively check the job's status. - * @param id Job ID + * @param url API URL for job result */ -async function checkJobStatus(id: string) { - const res = await apiGetBase(`/api/extras/job-results/${id}/`); +async function checkJobStatus(url: string) { + const res = await apiGetBase(url); if (hasError(res)) { // If the response is an API error, display an error message and stop checking for job status. const toast = createToast('danger', 'Error', res.error); @@ -82,17 +83,17 @@ async function checkJobStatus(id: string) { if (timeout < 10000) { timeout += 1000; } - await Promise.all([checkJobStatus(id), asyncTimeout(timeout)]); + await Promise.all([checkJobStatus(url), asyncTimeout(timeout)]); } } } function initJobs() { - const { id, complete } = getJobInfo(); + const { url, complete } = getJobInfo(); - if (id !== null && !complete) { + if (url !== null && !complete) { // If there is a job ID and it is not completed, check for the job's status. - Promise.resolve(checkJobStatus(id)); + Promise.resolve(checkJobStatus(url)); } } diff --git a/netbox/project-static/src/tableConfig.ts b/netbox/project-static/src/tableConfig.ts index ff12e8d68..3d2ae012b 100644 --- a/netbox/project-static/src/tableConfig.ts +++ b/netbox/project-static/src/tableConfig.ts @@ -53,8 +53,8 @@ function removeColumns(event: Event): void { /** * Submit form configuration to the NetBox API. */ -async function submitFormConfig(formConfig: Dict): Promise> { - return await apiPatch('/api/users/config/', formConfig); +async function submitFormConfig(url: string, formConfig: Dict): Promise> { + return await apiPatch(url, formConfig); } /** @@ -66,6 +66,18 @@ function handleSubmit(event: Event): void { const element = event.currentTarget as HTMLFormElement; + // Get the API URL for submitting the form + const url = element.getAttribute('data-url'); + if (url == null) { + const toast = createToast( + 'danger', + 'Error Updating Table Configuration', + 'No API path defined for configuration form.' + ); + toast.show(); + return; + } + // Get all the selected options from any select element in the form. const options = getSelectedOptions(element); @@ -83,7 +95,7 @@ function handleSubmit(event: Event): void { const data = path.reduceRight>((value, key) => ({ [key]: value }), formData); // Submit the resulting object to the API to update the user's preferences for this table. - submitFormConfig(data).then(res => { + submitFormConfig(url, data).then(res => { if (hasError(res)) { const toast = createToast('danger', 'Error Updating Table Configuration', res.error); toast.show(); diff --git a/netbox/project-static/src/util.ts b/netbox/project-static/src/util.ts index 278ccc3e5..d7a8d5b6a 100644 --- a/netbox/project-static/src/util.ts +++ b/netbox/project-static/src/util.ts @@ -1,7 +1,4 @@ import Cookie from 'cookie'; -import queryString from 'query-string'; - -import type { Stringifiable } from 'query-string'; type Method = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; type ReqData = URLSearchParams | Dict | undefined | unknown; @@ -107,84 +104,8 @@ function getCsrfToken(): string { return csrfToken; } -/** - * Get the NetBox `settings.BASE_PATH` from the `` element's data attributes. - * - * @returns If there is no `BASE_PATH` specified, the return value will be `''`. - */ function getBasePath(): string { - const value = document.documentElement.getAttribute('data-netbox-base-path'); - if (value === null) { - return ''; - } - return value; -} - -/** - * Constrict an object from a URL query param string, with all values as an array. - * - * @param params URL search query string. - * @returns Constructed query object. - */ -function queryParamsToObject(params: string): Record { - const result = {} as Record; - const searchParams = new URLSearchParams(params); - for (const [key, originalValue] of searchParams.entries()) { - let value = [] as Stringifiable[]; - if (key in result) { - value = [...value, ...result[key]]; - } - if (Array.isArray(originalValue)) { - value = [...value, ...originalValue]; - } else { - value = [...value, originalValue]; - } - result[key] = value; - } - return result; -} - -/** - * Build a NetBox URL that includes `settings.BASE_PATH` and enforces leading and trailing slashes. - * - * @example - * ```js - * // With a BASE_PATH of 'netbox/' - * const url = buildUrl('/api/dcim/devices'); - * console.log(url); - * // => /netbox/api/dcim/devices/ - * ``` - * - * @param path Relative path _after_ (excluding) the `BASE_PATH`. - */ -function buildUrl(destination: string): string { - // Separate the path from any URL search params. - const [pathname, search] = destination.split(/(?=\?)/g); - - // If the `origin` exists in the API path (as in the case of paginated responses), remove it. - const origin = new RegExp(window.location.origin, 'g'); - const path = pathname.replaceAll(origin, ''); - - const basePath = getBasePath(); - - // Combine `BASE_PATH` with this request's path, removing _all_ slashes. - let combined = [...basePath.split('/'), ...path.split('/')].filter(p => p); - - if (combined[0] !== '/') { - // Ensure the URL has a leading slash. - combined = ['', ...combined]; - } - if (combined[combined.length - 1] !== '/') { - // Ensure the URL has a trailing slash. - combined = [...combined, '']; - } - const url = combined.join('/'); - // Construct an object from the URL search params so it can be re-serialized with the new URL. - const query = queryParamsToObject(search); - return queryString.stringifyUrl({ url, query }); -} - export async function apiRequest( - path: string, + url: string, method: Method, data?: D, ): Promise> { @@ -196,7 +117,6 @@ export async function apiRequest( body = JSON.stringify(data); headers.set('content-type', 'application/json'); } - const url = buildUrl(path); const res = await fetch(url, { method, body, headers, credentials: 'same-origin' }); const contentType = res.headers.get('Content-Type'); diff --git a/netbox/templates/dcim/inc/cable_toggle_buttons.html b/netbox/templates/dcim/inc/cable_toggle_buttons.html index 3fe34acd7..77db3093f 100644 --- a/netbox/templates/dcim/inc/cable_toggle_buttons.html +++ b/netbox/templates/dcim/inc/cable_toggle_buttons.html @@ -1,10 +1,10 @@ {% if perms.dcim.change_cable %} {% if cable.status == 'connected' %} - {% else %} - {% endif %} diff --git a/netbox/templates/extras/report_result.html b/netbox/templates/extras/report_result.html index 1f757d4cb..f8da50e8e 100644 --- a/netbox/templates/extras/report_result.html +++ b/netbox/templates/extras/report_result.html @@ -96,6 +96,6 @@ {% endblock %} {% block data %} - + {% endblock %} diff --git a/netbox/templates/extras/script_result.html b/netbox/templates/extras/script_result.html index 59ca8a69d..f463b0f2c 100644 --- a/netbox/templates/extras/script_result.html +++ b/netbox/templates/extras/script_result.html @@ -112,6 +112,6 @@ {% endblock content-wrapper %} {% block data %} - + {% endblock %} diff --git a/netbox/templates/utilities/templatetags/table_config_form.html b/netbox/templates/utilities/templatetags/table_config_form.html index 5e17497e9..cad3a306a 100644 --- a/netbox/templates/utilities/templatetags/table_config_form.html +++ b/netbox/templates/utilities/templatetags/table_config_form.html @@ -7,7 +7,7 @@ -
+