Adds a custom firewall log view panel, based on the existing log view panel.
The firewall log view panel is extended to include `since` and `until` filters
with date and time filtering, in contrast to the date only filtering for the log
view panel.

Signed-off-by: Christian Ebner <c.eb...@proxmox.com>
---
 src/Makefile                 |   1 +
 src/panel/FirewallLogView.js | 350 +++++++++++++++++++++++++++++++++++
 2 files changed, 351 insertions(+)
 create mode 100644 src/panel/FirewallLogView.js

diff --git a/src/Makefile b/src/Makefile
index 95da5aa..16cc8f1 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -55,6 +55,7 @@ JSSRC=                                        \
        panel/InputPanel.js             \
        panel/InfoWidget.js             \
        panel/LogView.js                \
+       panel/FirewallLogView.js        \
        panel/NodeInfoRepoStatus.js     \
        panel/JournalView.js            \
        panel/PermissionView.js         \
diff --git a/src/panel/FirewallLogView.js b/src/panel/FirewallLogView.js
new file mode 100644
index 0000000..6528f7c
--- /dev/null
+++ b/src/panel/FirewallLogView.js
@@ -0,0 +1,350 @@
+/*
+ * Display firewall log entries in a panel with scrollbar
+ * The log entries are automatically refreshed via a background task,
+ * with newest entries coming at the bottom
+ */
+Ext.define('Proxmox.panel.FirewallLogView', {
+    extend: 'Ext.panel.Panel',
+    xtype: 'proxmoxFirewallLogView',
+
+    pageSize: 510,
+    viewBuffer: 50,
+    lineHeight: 16,
+
+    scrollToEnd: true,
+
+    controller: {
+       xclass: 'Ext.app.ViewController',
+
+       updateParams: function() {
+           let me = this;
+           let viewModel = me.getViewModel();
+
+           if (viewModel.get('hide_timespan') || viewModel.get('livemode')) {
+               return;
+           }
+
+           let since = viewModel.get('since');
+           let until = viewModel.get('until');
+
+
+           if (since > until) {
+               Ext.Msg.alert('Error', 'Since date must be less equal than 
Until date.');
+               return;
+           }
+
+           viewModel.set('params.since', Ext.Date.format(since, 'U'));
+           viewModel.set('params.until', Ext.Date.format(until, 'U'));
+           me.getView().loadTask.delay(200);
+       },
+
+       scrollPosBottom: function() {
+           let view = this.getView();
+           let pos = view.getScrollY();
+           let maxPos = view.getScrollable().getMaxPosition().y;
+           return maxPos - pos;
+       },
+
+       updateView: function(lines, first, total) {
+           let me = this;
+           let view = me.getView();
+           let viewModel = me.getViewModel();
+           let content = me.lookup('content');
+           let data = viewModel.get('data');
+
+           if (first === data.first && total === data.total && lines.length 
=== data.lines) {
+               // before there is any real output, we get 'no output' as a 
single line, so always
+               // update if we only have one to be sure to catch the first 
real line of output
+               if (total !== 1) {
+                   return; // same content, skip setting and scrolling
+               }
+           }
+           viewModel.set('data', {
+               first: first,
+               total: total,
+               lines: lines.length,
+           });
+
+           let scrollPos = me.scrollPosBottom();
+           let scrollToBottom = view.scrollToEnd && scrollPos <= 5;
+
+           if (!scrollToBottom) {
+               // so that we have the 'correct' height for the text
+               lines.length = total;
+           }
+
+           content.update(lines.join('<br>'));
+
+           if (scrollToBottom) {
+               let scroller = view.getScrollable();
+               scroller.suspendEvent('scroll');
+               view.scrollTo(0, Infinity);
+               me.updateStart(true);
+               scroller.resumeEvent('scroll');
+           }
+       },
+
+       doLoad: function() {
+           let me = this;
+           if (me.running) {
+               me.requested = true;
+               return;
+           }
+           me.running = true;
+           let view = me.getView();
+           Proxmox.Utils.API2Request({
+               url: me.getView().url,
+               params: me.getViewModel().get('params'),
+               method: 'GET',
+               success: function(response) {
+                   if (me.isDestroyed) {
+                       return;
+                   }
+                   Proxmox.Utils.setErrorMask(me, false);
+                   let total = response.result.total;
+                   let lines = [];
+                   let first = Infinity;
+
+                   Ext.Array.each(response.result.data, function(line) {
+                       if (first > line.n) {
+                           first = line.n;
+                       }
+                       lines[line.n - 1] = Ext.htmlEncode(line.t);
+                   });
+
+                   me.updateView(lines, first - 1, total);
+                   me.running = false;
+                   if (me.requested) {
+                       me.requested = false;
+                       view.loadTask.delay(200);
+                   }
+               },
+               failure: function(response) {
+                   if (view.failCallback) {
+                       view.failCallback(response);
+                   } else {
+                       let msg = response.htmlStatus;
+                       Proxmox.Utils.setErrorMask(me, msg);
+                   }
+                   me.running = false;
+                   if (me.requested) {
+                       me.requested = false;
+                       view.loadTask.delay(200);
+                   }
+               },
+           });
+       },
+
+       updateStart: function(scrolledToBottom, targetLine) {
+           let me = this;
+           let view = me.getView(), viewModel = me.getViewModel();
+
+           let limit = viewModel.get('params.limit');
+           let total = viewModel.get('data.total');
+
+           // heuristic: scroll up? -> load more in front; scroll down? -> 
load more at end
+           let startRatio = view.lastTargetLine && view.lastTargetLine > 
targetLine ? 2/3 : 1/3;
+           view.lastTargetLine = targetLine;
+
+           let newStart = scrolledToBottom
+               ? Math.trunc(total - limit, 10)
+               : Math.trunc(targetLine - (startRatio * limit) + 10);
+
+           viewModel.set('params.start', Math.max(newStart, 0));
+
+           view.loadTask.delay(200);
+       },
+
+       onScroll: function(x, y) {
+           let me = this;
+           let view = me.getView(), viewModel = me.getViewModel();
+
+           let line = view.getScrollY() / view.lineHeight;
+           let viewLines = view.getHeight() / view.lineHeight;
+
+           let viewStart = Math.max(Math.trunc(line - 1 - view.viewBuffer), 0);
+           let viewEnd = Math.trunc(line + viewLines + 1 + view.viewBuffer);
+
+           let { start, limit } = viewModel.get('params');
+
+           let margin = start < 20 ? 0 : 20;
+
+           if (viewStart < start + margin || viewEnd > start + limit - margin) 
{
+               me.updateStart(false, line);
+           }
+       },
+
+       onLiveMode: function() {
+           let me = this;
+           let viewModel = me.getViewModel();
+           viewModel.set('livemode', true);
+           viewModel.set('params', { start: 0, limit: 510 });
+
+           let view = me.getView();
+           delete view.content;
+           view.scrollToEnd = true;
+           me.updateView([], true, false);
+       },
+
+       onTimespan: function() {
+           let me = this;
+           me.getViewModel().set('livemode', false);
+           me.updateView([], false);
+           // Directly apply currently selected values without update
+           // button click.
+           me.updateParams();
+       },
+
+       init: function(view) {
+           let me = this;
+
+           if (!view.url) {
+               throw "no url specified";
+           }
+
+           let viewModel = me.getViewModel();
+           viewModel.set('until', new Date());
+           viewModel.set('since', new Date());
+           viewModel.set('params.limit', view.pageSize);
+           viewModel.set('hide_timespan', !view.log_select_timespan);
+           me.lookup('content').setStyle('line-height', 
`${view.lineHeight}px`);
+
+           view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
+
+           me.updateParams();
+           view.task = Ext.TaskManager.start({
+               run: () => {
+                   if (!view.isVisible() || !view.scrollToEnd) {
+                       return;
+                   }
+                   if (me.scrollPosBottom() <= 5) {
+                       view.loadTask.delay(200);
+                   }
+               },
+               interval: 1000,
+           });
+       },
+    },
+
+    onDestroy: function() {
+       let me = this;
+       me.loadTask.cancel();
+       Ext.TaskManager.stop(me.task);
+    },
+
+    // for user to initiate a load from outside
+    requestUpdate: function() {
+       let me = this;
+       me.loadTask.delay(200);
+    },
+
+    viewModel: {
+       data: {
+           since: null,
+           until: null,
+           livemode: true,
+           hide_timespan: false,
+           data: {
+               start: 0,
+               total: 0,
+               textlen: 0,
+           },
+           params: {
+               start: 0,
+               limit: 510,
+           },
+       },
+    },
+
+    layout: 'auto',
+    bodyPadding: 5,
+    scrollable: {
+       x: 'auto',
+       y: 'auto',
+       listeners: {
+           // we have to have this here, since we cannot listen to events of 
the scroller in
+           // the viewcontroller (extjs bug?), nor does the panel have a 
'scroll' event'
+           scroll: {
+               fn: function(scroller, x, y) {
+                   let controller = this.component.getController();
+                   if (controller) { // on destroy, controller can be gone
+                       controller.onScroll(x, y);
+                   }
+               },
+               buffer: 200,
+           },
+       },
+    },
+
+    tbar: {
+       items: [
+           '->',
+           {
+               xtype: 'segmentedbutton',
+               items: [
+                   {
+                       text: gettext('Live Mode'),
+                       bind: {
+                           pressed: '{livemode}',
+                       },
+                       handler: 'onLiveMode',
+                   },
+                   {
+                       text: gettext('Select Timespan'),
+                       bind: {
+                           pressed: '{!livemode}',
+                       },
+                       handler: 'onTimespan',
+                   },
+               ],
+           },
+           {
+               xtype: 'box',
+               autoEl: { cn: gettext('Since') + ':' },
+           },
+           {
+               xtype: 'promxoxDateTimeField',
+               name: 'since_date_time',
+               reference: 'since',
+               bind: {
+                   disabled: '{livemode}',
+                   value: '{since}',
+                   maxValue: '{until}',
+               },
+           },
+           {
+               xtype: 'box',
+               autoEl: { cn: gettext('Until') + ':' },
+           },
+           {
+               xtype: 'promxoxDateTimeField',
+               name: 'until_date_time',
+               reference: 'until',
+               bind: {
+                   disabled: '{livemode}',
+                   value: '{until}',
+                   minValue: '{since}',
+               },
+           },
+           {
+               xtype: 'button',
+               text: 'Update',
+               bind: {
+                   disabled: '{livemode}',
+               },
+               handler: 'updateParams',
+           },
+       ],
+    },
+
+    items: [
+       {
+           xtype: 'box',
+           reference: 'content',
+           style: {
+               font: 'normal 11px tahoma, arial, verdana, sans-serif',
+               'white-space': 'pre',
+           },
+       },
+    ],
+});
-- 
2.30.2



_______________________________________________
pve-devel mailing list
pve-devel@lists.proxmox.com
https://lists.proxmox.com/cgi-bin/mailman/listinfo/pve-devel

Reply via email to