]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/ceph/Status.js
api: ceph: osd: create: rename size parameters
[pve-manager.git] / www / manager6 / ceph / Status.js
index 495bd42ca5a34812f004b2d87f429bc43b59f76b..e92c698b7008ebeecbaeaeff18f3b9bd98208e55 100644 (file)
 Ext.define('PVE.node.CephStatus', {
-    extend: 'PVE.grid.ObjectGrid',
-    alias: ['widget.pveNodeCephStatus'],
-    cwidth1: 150,
-    interval: 3000,
-    initComponent: function() {
-        /*jslint confusion: true */
-        var me = this;
+    extend: 'Ext.panel.Panel',
+    alias: 'widget.pveNodeCephStatus',
 
-       var nodename = me.pveSelNode.data.node;
-       if (!nodename) {
-           throw "no node name specified";
-       }
+    onlineHelp: 'chapter_pveceph',
 
-       var renderquorum = function(value) {
-           if (!value || value.length < 0) {
-               return 'No';
-           }
+    scrollable: true,
+    bodyPadding: 5,
+    layout: {
+       type: 'column',
+    },
 
-           return 'Yes {' + value.join(' ') + '}';
-       };
+    defaults: {
+       padding: 5,
+    },
 
-       var rendermonmap = function(d) {
-           if (!d) {
-               return '';
-           }
+    items: [
+       {
+           xtype: 'panel',
+           title: gettext('Health'),
+           bodyPadding: 10,
+           plugins: 'responsive',
+           responsiveConfig: {
+               'width < 1600': {
+                   minHeight: 230,
+                   columnWidth: 1,
+               },
+               'width >= 1600': {
+                   minHeight: 500,
+                   columnWidth: 0.5,
+               },
+           },
+           layout: {
+               type: 'hbox',
+               align: 'stretch',
+           },
+           items: [
+               {
+                   xtype: 'container',
+                   layout: {
+                       type: 'vbox',
+                       align: 'stretch',
+                   },
+                   flex: 1,
+                   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,
+                   itemId: 'warnings',
+                   stateful: true,
+                   stateId: 'ceph-status-warnings',
+                   xtype: 'grid',
+                   // we load the store manually, to show an emptyText specify an empty intermediate store
+                   store: {
+                       trackRemoved: false,
+                       data: [],
+                   },
+                   emptyText: gettext('No Warnings/Errors'),
+                   columns: [
+                       {
+                           dataIndex: 'severity',
+                           header: gettext('Severity'),
+                           align: 'center',
+                           width: 70,
+                           renderer: function(value) {
+                               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) {
+                                   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,
+                       },
+                       {
+                           xtype: 'actioncolumn',
+                           width: 40,
+                           align: 'center',
+                           tooltip: gettext('Detail'),
+                           items: [
+                               {
+                                   iconCls: 'x-fa fa-info-circle',
+                                   handler: function(grid, rowindex, colindex, item, e, record) {
+                                       var win = Ext.create('Ext.window.Window', {
+                                           title: gettext('Detail'),
+                                           resizable: true,
+                                           modal: true,
+                                           width: 650,
+                                           height: 400,
+                                           layout: {
+                                               type: 'fit',
+                                           },
+                                           items: [{
+                                               scrollable: true,
+                                               padding: 10,
+                                               xtype: 'box',
+                                               html: [
+                                                   '<span>' + Ext.htmlEncode(record.data.summary) + '</span>',
+                                                   '<pre>' + Ext.htmlEncode(record.data.detail) + '</pre>',
+                                               ],
+                                           }],
+                                       });
+                                       win.show();
+                                   },
+                               },
+                           ],
+                       },
+                   ],
+               },
+           ],
+       },
+       {
+           xtype: 'pveCephStatusDetail',
+           itemId: 'statusdetail',
+           plugins: 'responsive',
+           responsiveConfig: {
+               'width < 1600': {
+                   columnWidth: 1,
+                   minHeight: 250,
+               },
+               'width >= 1600': {
+                   columnWidth: 0.5,
+                   minHeight: 300,
+               },
+           },
+           title: gettext('Status'),
+       },
+       {
+           title: gettext('Services'),
+           xtype: 'pveCephServices',
+           itemId: 'services',
+           plugins: 'responsive',
+           layout: {
+               type: 'hbox',
+               align: 'stretch',
+           },
+           responsiveConfig: {
+               'width < 1600': {
+                   columnWidth: 1,
+                   minHeight: 200,
+               },
+               'width >= 1600': {
+                   columnWidth: 0.5,
+                   minHeight: 200,
+               },
+           },
+       },
+       {
+           xtype: 'panel',
+           title: gettext('Performance'),
+           columnWidth: 1,
+           bodyPadding: 5,
+           layout: {
+               type: 'hbox',
+               align: 'center',
+           },
+           items: [
+               {
+                   flex: 1,
+                   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,
+                   },
+                   items: [
+                       {
+                           itemId: 'reads',
+                           xtype: 'pveRunningChart',
+                           title: gettext('Reads'),
+                           renderer: PVE.Utils.render_bandwidth,
+                       },
+                       {
+                           itemId: 'writes',
+                           xtype: 'pveRunningChart',
+                           title: gettext('Writes'),
+                           renderer: PVE.Utils.render_bandwidth,
+                       },
+                       {
+                           itemId: 'readiops',
+                           xtype: 'pveRunningChart',
+                           title: 'IOPS: ' + gettext('Reads'),
+                           renderer: Ext.util.Format.numberRenderer('0,000'),
+                       },
+                       {
+                           itemId: 'writeiops',
+                           xtype: 'pveRunningChart',
+                           title: 'IOPS: ' + gettext('Writes'),
+                           renderer: Ext.util.Format.numberRenderer('0,000'),
+                       },
+                   ],
+               },
+           ],
+       },
+    ],
 
-           var txt =  'e' + d.epoch + ': ' + d.mons.length + " mons at ";
+    generateCheckData: function(health) {
+       var result = [];
+       let checks = health.checks || {};
 
-           Ext.Array.each(d.mons, function(d) {
-               txt += d.name + '=' + d.addr + ',';
+       Object.keys(checks).sort().forEach(key => {
+           let check = checks[key];
+           result.push({
+               id: key,
+               summary: check.summary.message,
+               detail: check.detail.reduce((acc, v) => `${acc}\n${v.message}`, ''),
+               severity: check.severity,
            });
+       });
+       return result;
+    },
 
-           return txt;
-       };
+    updateAll: function(store, records, success) {
+       if (!success || records.length === 0) {
+           return;
+       }
 
-       var renderosdmap = function(value) {
-           if (!value || !value.osdmap) {
-               return '';
-           }
+       var me = this;
+       var rec = records[0];
+       me.status = rec.data;
+
+       // add health panel
+       me.down('#overallhealth').updateHealth(PVE.Utils.render_ceph_health(rec.data.health || {}));
+       // add errors to gridstore
+       me.down('#warnings').getStore().loadRawData(me.generateCheckData(rec.data.health || {}), false);
+
+       // update services
+       me.getComponent('services').updateAll(me.metadata || {}, rec.data);
+
+       // update detailstatus panel
+       me.getComponent('statusdetail').updateAll(me.metadata || {}, rec.data);
+
+       // add performance data
+       let pgmap = rec.data.pgmap;
+       let used = pgmap.bytes_used;
+       let total = pgmap.bytes_total;
 
-           var d = value.osdmap;
+       var text = Ext.String.format(gettext('{0} of {1}'),
+           Proxmox.Utils.render_size(used),
+           Proxmox.Utils.render_size(total),
+       );
 
-           var txt = 'e' + d.epoch + ': ';
+       // update the usage widget
+       me.down('#space').updateValue(used/total, text);
 
-           txt += d.num_osds + ' osds: ' + d.num_up_osds + ' up, ' +
-               d.num_in_osds + " in";
+       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;
 
-           return txt;
-       };
+       // update the graphs
+       me.reads.addDataPoint(reads);
+       me.writes.addDataPoint(writes);
+       me.readiops.addDataPoint(readiops);
+       me.writeiops.addDataPoint(writeiops);
 
-       var renderhealth = function(value) {
-           if (!value || !value.overall_status) {
-               return '';
+       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 toRecover = pgmap.misplaced_total || pgmap.unfound_total || pgmap.degraded_total || 0;
+           if (toRecover === 0) {
+               return; // FIXME: unexpected return and leaves things possible visible when it shouldn't?
            }
+           let recovered = toRecover - unhealthy || 0;
+           let speed = pgmap.recovering_bytes_per_sec || 0;
 
-           var txt = value.overall_status;
+           let recoveryRatio = recovered / total;
+           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)`;
+           }
 
-           Ext.Array.each(value.summary, function(d) {
-               txt += " " + d.summary + ';';
-           });
+           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);
+       }
+    },
 
-           return txt;
-       };
+    initComponent: function() {
+       var me = this;
+
+       var nodename = me.pveSelNode.data.node;
+
+       me.callParent();
+       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',
+           },
+       });
+
+       me.metadatastore = Ext.create('Proxmox.data.UpdateStore', {
+           storeid: 'ceph-metadata-' + (nodename || 'cluster'),
+           interval: 15*1000,
+           proxy: {
+               type: 'proxmox',
+               url: '/api2/json/cluster/ceph/metadata',
+           },
+       });
 
-       var renderpgmap = function(d) {
-           if (!d) {
-               return '';
+       // save references for the updatefunction
+       me.iops = me.down('#iops');
+       me.readiops = me.down('#readiops');
+       me.writeiops = me.down('#writeiops');
+       me.reads = me.down('#reads');
+       me.writes = me.down('#writes');
+
+       // 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;
            }
+           me.metadata = records[0].data;
 
-           var txt = 'v' + d.version + ': ';
+           // update services
+           me.getComponent('services').updateAll(me.metadata, me.status || {});
 
-           txt += d.num_pgs + " pgs:";
+           // update detailstatus panel
+           me.getComponent('statusdetail').updateAll(me.metadata, me.status || {});
 
-           Ext.Array.each(d.pgs_by_state, function(s) {
-               txt += " " + s.count + " " + s.state_name;
-           });
-           txt += '; ';
-
-           txt += PVE.Utils.format_size(d.data_bytes) + " data, ";
-           txt += PVE.Utils.format_size(d.bytes_used) + " used, ";
-           txt += PVE.Utils.format_size(d.bytes_avail) + " avail";
-
-           return txt;
-       };
-
-       Ext.applyIf(me, {
-           url: "/api2/json/nodes/" + nodename + "/ceph/status",
-           rows: {
-               health: {
-                   header: 'health',
-                   renderer: renderhealth,
-                   required: true
-               },
-               quorum_names: {
-                   header: 'quorum',
-                   renderer: renderquorum,
-                   required: true
-               },
-               fsid: {
-                   header: 'cluster',
-                   required: true
-               },
-               monmap: {
-                   header: 'monmap',
-                   renderer: rendermonmap,
-                   required: true
-               },
-               osdmap: {
-                   header: 'osdmap',
-                   renderer: renderosdmap,
-                   required: true
-               },
-               pgmap: {
-                   header: 'pgmap',
-                   renderer: renderpgmap,
-                   required: true
+           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.callParent();
+       me.on('destroy', me.store.stopUpdate);
+       me.on('destroy', me.metadatastore.stopUpdate);
+       me.store.startUpdate();
+       me.metadatastore.startUpdate();
+    },
 
-       me.on('activate', me.rstore.startUpdate);
-       me.on('hide', me.rstore.stopUpdate);
-       me.on('destroy', me.rstore.stopUpdate);
-    }
 });