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