/** @preserve Eloquence WEBDLG v2
 (C) Copyright Marxmeier Software AG, 2020-2025
 Version: 2.1.2
 $Id: 00-base.js,v 29.22 2025/06/18 11:26:11 rhg Exp $
*//*======================================================================
   WEBDLG v2 namespace / globals
========================================================================*/

var eq = eq || {};
eq.c = eq.c || {};   // Classes
eq.p = eq.p || {};   // Properties:
eq.p.version = 20102; // Version 2.01.02 (major*10,000 + minor*100 + build)

/*========================================================================
   class Obj
   Generic base class.
========================================================================*/

eq.c.Obj = function() {};

/*------------------------------------------------------------------------
   static Obj.subClass()
   Subclassing convenience helper.
------------------------------------------------------------------------*/

(eq.c.Obj.setClassName = function(name, cls) {
   var pd = Object.getOwnPropertyDescriptor(cls.prototype.constructor, 'name');
   if (pd !== undefined)
      pd.value = name;
   else
      pd = {
         value : name,
         configurable : true,
         enumerable : false,
         writable : false
      };
   Object.defineProperty(cls.prototype.constructor, 'name', pd);
})("Obj", eq.c.Obj);

eq.c.Obj.subClass = function(name, cls) {
   var base = this;
   cls.prototype = Object.create(base.prototype);
   Object.defineProperty(cls.prototype, 'constructor', {
      value : cls,
      configurable : false,
      enumerable : false,
      writable : true
   });
   eq.c.Obj.setClassName(name, cls);
   cls.subClass = function(n, c) { return base.subClass.call(this, n, c) };
   return eq.c[name] = cls;
};

/** @preserve $Id: 10-status.js,v 29.1 2023/06/07 08:47:36 rhg Exp $
*//*======================================================================
   Status API
========================================================================*/

/*------------------------------------------------------------------------
   static class Xhr
   XMLHttpRequest wrapper class.
------------------------------------------------------------------------*/

eq.xhr = {
   get : function(self, uri) {
      var cls = eq.xhr;
      if (cls.base === undefined) {
         var b = /^([^?#]+)/.exec(window.location.href)[1];
         cls.base = b + (/\/$/.test(b) ? 'api/' : '/api/');
      }
      var r = new XMLHttpRequest();
      r.onload = function() {
         var json;
         try {
            json = JSON.parse(this.responseText);
         }
         catch (e) {
            throw new Error("Failed to load '" + cls.base + uri +
               "' (" + this.responseText.trim() + ")");
         }
         self.update(json);
      };
      r.open('GET', cls.base + uri);
      r.responseType = 'text';
      r.send();
   },
   refresh : function(self, seconds) {
      var cls = eq.xhr, p;
      cls.refreshTimer.forEach(cls.cancelRefresh, self);
      cls.refreshTimer.set(
         window.setInterval(cls.onRefresh, seconds * 1000, self),
         self);
   },
   refreshTimer : new Map(),
   onRefresh : function(self) {
      self.get();
   },
   cancelRefresh : function(self, id) {
      if (this && this === self) {
         window.clearInterval(id);
         eq.xhr.refreshTimer.delete(id);
      }
   }
};

/*------------------------------------------------------------------------
   static class YesNo
   Yes/No question dialog class.
------------------------------------------------------------------------*/

eq.yesno = {
   open : function(el, msg, self, cb, arg) {
      var cls = eq.yesno, yn, ch;
      cls.close();
      cls.self = self;
      cls.cb = cb;
      cls.arg = arg;
      yn = document.createElement('DIV');
      yn.className = 'eq-yesno';
      ch = document.createElement('SPAN');
      ch.textContent = msg;
      yn.appendChild(ch);
      ch = document.createElement('BUTTON');
      ch.type = 'button';
      ch.className = 'eq-yes';
      ch.textContent = "Yes";
      ch.onclick = cls.close;
      yn.appendChild(ch);
      ch = document.createElement('BUTTON');
      ch.type = 'button';
      ch.className = 'eq-no';
      ch.textContent = "No";
      ch.onclick = cls.close;
      yn.appendChild(ch);
      yn.appendChild(document.createElement('I'));
      window.addEventListener('mousedown', cls.close);
      el.appendChild(yn);
   },
   close : function(e) {
      var cls = eq.yesno;
      if (e) {
         e.preventDefault();
         e.stopImmediatePropagation();
         var t = e.target, cl;
         while (t) {
            if (t === document)
               break;
            if (cl = t.classList) {
               if (cl.contains('eq-yes')) {
                  cls.cb(cls.self, cls.arg);
                  break;
               }
               if (cl.contains('eq-no'))
                  break;
               if (cl.contains('eq-yesno'))
                  return;
            }
            t = t.parentNode;
         }
      }
      cls.self = undefined;
      cls.cb = undefined;
      cls.arg = undefined;
      var n = document.getElementsByClassName('eq-yesno');
      for (var i = 0, l = n.length; i < l; i++) {
         var el = n[i];
         el.parentNode.removeChild(el);
      }
      window.removeEventListener('mousedown', cls.close);
   }
};

/*========================================================================
   class StatusElement extends Obj
   Status element base class.
========================================================================*/

eq.c.Obj.subClass('StatusElement', function(uri) {
   eq.c.Obj.call(this);
   this.uri = uri;
});

/*------------------------------------------------------------------------
   StatusElement.get()
   Request status element update.
------------------------------------------------------------------------*/

eq.c.StatusElement.prototype.get = function() {
   eq.xhr.get(this, this.uri);
};

/*========================================================================
   final class StatusList extends StatusElement
   Status list class.
========================================================================*/

eq.c.StatusElement.subClass('StatusList', function(id, uri, p) {
   eq.c.StatusElement.call(this, uri);
   this.id = id;

   var ta = document.createElement('TABLE');
   ta.id = id;
   ta.classList.add('eq-list');
   (p || document.body).appendChild(ta);

   eq.c.StatusList.map.set(id, this);
   this.get();
});

delete eq.c.StatusList.subClass; // final

/*------------------------------------------------------------------------
   static StatusList.map
   Map table id to StatusList instance.
------------------------------------------------------------------------*/

eq.c.StatusList.map = new Map();

/*------------------------------------------------------------------------
   StatusList.get()
   Request status table update.
------------------------------------------------------------------------*/

eq.c.StatusList.prototype.get = function() {
   eq.xhr.get(this, this.uri);
};

/*------------------------------------------------------------------------
   StatusList.update()
   Update status table.
------------------------------------------------------------------------*/

eq.c.StatusList.prototype.update = function(da) {
   var
      ta = document.getElementById(this.id),
      tb = ta.firstElementChild;
   if (!tb) {
      tb = document.createElement('TBODY');
      ta.appendChild(tb);
   }
   for (var i = 0, l = da.length; i < l; i++) {
      var
         di = da[i],
         tr = document.getElementById(di.id),
         tl, td, sp, at;
      if (tr) {
         tl = tr.firstElementChild;
         td = tl.nextSibling;
      }
      else {
         tr = document.createElement('TR');
         tr.id = di.id;
         tl = document.createElement('TD');
         tl.className = 'eq-label';
         td = document.createElement('TD');
         td.className = 'eq-val';
         tr.appendChild(tl);
         tr.appendChild(td);
         tb.appendChild(tr);
      }
      tl.textContent = di.label + ':';
      td.textContent = ''; // Delete children.
      sp = document.createElement('SPAN');
      sp.className = 'eq-val';
      sp.textContent = di.value;
      td.appendChild(sp);
      if (at = di.attr)
         for (var j = 0, jl = at.length; j < jl; j++) {
            var ai = at[j];
            if (ai.label) {
               sp = document.createElement('SPAN');
               if (!ai.value)
                  sp.id = ai.id;
               sp.className = 'eq-label';
               sp.textContent = ai.label;
               td.appendChild(sp);
            }
            if (ai.value) {
               sp = document.createElement('SPAN');
               sp.id = ai.id;
               sp.className = 'eq-val';
               sp.textContent = ai.value;
               td.appendChild(sp);
            }
         }
   }
};

/*========================================================================
   final class StatusTable extends StatusElement
   Status table class.
========================================================================*/

eq.c.StatusElement.subClass('StatusTable', function(title, id, uri, fld, cl, p) {
   eq.c.StatusElement.call(this, uri);
   this.id = id;
   this.fld = fld;

   if (title) {
      var tt = document.createElement('DIV');
      tt.className = 'eq-title';
      tt.textContent = title + ":";
      (p || document.body).appendChild(tt);
   }

   var ta = document.createElement('TABLE');
   ta.id = id;
   if (cl)
      ta.className = cl;
   ta.classList.add('eq-table');
   (p || document.body).appendChild(ta);

   eq.c.StatusTable.map.set(id, this);
   this.get();
});

delete eq.c.StatusTable.subClass; // final

/*------------------------------------------------------------------------
   static StatusTable.map
   Map table id to StatusTable instance.
------------------------------------------------------------------------*/

eq.c.StatusTable.map = new Map();

/*------------------------------------------------------------------------
   StatusTable.setActionHandler()
   Set status table action handler.
------------------------------------------------------------------------*/

eq.c.StatusTable.prototype.setActionHandler = function(cb, icon) {
   var ta = document.getElementById(this.id);
   if (cb) {
      this.actionHandler = { cb, icon };
      ta.classList.add('eq-action');
   }
   else {
      this.actionHandler = undefined;
      ta.classList.remove('eq-action');
   }
};

/*------------------------------------------------------------------------
   StatusTable.get()
   Request status table update.
------------------------------------------------------------------------*/

eq.c.StatusTable.prototype.get = function(arg, order) {
   if (order === undefined)
      order = this.order();
   var uri = this.uri;
   if (arg)
      uri += '?' + arg;
   if (order)
      uri += (arg ? '&' : '?') + 'order=' + order;
   eq.xhr.get(this, uri);
};

/*------------------------------------------------------------------------
   StatusTable.update()
   Update status table.
------------------------------------------------------------------------*/

eq.c.StatusTable.prototype.update = function(da) {
   var
      ta = document.getElementById(this.id),
      b = ta.getElementsByTagName('BUTTON');
   for (var i = 0, l = b.length; i < l; i++) {
      var btn = b[i];
      btn.disabled = false;
      btn.parentNode.classList.remove('eq-active');
   }
   var
      cls = eq.c.StatusTable,
      fld = this.fld,
      fll = fld.length,
      th = ta.firstElementChild,
      tb;
   if (th)
      tb = th.nextElementSibling;
   else {
      th = document.createElement('THEAD');
      th.className = 'eq-label';
      ta.appendChild(th);
      var
         thr = document.createElement('TR'),
         thh = document.createElement('TH'),
         btn = document.createElement('BUTTON');
      btn.type = 'button';
      btn.className = 'eq-icon-reload';
      btn.onclick = cls.reload;
      thh.appendChild(btn);
      thr.appendChild(thh);
      th.appendChild(thr);
      for (var f = 0; f < fll; f++) {
         var ff = fld[f];
         if (!ff.d) {
            thh = document.createElement('TH');
            var sp = document.createElement('SPAN');
            sp.textContent = ff.l;
            thh.onclick = cls.sort;
            thh.appendChild(sp);
            thr.appendChild(thh);
         }
      }
      tb = document.createElement('TBODY');
      ta.appendChild(tb);
   }
   var
      tr = tb.children,
      trl = tr.length / 2;
   for (var i = 0, l = da.length; i < l; i++) {
      var di = da[i], r, rl;
      if (i < trl) {
         r = tr[i * 2];
         rl = r.childElementCount;
      }
      else {
         r = document.createElement('TR');
         tb.appendChild(r);
         rl = 0;
      }
      for (var j = 0, f = 0; f < fll; f++) {
         if (j === 0) {
            if (rl === 0) {
               var
                  td = document.createElement('TD'),
                  actionHandler = this.actionHandler;
               if (actionHandler)
               {
                  var btn = document.createElement('BUTTON');
                  btn.type = 'button';
                  btn.className = actionHandler.icon;
                  btn.onclick = cls.onAction;
                  td.appendChild(btn);
               }
               r.appendChild(td);
            }
            j++;
         }
         var ff = fld[f];
         if (!ff.d) {
            var td;
            if (j < rl)
               td = r.children[j];
            else {
               td = document.createElement('TD');
               r.appendChild(td);
            }
            if (j === 1) {
               var sp = td.firstElementChild, v;
               if (!sp) {
                  sp = document.createElement('SPAN');
                  td.appendChild(sp);
               }
               sp.className = 'eq-line-id';
               if (sp.textContent !== (v = di[ff.f])) {
                  sp.textContent = v;
                  r.classList.remove('eq-open');
               }
               td.className = 'eq-click';
               td.onclick = cls.detailOpenClose;
            }
            else {
               td.textContent =  di[ff.f];
               var c = ff.c;
               if (c)
                  td.className = c;
            }
            j++;
         }
      }
      var dr;
      if (i < trl) {
         dr = tr[i * 2 + 1];
         dr.textContent = ''; // Delete children.
      }
      else {
         dr = document.createElement('TR');
         dr.className = 'eq-detail';
         tb.appendChild(dr);
      }
      dr.appendChild(document.createElement('TD'));
      var td = document.createElement('TD');
      td.onclick = cls.detailOpenClose;
      dr.appendChild(td);
      td = document.createElement('TD');
      dr.appendChild(td);
      td.colSpan = r.childElementCount - 1;
      for (var j = 0, f = 0; f < fll; f++) {
         var ff = fld[f], v;
         if (ff.d && (v = di[ff.f])) {
            var dd = document.createElement('DIV');
            td.appendChild(dd);
            var sp = document.createElement('SPAN');
            sp.className = 'eq-label';
            sp.textContent = ff.l + ":";
            dd.appendChild(sp);
            sp = document.createElement('SPAN');
            sp.textContent = v;
            dd.appendChild(sp);
            var c = ff.c;
            if (c)
               sp.className = c;
            j++;
         }
      }
   }
   for (l *= 2; tr.length > l;)
      tb.removeChild(tb.lastElementChild);
   if (da.length) {
      var tr = th.firstElementChild.children;
      for (var i = 0, l = tr.length; i < l; i++) {
         var
            thh = tr[i],
            sw = thh.style.width;
         if (sw)
            break;
         var
            cw = Number(sw.replace(/px$/,'')),
            nw = Math.ceil(thh.getBoundingClientRect().width * 100) / 100;
         if (isNaN(cw) || nw > cw)
            thh.style.width = nw + 'px';
      }
   }
};

/*------------------------------------------------------------------------
   StatusTable.order()
   Obtain current status table order.
------------------------------------------------------------------------*/

eq.c.StatusTable.prototype.order = function() {
   var
      fld = this.fld,
      fll = fld.length,
      ta = document.getElementById(this.id),
      th = ta.firstElementChild,
      order = '';
   if (th && (th = th.firstElementChild)) {
      var tc = th.children;
      for (var i = 1, f = 0, l = tc.length; i < l; i++) {
         var ff, cl = tc[i].classList, dir;
         while (f < fll && (ff = fld[f]).d)
            f++;
         if (f++ === fll)
            break;
         if (cl.contains('eq-dn'))
            dir = ':d';
         else if (cl.contains('eq-up'))
            dir = '';
         else
            continue;
         order = ff.f + dir;
         if (i !== 1)
            order += ',' + fld[0].f;
         break;
      }
   }
   return order;
};

/*------------------------------------------------------------------------
   static StatusTable.reload()
   Reload status table event handler.
------------------------------------------------------------------------*/

eq.c.StatusTable.reload = function(e) {
   var ta = e.target;
   while (ta && ta !== document && ta.tagName !== 'TABLE')
      ta = ta.parentNode;
   if (ta) {
      var self = eq.c.StatusTable.map.get(ta.id);
      if (!self)
         throw new Error("StatusTable: unknown instance");
      var b = ta.getElementsByTagName('BUTTON');
      for (var i = 0, l = b.length; i < l; i++) {
         var btn = b[i];
         btn.disabled = true;
         if (btn === e.target)
            btn.parentNode.classList.add('eq-active');
      }
      self.get();
   }
};

/*------------------------------------------------------------------------
   static StatusTable.sort()
   Sort status table event handler.
------------------------------------------------------------------------*/

eq.c.StatusTable.sort = function(e) {
   var t = e.target;
   while (t && t !== document && t.tagName !== 'TH')
      t = t.parentNode;
   if (t) {
      var
         cls = eq.c.StatusTable,
         ta = t,
         self;
      while ((ta = ta.parentNode) && ta !== document)
         if (ta.tagName === 'TABLE') {
            self = cls.map.get(ta.id);
            break;
         }
      if (!self)
         throw new Error("StatusTable: unknown instance");
      var
         fld = self.fld,
         fll = fld.length,
         tr = t.parentNode.children,
         order = '',
         cl;
      for (var i = 1, l = tr.length; i < l; i++) {
         var th = tr[i], cl = th.classList;
         if (t === th) {
            for (var j = 0, f = 0; f < fll; f++) {
               var ff = fld[f];
               if (!ff.d) {
                  if (j === i - 1) {
                     order = ff.f;
                     break;
                  }
                  j++;
               }
            }
            if (cl.contains('eq-up')) {
               cl.remove('eq-up');
               cl.add('eq-dn');
               order += ':d';
            }
            else if (cl.contains('eq-dn')) {
               cl.remove('eq-dn');
               order = '';
            }
            else
               cl.add('eq-up');
            if (order && i !== 1)
               order += ',' + fld[0].f;
         }
         else {
            cl.remove('eq-up');
            cl.remove('eq-dn');
         }
      }
      cls.detailCloseAll(e);
      self.get(null, order);
   }
};

/*------------------------------------------------------------------------
   static StatusTable.onAction()
   Status table action event handler.
------------------------------------------------------------------------*/

eq.c.StatusTable.onAction = function(e) {
   var t = e.target;
   while (t && t !== document && t.tagName !== 'TD')
      t = t.parentNode;
   if (t) {
      var
         cls = eq.c.StatusTable,
         ta = t,
         self, actionHandler;
      while ((ta = ta.parentNode) && ta !== document)
         if (ta.tagName === 'TABLE') {
            self = cls.map.get(ta.id);
            break;
         }
      if (!self)
         throw new Error("StatusTable: unknown instance");
      if (!(actionHandler = self.actionHandler))
         throw new Error("StatusTable: no action handler");
      var lineId = t.parentNode.querySelector('.eq-line-id').textContent;
      actionHandler.cb(self, lineId, e);
   }
};

/*------------------------------------------------------------------------
   static StatusTable.detailOpenClose()
   Open/close detail info status table event handler.
------------------------------------------------------------------------*/

eq.c.StatusTable.detailOpenClose = function(e) {
   var t = e.target;
   while (t && t != document && t.tagName != 'TR')
      t = t.parentNode;
   if (t) {
      var cl = t.classList;
      if (cl.contains('eq-detail'))
         cl = (t = t.previousElementSibling).classList;
      if (!e.shiftKey)
         eq.c.StatusTable.detailCloseAll(e, t);
      if (cl.contains('eq-open'))
         cl.remove('eq-open');
      else
         cl.add('eq-open');
   }
};

/*------------------------------------------------------------------------
   static StatusTable.detailCloseAll()
   Close all detail info status table detail event handler.
------------------------------------------------------------------------*/

eq.c.StatusTable.detailCloseAll = function(e, except) {
   var ta = e.target;
   while (ta && ta !== document && ta.tagName !== 'TABLE')
      ta = ta.parentNode;
   if (ta) {
      var self = eq.c.StatusTable.map.get(ta.id);
      if (!self)
         throw new Error("StatusTable: unknown instance");
      var tb = ta.lastElementChild;
      if (tb) {
         var tr = tb.children;
         for (var i = 0, l = tr.length; i < l; i++) {
            var thh = tr[i];
            if (thh !== except)
               thh.classList.remove('eq-open');
         }
      }
   }
};

/*========================================================================
   Setup status page.
========================================================================*/

window.onload = function() {
   var p = document.getElementById('eq-page');

   // Statistics, refresh every 10 seconds.
   eq.xhr.refresh(new eq.c.StatusList('eq-stats', 'statistics', p), 10);

   // Active sessions.
   var ssn = new eq.c.StatusTable("Active sessions",
      'eq-sessions', 'sessions',
      [
         { l : "ID",           f : 'id',                                 },
         { l : "Application",  f : 'app',                                },
         { l : "Login",        f : 'login'                               },
         { l : "User address", f : 'peer_ws',    c : 'eq-mono'           },
         { l : "DLG address",  f : 'peer_dlg',   c : 'eq-mono'           },
         { l : "Connected",    f : 'connected',  c : 'eq-mono', d : true },
         { l : "Online",       f : 'online',     c : 'eq-mono'           },
         { l : "Idle",         f : 'idle',       c : 'eq-mono'           },
         { l : "Dialog",       f : 'dialog',     c : 'eq-mono', d : true },
         { l : "User-agent",   f : 'user_agent', c : 'eq-mono', d : true }
      ],
      'eq-sort eq-detail', p);
   ssn.kill = function(self, id, e) {
      if (e !== undefined) {
         eq.yesno.open(e.target,
            "Terminate session #" + id + "?",
            self, self.kill, id);
         return;
      }
      self.get('kill=' + id);
   };
   ssn.setActionHandler(ssn.kill, 'eq-icon-kill');
};
