]> git.proxmox.com Git - proxmox-backup.git/blob - www/Dashboard.js
ui: Dashboard/TaskSummary: show task overlay when clicking on a count
[proxmox-backup.git] / www / Dashboard.js
1 Ext.define('PBS.Dashboard', {
2 extend: 'Ext.panel.Panel',
3 xtype: 'pbsDashboard',
4
5 controller: {
6 xclass: 'Ext.app.ViewController',
7
8 openDashboardOptions: function() {
9 var me = this;
10 var viewModel = me.getViewModel();
11 Ext.create('Ext.window.Window', {
12 modal: true,
13 width: 300,
14 title: gettext('Dashboard Options'),
15 layout: {
16 type: 'auto',
17 },
18 items: [{
19 xtype: 'form',
20 bodyPadding: '10 10 10 10',
21 defaultButton: 'savebutton',
22 items: [{
23 xtype: 'proxmoxintegerfield',
24 itemId: 'days',
25 labelWidth: 100,
26 anchor: '100%',
27 allowBlank: false,
28 minValue: 1,
29 maxValue: 60,
30 value: viewModel.get('days'),
31 fieldLabel: gettext('Hours to show'),
32 }],
33 buttons: [{
34 text: gettext('Save'),
35 reference: 'savebutton',
36 formBind: true,
37 handler: function() {
38 var win = this.up('window');
39 var days = win.down('#days').getValue();
40 me.setDays(days, true);
41 win.close();
42 },
43 }],
44 }],
45 }).show();
46 },
47
48 setDays: function(days, setState) {
49 var me = this;
50 var viewModel = me.getViewModel();
51 viewModel.set('days', days);
52 viewModel.notify();
53
54 viewModel.getStore('tasks').reload();
55
56 if (setState) {
57 var sp = Ext.state.Manager.getProvider();
58 sp.set('dashboard-days', days);
59 }
60 },
61
62
63 updateSubscription: function(store, records, success) {
64 if (!success) { return; }
65 let me = this;
66 let subStatus = records[0].data.status === 'Active' ? 2 : 0; // 2 = all good, 1 = different leves, 0 = none
67 me.lookup('subscription').setSubStatus(subStatus);
68 },
69
70 updateUsageStats: function(store, records, success) {
71 if (!success) {
72 return;
73 }
74 if (records === undefined || records.length < 1) {
75 return;
76 }
77 let me = this;
78 let viewmodel = me.getViewModel();
79
80 let res = records[0].data;
81 viewmodel.set('fingerprint', res.info.fingerprint || Proxmox.Utils.unknownText);
82
83 let cpu = res.cpu,
84 mem = res.memory,
85 root = res.root;
86
87 var cpuPanel = me.lookup('cpu');
88 cpuPanel.updateValue(cpu);
89
90 var memPanel = me.lookup('mem');
91 memPanel.updateValue(mem.used / mem.total);
92
93 var hdPanel = me.lookup('root');
94 hdPanel.updateValue(root.used / root.total);
95 },
96
97 showFingerPrint: function() {
98 let me = this;
99 let vm = me.getViewModel();
100 let fingerprint = vm.get('fingerprint');
101 Ext.create('Ext.window.Window', {
102 modal: true,
103 width: 600,
104 title: gettext('Fingerprint'),
105 layout: 'form',
106 bodyPadding: '10 0',
107 items: [
108 {
109 xtype: 'textfield',
110 inputId: 'fingerprintField',
111 value: fingerprint,
112 editable: false,
113 },
114 ],
115 buttons: [
116 {
117 xtype: 'button',
118 iconCls: 'fa fa-clipboard',
119 handler: function(b) {
120 var el = document.getElementById('fingerprintField');
121 el.select();
122 document.execCommand("copy");
123 },
124 text: gettext('Copy'),
125 },
126 {
127 text: gettext('Ok'),
128 handler: function() {
129 this.up('window').close();
130 },
131 },
132 ],
133 }).show();
134 },
135
136 updateTasks: function(store, records, success) {
137 if (!success) return;
138 let me = this;
139 let viewModel = me.getViewModel();
140
141 records.sort((a, b) => a.data.duration - b.data.duration);
142 let top10 = records.slice(-10);
143 me.lookup('longesttasks').updateTasks(top10);
144
145 let data = {
146 backup: { error: 0, warning: 0, ok: 0 },
147 prune: { error: 0, warning: 0, ok: 0 },
148 garbage_collection: { error: 0, warning: 0, ok: 0 },
149 sync: { error: 0, warning: 0, ok: 0 },
150 verify: { error: 0, warning: 0, ok: 0 },
151 };
152
153 records.forEach(record => {
154 let type = record.data.worker_type;
155 if (type === 'syncjob') {
156 type = 'sync';
157 }
158
159 if (type.startsWith('verify')) {
160 type = 'verify';
161 }
162
163 if (data[type] && record.data.status) {
164 let parsed = Proxmox.Utils.parse_task_status(record.data.status);
165 data[type][parsed]++;
166 }
167 });
168
169 me.lookup('tasksummary').updateTasks(data, viewModel.get('sinceEpoch'));
170 },
171
172 init: function(view) {
173 var me = this;
174 var sp = Ext.state.Manager.getProvider();
175 var days = sp.get('dashboard-days') || 30;
176 me.setDays(days, false);
177 },
178 },
179
180 viewModel: {
181 data: {
182 fingerprint: "",
183 days: 30,
184 },
185
186 formulas: {
187 disableFPButton: (get) => get('fingerprint') === "",
188 sinceEpoch: (get) => (Date.now()/1000 - get('days') * 24*3600).toFixed(0),
189 },
190
191 stores: {
192 usage: {
193 storeid: 'dash-usage',
194 type: 'update',
195 interval: 3000,
196 autoStart: true,
197 autoLoad: true,
198 autoDestroy: true,
199 proxy: {
200 type: 'proxmox',
201 url: '/api2/json/nodes/localhost/status',
202 },
203 listeners: {
204 load: 'updateUsageStats',
205 },
206 },
207 subscription: {
208 storeid: 'dash-subscription',
209 type: 'update',
210 interval: 10000,
211 autoStart: true,
212 autoLoad: true,
213 autoDestroy: true,
214 proxy: {
215 type: 'proxmox',
216 url: '/api2/json/nodes/localhost/subscription',
217 },
218 listeners: {
219 load: 'updateSubscription',
220 },
221 },
222 tasks: {
223 storeid: 'dash-tasks',
224 type: 'update',
225 interval: 15000,
226 autoStart: true,
227 autoLoad: true,
228 autoDestroy: true,
229 model: 'proxmox-tasks',
230 proxy: {
231 type: 'proxmox',
232 url: '/api2/json/status/tasks',
233 extraParams: {
234 since: '{sinceEpoch}',
235 },
236 },
237 listeners: {
238 load: 'updateTasks',
239 },
240 },
241 },
242 },
243
244 title: gettext('Dashboard'),
245
246 layout: {
247 type: 'column',
248 },
249
250 bodyPadding: '20 0 0 20',
251
252 defaults: {
253 columnWidth: 0.49,
254 xtype: 'panel',
255 margin: '0 20 20 0',
256 },
257
258 tools: [
259 {
260 type: 'gear',
261 handler: 'openDashboardOptions',
262 },
263 ],
264
265 scrollable: true,
266
267 items: [
268 {
269 height: 250,
270 iconCls: 'fa fa-tasks',
271 title: gettext('Server Resources'),
272 bodyPadding: '0 20 0 20',
273 tools: [
274 {
275 xtype: 'button',
276 text: gettext('Show Fingerprint'),
277 handler: 'showFingerPrint',
278 bind: {
279 disabled: '{disableFPButton}',
280 },
281 },
282 ],
283 layout: {
284 type: 'hbox',
285 align: 'center',
286 },
287 defaults: {
288 xtype: 'proxmoxGauge',
289 spriteFontSize: '20px',
290 flex: 1,
291 },
292 items: [
293 {
294 title: gettext('CPU'),
295 reference: 'cpu',
296 },
297 {
298 title: gettext('Memory'),
299 reference: 'mem',
300 },
301 {
302 title: gettext('Root Disk'),
303 reference: 'root',
304 },
305 ],
306 },
307 {
308 xtype: 'pbsDatastoresStatistics',
309 height: 250,
310 },
311 {
312 xtype: 'pbsLongestTasks',
313 bind: {
314 title: gettext('Longest Tasks') + ' (' +
315 Ext.String.format(gettext('{0} days'), '{days}') + ')',
316 },
317 reference: 'longesttasks',
318 height: 250,
319 },
320 {
321 xtype: 'pbsRunningTasks',
322 height: 250,
323 },
324 {
325 bind: {
326 title: gettext('Task Summary') + ' (' +
327 Ext.String.format(gettext('{0} days'), '{days}') + ')',
328 },
329 xtype: 'pbsTaskSummary',
330 reference: 'tasksummary',
331 },
332 {
333 iconCls: 'fa fa-ticket',
334 title: 'Subscription',
335 height: 166,
336 reference: 'subscription',
337 xtype: 'pbsSubscriptionInfo',
338 },
339 ],
340 });
341
342 Ext.define('PBS.dashboard.SubscriptionInfo', {
343 extend: 'Ext.panel.Panel',
344 xtype: 'pbsSubscriptionInfo',
345
346 style: {
347 cursor: 'pointer',
348 },
349
350 layout: {
351 type: 'hbox',
352 align: 'middle',
353 },
354
355 items: [
356 {
357 xtype: 'box',
358 itemId: 'icon',
359 data: {
360 icon: 'question-circle',
361 },
362 width: 100,
363 tpl: '<center><i class="fa fa-3x fa-{icon}"></i></center>',
364 },
365 {
366 flex: 1,
367 xtype: 'box',
368 data: {
369 message: gettext('Unknown'),
370 },
371 itemId: 'message',
372 tpl: '<center>{message}</center>',
373 },
374 ],
375
376 setSubStatus: function(status) {
377 var me = this;
378 let icon = '';
379 let message = '';
380
381 switch (status) {
382 case 2:
383 icon = 'check good';
384 message = gettext('Your subscription status is valid.');
385 break;
386 case 1:
387 icon = 'exclamation-triangle warning';
388 message = gettext('Warning: Your subscription levels are not the same.');
389 break;
390 case 0:
391 icon = 'times-circle critical';
392 message = gettext('This node does not have a subscription.');
393 break;
394 default:
395 throw 'invalid subscription status';
396 }
397 me.getComponent('icon').update({ icon });
398 me.getComponent('message').update({ message });
399 },
400
401 listeners: {
402 click: {
403 element: 'body',
404 fn: function() {
405 var mainview = this.component.up('mainview');
406 mainview.getController().redirectTo('pbsSubscription');
407 },
408 },
409 },
410 });