[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