Fixes #7091: Ensure API requests from the UI are aware of BASE_PATH

This commit is contained in:
thatmattlove 2021-08-31 07:56:53 -07:00
parent 515b6bf71a
commit 679bbd3e76
13 changed files with 73 additions and 26 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -11,7 +11,7 @@ type InferredProps<
// Element name.
T extends keyof HTMLElementTagNameMap,
// Element type.
E extends HTMLElementTagNameMap[T] = HTMLElementTagNameMap[T]
E extends HTMLElementTagNameMap[T] = HTMLElementTagNameMap[T],
> = Partial<Record<keyof E, E[keyof E]>>;
export function isApiError(data: Record<string, unknown>): data is APIError {
@ -94,7 +94,7 @@ export function isElement(obj: Element | null | undefined): obj is Element {
/**
* Retrieve the CSRF token from cookie storage.
*/
export function getCsrfToken(): string {
function getCsrfToken(): string {
const { csrftoken: csrfToken } = Cookie.parse(document.cookie);
if (typeof csrfToken === 'undefined') {
throw new Error('Invalid or missing CSRF token');
@ -102,8 +102,45 @@ export function getCsrfToken(): string {
return csrfToken;
}
/**
* Get the NetBox `settings.BASE_PATH` from the `<html/>` 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;
}
/**
* 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(path: string): string {
const basePath = getBasePath();
let combined = [...basePath.split('/'), ...path.split('/')].filter(p => p);
if (combined[0] !== '/') {
combined = ['', ...combined];
}
if (combined[combined.length - 1] !== '/') {
combined = [...combined, ''];
}
return combined.join('/');
}
export async function apiRequest<R extends Dict, D extends ReqData = undefined>(
url: string,
path: string,
method: Method,
data?: D,
): Promise<APIResponse<R>> {
@ -115,6 +152,7 @@ export async function apiRequest<R extends Dict, D extends ReqData = undefined>(
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');
@ -367,7 +405,7 @@ export function createElement<
// Element props.
P extends InferredProps<T>,
// Child element type.
C extends HTMLElement = HTMLElement
C extends HTMLElement = HTMLElement,
>(tag: T, properties: P | null, classes: string[], children: C[] = []): HTMLElementTagNameMap[T] {
// Create the base element.
const element = document.createElement<T>(tag);

View File

@ -5,6 +5,7 @@
<html
lang="en"
data-netbox-path="{{ request.path }}"
data-netbox-base-path="{% base_path %}"
{% if preferences|get_key:'ui.colormode' == 'dark'%}
data-netbox-color-mode="dark"
{% else %}

View File

@ -304,28 +304,6 @@ def get_item(value: object, attr: str) -> Any:
return value[attr]
#
# Tags
#
@register.simple_tag()
def querystring(request, **kwargs):
"""
Append or update the page number in a querystring.
"""
querydict = request.GET.copy()
for k, v in kwargs.items():
if v is not None:
querydict[k] = str(v)
elif k in querydict:
querydict.pop(k)
querystring = querydict.urlencode(safe='/')
if querystring:
return '?' + querystring
else:
return ''
@register.filter
def status_from_tag(tag: str = "info") -> str:
"""
@ -355,6 +333,36 @@ def icon_from_status(status: str = "info") -> str:
return icon_map.get(status.lower(), 'information')
#
# Tags
#
@register.simple_tag()
def querystring(request, **kwargs):
"""
Append or update the page number in a querystring.
"""
querydict = request.GET.copy()
for k, v in kwargs.items():
if v is not None:
querydict[k] = str(v)
elif k in querydict:
querydict.pop(k)
querystring = querydict.urlencode(safe='/')
if querystring:
return '?' + querystring
else:
return ''
@register.simple_tag()
def base_path():
"""
Access `BASE_PATH` in templates.
"""
return settings.BASE_PATH
@register.inclusion_tag('utilities/templatetags/utilization_graph.html')
def utilization_graph(utilization, warning_threshold=75, danger_threshold=90):
"""