App-Netdisco
view release on metacpan or search on metacpan
share/public/javascripts/netdisco.js view on Meta::CPAN
// Must stay above every other statement here: this file executes during parse,
// so a request started from a DOMContentLoaded listener is already too late.
//
// htmx swaps a 4xx or 5xx body into the target by default. Left on, a Dancer
// error page replaces the pane's own failure message, and in a deferred
// connected-nodes box it destroys the indicator that box refuses to fetch
// without, so a box that failed once can never be clicked again.
htmx.config.noSwap = [204, 304, '4xx', '5xx'];
// Back and Forward re-request the page, which is what we want, but the library
// answers them by swapping the response into the body rather than navigating.
// That re-inserts the layout's script tags and runs them against a document
// that already ran them, so every const in them is declared twice and the page
// dies. Asking for a real reload is the same round trip without that.
htmx.config.history = 'reload';
// The library's own ceiling is 60 seconds, which a Ports tab on a large device
// has been measured to exceed. This one is far above any pane load ever
// observed, so it only ever cuts off a request that was never going to answer.
htmx.config.defaultTimeout = 300000;
// promoted from a <body> data attribute; the layout carries no inline
// JavaScript for CodeQL to skip.
var uri_base = document.body.dataset.ndUriBase;
var nd_check_userlog = (document.body.dataset.ndCheckUserlog === '1');
// a data-nd-has-sidebar="tab" marker of 0 means the tab ships no sidebar
// template. Shared with the htmx path, which does not call do_search.
function nd_has_sidebar(tab) {
// A tab whose sidebar include throws renders the try block's marker (1)
// and then the catch block's (0); a tab with its own sidebar template that
// deliberately reports 0 renders that same 1 first, then its own 0. Either
// way, the last marker in the document is the one taken as authoritative.
var markers = document.querySelectorAll('[data-nd-has-sidebar="' + tab + '"]');
var marker = markers[markers.length - 1];
return marker ? marker.value !== '0' : true;
}
function nd_apply_sidebar (tab) {
if (!nd_has_sidebar(tab)) {
hideWithTooltip('.nd_sidebar, #nd_sidebar-toggle-img-out');
document.querySelectorAll('.content').forEach(function (el) { el.style.marginRight = '10px' });
}
else {
if (sidebar_hidden) {
// netdisco.css sets #nd_sidebar-toggle-img-out { display: none }, so an
// inline style of '' would not override it; the icon is an <i>, whose
// own default display is inline.
var toggleOut = document.getElementById('nd_sidebar-toggle-img-out');
if (toggleOut) toggleOut.style.display = 'inline';
}
else {
document.querySelectorAll('.content').forEach(function (el) { el.style.marginRight = '215px' });
document.querySelectorAll('.nd_sidebar').forEach(function (el) { el.style.display = '' });
}
}
}
// Nothing shipped calls this. It is here for site-local code whose own
// submit handler still calls it; the shipped equivalent is the delegated
// submit listener in this file. It forwards with htmx.ajax() rather than by
// dispatching a submit event, which would re-enter the caller's own handler
// and recurse.
function do_search (event, tab) {
event.preventDefault();
nd_apply_sidebar(tab);
console.error('do_search() now only forwards to htmx and will be removed in '
+ 'a future release. Give the #' + tab + '_form element the hx-get, '
+ 'hx-target, hx-headers and hx-indicator attributes used in '
+ 'share/views/device.tt, then drop this call. Run '
+ '"netdisco-do checksitelocal" to find every affected file.');
// A site that overrode only this handler still has the shipped form, whose
// hx-get has already loaded the pane off this same submit event.
var form = document.getElementById(tab + '_form');
if (form && form.getAttribute('hx-get')) { return }
htmx.ajax('GET',
uri_base + '/ajax/content/' + path + '/' + tab + '?'
share/public/javascripts/netdisco.js view on Meta::CPAN
// it for good. The job queue is that pane.
var tab = target.id.replace(/_pane$/, '');
if (!document.getElementById(tab + '_indicator')) return;
target.classList.add('nd_pane-settling');
});
// Empty the pane for the duration of the request, so the indicator is the
// only thing on screen. Leaving the previous results up gives an interactive
// table that no longer answers the search being run.
//
// jobqueue is excluded for the reason it carries no indicator: it refreshes
// on a timer and would blank on every tick.
document.body.addEventListener('htmx:before:request', function (evt) {
var ctx = evt.detail.ctx;
var target = ctx.target;
if (!target.id.match(/_pane$/)) return;
nd_latest_pane_request = ctx;
disposePopovers();
if (target.id === 'jobqueue_pane') return;
// force-graph renders every frame until destroyed, and emptying the pane
// only detaches its canvas. netdisco-netmap.js destroys the previous
// instance too, but not until its own swap listener re-initializes.
if (target.id === 'netmap_pane' && window.graph && window.graph.fg
&& typeof window.graph.fg._destructor === 'function') {
window.graph.fg._destructor();
}
target.innerHTML = '';
});
document.body.addEventListener('htmx:response:error', function (evt) {
var target = evt.detail.ctx.target;
if (!target.id.match(/_pane$/)) return;
// An expired session is not a fault the reader should report to anyone. The
// server sends HX-Redirect with it, so this message is what remains on
// screen for the moment before the browser leaves, and all that is left for
// a request htmx did not make.
target.classList.remove('nd_pane-settling');
var status = evt.detail.ctx.response && evt.detail.ctx.response.status;
if (status === 401 || status === 403) return nd_session_expired(target);
nd_pane_failure(target, 'server error');
});
// htmx puts a failed send, a timed out request, an abandoned request and an
// exception thrown while displaying an answer on this one event. No context
// at all means there was no request to fail, and an answer that arrived and
// then failed is a display problem the reporter below owns. An abort is
// neither, wherever in the exchange it landed: htmx sets the response before
// it reads the body, so a request abandoned during the read arrives here
// looking like it was answered.
document.body.addEventListener('htmx:error', function (evt) {
var ctx = evt.detail.ctx;
if (!ctx) return;
var error = evt.detail.error;
var aborted = !!(error && error.name === 'AbortError');
if (ctx.response && !aborted) return;
var target = ctx.target;
if (!target.id.match(/_pane$/)) return;
if (nd_latest_pane_request !== ctx) return;
// An abort nothing replaced is the ceiling in htmx.config.defaultTimeout
// firing, which is worth naming: it sends an administrator looking at how
// long the query takes rather than at the network.
nd_pane_failure(target, aborted ? 'request timed out' : 'network error');
});
function nd_session_expired(pane) {
// every part is a literal
// eslint-disable-next-line no-unsanitized/property
pane.innerHTML =
'<div class="col-md-5 alert alert-warning"><i class="fas fa-right-to-bracket"></i> ' +
'Your session has expired. <a href="' + nd_login_url() + '">Log in again</a> to carry on.</div>';
}
function nd_login_url() {
return uri_base + '/login?return_url=' + encodeURIComponent(window.location.pathname + window.location.search);
}
function nd_pane_failure(pane, reason) {
// every part is a literal, including reason: its three call sites pass one
// of three fixed strings and nothing here comes from a response
// eslint-disable-next-line no-unsanitized/property
pane.innerHTML =
'<div class="col-md-5 alert alert-danger"><i class="fas fa-triangle-exclamation"></i> ' +
'Search failed! Please contact your site administrator (' + reason + ').</div>';
}
// A script error otherwise fails silently: htmx fires an event nobody
// listens to, and an exception inside one of our own handlers reaches
// nobody. One toast per page load; the console carries the detail.
var ndReported = false;
function nd_report_script_error(message, detail) {
console.error(message, detail);
if (ndReported) return;
ndReported = true;
ndToast.error('Something on this page failed. The browser console has the details.');
}
window.addEventListener('error', function (evt) {
// a script from another origin reports only the bare "Script error."
if (!evt.error && evt.message === 'Script error.') return;
nd_report_script_error(evt.message, evt.error);
});
window.addEventListener('unhandledrejection', function (evt) {
nd_report_script_error('Unhandled rejection', evt.reason);
});
// The pane handler above takes the failures whose answer never arrived, and
// every abort, and says so in the pane itself. What is left to report as a
// script error is a failure with no request behind it, or an answer that
// arrived and could not be displayed.
document.body.addEventListener('htmx:error', function (evt) {
var error = evt.detail.error;
if (error && error.name === 'AbortError') return;
var ctx = evt.detail.ctx;
if (ctx && !ctx.response) return;
nd_report_script_error('Response could not be displayed', evt.detail);
});
// The chrome is replaced rather than hidden now, so the tip of an element
// being swapped out has to go with it. htmx describes the whole response as
// one list of swap tasks, which is also the only place a piece of chrome the
// page has no element for is still visible: htmx builds no task for it and
// drops it from the response without a word, which reads as the csv or reset
// link quietly going stale. Most often a site-local page template that has
// dropped one of the ids.
( run in 3.865 seconds using v1.01-cache-2.11-cpan-85d3896f969 )