]> git.proxmox.com Git - proxmox-widget-toolkit.git/blobdiff - src/node/APTRepositories.js
bump version to 4.2.3
[proxmox-widget-toolkit.git] / src / node / APTRepositories.js
index eb22ba1cb2426d51fabdc40c6eab33ae0c96bf90..4e74da20b13e17a518bb20b79b44d9260af6c3e2 100644 (file)
@@ -141,6 +141,7 @@ Ext.define('Proxmox.node.APTRepositoriesErrors', {
 Ext.define('Proxmox.node.APTRepositoriesGrid', {
     extend: 'Ext.grid.GridPanel',
     xtype: 'proxmoxNodeAPTRepositoriesGrid',
+    mixins: ['Proxmox.Mixin.CBind'],
 
     title: gettext('APT Repositories'),
 
@@ -159,9 +160,12 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
        },
        {
            text: gettext('Add'),
-           id: 'addButton',
+           name: 'addRepo',
            disabled: true,
            repoInfo: undefined,
+           cbind: {
+               onlineHelp: '{onlineHelp}',
+           },
            handler: function(button, event, record) {
                Proxmox.Utils.checked_command(() => {
                    let me = this;
@@ -177,6 +181,7 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
                        url: `/api2/extjs/nodes/${panel.nodename}/apt/repositories`,
                        method: 'PUT',
                        extraRequestParams: extraParams,
+                       onlineHelp: me.onlineHelp,
                        listeners: {
                            destroy: function() {
                                panel.reload();
@@ -188,11 +193,10 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
        },
        '-',
        {
-           xtype: 'proxmoxButton',
-           text: gettext('Enable'),
+           xtype: 'proxmoxAltTextButton',
            defaultText: gettext('Enable'),
            altText: gettext('Disable'),
-           id: 'repoEnableButton',
+           name: 'repoEnable',
            disabled: true,
            bind: {
                text: '{enableButtonText}',
@@ -224,18 +228,6 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
                    },
                });
            },
-           listeners: {
-               render: function(btn) {
-                   // HACK: calculate the max button width on first render to avoid toolbar glitches
-                   let defSize = btn.getSize().width;
-
-                   btn.setText(btn.altText);
-                   let altSize = btn.getSize().width;
-
-                   btn.setText(btn.defaultText);
-                   btn.setSize({ width: altSize > defSize ? altSize : defSize });
-               },
-           },
        },
     ],
 
@@ -247,12 +239,10 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
 
     columns: [
        {
-           xtype: 'checkcolumn',
            header: gettext('Enabled'),
            dataIndex: 'Enabled',
-           listeners: {
-               beforecheckchange: () => false, // veto, we don't want to allow inline change - to subtle
-           },
+           align: 'center',
+           renderer: Proxmox.Utils.renderEnabledIcon,
            width: 90,
        },
        {
@@ -300,6 +290,9 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
            header: gettext('Components'),
            dataIndex: 'Components',
            renderer: function(components, metaData, record) {
+               if (components === undefined) {
+                   return '';
+               }
                let err = '';
                if (components.length === 1) {
                    // FIXME: this should be a flag set to the actual repsotiories, i.e., a tristate
@@ -339,7 +332,7 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
                        let values = option.Values.join(' ');
                        text += `${key}: ${values}<br>`;
                    } else {
-                       throw "unkown file type";
+                       throw "unknown file type";
                    }
                });
                return text;
@@ -350,14 +343,15 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
            header: gettext('Origin'),
            dataIndex: 'Origin',
            width: 120,
-           renderer: (value, meta, rec) => {
+           renderer: function(value, meta, rec) {
                if (typeof value !== 'string' || value.length === 0) {
                    value = gettext('Other');
                }
                let cls = 'fa fa-fw fa-question-circle-o';
-               if (value.match(/^\s*Proxmox\s*$/i)) {
+               let originType = this.up('proxmoxNodeAPTRepositories').classifyOrigin(value);
+               if (originType === 'Proxmox') {
                    cls = 'pmx-itype-icon pmx-itype-icon-proxmox-x';
-               } else if (value.match(/^\s*Debian\s*$/i)) {
+               } else if (originType === 'Debian') {
                    cls = 'pmx-itype-icon pmx-itype-icon-debian-swirl';
                }
                return `<i class='${cls}'></i> ${value}`;
@@ -367,9 +361,29 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
            header: gettext('Comment'),
            dataIndex: 'Comment',
            flex: 2,
+           renderer: Ext.String.htmlEncode,
+       },
+    ],
+
+    features: [
+       {
+           ftype: 'grouping',
+           groupHeaderTpl: '{[ "File: " + values.name ]} ({rows.length} repositor{[values.rows.length > 1 ? "ies" : "y"]})',
+           enableGroupingMenu: false,
        },
     ],
 
+    store: {
+       model: 'apt-repolist',
+       groupField: 'Path',
+       sorters: [
+           {
+               property: 'Index',
+               direction: 'ASC',
+           },
+       ],
+    },
+
     initComponent: function() {
        let me = this;
 
@@ -377,28 +391,6 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
            throw "no node name specified";
        }
 
-       let store = Ext.create('Ext.data.Store', {
-           model: 'apt-repolist',
-           groupField: 'Path',
-           sorters: [
-               {
-                   property: 'Index',
-                   direction: 'ASC',
-               },
-           ],
-       });
-
-       let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
-           groupHeaderTpl: '{[ "File: " + values.name ]} ({rows.length} ' +
-               'repositor{[values.rows.length > 1 ? "ies" : "y"]})',
-           enableGroupingMenu: false,
-       });
-
-       Ext.apply(me, {
-           store: store,
-           features: [groupingFeature],
-       });
-
        me.callParent();
     },
 });
@@ -410,8 +402,20 @@ Ext.define('Proxmox.node.APTRepositories', {
 
     digest: undefined,
 
+    onlineHelp: undefined,
+
     product: 'Proxmox VE', // default
 
+    classifyOrigin: function(origin) {
+       origin ||= '';
+       if (origin.match(/^\s*Proxmox\s*$/i)) {
+           return 'Proxmox';
+       } else if (origin.match(/^\s*Debian\s*(:?Backports)?$/i)) {
+           return 'Debian';
+       }
+       return 'Other';
+    },
+
     controller: {
        xclass: 'Ext.app.ViewController',
 
@@ -433,41 +437,44 @@ Ext.define('Proxmox.node.APTRepositories', {
            let store = vm.get('errorstore');
            store.removeAll();
 
-           let errors = vm.get('errors');
-           errors.forEach((error) => {
-               store.add({
-                   status: 'critical',
-                   message: `${error.path} - ${error.error}`,
-               });
-           });
+           let status = 'good'; // start with best, the helper below will downgrade if needed
+           let text = gettext('All OK, you have production-ready repositories configured!');
 
-           let text = gettext('Repositories are configured in a recommended way');
-           let status = 'good';
+           let addGood = message => store.add({ status: 'good', message });
+           let addWarn = (message, important) => {
+               if (status !== 'critical') {
+                   status = 'warning';
+                   text = important ? message : gettext('Warning');
+               }
+               store.add({ status: 'warning', message });
+           };
+           let addCritical = (message, important) => {
+               status = 'critical';
+               text = important ? message : gettext('Error');
+               store.add({ status: 'critical', message });
+           };
+
+           let errors = vm.get('errors');
+           errors.forEach(error => addCritical(`${error.path} - ${error.error}`));
 
            let activeSubscription = vm.get('subscriptionActive');
            let enterprise = vm.get('enterpriseRepo');
            let nosubscription = vm.get('noSubscriptionRepo');
            let test = vm.get('testRepo');
-           let wrongSuites = vm.get('suitesWarning');
-
-           let addGood = function(message) {
-               store.add({
-                   status: 'good',
-                   message,
-               });
-           };
-
-           let addWarn = function(message) {
-               status = 'warning';
-               text = message;
-               store.add({
-                   status,
-                   message,
-               });
+           let cephRepos = {
+               enterprise: vm.get('cephEnterpriseRepo'),
+               nosubscription: vm.get('cephNoSubscriptionRepo'),
+               test: vm.get('cephTestRepo'),
            };
+           let wrongSuites = vm.get('suitesWarning');
+           let mixedSuites = vm.get('mixedSuites');
 
            if (!enterprise && !nosubscription && !test) {
-               addWarn(Ext.String.format(gettext('No {0} repository is enabled!'), vm.get('product')));
+               addCritical(
+                   Ext.String.format(gettext('No {0} repository is enabled, you do not get any updates!'), vm.get('product')),
+               );
+           } else if (errors.length > 0) {
+               // nothing extra, just avoid that we show "get updates"
            } else if (enterprise && !nosubscription && !test && activeSubscription) {
                addGood(Ext.String.format(gettext('You get supported updates for {0}'), vm.get('product')));
            } else if (nosubscription || test) {
@@ -475,27 +482,43 @@ Ext.define('Proxmox.node.APTRepositories', {
            }
 
            if (wrongSuites) {
-               addWarn(gettext('Some Suites are misconfigured'));
+               addWarn(gettext('Some suites are misconfigured'));
            }
 
-           if (!activeSubscription && enterprise) {
-               addWarn(gettext('The enterprise repository is enabled, but there is no active subscription!'));
+           if (mixedSuites) {
+               addWarn(gettext('Detected mixed suites before upgrade'));
            }
 
-           if (nosubscription) {
-               addWarn(gettext('The no-subscription repository is not recommended for production use!'));
-           }
+           let productionReadyCheck = (repos, type, noSubAlternateName) => {
+               if (!activeSubscription && repos.enterprise) {
+                   addWarn(Ext.String.format(
+                       gettext('The {0}enterprise repository is enabled, but there is no active subscription!'),
+                       type,
+                   ));
+               }
 
-           if (test) {
-               addWarn(gettext('The test repository is not recommended for production use!'));
-           }
+               if (repos.nosubscription) {
+                   addWarn(Ext.String.format(
+                       gettext('The {0}no-subscription{1} repository is not recommended for production use!'),
+                       type,
+                       noSubAlternateName,
+                   ));
+               }
+
+               if (repos.test) {
+                   addWarn(Ext.String.format(
+                       gettext('The {0}test repository may pull in unstable updates and is not recommended for production use!'),
+                       type,
+                   ));
+               }
+           };
+
+           productionReadyCheck({ enterprise, nosubscription, test }, '', '');
+           // TODO drop alternate 'main' name when no longer relevant
+           productionReadyCheck(cephRepos, 'Ceph ', '/main');
 
            if (errors.length > 0) {
-               vm.set('state', {
-                   iconCls: Proxmox.Utils.get_health_icon('critical', true),
-                   text: gettext('Error parsing repositories'),
-               });
-               return;
+               text = gettext('Fatal parsing error for at least one repository');
            }
 
            let iconCls = Proxmox.Utils.get_health_icon(status, true);
@@ -512,10 +535,14 @@ Ext.define('Proxmox.node.APTRepositories', {
            product: 'Proxmox VE', // default
            errors: [],
            suitesWarning: false,
+           mixedSuites: false, // used before major upgrade
            subscriptionActive: '',
            noSubscriptionRepo: '',
            enterpriseRepo: '',
            testRepo: '',
+           cephEnterpriseRepo: '',
+           cephNoSubscriptionRepo: '',
+           cephTestRepo: '',
            selectionenabled: false,
            state: {},
        },
@@ -549,7 +576,7 @@ Ext.define('Proxmox.node.APTRepositories', {
            items: [
                {
                    xtype: 'box',
-                   flex: 1,
+                   flex: 2,
                    margin: 10,
                    data: {
                        iconCls: Proxmox.Utils.get_health_icon(undefined, true),
@@ -559,9 +586,8 @@ Ext.define('Proxmox.node.APTRepositories', {
                        data: '{state}',
                    },
                    tpl: [
-                       '<center>',
+                       '<center class="centered-flex-column" style="font-size:15px;line-height: 25px;">',
                        '<i class="fa fa-4x {iconCls}"></i>',
-                       '<br/><br/>',
                        '{text}',
                        '</center>',
                    ],
@@ -569,7 +595,7 @@ Ext.define('Proxmox.node.APTRepositories', {
                {
                    xtype: 'proxmoxNodeAPTRepositoriesErrors',
                    name: 'repositoriesErrors',
-                   flex: 2,
+                   flex: 7,
                    margin: 10,
                    bind: {
                        store: '{errorstore}',
@@ -583,6 +609,7 @@ Ext.define('Proxmox.node.APTRepositories', {
            flex: 1,
            cbind: {
                nodename: '{nodename}',
+               onlineHelp: '{onlineHelp}',
            },
            majorUpgradeAllowed: false, // TODO get release information from an API call?
            listeners: {
@@ -612,9 +639,9 @@ Ext.define('Proxmox.node.APTRepositories', {
        let me = this;
        let vm = me.getViewModel();
 
-       let addButton = me.down('#addButton');
-       addButton.repoInfo = [];
+       let addButton = me.down('button[name=addRepo]');
 
+       addButton.repoInfo = [];
        for (const standardRepo of standardRepos) {
            const handle = standardRepo.handle;
            const status = standardRepo.status;
@@ -625,6 +652,12 @@ Ext.define('Proxmox.node.APTRepositories', {
                vm.set('noSubscriptionRepo', status);
            } else if (handle === 'test') {
                vm.set('testRepo', status);
+           } else if (handle.match(/^ceph-[a-zA-Z]+-enterprise$/)) {
+               vm.set('cephEnterpriseRepo', status);
+           } else if (handle.match(/^ceph-[a-zA-Z]+-no-subscription$/)) {
+               vm.set('cephNoSubscriptionRepo', status);
+           } else if (handle.match(/^ceph-[a-zA-Z]+-test$/)) {
+               vm.set('cephTestRepo', status);
            }
            me.getController().updateState();
 
@@ -646,6 +679,11 @@ Ext.define('Proxmox.node.APTRepositories', {
            let digest;
            let suitesWarning = false;
 
+           // Usually different suites will give errors anyways, but before a major upgrade the
+           // current and the next suite are allowed, so it makes sense to check for mixed suites.
+           let checkMixedSuites = false;
+           let mixedSuites = false;
+
            if (success && records.length > 0) {
                let data = records[0].data;
                let files = data.files;
@@ -664,20 +702,23 @@ Ext.define('Proxmox.node.APTRepositories', {
                        infos[path][idx] = {
                            origin: '',
                            warnings: [],
+                           // Used as a heuristic to detect mixed repositories pre-upgrade. The
+                           // warning is set on all repositories that do configure the next suite.
+                           gotIgnorePreUpgradeWarning: false,
                        };
                    }
 
                    if (info.kind === 'origin') {
                        infos[path][idx].origin = info.message;
-                   } else if (info.kind === 'warning' ||
-                       (info.kind === 'ignore-pre-upgrade-warning' && !repoGrid.majorUpgradeAllowed)
-                   ) {
+                   } else if (info.kind === 'warning') {
                        infos[path][idx].warnings.push(info);
-                       if (!suitesWarning && info.property === 'Suites') {
-                           suitesWarning = true;
+                   } else if (info.kind === 'ignore-pre-upgrade-warning') {
+                       infos[path][idx].gotIgnorePreUpgradeWarning = true;
+                       if (!repoGrid.majorUpgradeAllowed) {
+                           infos[path][idx].warnings.push(info);
+                       } else {
+                           checkMixedSuites = true;
                        }
-                   } else {
-                       throw 'unknown info';
                    }
                }
 
@@ -688,8 +729,25 @@ Ext.define('Proxmox.node.APTRepositories', {
                        repo.Path = file.path;
                        repo.Index = n;
                        if (infos[file.path] && infos[file.path][n]) {
-                           repo.Origin = infos[file.path][n].origin || Proxmox.Utils.UnknownText;
+                           repo.Origin = infos[file.path][n].origin || Proxmox.Utils.unknownText;
                            repo.warnings = infos[file.path][n].warnings || [];
+
+                           if (repo.Enabled) {
+                               if (repo.warnings.some(w => w.property === 'Suites')) {
+                                   suitesWarning = true;
+                               }
+
+                               let originType = me.classifyOrigin(repo.Origin);
+                               // Only Proxmox and Debian repositories checked here, because the
+                               // warning can be missing for others for a different reason (e.g.
+                               // using 'stable' or non-Debian code names).
+                               if (checkMixedSuites && repo.Types.includes('deb') &&
+                                   (originType === 'Proxmox' || originType === 'Debian') &&
+                                   !infos[file.path][n].gotIgnorePreUpgradeWarning
+                               ) {
+                                   mixedSuites = true;
+                               }
+                           }
                        }
                        gridData.push(repo);
                    }
@@ -704,6 +762,7 @@ Ext.define('Proxmox.node.APTRepositories', {
 
            vm.set('errors', errors);
            vm.set('suitesWarning', suitesWarning);
+           vm.set('mixedSuites', mixedSuites);
            me.getController().updateState();
        });