]> git.proxmox.com Git - pve-manager.git/blame - www/manager6/grid/Replication.js
Do not translate PC hardware specific terms
[pve-manager.git] / www / manager6 / grid / Replication.js
CommitLineData
9058a478
DC
1Ext.define('PVE.window.ReplicaEdit', {
2 extend: 'PVE.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'),
42b45f8f 38 emptyText: '*/15 - ' + Ext.String.format(gettext('Every {0} minutes'), 15),
9058a478
DC
39 name: 'schedule'
40 },
41 {
42 xtype: 'numberfield',
defa3bb7 43 fieldLabel: gettext('Rate limit') + ' (MB/s)',
9058a478
DC
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'
959f37af
TL
53 },
54 {
55 xtype: 'pvecheckbox',
56 name: 'enabled',
57 defaultValue: 'on',
58 checked: true,
59 fieldLabel: gettext('Enabled')
9058a478
DC
60 }
61 );
62
63 me.items = [
64 {
65 xtype: 'inputpanel',
66 itemId: 'ipanel',
1ab33d14 67 onlineHelp: 'pvesr_schedule_time_format',
9058a478
DC
68
69 onGetValues: function(values) {
70 var me = this.up('window');
71
959f37af
TL
72 values.disable = values.enabled ? 0 : 1;
73 delete values.enabled;
74
9058a478 75 PVE.Utils.delete_if_default(values, 'rate', '', me.isCreate);
959f37af 76 PVE.Utils.delete_if_default(values, 'disable', 0, me.isCreate);
9058a478
DC
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';
de2022b8 82 var vm = vmid || values.guest;
9058a478 83 var id = -1;
de2022b8
DC
84 if (me.highestids[vm] !== undefined) {
85 id = me.highestids[vm];
9058a478
DC
86 }
87 id++;
de2022b8 88 values.id = vm + '-' + id.toString();
9058a478
DC
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) {
959f37af 123 response.result.data.enabled = !response.result.data.disable;
9058a478
DC
124 me.setValues(response.result.data);
125 me.digest = response.result.data.digest;
126 }
127 });
128 }
129 }
130});
131
132Ext.define('PVE.grid.ReplicaView', {
133 extend: 'Ext.grid.Panel',
134 xtype: 'pveReplicaView',
135
1ab33d14 136 onlineHelp: 'chapter_pvesr',
9058a478
DC
137
138 stateful: true,
139 stateId: 'grid-pve-replication-status',
140
141 controller: {
142 xclass: 'Ext.app.ViewController',
143
144 addJob: function(button,event,rec) {
145 var me = this.getView();
146 var controller = this;
147 var win = Ext.create('PVE.window.ReplicaEdit', {
148 isCreate: true,
149 method: 'POST',
150 pveSelNode: me.pveSelNode
151 });
152 win.on('destroy', function() { controller.reload(); });
153 win.show();
154 },
155
156 editJob: function(button,event,rec) {
157 var me = this.getView();
158 var controller = this;
159 var data = rec.data;
160 var win = Ext.create('PVE.window.ReplicaEdit', {
161 url: '/cluster/replication/' + data.id,
162 method: 'PUT',
163 pveSelNode: me.pveSelNode
164 });
165 win.on('destroy', function() { controller.reload(); });
166 win.show();
167 },
168
169 removeJob: function(button,event,rec) {
170 var me = this.getView();
171 var controller = this;
172 PVE.Utils.API2Request({
173 url: '/api2/extjs/cluster/replication/' + rec.data.id,
174 waitMsgTarget: me,
175 method: 'DELETE',
176 callback: function() { controller.reload(); },
177 failure: function (response, opts) {
4029c97f 178 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
9058a478
DC
179 }
180 });
181 },
182
85167e5e
DM
183 scheduleJobNow: function(button,event,rec) {
184 var me = this.getView();
185 var controller = this;
186
187 PVE.Utils.API2Request({
188 url: "/api2/extjs/nodes/" + me.nodename + "/replication/" + rec.data.id + "/schedule_now",
189 method: 'POST',
190 waitMsgTarget: me,
191 callback: function() { controller.reload(); },
192 failure: function (response, opts) {
193 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
194 }
195 });
196 },
197
9058a478
DC
198 showLog: function(button, event, rec) {
199 var me = this.getView();
200 var controller = this;
201 var logView = Ext.create('PVE.panel.LogView', {
202 border: false,
203 url: "/api2/extjs/nodes/" + me.nodename + "/replication/" + rec.data.id + "/log"
204 });
205 var win = Ext.create('Ext.window.Window', {
206 items: [ logView ],
207 layout: 'fit',
208 width: 800,
209 height: 400,
210 modal: true,
211 title: gettext("Replication Log")
212 });
213 var task = {
214 run: function() {
215 logView.requestUpdate();
216 },
217 interval: 1000
218 };
219 Ext.TaskManager.start(task);
220 win.on('destroy', function() {
221 Ext.TaskManager.stop(task);
222 controller.reload();
223 });
224 win.show();
225 },
226
227 reload: function() {
228 var me = this.getView();
229 me.rstore.load();
230 },
231
232 dblClick: function(grid, record, item) {
233 var me = this;
234 me.editJob(undefined, undefined, record);
235 },
236
237 // check for cluster
238 // currently replication is for cluster only, so we disable the whole
239 // component
240 checkPrerequisites: function() {
241 var me = this.getView();
242 if (PVE.data.ResourceStore.getNodes().length < 2) {
243 me.mask(gettext("Replication needs at least two nodes"), ['pve-static-mask']);
244 }
245 },
246
247 control: {
248 '#': {
249 itemdblclick: 'dblClick',
250 afterlayout: 'checkPrerequisites'
251 }
252 }
253 },
254
255 tbar: [
256 {
257 text: gettext('Add'),
258 itemId: 'addButton',
259 handler: 'addJob'
260 },
261 {
262 xtype: 'pveButton',
263 text: gettext('Edit'),
264 itemId: 'editButton',
265 handler: 'editJob',
266 disabled: true
267 },
268 {
269 xtype: 'pveButton',
270 text: gettext('Remove'),
271 itemId: 'removeButton',
272 handler: 'removeJob',
273 dangerous: true,
274 confirmMsg: function(rec) {
d4177d53 275 var msg = Ext.String.format(gettext("Are you sure you want to remove entry {0}"), rec.id);
9058a478
DC
276 return msg;
277 },
278 disabled: true
279 },
280 {
281 xtype: 'pveButton',
282 text: gettext('Log'),
283 itemId: 'logButton',
284 handler: 'showLog',
285 disabled: true
85167e5e
DM
286 },
287 {
288 xtype: 'pveButton',
289 text: gettext('Schedule now'),
290 itemId: 'scheduleNowButton',
291 handler: 'scheduleJobNow',
292 disabled: true
9058a478
DC
293 }
294 ],
295
296 initComponent: function() {
297 var me = this;
298 var mode = '';
299 var url = '/cluster/replication';
300
301 me.nodename = me.pveSelNode.data.node;
302 me.vmid = me.pveSelNode.data.vmid;
303
304 me.columns = [
959f37af
TL
305 {
306 text: gettext('Enabled'),
307 dataIndex: 'enabled',
308 xtype: 'checkcolumn',
309 sortable: true,
310 disabled: true
311 },
9058a478
DC
312 {
313 text: gettext('ID'),
314 dataIndex: 'id',
315 width: 60,
316 hidden: true
317 },
318 {
319 text: gettext('Guest'),
320 dataIndex: 'guest',
321 width: 75
322 },
323 {
324 text: gettext('Job'),
325 dataIndex: 'jobnum',
17975935 326 width: 60
9058a478
DC
327 },
328 {
329 text: gettext('Target'),
330 dataIndex: 'target'
331 }
332 ];
333
334 if (!me.nodename) {
335 mode = 'dc';
336 me.stateId = 'grid-pve-replication-dc';
337 } else if (!me.vmid) {
338 mode = 'node';
339 url = '/nodes/' + me.nodename + '/replication';
340 } else {
341 mode = 'vm';
342 url = '/nodes/' + me.nodename + '/replication' + '?guest=' + me.vmid;
343 }
344
345 if (mode !== 'dc') {
346 me.columns.push(
347 {
348 text: gettext('Status'),
349 dataIndex: 'state',
3b0ab40a
TL
350 minWidth: 160,
351 flex: 1,
9058a478
DC
352 renderer: function(value, metadata, record) {
353
354 if (record.data.pid) {
355 metadata.tdCls = 'x-grid-row-loading';
356 return '';
357 }
358
3b0ab40a 359 var icons = [];
9058a478
DC
360 var states = [];
361
362 if (record.data.remove_job) {
3b0ab40a 363 icons.push('<i class="fa fa-ban warning" title="'
9058a478 364 + gettext("Removal Scheduled") + '"></i>');
9058a478
DC
365 states.push(gettext("Removal Scheduled"));
366 }
367
368 if (record.data.error) {
3b0ab40a
TL
369 icons.push('<i class="fa fa-times critical" title="'
370 + gettext("Error") + '"></i>');
9058a478
DC
371 states.push(record.data.error);
372 }
373
3b0ab40a
TL
374 if (icons.length == 0) {
375 icons.push('<i class="fa fa-check good"></i>');
376 states.push(gettext('OK'));
9058a478
DC
377 }
378
3b0ab40a 379 return icons.join(',') + ' ' + states.join(',');
9058a478
DC
380 }
381 },
382 {
383 text: gettext('Last Sync'),
384 dataIndex: 'last_sync',
17975935 385 width: 150,
9058a478
DC
386 renderer: function(value, metadata, record) {
387 if (!value) {
388 return '-';
389 }
390
391 if (record.data.pid) {
392 return gettext('syncing');
393 }
394
395 return PVE.Utils.render_timestamp(value);
396 }
397 },
398 {
399 text: gettext('Duration'),
400 dataIndex: 'duration',
17975935 401 width: 60,
9058a478
DC
402 renderer: PVE.Utils.render_duration
403 },
404 {
405 text: gettext('Next Sync'),
406 dataIndex: 'next_sync',
17975935 407 width: 150,
9058a478
DC
408 renderer: function(value) {
409 if (!value) {
410 return '-';
411 }
412
413 var now = new Date();
414 var next = new Date(value*1000);
415
416 if (next < now) {
0b142737 417 return gettext('pending');
9058a478
DC
418 }
419
420 return PVE.Utils.render_timestamp(value);
421 }
422 }
423 );
424 }
425
426 me.columns.push(
427 {
428 text: gettext('Schedule'),
17975935 429 width: 75,
9058a478
DC
430 dataIndex: 'schedule'
431 },
432 {
defa3bb7 433 text: gettext('Rate limit'),
9058a478
DC
434 dataIndex: 'rate',
435 renderer: function(value) {
436 if (!value) {
437 return gettext('unlimited');
438 }
439
440 return value.toString() + ' MB/s';
441 },
442 hidden: true
443 },
444 {
445 text: gettext('Comment'),
446 dataIndex: 'comment',
447 renderer: Ext.htmlEncode
448 }
449 );
450
451 me.rstore = Ext.create('PVE.data.UpdateStore', {
452 storeid: 'pve-replica-' + me.nodename + me.vmid,
453 model: (mode === 'dc')? 'pve-replication' : 'pve-replication-state',
454 interval: 3000,
455 proxy: {
456 type: 'pve',
457 url: "/api2/json" + url
458 }
459 });
460
461 me.store = Ext.create('PVE.data.DiffStore', {
462 rstore: me.rstore,
463 sorters: [
464 {
465 property: 'guest'
466 },
467 {
468 property: 'jobnum'
469 }
470 ]
471 });
472
473 me.callParent();
474
85167e5e
DM
475 // we cannot access the log and scheduleNow button
476 // in the datacenter, because
9058a478
DC
477 // we do not know where/if the jobs runs
478 if (mode === 'dc') {
479 me.down('#logButton').setHidden(true);
85167e5e 480 me.down('#scheduleNowButton').setHidden(true);
9058a478
DC
481 }
482
483 // if we set the warning mask, we do not want to load
484 // or set the mask on store errors
485 if (PVE.data.ResourceStore.getNodes().length < 2) {
486 return;
487 }
488
489 PVE.Utils.monStoreErrors(me, me.rstore);
490
491 me.on('destroy', me.rstore.stopUpdate);
492 me.rstore.startUpdate();
493 }
494}, function() {
495
496 Ext.define('pve-replication', {
497 extend: 'Ext.data.Model',
498 fields: [
499 'id', 'target', 'comment', 'rate', 'type',
500 { name: 'guest', type: 'integer' },
501 { name: 'jobnum', type: 'integer' },
959f37af
TL
502 { name: 'schedule', defaultValue: '*/15' },
503 { name: 'disable', defaultValue: '' },
504 { name: 'enabled', calculate: function(data) { return !data.disable; } }
9058a478
DC
505 ]
506 });
507
508 Ext.define('pve-replication-state', {
509 extend: 'pve-replication',
510 fields: [
511 'last_sync', 'next_sync', 'error', 'duration', 'state',
512 'fail_count', 'remove_job', 'pid'
513 ]
514 });
515
516});