]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/grid/Replication.js
ui: eslint: fix trailing comma and comma related whitespaces errors
[pve-manager.git] / www / manager6 / grid / Replication.js
1 Ext.define('PVE.window.ReplicaEdit', {
2 extend: 'Proxmox.window.Edit',
3 xtype: 'pveReplicaEdit',
4
5 subject: gettext('Replication Job'),
6
7
8 url: '/cluster/replication',
9 method: 'POST',
10
11 initComponent: function() {
12 var me = this;
13
14 var vmid = me.pveSelNode.data.vmid;
15 var nodename = me.pveSelNode.data.node;
16
17 var items = [];
18
19 items.push({
20 xtype: (me.isCreate && !vmid)?'pveGuestIDSelector':'displayfield',
21 name: 'guest',
22 fieldLabel: 'CT/VM ID',
23 value: vmid || '',
24 });
25
26 items.push(
27 {
28 xtype: me.isCreate ? 'pveNodeSelector':'displayfield',
29 name: 'target',
30 disallowedNodes: [nodename],
31 allowBlank: false,
32 onlineValidator: true,
33 fieldLabel: gettext("Target"),
34 },
35 {
36 xtype: 'pveCalendarEvent',
37 fieldLabel: gettext('Schedule'),
38 emptyText: '*/15 - ' + Ext.String.format(gettext('Every {0} minutes'), 15),
39 name: 'schedule',
40 },
41 {
42 xtype: 'numberfield',
43 fieldLabel: gettext('Rate limit') + ' (MB/s)',
44 step: 1,
45 minValue: 1,
46 emptyText: gettext('unlimited'),
47 name: 'rate',
48 },
49 {
50 xtype: 'textfield',
51 fieldLabel: gettext('Comment'),
52 name: 'comment',
53 },
54 {
55 xtype: 'proxmoxcheckbox',
56 name: 'enabled',
57 defaultValue: 'on',
58 checked: true,
59 fieldLabel: gettext('Enabled'),
60 },
61 );
62
63 me.items = [
64 {
65 xtype: 'inputpanel',
66 itemId: 'ipanel',
67 onlineHelp: 'pvesr_schedule_time_format',
68
69 onGetValues: function(values) {
70 var me = this.up('window');
71
72 values.disable = values.enabled ? 0 : 1;
73 delete values.enabled;
74
75 PVE.Utils.delete_if_default(values, 'rate', '', me.isCreate);
76 PVE.Utils.delete_if_default(values, 'disable', 0, me.isCreate);
77 PVE.Utils.delete_if_default(values, 'schedule', '*/15', me.isCreate);
78 PVE.Utils.delete_if_default(values, 'comment', '', me.isCreate);
79
80 if (me.isCreate) {
81 values.type = 'local';
82 var vm = vmid || values.guest;
83 var id = -1;
84 if (me.highestids[vm] !== undefined) {
85 id = me.highestids[vm];
86 }
87 id++;
88 values.id = vm + '-' + id.toString();
89 delete values.guest;
90 }
91 return values;
92 },
93 items: items,
94 },
95 ];
96
97 me.callParent();
98
99 if (me.isCreate) {
100 me.load({
101 success: function(response) {
102 var jobs = response.result.data;
103 var highestids = {};
104 Ext.Array.forEach(jobs, function(job) {
105 var match = /^([0-9]+)\-([0-9]+)$/.exec(job.id);
106 if (match) {
107 var vmid = parseInt(match[1], 10);
108 var id = parseInt(match[2], 10);
109 if (highestids[vmid] < id ||
110 highestids[vmid] === undefined) {
111 highestids[vmid] = id;
112 }
113 }
114 });
115
116 me.highestids = highestids;
117 },
118 });
119
120 } else {
121 me.load({
122 success: function(response, options) {
123 response.result.data.enabled = !response.result.data.disable;
124 me.setValues(response.result.data);
125 me.digest = response.result.data.digest;
126 },
127 });
128 }
129 },
130 });
131
132 /* callback is a function and string */
133 Ext.define('PVE.grid.ReplicaView', {
134 extend: 'Ext.grid.Panel',
135 xtype: 'pveReplicaView',
136
137 onlineHelp: 'chapter_pvesr',
138
139 stateful: true,
140 stateId: 'grid-pve-replication-status',
141
142 controller: {
143 xclass: 'Ext.app.ViewController',
144
145 addJob: function(button, event, rec) {
146 var me = this.getView();
147 var controller = this;
148 var win = Ext.create('PVE.window.ReplicaEdit', {
149 isCreate: true,
150 method: 'POST',
151 pveSelNode: me.pveSelNode,
152 });
153 win.on('destroy', function() { controller.reload(); });
154 win.show();
155 },
156
157 editJob: function(button, event, rec) {
158 var me = this.getView();
159 var controller = this;
160 var data = rec.data;
161 var win = Ext.create('PVE.window.ReplicaEdit', {
162 url: '/cluster/replication/' + data.id,
163 method: 'PUT',
164 pveSelNode: me.pveSelNode,
165 });
166 win.on('destroy', function() { controller.reload(); });
167 win.show();
168 },
169
170 scheduleJobNow: function(button, event, rec) {
171 var me = this.getView();
172 var controller = this;
173
174 Proxmox.Utils.API2Request({
175 url: "/api2/extjs/nodes/" + me.nodename + "/replication/" + rec.data.id + "/schedule_now",
176 method: 'POST',
177 waitMsgTarget: me,
178 callback: function() { controller.reload(); },
179 failure: function (response, opts) {
180 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
181 },
182 });
183 },
184
185 showLog: function(button, event, rec) {
186 var me = this.getView();
187 var controller = this;
188 var logView = Ext.create('Proxmox.panel.LogView', {
189 border: false,
190 url: "/api2/extjs/nodes/" + me.nodename + "/replication/" + rec.data.id + "/log",
191 });
192 var win = Ext.create('Ext.window.Window', {
193 items: [ logView ],
194 layout: 'fit',
195 width: 800,
196 height: 400,
197 modal: true,
198 title: gettext("Replication Log"),
199 });
200 var task = {
201 run: function() {
202 logView.requestUpdate();
203 },
204 interval: 1000,
205 };
206 Ext.TaskManager.start(task);
207 win.on('destroy', function() {
208 Ext.TaskManager.stop(task);
209 controller.reload();
210 });
211 win.show();
212 },
213
214 reload: function() {
215 var me = this.getView();
216 me.rstore.load();
217 },
218
219 dblClick: function(grid, record, item) {
220 var me = this;
221 me.editJob(undefined, undefined, record);
222 },
223
224 // check for cluster
225 // currently replication is for cluster only, so we disable the whole
226 // component
227 checkPrerequisites: function() {
228 var me = this.getView();
229 if (PVE.data.ResourceStore.getNodes().length < 2) {
230 me.mask(gettext("Replication needs at least two nodes"), ['pve-static-mask']);
231 }
232 },
233
234 control: {
235 '#': {
236 itemdblclick: 'dblClick',
237 afterlayout: 'checkPrerequisites',
238 },
239 },
240 },
241
242 tbar: [
243 {
244 text: gettext('Add'),
245 itemId: 'addButton',
246 handler: 'addJob',
247 },
248 {
249 xtype: 'proxmoxButton',
250 text: gettext('Edit'),
251 itemId: 'editButton',
252 handler: 'editJob',
253 disabled: true,
254 },
255 {
256 xtype: 'proxmoxStdRemoveButton',
257 itemId: 'removeButton',
258 baseurl: '/api2/extjs/cluster/replication/',
259 dangerous: true,
260 callback: 'reload',
261 },
262 {
263 xtype: 'proxmoxButton',
264 text: gettext('Log'),
265 itemId: 'logButton',
266 handler: 'showLog',
267 disabled: true,
268 },
269 {
270 xtype: 'proxmoxButton',
271 text: gettext('Schedule now'),
272 itemId: 'scheduleNowButton',
273 handler: 'scheduleJobNow',
274 disabled: true,
275 },
276 ],
277
278 initComponent: function() {
279 var me = this;
280 var mode = '';
281 var url = '/cluster/replication';
282
283 me.nodename = me.pveSelNode.data.node;
284 me.vmid = me.pveSelNode.data.vmid;
285
286 me.columns = [
287 {
288 text: gettext('Enabled'),
289 dataIndex: 'enabled',
290 xtype: 'checkcolumn',
291 sortable: true,
292 disabled: true,
293 },
294 {
295 text: 'ID',
296 dataIndex: 'id',
297 width: 60,
298 hidden: true,
299 },
300 {
301 text: gettext('Guest'),
302 dataIndex: 'guest',
303 width: 75,
304 },
305 {
306 text: gettext('Job'),
307 dataIndex: 'jobnum',
308 width: 60,
309 },
310 {
311 text: gettext('Target'),
312 dataIndex: 'target',
313 },
314 ];
315
316 if (!me.nodename) {
317 mode = 'dc';
318 me.stateId = 'grid-pve-replication-dc';
319 } else if (!me.vmid) {
320 mode = 'node';
321 url = '/nodes/' + me.nodename + '/replication';
322 } else {
323 mode = 'vm';
324 url = '/nodes/' + me.nodename + '/replication' + '?guest=' + me.vmid;
325 }
326
327 if (mode !== 'dc') {
328 me.columns.push(
329 {
330 text: gettext('Status'),
331 dataIndex: 'state',
332 minWidth: 160,
333 flex: 1,
334 renderer: function(value, metadata, record) {
335
336 if (record.data.pid) {
337 metadata.tdCls = 'x-grid-row-loading';
338 return '';
339 }
340
341 var icons = [];
342 var states = [];
343
344 if (record.data.remove_job) {
345 icons.push('<i class="fa fa-ban warning" title="'
346 + gettext("Removal Scheduled") + '"></i>');
347 states.push(gettext("Removal Scheduled"));
348 }
349
350 if (record.data.error) {
351 icons.push('<i class="fa fa-times critical" title="'
352 + gettext("Error") + '"></i>');
353 states.push(record.data.error);
354 }
355
356 if (icons.length == 0) {
357 icons.push('<i class="fa fa-check good"></i>');
358 states.push(gettext('OK'));
359 }
360
361 return icons.join(',') + ' ' + states.join(',');
362 },
363 },
364 {
365 text: gettext('Last Sync'),
366 dataIndex: 'last_sync',
367 width: 150,
368 renderer: function(value, metadata, record) {
369 if (!value) {
370 return '-';
371 }
372
373 if (record.data.pid) {
374 return gettext('syncing');
375 }
376
377 return Proxmox.Utils.render_timestamp(value);
378 },
379 },
380 {
381 text: gettext('Duration'),
382 dataIndex: 'duration',
383 width: 60,
384 renderer: Proxmox.Utils.render_duration,
385 },
386 {
387 text: gettext('Next Sync'),
388 dataIndex: 'next_sync',
389 width: 150,
390 renderer: function(value) {
391 if (!value) {
392 return '-';
393 }
394
395 var now = new Date();
396 var next = new Date(value*1000);
397
398 if (next < now) {
399 return gettext('pending');
400 }
401
402 return Proxmox.Utils.render_timestamp(value);
403 },
404 },
405 );
406 }
407
408 me.columns.push(
409 {
410 text: gettext('Schedule'),
411 width: 75,
412 dataIndex: 'schedule',
413 },
414 {
415 text: gettext('Rate limit'),
416 dataIndex: 'rate',
417 renderer: function(value) {
418 if (!value) {
419 return gettext('unlimited');
420 }
421
422 return value.toString() + ' MB/s';
423 },
424 hidden: true,
425 },
426 {
427 text: gettext('Comment'),
428 dataIndex: 'comment',
429 renderer: Ext.htmlEncode,
430 },
431 );
432
433 me.rstore = Ext.create('Proxmox.data.UpdateStore', {
434 storeid: 'pve-replica-' + me.nodename + me.vmid,
435 model: (mode === 'dc')? 'pve-replication' : 'pve-replication-state',
436 interval: 3000,
437 proxy: {
438 type: 'proxmox',
439 url: "/api2/json" + url,
440 },
441 });
442
443 me.store = Ext.create('Proxmox.data.DiffStore', {
444 rstore: me.rstore,
445 sorters: [
446 {
447 property: 'guest',
448 },
449 {
450 property: 'jobnum',
451 },
452 ],
453 });
454
455 me.callParent();
456
457 // we cannot access the log and scheduleNow button
458 // in the datacenter, because
459 // we do not know where/if the jobs runs
460 if (mode === 'dc') {
461 me.down('#logButton').setHidden(true);
462 me.down('#scheduleNowButton').setHidden(true);
463 }
464
465 // if we set the warning mask, we do not want to load
466 // or set the mask on store errors
467 if (PVE.data.ResourceStore.getNodes().length < 2) {
468 return;
469 }
470
471 Proxmox.Utils.monStoreErrors(me, me.rstore);
472
473 me.on('destroy', me.rstore.stopUpdate);
474 me.rstore.startUpdate();
475 },
476 }, function() {
477
478 Ext.define('pve-replication', {
479 extend: 'Ext.data.Model',
480 fields: [
481 'id', 'target', 'comment', 'rate', 'type',
482 { name: 'guest', type: 'integer' },
483 { name: 'jobnum', type: 'integer' },
484 { name: 'schedule', defaultValue: '*/15' },
485 { name: 'disable', defaultValue: '' },
486 { name: 'enabled', calculate: function(data) { return !data.disable; } },
487 ],
488 });
489
490 Ext.define('pve-replication-state', {
491 extend: 'pve-replication',
492 fields: [
493 'last_sync', 'next_sync', 'error', 'duration', 'state',
494 'fail_count', 'remove_job', 'pid',
495 ],
496 });
497
498 });