Ext.define('PVE.node.CreateZFS', { extend: 'Proxmox.window.Edit', xtype: 'pveCreateZFS', onlineHelp: 'chapter_zfs', subject: 'ZFS', showProgress: true, isCreate: true, width: 800, viewModel: { data: { raidLevel: 'single', }, formulas: { isDraid: get => get('raidLevel')?.startsWith("draid"), }, }, initComponent: function() { let me = this; if (!me.nodename) { throw "no node name specified"; } Ext.apply(me, { url: `/nodes/${me.nodename}/disks/zfs`, method: 'POST', items: [ { xtype: 'inputpanel', onGetValues: function(values) { if (values.draidData || values.draidSpares) { let opt = { data: values.draidData, spares: values.draidSpares }; values['draid-config'] = PVE.Parser.printPropertyString(opt); } delete values.draidData; delete values.draidSpares; return values; }, column1: [ { xtype: 'proxmoxtextfield', name: 'name', fieldLabel: gettext('Name'), allowBlank: false, maxLength: 128, // ZFS_MAX_DATASET_NAME_LEN is (256 - some edge case) validator: v => { // see zpool_name_valid function in libzfs_zpool.c if (v.match(/^(mirror|raidz|draid|spare)/) || v === 'log') { return gettext('Cannot use reserved pool name'); } else if (!v.match(/^[a-zA-Z][a-zA-Z0-9\-_.]*$/)) { // note: zfs would support also : and whitespace, but we don't return gettext("Invalid characters in pool name"); } return true; }, }, { xtype: 'proxmoxcheckbox', name: 'add_storage', fieldLabel: gettext('Add Storage'), value: '1', }, ], column2: [ { xtype: 'proxmoxKVComboBox', fieldLabel: gettext('RAID Level'), name: 'raidlevel', value: 'single', comboItems: [ ['single', gettext('Single Disk')], ['mirror', 'Mirror'], ['raid10', 'RAID10'], ['raidz', 'RAIDZ'], ['raidz2', 'RAIDZ2'], ['raidz3', 'RAIDZ3'], ['draid', 'dRAID'], ['draid2', 'dRAID2'], ['draid3', 'dRAID3'], ], bind: { value: '{raidLevel}', }, }, { xtype: 'proxmoxKVComboBox', fieldLabel: gettext('Compression'), name: 'compression', value: 'on', comboItems: [ ['on', 'on'], ['off', 'off'], ['gzip', 'gzip'], ['lz4', 'lz4'], ['lzjb', 'lzjb'], ['zle', 'zle'], ['zstd', 'zstd'], ], }, { xtype: 'proxmoxintegerfield', fieldLabel: gettext('ashift'), minValue: 9, maxValue: 16, value: '12', name: 'ashift', }, ], columnB: [ { xtype: 'fieldset', title: gettext('dRAID Config'), collapsible: false, bind: { hidden: '{!isDraid}', }, layout: 'hbox', padding: '5px 10px', defaults: { flex: 1, layout: 'anchor', }, items: [{ xtype: 'proxmoxintegerfield', name: 'draidData', fieldLabel: gettext('Data Devs'), minValue: 1, allowBlank: false, disabled: true, hidden: true, bind: { disabled: '{!isDraid}', hidden: '{!isDraid}', }, padding: '0 10 0 0', }, { xtype: 'proxmoxintegerfield', name: 'draidSpares', fieldLabel: gettext('Spares'), minValue: 0, allowBlank: false, disabled: true, hidden: true, bind: { disabled: '{!isDraid}', hidden: '{!isDraid}', }, padding: '0 0 0 10', }], }, { xtype: 'pmxMultiDiskSelector', name: 'devices', nodename: me.nodename, diskType: 'unused', includePartitions: true, height: 200, emptyText: gettext('No Disks unused'), itemId: 'disklist', }, ], }, { xtype: 'displayfield', padding: '5 0 0 0', userCls: 'pmx-hint', value: 'Note: ZFS is not compatible with disks backed by a hardware ' + 'RAID controller. For details see the reference documentation.', }, ], }); me.callParent(); }, }); Ext.define('PVE.node.ZFSList', { extend: 'Ext.grid.Panel', xtype: 'pveZFSList', viewModel: { data: { pool: '', }, }, controller: { xclass: 'Ext.app.ViewController', destroyPool: function() { let me = this; let vm = me.getViewModel(); let view = me.getView(); const pool = vm.get('pool'); if (!view.nodename) { throw "no node name specified"; } if (!pool) { throw "no pool specified"; } Ext.create('PVE.window.SafeDestroyStorage', { url: `/nodes/${view.nodename}/disks/zfs/${pool}`, item: { id: pool }, taskName: 'zfsremove', taskDone: () => { view.reload(); }, }).show(); }, }, 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() { this.up('panel').reload(); }, }, { text: gettext('Create') + ': ZFS', handler: function() { let view = this.up('panel'); Ext.create('PVE.node.CreateZFS', { nodename: view.nodename, listeners: { destroy: () => view.reload(), }, autoShow: true, }); }, }, { text: gettext('Detail'), itemId: 'detailbtn', disabled: true, handler: function() { let view = this.up('panel'); let selection = view.getSelection(); if (selection.length) { view.show_detail(selection[0].get('name')); } }, }, '->', { xtype: 'tbtext', data: { pool: undefined, }, bind: { data: { pool: "{pool}", }, }, tpl: [ '', 'Pool {pool}:', '', Ext.String.format(gettext('No {0} selected'), 'pool'), '', ], }, { text: gettext('More'), iconCls: 'fa fa-bars', disabled: true, bind: { disabled: '{!pool}', }, menu: [ { text: gettext('Destroy'), itemId: 'remove', iconCls: 'fa fa-fw fa-trash-o', handler: 'destroyPool', disabled: true, bind: { disabled: '{!pool}', }, }, ], }, ], show_detail: function(zpool) { let me = this; Ext.create('Proxmox.window.ZFSDetail', { zpool, nodename: me.nodename, }).show(); }, set_button_status: function() { var me = this; }, reload: function() { var me = this; me.store.load(); me.store.sort(); }, listeners: { activate: function() { this.reload(); }, selectionchange: function(model, selected) { let me = this; let vm = me.getViewModel(); me.down('#detailbtn').setDisabled(selected.length === 0); vm.set('pool', selected[0]?.data.name || ''); }, itemdblclick: function(grid, record) { this.show_detail(record.get('name')); }, }, initComponent: function() { let 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(); }, });