]> git.proxmox.com Git - proxmox-widget-toolkit.git/blobdiff - src/node/APTRepositories.js
ui: repo status: adapt flex-ratio and center health vertically
[proxmox-widget-toolkit.git] / src / node / APTRepositories.js
index 99955506d1ea907e81af48999c7a9e3431d59733..5c1091b46758f555bb981606a39c8cf761c30f24 100644 (file)
@@ -60,21 +60,24 @@ Ext.define('Proxmox.window.APTRepositoryAdd', {
            name: 'handle',
            allowBlank: false,
            comboItems: me.repoInfo.map(info => [info.handle, info.name]),
-           isValid: function() {
-               const handle = this.value;
+           validator: function(renderedValue) {
+               let handle = this.value;
+               // we cannot use this.callParent in instantiations
+               let valid = Proxmox.form.KVComboBox.prototype.validator.call(this, renderedValue);
 
-               if (!handle) {
+               if (!valid || !handle) {
                    return false;
                }
 
                const info = me.repoInfo.find(elem => elem.handle === handle);
-
                if (!info) {
                    return false;
                }
 
-               // not yet configured
-               return info.status === undefined || info.status === null;
+               if (info.status) {
+                   return Ext.String.format(gettext('{0} is already configured'), renderedValue);
+               }
+               return valid;
            },
            listeners: {
                change: function(f, value) {
@@ -105,27 +108,31 @@ Ext.define('Proxmox.node.APTRepositoriesErrors', {
 
     xtype: 'proxmoxNodeAPTRepositoriesErrors',
 
-    title: gettext('Errors'),
-
     store: {},
 
-    border: false,
+    scrollable: true,
 
     viewConfig: {
        stripeRows: false,
-       getRowClass: () => 'proxmox-invalid-row',
+       getRowClass: (record) => {
+           switch (record.data.status) {
+               case 'warning': return 'proxmox-warning-row';
+               case 'critical': return 'proxmox-invalid-row';
+               default: return '';
+           }
+       },
     },
 
+    hideHeaders: true,
+
     columns: [
        {
-           header: gettext('File'),
-           dataIndex: 'path',
-           renderer: value => `<i class='pve-grid-fa fa fa-fw fa-exclamation-triangle'></i>${value}`,
-           width: 350,
+           dataIndex: 'status',
+           renderer: (value) => `<i class="fa fa-fw ${Proxmox.Utils.get_health_icon(value, true)}"></i>`,
+           width: 50,
        },
        {
-           header: gettext('Error'),
-           dataIndex: 'error',
+           dataIndex: 'message',
            flex: 1,
        },
     ],
@@ -167,7 +174,7 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
 
                    Ext.create('Proxmox.window.APTRepositoryAdd', {
                        repoInfo: me.repoInfo,
-                       url: `/api2/json/nodes/${panel.nodename}/apt/repositories`,
+                       url: `/api2/extjs/nodes/${panel.nodename}/apt/repositories`,
                        method: 'PUT',
                        extraRequestParams: extraParams,
                        listeners: {
@@ -233,6 +240,10 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
     ],
 
     sortableColumns: false,
+    viewConfig: {
+       stripeRows: false,
+       getRowClass: (record, index) => record.get('Enabled') ? '' : 'proxmox-disabled-row',
+    },
 
     columns: [
        {
@@ -273,8 +284,13 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
                        }
                    });
                    metaData.tdAttr = `data-qtip="${Ext.htmlEncode(txt.join('<br>'))}"`;
-                   metaData.tdCls = 'proxmox-invalid-row';
-                   err = '<i class="fa fa-fw critical fa-exclamation-circle"></i> ';
+                   if (record.data.Enabled) {
+                       metaData.tdCls = 'proxmox-invalid-row';
+                       err = '<i class="fa fa-fw critical fa-exclamation-circle"></i> ';
+                   } else {
+                       metaData.tdCls = 'proxmox-warning-row';
+                       err = '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
+                   }
                }
                return suites.join(' ') + err;
            },
@@ -283,8 +299,23 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
        {
            header: gettext('Components'),
            dataIndex: 'Components',
-           renderer: function(components, cell, record) {
-               return components.join(' ');
+           renderer: function(components, metaData, record) {
+               let err = '';
+               if (components.length === 1) {
+                   // FIXME: this should be a flag set to the actual repsotiories, i.e., a tristate
+                   // like production-ready = <yes|no|other> (Option<bool>)
+                   if (components[0].match(/\w+(-no-subscription|test)\s*$/i)) {
+                       metaData.tdCls = 'proxmox-warning-row';
+                       err = '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
+
+                       let qtip = components[0].match(/no-subscription/)
+                           ? gettext('The no-subscription repository is NOT production-ready')
+                           : gettext('The test repository may contain unstable updates')
+                           ;
+                           metaData.tdAttr = `data-qtip="${Ext.htmlEncode(qtip)}"`;
+                   }
+               }
+               return components.join(' ') + err;
            },
            width: 170,
        },
@@ -320,6 +351,9 @@ Ext.define('Proxmox.node.APTRepositoriesGrid', {
            dataIndex: 'Origin',
            width: 120,
            renderer: (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)) {
                    cls = 'pmx-itype-icon pmx-itype-icon-proxmox-x';
@@ -389,46 +423,100 @@ Ext.define('Proxmox.node.APTRepositories', {
            let rec = selection[0];
            let vm = me.getViewModel();
            vm.set('selectionenabled', rec.get('Enabled'));
+           vm.notify();
+       },
+
+       updateState: function() {
+           let me = this;
+           let vm = me.getViewModel();
+
+           let store = vm.get('errorstore');
+           store.removeAll();
+
+           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 errors = vm.get('errors');
+           errors.forEach((error) => {
+               status = 'critical';
+               store.add({
+                   status: 'critical',
+                   message: `${error.path} - ${error.error}`,
+               });
+           });
+
+           let addGood = message => store.add({ status: 'good', message });
+
+           let addWarn = message => {
+               if (status === 'good') {
+                   status = 'warning';
+                   text = message;
+               }
+               store.add({ status: 'warning', message });
+           };
+
+           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');
+
+           if (!enterprise && !nosubscription && !test) {
+               addWarn(Ext.String.format(gettext('No {0} repository is enabled!'), vm.get('product')));
+           } else if (enterprise && !nosubscription && !test && activeSubscription) {
+               addGood(Ext.String.format(gettext('You get supported updates for {0}'), vm.get('product')));
+           } else if (nosubscription || test) {
+               addGood(Ext.String.format(gettext('You get updates for {0}'), vm.get('product')));
+           }
+
+           if (wrongSuites) {
+               addWarn(gettext('Some Suites are misconfigured'));
+           }
+
+           if (!activeSubscription && enterprise) {
+               addWarn(gettext('The enterprise repository is enabled, but there is no active subscription!'));
+           }
+
+           if (nosubscription) {
+               addWarn(gettext('The no-subscription repository is not recommended for production use!'));
+           }
+
+           if (test) {
+               addWarn(gettext('The test repository is not recommended for production use!'));
+           }
+
+           if (errors.length > 0) {
+               text = gettext('Error parsing repositories');
+           }
+
+           let iconCls = Proxmox.Utils.get_health_icon(status, true);
+
+           vm.set('state', {
+               iconCls,
+               text,
+           });
        },
     },
 
     viewModel: {
        data: {
            product: 'Proxmox VE', // default
-           errorCount: 0,
+           errors: [],
+           suitesWarning: false,
            subscriptionActive: '',
            noSubscriptionRepo: '',
            enterpriseRepo: '',
+           testRepo: '',
            selectionenabled: false,
+           state: {},
        },
        formulas: {
-           noErrors: (get) => get('errorCount') === 0,
            enableButtonText: (get) => get('selectionenabled')
                ? gettext('Disable') : gettext('Enable'),
-           mainWarning: function(get) {
-               // Not yet initialized
-               if (get('subscriptionActive') === '' ||
-                   get('enterpriseRepo') === '') {
-                   return '';
-               }
-
-               let icon = `<i class='fa fa-fw fa-exclamation-triangle critical'></i>`;
-               let fmt = (msg) => `<div class="black">${icon}${gettext('Warning')}: ${msg}</div>`;
-
-               if (!get('subscriptionActive') && get('enterpriseRepo')) {
-                   return fmt(gettext('The enterprise repository is enabled, but there is no active subscription!'));
-               }
-
-               if (get('noSubscriptionRepo')) {
-                   return fmt(gettext('The no-subscription repository is not recommended for production use!'));
-               }
-
-               if (!get('enterpriseRepo') && !get('noSubscriptionRepo')) {
-                   let msg = Ext.String.format(gettext('No {0} repository is enabled!'), get('product'));
-                   return fmt(msg);
-               }
-
-               return '';
+       },
+       stores: {
+           errorstore: {
+               fields: ['status', 'message'],
            },
        },
     },
@@ -441,32 +529,49 @@ Ext.define('Proxmox.node.APTRepositories', {
 
     items: [
        {
-           xtype: 'header',
-           baseCls: 'x-panel-header',
-           bind: {
-               hidden: '{!mainWarning}',
-               title: '{mainWarning}',
-           },
-       },
-       {
-           xtype: 'box',
-           bind: {
-               hidden: '{!mainWarning}',
-           },
-           height: 5,
-       },
-       {
-           xtype: 'proxmoxNodeAPTRepositoriesErrors',
-           name: 'repositoriesErrors',
-           hidden: true,
-           padding: '0 0 5 0',
-           bind: {
-               hidden: '{noErrors}',
+           xtype: 'panel',
+           border: false,
+           layout: {
+               type: 'hbox',
+               align: 'stretch',
            },
+           height: 200,
+           title: gettext('Status'),
+           items: [
+               {
+                   xtype: 'box',
+                   flex: 2,
+                   margin: 10,
+                   data: {
+                       iconCls: Proxmox.Utils.get_health_icon(undefined, true),
+                       text: '',
+                   },
+                   bind: {
+                       data: '{state}',
+                   },
+                   tpl: [
+                       '<center class="centered-flex-column" style="font-size:15px;line-height: 25px;">',
+                       '<i class="fa fa-4x {iconCls}"></i>',
+                       '<br/><br/>',
+                       '{text}',
+                       '</center>',
+                   ],
+               },
+               {
+                   xtype: 'proxmoxNodeAPTRepositoriesErrors',
+                   name: 'repositoriesErrors',
+                   flex: 7,
+                   margin: 10,
+                   bind: {
+                       store: '{errorstore}',
+                   },
+               },
+           ],
        },
        {
            xtype: 'proxmoxNodeAPTRepositoriesGrid',
            name: 'repositoriesGrid',
+           flex: 1,
            cbind: {
                nodename: '{nodename}',
            },
@@ -489,6 +594,7 @@ Ext.define('Proxmox.node.APTRepositories', {
                const res = response.result;
                const subscription = !(!res || !res.data || res.data.status.toLowerCase() !== 'active');
                vm.set('subscriptionActive', subscription);
+               me.getController().updateState();
            },
        });
     },
@@ -508,7 +614,10 @@ Ext.define('Proxmox.node.APTRepositories', {
                vm.set('enterpriseRepo', status);
            } else if (handle === "no-subscription") {
                vm.set('noSubscriptionRepo', status);
+           } else if (handle === 'test') {
+               vm.set('testRepo', status);
            }
+           me.getController().updateState();
 
            addButton.repoInfo.push(standardRepo);
            addButton.digest = me.digest;
@@ -521,12 +630,12 @@ Ext.define('Proxmox.node.APTRepositories', {
        let me = this;
        let vm = me.getViewModel();
        let repoGrid = me.down('proxmoxNodeAPTRepositoriesGrid');
-       let errorGrid = me.down('proxmoxNodeAPTRepositoriesErrors');
 
        me.store.load(function(records, operation, success) {
            let gridData = [];
            let errors = [];
            let digest;
+           let suitesWarning = false;
 
            if (success && records.length > 0) {
                let data = records[0].data;
@@ -555,6 +664,9 @@ Ext.define('Proxmox.node.APTRepositories', {
                        (info.kind === 'ignore-pre-upgrade-warning' && !repoGrid.majorUpgradeAllowed)
                    ) {
                        infos[path][idx].warnings.push(info);
+                       if (!suitesWarning && info.property === 'Suites') {
+                           suitesWarning = true;
+                       }
                    } else {
                        throw 'unknown info';
                    }
@@ -581,8 +693,9 @@ Ext.define('Proxmox.node.APTRepositories', {
 
            me.digest = digest;
 
-           vm.set('errorCount', errors.length);
-           errorGrid.store.loadData(errors);
+           vm.set('errors', errors);
+           vm.set('suitesWarning', suitesWarning);
+           me.getController().updateState();
        });
 
        me.check_subscription();