From fee716d32b75d03ba8c4a3b7d8f4d76a1178beef Mon Sep 17 00:00:00 2001 From: Dominik Csapak Date: Tue, 7 Aug 2018 16:51:11 +0200 Subject: [PATCH] add ZFS list/detail/create gui list shows zpools only, with detail the user can see the status of the indiviual vdevs/mirrors Signed-off-by: Dominik Csapak --- www/manager6/Makefile | 1 + www/manager6/Utils.js | 21 ++ www/manager6/node/Config.js | 8 + www/manager6/node/ZFS.js | 421 ++++++++++++++++++++++++++++++++++++ 4 files changed, 451 insertions(+) create mode 100644 www/manager6/node/ZFS.js diff --git a/www/manager6/Makefile b/www/manager6/Makefile index a9790767..68b5227b 100644 --- a/www/manager6/Makefile +++ b/www/manager6/Makefile @@ -102,6 +102,7 @@ JSSRC= \ node/LVM.js \ node/LVMThin.js \ node/Directory.js \ + node/ZFS.js \ node/StatusView.js \ node/Summary.js \ node/Subscription.js \ diff --git a/www/manager6/Utils.js b/www/manager6/Utils.js index 912cd3f7..c2007f6d 100644 --- a/www/manager6/Utils.js +++ b/www/manager6/Utils.js @@ -122,6 +122,27 @@ Ext.define('PVE.Utils', { utilities: { return state; }, + render_zfs_health: function(value) { + var iconCls = 'question-circle'; + switch (value) { + case 'ONLINE': + iconCls = 'check-circle good'; + break; + case 'REMOVED': + case 'DEGRADED': + iconCls = 'exclamation-circle warning'; + break; + case 'UNAVAIL': + case 'FAULTED': + case 'OFFLINE': + iconCls = 'times-circle critical'; + break; + default: //unknown + } + + return ' ' + value; + }, + get_kvm_osinfo: function(value) { var info = { base: 'Other' }; // default if (value) { diff --git a/www/manager6/node/Config.js b/www/manager6/node/Config.js index eacee7c1..14738296 100644 --- a/www/manager6/node/Config.js +++ b/www/manager6/node/Config.js @@ -290,6 +290,14 @@ Ext.define('PVE.node.Config', { groups: ['storage'], xtype: 'pveDirectoryList' }, + { + title: 'ZFS', + itemId: 'zfs', + onlineHelp: 'chapter_zfs', + iconCls: 'fa fa-th-large', + groups: ['storage'], + xtype: 'pveZFSList' + }, { title: 'Ceph', itemId: 'ceph', diff --git a/www/manager6/node/ZFS.js b/www/manager6/node/ZFS.js new file mode 100644 index 00000000..babd8980 --- /dev/null +++ b/www/manager6/node/ZFS.js @@ -0,0 +1,421 @@ +Ext.define('PVE.node.CreateZFS', { + extend: 'Proxmox.window.Edit', + xtype: 'pveCreateZFS', + + subject: 'ZFS', + + showProgress: true, + + onlineHelp: 'chapter_zfs', + + initComponent : function() { + var me = this; + + if (!me.nodename) { + throw "no node name specified"; + } + + me.isCreate = true; + + var update_disklist = function() { + var grid = me.down('#disklist'); + var disks = grid.getSelection(); + + var val = []; + disks.sort(function(a,b) { + var aorder = a.get('order') || 0; + var border = b.get('order') || 0; + return (aorder - border); + }); + + disks.forEach(function(disk) { + val.push(disk.get('devpath')); + }); + + me.down('field[name=devices]').setValue(val.join(',')); + }; + + Ext.apply(me, { + url: '/nodes/' + me.nodename + '/disks/zfs', + method: 'POST', + items: [ + { + xtype: 'inputpanel', + onGetValues: function(values) { + return values; + }, + column1: [ + { + xtype: 'textfield', + hidden: true, + name: 'devices', + allowBlank: false + }, + { + xtype: 'proxmoxtextfield', + name: 'name', + fieldLabel: gettext('Name'), + allowBlank: false + }, + { + xtype: 'proxmoxcheckbox', + name: 'add_storage', + fieldLabel: gettext('Add Storage'), + value: '1' + } + ], + column2: [ + { + xtype: 'proxmoxKVComboBox', + fieldLabel: gettext('RAID Level'), + name: 'raidlevel', + value: 'mirror', + comboItems: [ + ['mirror', 'Mirror'], + ['raid10', 'RAID10'], + ['raidz', 'RAIDZ'], + ['raidz2', 'RAIDZ2'], + ['raidz3', 'RAIDZ3'] + ] + }, + { + xtype: 'proxmoxKVComboBox', + fieldLabel: gettext('Compression'), + name: 'compression', + value: 'on', + comboItems: [ + ['on', 'on'], + ['off', 'off'], + ['gzip', 'gzip'], + ['lz4', 'lz4'], + ['lzjb', 'lzjb'], + ['zle', 'zle'] + ] + }, + { + xtype: 'proxmoxintegerfield', + fieldLabel: gettext('ashift'), + minValue: 9, + maxValue: 16, + value: '12', + name: 'ashift' + } + ], + columnB: [ + { + xtype: 'grid', + height: 200, + emptyText: gettext('No Disks unused'), + itemId: 'disklist', + selModel: 'checkboxmodel', + listeners: { + selectionchange: update_disklist + }, + store: { + proxy: { + type: 'proxmox', + url: '/api2/json/nodes/' + me.nodename + '/disks/list?type=unused' + } + }, + columns: [ + { + text: gettext('Device'), + dataIndex: 'devpath', + flex: 1 + }, + { + text: gettext('Serial'), + dataIndex: 'serial' + }, + { + text: gettext('Size'), + dataIndex: 'size', + renderer: PVE.Utils.render_size + }, + { + header: gettext('Order'), + xtype: 'widgetcolumn', + dataIndex: 'order', + sortable: true, + widget: { + xtype: 'proxmoxintegerfield', + minValue: 1, + isFormField: false, + listeners: { + change: function(numberfield, value, old_value) { + var record = numberfield.getWidgetRecord(); + record.set('order', value); + update_disklist(record); + } + } + } + + } + ] + } + ] + } + ] + }); + + me.callParent(); + me.down('#disklist').getStore().load(); + } +}); + +Ext.define('PVE.node.ZFSStatus', { + extend: 'Ext.tree.Panel', + xtype: 'pveZFSStatus', + stateful: true, + stateId: 'grid-node-zfsstatus', + columns: [ + { + xtype: 'treecolumn', + text: gettext('Name'), + dataIndex: 'name', + flex: 1 + }, + { + text: gettext('Health'), + renderer: PVE.Utils.render_zfs_health, + dataIndex: 'state' + }, + { + text: gettext('Message'), + dataIndex: 'msg' + } + ], + + rootVisible: true, + + tbar: [ + { + text: gettext('Reload'), + iconCls: 'fa fa-refresh', + handler: function() { + var me = this.up('panel'); + me.reload(); + } + } + ], + + reload: function() { + var me = this; + var sm = me.getSelectionModel(); + Proxmox.Utils.API2Request({ + url: "/nodes/" + me.nodename + "/disks/zfs/" + me.zpool, + waitMsgTarget: me, + method: 'GET', + failure: function(response, opts) { + Proxmox.Utils.setErrorMask(me, response.htmlStatus); + }, + success: function(response, opts) { + sm.deselectAll(); + me.setRootNode(response.result.data); + me.expandAll(); + } + }); + }, + + listeners: { + activate: function() { + var me = this; + me.reload(); + } + }, + + initComponent: function() { + /*jslint confusion: true */ + var me = this; + + if (!me.nodename) { + throw "no node name specified"; + } + + if (!me.zpool) { + throw "no zpool specified"; + } + + var sm = Ext.create('Ext.selection.TreeModel', {}); + + Ext.apply(me, { + selModel: sm, + fields: ['name', 'status', + { + type: 'string', + name: 'iconCls', + calculate: function(data) { + var txt = 'fa x-fa-tree fa-'; + if (data.leaf) { + return txt + 'hdd-o'; + } + } + } + ], + sorters: 'name' + }); + + me.callParent(); + + me.reload(); + } +}); + +Ext.define('PVE.node.ZFSList', { + extend: 'Ext.grid.Panel', + xtype: 'pveZFSList', + + stateful: true, + stateId: 'grid-node-zfs', + columns: [ + { + text: gettext('Name'), + dataIndex: 'name', + flex: 1 + }, + { + header: gettext('Size'), + renderer: Proxmox.Utils.format_size, + dataIndex: 'size' + }, + { + header: gettext('Free'), + renderer: Proxmox.Utils.format_size, + dataIndex: 'free' + }, + { + header: gettext('Allocated'), + renderer: Proxmox.Utils.format_size, + dataIndex: 'alloc' + }, + { + header: gettext('Fragmentation'), + renderer: function(value) { + return value.toString() + '%'; + }, + dataIndex: 'frag' + }, + { + header: gettext('Health'), + renderer: PVE.Utils.render_zfs_health, + dataIndex: 'health' + }, + { + header: gettext('Deduplication'), + hidden: true, + renderer: function(value) { + return value.toFixed(2).toString() + 'x'; + }, + dataIndex: 'dedup' + } + ], + + rootVisible: false, + useArrows: true, + + tbar: [ + { + text: gettext('Reload'), + iconCls: 'fa fa-refresh', + handler: function() { + var me = this.up('panel'); + me.reload(); + } + }, + { + text: gettext('Create') + ': ZFS', + handler: function() { + var me = this.up('panel'); + var win = Ext.create('PVE.node.CreateZFS', { + nodename: me.nodename + }).show(); + win.on('destroy', function() { me.reload(); }); + } + }, + { + text: gettext('Detail'), + itemId: 'detailbtn', + disabled: true, + handler: function() { + var me = this.up('panel'); + var selection = me.getSelection(); + if (selection.length < 1) { + return; + } + me.show_detail(selection[0].get('name')); + } + } + ], + + show_detail: function(zpool) { + var me = this; + var win = Ext.create('Ext.window.Window', { + modal: true, + width: 800, + height: 400, + resizable: true, + layout: 'fit', + title: gettext('Status') + ': ' + zpool, + items: [ + { + xtype: 'pveZFSStatus', + nodename: me.nodename, + zpool: zpool + } + ] + }).show(); + }, + + set_button_status: function() { + var me = this; + var selection = me.getSelection(); + me.down('#detailbtn').setDisabled(selection.length === 0); + }, + + reload: function() { + var me = this; + me.store.load(); + me.store.sort(); + }, + + listeners: { + activate: function() { + var me = this; + me.reload(); + }, + selectionchange: function() { + this.set_button_status(); + }, + itemdblclick: function(grid, record) { + var me = this; + me.show_detail(record.get('name')); + } + }, + + initComponent: function() { + /*jslint confusion: true */ + var me = this; + + me.nodename = me.pveSelNode.data.node; + if (!me.nodename) { + throw "no node name specified"; + } + + Ext.apply(me, { + store: { + fields: ['name', 'size', 'free', 'alloc', 'dedup', 'frag', 'health'], + proxy: { + type: 'proxmox', + url: "/api2/json/nodes/" + me.nodename + '/disks/zfs' + }, + sorters: 'name' + } + }); + + me.callParent(); + + Proxmox.Utils.monStoreErrors(me, me.getStore(), true); + me.reload(); + } +}); + -- 2.39.2