]> git.proxmox.com Git - proxmox-backup.git/blob - www/Dashboard.js
ui: tape/BackupOverview: show namespaces as their own level above groups
[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('Days 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 updateRepositoryStatus: function(store, records, success) {
63 if (!success) { return; }
64 let me = this;
65 me.lookup('nodeInfo').setRepositoryInfo(records[0].data['standard-repos']);
66 },
67
68 updateSubscription: function(store, records, success) {
69 if (!success) { return; }
70 let me = this;
71 let status = records[0].data.status || 'unknown';
72 // 2 = all good, 1 = different leves, 0 = none
73 let subStatus = status.toLowerCase() === 'active' ? 2 : 0;
74 me.lookup('subscription').setSubStatus(subStatus);
75 me.lookup('nodeInfo').setSubscriptionStatus(subStatus);
76 },
77
78 updateTasks: function(store, records, success) {
79 if (!success) return;
80 let me = this;
81 let viewModel = me.getViewModel();
82
83 records.sort((a, b) => a.data.duration - b.data.duration);
84 let top10 = records.slice(-10);
85 me.lookup('longesttasks').updateTasks(top10);
86
87 let data = {
88 backup: { error: 0, warning: 0, ok: 0 },
89 prune: { error: 0, warning: 0, ok: 0 },
90 garbage_collection: { error: 0, warning: 0, ok: 0 },
91 sync: { error: 0, warning: 0, ok: 0 },
92 verify: { error: 0, warning: 0, ok: 0 },
93 };
94
95 records.forEach(record => {
96 let task = record.data;
97 let type = task.worker_type;
98 if (type === 'syncjob') {
99 type = 'sync';
100 }
101
102 if (type.startsWith('verif')) {
103 type = 'verify';
104 }
105
106 if (data[type] && task.status) {
107 let parsed = Proxmox.Utils.parse_task_status(task.status);
108 data[type][parsed]++;
109 }
110 });
111
112 me.lookup('tasksummary').updateTasks(data, viewModel.get('sinceEpoch'));
113 },
114
115 init: function(view) {
116 var me = this;
117 var sp = Ext.state.Manager.getProvider();
118 var days = sp.get('dashboard-days') || 30;
119 me.setDays(days, false);
120
121 view.mon(sp, 'statechange', function(provider, key, value) {
122 if (key !== 'summarycolumns') {
123 return;
124 }
125 Proxmox.Utils.updateColumns(view);
126 });
127 },
128 },
129
130 viewModel: {
131 data: {
132 days: 30,
133 },
134
135 formulas: {
136 sinceEpoch: (get) => (Date.now()/1000 - get('days') * 24*3600).toFixed(0),
137 },
138
139 stores: {
140 repositories: {
141 storeid: 'dash-repositories',
142 type: 'update',
143 interval: 15000,
144 autoStart: true,
145 autoLoad: true,
146 autoDestroy: true,
147 proxy: {
148 type: 'proxmox',
149 url: '/api2/json/nodes/localhost/apt/repositories',
150 },
151 listeners: {
152 load: 'updateRepositoryStatus',
153 },
154 },
155 subscription: {
156 storeid: 'dash-subscription',
157 type: 'update',
158 interval: 10000,
159 autoStart: true,
160 autoLoad: true,
161 autoDestroy: true,
162 proxy: {
163 type: 'proxmox',
164 url: '/api2/json/nodes/localhost/subscription',
165 },
166 listeners: {
167 load: 'updateSubscription',
168 },
169 },
170 tasks: {
171 storeid: 'dash-tasks',
172 type: 'update',
173 interval: 15000,
174 autoStart: true,
175 autoLoad: true,
176 autoDestroy: true,
177 model: 'proxmox-tasks',
178 proxy: {
179 type: 'proxmox',
180 url: '/api2/json/nodes/localhost/tasks',
181 extraParams: {
182 limit: 0,
183 since: '{sinceEpoch}',
184 },
185 },
186 listeners: {
187 load: 'updateTasks',
188 },
189 },
190 },
191 },
192
193 listeners: {
194 resize: function(panel) {
195 Proxmox.Utils.updateColumns(panel);
196 },
197 },
198
199 title: gettext('Dashboard'),
200
201 layout: {
202 type: 'column',
203 },
204
205 bodyPadding: '20 0 0 20',
206
207 minWidth: 700,
208
209 defaults: {
210 columnWidth: 0.49,
211 xtype: 'panel',
212 margin: '0 20 20 0',
213 },
214
215 tools: [
216 {
217 type: 'gear',
218 tooltip: gettext('Edit dashboard settings'),
219 handler: 'openDashboardOptions',
220 },
221 ],
222
223 scrollable: true,
224
225 items: [
226 {
227 xtype: 'pbsNodeInfoPanel',
228 reference: 'nodeInfo',
229 height: 280,
230 },
231 {
232 xtype: 'pbsDatastoresStatistics',
233 height: 280,
234 },
235 {
236 xtype: 'pbsLongestTasks',
237 bind: {
238 title: gettext('Longest Tasks') + ' (' +
239 Ext.String.format(gettext('{0} days'), '{days}') + ')',
240 },
241 reference: 'longesttasks',
242 height: 280,
243 },
244 {
245 xtype: 'pbsRunningTasks',
246 height: 280,
247 },
248 {
249 bind: {
250 title: gettext('Task Summary') + ' (' +
251 Ext.String.format(gettext('{0} days'), '{days}') + ')',
252 },
253 xtype: 'pbsTaskSummary',
254 height: 200,
255 reference: 'tasksummary',
256 },
257 {
258 iconCls: 'fa fa-ticket',
259 title: 'Subscription',
260 height: 200,
261 reference: 'subscription',
262 xtype: 'pbsSubscriptionInfo',
263 },
264 ],
265 });
266
267 Ext.define('PBS.dashboard.SubscriptionInfo', {
268 extend: 'Ext.panel.Panel',
269 xtype: 'pbsSubscriptionInfo',
270
271 style: {
272 cursor: 'pointer',
273 },
274
275 layout: {
276 type: 'hbox',
277 align: 'middle',
278 },
279
280 items: [
281 {
282 xtype: 'box',
283 itemId: 'icon',
284 data: {
285 icon: 'question-circle',
286 },
287 width: 100,
288 tpl: '<center><i class="fa fa-3x fa-{icon}"></i></center>',
289 },
290 {
291 flex: 1,
292 xtype: 'box',
293 data: {
294 message: gettext('Unknown'),
295 },
296 itemId: 'message',
297 tpl: '<center>{message}</center>',
298 },
299 ],
300
301 setSubStatus: function(status) {
302 var me = this;
303 let icon = '';
304 let message = '';
305
306 switch (status) {
307 case 2:
308 icon = 'check good';
309 message = gettext('Your subscription status is valid.');
310 break;
311 case 1:
312 icon = 'exclamation-triangle warning';
313 message = gettext('Warning: Your subscription levels are not the same.');
314 break;
315 case 0:
316 icon = 'times-circle critical';
317 message = `<h1>${gettext('No valid subscription')}</h1>${PBS.Utils.noSubKeyHtml}`;
318 break;
319 default:
320 throw 'invalid subscription status';
321 }
322 me.getComponent('icon').update({ icon });
323 me.getComponent('message').update({ message });
324 },
325
326 listeners: {
327 click: {
328 element: 'body',
329 fn: function() {
330 var mainview = this.component.up('mainview');
331 mainview.getController().redirectTo('pbsSubscription');
332 },
333 },
334 },
335 });