]> git.datanom.net - omvzfs.git/blob - gui/js/omv/module/admin/storage/zfs/TreePanel.js
Support for cloned filesystems and volumes.
[omvzfs.git] / gui / js / omv / module / admin / storage / zfs / TreePanel.js
1 /**
2 * This file is part of OpenMediaVault.
3 *
4 * @license http://www.gnu.org/licenses/gpl.html GPL Version 3
5 * @author Volker Theile <volker.theile@openmediavault.org>
6 * @copyright Copyright (c) 2009-2014 Volker Theile
7 *
8 * OpenMediaVault is free software: you can redistribute it and/or modify
9 * it under the terms of the GNU General Public License as published by
10 * the Free Software Foundation, either version 3 of the License, or
11 * any later version.
12 *
13 * OpenMediaVault is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with OpenMediaVault. If not, see <http://www.gnu.org/licenses/>.
20 */
21 // require("js/omv/tree/Panel.js")
22 // require("js/omv/grid/Panel.js")
23 // require("js/omv/grid/column/BinaryUnit.js")
24 // require("js/omv/grid/column/BooleanIcon.js")
25 // require("js/omv/grid/column/BooleanText.js")
26 // require("js/omv/grid/column/Empty.js")
27 // require("js/omv/grid/column/Hyperlink.js")
28 // require("js/omv/grid/column/UnixTimestamp.js")
29 // require("js/omv/grid/column/WhiteSpace.js")
30 // require("js/omv/window/MessageBox.js")
31
32 /**
33 * @ingroup webgui
34 * @class OMV.workspace.grid.Panel
35 * @derived OMV.grid.Panel
36 * An enhanced grid panel. This grid provides 'Add', 'Edit' and 'Delete'
37 * buttons in the toolbar by default. The basic delete functionality is also
38 * implemented, simply overwrite the 'doDeletion' and 'afterDeletion'
39 * functions to implement fit your requirements. To implement the 'Add' and
40 * 'Edit' functionality overwrite the 'onAdd' and 'onEdit' callback
41 * functions. A paging toolbar which is displayed at the bottom of the grid
42 * can be displayed also. It is also possible to reload the grid
43 * automatically in a given interval.
44 * @param hideTopToolbar TRUE to hide the whole toolbar. Defaults to FALSE.
45 * @param hidePagingToolbar TRUE to hide the paging toolbar at the bottom of
46 * the grid. Defaults to TRUE.
47 * @param hideAddButton Hide the 'Add' button in the top toolbar.
48 * Defaults to FALSE.
49 * @param hideEditButton Hide the 'Edit' button in the top toolbar.
50 * Defaults to FALSE.
51 * @param hideDeleteButton Hide the 'Delete' button in the top toolbar.
52 * Defaults to FALSE.
53 * @param hideUpButton Hide the 'Up' button in the top toolbar.
54 * Defaults to TRUE.
55 * @param hideDownButton Hide the 'Down' button in the top toolbar.
56 * Defaults to TRUE.
57 * @param hideApplyButton Hide the 'Apply' button in the top toolbar.
58 * Defaults to TRUE.
59 * @param hideRefreshButton Hide the 'Refresh' button in the top toolbar.
60 * Defaults to TRUE.
61 * @param addButtonText The button text. Defaults to 'Add'.
62 * @param editButtonText The button text. Defaults to 'Edit'.
63 * @param deleteButtonText The button text. Defaults to 'Delete'.
64 * @param upButtonText The button text. Defaults to 'Up'.
65 * @param downButtonText The button text. Defaults to 'Down'.
66 * @param applyButtonText The button text. Defaults to 'Save'.
67 * @param refreshButtonText The button text. Defaults to 'Refresh'.
68 * @param deletionConfirmRequired Set to TRUE to force the user to confirm
69 * the deletion request. Defaults to TRUE.
70 * @param deletionWaitMsg The message displayed during the deletion process.
71 * @param mode The mode how to retrieve the data displayed in the grid panel.
72 * This can be 'local' or 'remote' which means the data is requested via
73 * RPC. Defaults to 'remote'.
74 * @param rememberSelected TRUE to reselect the previous selected rows
75 * after the grid content has been reloaded/refreshed. Defaults to FALSE.
76 */
77 Ext.define("OMV.module.admin.storage.zfs.TreePanel", {
78 extend: "OMV.tree.Panel",
79 requires: [
80 "OMV.window.MessageBox",
81 "OMV.grid.column.BinaryUnit",
82 "OMV.grid.column.BooleanIcon",
83 "OMV.grid.column.BooleanText",
84 "OMV.grid.column.Empty",
85 "OMV.grid.column.Hyperlink",
86 "OMV.grid.column.UnixTimestamp",
87 "OMV.grid.column.WhiteSpace"
88 ],
89
90 border: false,
91 rowLines: false,
92 columnLines: true,
93 selModel: {
94 allowDeselect: true,
95 mode: "SINGLE"
96 },
97
98 hideTopToolbar: false,
99 hidePagingToolbar: true,
100 hideAddButton: false,
101 hideAddObjButton: true,
102 hideEditButton: true,
103 hideDeleteButton: true,
104 hideUpButton: true,
105 hideDownButton: true,
106 hideApplyButton: true,
107 hideRefreshButton: true,
108 hideExpandPoolButton: true,
109 addButtonText: _("Add Pool"),
110 addObjButtonText: _("Add Object"),
111 expandPoolButtonText: _("Expand"),
112 editButtonText: _("Edit"),
113 deleteButtonText: _("Delete"),
114 upButtonText: _("Up"),
115 downButtonText: _("Down"),
116 applyButtonText: _("Save"),
117 refreshButtonText: _("Refresh"),
118 deletionConfirmRequired: true,
119 deletionWaitMsg: _("Deleting selected item(s)"),
120 mode: "remote",
121 rememberSelected: false,
122
123 initComponent: function() {
124 var me = this;
125 // Initialize toolbars.
126 me.dockedItems = [];
127 if(!me.hideTopToolbar) {
128 me.dockedItems.push(me.topToolbar = Ext.widget({
129 xtype: "toolbar",
130 dock: "top",
131 items: me.getTopToolbarItems(me)
132 }));
133 }
134 if(!me.hidePagingToolbar) {
135 me.dockedItems.push({
136 xtype: "toolbar",
137 dock: "bottom",
138 items: [ me.pagingToolbar = Ext.widget({
139 xtype: "pagingtoolbar",
140 store: me.store,
141 displayInfo: true,
142 displayMsg: _("Displaying items {0} - {1} of {2}"),
143 emptyMsg: _("No items to display")
144 }) ]
145 });
146 }
147 me.callParent(arguments);
148 // Register event handler.
149 // Process double clicks in grid.
150 me.on("itemdblclick", me.onItemDblClick, me);
151 // Process selections in grid, e.g. to update the toolbar.
152 var selModel = me.getSelectionModel();
153 selModel.on("selectionchange", me.onSelectionChange, me);
154 // Remember selection to restore it after the grid has been
155 // refreshed.
156 if(me.rememberSelected) {
157 me.getStore().on("beforeload", function() {
158 if(!me.rendered || Ext.isEmpty(me.getEl()))
159 return;
160 if(!selModel.hasSelection())
161 return;
162 me.previousSelected = selModel.getSelection();
163 });
164 me.getView().on("refresh", function(view) {
165 if(Ext.isEmpty(me.previousSelected))
166 return;
167 var select = [];
168 Ext.Array.each(me.previousSelected, function(r) {
169 var record = me.getStore().getById(r.getId());
170 if(!Ext.isEmpty(record))
171 select.push(record);
172 });
173 delete me.previousSelected;
174 if(select.length > 0) {
175 selModel.select(select, false, false);
176 selModel.view.focusNode(select[0]);
177 }
178 });
179 }
180 },
181
182 /**
183 * Returns the items displayed in the top toolbar.
184 * @param c This component object.
185 * @return An array of buttons displayed in the top toolbar.
186 */
187 getTopToolbarItems: function(c) {
188 var me = this;
189 return [{
190 id: me.getId() + "-add",
191 xtype: "button",
192 text: me.addButtonText,
193 icon: "images/zfs_addpool.png",
194 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
195 hidden: me.hideAddButton,
196 handler: Ext.Function.bind(me.onAddButton, me, [ me ]),
197 scope: me
198 },{
199 id: me.getId() + "-edit",
200 xtype: "button",
201 text: me.editButtonText,
202 icon: "images/edit.png",
203 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
204 hidden: me.hideEditButton,
205 handler: Ext.Function.bind(me.onEditButton, me, [ me ]),
206 scope: me,
207 disabled: true
208 },{
209 id: me.getId() + "-addobj",
210 xtype: "button",
211 text: me.addObjButtonText,
212 icon: "images/zfs_addobject.png",
213 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
214 hidden: me.hideAddObjButton,
215 handler: Ext.Function.bind(me.onAddObjButton, me, [ me ]),
216 scope: me,
217 disabled: true
218 },{
219 id: me.getId() + "-expand",
220 xtype: "button",
221 text: me.expandPoolButtonText,
222 icon: "images/zfs_expand.png",
223 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
224 hidden: me.hideExpandPoolButton,
225 handler: Ext.Function.bind(me.onExpandPoolButton, me, [ me ]),
226 scope: me,
227 disabled: true
228 },{
229 id: me.getId() + "-delete",
230 xtype: "button",
231 text: me.deleteButtonText,
232 icon: "images/delete.png",
233 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
234 hidden: me.hideDeleteButton,
235 handler: Ext.Function.bind(me.onDeleteButton, me, [ me ]),
236 scope: me,
237 disabled: true
238 },{
239 id: me.getId() + "-up",
240 xtype: "button",
241 text: me.upButtonText,
242 icon: "images/arrow-up.png",
243 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
244 hidden: me.hideUpButton,
245 handler: Ext.Function.bind(me.onUpButton, me, [ me ]),
246 scope: me,
247 disabled: true
248 },{
249 id: me.getId() + "-down",
250 xtype: "button",
251 text: me.downButtonText,
252 icon: "images/arrow-down.png",
253 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
254 hidden: me.hideDownButton,
255 handler: Ext.Function.bind(me.onDownButton, me, [ me ]),
256 scope: me,
257 disabled: true
258 },{
259 id: me.getId() + "-apply",
260 xtype: "button",
261 text: me.applyButtonText,
262 icon: "images/checkmark.png",
263 hidden: me.hideApplyButton,
264 handler: Ext.Function.bind(me.onApplyButton, me, [ me ]),
265 scope: me
266 },{
267 id: me.getId() + "-refresh",
268 xtype: "button",
269 text: me.refreshButtonText,
270 icon: "images/refresh.png",
271 iconCls: Ext.baseCSSPrefix + "btn-icon-16x16",
272 hidden: me.hideRefreshButton,
273 handler: Ext.Function.bind(me.onRefreshButton, me, [ me ]),
274 scope: me
275 }]
276 },
277
278 /**
279 * Handler that is called whenever the selection in the grid has
280 * been changed. The top toolbar buttons will be enabled/disabled
281 * depending on how much rows has been selected.
282 * @param model The selection model
283 */
284 onSelectionChange: function(model, records) {
285 var me = this;
286 if(me.hideTopToolbar)
287 return;
288 var tbarBtnName = [ "addobj", "edit", "delete", "up", "down", "expand" ];
289 var tbarBtnDisabled = {
290 "addobj": false,
291 "edit": false,
292 "delete": false,
293 "expand": false,
294 "up": true,
295 "down": true,
296 };
297 var tbarBtnHidden = {
298 "addobj": true,
299 "edit": true,
300 "delete": true,
301 "expand": true,
302 "up": true,
303 "down": true
304 };
305 // Enable/disable buttons depending on the number of selected rows.
306 if(records.length <= 0) {
307 tbarBtnDisabled["addobj"] = true;
308 tbarBtnDisabled["edit"] = true;
309 tbarBtnDisabled["delete"] = true;
310 tbarBtnDisabled["up"] = true;
311 tbarBtnDisabled["down"] = true;
312 tbarBtnDisabled["expand"] = true;
313 tbarBtnHidden["addobj"] = true;
314 tbarBtnHidden["edit"] = true;
315 tbarBtnHidden["delete"] = true;
316 tbarBtnHidden["expand"] = true;
317 } else if(records.length == 1) {
318 tbarBtnDisabled["addobj"] = false;
319 tbarBtnDisabled["edit"] = false;
320 tbarBtnDisabled["delete"] = false;
321 tbarBtnDisabled["up"] = false;
322 tbarBtnDisabled["down"] = false;
323 tbarBtnDisabled["expand"] = false;
324 tbarBtnHidden["addobj"] = false;
325 tbarBtnHidden["edit"] = false;
326 tbarBtnHidden["delete"] = false;
327 tbarBtnHidden["expand"] = false;
328 } else {
329 tbarBtnDisabled["addobj"] = true;
330 tbarBtnDisabled["edit"] = true;
331 tbarBtnDisabled["delete"] = false;
332 tbarBtnDisabled["up"] = false;
333 tbarBtnDisabled["down"] = false;
334 tbarBtnDisabled["expand"] = true;
335 tbarBtnHidden["addobj"] = true;
336 tbarBtnHidden["edit"] = true;
337 tbarBtnHidden["delete"] = false;
338 tbarBtnHidden["expand"] = true;
339 }
340 //Disable 'AddObj' button if selected row is a Clone
341 Ext.Array.each(records, function(record) {
342 if("Clone" == record.get("type")) {
343 tbarBtnDisabled["addobj"] = true;
344 tbarBtnHidden["addobj"] = true;
345 return false;
346 }
347 });
348
349 // Disable 'Delete' button if a selected node is not a leaf
350 Ext.Array.each(records, function(record) {
351 if((false == record.get("leaf"))) {
352 tbarBtnDisabled["delete"] = true;
353 tbarBtnHidden["delete"] = true;
354 return false;
355 }
356 });
357
358 //Disable 'ExpandPool' button if selected row is not a Pool
359 Ext.Array.each(records, function(record) {
360 if(!("Pool" == record.get("type"))) {
361 tbarBtnDisabled["expand"] = true;
362 tbarBtnHidden["expand"] = true;
363 return false;
364 }
365 });
366
367 // Update the button controls.
368 Ext.Array.each(tbarBtnName, function(name) {
369 var tbarBtnCtrl = me.queryById(me.getId() + "-" + name);
370 if(!Ext.isEmpty(tbarBtnCtrl)) {
371 if(true == tbarBtnDisabled[name]) {
372 tbarBtnCtrl.disable();
373 } else {
374 tbarBtnCtrl.enable();
375 }
376 if(true == tbarBtnHidden[name]) {
377 tbarBtnCtrl.hide();
378 } else {
379 tbarBtnCtrl.show();
380 }
381 }
382 });
383 },
384
385 onItemDblClick: function() {
386 var me = this;
387 if(!me.hideTopToolbar && !me.hideEditButton) {
388 me.onEditButton(me);
389 }
390 },
391
392 /**
393 * Load the grid content.
394 */
395 doLoad: function() {
396 var me = this;
397 if(me.mode === "remote") {
398 me.store.load();
399 }
400 },
401
402 /**
403 * Reload the grid content.
404 */
405 doReload: function() {
406 var me = this;
407 if(me.mode === "remote") {
408 me.store.reload();
409 }
410 },
411
412 /**
413 * Handler that is called when the 'Add' button in the top toolbar
414 * is pressed. Override this method to customize the default behaviour.
415 * @param this The grid itself.
416 */
417 onAddButton: function() {
418 // Nothing to do here
419 },
420
421 /**
422 * * Handler that is called when the 'AddObj' button in the top toolbar
423 * is pressed. Override this method to customize the default behaviour.
424 * @param this The grid itself.
425 */
426 onAddObjButton: function() {
427 // Nothing to do here
428 },
429
430
431 /**
432 * Handler that is called when the 'Edit' button in the top toolbar
433 * is pressed. Override this method to customize the default behaviour.
434 * @param this The grid itself.
435 */
436 onEditButton: function() {
437 // Nothing to do here
438 },
439
440 /**
441 * Handler that is called when the 'Up' button in the top toolbar
442 * is pressed. Override this method to customize the default behaviour.
443 * @param this The grid itself.
444 */
445 onUpButton: function() {
446 var me = this;
447 var sm = me.getSelectionModel();
448 var records = sm.getSelection();
449 if(records.length > 0) {
450 // Find the smallest index of the selected rows.
451 var ltIndex = me.store.indexOf(records[0]);
452 Ext.Array.each(records, function(record) {
453 var index = me.store.indexOf(record);
454 if(ltIndex > index)
455 ltIndex = index;
456 });
457 // Calculate the index where to insert the rows.
458 var index = ltIndex - 1;
459 if(index < 0)
460 index = 0;
461 me.doMoveRows(records, index);
462 }
463 },
464
465 /**
466 * Handler that is called when the 'Down' button in the top toolbar
467 * is pressed.
468 * @param this The grid itself.
469 */
470 onDownButton: function() {
471 var me = this;
472 var sm = me.getSelectionModel();
473 var records = sm.getSelection();
474 if(records.length > 0) {
475 // Find the smallest index of the selected rows.
476 var ltIndex = me.store.indexOf(records[0]);
477 Ext.Array.each(records, function(record) {
478 var index = me.store.indexOf(record);
479 if(ltIndex > index)
480 ltIndex = index;
481 });
482 // Calculate the index where to insert the rows.
483 var index = ltIndex + records.length;
484 var count = me.store.getCount() - 1;
485 if(index > count)
486 index = count;
487 me.doMoveRows(records, index);
488 }
489 },
490
491 /**
492 * Handler that is called when the 'Apply' button in the top toolbar
493 * is pressed. Override this method to customize the default behaviour.
494 * @param this The grid itself.
495 */
496 onApplyButton: function() {
497 // Nothing to do here
498 },
499
500 /**
501 * Handler that is called when the 'Refresh' button in the top toolbar
502 * is pressed. Override this method to customize the default behaviour.
503 * @param this The grid itself.
504 */
505 onRefreshButton: function() {
506 this.doReload();
507 },
508
509 /**
510 * Move the given rows to the given index.
511 * @param records The records to move.
512 * @param index The index where to insert the rows to be moved.
513 */
514 doMoveRows: function(records, index) {
515 var me = this;
516 if(!Ext.isNumber(index))
517 return;
518 records = Ext.Array.from(records);
519 me.store.suspendEvents();
520 Ext.Array.each(records, function(record) {
521 me.store.remove(record);
522 me.store.insert(index, record);
523 });
524 me.store.resumeEvents();
525 me.afterMoveRows(records, index);
526 me.getView().refresh();
527 },
528
529 /**
530 * Function that is called after the selected rows have been moved.
531 * Override this method to customize the default behaviour.
532 * @param records The records that have been move.
533 * @param index The index where the rows have been inserted.
534 */
535 afterMoveRows: function(records, index) {
536 var sm = this.getSelectionModel();
537 sm.select(records);
538 },
539
540 /**
541 * Handler that is called when the 'Delete' button in the top toolbar
542 * is pressed.
543 */
544 onDeleteButton: function() {
545 var me = this;
546 var sm = me.getSelectionModel();
547 var records = sm.getSelection();
548 if(me.deletionConfirmRequired === true) {
549 var msg = _("Do you really want to delete the selected item(s)?");
550 OMV.MessageBox.show({
551 title: _("Confirmation"),
552 msg: msg,
553 buttons: Ext.Msg.YESNO,
554 fn: function(answer) {
555 if(answer !== "yes")
556 return;
557 me.startDeletion(records);
558 },
559 scope: me,
560 icon: Ext.Msg.QUESTION
561 });
562 } else {
563 me.startDeletion(records);
564 }
565 },
566
567 /**
568 * @private
569 * Private method that is called when the deletion of the selected records
570 * has been aggreed.
571 * @param records The records to delete.
572 */
573 startDeletion: function(records) {
574 var me = this;
575 if(records.length <= 0)
576 return;
577 // Store selected records in a local variable
578 me.delActionInfo = {
579 records: records,
580 count: records.length
581 }
582 // Get first record to be deleted
583 var record = me.delActionInfo.records.pop();
584 // Display progress dialog
585 OMV.MessageBox.progress("", me.deletionWaitMsg, "");
586 me.updateDeletionProgress();
587 // Execute deletion function
588 me.doDeletion(record);
589 },
590
591 /**
592 * The method that is called to delete a selected record. Override this
593 * method to customize the default behaviour. This is necessary in
594 * 'remote' mode.
595 */
596 doDeletion: function(record) {
597 var me = this;
598 if(me.mode === "local") {
599 // Remove record from store
600 me.store.remove(record);
601 // Continue deletion process
602 me.onDeletion(null, true, null);
603 }
604 },
605
606 /**
607 * The method that is called by the 'doDeletion' method. The progress
608 * bar will be updated and the deletion progress will be continued if
609 * there are still records to delete.
610 */
611 onDeletion: function(id, success, response) {
612 var me = this;
613 if(!success) {
614 // Remove temporary local variables
615 delete me.delActionInfo;
616 // Hide progress dialog
617 OMV.MessageBox.hide();
618 // Display error message
619 OMV.MessageBox.error(null, response);
620 } else {
621 if(me.delActionInfo.records.length > 0) {
622 var record = me.delActionInfo.records.pop();
623 // Update progress dialog
624 me.updateDeletionProgress();
625 // Execute deletion function
626 me.doDeletion(record);
627 } else {
628 // Remove temporary local variables
629 delete me.delActionInfo;
630 // Update and hide progress dialog
631 OMV.MessageBox.updateProgress(1, _("100% completed ..."));
632 OMV.MessageBox.hide();
633 me.afterDeletion();
634 }
635 }
636 },
637
638 /**
639 * Function that is called after the deletion has been successful finished.
640 */
641 afterDeletion: function() {
642 var me = this;
643 if(me.mode === "remote") {
644 me.doReload();
645 }
646 },
647
648 /**
649 * @private
650 * Private helper function to update the progress dialog.
651 */
652 updateDeletionProgress: function() {
653 var me = this;
654 // Calculate percentage
655 var p = (me.delActionInfo.count - me.delActionInfo.records.length) /
656 me.delActionInfo.count;
657 // Create message text
658 var text = Math.round(100 * p) + _("% completed ...");
659 // Update progress dialog
660 OMV.MessageBox.updateProgress(p, text);
661 },
662
663 /**
664 * Convenience function for setting the given toolbar button
665 * disabled/enabled.
666 * @param name The name of the toolbar button.
667 * @param disabled TRUE to disable the button, FALSE to enable.
668 * @return The button component, otherwise FALSE.
669 */
670 setToolbarButtonDisabled: function(name, disabled) {
671 var me = this;
672 var result = false;
673 var btnCtrl = me.queryById(me.getId() + "-" + name);
674 if(!Ext.isEmpty(btnCtrl) && btnCtrl.isButton)
675 result = btnCtrl.setDisabled(disabled);
676 return result;
677 },
678
679 /**
680 * Helper function to get the top toolbar object.
681 * @return The paging toolbar object or NULL.
682 */
683 getTopToolbar: function() {
684 return this.topToolbar;
685 },
686
687 /**
688 * Helper function to get the paging toolbar object.
689 * @return The paging toolbar object or NULL.
690 */
691 getPagingToolbar: function() {
692 return this.pagingToolbar;
693 }
694 });
695
696
697
This page took 0.161549 seconds and 6 git commands to generate.