Files
Netbox-Utilities/netbox_utilities/static/netbox_utilities/reorder-rack-width.js
T

142 lines
5.6 KiB
JavaScript

(() => {
'use strict';
const source = document.getElementById('netbox-utilities-reorder-rack-width-data');
if (!source) return;
let data;
try {
data = JSON.parse(source.textContent);
} catch (error) {
console.error('NetBox Utilities could not read the Reorder Rack width data.', error);
return;
}
if (data.status && data.status !== 'ready') {
console.warn('NetBox Utilities is leaving Reorder Rack in its native layout.', data.status);
return;
}
const columns = Number.parseInt(data.columns, 10) || 12;
const gridElements = {
front: document.getElementById('grid-front'),
rear: document.getElementById('grid-rear'),
other: document.getElementById('grid-other'),
};
if (!gridElements.front || !gridElements.rear || !gridElements.other) return;
const setAttribute = (element, name, value) => element.setAttribute(name, String(value));
const originalFetch = window.fetch.bind(window);
window.fetch = (resource, options = {}) => {
const resourceUrl = typeof resource === 'string' || resource instanceof URL
? resource
: resource?.url;
let url;
try {
url = new URL(resourceUrl, document.baseURI);
} catch (_) {
return originalFetch(resource, options);
}
const method = String(options.method || resource?.method || 'GET').toUpperCase();
if (method !== 'PUT' || !/\/api\/plugins\/reorder\/save\/\d+\/?$/.test(url.pathname)) {
return originalFetch(resource, options);
}
const headers = new Headers(options.headers || resource?.headers);
headers.set('X-NetBox-Utilities-Rack-Grid-Columns', String(columns));
return originalFetch(resource, {...options, headers});
};
Object.entries(gridElements).forEach(([face, grid]) => {
setAttribute(grid, 'gs-column', columns);
grid.querySelectorAll('.grid-stack-item').forEach(item => {
// The native Reorder view is based on Rack.get_rack_units(), which can
// expose only one device per rack unit. Rebuild the mounted grids from
// the complete server payload; keep native non-racked devices intact.
if (face !== 'other') {
item.remove();
return;
}
setAttribute(item, 'gs-w', columns);
setAttribute(item, 'gs-x', 0);
item.dataset.rackWidth = '1';
item.dataset.horizontalPosition = '1';
});
});
const addWidget = (device, gridFace, itemFace, rearSide = false) => {
const item = document.createElement('div');
item.className = 'grid-stack-item netbox-utilities-reorder-rack-device';
setAttribute(item, 'gs-w', device.grid_width);
setAttribute(item, 'gs-h', device.grid_height);
setAttribute(item, 'gs-x', device.grid_x);
setAttribute(item, 'gs-y', device.grid_y);
setAttribute(item, 'gs-id', device.id);
setAttribute(item, 'gs-locked', device.locked ? 'true' : 'false');
setAttribute(item, 'gs-no-move', device.locked ? 'true' : 'false');
item.dataset.itemColor = device.color;
item.dataset.itemTextColor = device.text_color;
item.dataset.fullDepth = device.full_depth ? 'True' : 'False';
item.dataset.itemFace = itemFace;
item.dataset.rackWidth = String(device.width);
item.dataset.horizontalPosition = String(device.horizontal_position);
const content = document.createElement('div');
content.className = 'grid-stack-item-content';
if (rearSide) content.classList.add('device_rear');
const image = rearSide ? device.rear_image : device.front_image;
if (image && data.images) {
content.style.backgroundImage = `url("${String(image).replaceAll('"', '\\"')}")`;
content.style.backgroundSize = `${data.unit_width}px`;
content.style.color = `#${device.text_color}`;
if (data.labels) content.textContent = device.label;
} else {
if (!rearSide) content.style.backgroundColor = `#${device.color}`;
content.style.color = rearSide ? '#000000' : `#${device.text_color}`;
content.textContent = device.label;
}
item.appendChild(content);
gridElements[gridFace].appendChild(item);
};
data.devices.forEach(device => {
const face = device.face === 'rear' ? 'rear' : 'front';
addWidget(device, face, face);
if (device.full_depth) {
addWidget(device, face === 'front' ? 'rear' : 'front', 'back', true);
}
});
const snapElement = element => {
if (!element?.gridstackNode) return;
const width = Number.parseInt(element.dataset.rackWidth || '1', 10);
if (![1, 2, 3, 4].includes(width)) return;
const gridWidth = columns / width;
const node = element.gridstackNode;
const gridX = Math.max(0, Math.min(columns - gridWidth, Math.round(node.x / gridWidth) * gridWidth));
element.dataset.horizontalPosition = String(gridX / gridWidth + 1);
if (node.x !== gridX || node.w !== gridWidth) {
node.grid.update(element, {x: gridX, w: gridWidth});
}
};
const snapGrid = grid => grid.getGridItems().forEach(snapElement);
const attachSnapping = () => {
Object.values(gridElements).forEach(element => {
const grid = element.gridstack;
if (!grid) return;
grid.on('dragstop', (_event, item) => snapElement(item));
grid.on('dropped', (_event, _previous, current) => snapElement(current?.el));
});
};
document.addEventListener('click', event => {
const target = event.target instanceof Element ? event.target : event.target?.parentElement;
if (!target?.closest('#saveButton')) return;
Object.values(gridElements).forEach(element => {
if (element.gridstack) snapGrid(element.gridstack);
});
}, true);
document.addEventListener('DOMContentLoaded', attachSnapping, {once: true});
})();