Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions netbox/dcim/svg.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ def _draw_device_front(self, drawing, device, start, end, text):
)
image.fit(scale='slice')
link.add(image)
link.add(drawing.text(str(name), insert=text, stroke='black',
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
link.add(drawing.text(str(name), insert=text, fill='white', class_='device-image-label'))

def _draw_device_rear(self, drawing, device, start, end, text):
rect = drawing.rect(start, end, class_="slot blocked")
Expand All @@ -129,6 +132,9 @@ def _draw_device_rear(self, drawing, device, start, end, text):
)
image.fit(scale='slice')
drawing.add(image)
drawing.add(drawing.text(str(device), insert=text, stroke='black',
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
drawing.add(drawing.text(str(device), insert=text, fill='white', class_='device-image-label'))

@staticmethod
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):
Expand Down
14 changes: 7 additions & 7 deletions netbox/project-static/dist/netbox.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion netbox/project-static/dist/netbox.js.map

Large diffs are not rendered by default.

112 changes: 55 additions & 57 deletions netbox/project-static/src/racks.ts
Original file line number Diff line number Diff line change
@@ -1,92 +1,90 @@
import { rackImagesState } from './stores';
import { rackImagesState, RackViewSelection } from './stores';
import { getElements } from './util';

import type { StateManager } from './state';

type RackToggleState = { hidden: boolean };
export type RackViewState = { view: RackViewSelection };

/**
* Toggle the Rack Image button to reflect the current state. If the current state is hidden and
* the images are therefore hidden, the button should say "Show Images". Likewise, if the current
* state is *not* hidden, and therefore the images are shown, the button should say "Hide Images".
*
* @param hidden Current State - `true` if images are hidden, `false` otherwise.
* @param button Button element.
* Show or hide images and labels to build the desired rack view.
*/
function toggleRackImagesButton(hidden: boolean, button: HTMLButtonElement): void {
const text = hidden ? 'Show Images' : 'Hide Images';
const selected = hidden ? '' : 'selected';
button.setAttribute('selected', selected);
button.innerHTML = `<i class="mdi mdi-file-image-outline"></i>&nbsp;${text}`;
function setRackView(
view: RackViewSelection,
elevation: HTMLObjectElement,
): void {
switch(view) {
case 'images-and-labels': {
showRackElements('image.device-image', elevation);
showRackElements('text.device-image-label', elevation);
break;
}
case 'images-only': {
showRackElements('image.device-image', elevation);
hideRackElements('text.device-image-label', elevation);
break;
}
case 'labels-only': {
hideRackElements('image.device-image', elevation);
hideRackElements('text.device-image-label', elevation);
break;
}
}
}

/**
* Show all rack images.
*/
function showRackImages(): void {
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? [];
for (const image of images) {
image.classList.remove('hidden');
}
function showRackElements(
selector: string,
elevation: HTMLObjectElement,
): void {
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
for (const element of elements) {
element.classList.remove('hidden');
}
}

/**
* Hide all rack images.
*/
function hideRackImages(): void {
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? [];
for (const image of images) {
image.classList.add('hidden');
}
function hideRackElements(
selector: string,
elevation: HTMLObjectElement,
): void {
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
for (const element of elements) {
element.classList.add('hidden');
}
}

/**
* Toggle the visibility of device images and update the toggle button style.
* Change the visibility of all racks in response to selection.
*/
function handleRackImageToggle(
target: HTMLButtonElement,
state: StateManager<RackToggleState>,
function handleRackViewSelect(
newView: RackViewSelection,
state: StateManager<RackViewState>,
): void {
const initiallyHidden = state.get('hidden');
state.set('hidden', !initiallyHidden);
const hidden = state.get('hidden');

if (hidden) {
hideRackImages();
} else {
showRackImages();
state.set('view', newView);
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
setRackView(newView, elevation);
}
toggleRackImagesButton(hidden, target);
}

/**
* Add onClick callback for toggling rack elevation images. Synchronize the image toggle button
* text and display state of images with the local state.
* Add change callback for selecting rack elevation images, and set
* initial state of select and the images themselves
*/
export function initRackElevation(): void {
const initiallyHidden = rackImagesState.get('hidden');
for (const button of getElements<HTMLButtonElement>('button.toggle-images')) {
toggleRackImagesButton(initiallyHidden, button);
const initialView = rackImagesState.get('view');

button.addEventListener(
'click',
for (const control of getElements<HTMLSelectElement>('select.rack-view')) {
control.selectedIndex = [...control.options].findIndex(o => o.value == initialView);
control.addEventListener(
'change',
event => {
handleRackImageToggle(event.currentTarget as HTMLButtonElement, rackImagesState);
handleRackViewSelect((event.currentTarget as any).value as RackViewSelection, rackImagesState);
},
false,
);
}

for (const element of getElements<HTMLObjectElement>('.rack_elevation')) {
element.addEventListener('load', () => {
if (initiallyHidden) {
hideRackImages();
} else if (!initiallyHidden) {
showRackImages();
}
setRackView(initialView, element);
});
}
}
6 changes: 4 additions & 2 deletions netbox/project-static/src/stores/rackImages.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { createState } from '../state';

export const rackImagesState = createState<{ hidden: boolean }>(
{ hidden: false },
export type RackViewSelection = 'images-and-labels' | 'images-only' | 'labels-only';

export const rackImagesState = createState<{ view: RackViewSelection }>(
{ view: 'images-and-labels' },
{ persist: true },
);
9 changes: 5 additions & 4 deletions netbox/templates/dcim/rack.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
{% endblock %}

{% block extra_controls %}
<button class="btn btn-sm btn-outline-primary toggle-images" selected="selected">
<i class="mdi mdi-file-image-outline"></i>
Hide Images
</button>
<select class="btn btn-sm btn-outline-dark rack-view">
<option value="images-and-labels" selected="selected">Images and Labels</option>
<option value="images-only">Images only</option>
<option value="labels-only">Labels only</option>
</select>
<a {% if prev_rack %}href="{% url 'dcim:rack' pk=prev_rack.pk %}{% endif %}" class="btn btn-sm btn-primary{% if not prev_rack %} disabled{% endif %}">
<i class="mdi mdi-chevron-left" aria-hidden="true"></i> Previous
</a>
Expand Down
10 changes: 7 additions & 3 deletions netbox/templates/dcim/rack_elevation_list.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,13 @@
{% block controls %}
<div class="controls">
<div class="control-group">
<button class="btn btn-sm btn-outline-dark toggle-images" selected="selected">
<span class="mdi mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
</button>
<div class="btn-group btn-group-sm" role="group">
<select class="btn btn-sm btn-outline-secondary active rack-view">
<option value="images-and-labels" selected="selected">Images and Labels</option>
<option value="images-only">Images only</option>
<option value="labels-only">Labels only</option>
</select>
</div>
<div class="btn-group btn-group-sm" role="group">
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">Front</a>
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">Rear</a>
Expand Down