]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/LogView.js
log viewer: add heuristic for scroll-direction dependent ratio distribution
[proxmox-widget-toolkit.git] / src / panel / LogView.js
1 /*
2 * Display log entries in a panel with scrollbar
3 * The log entries are automatically refreshed via a background task,
4 * with newest entries comming at the bottom
5 */
6 Ext.define('Proxmox.panel.LogView', {
7 extend: 'Ext.panel.Panel',
8 xtype: 'proxmoxLogView',
9
10 pageSize: 510,
11 viewBuffer: 50,
12 lineHeight: 16,
13
14 scrollToEnd: true,
15
16 // callback for load failure, used for ceph
17 failCallback: undefined,
18
19 controller: {
20 xclass: 'Ext.app.ViewController',
21
22 updateParams: function() {
23 let me = this;
24 let viewModel = me.getViewModel();
25 let since = viewModel.get('since');
26 let until = viewModel.get('until');
27 if (viewModel.get('hide_timespan')) {
28 return;
29 }
30
31 if (since > until) {
32 Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
33 return;
34 }
35
36 viewModel.set('params.since', Ext.Date.format(since, 'Y-m-d'));
37 viewModel.set('params.until', Ext.Date.format(until, 'Y-m-d') + ' 23:59:59');
38 me.getView().loadTask.delay(200);
39 },
40
41 scrollPosBottom: function() {
42 let view = this.getView();
43 let pos = view.getScrollY();
44 let maxPos = view.getScrollable().getMaxPosition().y;
45 return maxPos - pos;
46 },
47
48 updateView: function(lines, first, total) {
49 let me = this;
50 let view = me.getView();
51 let viewModel = me.getViewModel();
52 let content = me.lookup('content');
53 let data = viewModel.get('data');
54
55 if (first === data.first && total === data.total && lines.length === data.lines) {
56 return; // same content, skip setting and scrolling
57 }
58 viewModel.set('data', {
59 first: first,
60 total: total,
61 lines: lines.length,
62 });
63
64 let scrollPos = me.scrollPosBottom();
65 let scrollToBottom = view.scrollToEnd && scrollPos <= 5;
66
67 if (!scrollToBottom) {
68 // so that we have the 'correct' height for the text
69 lines.length = total;
70 }
71
72 content.update(lines.join('<br>'));
73
74 if (scrollToBottom) {
75 let scroller = view.getScrollable();
76 scroller.suspendEvent('scroll');
77 view.scrollTo(0, Infinity);
78 me.updateStart(true);
79 scroller.resumeEvent('scroll');
80 }
81 },
82
83 doLoad: function() {
84 let me = this;
85 if (me.running) {
86 me.requested = true;
87 return;
88 }
89 me.running = true;
90 let view = me.getView();
91 let viewModel = me.getViewModel();
92 Proxmox.Utils.API2Request({
93 url: me.getView().url,
94 params: viewModel.get('params'),
95 method: 'GET',
96 success: function(response) {
97 Proxmox.Utils.setErrorMask(me, false);
98 let total = response.result.total;
99 let lines = [];
100 let first = Infinity;
101
102 Ext.Array.each(response.result.data, function(line) {
103 if (first > line.n) {
104 first = line.n;
105 }
106 lines[line.n - 1] = Ext.htmlEncode(line.t);
107 });
108
109 me.updateView(lines, first - 1, total);
110 me.running = false;
111 if (me.requested) {
112 me.requested = false;
113 view.loadTask.delay(200);
114 }
115 },
116 failure: function(response) {
117 if (view.failCallback) {
118 view.failCallback(response);
119 } else {
120 let msg = response.htmlStatus;
121 Proxmox.Utils.setErrorMask(me, msg);
122 }
123 me.running = false;
124 if (me.requested) {
125 me.requested = false;
126 view.loadTask.delay(200);
127 }
128 },
129 });
130 },
131
132 updateStart: function(scrolledToBottom, targetLine) {
133 let me = this;
134 let view = me.getView(), viewModel = me.getViewModel();
135
136 let limit = viewModel.get('params.limit');
137 let total = viewModel.get('data.total');
138
139 // heuristic: scroll up? -> load more in front; scroll down? -> load more at end
140 let startRatio = view.lastTargetLine && view.lastTargetLine > targetLine ? 2/3 : 1/3;
141 view.lastTargetLine = targetLine;
142
143 let newStart = scrolledToBottom
144 ? Math.trunc(total - limit, 10)
145 : Math.trunc(targetLine - (startRatio * limit) + 10);
146
147 viewModel.set('params.start', Math.max(newStart, 0));
148
149 view.loadTask.delay(200);
150 },
151
152 onScroll: function(x, y) {
153 let me = this;
154 let view = me.getView(), viewModel = me.getViewModel();
155
156 let line = view.getScrollY() / view.lineHeight;
157 let viewLines = view.getHeight() / view.lineHeight;
158
159 let viewStart = Math.max(Math.trunc(line - 1 - view.viewBuffer), 0);
160 let viewEnd = Math.trunc(line + viewLines + 1 + view.viewBuffer);
161
162 let { start, limit } = viewModel.get('params');
163
164 if (viewStart < start || viewEnd > start + limit) {
165 me.updateStart(false, line);
166 }
167 },
168
169 init: function(view) {
170 let me = this;
171
172 if (!view.url) {
173 throw "no url specified";
174 }
175
176 let viewModel = this.getViewModel();
177 let since = new Date();
178 since.setDate(since.getDate() - 3);
179 viewModel.set('until', new Date());
180 viewModel.set('since', since);
181 viewModel.set('params.limit', view.pageSize);
182 viewModel.set('hide_timespan', !view.log_select_timespan);
183 me.lookup('content').setStyle('line-height', `${view.lineHeight}px`);
184
185 view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
186
187 me.updateParams();
188 view.task = Ext.TaskManager.start({
189 run: () => {
190 if (!view.isVisible() || !view.scrollToEnd) {
191 return;
192 }
193 if (me.scrollPosBottom() <= 5) {
194 view.loadTask.delay(200);
195 }
196 },
197 interval: 1000,
198 });
199 },
200 },
201
202 onDestroy: function() {
203 let me = this;
204 me.loadTask.cancel();
205 Ext.TaskManager.stop(me.task);
206 },
207
208 // for user to initiate a load from outside
209 requestUpdate: function() {
210 let me = this;
211 me.loadTask.delay(200);
212 },
213
214 viewModel: {
215 data: {
216 until: null,
217 since: null,
218 hide_timespan: false,
219 data: {
220 start: 0,
221 total: 0,
222 textlen: 0,
223 },
224 params: {
225 start: 0,
226 limit: 510,
227 },
228 },
229 },
230
231 layout: 'auto',
232 bodyPadding: 5,
233 scrollable: {
234 x: 'auto',
235 y: 'auto',
236 listeners: {
237 // we have to have this here, since we cannot listen to events of the scroller in
238 // the viewcontroller (extjs bug?), nor does the panel have a 'scroll' event'
239 scroll: {
240 fn: function(scroller, x, y) {
241 let controller = this.component.getController();
242 if (controller) { // on destroy, controller can be gone
243 controller.onScroll(x, y);
244 }
245 },
246 buffer: 200,
247 },
248 },
249 },
250
251 tbar: {
252 bind: {
253 hidden: '{hide_timespan}',
254 },
255 items: [
256 '->',
257 'Since: ',
258 {
259 xtype: 'datefield',
260 name: 'since_date',
261 reference: 'since',
262 format: 'Y-m-d',
263 bind: {
264 value: '{since}',
265 maxValue: '{until}',
266 },
267 },
268 'Until: ',
269 {
270 xtype: 'datefield',
271 name: 'until_date',
272 reference: 'until',
273 format: 'Y-m-d',
274 bind: {
275 value: '{until}',
276 minValue: '{since}',
277 },
278 },
279 {
280 xtype: 'button',
281 text: 'Update',
282 handler: 'updateParams',
283 },
284 ],
285 },
286
287 items: [
288 {
289 xtype: 'box',
290 reference: 'content',
291 style: {
292 font: 'normal 11px tahoma, arial, verdana, sans-serif',
293 'white-space': 'pre',
294 },
295 },
296 ],
297 });