]>
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({ | |
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 |
130 | Ext.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 | }); |