@ntlab/sipd-tu-bridge-ui 1.4.1 → 1.6.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.
@@ -25,6 +25,11 @@
25
25
  const Controller = require('@ntlab/express-controller');
26
26
  const Express = require('express').application;
27
27
 
28
+ /**
29
+ * Security controller provides user authentication mechanism.
30
+ *
31
+ * @author Toha <tohenk@yahoo.com>
32
+ */
28
33
  class SecurityController extends Controller {
29
34
 
30
35
  buildRoutes() {
package/controller/ui.js CHANGED
@@ -27,6 +27,11 @@ const path = require('path');
27
27
  const Controller = require('@ntlab/express-controller');
28
28
  const Express = require('express').application;
29
29
 
30
+ /**
31
+ * Ui controller provides user interface interaction.
32
+ *
33
+ * @author Toha <tohenk@yahoo.com>
34
+ */
30
35
  class UiController extends Controller {
31
36
 
32
37
  buildRoutes() {
@@ -38,6 +43,7 @@ class UiController extends Controller {
38
43
  bridge.stat = await bridge.getStats();
39
44
  bridge.last = await bridge.getLast();
40
45
  bridge.current = await bridge.getCurrent();
46
+ bridge.logs = await bridge.getLogFiles();
41
47
  }
42
48
  const socketOptions = {};
43
49
  if (req.app.get('root') !== '/') {
@@ -109,6 +115,37 @@ class UiController extends Controller {
109
115
  result.pages = req.app.locals.pager(result.count, result.size, result.page);
110
116
  res.json(result);
111
117
  });
118
+ this.addRoute('task', 'post', '/task/:op', async (req, res, next) => {
119
+ const result = {
120
+ success: false
121
+ }
122
+ let retval;
123
+ /** @type {import('..').SipdApi} */
124
+ const api = req.app.api;
125
+ switch (req.params.op) {
126
+ case 'log':
127
+ if (req.body.seq) {
128
+ if (req.body.log) {
129
+ retval = await api.query({cmd: 'log-file', log: req.body.log, seq: req.body.seq});
130
+ } else {
131
+ retval = await api.query({cmd: 'log-file', seq: req.body.seq});
132
+ }
133
+ }
134
+ break;
135
+ case 'remove':
136
+ if (req.body.queue) {
137
+ retval = await api.query({cmd: 'remove-queue', queue: req.body.queue});
138
+ }
139
+ if (req.body.error) {
140
+ retval = await api.query({cmd: 'clean-err', error: req.body.error});
141
+ }
142
+ break;
143
+ case 'restart':
144
+ retval = await api.query({cmd: 'restart'});
145
+ break;
146
+ }
147
+ res.json({...result, ...(retval || {})});
148
+ });
112
149
  this.addRoute('about', 'get', '/about', (req, res, next) => {
113
150
  let about;
114
151
  if (req.app.about) {
@@ -125,24 +162,6 @@ class UiController extends Controller {
125
162
  }
126
163
  res.json(about);
127
164
  });
128
- this.addRoute('task', 'post', '/task/:op', async (req, res, next) => {
129
- const result = {
130
- success: false
131
- }
132
- /** @type {import('..').SipdApi} */
133
- const api = req.app.api;
134
- switch (req.params.op) {
135
- case 'remove':
136
- if (req.body.error) {
137
- Object.assign(result, await api.query({cmd: 'clean-err', error: req.body.error}));
138
- }
139
- break;
140
- case 'restart':
141
- Object.assign(result, await api.query({cmd: 'restart'}));
142
- break;
143
- }
144
- res.json(result);
145
- });
146
165
  }
147
166
 
148
167
  /**
package/helper/app.js CHANGED
@@ -32,6 +32,8 @@ const { minify_sync } = require('terser');
32
32
 
33
33
  /**
34
34
  * Express app middleware.
35
+ *
36
+ * @author Toha <tohenk@yahoo.com>
35
37
  */
36
38
  class AppFunctions extends HelperFunctions {
37
39
 
package/index.js CHANGED
@@ -22,7 +22,20 @@
22
22
  * SOFTWARE.
23
23
  */
24
24
 
25
- /* --- BEGIN API --- */
25
+ const createError = require('http-errors');
26
+ const express = require('express');
27
+ const path = require('path');
28
+ const logger = require('morgan');
29
+ const session = require('express-session');
30
+ const FileStore = require('session-file-store')(session);
31
+ const { Helper, Security, Factory } = require('@ntlab/express-middleware');
32
+ const { ScriptManager, ScriptAsset } = require('@ntlab/ntjs');
33
+ const { Assets, CDN } = require('@ntlab/ntjs-assets');
34
+
35
+ // register script repository
36
+ require('@ntlab/ntjs-repo')();
37
+
38
+ /* --- BEGIN API V1 --- */
26
39
 
27
40
  /**
28
41
  * SIPD Penatausahaan Bridge main application.
@@ -35,10 +48,10 @@
35
48
  * @property {object} config Configuration
36
49
  * @property {SipdBridge[]} bridges Bridges
37
50
  * @property {AuthenticateFunction} authenticate Perform usename and password authentication
38
- * @property {PagedObjectsPromiseFunction} getQueues Get queues
39
- * @property {StringPromiseFunction} getActivity Get activity logs
40
- * @property {ObjectPromiseFunction} getCount Get activity count
41
- * @property {PagedObjectsPromiseFunction} getErrors Get captured errors
51
+ * @property {PagedObjectsFunction} getQueues Get queues
52
+ * @property {ActivityFunction} getActivity Get activity logs
53
+ * @property {ObjectFunction} getCount Get activity count
54
+ * @property {PagedObjectsFunction} getErrors Get captured errors
42
55
  * @property {QueryFunction} query Perform API query
43
56
  */
44
57
 
@@ -48,10 +61,11 @@
48
61
  * @typedef {Object} SipdBridge
49
62
  * @property {string} name Name
50
63
  * @property {number} year Year
51
- * @property {ObjectPromiseFunction} getStats Get bridge stats
52
- * @property {StringPromiseFunction} getLogs Get bridge logs
53
- * @property {ObjectPromiseFunction} getLast Get last queue
54
- * @property {ObjectPromiseFunction} getCurrent Get current processing queue
64
+ * @property {ObjectFunction} getStats Get bridge stats
65
+ * @property {ActivityFunction} getLogs Get bridge logs
66
+ * @property {LogFilesFunction} getLogFiles Get bridge addiitonal log files
67
+ * @property {ObjectFunction} getLast Get last queue
68
+ * @property {ObjectFunction} getCurrent Get current processing queue
55
69
  */
56
70
 
57
71
  /**
@@ -74,50 +88,46 @@
74
88
  */
75
89
 
76
90
  /**
77
- * A function which returns paged objects Promise.
91
+ * Get objects at specified page with size of limit. If none specified
92
+ * it returns first page with default size limit (either 10 or 25).
78
93
  *
79
- * @callback PagedObjectsPromiseFunction
80
- * @param {number} page Page number
81
- * @param {number} size Page size
94
+ * @callback PagedObjectsFunction
95
+ * @param {?number} page Page number
96
+ * @param {?number} size Page size
82
97
  * @returns {Promise<object[]>}
83
98
  */
84
99
 
85
100
  /**
86
- * A function which returns object Promise.
101
+ * Get miscellanous object such as queue or log.
87
102
  *
88
- * @callback ObjectPromiseFunction
103
+ * @callback ObjectFunction
89
104
  * @returns {Promise<object>}
90
105
  */
91
106
 
92
107
  /**
93
- * A function which returns string Promise.
108
+ * Get string content such as activity logs.
94
109
  *
95
- * @callback StringPromiseFunction
110
+ * @callback ActivityFunction
111
+ * @param {?string} seq Sequence number
96
112
  * @returns {Promise<string>}
97
113
  */
98
114
 
99
115
  /**
100
- * A function which returns object Promise.
116
+ * Query api and return result object.
101
117
  *
102
118
  * @callback QueryFunction
103
119
  * @param {object} data Query data
104
120
  * @returns {Promise<object>}
105
121
  */
106
122
 
107
- /* --- END API --- */
108
-
109
- const createError = require('http-errors');
110
- const express = require('express');
111
- const path = require('path');
112
- const logger = require('morgan');
113
- const session = require('express-session');
114
- const FileStore = require('session-file-store')(session);
115
- const { Helper, Security, Factory } = require('@ntlab/express-middleware');
116
- const { ScriptManager, ScriptAsset } = require('@ntlab/ntjs');
117
- const { Assets, CDN } = require('@ntlab/ntjs-assets');
123
+ /**
124
+ * Get additional log files.
125
+ *
126
+ * @callback LogFilesFunction
127
+ * @returns {Promise<[{name: string, seq: string, time: number}]>}
128
+ */
118
129
 
119
- // register script repository
120
- require('@ntlab/ntjs-repo')();
130
+ /* --- END API --- */
121
131
 
122
132
  /**
123
133
  * Express application.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ntlab/sipd-tu-bridge-ui",
3
- "version": "1.4.1",
3
+ "version": "1.6.0",
4
4
  "description": "SIPD Penatausahaan Bridge Web Interface",
5
5
  "main": "index.js",
6
6
  "scripts": {
@@ -31,17 +31,16 @@
31
31
  },
32
32
  "homepage": "https://github.com/tohenk/node-sipd-tu-bridge-ui#readme",
33
33
  "dependencies": {
34
- "@ntlab/express-controller": "^1.2.0",
35
- "@ntlab/express-middleware": "^2.4.0",
34
+ "@ntlab/express-controller": "^1.3.0",
35
+ "@ntlab/express-middleware": "^2.6.0",
36
36
  "@ntlab/ntjs": "^3.1.0",
37
- "@ntlab/ntjs-assets": "^2.139.0",
37
+ "@ntlab/ntjs-assets": "^2.144.0",
38
38
  "@ntlab/ntjs-repo": "^3.1.0",
39
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",
43
43
  "http-errors": "^2.0.1",
44
- "moment": "^2.30.1",
45
44
  "morgan": "^1.10.1",
46
45
  "session-file-store": "^1.5.0",
47
46
  "terser": "^5.46.0"
@@ -1,3 +1,8 @@
1
+ :root {
2
+ --viewport-h-limit: 70vh;
3
+ --viewport-w-limit: 70vw;
4
+ }
5
+
1
6
  body.with-menu {
2
7
  padding-top: 5em;
3
8
  }
@@ -15,6 +20,14 @@ img.logo {
15
20
  margin-right: 1em !important;
16
21
  }
17
22
 
23
+ .v-max {
24
+ max-height: var(--viewport-h-limit) !important;
25
+ }
26
+
27
+ .v-min {
28
+ min-height: var(--viewport-h-limit) !important;
29
+ }
30
+
18
31
  @media only screen and (min-width: 1200px) {
19
32
  .ui.container.queue-name {
20
33
  max-width: 20em;
@@ -47,10 +60,10 @@ img.logo {
47
60
 
48
61
  @media only screen and (min-width: 768px) and (max-width: 991.98px) {
49
62
  .ui.container.queue-name {
50
- max-width: 10em;
63
+ max-width: 8em;
51
64
  }
52
65
  .ui.container.queue-result {
53
- max-width: 12.5em;
66
+ max-width: 10em;
54
67
  }
55
68
  .ui.container.err-message {
56
69
  max-width: 12.5em;
@@ -65,7 +78,7 @@ img.logo {
65
78
  .ui.container.queue-result,
66
79
  .ui.container.err-message,
67
80
  .ui.container.err-data {
68
- max-width: 75vw;
81
+ max-width: var(--viewport-w-limit);
69
82
  margin-left: 0 !important;
70
83
  margin-right: 0 !important;
71
84
  }
@@ -15,17 +15,17 @@
15
15
  <body<% if (mainmenu) { %> class="with-menu"<% } %>>
16
16
  <%_ if (mainmenu) { -%>
17
17
  <section class="ui fixed inverted menu">
18
- <%- mainmenu -%>
18
+ <%- indent(mainmenu, 4) -%>
19
19
  </section>
20
20
  <%_ } -%>
21
21
  <main class="ui container">
22
22
  <%_ if (title.length) { -%>
23
23
  <h1 class="ui dividing header"><%= title %></h1>
24
24
  <%_ } -%>
25
- <%- content -%>
25
+ <%- indent(content, 4) -%>
26
26
  </main>
27
27
  <%_ if ((addons = slot('addons')) && (addons = include(addons))) { -%>
28
- <%- addons -%>
28
+ <%- indent(addons, 2) -%>
29
29
  <%_ } -%>
30
30
  <%_ javascripts().forEach(js => { -%>
31
31
  <script type="text/javascript" src="<%- path(js) %>"></script>
@@ -32,7 +32,7 @@
32
32
  }
33
33
  });
34
34
  } -%>
35
- <%- menu(menus, {mainmenu: true, indentation: 2}) %>
35
+ <%- menu(menus, {mainmenu: true}) %>
36
36
  <%_ script.create('JQuery')
37
37
  .useDependencies(['JQuery/Util', 'SemanticUI/Notification', 'SemanticUI/Dialog/Message', 'SemanticUI/Dialog/Confirm'])
38
38
  .add(`
@@ -11,13 +11,30 @@
11
11
  </div>
12
12
  <div class="twelve wide column">
13
13
  <%_ bridges.forEach(bridge => { -%>
14
- <div class="ui form bridge <%= bridge.name %>" style="display: none;">
15
- <%_ Object.keys(bridge.stat).forEach(stat => { -%>
16
- <div class="fields">
17
- <div class="six wide field"><label><%= _(bridge.stat[stat].label) %></label></div>
18
- <div class="ten wide field"><input type="text" value="<%- bridge.stat[stat].value %>" data-key="<%= stat %>" readonly></div>
14
+ <div class="ui form bridge <%= bridge.name %>" data-bridge="<%= bridge.name %>" style="display: none;">
15
+ <div class="ui two column stackable grid">
16
+ <div class="column">
17
+ <%_ Object.keys(bridge.stat).forEach(stat => { -%>
18
+ <div class="two fields">
19
+ <div class="field"><label><%= _(bridge.stat[stat].label) %></label></div>
20
+ <div class="field"><input type="text" value="<%- bridge.stat[stat].value %>" data-key="<%= stat %>" readonly></div>
21
+ </div>
22
+ <%_ }) -%>
23
+ </div>
24
+ <div class="column">
25
+ <div class="two fields">
26
+ <div class="field"><label><%= _('Archived logs') %></label></div>
27
+ <div class="field">
28
+ <select class="ui dropdown" data-log="<%= bridge.name %>" data-title="<%= _('Bridge <span class="ui info text">%NAME%</span> log %LOG%') %>">
29
+ <option value=""><%= _('- Select log -') %></option>
30
+ <%_ bridge.logs.forEach(log => { -%>
31
+ <option value="<%= log.seq %>"><%= log.name %></option>
32
+ <%_ }) -%>
33
+ </select>
34
+ </div>
35
+ </div>
36
+ </div>
19
37
  </div>
20
- <%_ }) -%>
21
38
  <div class="field">
22
39
  <label><%= _('Logs') %></label>
23
40
  <textarea class="log <%= bridge.name %>" rows="20" readonly></textarea>
@@ -39,4 +56,5 @@ $('a[data-bridge]').on('click', function(e) {
39
56
  $(\`.ui.form.bridge\`).hide();
40
57
  $(\`.ui.form.bridge.\${a.data('bridge')}\`).show();
41
58
  }).filter(':first').click();
59
+ $('.ui.dropdown').dropdown();
42
60
  `) -%>
@@ -39,21 +39,21 @@ $.error = $.loader($('div[data-tab="error"] table'), {
39
39
  });
40
40
  $.error.toRow = function(data) {
41
41
  return $(\`
42
- <tr><td>\${data.nr}</td>
43
- <td>\${this.toImg($.toStr(data.image), data.filename)}</td>
44
- <td>\${this.toPayload(data.error, '${_('Error Message')}')}</td>
45
- <td>\${this.toPayload(data.data, '${_('Error Data')}')}</td>
46
- <td>
47
- <a href="#" class="err-clicker" data-op="delete" data-filename="\${data.filename}" role="button"><i class="trash alternate outline red icon"></i></a>
48
- </td>
49
- </tr>\`);
42
+ <tr><td>\${data.nr}</td>
43
+ <td>\${this.toImg($.toStr(data.image), data.filename)}</td>
44
+ <td>\${this.toPayload(data.error, '${_('Error Message')}')}</td>
45
+ <td>\${this.toPayload(data.data, '${_('Error Data')}')}</td>
46
+ <td>
47
+ <a href="#" class="err-clicker" data-op="delete" data-filename="\${data.filename}" role="button"><i class="trash alternate outline red icon"></i></a>
48
+ </td>
49
+ </tr>\`);
50
50
  }
51
51
  $.error.toImg = function(data, alt) {
52
52
  if (data) {
53
53
  return \`
54
- <a href="#" class="err-clicker" data-op="view" data-title="\${alt}" data-tooltip="\${alt}" data-position="right center">
55
- <img class="ui medium rounded bordered image" src="\${data}" alt="\${alt}">
56
- </a>\`;
54
+ <a href="#" class="err-clicker" data-op="view" data-title="\${alt}" data-tooltip="\${alt}" data-position="right center">
55
+ <img class="ui medium rounded bordered image" src="\${data}" alt="\${alt}">
56
+ </a>\`;
57
57
  }
58
58
  }
59
59
  $.error.toPayload = function(data, title) {
@@ -79,10 +79,10 @@ $.error.toPayload = function(data, title) {
79
79
  excerpt += '&hellip;';
80
80
  }
81
81
  return \`
82
- <a href="#" class="err-clicker" data-op="view" data-title="\${title}">
83
- <span>\${excerpt}</span>
84
- <pre style="display: none;">\${payload}</pre>
85
- </a>\`;
82
+ <a href="#" class="err-clicker" data-op="view" data-title="\${title}">
83
+ <span>\${excerpt}</span>
84
+ <pre style="display: none;">\${payload}</pre>
85
+ </a>\`;
86
86
  }
87
87
  }
88
88
  $.error.handle = function(el) {
@@ -92,7 +92,7 @@ $.error.handle = function(el) {
92
92
  let content, size = 'large';
93
93
  const img = el.find('img'), pre = el.find('pre');
94
94
  if (img.length) {
95
- content = \`<img class="ui fluid image" src="\${img.attr('src')}" style="max-height: 70vh;">\`;
95
+ content = \`<img class="ui fluid image v-max" src="\${img.attr('src')}">\`;
96
96
  size = 'fullscreen';
97
97
  }
98
98
  if (pre.length) {
@@ -113,7 +113,7 @@ $.error.handle = function(el) {
113
113
  $.ntdlg.confirm(
114
114
  'err-delete-confirm-dlg',
115
115
  '${_('Confirm')}',
116
- '${_('Are you sure want to remove error <code>%ERR%</code>?')}'.replace(/%ERR%/g, el.data('filename')),
116
+ '${_('Are you sure want to remove error <span class="ui error text">%ERR%</span>?')}'.replace(/%ERR%/g, el.data('filename')),
117
117
  $.ntdlg.ICON_QUESTION,
118
118
  function() {
119
119
  $.post('${route('Ui', {name: 'task', op: 'remove'})}', {error: el.data('filename')})
@@ -1,29 +1,29 @@
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') %> <span class="ui hidden tiny label"></span></a>
4
+ <a class="item" data-tab="queue"><%= _('Queue') %> <span class="ui hidden blue tiny label"></span></a>
5
5
  <%_ if (api.bridges.length) { -%>
6
- <a class="item" data-tab="bridge"><%= _('Bridge') %> <span class="ui tiny label"><%= api.bridges.length %></span></a>
6
+ <a class="item" data-tab="bridge"><%= _('Bridge') %> <span class="ui green 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
+ <a class="item" data-tab="error"><%= _('Error') %> <span class="ui hidden red tiny label"></span></a>
9
9
  </div>
10
10
  <div class="ui bottom attached tab segment" data-tab="status">
11
- <%- include('status') -%>
11
+ <%- indent(include('status'), 2) -%>
12
12
  </div>
13
13
  <div class="ui bottom attached tab segment" data-tab="activity">
14
- <%- include('activity') -%>
14
+ <%- indent(include('activity'), 2) -%>
15
15
  </div>
16
16
  <div class="ui bottom attached tab segment" data-tab="queue">
17
- <%- include('queue') -%>
17
+ <%- indent(include('queue'), 2) -%>
18
18
  </div>
19
19
  <%- include('bridgehandler') -%>
20
20
  <%_ if (api.bridges.length) { -%>
21
21
  <div class="ui bottom attached tab segment" data-tab="bridge">
22
- <%- include('bridge') -%>
22
+ <%- indent(include('bridge'), 2) -%>
23
23
  </div>
24
24
  <%_ } -%>
25
25
  <div class="ui bottom attached tab segment" data-tab="error">
26
- <%- include('error') -%>
26
+ <%- indent(include('error'), 2) -%>
27
27
  </div>
28
28
  <%- include('ticker') -%>
29
29
  <%- include('util') -%>
@@ -84,5 +84,18 @@ $('.menu [data-tab]').tab({
84
84
  window.location.hash = path;
85
85
  }
86
86
  });
87
+ $('select[data-log]').on('change', function(e) {
88
+ e.preventDefault();
89
+ const el = $(this);
90
+ const seq = el.val();
91
+ if (seq !== '') {
92
+ const log = el.data('log');
93
+ const logname = el.find(\`option[value="\${seq}"]\`).text();
94
+ const title = el.data('title')
95
+ .replace(/%LOG%/g, logname)
96
+ .replace(/%NAME%/g, log?.toUpperCase());
97
+ $.viewLog(title, log, seq);
98
+ }
99
+ });
87
100
  $.uiCon.init();
88
101
  `) -%>
package/views/ui/info.ejs CHANGED
@@ -3,27 +3,27 @@
3
3
  <div class="item header"><i class="ui info circle icon"></i><%- _('General Information') %></div>
4
4
  </div>
5
5
  <div class="extra content">
6
- <div class="ui form">
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>
6
+ <div class="ui form">
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>
17
12
  </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
- <%_ }) -%>
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>
25
16
  </div>
26
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>
27
26
  </div>
27
+ </div>
28
28
  </div>
29
29
  </div>
@@ -9,11 +9,12 @@
9
9
  <th><%= _('Status') %></th>
10
10
  <th><%= _('Result') %></th>
11
11
  <th><%= _('Time') %></th>
12
+ <th><%= _('Action') %></th>
12
13
  </tr>
13
14
  </thead>
14
15
  </table>
15
16
  <%_ script.create('JQuery')
16
- .useDependencies('SemanticUI/Loader')
17
+ .useDependencies(['SemanticUI/Loader', 'SemanticUI/Dialog/Confirm'])
17
18
  .add(`
18
19
  $.queue = $.loader($('div[data-tab="queue"] table'), {
19
20
  url: '${route('Ui', {name: 'queue', page: 'PAGE'})}',
@@ -30,6 +31,11 @@ $.queue = $.loader($('div[data-tab="queue"] table'), {
30
31
  counter.addClass('hidden');
31
32
  }
32
33
  }
34
+ $('[data-status!="processing"] [data-op="delete"]').hide();
35
+ $('a.queue-clicker').on('click', function(e) {
36
+ e.preventDefault();
37
+ self.handle($(this));
38
+ });
33
39
  if (self.loading) {
34
40
  self.loading = false;
35
41
  }
@@ -37,14 +43,17 @@ $.queue = $.loader($('div[data-tab="queue"] table'), {
37
43
  });
38
44
  $.queue.toRow = function(data) {
39
45
  return $(\`
40
- <tr><td>\${data.nr}</td>
41
- <td>\${$.toStr(data.id)}</td>
42
- <td>\${$.toStr(data.type)}</td>
43
- <td><div class="ui scrolling container queue-name">\${$.hidePayload($.toStr(data.name))}</div></td>
44
- <td>\${this.toStatus(data.status)}</td>
45
- <td><div class="ui scrolling container queue-result">\${$.hidePayload($.toStr(data.result))}</div></td>
46
- <td>\${$.toStr(data.time)}</td>
47
- </tr>\`);
46
+ <tr data-status="\${data.status}"><td>\${data.nr}</td>
47
+ <td>\${$.toStr(data.id)}</td>
48
+ <td>\${$.toStr(data.type)}</td>
49
+ <td><div class="ui scrolling container queue-name">\${$.hidePayload($.toStr(data.name))}</div></td>
50
+ <td>\${this.toStatus(data.status)}</td>
51
+ <td><div class="ui scrolling container queue-result">\${$.hidePayload($.toStr(data.result))}</div></td>
52
+ <td>\${$.toStr(data.time)}</td>
53
+ <td>
54
+ <a href="#" class="queue-clicker" data-op="delete" data-queue="\${data.id}" role="button"><i class="trash alternate outline red icon"></i></a>
55
+ </td>
56
+ </tr>\`);
48
57
  }
49
58
  $.queue.toStatus = function(data) {
50
59
  const icon = {
@@ -58,6 +67,28 @@ $.queue.toStatus = function(data) {
58
67
  return icon ? \`<div data-tooltip="\${data}" data-position="right center"><i class="\${icon} icon"></i></div>\` :
59
68
  $.toStr(data);
60
69
  }
70
+ $.queue.handle = function(el) {
71
+ const self = this;
72
+ switch (el.data('op')) {
73
+ case 'delete':
74
+ $.ntdlg.confirm(
75
+ 'queue-delete-confirm-dlg',
76
+ '${_('Confirm')}',
77
+ '${_('Are you sure want to remove queue <span class="ui error text">%QUEUE%</span>?')}'.replace(/%QUEUE%/g, el.data('queue')),
78
+ $.ntdlg.ICON_QUESTION,
79
+ function() {
80
+ $.post('${route('Ui', {name: 'task', op: 'remove'})}', {queue: el.data('queue')})
81
+ .done(function(json) {
82
+ $.tasks.notify(json);
83
+ if (json.success) {
84
+ self.reload();
85
+ }
86
+ });
87
+ }
88
+ );
89
+ break;
90
+ }
91
+ }
61
92
  $.queue.reload = function() {
62
93
  const self = this;
63
94
  if (!self.loading) {
@@ -2,7 +2,7 @@
2
2
  <div class="row"></div>
3
3
  <div class="row">
4
4
  <div class="twelve wide column">
5
- <%- include('info') -%>
5
+ <%- indent(include('info'), 6) -%>
6
6
  </div>
7
7
  </div>
8
8
  <%_ if (api.bridges.length) { -%>
@@ -18,7 +18,7 @@
18
18
  <div class="twelve wide column">
19
19
  <div class="ui fluid card">
20
20
  <div class="content">
21
- <div class="ui right floated tiny label"><%= bridge.year %></div>
21
+ <div class="ui red right floated tiny label"><%= bridge.year %></div>
22
22
  <div class="item header"><i class="ui laptop code icon"></i><%= bridge.name.toUpperCase() %></div>
23
23
  </div>
24
24
  <div class="extra content" data-bridge="<%= bridge.name %>">
@@ -71,12 +71,16 @@
71
71
  </div>
72
72
  <%_ }) -%>
73
73
  <%_ } else { -%>
74
- <div class="ui placeholder segment" style="min-height: 75vh;">
75
- <div class="ui icon header">
76
- <i class="laptop code icon"></i>
77
- <div class="ui big hidden divider"></div>
78
- <%= _('Currently, no connected bridge available.') %><br/>
79
- <%= _('Once a connection established, it will appear here.') %>
74
+ <div class="row">
75
+ <div class="twelve wide column">
76
+ <div class="ui placeholder segment v-min">
77
+ <div class="ui icon header">
78
+ <i class="laptop code icon"></i>
79
+ <div class="ui big hidden divider"></div>
80
+ <%= _('Currently, no configured bridge available.') %><br/>
81
+ <%= _('Once a bridge is configured, it will appear here.') %>
82
+ </div>
83
+ </div>
80
84
  </div>
81
85
  </div>
82
86
  <%_ } -%>
@@ -35,11 +35,11 @@ $.ticker = {
35
35
  'restart-timer-dlg',
36
36
  '${_('Please Wait')}',
37
37
  \`<div class="ui grid container">
38
- <div class="row">
38
+ <div class="row">
39
39
  <span class="ui large red text"><i class="clock outline icon"></i><span class="elapsed">00:00:00</span></span>
40
40
  <span>&nbsp;&nbsp;&nbsp;</span>
41
41
  <span class="ui large text">\${message}</span>
42
- </div>
42
+ </div>
43
43
  </div>\`,
44
44
  {
45
45
  size: 'small',
package/views/ui/util.ejs CHANGED
@@ -38,6 +38,30 @@ $.addLog = function(el, message) {
38
38
  }
39
39
  return false;
40
40
  }
41
+ $.viewLog = function(title, log, seq) {
42
+ $.post('${route('Ui', {name: 'task', op: 'log'})}', {log, seq})
43
+ .done(function(json) {
44
+ if (json.success) {
45
+ const logs = document.createTextNode(json.logs);
46
+ const content = \`<div class="ui fluid scrolling container v-min v-max"><pre></pre></div>\`;
47
+ const dlg = $.ntdlg.create('log-view-dlg', title, content, {
48
+ size: 'fullscreen',
49
+ show() {
50
+ dlg.find('pre').append(logs);
51
+ },
52
+ buttons: {
53
+ okay: {
54
+ type: 'green approve',
55
+ caption: '<i class="check icon"></i>${_('Ok')}',
56
+ }
57
+ }
58
+ });
59
+ $.ntdlg.show(dlg);
60
+ } else {
61
+ $.tasks.notify(json);
62
+ }
63
+ });
64
+ }
41
65
  $.hidePayload = function(message) {
42
66
  if (message) {
43
67
  message = message.replace(