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