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 )