fields: [
'Path',
'Index',
- 'OfficialHost',
+ 'Origin',
'FileType',
'Enabled',
'Comment',
],
});
+Ext.define('Proxmox.window.APTRepositoryAdd', {
+ extend: 'Proxmox.window.Edit',
+ alias: 'widget.pmxAPTRepositoryAdd',
+
+ isCreate: true,
+ isAdd: true,
+
+ subject: gettext('Repository'),
+ width: 600,
+
+ initComponent: function() {
+ let me = this;
+
+ if (!me.repoInfo || me.repoInfo.length === 0) {
+ throw "repository information not initialized";
+ }
+
+ let description = Ext.create('Ext.form.field.Display', {
+ fieldLabel: gettext('Description'),
+ name: 'description',
+ });
+
+ let status = Ext.create('Ext.form.field.Display', {
+ fieldLabel: gettext('Status'),
+ name: 'status',
+ renderer: function(value) {
+ let statusText = gettext('Not yet configured');
+ if (value !== '') {
+ statusText = Ext.String.format(
+ '{0}: {1}',
+ gettext('Configured'),
+ value ? gettext('enabled') : gettext('disabled'),
+ );
+ }
+
+ return statusText;
+ },
+ });
+
+ let repoSelector = Ext.create('Proxmox.form.KVComboBox', {
+ fieldLabel: gettext('Repository'),
+ xtype: 'proxmoxKVComboBox',
+ name: 'handle',
+ allowBlank: false,
+ comboItems: me.repoInfo.map(info => [info.handle, info.name]),
+ isValid: function() {
+ const handle = this.value;
+
+ if (!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;
+ },
+ listeners: {
+ change: function(f, value) {
+ const info = me.repoInfo.find(elem => elem.handle === value);
+ description.setValue(info.description);
+ status.setValue(info.status);
+ },
+ },
+ });
+
+ repoSelector.setValue(me.repoInfo[0].handle);
+
+ Ext.apply(me, {
+ items: [
+ repoSelector,
+ description,
+ status,
+ ],
+ repoSelector: repoSelector,
+ });
+
+ me.callParent();
+ },
+});
+
Ext.define('Proxmox.node.APTRepositoriesErrors', {
extend: 'Ext.grid.GridPanel',
store: {},
+ border: false,
+
viewConfig: {
stripeRows: false,
getRowClass: () => 'proxmox-invalid-row',
title: gettext('APT Repositories'),
+ cls: 'proxmox-apt-repos', // to allow applying styling to general components with local effect
+
+ border: false,
+
tbar: [
{
text: gettext('Reload'),
},
{
text: gettext('Add'),
- menu: {
- plain: true,
- itemId: "addMenu",
- items: [],
+ id: 'addButton',
+ disabled: true,
+ repoInfo: undefined,
+ handler: function(button, event, record) {
+ Proxmox.Utils.checked_command(() => {
+ let me = this;
+ let panel = me.up('proxmoxNodeAPTRepositories');
+
+ let extraParams = {};
+ if (panel.digest !== undefined) {
+ extraParams.digest = panel.digest;
+ }
+
+ Ext.create('Proxmox.window.APTRepositoryAdd', {
+ repoInfo: me.repoInfo,
+ url: `/api2/json/nodes/${panel.nodename}/apt/repositories`,
+ method: 'PUT',
+ extraRequestParams: extraParams,
+ listeners: {
+ destroy: function() {
+ panel.reload();
+ },
+ },
+ }).show();
+ });
},
},
'-',
altText: gettext('Disable'),
id: 'repoEnableButton',
disabled: true,
+ bind: {
+ text: '{enableButtonText}',
+ },
handler: function(button, event, record) {
let me = this;
let panel = me.up('proxmoxNodeAPTRepositories');
columns: [
{
- header: gettext('Official'),
- dataIndex: 'OfficialHost',
- renderer: function(value, cell, record) {
- let icon = (cls) => `<i class="fa fa-fw ${cls}"></i>`;
-
- const enabled = record.data.Enabled;
-
- if (value === undefined || value === null) {
- return icon('fa-question-circle-o');
- }
- if (!value) {
- return icon('fa-times ' + (enabled ? 'critical' : 'faded'));
- }
- return icon('fa-check ' + (enabled ? 'good' : 'faded'));
- },
- width: 70,
- },
- {
+ xtype: 'checkcolumn',
header: gettext('Enabled'),
dataIndex: 'Enabled',
- renderer: Proxmox.Utils.format_enabled_toggle,
+ listeners: {
+ beforecheckchange: () => false, // veto, we don't want to allow inline change - to subtle
+ },
width: 90,
},
{
{
header: gettext('Suites'),
dataIndex: 'Suites',
- renderer: function(suites, cell, record) {
- return suites.join(' ');
+ renderer: function(suites, metaData, record) {
+ let err = '';
+ if (record.data.warnings && record.data.warnings.length > 0) {
+ let txt = [gettext('Warning')];
+ record.data.warnings.forEach((warning) => {
+ if (warning.property === 'Suites') {
+ txt.push(warning.message);
+ }
+ });
+ 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> ';
+ }
+ return suites.join(' ') + err;
},
width: 130,
},
},
flex: 1,
},
+ {
+ header: gettext('Origin'),
+ dataIndex: 'Origin',
+ width: 120,
+ renderer: (value, meta, rec) => {
+ 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';
+ } else if (value.match(/^\s*Debian\s*$/i)) {
+ cls = 'pmx-itype-icon pmx-itype-icon-debian-swirl';
+ }
+ return `<i class='${cls}'></i> ${value}`;
+ },
+ },
{
header: gettext('Comment'),
dataIndex: 'Comment',
},
],
- addAdditionalInfos: function(gridData, infos) {
- let me = this;
-
- let warnings = {};
- let officialHosts = {};
-
- let addLine = function(obj, key, line) {
- if (obj[key]) {
- obj[key] += "\n";
- obj[key] += line;
- } else {
- obj[key] = line;
- }
- };
-
- for (const info of infos) {
- const key = `${info.path}:${info.index}`;
- if (info.kind === 'warning' ||
- (info.kind === 'ignore-pre-upgrade-warning' && !me.majorUpgradeAllowed)
- ) {
- addLine(warnings, key, gettext('Warning') + ": " + info.message);
- } else if (info.kind === 'badge' && info.message === 'official host name') {
- officialHosts[key] = true;
- }
- }
-
- gridData.forEach(function(record) {
- const key = `${record.Path}:${record.Index}`;
- record.OfficialHost = !!officialHosts[key];
- });
-
- me.rowBodyFeature.getAdditionalData = function(innerData, rowIndex, record, orig) {
- let headerCt = this.view.headerCt;
- let colspan = headerCt.getColumnCount();
-
- const key = `${innerData.Path}:${innerData.Index}`;
- const warning_text = warnings[key];
-
- return {
- rowBody: '<div style="color: red; white-space: pre-line">' +
- Ext.String.htmlEncode(warning_text) + '</div>',
- rowBodyCls: warning_text ? '' : Ext.baseCSSPrefix + 'grid-row-body-hidden',
- rowBodyColspan: colspan,
- };
- };
- },
-
initComponent: function() {
let me = this;
],
});
- let rowBodyFeature = Ext.create('Ext.grid.feature.RowBody', {});
-
let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
groupHeaderTpl: '{[ "File: " + values.name ]} ({rows.length} ' +
'repositor{[values.rows.length > 1 ? "ies" : "y"]})',
enableGroupingMenu: false,
});
- let sm = Ext.create('Ext.selection.RowModel', {});
-
Ext.apply(me, {
store: store,
- selModel: sm,
- rowBodyFeature: rowBodyFeature,
- features: [groupingFeature, rowBodyFeature],
+ features: [groupingFeature],
});
me.callParent();
},
-
- listeners: {
- selectionchange: function() {
- let me = this;
-
- if (me.onSelectionChange) {
- let sm = me.getSelectionModel();
- let rec = sm.getSelection()[0];
-
- me.onSelectionChange(rec, sm);
- }
- },
- },
});
Ext.define('Proxmox.node.APTRepositories', {
digest: undefined,
+ product: 'Proxmox VE', // default
+
+ controller: {
+ xclass: 'Ext.app.ViewController',
+
+ selectionChange: function(grid, selection) {
+ let me = this;
+ if (!selection || selection.length < 1) {
+ return;
+ }
+ let rec = selection[0];
+ let vm = me.getViewModel();
+ vm.set('selectionenabled', rec.get('Enabled'));
+ },
+ },
+
viewModel: {
data: {
+ product: 'Proxmox VE', // default
errorCount: 0,
subscriptionActive: '',
noSubscriptionRepo: '',
enterpriseRepo: '',
+ selectionenabled: false,
},
formulas: {
noErrors: (get) => get('errorCount') === 0,
+ enableButtonText: (get) => get('selectionenabled')
+ ? gettext('Disable') : gettext('Enable'),
mainWarning: function(get) {
// Not yet initialized
if (get('subscriptionActive') === '' ||
return '';
}
- let withStyle = (msg) => "<div style='color:red;'><i class='fa fa-fw " +
- "fa-exclamation-triangle'></i>" + gettext('Warning') + ': ' + msg + "</div>";
+ 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 withStyle(gettext('The enterprise repository is ' +
- 'enabled, but there is no active subscription!'));
+ return fmt(gettext('The enterprise repository is enabled, but there is no active subscription!'));
}
if (get('noSubscriptionRepo')) {
- return withStyle(gettext('The no-subscription repository is ' +
- 'not recommended for production use!'));
+ return fmt(gettext('The no-subscription repository is not recommended for production use!'));
}
if (!get('enterpriseRepo') && !get('noSubscriptionRepo')) {
- return withStyle(gettext('No Proxmox repository is enabled!'));
+ let msg = Ext.String.format(gettext('No {0} repository is enabled!'), get('product'));
+ return fmt(msg);
}
return '';
},
},
+ scrollable: true,
+ layout: {
+ type: 'vbox',
+ align: 'stretch',
+ },
+
items: [
{
- title: gettext('Warning'),
- name: 'repositoriesMainWarning',
- xtype: 'panel',
+ 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}',
},
nodename: '{nodename}',
},
majorUpgradeAllowed: false, // TODO get release information from an API call?
- onSelectionChange: function(rec, sm) {
- let me = this;
- if (rec) {
- let btn = me.up('proxmoxNodeAPTRepositories').down('#repoEnableButton');
- btn.setText(rec.get('Enabled') ? gettext('Disable') : gettext('Enable'));
- }
+ listeners: {
+ selectionchange: 'selectionChange',
},
},
],
let me = this;
let vm = me.getViewModel();
- let menu = me.down('#addMenu');
- menu.removeAll();
+ let addButton = me.down('#addButton');
+ addButton.repoInfo = [];
for (const standardRepo of standardRepos) {
const handle = standardRepo.handle;
vm.set('noSubscriptionRepo', status);
}
- let status_text = '';
- if (status !== undefined && status !== null) {
- status_text = Ext.String.format(
- ' ({0}, {1})',
- gettext('configured'),
- status ? gettext('enabled') : gettext('disabled'),
- );
- }
-
- menu.add({
- text: standardRepo.name + status_text,
- disabled: status !== undefined && status !== null,
- repoHandle: handle,
- handler: function(menuItem) {
- let params = {
- handle: menuItem.repoHandle,
- };
-
- if (me.digest !== undefined) {
- params.digest = me.digest;
- }
-
- Proxmox.Utils.API2Request({
- url: `/nodes/${me.nodename}/apt/repositories`,
- method: 'PUT',
- params: params,
- failure: function(response, opts) {
- Ext.Msg.alert(gettext('Error'), response.htmlStatus);
- me.reload();
- },
- success: function(response, opts) {
- me.reload();
- },
- });
- },
- });
+ addButton.repoInfo.push(standardRepo);
+ addButton.digest = me.digest;
}
+
+ addButton.setDisabled(false);
},
reload: function() {
errors = data.errors;
digest = data.digest;
+ let infos = {};
+ for (const info of data.infos) {
+ let path = info.path;
+ let idx = info.index;
+
+ if (!infos[path]) {
+ infos[path] = {};
+ }
+ if (!infos[path][idx]) {
+ infos[path][idx] = {
+ origin: '',
+ warnings: [],
+ };
+ }
+
+ if (info.kind === 'origin') {
+ infos[path][idx].origin = info.message;
+ } else if (info.kind === 'warning' ||
+ (info.kind === 'ignore-pre-upgrade-warning' && !repoGrid.majorUpgradeAllowed)
+ ) {
+ infos[path][idx].warnings.push(info);
+ } else {
+ throw 'unknown info';
+ }
+ }
+
+
files.forEach(function(file) {
for (let n = 0; n < file.repositories.length; n++) {
let repo = file.repositories[n];
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.warnings = infos[file.path][n].warnings || [];
+ }
gridData.push(repo);
}
});
- repoGrid.addAdditionalInfos(gridData, data.infos);
repoGrid.store.loadData(gridData);
me.updateStandardRepos(data['standard-repos']);
Proxmox.Utils.monStoreErrors(me, me.store, true);
me.callParent();
+
+ me.getViewModel().set('product', me.product);
},
});