diff --git a/netbox/dcim/svg.py b/netbox/dcim/svg.py index f8e7bdf10..5601bc591 100644 --- a/netbox/dcim/svg.py +++ b/netbox/dcim/svg.py @@ -112,6 +112,9 @@ class RackElevationSVG: ) 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") @@ -129,6 +132,9 @@ class RackElevationSVG: ) 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): diff --git a/netbox/project-static/dist/netbox.js b/netbox/project-static/dist/netbox.js index 24524fad3..cc12e4855 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 be7ed66c0..a67c6cbd8 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/src/racks.ts b/netbox/project-static/src/racks.ts index 83d7abc14..cae9897fc 100644 --- a/netbox/project-static/src/racks.ts +++ b/netbox/project-static/src/racks.ts @@ -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 = ` ${text}`; -} - -/** - * Show all rack images. - */ -function showRackImages(): void { - for (const elevation of getElements('.rack_elevation')) { - const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? []; - for (const image of images) { - image.classList.remove('hidden'); - } - } -} - -/** - * Hide all rack images. - */ -function hideRackImages(): void { - for (const elevation of getElements('.rack_elevation')) { - const images = elevation.contentDocument?.querySelectorAll('image.device-image') ?? []; - for (const image of images) { - image.classList.add('hidden'); - } - } -} - -/** - * Toggle the visibility of device images and update the toggle button style. - */ -function handleRackImageToggle( - target: HTMLButtonElement, - state: StateManager, +function setRackView( + view: RackViewSelection, + elevation: HTMLObjectElement, ): void { - const initiallyHidden = state.get('hidden'); - state.set('hidden', !initiallyHidden); - const hidden = state.get('hidden'); - - if (hidden) { - hideRackImages(); - } else { - showRackImages(); + 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; + } + } +} + +function showRackElements( + selector: string, + elevation: HTMLObjectElement, +): void { + const elements = elevation.contentDocument?.querySelectorAll(selector) ?? []; + for (const element of elements) { + element.classList.remove('hidden'); + } +} + +function hideRackElements( + selector: string, + elevation: HTMLObjectElement, +): void { + const elements = elevation.contentDocument?.querySelectorAll(selector) ?? []; + for (const element of elements) { + element.classList.add('hidden'); } - 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. + * Change the visibility of all racks in response to selection. + */ +function handleRackViewSelect( + newView: RackViewSelection, + state: StateManager, +): void { + state.set('view', newView); + for (const elevation of getElements('.rack_elevation')) { + setRackView(newView, elevation); + } +} + +/** + * 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('button.toggle-images')) { - toggleRackImagesButton(initiallyHidden, button); + const initialView = rackImagesState.get('view'); - button.addEventListener( - 'click', + for (const control of getElements('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('.rack_elevation')) { element.addEventListener('load', () => { - if (initiallyHidden) { - hideRackImages(); - } else if (!initiallyHidden) { - showRackImages(); - } + setRackView(initialView, element); }); } } diff --git a/netbox/project-static/src/stores/rackImages.ts b/netbox/project-static/src/stores/rackImages.ts index beeb25bce..d32833b20 100644 --- a/netbox/project-static/src/stores/rackImages.ts +++ b/netbox/project-static/src/stores/rackImages.ts @@ -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 }, ); diff --git a/netbox/templates/dcim/rack.html b/netbox/templates/dcim/rack.html index bf9a11819..98009cb55 100644 --- a/netbox/templates/dcim/rack.html +++ b/netbox/templates/dcim/rack.html @@ -18,10 +18,11 @@ {% endblock %} {% block extra_controls %} - + Previous diff --git a/netbox/templates/dcim/rack_elevation_list.html b/netbox/templates/dcim/rack_elevation_list.html index 468d44f76..f5641c944 100644 --- a/netbox/templates/dcim/rack_elevation_list.html +++ b/netbox/templates/dcim/rack_elevation_list.html @@ -7,9 +7,13 @@ {% block controls %}
- +
+ +