onlineHelp: 'chapter_pveceph',
scrollable: true,
-
bodyPadding: 5,
-
layout: {
- type: 'column'
+ type: 'column',
},
defaults: {
- padding: 5
+ padding: 5,
},
items: [
bodyPadding: 10,
plugins: 'responsive',
responsiveConfig: {
- 'width < 1900': {
- columnWidth: 1
+ 'width < 1600': {
+ minHeight: 230,
+ columnWidth: 1,
+ },
+ 'width >= 1600': {
+ minHeight: 500,
+ columnWidth: 0.5,
},
- 'width >= 1900': {
- columnWidth: 0.5
- }
},
- minHeight: 210,
layout: {
type: 'hbox',
- align: 'stretch'
+ align: 'stretch',
},
items: [
{
+ xtype: 'container',
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
flex: 1,
- itemId: 'overallhealth',
- xtype: 'pveHealthWidget',
- title: gettext('Status')
+ items: [
+ {
+ flex: 1,
+ itemId: 'overallhealth',
+ xtype: 'pveHealthWidget',
+ title: gettext('Status'),
+ },
+ {
+ itemId: 'versioninfo',
+ xtype: 'displayfield',
+ fieldLabel: gettext('Ceph Version'),
+ value: "",
+ autoEl: {
+ tag: 'div',
+ 'data-qtip': gettext('The newest version installed in the Cluster.'),
+ },
+ padding: '10 0 0 0',
+ style: {
+ 'text-align': 'center',
+ },
+ },
+ ],
},
{
flex: 2,
stateful: true,
stateId: 'ceph-status-warnings',
xtype: 'grid',
- // since we load the store manually,
- // to show the emptytext, we have to
- // specify an empty store
- store: { data:[] },
+ // we load the store manually, to show an emptyText specify an empty intermediate store
+ store: {
+ trackRemoved: false,
+ data: [],
+ },
emptyText: gettext('No Warnings/Errors'),
columns: [
{
align: 'center',
width: 70,
renderer: function(value) {
- var health = PVE.Utils.map_ceph_health[value];
- var classes = PVE.Utils.get_health_icon(health);
-
- return '<i class="fa fa-fw ' + classes + '"></i>';
+ let health = PVE.Utils.map_ceph_health[value];
+ let icon = PVE.Utils.get_health_icon(health);
+ return `<i class="fa fa-fw ${icon}"></i>`;
},
sorter: {
- sorterFn: function(a,b) {
- var healthArr = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
- return healthArr.indexOf(b.data.severity) - healthArr.indexOf(a.data.severity);
- }
- }
+ sorterFn: function(a, b) {
+ let health = ['HEALTH_ERR', 'HEALTH_WARN', 'HEALTH_OK'];
+ return health.indexOf(b.data.severity) - health.indexOf(a.data.severity);
+ },
+ },
},
{
dataIndex: 'summary',
header: gettext('Summary'),
- flex: 1
+ flex: 1,
},
{
xtype: 'actioncolumn',
width: 650,
height: 400,
layout: {
- type: 'fit'
+ type: 'fit',
},
items: [{
scrollable: true,
xtype: 'box',
html: [
'<span>' + Ext.htmlEncode(record.data.summary) + '</span>',
- '<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>'
- ]
- }]
+ '<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>',
+ ],
+ }],
});
win.show();
- }
- }
- ]
- }
- ]
- }
- ]
+ },
+ },
+ ],
+ },
+ ],
+ },
+ ],
},
{
xtype: 'pveCephStatusDetail',
itemId: 'statusdetail',
plugins: 'responsive',
responsiveConfig: {
- 'width < 1900': {
- columnWidth: 1
+ 'width < 1600': {
+ columnWidth: 1,
+ minHeight: 250,
+ },
+ 'width >= 1600': {
+ columnWidth: 0.5,
+ minHeight: 300,
},
- 'width >= 1900': {
- columnWidth: 0.5
- }
},
- title: gettext('Status')
+ title: gettext('Status'),
},
{
title: gettext('Services'),
plugins: 'responsive',
layout: {
type: 'hbox',
- align: 'stretch'
+ align: 'stretch',
},
responsiveConfig: {
- 'width < 1900': {
+ 'width < 1600': {
columnWidth: 1,
- minHeight: 200
+ minHeight: 200,
},
- 'width >= 1900': {
+ 'width >= 1600': {
columnWidth: 0.5,
- minHeight: 200
- }
- }
+ minHeight: 200,
+ },
+ },
},
{
xtype: 'panel',
bodyPadding: 5,
layout: {
type: 'hbox',
- align: 'center'
+ align: 'center',
},
items: [
{
flex: 1,
- xtype: 'proxmoxGauge',
- itemId: 'space',
- title: gettext('Usage')
+ xtype: 'container',
+ items: [
+ {
+ xtype: 'proxmoxGauge',
+ itemId: 'space',
+ title: gettext('Usage'),
+ },
+ {
+ flex: 1,
+ border: false,
+ },
+ {
+ xtype: 'container',
+ itemId: 'recovery',
+ hidden: true,
+ padding: 25,
+ items: [
+ {
+ itemId: 'recoverychart',
+ xtype: 'pveRunningChart',
+ title: gettext('Recovery') +'/ '+ gettext('Rebalance'),
+ renderer: PVE.Utils.render_bandwidth,
+ height: 100,
+ },
+ {
+ xtype: 'progressbar',
+ itemId: 'recoveryprogress',
+ },
+ ],
+ },
+ ],
},
{
flex: 2,
xtype: 'container',
defaults: {
padding: 0,
- height: 100
+ height: 100,
},
items: [
{
itemId: 'reads',
xtype: 'pveRunningChart',
title: gettext('Reads'),
- renderer: PVE.Utils.render_bandwidth
+ renderer: PVE.Utils.render_bandwidth,
},
{
itemId: 'writes',
xtype: 'pveRunningChart',
title: gettext('Writes'),
- renderer: PVE.Utils.render_bandwidth
- },
- {
- itemId: 'iops',
- xtype: 'pveRunningChart',
- hidden: true,
- title: 'IOPS', // do not localize
- renderer: Ext.util.Format.numberRenderer('0,000')
+ renderer: PVE.Utils.render_bandwidth,
},
{
itemId: 'readiops',
xtype: 'pveRunningChart',
- hidden: true,
title: 'IOPS: ' + gettext('Reads'),
- renderer: Ext.util.Format.numberRenderer('0,000')
+ renderer: Ext.util.Format.numberRenderer('0,000'),
},
{
itemId: 'writeiops',
xtype: 'pveRunningChart',
- hidden: true,
title: 'IOPS: ' + gettext('Writes'),
- renderer: Ext.util.Format.numberRenderer('0,000')
- }
- ]
- }
- ]
- }
+ renderer: Ext.util.Format.numberRenderer('0,000'),
+ },
+ ],
+ },
+ ],
+ },
],
generateCheckData: function(health) {
var result = [];
- var checks = health.checks || {};
- var keys = Ext.Object.getKeys(checks).sort();
+ let checks = health.checks || {};
- Ext.Array.forEach(keys, function(key) {
- var details = checks[key].detail || [];
+ Object.keys(checks).sort().forEach(key => {
+ let check = checks[key];
result.push({
id: key,
- summary: checks[key].summary.message,
- detail: Ext.Array.reduce(
- checks[key].detail,
- function(first, second) {
- return first + '\n' + second.message;
- },
- ''
- ),
- severity: checks[key].severity
+ summary: check.summary.message,
+ detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, ''),
+ severity: check.severity,
});
});
-
return result;
},
me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data);
// add performance data
- var used = rec.data.pgmap.bytes_used;
- var total = rec.data.pgmap.bytes_total;
+ let pgmap = rec.data.pgmap;
+ let used = pgmap.bytes_used;
+ let total = pgmap.bytes_total;
var text = Ext.String.format(gettext('{0} of {1}'),
- PVE.Utils.render_size(used),
- PVE.Utils.render_size(total)
+ Proxmox.Utils.render_size(used),
+ Proxmox.Utils.render_size(total),
);
// update the usage widget
me.down('#space').updateValue(used/total, text);
- // TODO: logic for jewel (iops splitted in read/write)
-
- var iops = rec.data.pgmap.op_per_sec;
- var readiops = rec.data.pgmap.read_op_per_sec;
- var writeiops = rec.data.pgmap.write_op_per_sec;
- var reads = rec.data.pgmap.read_bytes_sec || 0;
- var writes = rec.data.pgmap.write_bytes_sec || 0;
+ let readiops = pgmap.read_op_per_sec;
+ let writeiops = pgmap.write_op_per_sec;
+ let reads = pgmap.read_bytes_sec || 0;
+ let writes = pgmap.write_bytes_sec || 0;
- if (iops !== undefined && me.version !== 'hammer') {
- me.change_version('hammer');
- } else if((readiops !== undefined || writeiops !== undefined) && me.version !== 'jewel') {
- me.change_version('jewel');
- }
// update the graphs
me.reads.addDataPoint(reads);
me.writes.addDataPoint(writes);
- me.iops.addDataPoint(iops);
me.readiops.addDataPoint(readiops);
me.writeiops.addDataPoint(writeiops);
- },
- change_version: function(version) {
- var me = this;
- me.version = version;
- me.sp.set('ceph-version', version);
- me.iops.setVisible(version === 'hammer');
- me.readiops.setVisible(version === 'jewel');
- me.writeiops.setVisible(version === 'jewel');
+ let degraded = pgmap.degraded_objects || 0;
+ let misplaced = pgmap.misplaced_objects || 0;
+ let unfound = pgmap.unfound_objects || 0;
+ let unhealthy = degraded + unfound + misplaced;
+ // update recovery
+ if (pgmap.recovering_objects_per_sec !== undefined || unhealthy > 0) {
+ let toRecoverObjects = pgmap.misplaced_total || pgmap.unfound_total || pgmap.degraded_total || 0;
+ if (toRecoverObjects === 0) {
+ return; // FIXME: unexpected return and leaves things possible visible when it shouldn't?
+ }
+ let recovered = toRecoverObjects - unhealthy || 0;
+ let speed = pgmap.recovering_bytes_per_sec || 0;
+
+ let recoveryRatio = recovered / toRecoverObjects;
+ let txt = `${(recoveryRatio * 100).toFixed(2)}%`;
+ if (speed > 0) {
+ let obj_per_sec = speed / (4 * 1024 * 1024); // 4 MiB per Object
+ let duration = Proxmox.Utils.format_duration_human(unhealthy/obj_per_sec);
+ let speedTxt = PVE.Utils.render_bandwidth(speed);
+ txt += ` (${speedTxt} - ${duration} left)`;
+ }
+
+ me.down('#recovery').setVisible(true);
+ me.down('#recoveryprogress').updateValue(recoveryRatio);
+ me.down('#recoveryprogress').updateText(txt);
+ me.down('#recoverychart').addDataPoint(speed);
+ } else {
+ me.down('#recovery').setVisible(false);
+ me.down('#recoverychart').addDataPoint(0);
+ }
},
initComponent: function() {
var nodename = me.pveSelNode.data.node;
me.callParent();
- var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph/';
+ var baseurl = '/api2/json' + (nodename ? '/nodes/' + nodename : '/cluster') + '/ceph';
me.store = Ext.create('Proxmox.data.UpdateStore', {
storeid: 'ceph-status-' + (nodename || 'cluster'),
interval: 5000,
proxy: {
type: 'proxmox',
- url: baseurl + '/status'
- }
+ url: baseurl + '/status',
+ },
});
me.metadatastore = Ext.create('Proxmox.data.UpdateStore', {
interval: 15*1000,
proxy: {
type: 'proxmox',
- url: baseurl + 'metadata'
- }
+ url: '/api2/json/cluster/ceph/metadata',
+ },
});
// save references for the updatefunction
me.reads = me.down('#reads');
me.writes = me.down('#writes');
- // get ceph version
- me.sp = Ext.state.Manager.getProvider();
- me.version = me.sp.get('ceph-version');
- me.change_version(me.version);
-
- var regex = new RegExp("not (installed|initialized)", "i");
- PVE.Utils.handleStoreErrorOrMask(me, me.store, regex, function(me, error){
- me.store.stopUpdate();
- PVE.Utils.showCephInstallOrMask(me, error.statusText, (nodename || 'localhost'),
- function(win){
- me.mon(win, 'cephInstallWindowClosed', function(){
- me.store.startUpdate();
- });
- }
- );
- });
+ // manages the "install ceph?" overlay
+ PVE.Utils.monitor_ceph_installed(me, me.store, nodename);
me.mon(me.store, 'load', me.updateAll, me);
me.mon(me.metadatastore, 'load', function(store, records, success) {
if (!success || records.length < 1) {
return;
}
- var rec = records[0];
- me.metadata = rec.data;
+ me.metadata = records[0].data;
// update services
- me.getComponent('services').updateAll(rec.data, me.status || {});
+ me.getComponent('services').updateAll(me.metadata, me.status || {});
// update detailstatus panel
- me.getComponent('statusdetail').updateAll(rec.data, me.status || {});
-
+ me.getComponent('statusdetail').updateAll(me.metadata, me.status || {});
+
+ let maxversion = [];
+ let maxversiontext = "";
+ for (const [_nodename, data] of Object.entries(me.metadata.node)) {
+ let version = data.version.parts;
+ if (PVE.Utils.compare_ceph_versions(version, maxversion) > 0) {
+ maxversion = version;
+ maxversiontext = data.version.str;
+ }
+ }
+ me.down('#versioninfo').setValue(maxversiontext);
}, me);
me.on('destroy', me.store.stopUpdate);
me.on('destroy', me.metadatastore.stopUpdate);
me.store.startUpdate();
me.metadatastore.startUpdate();
- }
+ },
});