[pve-devel] [RFC widget-toolkit 2/2] fix #4442: panel: Add firewall log view panel
Christian Ebner
c.ebner at proxmox.com
Mon Jan 30 10:07:15 CET 2023
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.ebner at 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
More information about the pve-devel
mailing list