[pve-devel] [PATCH manager 2/2] add ZFS list/detail/create gui

Dominik Csapak d.csapak at proxmox.com
Tue Aug 7 16:51:11 CEST 2018


list shows zpools only, with detail the user can see
the status of the indiviual vdevs/mirrors

Signed-off-by: Dominik Csapak <d.csapak at proxmox.com>
---
 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 '<i class="fa fa-' + iconCls + '"></i> ' + 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
@@ -291,6 +291,14 @@ Ext.define('PVE.node.Config', {
 		    xtype: 'pveDirectoryList'
 		},
 		{
+		    title: 'ZFS',
+		    itemId: 'zfs',
+		    onlineHelp: 'chapter_zfs',
+		    iconCls: 'fa fa-th-large',
+		    groups: ['storage'],
+		    xtype: 'pveZFSList'
+		},
+		{
 		    title: 'Ceph',
 		    itemId: 'ceph',
 		    iconCls: 'fa fa-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.11.0





More information about the pve-devel mailing list