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 )