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