]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/storage/ContentView.js
drop jslint lines
[pve-manager.git] / www / manager6 / storage / ContentView.js
1 Ext.define('PVE.grid.TemplateSelector', {
2 extend: 'Ext.grid.GridPanel',
3
4 alias: 'widget.pveTemplateSelector',
5
6 stateful: true,
7 stateId: 'grid-template-selector',
8 viewConfig: {
9 trackOver: false
10 },
11 initComponent : function() {
12 var me = this;
13
14 if (!me.nodename) {
15 throw "no node name specified";
16 }
17
18 var baseurl = "/nodes/" + me.nodename + "/aplinfo";
19 var store = new Ext.data.Store({
20 model: 'pve-aplinfo',
21 groupField: 'section',
22 proxy: {
23 type: 'proxmox',
24 url: '/api2/json' + baseurl
25 }
26 });
27
28 var sm = Ext.create('Ext.selection.RowModel', {});
29
30 var groupingFeature = Ext.create('Ext.grid.feature.Grouping',{
31 groupHeaderTpl: '{[ "Section: " + values.name ]} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
32 });
33
34 var reload = function() {
35 store.load();
36 };
37
38 Proxmox.Utils.monStoreErrors(me, store);
39
40 Ext.apply(me, {
41 store: store,
42 selModel: sm,
43 tbar: [
44 '->',
45 gettext('Search'),
46 {
47 xtype: 'textfield',
48 width: 200,
49 enableKeyEvents: true,
50 listeners: {
51 buffer: 500,
52 keyup: function(field) {
53 var value = field.getValue().toLowerCase();
54 store.clearFilter(true);
55 store.filterBy(function(rec) {
56 return (rec.data['package'].toLowerCase().indexOf(value) !== -1)
57 || (rec.data.headline.toLowerCase().indexOf(value) !== -1);
58 });
59 }
60 }
61 }
62 ],
63 features: [ groupingFeature ],
64 columns: [
65 {
66 header: gettext('Type'),
67 width: 80,
68 dataIndex: 'type'
69 },
70 {
71 header: gettext('Package'),
72 flex: 1,
73 dataIndex: 'package'
74 },
75 {
76 header: gettext('Version'),
77 width: 80,
78 dataIndex: 'version'
79 },
80 {
81 header: gettext('Description'),
82 flex: 1.5,
83 renderer: Ext.String.htmlEncode,
84 dataIndex: 'headline'
85 }
86 ],
87 listeners: {
88 afterRender: reload
89 }
90 });
91
92 me.callParent();
93 }
94
95 }, function() {
96
97 Ext.define('pve-aplinfo', {
98 extend: 'Ext.data.Model',
99 fields: [
100 'template', 'type', 'package', 'version', 'headline', 'infopage',
101 'description', 'os', 'section'
102 ],
103 idProperty: 'template'
104 });
105
106 });
107
108 Ext.define('PVE.storage.TemplateDownload', {
109 extend: 'Ext.window.Window',
110 alias: 'widget.pveTemplateDownload',
111
112 modal: true,
113 title: gettext('Templates'),
114 layout: 'fit',
115 width: 900,
116 height: 600,
117 initComponent : function() {
118 var me = this;
119
120 var grid = Ext.create('PVE.grid.TemplateSelector', {
121 border: false,
122 scrollable: true,
123 nodename: me.nodename
124 });
125
126 var sm = grid.getSelectionModel();
127
128 var submitBtn = Ext.create('Proxmox.button.Button', {
129 text: gettext('Download'),
130 disabled: true,
131 selModel: sm,
132 handler: function(button, event, rec) {
133 Proxmox.Utils.API2Request({
134 url: '/nodes/' + me.nodename + '/aplinfo',
135 params: {
136 storage: me.storage,
137 template: rec.data.template
138 },
139 method: 'POST',
140 failure: function (response, opts) {
141 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
142 },
143 success: function(response, options) {
144 var upid = response.result.data;
145
146 Ext.create('Proxmox.window.TaskViewer', {
147 upid: upid,
148 listeners: {
149 destroy: me.reloadGrid
150 }
151 }).show();
152
153 me.close();
154 }
155 });
156 }
157 });
158
159 Ext.apply(me, {
160 items: grid,
161 buttons: [ submitBtn ]
162 });
163
164 me.callParent();
165 }
166 });
167
168 Ext.define('PVE.storage.Upload', {
169 extend: 'Ext.window.Window',
170 alias: 'widget.pveStorageUpload',
171
172 resizable: false,
173
174 modal: true,
175
176 initComponent : function() {
177 var me = this;
178
179 var xhr;
180
181 if (!me.nodename) {
182 throw "no node name specified";
183 }
184
185 if (!me.storage) {
186 throw "no storage ID specified";
187 }
188
189 var baseurl = "/nodes/" + me.nodename + "/storage/" + me.storage + "/upload";
190
191 var pbar = Ext.create('Ext.ProgressBar', {
192 text: 'Ready',
193 hidden: true
194 });
195
196 me.formPanel = Ext.create('Ext.form.Panel', {
197 method: 'POST',
198 waitMsgTarget: true,
199 bodyPadding: 10,
200 border: false,
201 width: 300,
202 fieldDefaults: {
203 labelWidth: 100,
204 anchor: '100%'
205 },
206 items: [
207 {
208 xtype: 'pveContentTypeSelector',
209 cts: me.contents,
210 fieldLabel: gettext('Content'),
211 name: 'content',
212 value: me.contents[0] || '',
213 allowBlank: false
214 },
215 {
216 xtype: 'filefield',
217 name: 'filename',
218 buttonText: gettext('Select File...'),
219 allowBlank: false,
220 listeners: {
221 afterrender: function(cmp) {
222 cmp.fileInputEl.set({
223 accept: '.img, .iso'
224 });
225 }
226 }
227 },
228 pbar
229 ]
230 });
231
232 var form = me.formPanel.getForm();
233
234 var doStandardSubmit = function() {
235 form.submit({
236 url: "/api2/htmljs" + baseurl,
237 waitMsg: gettext('Uploading file...'),
238 success: function(f, action) {
239 me.close();
240 },
241 failure: function(f, action) {
242 var msg = PVE.Utils.extractFormActionError(action);
243 Ext.Msg.alert(gettext('Error'), msg);
244 }
245 });
246 };
247
248 var updateProgress = function(per, bytes) {
249 var text = (per * 100).toFixed(2) + '%';
250 if (bytes) {
251 text += " (" + Proxmox.Utils.format_size(bytes) + ')';
252 }
253 pbar.updateProgress(per, text);
254 };
255
256 var abortBtn = Ext.create('Ext.Button', {
257 text: gettext('Abort'),
258 disabled: true,
259 handler: function() {
260 me.close();
261 }
262 });
263
264 var submitBtn = Ext.create('Ext.Button', {
265 text: gettext('Upload'),
266 disabled: true,
267 handler: function(button) {
268 var fd;
269 try {
270 fd = new FormData();
271 } catch (err) {
272 doStandardSubmit();
273 return;
274 }
275
276 button.setDisabled(true);
277 abortBtn.setDisabled(false);
278
279 var field = form.findField('content');
280 fd.append("content", field.getValue());
281 field.setDisabled(true);
282
283 field = form.findField('filename');
284 var file = field.fileInputEl.dom;
285 fd.append("filename", file.files[0]);
286 field.setDisabled(true);
287
288 pbar.setVisible(true);
289 updateProgress(0);
290
291 xhr = new XMLHttpRequest();
292
293 xhr.addEventListener("load", function(e) {
294 if (xhr.status == 200) {
295 me.close();
296 } else {
297 var msg = gettext('Error') + " " + xhr.status.toString() + ": " + Ext.htmlEncode(xhr.statusText);
298 if (xhr.responseText !== "") {
299 var result = Ext.decode(xhr.responseText);
300 result.message = msg;
301 msg = Proxmox.Utils.extractRequestError(result, true);
302 }
303 Ext.Msg.alert(gettext('Error'), msg, function(btn) {
304 me.close();
305 });
306 }
307 }, false);
308
309 xhr.addEventListener("error", function(e) {
310 var msg = "Error " + e.target.status.toString() + " occurred while receiving the document.";
311 Ext.Msg.alert(gettext('Error'), msg, function(btn) {
312 me.close();
313 });
314 });
315
316 xhr.upload.addEventListener("progress", function(evt) {
317 if (evt.lengthComputable) {
318 var percentComplete = evt.loaded / evt.total;
319 updateProgress(percentComplete, evt.loaded);
320 }
321 }, false);
322
323 xhr.open("POST", "/api2/json" + baseurl, true);
324 xhr.send(fd);
325 }
326 });
327
328 form.on('validitychange', function(f, valid) {
329 submitBtn.setDisabled(!valid);
330 });
331
332 Ext.apply(me, {
333 title: gettext('Upload'),
334 items: me.formPanel,
335 buttons: [ abortBtn, submitBtn ],
336 listeners: {
337 close: function() {
338 if (xhr) {
339 xhr.abort();
340 }
341 }
342 }
343 });
344
345 me.callParent();
346 }
347 });
348
349 Ext.define('PVE.storage.ContentView', {
350 extend: 'Ext.grid.GridPanel',
351
352 alias: 'widget.pveStorageContentView',
353
354 stateful: true,
355 stateId: 'grid-storage-content',
356 viewConfig: {
357 trackOver: false,
358 loadMask: false
359 },
360 features: [
361 {
362 ftype: 'grouping',
363 groupHeaderTpl: '{name} ({rows.length} Item{[values.rows.length > 1 ? "s" : ""]})'
364 }
365 ],
366 initComponent : function() {
367 var me = this;
368
369 var nodename = me.pveSelNode.data.node;
370 if (!nodename) {
371 throw "no node name specified";
372 }
373
374 var storage = me.pveSelNode.data.storage;
375 if (!storage) {
376 throw "no storage ID specified";
377 }
378
379 var baseurl = "/nodes/" + nodename + "/storage/" + storage + "/content";
380 var store = Ext.create('Ext.data.Store',{
381 model: 'pve-storage-content',
382 groupField: 'content',
383 proxy: {
384 type: 'proxmox',
385 url: '/api2/json' + baseurl
386 },
387 sorters: {
388 property: 'volid',
389 order: 'DESC'
390 }
391 });
392
393 var sm = Ext.create('Ext.selection.RowModel', {});
394
395 var reload = function() {
396 store.load();
397 me.statusStore.load();
398 };
399
400 Proxmox.Utils.monStoreErrors(me, store);
401
402 var templateButton = Ext.create('Proxmox.button.Button',{
403 itemId: 'tmpl-btn',
404 text: gettext('Templates'),
405 handler: function() {
406 var win = Ext.create('PVE.storage.TemplateDownload', {
407 nodename: nodename,
408 storage: storage,
409 reloadGrid: reload
410 });
411 win.show();
412 }
413 });
414
415 var uploadButton = Ext.create('Proxmox.button.Button', {
416 contents : ['iso','vztmpl'],
417 text: gettext('Upload'),
418 handler: function() {
419 var me = this;
420 var win = Ext.create('PVE.storage.Upload', {
421 nodename: nodename,
422 storage: storage,
423 contents: me.contents
424 });
425 win.show();
426 win.on('destroy', reload);
427 }
428 });
429
430 var imageRemoveButton;
431 var removeButton = Ext.create('Proxmox.button.StdRemoveButton',{
432 selModel: sm,
433 delay: 5,
434 enableFn: function(rec) {
435 if (rec && rec.data.content !== 'images') {
436 imageRemoveButton.setVisible(false);
437 removeButton.setVisible(true);
438 return true;
439 }
440 return false;
441 },
442 callback: function() {
443 reload();
444 },
445 baseurl: baseurl + '/'
446 });
447
448 imageRemoveButton = Ext.create('Proxmox.button.Button',{
449 selModel: sm,
450 hidden: true,
451 text: gettext('Remove'),
452 enableFn: function(rec) {
453 if (rec && rec.data.content === 'images') {
454 removeButton.setVisible(false);
455 imageRemoveButton.setVisible(true);
456 return true;
457 }
458 return false;
459 },
460 handler: function(btn, event, rec) {
461 me = this;
462
463 var url = baseurl + '/' + rec.data.volid;
464 var vmid = rec.data.vmid;
465
466 var store = PVE.data.ResourceStore;
467
468 if (vmid && store.findVMID(vmid)) {
469 var guest_node = store.guestNode(vmid);
470 var storage_path = 'storage/' + nodename + '/' + storage;
471
472 // allow to delete local backed images if a VMID exists on another node.
473 if (store.storageIsShared(storage_path) || guest_node == nodename) {
474 var msg = Ext.String.format(
475 gettext("Cannot remove image, a guest with VMID '{0}' exists!"), vmid);
476 msg += '<br />' + gettext("You can delete the image from the guest's hardware pane");
477
478 Ext.Msg.show({
479 title: gettext('Cannot remove disk image.'),
480 icon: Ext.Msg.ERROR,
481 msg: msg
482 });
483 return;
484 }
485 }
486 var win = Ext.create('PVE.window.SafeDestroy', {
487 title: Ext.String.format(gettext("Destroy '{0}'"), rec.data.volid),
488 showProgress: true,
489 url: url,
490 item: { type: 'Image', id: vmid }
491 }).show();
492 win.on('destroy', function() {
493 me.statusStore = Ext.create('Proxmox.data.ObjectStore', {
494 url: '/api2/json/nodes/' + nodename + '/storage/' + storage + '/status'
495 });
496 reload();
497
498 });
499 }
500 });
501
502 me.statusStore = Ext.create('Proxmox.data.ObjectStore', {
503 url: '/api2/json/nodes/' + nodename + '/storage/' + storage + '/status'
504 });
505
506 Ext.apply(me, {
507 store: store,
508 selModel: sm,
509 tbar: [
510 {
511 xtype: 'proxmoxButton',
512 text: gettext('Restore'),
513 selModel: sm,
514 disabled: true,
515 enableFn: function(rec) {
516 return rec && rec.data.content === 'backup';
517 },
518 handler: function(b, e, rec) {
519 var vmtype;
520 if (PVE.Utils.volume_is_qemu_backup(rec.data.volid, rec.data.format)) {
521 vmtype = 'qemu';
522 } else if (PVE.Utils.volume_is_lxc_backup(rec.data.volid, rec.data.format)) {
523 vmtype = 'lxc';
524 } else {
525 return;
526 }
527
528 var win = Ext.create('PVE.window.Restore', {
529 nodename: nodename,
530 volid: rec.data.volid,
531 volidText: PVE.Utils.render_storage_content(rec.data.volid, {}, rec),
532 vmtype: vmtype
533 });
534 win.show();
535 win.on('destroy', reload);
536 }
537 },
538 removeButton,
539 imageRemoveButton,
540 templateButton,
541 uploadButton,
542 {
543 xtype: 'proxmoxButton',
544 text: gettext('Show Configuration'),
545 disabled: true,
546 selModel: sm,
547 enableFn: function(rec) {
548 return rec && rec.data.content === 'backup';
549 },
550 handler: function(b,e,rec) {
551 var win = Ext.create('PVE.window.BackupConfig', {
552 volume: rec.data.volid,
553 pveSelNode: me.pveSelNode
554 });
555
556 win.show();
557 }
558 },
559 '->',
560 gettext('Search') + ':', ' ',
561 {
562 xtype: 'textfield',
563 width: 200,
564 enableKeyEvents: true,
565 listeners: {
566 buffer: 500,
567 keyup: function(field) {
568 store.clearFilter(true);
569 store.filter([
570 {
571 property: 'text',
572 value: field.getValue(),
573 anyMatch: true,
574 caseSensitive: false
575 }
576 ]);
577 }
578 }
579 }
580 ],
581 columns: [
582 {
583 header: gettext('Name'),
584 flex: 1,
585 sortable: true,
586 renderer: PVE.Utils.render_storage_content,
587 dataIndex: 'text'
588 },
589 {
590 header: gettext('Date'),
591 width: 150,
592 dataIndex: 'vdate'
593 },
594 {
595 header: gettext('Format'),
596 width: 100,
597 dataIndex: 'format'
598 },
599 {
600 header: gettext('Type'),
601 width: 100,
602 dataIndex: 'content',
603 renderer: PVE.Utils.format_content_types
604 },
605 {
606 header: gettext('Size'),
607 width: 100,
608 renderer: Proxmox.Utils.format_size,
609 dataIndex: 'size'
610 }
611 ],
612 listeners: {
613 activate: reload
614 }
615 });
616
617 me.callParent();
618
619 // disable the buttons/restrict the upload window
620 // if templates or uploads are not allowed
621 me.mon(me.statusStore, 'load', function(s, records, success) {
622 var availcontent = [];
623 Ext.Array.each(records, function(item){
624 if (item.id === 'content') {
625 availcontent = item.data.value.split(',');
626 }
627 });
628 var templ = false;
629 var upload = false;
630 var cts = [];
631
632 Ext.Array.each(availcontent, function(content) {
633 if (content === 'vztmpl') {
634 templ = true;
635 cts.push('vztmpl');
636 } else if (content === 'iso') {
637 upload = true;
638 cts.push('iso');
639 }
640 });
641
642 if (templ !== upload) {
643 uploadButton.contents = cts;
644 }
645
646 templateButton.setDisabled(!templ);
647 uploadButton.setDisabled(!upload && !templ);
648 });
649 }
650 }, function() {
651
652 Ext.define('pve-storage-content', {
653 extend: 'Ext.data.Model',
654 fields: [
655 'volid', 'content', 'format', 'size', 'used', 'vmid',
656 'channel', 'id', 'lun',
657 {
658 name: 'text',
659 convert: function(value, record) {
660 // check for volid, because if you click on a grouping header,
661 // it calls convert (but with an empty volid)
662 if (value || record.data.volid === null) {
663 return value;
664 }
665 return PVE.Utils.render_storage_content(value, {}, record);
666 }
667 },
668 {
669 name: 'vdate',
670 convert: function(value, record) {
671 // check for volid, because if you click on a grouping header,
672 // it calls convert (but with an empty volid)
673 if (value || record.data.volid === null) {
674 return value;
675 }
676 let t = record.data.content;
677 if (t === "backup") {
678 let v = record.data.volid;
679 let match = v.match(/(\d{4}_\d{2}_\d{2})-(\d{2}_\d{2}_\d{2})/);
680 if (match) {
681 let date = match[1].replace(/_/g, '-');
682 let time = match[2].replace(/_/g, ':');
683 return date + " " + time;
684 }
685 }
686 if (record.data.ctime) {
687 let ctime = new Date(record.data.ctime * 1000);
688 return Ext.Date.format(ctime,'Y-m-d H:i:s');
689 }
690 return '';
691 }
692 },
693 ],
694 idProperty: 'volid'
695 });
696
697 });