App-Netdisco
view release on metacpan or search on metacpan
share/public/javascripts/netdisco-admin.js view on Meta::CPAN
// Admin-only page glue, split out of netdisco.js so it is parsed only on
// admin pages. Registers with ndPages (declared in netdisco.js) so
// netdisco.js can drive it without knowing admin exists. It reads
// netdisco.js's uri_base, nd_active_tab, nd_active_target and activeForm
// globals.
// admin jobqueue: keep track of timers so we can kill them
var nd_timers = new Array();
var timermax;
var timercache;
/**
* The admin page's entry in the ndPages registry declared in netdisco.js,
* which documents the three members. The rest of this file is the admin
* fragments' handlers, bound at load because their markup exists only on
* admin pages.
*/
ndPages.admin = {
// no admin sidebar form carries the colored-input styling
// device_form_state applies, so there is nothing to collect here.
formInputs: function () {
return [];
},
innerView: function (tab) {
// reload this table every 5 seconds
var countdownIcon = document.getElementById('nd_countdown-control-icon');
if ((tab == 'jobqueue')
&& countdownIcon && countdownIcon.classList.contains('fa-play')) {
var countdownLabel = document.getElementById('nd_countdown');
if (countdownLabel) countdownLabel.textContent = String(timermax);
// add new timers
for (var i = timercache; i > 0; i--) {
nd_timers.push(setTimeout(function() {
var label = document.getElementById('nd_countdown');
if (label) label.textContent = String(timercache);
timercache = timercache - 1;
}, ((timermax * 1000) - (i * 1000)) ));
}
nd_timers.push(setTimeout(function() {
// clear any running timers
for (var j = 0; j < nd_timers.length; j++) {
clearTimeout(nd_timers[j]);
}
// reset the timer cache
timercache = timermax - 1;
// reload the tab content in...
htmx.trigger('#' + tab + '_form', 'submit');
}, (timermax * 1000)));
}
// Cast once: querySelectorAll's own return type carries only Element,
// and that leaves .dataset untyped for the closure below.
var extraButtons = /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.nd_jobqueue-extra'));
extraButtons.forEach(function (button) {
button.addEventListener('click', function(event) {
event.preventDefault();
var icon = button.querySelector(':scope > i');
if (icon) {
icon.classList.toggle('fa-plus');
icon.classList.toggle('fa-minus');
}
var extraId = button.dataset.extra;
if (extraId) {
var extra = document.getElementById(extraId);
if (extra) extra.classList.toggle('nd_collapse-pre-hidden');
}
});
});
},
ready: function () {
var tab = nd_active_tab;
var target = nd_active_target;
timermax = Number((activeForm && activeForm.dataset.ndJobqueueRefresh) || 5);
timercache = timermax - 1;
// job control sidebar submit should reset timer
// and update bookmark
var submitBtn = document.getElementById(tab + '_submit');
if (submitBtn) submitBtn.addEventListener('click', function() {
for (var i = 0; i < nd_timers.length; i++) {
clearTimeout(nd_timers[i]);
}
// reset the timer cache
timercache = timermax - 1;
// bookmark
var tabForm = document.getElementById(tab + '_form');
var querystr = tabForm ? ndRequest.query(tabForm) : '';
var bookmark = document.getElementById('nd_jobqueue-bookmark');
if (bookmark) bookmark.setAttribute('href', uri_base + '/admin/' + tab + '?' + querystr);
});
// job control refresh icon should reload the page
var refreshBtn = document.getElementById('nd_countdown-refresh');
if (refreshBtn) refreshBtn.addEventListener('click', function(event) {
event.preventDefault();
for (var i = 0; i < nd_timers.length; i++) {
clearTimeout(nd_timers[i]);
}
// reset the timer cache
timercache = timermax - 1;
// and reload content
htmx.trigger('#' + tab + '_form', 'submit');
});
// job control pause/play icon switcheroo
var controlBtn = document.getElementById('nd_countdown-control');
if (controlBtn) controlBtn.addEventListener('click', function(event) {
event.preventDefault();
var icon = document.getElementById('nd_countdown-control-icon');
if (!icon) return;
icon.classList.toggle('fa-pause');
icon.classList.toggle('fa-play');
icon.classList.toggle('text-danger');
icon.classList.toggle('text-success');
if (icon.classList.contains('fa-pause')) {
for (var i = 0; i < nd_timers.length; i++) {
clearTimeout(nd_timers[i]);
}
var countdownLabel = document.getElementById('nd_countdown');
if (countdownLabel) countdownLabel.textContent = '0';
}
else {
htmx.trigger('#' + tab + '_form', 'submit');
}
});
// activity for admin task tables
// dynamically bind to all forms in the table
// const so TypeScript keeps the null-narrowing inside the closure below
const content = document.querySelector('.content');
if (content) content.addEventListener('click', function(event) {
var button = event.target instanceof Element ? event.target.closest('.nd_adminbutton') : null;
if (!button || !content.contains(button)) return;
// stop form from submitting normally
event.preventDefault();
// clear any running timers
for (var i = 0; i < nd_timers.length; i++) {
clearTimeout(nd_timers[i]);
}
// what purpose - add/update/del
var mode = button.getAttribute('name');
// admin task name with special case(s)
var task = tab + '/';
if (tab == 'duplicatedevices') {
task = '';
}
var row = button.closest('tr');
if (!row) return;
// collected before the pane is wiped below, which detaches this row
var body = ndRequest.fields(row, 'input[data-form="' + mode + '"],select[data-form="' + mode + '"]');
if (mode == 'add' || mode == 'delete') {
var targetEl = document.querySelector(target);
if (targetEl) {
targetEl.textContent = '';
var alertDiv = document.createElement('div');
alertDiv.className = 'col-md-2 alert';
alertDiv.textContent = 'Request submitted...';
targetEl.appendChild(alertDiv);
}
}
// submit the query and put results into the tab pane
ndRequest.post(uri_base + '/ajax/control/admin/' + task + mode, body)
.then(function (res) {
// TODO: fix sanity_ok in Netdisco Web
if (!res.ok) {
if (mode == 'add') {
ndToast.error('Failed to add record');
htmx.trigger('#' + tab + '_form', 'submit');
}
else if (mode == 'delete') {
ndToast.error('Failed to delete record');
htmx.trigger('#' + tab + '_form', 'submit');
}
else {
ndToast.error('Failed to update record');
}
return;
}
if (mode == 'add') {
ndToast.success('Added record');
}
else if (mode == 'delete') {
ndToast.success('Deleted record');
}
else {
ndToast.success('Updated record');
}
// one refresh for every mode; add and delete also trigger their
// own, so both would race into the pane if the sidebar did not
// cancel its in-flight request first, and the aborted one logs
// to the console.
htmx.trigger('#' + tab + '_form', 'submit');
( run in 4.437 seconds using v1.01-cache-2.11-cpan-85d3896f969 )