@ntlab/sipd-tu-bridge-ui 1.0.3 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/controller/ui.js CHANGED
@@ -33,15 +33,17 @@ class UiController extends Controller {
33
33
  this.addRoute('index', 'get', '/', async (req, res, next) => {
34
34
  /** @type {import('..').SipdApi} */
35
35
  const api = req.app.api;
36
+ api.counter = await api.getCount();
36
37
  for (const bridge of api.bridges) {
37
38
  bridge.stat = await bridge.getStats();
38
39
  bridge.last = await bridge.getLast();
39
40
  bridge.current = await bridge.getCurrent();
40
41
  }
41
- const socketOptions = {reconnection: true};
42
+ const socketOptions = {};
42
43
  if (req.app.get('root') !== '/') {
43
44
  socketOptions.path = req.getPath('/socket.io/');
44
45
  }
46
+ socketOptions.reconnection = true;
45
47
  res.render('ui/index', {
46
48
  socket: {
47
49
  url: `${req.getUri({noproto: true})}/ui`,
@@ -53,6 +55,7 @@ class UiController extends Controller {
53
55
  const result = {updates: {}};
54
56
  /** @type {import('..').SipdApi} */
55
57
  const api = req.app.api;
58
+ result.counter = await api.getCount();
56
59
  for (const bridge of api.bridges) {
57
60
  bridge.stat = await bridge.getStats();
58
61
  bridge.last = await bridge.getLast();
@@ -99,6 +102,13 @@ class UiController extends Controller {
99
102
  }
100
103
  res.json(result);
101
104
  });
105
+ this.addRoute('error', 'get', '/error', async (req, res, next) => {
106
+ /** @type {import('..').SipdApi} */
107
+ const api = req.app.api;
108
+ const result = await api.getErrors(req.params.page || req.query.page, req.params.size || req.query.size);
109
+ result.pages = req.app.locals.pager(result.count, result.size, result.page);
110
+ res.json(result);
111
+ });
102
112
  this.addRoute('about', 'get', '/about', (req, res, next) => {
103
113
  let about;
104
114
  if (req.app.about) {
package/index.js CHANGED
@@ -35,8 +35,10 @@
35
35
  * @property {object} config Configuration
36
36
  * @property {SipdBridge[]} bridges Bridges
37
37
  * @property {AuthenticateFunction} authenticate Perform usename and password authentication
38
- * @property {GetQueuesFunction} getQueues Get queues
38
+ * @property {PagedObjectsPromiseFunction} getQueues Get queues
39
39
  * @property {StringPromiseFunction} getActivity Get activity logs
40
+ * @property {ObjectPromiseFunction} getCount Get activity count
41
+ * @property {PagedObjectsPromiseFunction} getErrors Get captured errors
40
42
  */
41
43
 
42
44
  /**
@@ -71,9 +73,9 @@
71
73
  */
72
74
 
73
75
  /**
74
- * Get queues.
76
+ * A function which returns paged objects Promise.
75
77
  *
76
- * @callback GetQueuesFunction
78
+ * @callback PagedObjectsPromiseFunction
77
79
  * @param {number} page Page number
78
80
  * @param {number} size Page size
79
81
  * @returns {Promise<object[]>}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ntlab/sipd-tu-bridge-ui",
3
- "version": "1.0.3",
3
+ "version": "1.1.0",
4
4
  "description": "SIPD Penatausahaan Bridge Web Interface",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -33,10 +33,10 @@
33
33
  "dependencies": {
34
34
  "@ntlab/express-controller": "^1.2.0",
35
35
  "@ntlab/express-middleware": "^2.4.0",
36
- "@ntlab/ntjs": "^3.0.0",
36
+ "@ntlab/ntjs": "^3.1.0",
37
37
  "@ntlab/ntjs-assets": "^2.139.0",
38
- "@ntlab/ntjs-repo": "^3.0.1",
39
- "@ntlab/ntlib": "^2.9.0",
38
+ "@ntlab/ntjs-repo": "^3.1.0",
39
+ "@ntlab/ntlib": "^2.9.1",
40
40
  "ejs": "^3.1.10",
41
41
  "express": "^5.2.1",
42
42
  "express-session": "^1.19.0",
@@ -14,3 +14,43 @@ body:not([class]) main, #login-container {
14
14
  img.logo {
15
15
  margin-right: 1em !important;
16
16
  }
17
+
18
+ @media only screen and (min-width: 1200px) {
19
+ .ui.container.queue-result {
20
+ max-width: 30em;
21
+ }
22
+ .ui.container.err-data {
23
+ max-width: 40em;
24
+ }
25
+ }
26
+
27
+ @media only screen and (min-width: 992px) and (max-width: 1199.98px) {
28
+ .ui.container.queue-result {
29
+ max-width: 25em;
30
+ }
31
+ .ui.container.err-data {
32
+ max-width: 30em;
33
+ }
34
+ }
35
+
36
+ @media only screen and (min-width: 768px) and (max-width: 991.98px) {
37
+ .ui.container.queue-result {
38
+ max-width: 20em;
39
+ }
40
+ .ui.container.err-data {
41
+ max-width: 20em;
42
+ }
43
+ }
44
+
45
+ @media only screen and (max-width: 767.98px) {
46
+ .ui.container.queue-result {
47
+ max-width: 75vw;
48
+ margin-left: 0 !important;
49
+ margin-right: 0 !important;
50
+ }
51
+ .ui.container.err-data {
52
+ max-width: 75vw;
53
+ margin-left: 0 !important;
54
+ margin-right: 0 !important;
55
+ }
56
+ }
@@ -29,7 +29,7 @@
29
29
  </div>
30
30
  <%_ script.create('JQuery')
31
31
  .useDependencies(['JQuery/Util', 'JQuery/FormPost'])
32
- .addMiddle(`
32
+ .add(`
33
33
  $.login = function(form) {
34
34
  const fp = $.formpost(form, {progress: false, xhr: true});
35
35
  const err = fp.errhelper;
@@ -57,6 +57,6 @@ $.login = function(form) {
57
57
  form.find('input[name=username]').focus();
58
58
  }
59
59
  `)
60
- .addLast(`
60
+ .addInitializer(`
61
61
  $.login($('form.login'));
62
62
  `) -%>
@@ -1,6 +1,6 @@
1
1
  <%_ script.create('JQuery')
2
2
  .useDependencies(['JQuery/Util'])
3
- .addMiddle(`
3
+ .add(`
4
4
  $.define('errhandler', {
5
5
  handlers: {},
6
6
  register(code, handler) {
@@ -1,6 +1,6 @@
1
1
  <%_ script.create('JQuery')
2
2
  .useDependencies(['JQuery/Util', 'SemanticUI/Dialog'])
3
- .addMiddle(`
3
+ .add(`
4
4
  $.errhandler.register(401, function() {
5
5
  const dlgId = 'err401';
6
6
  if ($.ntdlg.isVisible(dlgId)) {
@@ -1,6 +1,6 @@
1
1
  <%_ script.create('JQuery')
2
2
  .useDependencies(['JQuery/Util', 'SemanticUI/Dialog/Message'])
3
- .addMiddle(`
3
+ .add(`
4
4
  $.errhandler.register(500, function() {
5
5
  const dlgId = 'err500';
6
6
  if ($.ntdlg.isVisible(dlgId)) {
@@ -29,7 +29,7 @@
29
29
  <%- menu(menus, {mainmenu: true, indentation: 2}) %>
30
30
  <%_ script.create('JQuery')
31
31
  .useDependencies(['JQuery/Util', 'SemanticUI/Notification', 'SemanticUI/Dialog/Message'])
32
- .addMiddle(`
32
+ .add(`
33
33
  $.tasks = {
34
34
  about() {
35
35
  const self = this;
@@ -65,6 +65,6 @@ $.tasks = {
65
65
  });
66
66
  }
67
67
  }
68
- `).addLast(`
68
+ `).addInitializer(`
69
69
  $.tasks.init();
70
70
  `) -%>
@@ -2,7 +2,7 @@
2
2
  <textarea class="activity" rows="30" readonly></textarea>
3
3
  </div>
4
4
  <%_ script.create('JQuery')
5
- .addMiddle(`
5
+ .add(`
6
6
  $.activity = {
7
7
  el: $('textarea.activity'),
8
8
  url: '${route('Ui', {name: 'activity'})}',
@@ -29,6 +29,6 @@ $.activity = {
29
29
  }
30
30
  }
31
31
  `)
32
- .addLast(`
32
+ .addInitializer(`
33
33
  $.activity.load();
34
34
  `) -%>
@@ -29,7 +29,7 @@
29
29
  </div>
30
30
  <%_ items = JSON.stringify(names, null, 4) -%>
31
31
  <%_ script.create('JQuery')
32
- .add(`
32
+ .addInitializer(`
33
33
  $.bridge.init(${items});
34
34
  $('a[data-bridge]').on('click', function(e) {
35
35
  e.preventDefault();
@@ -1,5 +1,5 @@
1
1
  <%_ script.create('JQuery')
2
- .addMiddle(`
2
+ .add(`
3
3
  $.bridge = {
4
4
  bridges: {},
5
5
  getLog(name) {
@@ -25,27 +25,6 @@ $.bridge = {
25
25
  }
26
26
  }
27
27
  },
28
- update() {
29
- const self = this;
30
- $.get('${route('Ui', {name: 'updates'})}')
31
- .done(function(json) {
32
- if (json.updates) {
33
- for (const bridge of Object.keys(json.updates)) {
34
- for (const [k, v] of Object.entries(json.updates[bridge])) {
35
- $(\`[data-bridge="\${bridge}"] [data-key="\${k}"]\`).each(function() {
36
- const el = $(this);
37
- const value = v && v.value !== undefined ? v.value : v;
38
- if (el.is('input')) {
39
- el.val(value);
40
- } else {
41
- el.html(value ? value : '&ndash;');
42
- }
43
- });
44
- }
45
- }
46
- }
47
- });
48
- },
49
28
  init(bridges) {
50
29
  const self = this;
51
30
  for (const bridge of bridges) {
@@ -0,0 +1,78 @@
1
+ <h3 class="ui header x-title"></h3>
2
+ <table class="ui selectable celled table">
3
+ <thead>
4
+ <tr>
5
+ <th><%= _('#') %></th>
6
+ <th><%= _('Screen') %></th>
7
+ <th><%= _('Error') %></th>
8
+ <th><%= _('Data') %></th>
9
+ </tr>
10
+ </thead>
11
+ </table>
12
+ <%_ script.create('JQuery')
13
+ .useDependencies(['SemanticUI/Loader', 'SemanticUI/Dialog'])
14
+ .add(`
15
+ $.error = $.loader($('div[data-tab="error"] table'), {
16
+ url: '${route('Ui', {name: 'error', page: 'PAGE'})}',
17
+ formatRow(item) {
18
+ return this.toRow(item);
19
+ },
20
+ loaded(json) {
21
+ const self = this;
22
+ if (json.count !== undefined) {
23
+ const counter = $('[data-tab="error"] .label');
24
+ if (json.count) {
25
+ counter.text(json.count).removeClass('hidden');
26
+ } else {
27
+ counter.addClass('hidden');
28
+ }
29
+ }
30
+ $('a.err-img-view').on('click', function(e) {
31
+ e.preventDefault();
32
+ const a = $(this);
33
+ const dlg = $.ntdlg.create(
34
+ 'err-img-view-dlg',
35
+ a.data('tooltip'),
36
+ \`<img class="ui fluid image" src="\${a.find('img').attr('src')}" style="max-height: 70vh;">\`, {
37
+ size: 'fullscreen',
38
+ buttons: {
39
+ okay: {
40
+ type: 'green approve',
41
+ caption: '<i class="check icon"></i>${_('Ok')}',
42
+ }
43
+ }
44
+ }
45
+ );
46
+ $.ntdlg.show(dlg);
47
+ });
48
+ if (self.loading) {
49
+ self.loading = false;
50
+ }
51
+ }
52
+ });
53
+ $.error.toRow = function(data) {
54
+ return $(
55
+ \`<tr><td>\${data.nr}</td>
56
+ <td>\${this.toImg($.toStr(data.image), data.filename)}</td>
57
+ <td>\${$.toStr(data.error)}</td>
58
+ <td><div class="ui scrolling container err-data">\${$.hidePayload($.toStr(data.data))}</div></td>
59
+ </tr>\`);
60
+ }
61
+ $.error.toImg = function(data, alt) {
62
+ if (data) {
63
+ return \`<a href="#" class="err-img-view" data-tooltip="\${alt}" data-position="right center">
64
+ <img class="ui medium rounded bordered image" src="\${data}" alt="\${alt}">
65
+ </a>\`;
66
+ }
67
+ }
68
+ $.error.reload = function() {
69
+ const self = this;
70
+ if (!self.loading) {
71
+ self.loading = true;
72
+ self.load();
73
+ }
74
+ }
75
+ `)
76
+ .addInitializer(`
77
+ $.error.load();
78
+ `) -%>
@@ -1,10 +1,11 @@
1
1
  <div class="ui top attached stackable tabular menu">
2
2
  <a class="item" data-tab="status"><%= _('Status') %></a>
3
3
  <a class="item" data-tab="activity"><%= _('Activity') %></a>
4
- <a class="item" data-tab="queue"><%= _('Queue') %></a>
4
+ <a class="item" data-tab="queue"><%= _('Queue') %> <span class="ui hidden tiny label"></span></a>
5
5
  <%_ if (api.bridges.length) { -%>
6
6
  <a class="item" data-tab="bridge"><%= _('Bridge') %> <span class="ui tiny label"><%= api.bridges.length %></span></a>
7
7
  <%_ } -%>
8
+ <a class="item" data-tab="error"><%= _('Error') %> <span class="ui hidden tiny label"></span></a>
8
9
  </div>
9
10
  <div class="ui bottom attached tab segment" data-tab="status">
10
11
  <%- include('status') -%>
@@ -21,9 +22,13 @@
21
22
  <%- include('bridge') -%>
22
23
  </div>
23
24
  <%_ } -%>
25
+ <div class="ui bottom attached tab segment" data-tab="error">
26
+ <%- include('error') -%>
27
+ </div>
28
+ <%- include('util') -%>
24
29
  <%_ script.create('JQuery')
25
30
  .useDependencies(['SocketIO'])
26
- .addMiddle(`
31
+ .add(`
27
32
  $.uiCon = {
28
33
  connected: false,
29
34
  data: ${s(socket, 1)},
@@ -55,12 +60,12 @@ $.uiCon = {
55
60
  })
56
61
  .on('queue', function() {
57
62
  $.queue.reload();
58
- $.bridge.update();
63
+ $.uiRefresh();
59
64
  });
60
65
  }
61
66
  }
62
67
  `)
63
- .addLast(`
68
+ .addInitializer(`
64
69
  $('.menu .item').tab({
65
70
  autoTabActivation: window.location.hash ? window.location.hash.substr(1) : true,
66
71
  onLoad(path, params, history) {
package/views/ui/info.ejs CHANGED
@@ -4,13 +4,25 @@
4
4
  </div>
5
5
  <div class="extra content">
6
6
  <div class="ui form">
7
- <div class="fields">
8
- <div class="six wide field"><%= _('Protocol:') %></div>
9
- <div class="ten wide field"><%= api.proto %></div>
10
- </div>
11
- <div class="fields">
12
- <div class="six wide field"><%= _('Mode:') %></div>
13
- <div class="ten wide field"><%= api.mode %></div>
7
+ <div class="ui two column stackable grid">
8
+ <div class="column">
9
+ <div class="fields">
10
+ <div class="eight wide field"><label><%= _('Protocol:') %></label></div>
11
+ <div class="eight wide field"><label><%= api.proto %></label></div>
12
+ </div>
13
+ <div class="fields">
14
+ <div class="eight wide field"><label><%= _('Mode:') %></label></div>
15
+ <div class="eight wide field"><label><%= api.mode %></label></div>
16
+ </div>
17
+ </div>
18
+ <div class="column">
19
+ <%_ Object.keys(api.counter).forEach(counter => { -%>
20
+ <div class="fields">
21
+ <div class="eight wide field"><label><%= _(api.counter[counter].label) %></label></div>
22
+ <div class="eight wide field"><label data-counter="<%= counter %>"><%= _(api.counter[counter].value) %></label></div>
23
+ </div>
24
+ <%_ }) -%>
25
+ </div>
14
26
  </div>
15
27
  </div>
16
28
  </div>
@@ -14,14 +14,22 @@
14
14
  </table>
15
15
  <%_ script.create('JQuery')
16
16
  .useDependencies('SemanticUI/Loader')
17
- .addMiddle(`
17
+ .add(`
18
18
  $.queue = $.loader($('div[data-tab="queue"] table'), {
19
19
  url: '${route('Ui', {name: 'queue', page: 'PAGE'})}',
20
20
  formatRow(item) {
21
21
  return this.toRow(item);
22
22
  },
23
- loaded() {
23
+ loaded(json) {
24
24
  const self = this;
25
+ if (json.count !== undefined) {
26
+ const counter = $('[data-tab="queue"] .label');
27
+ if (json.count) {
28
+ counter.text(json.count).removeClass('hidden');
29
+ } else {
30
+ counter.addClass('hidden');
31
+ }
32
+ }
25
33
  if (self.loading) {
26
34
  self.loading = false;
27
35
  }
@@ -30,25 +38,14 @@ $.queue = $.loader($('div[data-tab="queue"] table'), {
30
38
  $.queue.toRow = function(data) {
31
39
  return $(
32
40
  \`<tr><td>\${data.nr}</td>
33
- <td>\${this.toStr(data.id)}</td>
34
- <td>\${this.toStr(data.type)}</td>
35
- <td>\${this.toStr(data.name)}</td>
41
+ <td>\${$.toStr(data.id)}</td>
42
+ <td>\${$.toStr(data.type)}</td>
43
+ <td>\${$.toStr(data.name)}</td>
36
44
  <td>\${this.toStatus(data.status)}</td>
37
- <td>\${this.toStr(data.result)}</td>
38
- <td>\${this.toStr(data.time)}</td>
45
+ <td><div class="ui scrolling container queue-result">\${$.hidePayload($.toStr(data.result))}</div></td>
46
+ <td>\${$.toStr(data.time)}</td>
39
47
  </tr>\`);
40
48
  }
41
- $.queue.toStr = function(o) {
42
- if (
43
- Array.isArray(o) ||
44
- typeof o === 'object' && o.constructor.name === 'Object'
45
- ) {
46
- return JSON.stringify(o);
47
- } else if (o !== undefined && o !== null) {
48
- return o.toString();
49
- }
50
- return '';
51
- }
52
49
  $.queue.toStatus = function(data) {
53
50
  const icon = {
54
51
  new: 'pause circle outline',
@@ -59,7 +56,7 @@ $.queue.toStatus = function(data) {
59
56
  skipped: 'exclamation',
60
57
  }[data];
61
58
  return icon ? \`<div data-tooltip="\${data}" data-position="right center"><i class="\${icon} icon"></i></div>\` :
62
- this.toStr(data);
59
+ $.toStr(data);
63
60
  }
64
61
  $.queue.reload = function() {
65
62
  const self = this;
@@ -69,6 +66,6 @@ $.queue.reload = function() {
69
66
  }
70
67
  }
71
68
  `)
72
- .addLast(`
69
+ .addInitializer(`
73
70
  $.queue.load();
74
71
  `) -%>
@@ -0,0 +1,52 @@
1
+ <%_ script.create('JQuery')
2
+ .add(`
3
+ $.uiRefresh = function() {
4
+ $.get('${route('Ui', {name: 'updates'})}')
5
+ .done(function(json) {
6
+ if (json.counter) {
7
+ for (const [k, v] of Object.entries(json.counter)) {
8
+ $(\`[data-counter="\${k}"]\`).each(function() {
9
+ const el = $(this);
10
+ const value = v && v.value !== undefined ? v.value : v;
11
+ el.html(value);
12
+ });
13
+ }
14
+ }
15
+ if (json.updates) {
16
+ for (const bridge of Object.keys(json.updates)) {
17
+ for (const [k, v] of Object.entries(json.updates[bridge])) {
18
+ $(\`[data-bridge="\${bridge}"] [data-key="\${k}"]\`).each(function() {
19
+ const el = $(this);
20
+ const value = v && v.value !== undefined ? v.value : v;
21
+ if (el.is('input')) {
22
+ el.val(value);
23
+ } else {
24
+ el.html(value ? value : '&ndash;');
25
+ }
26
+ });
27
+ }
28
+ }
29
+ }
30
+ });
31
+ }
32
+ $.hidePayload = function(message) {
33
+ if (message) {
34
+ message = message.replace(
35
+ /[A-Za-z0-9\\+\\/\\=\\@\\,]{40,}/g,
36
+ s => \`<i class="comment dots outline icon" data-tooltip="\${s}" data-position="right center"></i>\`
37
+ );
38
+ }
39
+ return message;
40
+ }
41
+ $.toStr = function(o) {
42
+ if (
43
+ Array.isArray(o) ||
44
+ typeof o === 'object' && o.constructor.name === 'Object'
45
+ ) {
46
+ return JSON.stringify(o);
47
+ } else if (o !== undefined && o !== null) {
48
+ return o.toString();
49
+ }
50
+ return '';
51
+ }
52
+ `) -%>