Skip to content

Commit 4be5d3f

Browse files
Merge pull request #6960 from candlerb/candlerb/6879-v3
Display device names in front of device front/rear images
2 parents 5315474 + eb9f2b3 commit 4be5d3f

File tree

7 files changed

+85
-74
lines changed

7 files changed

+85
-74
lines changed

netbox/dcim/svg.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ def _draw_device_front(self, drawing, device, start, end, text):
112112
)
113113
image.fit(scale='slice')
114114
link.add(image)
115+
link.add(drawing.text(str(name), insert=text, stroke='black',
116+
stroke_width='0.2em', stroke_linejoin='round', class_='device-image-label'))
117+
link.add(drawing.text(str(name), insert=text, fill='white', class_='device-image-label'))
115118

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

133139
@staticmethod
134140
def _draw_empty(drawing, rack, start, end, text, id_, face_id, class_, reservation):

netbox/project-static/dist/netbox.js

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox/project-static/dist/netbox.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

netbox/project-static/src/racks.ts

Lines changed: 55 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,92 +1,90 @@
1-
import { rackImagesState } from './stores';
1+
import { rackImagesState, RackViewSelection } from './stores';
22
import { getElements } from './util';
33

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

6-
type RackToggleState = { hidden: boolean };
6+
export type RackViewState = { view: RackViewSelection };
77

88
/**
9-
* Toggle the Rack Image button to reflect the current state. If the current state is hidden and
10-
* the images are therefore hidden, the button should say "Show Images". Likewise, if the current
11-
* state is *not* hidden, and therefore the images are shown, the button should say "Hide Images".
12-
*
13-
* @param hidden Current State - `true` if images are hidden, `false` otherwise.
14-
* @param button Button element.
9+
* Show or hide images and labels to build the desired rack view.
1510
*/
16-
function toggleRackImagesButton(hidden: boolean, button: HTMLButtonElement): void {
17-
const text = hidden ? 'Show Images' : 'Hide Images';
18-
const selected = hidden ? '' : 'selected';
19-
button.setAttribute('selected', selected);
20-
button.innerHTML = `<i class="mdi mdi-file-image-outline"></i>&nbsp;${text}`;
11+
function setRackView(
12+
view: RackViewSelection,
13+
elevation: HTMLObjectElement,
14+
): void {
15+
switch(view) {
16+
case 'images-and-labels': {
17+
showRackElements('image.device-image', elevation);
18+
showRackElements('text.device-image-label', elevation);
19+
break;
20+
}
21+
case 'images-only': {
22+
showRackElements('image.device-image', elevation);
23+
hideRackElements('text.device-image-label', elevation);
24+
break;
25+
}
26+
case 'labels-only': {
27+
hideRackElements('image.device-image', elevation);
28+
hideRackElements('text.device-image-label', elevation);
29+
break;
30+
}
31+
}
2132
}
2233

23-
/**
24-
* Show all rack images.
25-
*/
26-
function showRackImages(): void {
27-
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
28-
const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? [];
29-
for (const image of images) {
30-
image.classList.remove('hidden');
31-
}
34+
function showRackElements(
35+
selector: string,
36+
elevation: HTMLObjectElement,
37+
): void {
38+
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
39+
for (const element of elements) {
40+
element.classList.remove('hidden');
3241
}
3342
}
3443

35-
/**
36-
* Hide all rack images.
37-
*/
38-
function hideRackImages(): void {
39-
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
40-
const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? [];
41-
for (const image of images) {
42-
image.classList.add('hidden');
43-
}
44+
function hideRackElements(
45+
selector: string,
46+
elevation: HTMLObjectElement,
47+
): void {
48+
const elements = elevation.contentDocument?.querySelectorAll(selector) ?? [];
49+
for (const element of elements) {
50+
element.classList.add('hidden');
4451
}
4552
}
4653

4754
/**
48-
* Toggle the visibility of device images and update the toggle button style.
55+
* Change the visibility of all racks in response to selection.
4956
*/
50-
function handleRackImageToggle(
51-
target: HTMLButtonElement,
52-
state: StateManager<RackToggleState>,
57+
function handleRackViewSelect(
58+
newView: RackViewSelection,
59+
state: StateManager<RackViewState>,
5360
): void {
54-
const initiallyHidden = state.get('hidden');
55-
state.set('hidden', !initiallyHidden);
56-
const hidden = state.get('hidden');
57-
58-
if (hidden) {
59-
hideRackImages();
60-
} else {
61-
showRackImages();
61+
state.set('view', newView);
62+
for (const elevation of getElements<HTMLObjectElement>('.rack_elevation')) {
63+
setRackView(newView, elevation);
6264
}
63-
toggleRackImagesButton(hidden, target);
6465
}
6566

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

75-
button.addEventListener(
76-
'click',
74+
for (const control of getElements<HTMLSelectElement>('select.rack-view')) {
75+
control.selectedIndex = [...control.options].findIndex(o => o.value == initialView);
76+
control.addEventListener(
77+
'change',
7778
event => {
78-
handleRackImageToggle(event.currentTarget as HTMLButtonElement, rackImagesState);
79+
handleRackViewSelect((event.currentTarget as any).value as RackViewSelection, rackImagesState);
7980
},
8081
false,
8182
);
8283
}
84+
8385
for (const element of getElements<HTMLObjectElement>('.rack_elevation')) {
8486
element.addEventListener('load', () => {
85-
if (initiallyHidden) {
86-
hideRackImages();
87-
} else if (!initiallyHidden) {
88-
showRackImages();
89-
}
87+
setRackView(initialView, element);
9088
});
9189
}
9290
}
Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { createState } from '../state';
22

3-
export const rackImagesState = createState<{ hidden: boolean }>(
4-
{ hidden: false },
3+
export type RackViewSelection = 'images-and-labels' | 'images-only' | 'labels-only';
4+
5+
export const rackImagesState = createState<{ view: RackViewSelection }>(
6+
{ view: 'images-and-labels' },
57
{ persist: true },
68
);

netbox/templates/dcim/rack.html

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,11 @@
1818
{% endblock %}
1919

2020
{% block extra_controls %}
21-
<button class="btn btn-sm btn-outline-primary toggle-images" selected="selected">
22-
<i class="mdi mdi-file-image-outline"></i>
23-
Hide Images
24-
</button>
21+
<select class="btn btn-sm btn-outline-dark rack-view">
22+
<option value="images-and-labels" selected="selected">Images and Labels</option>
23+
<option value="images-only">Images only</option>
24+
<option value="labels-only">Labels only</option>
25+
</select>
2526
<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 %}">
2627
<i class="mdi mdi-chevron-left" aria-hidden="true"></i> Previous
2728
</a>

netbox/templates/dcim/rack_elevation_list.html

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,13 @@
77
{% block controls %}
88
<div class="controls">
99
<div class="control-group">
10-
<button class="btn btn-sm btn-outline-dark toggle-images" selected="selected">
11-
<span class="mdi mdi mdi-checkbox-marked-circle-outline" aria-hidden="true"></span> Show Images
12-
</button>
10+
<div class="btn-group btn-group-sm" role="group">
11+
<select class="btn btn-sm btn-outline-secondary active rack-view">
12+
<option value="images-and-labels" selected="selected">Images and Labels</option>
13+
<option value="images-only">Images only</option>
14+
<option value="labels-only">Labels only</option>
15+
</select>
16+
</div>
1317
<div class="btn-group btn-group-sm" role="group">
1418
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='front' %}" class="btn btn-outline-secondary{% if rack_face == 'front' %} active{% endif %}">Front</a>
1519
<a href="{% url 'dcim:rack_elevation_list' %}{% querystring request face='rear' %}" class="btn btn-outline-secondary{% if rack_face == 'rear' %} active{% endif %}">Rear</a>

0 commit comments

Comments
 (0)