]>
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: { | |
21 | canSnapshot: function(get) { | |
22 | return get('snapshotAllowed') && get('snapshotFeature'); | |
23 | }, | |
24 | canRollback: function(get) { | |
25 | return get('rollbackAllowed') && | |
26 | get('selected') && get('selected') !== 'current'; | |
27 | }, | |
28 | canRemove: function(get) { | |
29 | return get('snapshotAllowed') && | |
30 | get('selected') && get('selected') !== 'current'; | |
31 | }, | |
32 | isSnapshot: function(get) { | |
33 | return get('selected') && get('selected') !== 'current'; | |
34 | }, | |
35 | buttonText: function(get) { | |
36 | return get('snapshotAllowed') ? gettext('Edit') : gettext('View'); | |
37 | }, | |
38 | showMemory: function(get) { | |
39 | return get('type') === 'qemu'; | |
40 | }, | |
41 | }, | |
42 | }, | |
43 | ||
44 | controller: { | |
45 | xclass: 'Ext.app.ViewController', | |
46 | ||
47 | newSnapshot: function() { | |
48 | this.run_editor(false); | |
49 | }, | |
50 | ||
51 | editSnapshot: function() { | |
52 | this.run_editor(true); | |
53 | }, | |
54 | ||
55 | run_editor: function(edit) { | |
56 | let me = this; | |
57 | let vm = me.getViewModel(); | |
58 | let snapname; | |
59 | if (edit) { | |
60 | snapname = vm.get('selected'); | |
61 | if (!snapname || snapname === 'current') { return; } | |
62 | } | |
63 | let win = Ext.create('PVE.window.Snapshot', { | |
64 | nodename: vm.get('nodename'), | |
65 | vmid: vm.get('vmid'), | |
66 | viewonly: !vm.get('snapshotAllowed'), | |
67 | type: vm.get('type'), | |
68 | isCreate: !edit, | |
69 | submitText: !edit ? gettext('Take Snapshot') : undefined, | |
70 | snapname: snapname, | |
71 | }); | |
72 | win.show(); | |
73 | me.mon(win, 'destroy', me.reload, me); | |
74 | }, | |
75 | ||
76 | snapshotAction: function(action, method) { | |
77 | let me = this; | |
78 | let view = me.getView(); | |
79 | let vm = me.getViewModel(); | |
80 | let snapname = vm.get('selected'); | |
81 | if (!snapname) { return; } | |
82 | ||
83 | let nodename = vm.get('nodename'); | |
84 | let type = vm.get('type'); | |
85 | let vmid = vm.get('vmid'); | |
86 | ||
87 | Proxmox.Utils.API2Request({ | |
88 | url: `/nodes/${nodename}/${type}/${vmid}/snapshot/${snapname}/${action}`, | |
89 | method: method, | |
90 | waitMsgTarget: view, | |
91 | callback: function() { | |
92 | me.reload(); | |
93 | }, | |
94 | failure: function (response, opts) { | |
95 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
96 | }, | |
97 | success: function(response, options) { | |
98 | var upid = response.result.data; | |
99 | var win = Ext.create('Proxmox.window.TaskProgress', { upid: upid }); | |
100 | win.show(); | |
101 | } | |
102 | }); | |
103 | }, | |
104 | ||
105 | rollback: function() { this.snapshotAction('rollback', 'POST'); }, | |
106 | remove: function() { this.snapshotAction('', 'DELETE'); }, | |
107 | ||
108 | cancel: function() { | |
109 | this.load_task.cancel(); | |
110 | }, | |
111 | ||
112 | reload: function() { | |
113 | let me = this; | |
114 | let view = me.getView(); | |
115 | let vm = me.getViewModel(); | |
116 | let nodename = vm.get('nodename'); | |
117 | let vmid = vm.get('vmid'); | |
118 | let type = vm.get('type'); | |
119 | let load_delay = vm.get('load_delay'); | |
120 | ||
121 | Proxmox.Utils.API2Request({ | |
122 | url: `/nodes/${nodename}/${type}/${vmid}/snapshot`, | |
123 | method: 'GET', | |
124 | failure: function(response, opts) { | |
125 | Proxmox.Utils.setErrorMask(view, response.htmlStatus); | |
126 | me.load_task.delay(load_delay); | |
127 | }, | |
128 | success: function(response, opts) { | |
129 | Proxmox.Utils.setErrorMask(view, false); | |
130 | var digest = 'invalid'; | |
131 | var idhash = {}; | |
132 | var root = { name: '__root', expanded: true, children: [] }; | |
133 | Ext.Array.each(response.result.data, function(item) { | |
134 | item.leaf = true; | |
135 | item.children = []; | |
136 | if (item.name === 'current') { | |
137 | digest = item.digest + item.running; | |
138 | item.iconCls = PVE.Utils.get_object_icon_class(vm.get('type'), item); | |
139 | } else { | |
140 | item.iconCls = 'fa fa-fw fa-history x-fa-tree'; | |
141 | } | |
142 | idhash[item.name] = item; | |
143 | }); | |
144 | ||
145 | if (digest !== me.old_digest) { | |
146 | me.old_digest = digest; | |
147 | ||
148 | Ext.Array.each(response.result.data, function(item) { | |
149 | if (item.parent && idhash[item.parent]) { | |
150 | var parent_item = idhash[item.parent]; | |
151 | parent_item.children.push(item); | |
152 | parent_item.leaf = false; | |
153 | parent_item.expanded = true; | |
154 | parent_item.expandable = false; | |
155 | } else { | |
156 | root.children.push(item); | |
157 | } | |
158 | }); | |
159 | ||
160 | me.getView().setRootNode(root); | |
161 | } | |
162 | ||
163 | me.load_task.delay(load_delay); | |
164 | } | |
165 | }); | |
166 | ||
167 | // if we do not have the permissions, we don't have to check | |
168 | // if we can create a snapshot, since the butten stays disabled | |
169 | if (!vm.get('snapshotAllowed')) { | |
170 | return; | |
171 | } | |
172 | ||
173 | Proxmox.Utils.API2Request({ | |
174 | url: `/nodes/${nodename}/${type}/${vmid}/feature`, | |
175 | params: { feature: 'snapshot' }, | |
176 | method: 'GET', | |
177 | success: function(response, options) { | |
178 | var res = response.result.data; vm.set('snapshotFeature', !!res.hasFeature); } | |
179 | }); | |
180 | }, | |
181 | ||
182 | select: function(grid, val) { | |
183 | let vm = this.getViewModel(); | |
184 | if (val.length < 1) { | |
185 | vm.set('selected', ''); | |
186 | return; | |
187 | } | |
188 | vm.set('selected', val[0].data.name); | |
189 | }, | |
190 | ||
191 | init: function(view) { | |
192 | let me = this; | |
193 | let vm = me.getViewModel(); | |
194 | me.load_task = new Ext.util.DelayedTask(me.reload, me); | |
195 | ||
196 | if (!view.type) { | |
197 | throw 'guest type not set'; | |
198 | } | |
199 | vm.set('type', view.type); | |
200 | ||
201 | if (!view.pveSelNode.data.node) { | |
202 | throw "no node name specified"; | |
203 | } | |
204 | vm.set('nodename', view.pveSelNode.data.node); | |
205 | ||
206 | if (!view.pveSelNode.data.vmid) { | |
207 | throw "no VM ID specified"; | |
208 | } | |
209 | vm.set('vmid', view.pveSelNode.data.vmid); | |
210 | ||
211 | let caps = Ext.state.Manager.get('GuiCap'); | |
212 | vm.set('snapshotAllowed', !!caps.vms['VM.Snapshot']); | |
213 | vm.set('rollbackAllowed', !!caps.vms['VM.Snapshot.Rollback']); | |
214 | ||
215 | view.getStore().sorters.add({ | |
216 | property: 'order', | |
217 | direction: 'ASC', | |
218 | }); | |
219 | ||
220 | me.reload(); | |
221 | }, | |
222 | }, | |
223 | ||
224 | listeners: { | |
225 | selectionchange: 'select', | |
226 | itemdblclick: 'editSnapshot', | |
227 | destroy: 'cancel', | |
228 | }, | |
229 | ||
230 | layout: 'fit', | |
231 | rootVisible: false, | |
232 | animate: false, | |
233 | sortableColumns: false, | |
234 | ||
235 | tbar: [ | |
236 | { | |
237 | xtype: 'proxmoxButton', | |
238 | text: gettext('Take Snapshot'), | |
239 | disabled: true, | |
240 | bind: { | |
241 | disabled: "{!canSnapshot}", | |
242 | }, | |
243 | handler: 'newSnapshot', | |
244 | }, | |
245 | { | |
246 | xtype: 'proxmoxButton', | |
247 | text: gettext('Rollback'), | |
248 | disabled: true, | |
249 | bind: { | |
250 | disabled: '{!canRollback}', | |
251 | }, | |
252 | confirmMsg: function() { | |
253 | let view = this.up('treepanel'); | |
254 | let rec = view.getSelection()[0]; | |
255 | let vmid = view.getViewModel().get('vmid'); | |
256 | return Proxmox.Utils.format_task_description('qmrollback', vmid) + | |
257 | " '" + rec.data.name + "'"; | |
258 | }, | |
259 | handler: 'rollback', | |
260 | }, | |
261 | { | |
262 | xtype: 'proxmoxButton', | |
263 | text: gettext('Remove'), | |
264 | disabled: true, | |
265 | bind: { | |
266 | disabled: '{!canRemove}', | |
267 | }, | |
268 | confirmMsg: function() { | |
269 | let view = this.up('treepanel'); | |
270 | let rec = view.getSelection()[0]; | |
271 | return Ext.String.format( | |
272 | gettext('Are you sure you want to remove entry {0}'), | |
273 | `'${rec.data.name}'` | |
274 | ); | |
275 | }, | |
276 | handler: 'remove', | |
277 | }, | |
278 | { | |
279 | xtype: 'proxmoxButton', | |
280 | text: gettext('Edit'), | |
281 | bind: { | |
282 | text: '{buttonText}', | |
283 | disabled: '{!isSnapshot}', | |
284 | }, | |
285 | disabled: true, | |
286 | edit: true, | |
287 | handler: 'editSnapshot', | |
288 | } | |
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 | }); |