[pve-devel] [PATCH widget-toolkit 1/1] rework panel/LogView

Dominik Csapak d.csapak at proxmox.com
Tue Apr 2 12:16:49 CEST 2019


use the view, viewmodel, controller style code, which makes the logic
much more understandable

this adds two features to it, namely a viewBuffer range, which controls
the distance to the end of the data on which the panel loads the next data
and an optional failCallback, which will be used for the cephlog

this also fixes #2151

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 panel/LogView.js     | 446 +++++++++++++++++++++++++--------------------------
 window/TaskViewer.js |   2 +-
 2 files changed, 217 insertions(+), 231 deletions(-)

diff --git a/panel/LogView.js b/panel/LogView.js
index 05a90a3..fe8d274 100644
--- a/panel/LogView.js
+++ b/panel/LogView.js
@@ -5,270 +5,256 @@
  */
 Ext.define('Proxmox.panel.LogView', {
     extend: 'Ext.panel.Panel',
-
-    alias: ['widget.proxmoxLogView'],
+    xtype: 'proxmoxLogView',
 
     pageSize: 500,
-
+    viewBuffer: 50,
     lineHeight: 16,
 
-    viewInfo: undefined,
-
     scrollToEnd: true,
 
-    autoScroll: true,
-
-    layout: 'auto',
-
-    bodyPadding: 5,
-
-    getMaxDown: function(scrollToEnd) {
-        var me = this;
-
-	var target = me.getTargetEl();
-	var dom = target.dom;
-	if (scrollToEnd) {
-	    dom.scrollTop = dom.scrollHeight - dom.clientHeight;
-	}
-
-	var maxDown = dom.scrollHeight - dom.clientHeight -
-	    dom.scrollTop;
-
-	return maxDown;
-    },
-
-    updateView: function(start, end, total, text) {
-        var me = this;
-
-	if (me.destroyed) { // return if element is not there anymore
-	    return;
-	}
-
-	var el = me.dataCmp.el;
-
-	if (me.viewInfo && me.viewInfo.start === start &&
-	    me.viewInfo.end === end && me.viewInfo.total === total &&
-	    me.viewInfo.textLength === text.length) {
-	    return; // same content
-	}
+    // callback for load failure, used for ceph
+    failCallback: undefined,
 
-	var maxDown = me.getMaxDown();
-	var scrollToEnd = (maxDown <= 0) && me.scrollToEnd;
+    controller: {
+	xclass: 'Ext.app.ViewController',
 
-	el.setStyle('padding-top', (start*me.lineHeight).toString() + 'px');
-	el.update(text);
-	me.dataCmp.setHeight(total*me.lineHeight);
-
-	if (scrollToEnd) {
-	    me.getMaxDown(true);
-	}
+	updateParams: function() {
+	    var me = this;
+	    var viewModel = me.getViewModel();
+	    var since = viewModel.get('since');
+	    var until = viewModel.get('until');
+	    if (viewModel.get('hide_timespan')) {
+		return;
+	    }
 
-	me.viewInfo = {
-	    start: start,
-	    end: end,
-	    total: total,
-	    textLength:  text.length
-	};
-    },
+	    if (since > until) {
+		Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
+		return;
+	    }
 
-    doAttemptLoad: function(start) {
-        var me = this;
+	    viewModel.set('params.since', Ext.Date.format(since, 'Y-m-d'));
+	    viewModel.set('params.until', Ext.Date.format(until, 'Y-m-d') + ' 23:59:59');
+	    me.getView().loadTask.delay(200);
+	},
+
+	scrollPosBottom: function() {
+	    var view = this.getView();
+	    var pos = view.getScrollY();
+	    var maxPos = view.getScrollable().getMaxPosition().y;
+	    return maxPos - pos;
+	},
+
+	updateView: function(text, first, total) {
+	    var me = this;
+	    var view = me.getView();
+	    var viewModel = me.getViewModel();
+	    var content = me.lookup('content');
+	    var data = viewModel.get('data');
+
+	    if (first === data.first && total === data.total && text.length === data.textlen) {
+		return; // same content, skip setting and scrolling
+	    }
+	    viewModel.set('data', {
+		first: first,
+		total: total,
+		textlen: text.length
+	    });
 
-	var req_params = {
-	    start: start,
-	    limit: me.pageSize
-	};
+	    var scrollPos = me.scrollPosBottom();
 
-	if (me.log_select_timespan) {
-	    // always show log until the end of the selected day
-	    req_params.until = Ext.Date.format(me.until_date, 'Y-m-d') + ' 23:59:59';
-	    req_params.since = Ext.Date.format(me.since_date, 'Y-m-d');
-	}
+	    content.update(text);
 
-	Proxmox.Utils.API2Request({
-	    url: me.url,
-	    params: req_params,
-	    method: 'GET',
-	    success: function(response) {
-		Proxmox.Utils.setErrorMask(me, false);
-		var list = response.result.data;
-		var total = response.result.total;
-		var first = 0, last = 0;
-		var text = '';
-		Ext.Array.each(list, function(item) {
-		    if (!first|| item.n < first) {
-			first = item.n;
-		    }
-		    if (!last || item.n > last) {
-			last = item.n;
+	    if (view.scrollToEnd && scrollPos <= 0) {
+		// we use setTimeout to work around scroll handling on touchscreens
+		setTimeout(function() { view.scrollTo(0, Infinity); }, 10);
+	    }
+	},
+
+	doLoad: function() {
+	    var me = this;
+	    var view = me.getView();
+	    var viewModel = me.getViewModel();
+	    Proxmox.Utils.API2Request({
+		url: me.getView().url,
+		params: viewModel.get('params'),
+		method: 'GET',
+		success: function(response) {
+		    Proxmox.Utils.setErrorMask(me, false);
+		    var total = response.result.total;
+		    var lines = new Array();
+		    var 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);
+		    });
+
+		    lines.length = total;
+		    me.updateView(lines.join('<br>'), first - 1, total);
+		},
+		failure: function(response) {
+		    if (view.failCallback) {
+			view.failCallback(response);
+		    } else {
+			var msg = response.htmlStatus;
+			Proxmox.Utils.setErrorMask(me, msg);
 		    }
-		    text = text + Ext.htmlEncode(item.t) + "<br>";
-		});
-
-		if (first && last && total) {
-		    me.updateView(first -1 , last -1, total, text);
-		} else {
-		    me.updateView(0, 0, 0, '');
 		}
-	    },
-	    failure: function(response) {
-		var msg = response.htmlStatus;
-		Proxmox.Utils.setErrorMask(me, msg);
+	    });
+	},
+
+	onScroll: function(x, y) {
+	    var me = this;
+	    var view = me.getView();
+	    var viewModel = me.getViewModel();
+
+	    var lineHeight = view.lineHeight;
+	    var line = view.getScrollY()/lineHeight;
+	    var start = viewModel.get('params.start');
+	    var limit = viewModel.get('params.limit');
+	    var viewLines = view.getHeight()/lineHeight;
+
+	    var viewStart = Math.max(parseInt(line - 1 - view.viewBuffer, 10), 0);
+	    var viewEnd = parseInt(line + viewLines + 1 + view.viewBuffer, 10);
+
+	    if (viewStart < start || viewEnd > (start+limit)) {
+		viewModel.set('params.start',
+		    Math.max(parseInt(line - limit/2 + 10, 10), 0));
+		view.loadTask.delay(200);
 	    }
-	});
-    },
+	},
 
-    attemptLoad: function(start) {
-        var me = this;
-        if (!me.loadTask) {
-            me.loadTask = Ext.create('Ext.util.DelayedTask', me.doAttemptLoad, me, []);
-        }
-        me.loadTask.delay(200, me.doAttemptLoad, me, [start]);
-    },
+	init: function(view) {
+	    var me = this;
 
-    requestUpdate: function(top, force) {
-	var me = this;
-
-	if (top === undefined) {
-	    var target = me.getTargetEl();
-	    top = target.dom.scrollTop;
-	}
-
-	var viewStart = parseInt((top / me.lineHeight) - 1, 10);
-	if (viewStart < 0) {
-	    viewStart = 0;
-	}
-	var viewEnd = parseInt(((top + me.getHeight())/ me.lineHeight) + 1, 10);
-	var info = me.viewInfo;
-
-	if (info && !force) {
-	    if (viewStart >= info.start && viewEnd <= info.end) {
-		return;
+	    if (!view.url) {
+		throw "no url specified";
 	    }
-	}
 
-	var line = parseInt((top / me.lineHeight) - (me.pageSize / 2) + 10, 10);
-	if (line < 0) {
-	    line = 0;
-	}
+	    var viewModel = this.getViewModel();
+	    var since = new Date();
+	    since.setDate(since.getDate() - 3);
+	    viewModel.set('until', new Date());
+	    viewModel.set('since', since);
+	    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: function() {
+		    if (!view.isVisible() || !view.scrollToEnd) {
+			return;
+		    }
 
-	me.attemptLoad(line);
+		    if (me.scrollPosBottom() <= 1) {
+			view.loadTask.delay(200);
+		    }
+		},
+		interval: 1000
+	    });
+	}
     },
 
-    afterRender: function() {
+    onDestroy: function() {
 	var me = this;
-
-        me.callParent(arguments);
-
-	Ext.Function.defer(function() {
-	    var target = me.getTargetEl();
-	    target.on('scroll',  function(e) {
-		me.requestUpdate();
-	    });
-	    me.requestUpdate(0);
-	}, 20);
+	me.loadTask.cancel();
+	Ext.TaskManager.stop(me.task);
     },
 
-    initComponent : function() {
-	/*jslint confusion: true */
-
+    // for user to initiate a load from outside
+    requestUpdate: function() {
 	var me = this;
+	me.loadTask.delay(200);
+    },
 
-	if (!me.url) {
-	    throw "no url specified";
+    viewModel: {
+	data: {
+	    until: null,
+	    since: null,
+	    hide_timespan: false,
+	    data: {
+		start: 0,
+		total: 0,
+		textlen: 0
+	    },
+	    params: {
+		start: 0,
+		limit: 500,
+	    }
 	}
+    },
 
-       // show logs from today back to 3 days ago per default
-       me.until_date = new Date();
-       me.since_date = new Date();
-       me.since_date.setDate(me.until_date.getDate() - 3);
-
-	me.dataCmp = Ext.create('Ext.Component', {
-	    style: 'font:normal 11px tahoma, arial, verdana, sans-serif;' +
-		'line-height: ' + me.lineHeight.toString() + 'px; white-space: pre;'
-	});
-
-	me.task = Ext.TaskManager.start({
-	    run: function() {
-		if (!me.isVisible() || !me.scrollToEnd || !me.viewInfo) {
-		    return;
-		}
+    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
+	    },
+	}
+    },
 
-		var maxDown = me.getMaxDown();
-		if (maxDown > 0) {
-		    return;
+    tbar: {
+	bind: {
+	    hidden: '{hide_timespan}'
+	},
+	items: [
+	    '->',
+	    'Since: ',
+	    {
+		xtype: 'datefield',
+		name: 'since_date',
+		reference: 'since',
+		format: 'Y-m-d',
+		bind: {
+		    value: '{since}',
+		    maxValue: '{until}'
 		}
-
-		me.requestUpdate(undefined, true);
 	    },
-	    interval: 1000
-	});
-
-	Ext.apply(me, {
-	    items: me.dataCmp,
-	    listeners: {
-		destroy: function() {
-		    Ext.TaskManager.stop(me.task);
+	    'Until: ',
+	    {
+		xtype: 'datefield',
+		name: 'until_date',
+		reference: 'until',
+		format: 'Y-m-d',
+		bind: {
+		    value: '{until}',
+		    minValue: '{since}'
 		}
+	    },
+	    {
+		xtype: 'button',
+		text: 'Update',
+		handler: 'updateParams'
 	    }
-	});
-
-	if (me.log_select_timespan) {
-	    me.tbar = ['->','Since: ',
-		       {
-			   xtype: 'datefield',
-			   maxValue: me.until_date,
-			   value: me.since_date,
-			   name: 'since_date',
-			   format: 'Y-m-d',
-			   listeners: {
-			       select: function(field, date) {
-				   me.since_date_selected = date;
-				   var until_field = field.up().down('field[name=until_date]');
-				   if (date > until_field.getValue()) {
-				       until_field.setValue(date);
-				   }
-			       }
-			   }
-		       },
-		       'Until: ',
-		       {
-			   xtype: 'datefield',
-			   maxValue: me.until_date,
-			   value: me.until_date,
-			   name: 'until_date',
-			   format: 'Y-m-d',
-			   listeners: {
-			       select: function(field, date) {
-				   var since_field = field.up().down('field[name=since_date]');
-				   if (date < since_field.getValue()) {
-				       since_field.setValue(date);
-				   }
-			       }
-			   }
-		       },
-		       {
-			   xtype: 'button',
-			   text: 'Update',
-			   handler: function() {
-			       var until_field = me.down('field[name=until_date]');
-			       var since_field = me.down('field[name=since_date]');
-			       if (until_field.getValue() < since_field.getValue()) {
-				   Ext.Msg.alert('Error',
-						 'Since date must be less equal than Until date.');
-				   until_field.setValue(me.until_date);
-				   since_field.setValue(me.since_date);
-			       } else {
-				   me.until_date = until_field.getValue();
-				   me.since_date = since_field.getValue();
-				   me.requestUpdate();
-			       }
-			   }
-		       }
-		      ];
-	}
-
+	],
+    },
 
-	me.callParent();
-    }
+    items: [
+	{
+	    xtype: 'box',
+	    reference: 'content',
+	    style: {
+		font: 'normal 11px tahoma, arial, verdana, sans-serif',
+		'white-space': 'pre'
+	    },
+	}
+    ]
 });
diff --git a/window/TaskViewer.js b/window/TaskViewer.js
index 39f42ad..b508882 100644
--- a/window/TaskViewer.js
+++ b/window/TaskViewer.js
@@ -198,8 +198,8 @@ Ext.define('Proxmox.window.TaskViewer', {
 	    var status = statgrid.getObjectValue('status');
 	    
 	    if (status === 'stopped') {
-		logView.requestUpdate(undefined, true);
 		logView.scrollToEnd = false;
+		logView.requestUpdate();
 		statstore.stopUpdate();
 		me.taskDone(statgrid.getObjectValue('exitstatus') == 'OK');
 	    }
-- 
2.11.0





More information about the pve-devel mailing list