Ext.define('Proxmox.node.APTRepositoriesGrid', {
extend: 'Ext.grid.GridPanel',
xtype: 'proxmoxNodeAPTRepositoriesGrid',
+ mixins: ['Proxmox.Mixin.CBind'],
title: gettext('APT Repositories'),
name: 'addRepo',
disabled: true,
repoInfo: undefined,
+ cbind: {
+ onlineHelp: '{onlineHelp}',
+ },
handler: function(button, event, record) {
Proxmox.Utils.checked_command(() => {
let me = this;
url: `/api2/extjs/nodes/${panel.nodename}/apt/repositories`,
method: 'PUT',
extraRequestParams: extraParams,
+ onlineHelp: me.onlineHelp,
listeners: {
destroy: function() {
panel.reload();
},
'-',
{
- xtype: 'proxmoxButton',
- text: gettext('Enable'),
+ xtype: 'proxmoxAltTextButton',
defaultText: gettext('Enable'),
altText: gettext('Disable'),
name: 'repoEnable',
},
});
},
- 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 });
- },
- },
},
],
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,
},
{
let values = option.Values.join(' ');
text += `${key}: ${values}<br>`;
} else {
- throw "unkown file type";
+ throw "unknown file type";
}
});
return text;
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}`;
header: gettext('Comment'),
dataIndex: 'Comment',
flex: 2,
+ renderer: Ext.String.htmlEncode,
},
],
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',
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, important) => {
if (status !== 'critical') {
status = '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 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, you do not get any updates!'), 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) {
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 may pull in unstable updates and 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) {
- text = gettext('Fatal error when parsing at least one repository');
+ text = gettext('Fatal parsing error for at least one repository');
}
let iconCls = Proxmox.Utils.get_health_icon(status, true);
product: 'Proxmox VE', // default
errors: [],
suitesWarning: false,
+ mixedSuites: false, // used before major upgrade
subscriptionActive: '',
noSubscriptionRepo: '',
enterpriseRepo: '',
testRepo: '',
+ cephEnterpriseRepo: '',
+ cephNoSubscriptionRepo: '',
+ cephTestRepo: '',
selectionenabled: false,
state: {},
},
flex: 1,
cbind: {
nodename: '{nodename}',
+ onlineHelp: '{onlineHelp}',
},
majorUpgradeAllowed: false, // TODO get release information from an API call?
listeners: {
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();
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;
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';
}
}
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);
}
vm.set('errors', errors);
vm.set('suitesWarning', suitesWarning);
+ vm.set('mixedSuites', mixedSuites);
me.getController().updateState();
});