]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/ceph/ServiceList.js
api: add proxmox-firewall to versions pkg list
[pve-manager.git] / www / manager6 / ceph / ServiceList.js
index 581bd55437612819cc387afa09fe92b8ecdaec5f..767101465dfd92498b62884546e13889f073227d 100644 (file)
 Ext.define('PVE.CephCreateService', {
     extend: 'Proxmox.window.Edit',
+    mixins: ['Proxmox.Mixin.CBind'],
     xtype: 'pveCephCreateService',
 
+    method: 'POST',
+    isCreate: true,
     showProgress: true,
+    width: 450,
 
-    setNode: function(nodename) {
-        var me = this;
-
-       me.nodename = nodename;
-        me.url = "/nodes/" + nodename + "/ceph/" + me.type + "/" + nodename;
+    setNode: function(node) {
+       let me = this;
+       me.nodename = node;
+       me.updateUrl();
     },
+    setExtraID: function(extraID) {
+       let me = this;
+       me.extraID = me.type === 'mds' ? `-${extraID}` : '';
+       me.updateUrl();
+    },
+    updateUrl: function() {
+       let me = this;
 
-    method: 'POST',
-    isCreate: true,
+       let extraID = me.extraID ?? '';
+       let node = me.nodename;
+
+       me.url = `/nodes/${node}/ceph/${me.type}/${node}${extraID}`;
+    },
 
+    defaults: {
+       labelWidth: 75,
+    },
     items: [
        {
            xtype: 'pveNodeSelector',
-           submitValue: false,
            fieldLabel: gettext('Host'),
            selectCurNode: true,
            allowBlank: false,
+           submitValue: false,
            listeners: {
                change: function(f, value) {
-                   var me = this.up('pveCephCreateService');
-                   me.setNode(value);
-               }
-           }
-       }
+                   let view = this.up('pveCephCreateService');
+                   view.setNode(value);
+               },
+           },
+       },
+       {
+           xtype: 'textfield',
+           fieldLabel: gettext('Extra ID'),
+           regex: /[a-zA-Z0-9]+/,
+           regexText: gettext('ID may only consist of alphanumeric characters'),
+           submitValue: false,
+           emptyText: Proxmox.Utils.NoneText,
+           cbind: {
+               disabled: get => get('type') !== 'mds',
+               hidden: get => get('type') !== 'mds',
+           },
+           listeners: {
+               change: function(f, value) {
+                   let view = this.up('pveCephCreateService');
+                   view.setExtraID(value);
+               },
+           },
+       },
+       {
+           xtype: 'component',
+           border: false,
+           padding: '5 2',
+           style: {
+               fontSize: '12px',
+           },
+           userCls: 'pmx-hint',
+           cbind: {
+               hidden: get => get('type') !== 'mds',
+           },
+           html: gettext('The Extra ID allows creating multiple MDS per node, which increases redundancy with more than one CephFS.'),
+       },
     ],
 
-    initComponent : function() {
-        var me = this;
+    initComponent: function() {
+        let me = this;
 
        if (!me.nodename) {
            throw "no node name specified";
        }
-
        if (!me.type) {
            throw "no type specified";
        }
-
        me.setNode(me.nodename);
 
         me.callParent();
-    }
+    },
 });
 
-Ext.define('PVE.node.CephServiceList', {
-    extend: 'Ext.grid.GridPanel',
-    xtype: 'pveNodeCephServiceList',
+Ext.define('PVE.node.CephServiceController', {
+    extend: 'Ext.app.ViewController',
+    alias: 'controller.CephServiceList',
 
-    onlineHelp: 'chapter_pveceph',
-    emptyText: gettext('No such service configured.'),
-
-    stateful: true,
+    render_status: (value, metadata, rec) => value,
 
-    // will be called when the store loads
-    storeLoadCallback: Ext.emptyFn,
+    render_version: function(value, metadata, rec) {
+       if (value === undefined) {
+           return '';
+       }
+       let view = this.getView();
+       let host = rec.data.host, nodev = [0];
+       if (view.nodeversions[host] !== undefined) {
+           nodev = view.nodeversions[host].version.parts;
+       }
 
-    // if set to true, does shows the ceph install mask if needed
-    showCephInstallMask: false,
+       let icon = '';
+       if (PVE.Utils.compare_ceph_versions(view.maxversion, nodev) > 0) {
+           icon = PVE.Utils.get_ceph_icon_html('HEALTH_UPGRADE');
+       } else if (PVE.Utils.compare_ceph_versions(nodev, value) > 0) {
+           icon = PVE.Utils.get_ceph_icon_html('HEALTH_OLD');
+       } else if (view.mixedversions) {
+           icon = PVE.Utils.get_ceph_icon_html('HEALTH_OK');
+       }
+       return icon + value;
+    },
 
-    controller: {
-       xclass: 'Ext.app.ViewController',
-
-       render_version: function(value, metadata, rec) {
-           let me = this.getView();
-           let host = rec.data.host;
-           let icon = "";
-           let v = value;
-           let nodev = [0];
-           if (me.nodeversions[host] !== undefined) {
-               nodev = me.nodeversions[host].version.parts;
+    getMaxVersions: function(store, records, success) {
+       if (!success || records.length < 1) {
+           return;
+       }
+       let me = this;
+       let view = me.getView();
+
+       view.nodeversions = records[0].data.node;
+       view.maxversion = [];
+       view.mixedversions = false;
+       for (const [_nodename, data] of Object.entries(view.nodeversions)) {
+           let res = PVE.Utils.compare_ceph_versions(data.version.parts, view.maxversion);
+           if (res !== 0 && view.maxversion.length > 0) {
+               view.mixedversions = true;
            }
-           let maxv = me.maxversion;
-
-           if (PVE.Utils.compare_ceph_versions(maxv, nodev) > 0) {
-               icon = PVE.Utils.get_ceph_icon_html('HEALTH_UPGRADE');
-           } else if (PVE.Utils.compare_ceph_versions(nodev, v) > 0) {
-               icon = PVE.Utils.get_ceph_icon_html('HEALTH_OLD');
-           } else if (me.mixedversions) {
-               icon = PVE.Utils.get_ceph_icon_html('HEALTH_OK');
+           if (res > 0) {
+               view.maxversion = data.version.parts;
            }
+       }
+    },
 
-           return icon + v;
-       },
-
-       getMaxVersions: function(store, records, success) {
-           if (!success || records.length < 1) {
-               return;
-           }
-           let me = this;
-           let view = me.getView();
-
-           view.nodeversions = records[0].data.node;
-           view.maxversion = [];
-           view.mixedversions = false;
-           for (const [nodename, data] of Object.entries(view.nodeversions)) {
-               let res = PVE.Utils.compare_ceph_versions(data.version.parts, view.maxversion);
-               if (res !== 0 && view.maxversion.length > 0) {
-                   view.mixedversions = true;
-               }
-               if (res > 0) {
-                   view.maxversion = data.version.parts;
-               }
-           }
-       },
+    init: function(view) {
+       if (view.pveSelNode) {
+           view.nodename = view.pveSelNode.data.node;
+       }
+       if (!view.nodename) {
+           throw "no node name specified";
+       }
 
-       init: function(view) {
-           if (view.pveSelNode) {
-               view.nodename = view.pveSelNode.data.node;
-           }
-           if (!view.nodename) {
-               throw "no node name specified";
-           }
+       if (!view.type) {
+           throw "no type specified";
+       }
 
-           if (!view.type) {
-               throw "no type specified";
-           }
+       view.versionsstore = Ext.create('Proxmox.data.UpdateStore', {
+           autoStart: true,
+           interval: 10000,
+           storeid: `ceph-versions-${view.type}-list${view.nodename}`,
+           proxy: {
+               type: 'proxmox',
+               url: "/api2/json/cluster/ceph/metadata?scope=versions",
+           },
+       });
+       view.versionsstore.on('load', this.getMaxVersions, this);
+       view.on('destroy', view.versionsstore.stopUpdate);
+
+       view.rstore = Ext.create('Proxmox.data.UpdateStore', {
+           autoStart: true,
+           interval: 3000,
+           storeid: `ceph-${view.type}-list${view.nodename}`,
+           model: 'ceph-service-list',
+           proxy: {
+               type: 'proxmox',
+               url: `/api2/json/nodes/${view.nodename}/ceph/${view.type}`,
+           },
+       });
 
-           view.versionsstore = Ext.create('Proxmox.data.UpdateStore', {
-               autoStart: true,
-               interval: 10000,
-               storeid: 'ceph-versions-' + view.type + '-list' + view.nodename,
-               proxy: {
-                   type: 'proxmox',
-                   url: "/api2/json/cluster/ceph/metadata?scope=versions"
-               }
-           });
+       view.setStore(Ext.create('Proxmox.data.DiffStore', {
+           rstore: view.rstore,
+           sorters: [{ property: 'name' }],
+       }));
 
-           view.versionsstore.on('load', this.getMaxVersions, this);
-           view.on('destroy', view.versionsstore.stopUpdate);
-
-           view.rstore = Ext.create('Proxmox.data.UpdateStore', {
-               autoStart: true,
-               interval: 3000,
-               storeid: 'ceph-' + view.type + '-list' + view.nodename,
-               model: 'ceph-service-list',
-               proxy: {
-                   type: 'proxmox',
-                   url: "/api2/json/nodes/" + view.nodename + "/ceph/" + view.type
-               }
-           });
-
-           view.setStore(Ext.create('Proxmox.data.DiffStore', {
-               rstore: view.rstore,
-               sorters: [{ property: 'name' }]
-           }));
+       if (view.storeLoadCallback) {
+           view.rstore.on('load', view.storeLoadCallback, this);
+       }
+       view.on('destroy', view.rstore.stopUpdate);
 
-           if (view.storeLoadCallback) {
-               view.rstore.on('load', view.storeLoadCallback, this);
-           }
-           view.on('destroy', view.rstore.stopUpdate);
-
-           if (view.showCephInstallMask) {
-               var regex = new RegExp("not (installed|initialized)", "i");
-               PVE.Utils.handleStoreErrorOrMask(view, view.rstore, regex, function(me, error) {
-                   view.rstore.stopUpdate();
-                   PVE.Utils.showCephInstallOrMask(view.ownerCt, error.statusText, view.nodename,
-                       function(win){
-                           me.mon(win, 'cephInstallWindowClosed', function(){
-                               view.rstore.startUpdate();
-                           });
-                       }
-                   );
-               });
-           }
-       },
+       if (view.showCephInstallMask) {
+           PVE.Utils.monitor_ceph_installed(view, view.rstore, view.nodename, true);
+       }
+    },
 
-       service_cmd: function(rec, cmd) {
-           var view = this.getView();
-           if (!rec.data.host) {
-               Ext.Msg.alert(gettext('Error'), "entry has no host");
-               return;
-           }
+    service_cmd: function(rec, cmd) {
+       let view = this.getView();
+       if (!rec.data.host) {
+           Ext.Msg.alert(gettext('Error'), "entry has no host");
+           return;
+       }
+       let doRequest = function() {
            Proxmox.Utils.API2Request({
-               url: "/nodes/" + rec.data.host + "/ceph/" + cmd,
+               url: `/nodes/${rec.data.host}/ceph/${cmd}`,
                method: 'POST',
                params: { service: view.type + '.' + rec.data.name },
                success: function(response, options) {
-                   var upid = response.result.data;
-                   var win = Ext.create('Proxmox.window.TaskProgress', {
-                       upid: upid,
-                       taskDone: function() {
-                           view.rstore.load();
-                       }
+                   Ext.create('Proxmox.window.TaskProgress', {
+                       autoShow: true,
+                       upid: response.result.data,
+                       taskDone: () => view.rstore.load(),
                    });
-                   win.show();
                },
-               failure: function(response, opts) {
-                   Ext.Msg.alert(gettext('Error'), response.htmlStatus);
-               }
+               failure: (response, _opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
            });
-       },
-       onChangeService: function(btn) {
-           var me = this;
-           var view = this.getView();
-           var cmd = btn.action;
-           var rec = view.getSelection()[0];
-           me.service_cmd(rec, cmd);
-       },
-
-       showSyslog: function() {
-           var view = this.getView();
-           var rec = view.getSelection()[0];
-           var servicename = 'ceph-' + view.type + '@' + rec.data.name;
-           var url = "/api2/extjs/nodes/" + rec.data.host + "/syslog?service=" +  encodeURIComponent(servicename);
-           var win = Ext.create('Ext.window.Window', {
-               title: gettext('Syslog') + ': ' + servicename,
-               modal: true,
-               width: 800,
-               height: 400,
-               layout: 'fit',
-               items: [{
-                   xtype: 'proxmoxLogView',
-                   url: url,
-                   log_select_timespan: 1
-               }]
-           });
-           win.show();
-       },
-
-       onCreate: function() {
-           var view = this.getView();
-           var win = Ext.create('PVE.CephCreateService', {
-               autoShow: true,
-               nodename: view.nodename,
-               subject: view.getTitle(),
-               type: view.type,
-               taskDone: function() {
-                   view.rstore.load();
-               }
+       };
+       if (cmd === "stop" && ['mon', 'mds'].includes(view.type)) {
+           Proxmox.Utils.API2Request({
+               url: `/nodes/${rec.data.host}/ceph/cmd-safety`,
+               params: {
+                   service: view.type,
+                   id: rec.data.name,
+                   action: 'stop',
+               },
+               method: 'GET',
+               success: function({ result: { data } }) {
+                   let stopText = {
+                       mon: gettext('Stop MON'),
+                       mds: gettext('Stop MDS'),
+                   };
+                   if (!data.safe) {
+                       Ext.Msg.show({
+                           title: gettext('Warning'),
+                           message: data.status,
+                           icon: Ext.Msg.WARNING,
+                           buttons: Ext.Msg.OKCANCEL,
+                           buttonText: { ok: stopText[view.type] },
+                           fn: function(selection) {
+                               if (selection === 'ok') {
+                                   doRequest();
+                               }
+                           },
+                       });
+                   } else {
+                       doRequest();
+                   }
+               },
+               failure: (response, _opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
            });
+       } else {
+           doRequest();
        }
     },
+    onChangeService: function(button) {
+       let me = this;
+       let record = me.getView().getSelection()[0];
+       me.service_cmd(record, button.action);
+    },
+
+    showSyslog: function() {
+       let view = this.getView();
+       let rec = view.getSelection()[0];
+       let service = `ceph-${view.type}@${rec.data.name}`;
+       Ext.create('Ext.window.Window', {
+           title: `${gettext('Syslog')}: ${service}`,
+           autoShow: true,
+           modal: true,
+           width: 800,
+           height: 400,
+           layout: 'fit',
+           items: [{
+               xtype: 'proxmoxLogView',
+               url: `/api2/extjs/nodes/${rec.data.host}/syslog?service=${encodeURIComponent(service)}`,
+               log_select_timespan: 1,
+           }],
+       });
+    },
+
+    onCreate: function() {
+       let view = this.getView();
+       Ext.create('PVE.CephCreateService', {
+           autoShow: true,
+           nodename: view.nodename,
+           subject: view.getTitle(),
+           type: view.type,
+           taskDone: () => view.rstore.load(),
+       });
+    },
+});
+
+Ext.define('PVE.node.CephServiceList', {
+    extend: 'Ext.grid.GridPanel',
+    xtype: 'pveNodeCephServiceList',
+
+    onlineHelp: 'chapter_pveceph',
+    emptyText: gettext('No such service configured.'),
+
+    stateful: true,
+
+    // will be called when the store loads
+    storeLoadCallback: Ext.emptyFn,
+
+    // if set to true, does shows the ceph install mask if needed
+    showCephInstallMask: false,
+
+    controller: 'CephServiceList',
 
     tbar: [
        {
@@ -243,22 +309,17 @@ Ext.define('PVE.node.CephServiceList', {
            iconCls: 'fa fa-play',
            action: 'start',
            disabled: true,
-           enableFn: function(rec) {
-               return rec.data.state === 'stopped' ||
-                 rec.data.state === 'unknown';
-           },
-           handler: 'onChangeService'
+           enableFn: rec => rec.data.state === 'stopped' || rec.data.state === 'unknown',
+           handler: 'onChangeService',
        },
        {
            xtype: 'proxmoxButton',
            text: gettext('Stop'),
            iconCls: 'fa fa-stop',
            action: 'stop',
-           enableFn: function(rec) {
-               return rec.data.state !== 'stopped';
-           },
+           enableFn: rec => rec.data.state !== 'stopped',
            disabled: true,
-           handler: 'onChangeService'
+           handler: 'onChangeService',
        },
        {
            xtype: 'proxmoxButton',
@@ -266,51 +327,86 @@ Ext.define('PVE.node.CephServiceList', {
            iconCls: 'fa fa-refresh',
            action: 'restart',
            disabled: true,
-           enableFn: function(rec) {
-               return rec.data.state !== 'stopped';
-           },
-           handler: 'onChangeService'
+           enableFn: rec => rec.data.state !== 'stopped',
+           handler: 'onChangeService',
        },
        '-',
        {
            text: gettext('Create'),
            reference: 'createButton',
-           handler: 'onCreate'
+           handler: 'onCreate',
        },
        {
            text: gettext('Destroy'),
            xtype: 'proxmoxStdRemoveButton',
            getUrl: function(rec) {
-               var view = this.up('grid');
+               let view = this.up('grid');
                if (!rec.data.host) {
-                   Ext.Msg.alert(gettext('Error'), "entry has no host");
-                   return;
+                   Ext.Msg.alert(gettext('Error'), "entry has no host, cannot build API url");
+                   return '';
                }
-               return "/nodes/" + rec.data.host + "/ceph/" + view.type + "/" + rec.data.name;
+               return `/nodes/${rec.data.host}/ceph/${view.type}/${rec.data.name}`;
            },
            callback: function(options, success, response) {
-               var view = this.up('grid');
+               let view = this.up('grid');
                if (!success) {
                    Ext.Msg.alert(gettext('Error'), response.htmlStatus);
                    return;
                }
-               var upid = response.result.data;
-               var win = Ext.create('Proxmox.window.TaskProgress', {
-                   upid: upid,
-                   taskDone: function() {
-                       view.rstore.load();
-                   }
+               Ext.create('Proxmox.window.TaskProgress', {
+                   autoShow: true,
+                   upid: response.result.data,
+                   taskDone: () => view.rstore.load(),
                });
-               win.show();
-           }
+           },
+           handler: function(btn, event, rec) {
+               let me = this;
+               let view = me.up('grid');
+               let doRequest = function() {
+                   Proxmox.button.StdRemoveButton.prototype.handler.call(me, btn, event, rec);
+               };
+               if (view.type === 'mon') {
+                   Proxmox.Utils.API2Request({
+                       url: `/nodes/${rec.data.host}/ceph/cmd-safety`,
+                       params: {
+                           service: view.type,
+                           id: rec.data.name,
+                           action: 'destroy',
+                       },
+                       method: 'GET',
+                       success: function({ result: { data } }) {
+                           if (!data.safe) {
+                               Ext.Msg.show({
+                                   title: gettext('Warning'),
+                                   message: data.status,
+                                   icon: Ext.Msg.WARNING,
+                                   buttons: Ext.Msg.OKCANCEL,
+                                   buttonText: { ok: gettext('Destroy MON') },
+                                   fn: function(selection) {
+                                       if (selection === 'ok') {
+                                           doRequest();
+                                       }
+                                   },
+                               });
+                           } else {
+                               doRequest();
+                           }
+                       },
+                       failure: (response, _opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
+                   });
+               } else {
+                   doRequest();
+               }
+           },
+
        },
        '-',
        {
            xtype: 'proxmoxButton',
            text: gettext('Syslog'),
            disabled: true,
-           handler: 'showSyslog'
-       }
+           handler: 'showSyslog',
+       },
     ],
 
     columns: [
@@ -321,7 +417,7 @@ Ext.define('PVE.node.CephServiceList', {
            renderer: function(v) {
                return this.type + '.' + v;
            },
-           dataIndex: 'name'
+           dataIndex: 'name',
        },
        {
            header: gettext('Host'),
@@ -330,13 +426,14 @@ Ext.define('PVE.node.CephServiceList', {
            renderer: function(v) {
                return v || Proxmox.Utils.unknownText;
            },
-           dataIndex: 'host'
+           dataIndex: 'host',
        },
        {
            header: gettext('Status'),
            flex: 1,
            sortable: false,
-           dataIndex: 'state'
+           renderer: 'render_status',
+           dataIndex: 'state',
        },
        {
            header: gettext('Address'),
@@ -345,7 +442,7 @@ Ext.define('PVE.node.CephServiceList', {
            renderer: function(v) {
                return v || Proxmox.Utils.unknownText;
            },
-           dataIndex: 'addr'
+           dataIndex: 'addr',
        },
        {
            header: gettext('Version'),
@@ -353,29 +450,55 @@ Ext.define('PVE.node.CephServiceList', {
            sortable: true,
            dataIndex: 'version',
            renderer: 'render_version',
-       }
+       },
     ],
 
     initComponent: function() {
-       var me = this;
+       let me = this;
 
        if (me.additionalColumns) {
            me.columns = me.columns.concat(me.additionalColumns);
        }
 
        me.callParent();
-    }
+    },
 
 }, function() {
-
     Ext.define('ceph-service-list', {
        extend: 'Ext.data.Model',
-       fields: [ 'addr', 'name', 'rank', 'host', 'quorum', 'state',
-           'ceph_version', 'ceph_version_short',
-           { type: 'string', name: 'version', calculate: function(data) {
-               return PVE.Utils.parse_ceph_version(data);
-           } }
+       fields: [
+           'addr',
+           'name',
+           'fs_name',
+           'rank',
+           'host',
+           'quorum',
+           'state',
+           'ceph_version',
+           'ceph_version_short',
+           {
+               type: 'string',
+               name: 'version',
+               calculate: data => PVE.Utils.parse_ceph_version(data),
+           },
        ],
-       idProperty: 'name'
+       idProperty: 'name',
     });
 });
+
+Ext.define('PVE.node.CephMDSServiceController', {
+    extend: 'PVE.node.CephServiceController',
+    alias: 'controller.CephServiceMDSList',
+
+    render_status: (value, mD, rec) => rec.data.fs_name ? `${value} (${rec.data.fs_name})` : value,
+});
+
+Ext.define('PVE.node.CephMDSList', {
+    extend: 'PVE.node.CephServiceList',
+    xtype: 'pveNodeCephMDSList',
+
+    controller: {
+       type: 'CephServiceMDSList',
+    },
+});
+