]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/JournalView.js
panel/JournalView: fix flickering in journal livemode
[proxmox-widget-toolkit.git] / src / panel / JournalView.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.JournalView', {
7 extend: 'Ext.panel.Panel',
8 xtype: 'proxmoxJournalView',
9
10 numEntries: 500,
11 lineHeight: 16,
12
13 scrollToEnd: true,
14
15 controller: {
16 xclass: 'Ext.app.ViewController',
17
18 updateParams: function() {
19 let me = this;
20 let viewModel = me.getViewModel();
21 let since = viewModel.get('since');
22 let until = viewModel.get('until');
23
24 since.setHours(0, 0, 0, 0);
25 until.setHours(0, 0, 0, 0);
26 until.setDate(until.getDate()+1);
27
28 me.getView().loadTask.delay(200, undefined, undefined, [
29 false,
30 false,
31 Ext.Date.format(since, "U"),
32 Ext.Date.format(until, "U"),
33 ]);
34 },
35
36 scrollPosBottom: function() {
37 let view = this.getView();
38 let pos = view.getScrollY();
39 let maxPos = view.getScrollable().getMaxPosition().y;
40 return maxPos - pos;
41 },
42
43 scrollPosTop: function() {
44 let view = this.getView();
45 return view.getScrollY();
46 },
47
48 updateScroll: function(livemode, num, scrollPos, scrollPosTop) {
49 let me = this;
50 let view = me.getView();
51
52 if (!livemode) {
53 setTimeout(function() { view.scrollTo(0, 0); }, 10);
54 } else if (view.scrollToEnd && scrollPos <= 5) {
55 setTimeout(function() { view.scrollTo(0, Infinity); }, 10);
56 } else if (!view.scrollToEnd && scrollPosTop < 20 * view.lineHeight) {
57 setTimeout(function() { view.scrollTo(0, (num * view.lineHeight) + scrollPosTop); }, 10);
58 }
59 },
60
61 updateView: function(lines, livemode, top) {
62 let me = this;
63 let view = me.getView();
64 let viewmodel = me.getViewModel();
65 if (!viewmodel || viewmodel.get('livemode') !== livemode) {
66 return; // we switched mode, do not update the content
67 }
68 let contentEl = me.lookup('content');
69
70 // save old scrollpositions
71 let scrollPos = me.scrollPosBottom();
72 let scrollPosTop = me.scrollPosTop();
73
74 let newend = lines.shift();
75 let newstart = lines.pop();
76
77 let num = lines.length;
78 let text = lines.map(Ext.htmlEncode).join('<br>');
79
80 let contentChanged = true;
81
82 if (!livemode) {
83 if (num) {
84 view.content = text;
85 } else {
86 view.content = 'nothing logged or no timespan selected';
87 }
88 } else {
89 // update content
90 if (top && num) {
91 view.content = view.content ? text + '<br>' + view.content : text;
92 } else if (!top && num) {
93 view.content = view.content ? view.content + '<br>' + text : text;
94 } else {
95 contentChanged = false;
96 }
97
98 // update cursors
99 if (!top || !view.startcursor) {
100 view.startcursor = newstart;
101 }
102
103 if (top || !view.endcursor) {
104 view.endcursor = newend;
105 }
106 }
107
108 if (contentChanged) {
109 contentEl.update(view.content);
110 }
111
112 me.updateScroll(livemode, num, scrollPos, scrollPosTop);
113 },
114
115 doLoad: function(livemode, top, since, until) {
116 let me = this;
117 if (me.running) {
118 me.requested = true;
119 return;
120 }
121 me.running = true;
122 let view = me.getView();
123 let params = {
124 lastentries: view.numEntries || 500,
125 };
126 if (livemode) {
127 if (!top && view.startcursor) {
128 params = {
129 startcursor: view.startcursor,
130 };
131 } else if (view.endcursor) {
132 params.endcursor = view.endcursor;
133 }
134 } else {
135 params = {
136 since: since,
137 until: until,
138 };
139 }
140 Proxmox.Utils.API2Request({
141 url: view.url,
142 params: params,
143 waitMsgTarget: !livemode ? view : undefined,
144 method: 'GET',
145 success: function(response) {
146 Proxmox.Utils.setErrorMask(me, false);
147 let lines = response.result.data;
148 me.updateView(lines, livemode, top);
149 me.running = false;
150 if (me.requested) {
151 me.requested = false;
152 view.loadTask.delay(200);
153 }
154 },
155 failure: function(response) {
156 let msg = response.htmlStatus;
157 Proxmox.Utils.setErrorMask(me, msg);
158 me.running = false;
159 if (me.requested) {
160 me.requested = false;
161 view.loadTask.delay(200);
162 }
163 },
164 });
165 },
166
167 onScroll: function(x, y) {
168 let me = this;
169 let view = me.getView();
170 let viewmodel = me.getViewModel();
171 let livemode = viewmodel.get('livemode');
172 if (!livemode) {
173 return;
174 }
175
176 if (me.scrollPosTop() < 20*view.lineHeight) {
177 view.scrollToEnd = false;
178 view.loadTask.delay(200, undefined, undefined, [true, true]);
179 } else if (me.scrollPosBottom() <= 5) {
180 view.scrollToEnd = true;
181 }
182 },
183
184 init: function(view) {
185 let me = this;
186
187 if (!view.url) {
188 throw "no url specified";
189 }
190
191 let viewmodel = me.getViewModel();
192 let viewModel = this.getViewModel();
193 let since = new Date();
194 since.setDate(since.getDate() - 3);
195 viewModel.set('until', new Date());
196 viewModel.set('since', since);
197 me.lookup('content').setStyle('line-height', view.lineHeight + 'px');
198
199 view.loadTask = new Ext.util.DelayedTask(me.doLoad, me, [true, false]);
200
201 me.updateParams();
202 view.task = Ext.TaskManager.start({
203 run: function() {
204 if (!view.isVisible() || !view.scrollToEnd || !viewmodel.get('livemode')) {
205 return;
206 }
207
208 if (me.scrollPosBottom() <= 5) {
209 view.loadTask.delay(200, undefined, undefined, [true, false]);
210 }
211 },
212 interval: 1000,
213 });
214 },
215
216 onLiveMode: function() {
217 let me = this;
218 let view = me.getView();
219 delete view.startcursor;
220 delete view.endcursor;
221 delete view.content;
222 me.getViewModel().set('livemode', true);
223 view.scrollToEnd = true;
224 me.updateView([], true, false);
225 },
226
227 onTimespan: function() {
228 let me = this;
229 me.getViewModel().set('livemode', false);
230 me.updateView([], false);
231 },
232 },
233
234 onDestroy: function() {
235 let me = this;
236 me.loadTask.cancel();
237 Ext.TaskManager.stop(me.task);
238 delete me.content;
239 },
240
241 // for user to initiate a load from outside
242 requestUpdate: function() {
243 let me = this;
244 me.loadTask.delay(200);
245 },
246
247 viewModel: {
248 data: {
249 livemode: true,
250 until: null,
251 since: null,
252 },
253 },
254
255 layout: 'auto',
256 bodyPadding: 5,
257 scrollable: {
258 x: 'auto',
259 y: 'auto',
260 listeners: {
261 // we have to have this here, since we cannot listen to events
262 // of the scroller in the viewcontroller (extjs bug?), nor does
263 // the panel have a 'scroll' event'
264 scroll: {
265 fn: function(scroller, x, y) {
266 let controller = this.component.getController();
267 if (controller) { // on destroy, controller can be gone
268 controller.onScroll(x, y);
269 }
270 },
271 buffer: 200,
272 },
273 },
274 },
275
276 tbar: {
277
278 items: [
279 '->',
280 {
281 xtype: 'segmentedbutton',
282 items: [
283 {
284 text: gettext('Live Mode'),
285 bind: {
286 pressed: '{livemode}',
287 },
288 handler: 'onLiveMode',
289 },
290 {
291 text: gettext('Select Timespan'),
292 bind: {
293 pressed: '{!livemode}',
294 },
295 handler: 'onTimespan',
296 },
297 ],
298 },
299 {
300 xtype: 'box',
301 bind: { disabled: '{livemode}' },
302 autoEl: { cn: gettext('Since') + ':' },
303 },
304 {
305 xtype: 'datefield',
306 name: 'since_date',
307 reference: 'since',
308 format: 'Y-m-d',
309 bind: {
310 disabled: '{livemode}',
311 value: '{since}',
312 maxValue: '{until}',
313 },
314 },
315 {
316 xtype: 'box',
317 bind: { disabled: '{livemode}' },
318 autoEl: { cn: gettext('Until') + ':' },
319 },
320 {
321 xtype: 'datefield',
322 name: 'until_date',
323 reference: 'until',
324 format: 'Y-m-d',
325 bind: {
326 disabled: '{livemode}',
327 value: '{until}',
328 minValue: '{since}',
329 },
330 },
331 {
332 xtype: 'button',
333 text: 'Update',
334 reference: 'updateBtn',
335 handler: 'updateParams',
336 bind: {
337 disabled: '{livemode}',
338 },
339 },
340 ],
341 },
342
343 items: [
344 {
345 xtype: 'box',
346 reference: 'content',
347 style: {
348 font: 'normal 11px tahoma, arial, verdana, sans-serif',
349 'white-space': 'pre',
350 },
351 },
352 ],
353 });