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