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