[pve-devel] [PATCH widget-toolkit v2 1/1] add panel/JournalView

Dominik Csapak d.csapak at proxmox.com
Wed May 15 11:15:12 CEST 2019


similar to LogView, but expects the result from the /node/journal api call,
which is an array of strings, with start/endcursor instead of
an array of objects with line numbers

the new api call also does not accept start line numbers and limit,
so it is necessary to handle it differently

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 Makefile             |   1 +
 panel/JournalView.js | 335 +++++++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 336 insertions(+)
 create mode 100644 panel/JournalView.js

diff --git a/Makefile b/Makefile
index d2b905c..049a1fb 100644
--- a/Makefile
+++ b/Makefile
@@ -40,6 +40,7 @@ JSSRC=					\
 	grid/PendingObjectGrid.js	\
 	panel/InputPanel.js		\
 	panel/LogView.js		\
+	panel/JournalView.js		\
 	panel/RRDChart.js		\
 	panel/GaugeWidget.js		\
 	window/Edit.js			\
diff --git a/panel/JournalView.js b/panel/JournalView.js
new file mode 100644
index 0000000..79edaeb
--- /dev/null
+++ b/panel/JournalView.js
@@ -0,0 +1,335 @@
+/*
+ * Display log entries in a panel with scrollbar
+ * The log entries are automatically refreshed via a background task,
+ * with newest entries comming at the bottom
+ */
+Ext.define('Proxmox.panel.JournalView', {
+    extend: 'Ext.panel.Panel',
+    xtype: 'proxmoxJournalView',
+
+    numEntries: 500,
+    lineHeight: 16,
+
+    scrollToEnd: true,
+
+    controller: {
+	xclass: 'Ext.app.ViewController',
+
+	updateParams: function() {
+	    var me = this;
+	    var viewModel = me.getViewModel();
+	    var since = viewModel.get('since');
+	    var until = viewModel.get('until');
+
+	    since.setHours(0, 0, 0, 0);
+	    until.setHours(0, 0, 0, 0);
+	    until.setDate(until.getDate()+1);
+
+	    me.getView().loadTask.delay(200, undefined, undefined, [
+		false,
+		false,
+		Ext.Date.format(since, "U"),
+		Ext.Date.format(until, "U")
+	    ]);
+	},
+
+	scrollPosBottom: function() {
+	    var view = this.getView();
+	    var pos = view.getScrollY();
+	    var maxPos = view.getScrollable().getMaxPosition().y;
+	    return maxPos - pos;
+	},
+
+	scrollPosTop: function() {
+	    var view = this.getView();
+	    return view.getScrollY();
+	},
+
+	updateScroll: function(livemode, num, scrollPos, scrollPosTop) {
+	    var me = this;
+	    var view = me.getView();
+
+	    if (!livemode) {
+		setTimeout(function() { view.scrollTo(0, 0); }, 10);
+	    } else if (view.scrollToEnd && scrollPos <= 0) {
+		setTimeout(function() { view.scrollTo(0, Infinity); }, 10);
+	    } else if (!view.scrollToEnd && scrollPosTop < 20*view.lineHeight) {
+		setTimeout(function() { view.scrollTo(0, num*view.lineHeight + scrollPosTop); }, 10);
+	    }
+	},
+
+	updateView: function(lines, livemode, top) {
+	    var me = this;
+	    var view = me.getView();
+	    var viewmodel = me.getViewModel();
+	    if (viewmodel.get('livemode') !== livemode) {
+		return; // we switched mode, do not update the content
+	    }
+	    var contentEl = me.lookup('content');
+
+	    // save old scrollpositions
+	    var scrollPos = me.scrollPosBottom();
+	    var scrollPosTop = me.scrollPosTop();
+
+	    var newend = lines.shift();
+	    var newstart = lines.pop();
+
+	    var num = lines.length;
+	    var text = lines.map(Ext.htmlEncode).join('<br>');
+
+	    if (!livemode) {
+		view.content = num ? text : 'no content';
+	    } else {
+		// update content
+		if (top && num) {
+		    view.content = view.content ? text + '<br>' + view.content : text;
+		} else if (!top && num) {
+		    view.content = view.content ? view.content + '<br>' + text : text;
+		}
+
+		// update cursors
+		if (!top || !view.startcursor) {
+		    view.startcursor = newstart;
+		}
+
+		if (top || !view.endcursor) {
+		    view.endcursor = newend;
+		}
+	    }
+
+	    contentEl.update(view.content);
+
+	    me.updateScroll(livemode, num, scrollPos, scrollPosTop);
+	},
+
+	doLoad: function(livemode, top, since, until) {
+	    var me = this;
+	    if (me.running) {
+		me.requested = true;
+		return;
+	    }
+	    me.running = true;
+	    var view = me.getView();
+	    var params = {
+		lastentries: view.numEntries || 500,
+	    };
+	    if (livemode) {
+		if (!top && view.startcursor) {
+		    params = {
+			startcursor: view.startcursor
+		    };
+		} else if (view.endcursor) {
+		    params.endcursor = view.endcursor;
+		}
+	    } else {
+		params = {
+		    since: since,
+		    until: until
+		};
+	    }
+	    Proxmox.Utils.API2Request({
+		url: view.url,
+		params: params,
+		waitMsgTarget: (!livemode) ? view : undefined,
+		method: 'GET',
+		success: function(response) {
+		    Proxmox.Utils.setErrorMask(me, false);
+		    var lines = response.result.data;
+		    me.updateView(lines, livemode, top);
+		    me.running = false;
+		    if (me.requested) {
+			me.requested = false;
+			view.loadTask.delay(200);
+		    }
+		},
+		failure: function(response) {
+		    var msg = response.htmlStatus;
+		    Proxmox.Utils.setErrorMask(me, msg);
+		    me.running = false;
+		    if (me.requested) {
+			me.requested = false;
+			view.loadTask.delay(200);
+		    }
+		}
+	    });
+	},
+
+	onScroll: function(x, y) {
+	    var me = this;
+	    var view = me.getView();
+	    var viewmodel = me.getViewModel();
+	    var livemode = viewmodel.get('livemode');
+	    if (!livemode) {
+		return;
+	    }
+
+	    if (me.scrollPosTop() < 20*view.lineHeight) {
+		view.scrollToEnd = false;
+		view.loadTask.delay(200, undefined, undefined, [true, true]);
+	    } else if (me.scrollPosBottom() <= 1) {
+		view.scrollToEnd = true;
+	    }
+	},
+
+	init: function(view) {
+	    var me = this;
+
+	    if (!view.url) {
+		throw "no url specified";
+	    }
+
+	    var viewmodel = me.getViewModel();
+	    var viewModel = this.getViewModel();
+	    var since = new Date();
+	    since.setDate(since.getDate() - 3);
+	    viewModel.set('until', new Date());
+	    viewModel.set('since', since);
+	    me.lookup('content').setStyle('line-height', view.lineHeight + 'px');
+
+	    view.loadTask = new Ext.util.DelayedTask(me.doLoad, me, [true, false]);
+
+	    me.updateParams();
+	    view.task = Ext.TaskManager.start({
+		run: function() {
+		    if (!view.isVisible() || !view.scrollToEnd || !viewmodel.get('livemode')) {
+			return;
+		    }
+
+		    if (me.scrollPosBottom() <= 1) {
+			view.loadTask.delay(200, undefined, undefined, [true, false]);
+		    }
+		},
+		interval: 1000
+	    });
+	},
+
+	onLiveMode: function() {
+	    var me = this;
+	    var view = me.getView();
+	    delete view.startcursor;
+	    delete view.endcursor;
+	    delete view.content;
+	    me.getViewModel().set('livemode', true);
+	    view.scrollToEnd = true;
+	    me.updateView([], true, false);
+	},
+
+	onTimespan: function() {
+	    var me = this;
+	    me.getViewModel().set('livemode', false);
+	    me.updateView([], false);
+	}
+    },
+
+    onDestroy: function() {
+	var me = this;
+	me.loadTask.cancel();
+	Ext.TaskManager.stop(me.task);
+	delete me.content;
+    },
+
+    // for user to initiate a load from outside
+    requestUpdate: function() {
+	var me = this;
+	me.loadTask.delay(200);
+    },
+
+    viewModel: {
+	data: {
+	    livemode: true,
+	    until: null,
+	    since: null
+	}
+    },
+
+    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) {
+		    var 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: 'datefield',
+		fieldLabel: gettext('Since'),
+		name: 'since_date',
+		reference: 'since',
+		format: 'Y-m-d',
+		bind: {
+		    disabled: '{livemode}',
+		    value: '{since}',
+		    maxValue: '{until}'
+		}
+	    },
+	    {
+		xtype: 'datefield',
+		fieldLabel: gettext('Until'),
+		name: 'until_date',
+		reference: 'until',
+		format: 'Y-m-d',
+		bind: {
+		    disabled: '{livemode}',
+		    value: '{until}',
+		    minValue: '{since}'
+		}
+	    },
+	    {
+		xtype: 'button',
+		text: 'Update',
+		reference: 'updateBtn',
+		handler: 'updateParams',
+		bind: {
+		    disabled: '{livemode}'
+		}
+	    }
+	]
+    },
+
+    items: [
+	{
+	    xtype: 'box',
+	    reference: 'content',
+	    style: {
+		font: 'normal 11px tahoma, arial, verdana, sans-serif',
+		'white-space': 'pre'
+	    },
+	}
+    ]
+});
-- 
2.11.0





More information about the pve-devel mailing list