]>
git.proxmox.com Git - proxmox-widget-toolkit.git/blob - src/node/APTRepositories.js
1 Ext
.define('apt-repolist', {
2 extend
: 'Ext.data.Model',
18 Ext
.define('Proxmox.window.APTRepositoryAdd', {
19 extend
: 'Proxmox.window.Edit',
20 alias
: 'widget.pmxAPTRepositoryAdd',
25 subject
: gettext('Repository'),
28 initComponent: function() {
31 if (!me
.repoInfo
|| me
.repoInfo
.length
=== 0) {
32 throw "repository information not initialized";
35 let description
= Ext
.create('Ext.form.field.Display', {
36 fieldLabel
: gettext('Description'),
40 let status
= Ext
.create('Ext.form.field.Display', {
41 fieldLabel
: gettext('Status'),
43 renderer: function(value
) {
44 let statusText
= gettext('Not yet configured');
46 statusText
= Ext
.String
.format(
48 gettext('Configured'),
49 value
? gettext('enabled') : gettext('disabled'),
57 let repoSelector
= Ext
.create('Proxmox.form.KVComboBox', {
58 fieldLabel
: gettext('Repository'),
59 xtype
: 'proxmoxKVComboBox',
62 comboItems
: me
.repoInfo
.map(info
=> [info
.handle
, info
.name
]),
63 validator: function(renderedValue
) {
64 let handle
= this.value
;
65 // we cannot use this.callParent in instantiations
66 let valid
= Proxmox
.form
.KVComboBox
.prototype.validator
.call(this, renderedValue
);
68 if (!valid
|| !handle
) {
72 const info
= me
.repoInfo
.find(elem
=> elem
.handle
=== handle
);
78 return Ext
.String
.format(gettext('{0} is already configured'), renderedValue
);
83 change: function(f
, value
) {
84 const info
= me
.repoInfo
.find(elem
=> elem
.handle
=== value
);
85 description
.setValue(info
.description
);
86 status
.setValue(info
.status
);
91 repoSelector
.setValue(me
.repoInfo
[0].handle
);
99 repoSelector
: repoSelector
,
106 Ext
.define('Proxmox.node.APTRepositoriesErrors', {
107 extend
: 'Ext.grid.GridPanel',
109 xtype
: 'proxmoxNodeAPTRepositoriesErrors',
117 getRowClass
: (record
) => {
118 switch (record
.data
.status
) {
119 case 'warning': return 'proxmox-warning-row';
120 case 'critical': return 'proxmox-invalid-row';
131 renderer
: (value
) => `<i class="fa fa-fw ${Proxmox.Utils.get_health_icon(value, true)}"></i>`,
135 dataIndex
: 'message',
141 Ext
.define('Proxmox.node.APTRepositoriesGrid', {
142 extend
: 'Ext.grid.GridPanel',
143 xtype
: 'proxmoxNodeAPTRepositoriesGrid',
145 title
: gettext('APT Repositories'),
147 cls
: 'proxmox-apt-repos', // to allow applying styling to general components with local effect
153 text
: gettext('Reload'),
154 iconCls
: 'fa fa-refresh',
155 handler: function() {
157 me
.up('proxmoxNodeAPTRepositories').reload();
161 text
: gettext('Add'),
165 handler: function(button
, event
, record
) {
166 Proxmox
.Utils
.checked_command(() => {
168 let panel
= me
.up('proxmoxNodeAPTRepositories');
170 let extraParams
= {};
171 if (panel
.digest
!== undefined) {
172 extraParams
.digest
= panel
.digest
;
175 Ext
.create('Proxmox.window.APTRepositoryAdd', {
176 repoInfo
: me
.repoInfo
,
177 url
: `/api2/extjs/nodes/${panel.nodename}/apt/repositories`,
179 extraRequestParams
: extraParams
,
181 destroy: function() {
191 xtype
: 'proxmoxButton',
192 text
: gettext('Enable'),
193 defaultText
: gettext('Enable'),
194 altText
: gettext('Disable'),
195 id
: 'repoEnableButton',
198 text
: '{enableButtonText}',
200 handler: function(button
, event
, record
) {
202 let panel
= me
.up('proxmoxNodeAPTRepositories');
205 path
: record
.data
.Path
,
206 index
: record
.data
.Index
,
207 enabled
: record
.data
.Enabled
? 0 : 1, // invert
210 if (panel
.digest
!== undefined) {
211 params
.digest
= panel
.digest
;
214 Proxmox
.Utils
.API2Request({
215 url
: `/nodes/${panel.nodename}/apt/repositories`,
218 failure: function(response
, opts
) {
219 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
222 success: function(response
, opts
) {
228 render: function(btn
) {
229 // HACK: calculate the max button width on first render to avoid toolbar glitches
230 let defSize
= btn
.getSize().width
;
232 btn
.setText(btn
.altText
);
233 let altSize
= btn
.getSize().width
;
235 btn
.setText(btn
.defaultText
);
236 btn
.setSize({ width
: altSize
> defSize
? altSize
: defSize
});
242 sortableColumns
: false,
245 getRowClass
: (record
, index
) => record
.get('Enabled') ? '' : 'proxmox-disabled-row',
250 xtype
: 'checkcolumn',
251 header
: gettext('Enabled'),
252 dataIndex
: 'Enabled',
254 beforecheckchange
: () => false, // veto, we don't want to allow inline change - to subtle
259 header
: gettext('Types'),
261 renderer: function(types
, cell
, record
) {
262 return types
.join(' ');
267 header
: gettext('URIs'),
269 renderer: function(uris
, cell
, record
) {
270 return uris
.join(' ');
275 header
: gettext('Suites'),
277 renderer: function(suites
, metaData
, record
) {
279 if (record
.data
.warnings
&& record
.data
.warnings
.length
> 0) {
280 let txt
= [gettext('Warning')];
281 record
.data
.warnings
.forEach((warning
) => {
282 if (warning
.property
=== 'Suites') {
283 txt
.push(warning
.message
);
286 metaData
.tdAttr
= `data-qtip="${Ext.htmlEncode(txt.join('<br>'))}"`;
287 if (record
.data
.Enabled
) {
288 metaData
.tdCls
= 'proxmox-invalid-row';
289 err
= '<i class="fa fa-fw critical fa-exclamation-circle"></i> ';
291 metaData
.tdCls
= 'proxmox-warning-row';
292 err
= '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
295 return suites
.join(' ') + err
;
300 header
: gettext('Components'),
301 dataIndex
: 'Components',
302 renderer: function(components
, metaData
, record
) {
304 if (components
.length
=== 1) {
305 // FIXME: this should be a flag set to the actual repsotiories, i.e., a tristate
306 // like production-ready = <yes|no|other> (Option<bool>)
307 if (components
[0].match(/\w+(-no-subscription|test)\s*$/i)) {
308 metaData
.tdCls
= 'proxmox-warning-row';
309 err
= '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
311 let qtip
= components
[0].match(/no-subscription/)
312 ? gettext('The no-subscription repository is NOT production-ready')
313 : gettext('The test repository may contain unstable updates')
315 metaData
.tdAttr
= `data-qtip="${Ext.htmlEncode(qtip)}"`;
318 return components
.join(' ') + err
;
323 header
: gettext('Options'),
324 dataIndex
: 'Options',
325 renderer: function(options
, cell
, record
) {
330 let filetype
= record
.data
.FileType
;
333 options
.forEach(function(option
) {
334 let key
= option
.Key
;
335 if (filetype
=== 'list') {
336 let values
= option
.Values
.join(',');
337 text
+= `${key}=${values} `;
338 } else if (filetype
=== 'sources') {
339 let values
= option
.Values
.join(' ');
340 text
+= `${key}: ${values}<br>`;
342 throw "unkown file type";
350 header
: gettext('Origin'),
353 renderer
: (value
, meta
, rec
) => {
354 if (typeof value
!== 'string' || value
.length
=== 0) {
355 value
= gettext('Other');
357 let cls
= 'fa fa-fw fa-question-circle-o';
358 if (value
.match(/^\s*Proxmox\s*$/i)) {
359 cls
= 'pmx-itype-icon pmx-itype-icon-proxmox-x';
360 } else if (value
.match(/^\s*Debian\s*$/i)) {
361 cls
= 'pmx-itype-icon pmx-itype-icon-debian-swirl';
363 return `<i class='${cls}'></i> ${value}`;
367 header
: gettext('Comment'),
368 dataIndex
: 'Comment',
373 initComponent: function() {
377 throw "no node name specified";
380 let store
= Ext
.create('Ext.data.Store', {
381 model
: 'apt-repolist',
391 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
392 groupHeaderTpl
: '{[ "File: " + values.name ]} ({rows.length} ' +
393 'repositor{[values.rows.length > 1 ? "ies" : "y"]})',
394 enableGroupingMenu
: false,
399 features
: [groupingFeature
],
406 Ext
.define('Proxmox.node.APTRepositories', {
407 extend
: 'Ext.panel.Panel',
408 xtype
: 'proxmoxNodeAPTRepositories',
409 mixins
: ['Proxmox.Mixin.CBind'],
413 product
: 'Proxmox VE', // default
416 xclass
: 'Ext.app.ViewController',
418 selectionChange: function(grid
, selection
) {
420 if (!selection
|| selection
.length
< 1) {
423 let rec
= selection
[0];
424 let vm
= me
.getViewModel();
425 vm
.set('selectionenabled', rec
.get('Enabled'));
429 updateState: function() {
431 let vm
= me
.getViewModel();
433 let store
= vm
.get('errorstore');
436 let status
= 'good'; // start with best, the helper below will downgrade if needed
437 let text
= gettext('All OK, you have production-ready repositories configured!');
439 let errors
= vm
.get('errors');
440 errors
.forEach((error
) => {
444 message
: `${error.path} - ${error.error}`,
448 let addGood
= message
=> store
.add({ status
: 'good', message
});
450 let addWarn
= message
=> {
451 if (status
=== 'good') {
455 store
.add({ status
: 'warning', message
});
458 let activeSubscription
= vm
.get('subscriptionActive');
459 let enterprise
= vm
.get('enterpriseRepo');
460 let nosubscription
= vm
.get('noSubscriptionRepo');
461 let test
= vm
.get('testRepo');
462 let wrongSuites
= vm
.get('suitesWarning');
464 if (!enterprise
&& !nosubscription
&& !test
) {
465 addWarn(Ext
.String
.format(gettext('No {0} repository is enabled!'), vm
.get('product')));
466 } else if (enterprise
&& !nosubscription
&& !test
&& activeSubscription
) {
467 addGood(Ext
.String
.format(gettext('You get supported updates for {0}'), vm
.get('product')));
468 } else if (nosubscription
|| test
) {
469 addGood(Ext
.String
.format(gettext('You get updates for {0}'), vm
.get('product')));
473 addWarn(gettext('Some Suites are misconfigured'));
476 if (!activeSubscription
&& enterprise
) {
477 addWarn(gettext('The enterprise repository is enabled, but there is no active subscription!'));
480 if (nosubscription
) {
481 addWarn(gettext('The no-subscription repository is not recommended for production use!'));
485 addWarn(gettext('The test repository is not recommended for production use!'));
488 if (errors
.length
> 0) {
489 text
= gettext('Error parsing repositories');
492 let iconCls
= Proxmox
.Utils
.get_health_icon(status
, true);
503 product
: 'Proxmox VE', // default
505 suitesWarning
: false,
506 subscriptionActive
: '',
507 noSubscriptionRepo
: '',
510 selectionenabled
: false,
514 enableButtonText
: (get) => get('selectionenabled')
515 ? gettext('Disable') : gettext('Enable'),
519 fields
: ['status', 'message'],
539 title
: gettext('Status'),
546 iconCls
: Proxmox
.Utils
.get_health_icon(undefined, true),
554 '<i class="fa fa-4x {iconCls}"></i>',
561 xtype
: 'proxmoxNodeAPTRepositoriesErrors',
562 name
: 'repositoriesErrors',
566 store
: '{errorstore}',
572 xtype
: 'proxmoxNodeAPTRepositoriesGrid',
573 name
: 'repositoriesGrid',
576 nodename
: '{nodename}',
578 majorUpgradeAllowed
: false, // TODO get release information from an API call?
580 selectionchange
: 'selectionChange',
585 check_subscription: function() {
587 let vm
= me
.getViewModel();
589 Proxmox
.Utils
.API2Request({
590 url
: `/nodes/${me.nodename}/subscription`,
592 failure
: (response
, opts
) => Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
),
593 success: function(response
, opts
) {
594 const res
= response
.result
;
595 const subscription
= !(!res
|| !res
.data
|| res
.data
.status
.toLowerCase() !== 'active');
596 vm
.set('subscriptionActive', subscription
);
597 me
.getController().updateState();
602 updateStandardRepos: function(standardRepos
) {
604 let vm
= me
.getViewModel();
606 let addButton
= me
.down('#addButton');
607 addButton
.repoInfo
= [];
609 for (const standardRepo
of standardRepos
) {
610 const handle
= standardRepo
.handle
;
611 const status
= standardRepo
.status
;
613 if (handle
=== "enterprise") {
614 vm
.set('enterpriseRepo', status
);
615 } else if (handle
=== "no-subscription") {
616 vm
.set('noSubscriptionRepo', status
);
617 } else if (handle
=== 'test') {
618 vm
.set('testRepo', status
);
620 me
.getController().updateState();
622 addButton
.repoInfo
.push(standardRepo
);
623 addButton
.digest
= me
.digest
;
626 addButton
.setDisabled(false);
631 let vm
= me
.getViewModel();
632 let repoGrid
= me
.down('proxmoxNodeAPTRepositoriesGrid');
634 me
.store
.load(function(records
, operation
, success
) {
638 let suitesWarning
= false;
640 if (success
&& records
.length
> 0) {
641 let data
= records
[0].data
;
642 let files
= data
.files
;
643 errors
= data
.errors
;
644 digest
= data
.digest
;
647 for (const info
of data
.infos
) {
648 let path
= info
.path
;
649 let idx
= info
.index
;
654 if (!infos
[path
][idx
]) {
661 if (info
.kind
=== 'origin') {
662 infos
[path
][idx
].origin
= info
.message
;
663 } else if (info
.kind
=== 'warning' ||
664 (info
.kind
=== 'ignore-pre-upgrade-warning' && !repoGrid
.majorUpgradeAllowed
)
666 infos
[path
][idx
].warnings
.push(info
);
667 if (!suitesWarning
&& info
.property
=== 'Suites') {
668 suitesWarning
= true;
671 throw 'unknown info';
676 files
.forEach(function(file
) {
677 for (let n
= 0; n
< file
.repositories
.length
; n
++) {
678 let repo
= file
.repositories
[n
];
679 repo
.Path
= file
.path
;
681 if (infos
[file
.path
] && infos
[file
.path
][n
]) {
682 repo
.Origin
= infos
[file
.path
][n
].origin
|| Proxmox
.Utils
.UnknownText
;
683 repo
.warnings
= infos
[file
.path
][n
].warnings
|| [];
689 repoGrid
.store
.loadData(gridData
);
691 me
.updateStandardRepos(data
['standard-repos']);
696 vm
.set('errors', errors
);
697 vm
.set('suitesWarning', suitesWarning
);
698 me
.getController().updateState();
701 me
.check_subscription();
705 activate: function() {
711 initComponent: function() {
715 throw "no node name specified";
718 let store
= Ext
.create('Ext.data.Store', {
721 url
: `/api2/json/nodes/${me.nodename}/apt/repositories`,
725 Ext
.apply(me
, { store
: store
});
727 Proxmox
.Utils
.monStoreErrors(me
, me
.store
, true);
731 me
.getViewModel().set('product', me
.product
);