]>
Commit | Line | Data |
---|---|---|
5b1b9360 DC |
1 | Ext.define('PVE.guest.SnapshotTree', { |
2 | extend: 'Ext.tree.Panel', | |
3 | xtype: 'pveGuestSnapshotTree', | |
4 | ||
5 | stateful: true, | |
6 | stateId: 'grid-snapshots', | |
7 | ||
8 | viewModel: { | |
9 | data: { | |
10 | // should be 'qemu' or 'lxc' | |
11 | type: undefined, | |
12 | nodename: undefined, | |
13 | vmid: undefined, | |
14 | snapshotAllowed: false, | |
15 | rollbackAllowed: false, | |
16 | snapshotFeature: false, | |
7fb02366 | 17 | running: false, |
5b1b9360 DC |
18 | selected: '', |
19 | load_delay: 3000, | |
20 | }, | |
21 | formulas: { | |
b9c780c5 TL |
22 | canSnapshot: (get) => get('snapshotAllowed') && get('snapshotFeature'), |
23 | canRollback: (get) => get('rollbackAllowed') && get('isSnapshot'), | |
24 | canRemove: (get) => get('snapshotAllowed') && get('isSnapshot'), | |
25 | isSnapshot: (get) => get('selected') && get('selected') !== 'current', | |
c16c1302 | 26 | buttonText: (get) => get('snapshotAllowed') ? gettext('Edit') : gettext('View'), |
b9c780c5 | 27 | showMemory: (get) => get('type') === 'qemu', |
5b1b9360 DC |
28 | }, |
29 | }, | |
30 | ||
31 | controller: { | |
32 | xclass: 'Ext.app.ViewController', | |
33 | ||
34 | newSnapshot: function() { | |
35 | this.run_editor(false); | |
36 | }, | |
37 | ||
38 | editSnapshot: function() { | |
39 | this.run_editor(true); | |
40 | }, | |
41 | ||
42 | run_editor: function(edit) { | |
43 | let me = this; | |
44 | let vm = me.getViewModel(); | |
45 | let snapname; | |
46 | if (edit) { | |
47 | snapname = vm.get('selected'); | |
48 | if (!snapname || snapname === 'current') { return; } | |
49 | } | |
50 | let win = Ext.create('PVE.window.Snapshot', { | |
51 | nodename: vm.get('nodename'), | |
52 | vmid: vm.get('vmid'), | |
53 | viewonly: !vm.get('snapshotAllowed'), | |
54 | type: vm.get('type'), | |
55 | isCreate: !edit, | |
56 | submitText: !edit ? gettext('Take Snapshot') : undefined, | |
57 | snapname: snapname, | |
7fb02366 | 58 | running: vm.get('running'), |
5b1b9360 DC |
59 | }); |
60 | win.show(); | |
61 | me.mon(win, 'destroy', me.reload, me); | |
62 | }, | |
63 | ||
64 | snapshotAction: function(action, method) { | |
65 | let me = this; | |
66 | let view = me.getView(); | |
67 | let vm = me.getViewModel(); | |
68 | let snapname = vm.get('selected'); | |
69 | if (!snapname) { return; } | |
70 | ||
71 | let nodename = vm.get('nodename'); | |
72 | let type = vm.get('type'); | |
73 | let vmid = vm.get('vmid'); | |
74 | ||
75 | Proxmox.Utils.API2Request({ | |
76 | url: `/nodes/${nodename}/${type}/${vmid}/snapshot/${snapname}/${action}`, | |
77 | method: method, | |
78 | waitMsgTarget: view, | |
79 | callback: function() { | |
80 | me.reload(); | |
81 | }, | |
8058410f | 82 | failure: function(response, opts) { |
5b1b9360 DC |
83 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); |
84 | }, | |
85 | success: function(response, options) { | |
86 | var upid = response.result.data; | |
87 | var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid }); | |
88 | win.show(); | |
f6710aac | 89 | }, |
5b1b9360 DC |
90 | }); |
91 | }, | |
92 | ||
9a6b894f TL |
93 | rollback: function() { |
94 | this.snapshotAction('rollback', 'POST'); | |
95 | }, | |
96 | remove: function() { | |
97 | this.snapshotAction('', 'DELETE'); | |
98 | }, | |
5b1b9360 DC |
99 | cancel: function() { |
100 | this.load_task.cancel(); | |
101 | }, | |
102 | ||
103 | reload: function() { | |
104 | let me = this; | |
105 | let view = me.getView(); | |
106 | let vm = me.getViewModel(); | |
107 | let nodename = vm.get('nodename'); | |
108 | let vmid = vm.get('vmid'); | |
109 | let type = vm.get('type'); | |
110 | let load_delay = vm.get('load_delay'); | |
111 | ||
112 | Proxmox.Utils.API2Request({ | |
113 | url: `/nodes/${nodename}/${type}/${vmid}/snapshot`, | |
114 | method: 'GET', | |
115 | failure: function(response, opts) { | |
87ae19d1 | 116 | if (me.destroyed) return; |
5b1b9360 DC |
117 | Proxmox.Utils.setErrorMask(view, response.htmlStatus); |
118 | me.load_task.delay(load_delay); | |
119 | }, | |
120 | success: function(response, opts) { | |
87ae19d1 TL |
121 | if (me.destroyed) { |
122 | // this is in a delayed task, avoid dragons if view has | |
123 | // been destroyed already and go home. | |
124 | return; | |
125 | } | |
5b1b9360 DC |
126 | Proxmox.Utils.setErrorMask(view, false); |
127 | var digest = 'invalid'; | |
128 | var idhash = {}; | |
129 | var root = { name: '__root', expanded: true, children: [] }; | |
130 | Ext.Array.each(response.result.data, function(item) { | |
131 | item.leaf = true; | |
132 | item.children = []; | |
133 | if (item.name === 'current') { | |
7fb02366 | 134 | vm.set('running', !!item.running); |
5b1b9360 DC |
135 | digest = item.digest + item.running; |
136 | item.iconCls = PVE.Utils.get_object_icon_class(vm.get('type'), item); | |
137 | } else { | |
138 | item.iconCls = 'fa fa-fw fa-history x-fa-tree'; | |
139 | } | |
140 | idhash[item.name] = item; | |
141 | }); | |
142 | ||
143 | if (digest !== me.old_digest) { | |
144 | me.old_digest = digest; | |
145 | ||
146 | Ext.Array.each(response.result.data, function(item) { | |
147 | if (item.parent && idhash[item.parent]) { | |
148 | var parent_item = idhash[item.parent]; | |
149 | parent_item.children.push(item); | |
150 | parent_item.leaf = false; | |
151 | parent_item.expanded = true; | |
152 | parent_item.expandable = false; | |
153 | } else { | |
154 | root.children.push(item); | |
155 | } | |
156 | }); | |
157 | ||
158 | me.getView().setRootNode(root); | |
159 | } | |
160 | ||
161 | me.load_task.delay(load_delay); | |
f6710aac | 162 | }, |
5b1b9360 DC |
163 | }); |
164 | ||
165 | // if we do not have the permissions, we don't have to check | |
166 | // if we can create a snapshot, since the butten stays disabled | |
167 | if (!vm.get('snapshotAllowed')) { | |
168 | return; | |
169 | } | |
170 | ||
171 | Proxmox.Utils.API2Request({ | |
172 | url: `/nodes/${nodename}/${type}/${vmid}/feature`, | |
173 | params: { feature: 'snapshot' }, | |
174 | method: 'GET', | |
175 | success: function(response, options) { | |
9cc4958f TL |
176 | if (me.destroyed) { |
177 | // this is in a delayed task, the current view could been | |
178 | // destroyed already; then we mustn't do viemodel set | |
179 | return; | |
180 | } | |
9a6b894f TL |
181 | let res = response.result.data; |
182 | vm.set('snapshotFeature', !!res.hasFeature); | |
f6710aac | 183 | }, |
5b1b9360 DC |
184 | }); |
185 | }, | |
186 | ||
187 | select: function(grid, val) { | |
188 | let vm = this.getViewModel(); | |
189 | if (val.length < 1) { | |
190 | vm.set('selected', ''); | |
191 | return; | |
192 | } | |
193 | vm.set('selected', val[0].data.name); | |
194 | }, | |
195 | ||
196 | init: function(view) { | |
197 | let me = this; | |
198 | let vm = me.getViewModel(); | |
199 | me.load_task = new Ext.util.DelayedTask(me.reload, me); | |
200 | ||
201 | if (!view.type) { | |
202 | throw 'guest type not set'; | |
203 | } | |
204 | vm.set('type', view.type); | |
205 | ||
206 | if (!view.pveSelNode.data.node) { | |
207 | throw "no node name specified"; | |
208 | } | |
209 | vm.set('nodename', view.pveSelNode.data.node); | |
210 | ||
211 | if (!view.pveSelNode.data.vmid) { | |
212 | throw "no VM ID specified"; | |
213 | } | |
214 | vm.set('vmid', view.pveSelNode.data.vmid); | |
215 | ||
216 | let caps = Ext.state.Manager.get('GuiCap'); | |
217 | vm.set('snapshotAllowed', !!caps.vms['VM.Snapshot']); | |
218 | vm.set('rollbackAllowed', !!caps.vms['VM.Snapshot.Rollback']); | |
219 | ||
220 | view.getStore().sorters.add({ | |
221 | property: 'order', | |
222 | direction: 'ASC', | |
223 | }); | |
224 | ||
225 | me.reload(); | |
226 | }, | |
227 | }, | |
228 | ||
229 | listeners: { | |
230 | selectionchange: 'select', | |
231 | itemdblclick: 'editSnapshot', | |
232 | destroy: 'cancel', | |
233 | }, | |
234 | ||
235 | layout: 'fit', | |
236 | rootVisible: false, | |
237 | animate: false, | |
238 | sortableColumns: false, | |
239 | ||
240 | tbar: [ | |
241 | { | |
242 | xtype: 'proxmoxButton', | |
243 | text: gettext('Take Snapshot'), | |
244 | disabled: true, | |
245 | bind: { | |
246 | disabled: "{!canSnapshot}", | |
247 | }, | |
248 | handler: 'newSnapshot', | |
249 | }, | |
e1af1385 | 250 | '-', |
5b1b9360 DC |
251 | { |
252 | xtype: 'proxmoxButton', | |
253 | text: gettext('Rollback'), | |
254 | disabled: true, | |
255 | bind: { | |
256 | disabled: '{!canRollback}', | |
257 | }, | |
258 | confirmMsg: function() { | |
259 | let view = this.up('treepanel'); | |
260 | let rec = view.getSelection()[0]; | |
261 | let vmid = view.getViewModel().get('vmid'); | |
262 | return Proxmox.Utils.format_task_description('qmrollback', vmid) + | |
8058410f | 263 | " '" + rec.data.name + "'"; |
5b1b9360 DC |
264 | }, |
265 | handler: 'rollback', | |
266 | }, | |
e41a172b | 267 | '-', |
97f8afff MA |
268 | { |
269 | xtype: 'proxmoxButton', | |
270 | text: gettext('Edit'), | |
271 | bind: { | |
272 | text: '{buttonText}', | |
273 | disabled: '{!isSnapshot}', | |
274 | }, | |
275 | disabled: true, | |
276 | edit: true, | |
277 | handler: 'editSnapshot', | |
278 | }, | |
5b1b9360 DC |
279 | { |
280 | xtype: 'proxmoxButton', | |
281 | text: gettext('Remove'), | |
282 | disabled: true, | |
97f8afff | 283 | dangerous: true, |
5b1b9360 DC |
284 | bind: { |
285 | disabled: '{!canRemove}', | |
286 | }, | |
287 | confirmMsg: function() { | |
288 | let view = this.up('treepanel'); | |
289 | let rec = view.getSelection()[0]; | |
290 | return Ext.String.format( | |
291 | gettext('Are you sure you want to remove entry {0}'), | |
f6710aac | 292 | `'${rec.data.name}'`, |
5b1b9360 DC |
293 | ); |
294 | }, | |
295 | handler: 'remove', | |
296 | }, | |
e1af1385 TL |
297 | { |
298 | xtype: 'label', | |
299 | text: gettext("The current guest configuration does not support taking new snapshots"), | |
300 | hidden: true, | |
301 | bind: { | |
302 | hidden: "{canSnapshot}", | |
303 | }, | |
304 | }, | |
5b1b9360 DC |
305 | ], |
306 | ||
307 | columnLines: true, | |
308 | ||
309 | fields: [ | |
310 | 'name', 'description', 'snapstate', 'vmstate', 'running', | |
311 | { name: 'snaptime', type: 'date', dateFormat: 'timestamp' }, | |
312 | { | |
313 | name: 'order', | |
314 | calculate: function(data) { | |
315 | return data.snaptime || (data.name === 'current' ? 'ZZZ' : data.snapstate); | |
f6710aac TL |
316 | }, |
317 | }, | |
5b1b9360 DC |
318 | ], |
319 | ||
320 | columns: [ | |
321 | { | |
322 | xtype: 'treecolumn', | |
323 | text: gettext('Name'), | |
324 | dataIndex: 'name', | |
325 | width: 200, | |
326 | renderer: function(value, metaData, record) { | |
327 | if (value === 'current') { | |
328 | return gettext('NOW'); | |
329 | } else { | |
330 | return value; | |
331 | } | |
f6710aac | 332 | }, |
5b1b9360 DC |
333 | }, |
334 | { | |
335 | text: gettext('RAM'), | |
336 | hidden: true, | |
337 | bind: { | |
338 | hidden: '{!showMemory}', | |
339 | }, | |
340 | align: 'center', | |
341 | resizable: false, | |
342 | dataIndex: 'vmstate', | |
343 | width: 50, | |
344 | renderer: function(value, metaData, record) { | |
345 | if (record.data.name !== 'current') { | |
346 | return Proxmox.Utils.format_boolean(value); | |
347 | } | |
f6710aac | 348 | }, |
5b1b9360 DC |
349 | }, |
350 | { | |
351 | text: gettext('Date') + "/" + gettext("Status"), | |
352 | dataIndex: 'snaptime', | |
353 | width: 150, | |
354 | renderer: function(value, metaData, record) { | |
355 | if (record.data.snapstate) { | |
356 | return record.data.snapstate; | |
357 | } | |
358 | if (value) { | |
f6710aac | 359 | return Ext.Date.format(value, 'Y-m-d H:i:s'); |
5b1b9360 | 360 | } |
f6710aac | 361 | }, |
5b1b9360 DC |
362 | }, |
363 | { | |
364 | text: gettext('Description'), | |
365 | dataIndex: 'description', | |
366 | flex: 1, | |
367 | renderer: function(value, metaData, record) { | |
368 | if (record.data.name === 'current') { | |
369 | return gettext("You are here!"); | |
370 | } else { | |
371 | return Ext.String.htmlEncode(value); | |
372 | } | |
f6710aac TL |
373 | }, |
374 | }, | |
5b1b9360 DC |
375 | ], |
376 | ||
377 | }); |