]> git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/panel/LogView.js
bump version to 4.2.3
[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 coming 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
26 if (viewModel.get('hide_timespan') || viewModel.get('livemode')) {
27 return;
28 }
29
30 let since = viewModel.get('since');
31 let until = viewModel.get('until');
32
33 if (since > until) {
34 Ext.Msg.alert('Error', 'Since date must be less equal than Until date.');
35 return;
36 }
37
38 let submitFormat = viewModel.get('submitFormat');
39
40 viewModel.set('params.since', Ext.Date.format(since, submitFormat));
41 if (submitFormat === 'Y-m-d') {
42 viewModel.set('params.until', Ext.Date.format(until, submitFormat) + ' 23:59:59');
43 } else {
44 viewModel.set('params.until', Ext.Date.format(until, submitFormat));
45 }
46
47 me.getView().loadTask.delay(200);
48 },
49
50 scrollPosBottom: function() {
51 let view = this.getView();
52 let pos = view.getScrollY();
53 let maxPos = view.getScrollable().getMaxPosition().y;
54 return maxPos - pos;
55 },
56
57 updateView: function(lines, first, total) {
58 let me = this;
59 let view = me.getView();
60 let viewModel = me.getViewModel();
61 let content = me.lookup('content');
62 let data = viewModel.get('data');
63
64 if (first === data.first && total === data.total && lines.length === data.lines) {
65 // before there is any real output, we get 'no output' as a single line, so always
66 // update if we only have one to be sure to catch the first real line of output
67 if (total !== 1) {
68 return; // same content, skip setting and scrolling
69 }
70 }
71 viewModel.set('data', {
72 first: first,
73 total: total,
74 lines: lines.length,
75 });
76
77 let scrollPos = me.scrollPosBottom();
78 let scrollToBottom = view.scrollToEnd && scrollPos <= 5;
79
80 if (!scrollToBottom) {
81 // so that we have the 'correct' height for the text
82 lines.length = total;
83 }
84
85 content.update(lines.join('<br>'));
86
87 if (scrollToBottom) {
88 let scroller = view.getScrollable();
89 scroller.suspendEvent('scroll');
90 view.scrollTo(0, Infinity);
91 me.updateStart(true);
92 scroller.resumeEvent('scroll');
93 }
94 },
95
96 doLoad: function() {
97 let me = this;
98 if (me.running) {
99 me.requested = true;
100 return;
101 }
102 me.running = true;
103 let view = me.getView();
104 let viewModel = me.getViewModel();
105 Proxmox.Utils.API2Request({
106 url: me.getView().url,
107 params: viewModel.get('params'),
108 method: 'GET',
109 success: function(response) {
110 if (me.isDestroyed) {
111 return;
112 }
113 Proxmox.Utils.setErrorMask(me, false);
114 let total = response.result.total;
115 let lines = [];
116 let first = Infinity;
117
118 Ext.Array.each(response.result.data, function(line) {
119 if (first > line.n) {
120 first = line.n;
121 }
122 lines[line.n - 1] = Ext.htmlEncode(line.t);
123 });
124
125 me.updateView(lines, first - 1, total);
126 me.running = false;
127 if (me.requested) {
128 me.requested = false;
129 view.loadTask.delay(200);
130 }
131 },
132 failure: function(response) {
133 if (view.failCallback) {
134 view.failCallback(response);
135 } else {
136 let msg = response.htmlStatus;
137 Proxmox.Utils.setErrorMask(me, msg);
138 }
139 me.running = false;
140 if (me.requested) {
141 me.requested = false;
142 view.loadTask.delay(200);
143 }
144 },
145 });
146 },
147
148 updateStart: function(scrolledToBottom, targetLine) {
149 let me = this;
150 let view = me.getView(), viewModel = me.getViewModel();
151
152 let limit = viewModel.get('params.limit');
153 let total = viewModel.get('data.total');
154
155 // heuristic: scroll up? -> load more in front; scroll down? -> load more at end
156 let startRatio = view.lastTargetLine && view.lastTargetLine > targetLine ? 2/3 : 1/3;
157 view.lastTargetLine = targetLine;
158
159 let newStart = scrolledToBottom
160 ? Math.trunc(total - limit, 10)
161 : Math.trunc(targetLine - (startRatio * limit) + 10);
162
163 viewModel.set('params.start', Math.max(newStart, 0));
164
165 view.loadTask.delay(200);
166 },
167
168 onScroll: function(x, y) {
169 let me = this;
170 let view = me.getView(), viewModel = me.getViewModel();
171
172 let line = view.getScrollY() / view.lineHeight;
173 let viewLines = view.getHeight() / view.lineHeight;
174
175 let viewStart = Math.max(Math.trunc(line - 1 - view.viewBuffer), 0);
176 let viewEnd = Math.trunc(line + viewLines + 1 + view.viewBuffer);
177
178 let { start, limit } = viewModel.get('params');
179
180 let margin = start < 20 ? 0 : 20;
181
182 if (viewStart < start + margin || viewEnd > start + limit - margin) {
183 me.updateStart(false, line);
184 }
185 },
186
187 onLiveMode: function() {
188 let me = this;
189 let viewModel = me.getViewModel();
190 viewModel.set('livemode', true);
191 viewModel.set('params', { start: 0, limit: 510 });
192
193 let view = me.getView();
194 delete view.content;
195 view.scrollToEnd = true;
196 me.updateView([], true, false);
197 },
198
199 onTimespan: function() {
200 let me = this;
201 me.getViewModel().set('livemode', false);
202 me.updateView([], false);
203 // Directly apply currently selected values without update
204 // button click.
205 me.updateParams();
206 },
207
208 init: function(view) {
209 let me = this;
210
211 if (!view.url) {
212 throw "no url specified";
213 }
214
215 let viewModel = this.getViewModel();
216 let since = new Date();
217 since.setDate(since.getDate() - 3);
218 viewModel.set('until', new Date());
219 viewModel.set('since', since);
220 viewModel.set('params.limit', view.pageSize);
221 viewModel.set('hide_timespan', !view.log_select_timespan);
222 viewModel.set('submitFormat', view.submitFormat);
223 me.lookup('content').setStyle('line-height', `${view.lineHeight}px`);
224
225 view.loadTask = new Ext.util.DelayedTask(me.doLoad, me);
226
227 me.updateParams();
228 view.task = Ext.TaskManager.start({
229 run: () => {
230 if (!view.isVisible() || !view.scrollToEnd) {
231 return;
232 }
233 if (me.scrollPosBottom() <= 5) {
234 view.loadTask.delay(200);
235 }
236 },
237 interval: 1000,
238 });
239 },
240 },
241
242 onDestroy: function() {
243 let me = this;
244 me.loadTask.cancel();
245 Ext.TaskManager.stop(me.task);
246 },
247
248 // for user to initiate a load from outside
249 requestUpdate: function() {
250 let me = this;
251 me.loadTask.delay(200);
252 },
253
254 viewModel: {
255 data: {
256 until: null,
257 since: null,
258 submitFormat: 'Y-m-d',
259 livemode: true,
260 hide_timespan: false,
261 data: {
262 start: 0,
263 total: 0,
264 textlen: 0,
265 },
266 params: {
267 start: 0,
268 limit: 510,
269 },
270 },
271 },
272
273 layout: 'auto',
274 bodyPadding: 5,
275 scrollable: {
276 x: 'auto',
277 y: 'auto',
278 listeners: {
279 // we have to have this here, since we cannot listen to events of the scroller in
280 // the viewcontroller (extjs bug?), nor does the panel have a 'scroll' event'
281 scroll: {
282 fn: function(scroller, x, y) {
283 let controller = this.component.getController();
284 if (controller) { // on destroy, controller can be gone
285 controller.onScroll(x, y);
286 }
287 },
288 buffer: 200,
289 },
290 },
291 },
292
293 tbar: {
294 bind: {
295 hidden: '{hide_timespan}',
296 },
297 items: [
298 '->',
299 {
300 xtype: 'segmentedbutton',
301 items: [
302 {
303 text: gettext('Live Mode'),
304 bind: {
305 pressed: '{livemode}',
306 },
307 handler: 'onLiveMode',
308 },
309 {
310 text: gettext('Select Timespan'),
311 bind: {
312 pressed: '{!livemode}',
313 },
314 handler: 'onTimespan',
315 },
316 ],
317 },
318 {
319 xtype: 'box',
320 autoEl: { cn: gettext('Since') + ':' },
321 bind: {
322 disabled: '{livemode}',
323 },
324 },
325 {
326 xtype: 'proxmoxDateTimeField',
327 name: 'since_date',
328 reference: 'since',
329 format: 'Y-m-d',
330 bind: {
331 disabled: '{livemode}',
332 value: '{since}',
333 maxValue: '{until}',
334 submitFormat: '{submitFormat}',
335 },
336 },
337 {
338 xtype: 'box',
339 autoEl: { cn: gettext('Until') + ':' },
340 bind: {
341 disabled: '{livemode}',
342 },
343 },
344 {
345 xtype: 'proxmoxDateTimeField',
346 name: 'until_date',
347 reference: 'until',
348 format: 'Y-m-d',
349 bind: {
350 disabled: '{livemode}',
351 value: '{until}',
352 minValue: '{since}',
353 submitFormat: '{submitFormat}',
354 },
355 },
356 {
357 xtype: 'button',
358 text: 'Update',
359 handler: 'updateParams',
360 bind: {
361 disabled: '{livemode}',
362 },
363 },
364 ],
365 },
366
367 items: [
368 {
369 xtype: 'box',
370 reference: 'content',
371 style: {
372 font: 'normal 11px tahoma, arial, verdana, sans-serif',
373 'white-space': 'pre',
374 },
375 },
376 ],
377 });