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