onlineHelp: 'pve_ceph_pools',
subject: 'Ceph Pool',
+
+ defaultSize: undefined,
+ defaultMinSize: undefined,
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ init: function(view) {
+ let vm = this.getViewModel();
+ vm.set('size', Number(view.defaultSize));
+ vm.set('minSize', Number(view.defaultMinSize));
+ },
+ sizeChange: function(field, val) {
+ let vm = this.getViewModel();
+ let minSize = Math.round(val / 2);
+ if (minSize > 1) {
+ vm.set('minSize', minSize);
+ }
+ vm.set('size', val); // bind does not work in a pmxDisplayEditField, update manually
+ },
+ },
+
+ viewModel: {
+ data: {
+ minSize: null,
+ size: null,
+ },
+ formulas: {
+ minSizeLabel: (get) => {
+ if (get('showMinSizeOneWarning') || get('showMinSizeHalfWarning')) {
+ return `${gettext('Min. Size')} <i class="fa fa-exclamation-triangle warning"></i>`;
+ }
+ return gettext('Min. Size');
+ },
+ showMinSizeOneWarning: (get) => get('minSize') === 1,
+ showMinSizeHalfWarning: (get) => {
+ let minSize = get('minSize');
+ let size = get('size');
+ if (minSize === 1) {
+ return false;
+ }
+ return minSize < (size / 2) && minSize !== size;
+ },
+ },
+ },
+
column1: [
{
xtype: 'pmxDisplayEditField',
cbind: {
editable: '{isCreate}',
value: '{pool_name}',
- disabled: '{!isCreate}',
},
name: 'name',
allowBlank: false,
},
{
- xtype: 'proxmoxintegerfield',
+ xtype: 'pmxDisplayEditField',
+ cbind: {
+ editable: '{!isErasure}',
+ },
fieldLabel: gettext('Size'),
name: 'size',
- value: 3,
- minValue: 2,
- maxValue: 7,
- allowBlank: false,
- listeners: {
- change: function(field, val) {
- let size = Math.round(val / 2);
- if (size > 1) {
- field.up('inputpanel').down('field[name=min_size]').setValue(size);
- }
+ editConfig: {
+ xtype: 'proxmoxintegerfield',
+ cbind: {
+ value: (get) => get('defaultSize'),
+ },
+ minValue: 2,
+ maxValue: 7,
+ allowBlank: false,
+ listeners: {
+ change: 'sizeChange',
},
},
},
['on', 'on'],
['off', 'off'],
],
- value: 'warn',
+ value: 'on', // FIXME: check ceph version and only default to on on octopus and newer
allowBlank: false,
autoSelect: false,
labelWidth: 140,
advancedColumn1: [
{
xtype: 'proxmoxintegerfield',
- fieldLabel: gettext('Min. Size'),
+ bind: {
+ fieldLabel: '{minSizeLabel}',
+ value: '{minSize}',
+ },
name: 'min_size',
- value: 2,
cbind: {
- minValue: (get) => get('isCreate') ? 2 : 1,
- },
- maxValue: 7,
- allowBlank: false,
- listeners: {
- change: function(field, val) {
- let warn = true;
- let warn_text = gettext('Min. Size');
-
- if (val < 2) {
- warn = false;
- warn_text = gettext('Min. Size') + ' <i class="fa fa-exclamation-triangle warning"></i>';
+ value: (get) => get('defaultMinSize'),
+ minValue: (get) => {
+ if (Number(get('defaultMinSize')) === 1) {
+ return 1;
+ } else {
+ return get('isCreate') ? 2 : 1;
}
-
- field.up().down('field[name=min_size-warning]').setHidden(warn);
- field.setFieldLabel(warn_text);
},
},
+ maxValue: 7,
+ allowBlank: false,
},
{
xtype: 'displayfield',
- name: 'min_size-warning',
+ bind: {
+ hidden: '{!showMinSizeHalfWarning}',
+ },
+ hidden: true,
userCls: 'pmx-hint',
- value: 'A pool with min_size=1 could lead to data loss, incomplete PGs or unfound objects.',
+ value: gettext('min_size < size/2 can lead to data loss, incomplete PGs or unfound objects.'),
+ },
+ {
+ xtype: 'displayfield',
+ bind: {
+ hidden: '{!showMinSizeOneWarning}',
+ },
hidden: true,
+ userCls: 'pmx-hint',
+ value: gettext('a min_size of 1 is not recommended and can lead to data loss'),
},
{
- xtype: 'pveCephRuleSelector',
+ xtype: 'pmxDisplayEditField',
+ cbind: {
+ editable: '{!isErasure}',
+ nodename: '{nodename}',
+ isCreate: '{isCreate}',
+ },
fieldLabel: 'Crush Rule', // do not localize
- cbind: { nodename: '{nodename}' },
name: 'crush_rule',
- allowBlank: false,
+ editConfig: {
+ xtype: 'pveCephRuleSelector',
+ allowBlank: false,
+ },
},
{
xtype: 'proxmoxintegerfield',
advancedColumn2: [
{
xtype: 'numberfield',
- fieldLabel: gettext('Target Size Ratio'),
+ fieldLabel: gettext('Target Ratio'),
name: 'target_size_ratio',
- labelWidth: 140,
minValue: 0,
decimalPrecision: 3,
allowBlank: true,
emptyText: '0.0',
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('The ratio of storage amount this pool will consume compared to other pools with ratios. Used for auto-scaling.'),
+ },
},
{
xtype: 'pveSizeField',
- fieldLabel: gettext('Target Size') + ' (GiB)',
name: 'target_size',
- labelWidth: 140,
+ fieldLabel: gettext('Target Size'),
unit: 'GiB',
minValue: 0,
allowBlank: true,
+ allowZero: true,
emptyText: '0',
+ emptyValue: 0,
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('The amount of data eventually stored in this pool. Used for auto-scaling.'),
+ },
},
{
xtype: 'displayfield',
userCls: 'pmx-hint',
- value: 'Target Size Ratio takes precedence.',
+ value: Ext.String.format(gettext('{0} takes precedence.'), gettext('Target Ratio')), // FIXME: tooltip?
},
{
xtype: 'proxmoxintegerfield',
fieldLabel: 'Min. # of PGs',
name: 'pg_num_min',
- labelWidth: 140,
minValue: 0,
allowBlank: true,
emptyText: '0',
},
});
-Ext.define('PVE.CephPoolEdit', {
+Ext.define('PVE.Ceph.PoolEdit', {
extend: 'Proxmox.window.Edit',
alias: 'widget.pveCephPoolEdit',
- xtype: 'pveCephPoolEdit',
mixins: ['Proxmox.Mixin.CBind'],
cbindData: {
pool_name: '',
isCreate: (cfg) => !cfg.pool_name,
+ defaultSize: undefined,
+ defaultMinSize: undefined,
},
cbind: {
autoLoad: get => !get('isCreate'),
url: get => get('isCreate')
- ? `/nodes/${get('nodename')}/ceph/pools`
- : `/nodes/${get('nodename')}/ceph/pools/${get('pool_name')}`,
+ ? `/nodes/${get('nodename')}/ceph/pool`
+ : `/nodes/${get('nodename')}/ceph/pool/${get('pool_name')}`,
+ loadUrl: get => `/nodes/${get('nodename')}/ceph/pool/${get('pool_name')}/status`,
method: get => get('isCreate') ? 'POST' : 'PUT',
},
cbind: {
nodename: '{nodename}',
pool_name: '{pool_name}',
+ isErasure: '{isErasure}',
isCreate: '{isCreate}',
+ defaultSize: '{defaultSize}',
+ defaultMinSize: '{defaultMinSize}',
},
}],
});
-Ext.define('PVE.node.CephPoolList', {
+Ext.define('PVE.node.Ceph.PoolList', {
extend: 'Ext.grid.GridPanel',
alias: 'widget.pveNodeCephPoolList',
features: [{ ftype: 'summary' }],
columns: [
+ {
+ text: gettext('Pool #'),
+ minWidth: 70,
+ flex: 1,
+ align: 'right',
+ sortable: true,
+ dataIndex: 'pool',
+ },
{
text: gettext('Name'),
minWidth: 120,
sortable: true,
dataIndex: 'pool_name',
},
+ {
+ text: gettext('Type'),
+ minWidth: 100,
+ flex: 1,
+ dataIndex: 'type',
+ hidden: true,
+ },
{
text: gettext('Size') + '/min',
minWidth: 100,
flex: 1,
align: 'right',
- renderer: function(v, meta, rec) {
- return v + '/' + rec.data.min_size;
- },
+ renderer: (v, meta, rec) => `${v}/${rec.data.min_size}`,
dataIndex: 'size',
},
{
text: '# of Placement Groups',
flex: 1,
- minWidth: 150,
+ minWidth: 100,
align: 'right',
dataIndex: 'pg_num',
},
{
text: gettext('Optimal # of PGs'),
flex: 1,
- minWidth: 140,
+ minWidth: 100,
align: 'right',
dataIndex: 'pg_num_final',
renderer: function(value, metaData) {
{
text: gettext('Min. # of PGs'),
flex: 1,
- minWidth: 140,
+ minWidth: 100,
align: 'right',
dataIndex: 'pg_num_min',
hidden: true,
},
{
- text: gettext('Target Size Ratio'),
+ text: gettext('Target Ratio'),
flex: 1,
- minWidth: 140,
+ minWidth: 100,
align: 'right',
dataIndex: 'target_size_ratio',
renderer: Ext.util.Format.numberRenderer('0.0000'),
{
text: gettext('Target Size'),
flex: 1,
- minWidth: 140,
+ minWidth: 100,
align: 'right',
dataIndex: 'target_size',
hidden: true,
renderer: function(v, metaData, rec) {
- let value = PVE.Utils.render_size(v);
+ let value = Proxmox.Utils.render_size(v);
if (rec.data.target_size_ratio > 0) {
value = '<i class="fa fa-info-circle faded"></i> ' + value;
metaData.tdAttr = 'data-qtip="Target Size Ratio takes precedence over Target Size."';
{
text: gettext('Autoscale Mode'),
flex: 1,
- minWidth: 140,
+ minWidth: 100,
align: 'right',
dataIndex: 'pg_autoscale_mode',
},
flex: 1,
align: 'right',
minWidth: 150,
- renderer: function(v, meta, rec) {
- return v + ' (' + rec.data.crush_rule + ')';
- },
+ renderer: (v, meta, rec) => `${v} (${rec.data.crush_rule})`,
dataIndex: 'crush_rule_name',
},
{
text: gettext('Used') + ' (%)',
flex: 1,
- minWidth: 180,
+ minWidth: 150,
sortable: true,
align: 'right',
dataIndex: 'bytes_used',
summaryType: 'sum',
- summaryRenderer: PVE.Utils.render_size,
+ summaryRenderer: Proxmox.Utils.render_size,
renderer: function(v, meta, rec) {
let percentage = Ext.util.Format.percent(rec.data.percent_used, '0.00');
- let used = PVE.Utils.render_size(v);
- return used + ' (' + percentage + ')';
+ let used = Proxmox.Utils.render_size(v);
+ return `${used} (${percentage})`;
},
},
],
storeid: 'ceph-pool-list' + nodename,
model: 'ceph-pool-list',
proxy: {
- type: 'proxmox',
- url: "/api2/json/nodes/" + nodename + "/ceph/pools",
+ type: 'proxmox',
+ url: `/api2/json/nodes/${nodename}/ceph/pool`,
},
});
let store = Ext.create('Proxmox.data.DiffStore', { rstore: rstore });
- PVE.Utils.handleStoreErrorOrMask(
- me,
- rstore,
- /not (installed|initialized)/i,
- (_, error) => {
- rstore.stopUpdate();
- PVE.Utils.showCephInstallOrMask(me, error.statusText, nodename, win => {
- me.mon(win, 'cephInstallWindowClosed', () => rstore.startUpdate());
- });
- },
- );
+ // manages the "install ceph?" overlay
+ PVE.Utils.monitor_ceph_installed(me, rstore, nodename);
var run_editor = function() {
let rec = sm.getSelection()[0];
if (!rec || !rec.data.pool_name) {
return;
}
- Ext.create('PVE.CephPoolEdit', {
+ Ext.create('PVE.Ceph.PoolEdit', {
title: gettext('Edit') + ': Ceph Pool',
nodename: nodename,
pool_name: rec.data.pool_name,
+ isErasure: rec.data.type === 'erasure',
autoShow: true,
listeners: {
destroy: () => rstore.load(),
{
text: gettext('Create'),
handler: function() {
- Ext.create('PVE.CephPoolEdit', {
- title: gettext('Create') + ': Ceph Pool',
- isCreate: true,
- nodename: nodename,
- autoShow: true,
- listeners: {
- destroy: () => rstore.load(),
+ let keys = [
+ 'global:osd-pool-default-min-size',
+ 'global:osd-pool-default-size',
+ ];
+ let params = {
+ 'config-keys': keys.join(';'),
+ };
+
+ Proxmox.Utils.API2Request({
+ url: '/nodes/localhost/ceph/cfg/value',
+ method: 'GET',
+ params,
+ waitMsgTarget: me.getView(),
+ failure: response => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
+ success: function({ result: { data } }) {
+ let global = data.global;
+ let defaultSize = global?.['osd-pool-default-size'] ?? 3;
+ let defaultMinSize = global?.['osd-pool-default-min-size'] ?? 2;
+
+ Ext.create('PVE.Ceph.PoolEdit', {
+ title: gettext('Create') + ': Ceph Pool',
+ isCreate: true,
+ isErasure: false,
+ defaultSize,
+ defaultMinSize,
+ nodename: nodename,
+ autoShow: true,
+ listeners: {
+ destroy: () => rstore.load(),
+ },
+ });
},
});
},
return;
}
let poolName = rec.data.pool_name;
- Ext.create('PVE.window.SafeDestroy', {
+ Ext.create('Proxmox.window.SafeDestroy', {
showProgress: true,
- url: `/nodes/${nodename}/ceph/pools/${poolName}`,
+ url: `/nodes/${nodename}/ceph/pool/${poolName}`,
params: {
remove_storages: 1,
},
type: 'CephPool',
id: poolName,
},
+ taskName: 'cephdestroypool',
autoShow: true,
listeners: {
destroy: () => rstore.load(),
queryMode: 'local',
initComponent: function() {
- var me = this;
+ let me = this;
if (!me.nodename) {
throw "no nodename given";
}
- var store = Ext.create('Ext.data.Store', {
- fields: ['name'],
- sorters: 'name',
- proxy: {
- type: 'proxmox',
- url: `/api2/json/nodes/${me.nodename}/ceph/rules`,
- },
- });
+ me.originalAllowBlank = me.allowBlank;
+ me.allowBlank = true;
Ext.apply(me, {
- store: store,
- });
-
- me.callParent();
+ store: {
+ fields: ['name'],
+ sorters: 'name',
+ proxy: {
+ type: 'proxmox',
+ url: `/api2/json/nodes/${me.nodename}/ceph/rules`,
+ },
+ autoLoad: {
+ callback: (records, op, success) => {
+ if (me.isCreate && success && records.length > 0) {
+ me.select(records[0]);
+ }
- store.load({
- callback: function(rec, op, success) {
- if (success && rec.length > 0) {
- me.select(rec[0]);
- }
+ me.allowBlank = me.originalAllowBlank;
+ delete me.originalAllowBlank;
+ me.validate();
+ },
+ },
},
});
+
+ me.callParent();
},
});