]>
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
]),
64 const handle
= this.value
;
70 const info
= me
.repoInfo
.find(elem
=> elem
.handle
=== handle
);
77 return info
.status
=== undefined || info
.status
=== null;
80 change: function(f
, value
) {
81 const info
= me
.repoInfo
.find(elem
=> elem
.handle
=== value
);
82 description
.setValue(info
.description
);
83 status
.setValue(info
.status
);
88 repoSelector
.setValue(me
.repoInfo
[0].handle
);
96 repoSelector
: repoSelector
,
103 Ext
.define('Proxmox.node.APTRepositoriesErrors', {
104 extend
: 'Ext.grid.GridPanel',
106 xtype
: 'proxmoxNodeAPTRepositoriesErrors',
108 title
: gettext('Errors'),
116 getRowClass
: () => 'proxmox-invalid-row',
121 header
: gettext('File'),
123 renderer
: value
=> `<i class='pve-grid-fa fa fa-fw fa-exclamation-triangle'></i>${value}`,
127 header
: gettext('Error'),
134 Ext
.define('Proxmox.node.APTRepositoriesGrid', {
135 extend
: 'Ext.grid.GridPanel',
136 xtype
: 'proxmoxNodeAPTRepositoriesGrid',
138 title
: gettext('APT Repositories'),
140 cls
: 'proxmox-apt-repos', // to allow applying styling to general components with local effect
146 text
: gettext('Reload'),
147 iconCls
: 'fa fa-refresh',
148 handler: function() {
150 me
.up('proxmoxNodeAPTRepositories').reload();
154 text
: gettext('Add'),
158 handler: function(button
, event
, record
) {
159 Proxmox
.Utils
.checked_command(() => {
161 let panel
= me
.up('proxmoxNodeAPTRepositories');
163 let extraParams
= {};
164 if (panel
.digest
!== undefined) {
165 extraParams
.digest
= panel
.digest
;
168 Ext
.create('Proxmox.window.APTRepositoryAdd', {
169 repoInfo
: me
.repoInfo
,
170 url
: `/api2/json/nodes/${panel.nodename}/apt/repositories`,
172 extraRequestParams
: extraParams
,
174 destroy: function() {
184 xtype
: 'proxmoxButton',
185 text
: gettext('Enable'),
186 defaultText
: gettext('Enable'),
187 altText
: gettext('Disable'),
188 id
: 'repoEnableButton',
191 text
: '{enableButtonText}',
193 handler: function(button
, event
, record
) {
195 let panel
= me
.up('proxmoxNodeAPTRepositories');
198 path
: record
.data
.Path
,
199 index
: record
.data
.Index
,
200 enabled
: record
.data
.Enabled
? 0 : 1, // invert
203 if (panel
.digest
!== undefined) {
204 params
.digest
= panel
.digest
;
207 Proxmox
.Utils
.API2Request({
208 url
: `/nodes/${panel.nodename}/apt/repositories`,
211 failure: function(response
, opts
) {
212 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
215 success: function(response
, opts
) {
221 render: function(btn
) {
222 // HACK: calculate the max button width on first render to avoid toolbar glitches
223 let defSize
= btn
.getSize().width
;
225 btn
.setText(btn
.altText
);
226 let altSize
= btn
.getSize().width
;
228 btn
.setText(btn
.defaultText
);
229 btn
.setSize({ width
: altSize
> defSize
? altSize
: defSize
});
235 sortableColumns
: false,
238 getRowClass
: (record
, index
) => record
.get('Enabled') ? '' : 'proxmox-disabled-row',
243 xtype
: 'checkcolumn',
244 header
: gettext('Enabled'),
245 dataIndex
: 'Enabled',
247 beforecheckchange
: () => false, // veto, we don't want to allow inline change - to subtle
252 header
: gettext('Types'),
254 renderer: function(types
, cell
, record
) {
255 return types
.join(' ');
260 header
: gettext('URIs'),
262 renderer: function(uris
, cell
, record
) {
263 return uris
.join(' ');
268 header
: gettext('Suites'),
270 renderer: function(suites
, metaData
, record
) {
272 if (record
.data
.warnings
&& record
.data
.warnings
.length
> 0) {
273 let txt
= [gettext('Warning')];
274 record
.data
.warnings
.forEach((warning
) => {
275 if (warning
.property
=== 'Suites') {
276 txt
.push(warning
.message
);
279 metaData
.tdAttr
= `data-qtip="${Ext.htmlEncode(txt.join('<br>'))}"`;
280 if (record
.data
.Enabled
) {
281 metaData
.tdCls
= 'proxmox-invalid-row';
282 err
= '<i class="fa fa-fw critical fa-exclamation-circle"></i> ';
284 metaData
.tdCls
= 'proxmox-warning-row';
285 err
= '<i class="fa fa-fw warning fa-exclamation-circle"></i> ';
288 return suites
.join(' ') + err
;
293 header
: gettext('Components'),
294 dataIndex
: 'Components',
295 renderer: function(components
, cell
, record
) {
296 return components
.join(' ');
301 header
: gettext('Options'),
302 dataIndex
: 'Options',
303 renderer: function(options
, cell
, record
) {
308 let filetype
= record
.data
.FileType
;
311 options
.forEach(function(option
) {
312 let key
= option
.Key
;
313 if (filetype
=== 'list') {
314 let values
= option
.Values
.join(',');
315 text
+= `${key}=${values} `;
316 } else if (filetype
=== 'sources') {
317 let values
= option
.Values
.join(' ');
318 text
+= `${key}: ${values}<br>`;
320 throw "unkown file type";
328 header
: gettext('Origin'),
331 renderer
: (value
, meta
, rec
) => {
332 if (typeof value
!== 'string' || value
.length
=== 0) {
333 value
= gettext('Other');
335 let cls
= 'fa fa-fw fa-question-circle-o';
336 if (value
.match(/^\s*Proxmox\s*$/i)) {
337 cls
= 'pmx-itype-icon pmx-itype-icon-proxmox-x';
338 } else if (value
.match(/^\s*Debian\s*$/i)) {
339 cls
= 'pmx-itype-icon pmx-itype-icon-debian-swirl';
341 return `<i class='${cls}'></i> ${value}`;
345 header
: gettext('Comment'),
346 dataIndex
: 'Comment',
351 initComponent: function() {
355 throw "no node name specified";
358 let store
= Ext
.create('Ext.data.Store', {
359 model
: 'apt-repolist',
369 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
370 groupHeaderTpl
: '{[ "File: " + values.name ]} ({rows.length} ' +
371 'repositor{[values.rows.length > 1 ? "ies" : "y"]})',
372 enableGroupingMenu
: false,
377 features
: [groupingFeature
],
384 Ext
.define('Proxmox.node.APTRepositories', {
385 extend
: 'Ext.panel.Panel',
386 xtype
: 'proxmoxNodeAPTRepositories',
387 mixins
: ['Proxmox.Mixin.CBind'],
391 product
: 'Proxmox VE', // default
394 xclass
: 'Ext.app.ViewController',
396 selectionChange: function(grid
, selection
) {
398 if (!selection
|| selection
.length
< 1) {
401 let rec
= selection
[0];
402 let vm
= me
.getViewModel();
403 vm
.set('selectionenabled', rec
.get('Enabled'));
409 product
: 'Proxmox VE', // default
411 subscriptionActive
: '',
412 noSubscriptionRepo
: '',
414 selectionenabled
: false,
417 noErrors
: (get) => get('errorCount') === 0,
418 enableButtonText
: (get) => get('selectionenabled')
419 ? gettext('Disable') : gettext('Enable'),
420 mainWarning: function(get) {
421 // Not yet initialized
422 if (get('subscriptionActive') === '' ||
423 get('enterpriseRepo') === '') {
427 let icon
= `<i class='fa fa-fw fa-exclamation-triangle critical'></i>`;
428 let fmt
= (msg
) => `<div class="black">${icon}${gettext('Warning')}: ${msg}</div>`;
430 if (!get('subscriptionActive') && get('enterpriseRepo')) {
431 return fmt(gettext('The enterprise repository is enabled, but there is no active subscription!'));
434 if (get('noSubscriptionRepo')) {
435 return fmt(gettext('The no-subscription repository is not recommended for production use!'));
438 if (!get('enterpriseRepo') && !get('noSubscriptionRepo')) {
439 let msg
= Ext
.String
.format(gettext('No {0} repository is enabled!'), get('product'));
457 baseCls
: 'x-panel-header',
459 hidden
: '{!mainWarning}',
460 title
: '{mainWarning}',
466 hidden
: '{!mainWarning}',
471 xtype
: 'proxmoxNodeAPTRepositoriesErrors',
472 name
: 'repositoriesErrors',
476 hidden
: '{noErrors}',
480 xtype
: 'proxmoxNodeAPTRepositoriesGrid',
481 name
: 'repositoriesGrid',
483 nodename
: '{nodename}',
485 majorUpgradeAllowed
: false, // TODO get release information from an API call?
487 selectionchange
: 'selectionChange',
492 check_subscription: function() {
494 let vm
= me
.getViewModel();
496 Proxmox
.Utils
.API2Request({
497 url
: `/nodes/${me.nodename}/subscription`,
499 failure
: (response
, opts
) => Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
),
500 success: function(response
, opts
) {
501 const res
= response
.result
;
502 const subscription
= !(!res
|| !res
.data
|| res
.data
.status
.toLowerCase() !== 'active');
503 vm
.set('subscriptionActive', subscription
);
508 updateStandardRepos: function(standardRepos
) {
510 let vm
= me
.getViewModel();
512 let addButton
= me
.down('#addButton');
513 addButton
.repoInfo
= [];
515 for (const standardRepo
of standardRepos
) {
516 const handle
= standardRepo
.handle
;
517 const status
= standardRepo
.status
;
519 if (handle
=== "enterprise") {
520 vm
.set('enterpriseRepo', status
);
521 } else if (handle
=== "no-subscription") {
522 vm
.set('noSubscriptionRepo', status
);
525 addButton
.repoInfo
.push(standardRepo
);
526 addButton
.digest
= me
.digest
;
529 addButton
.setDisabled(false);
534 let vm
= me
.getViewModel();
535 let repoGrid
= me
.down('proxmoxNodeAPTRepositoriesGrid');
536 let errorGrid
= me
.down('proxmoxNodeAPTRepositoriesErrors');
538 me
.store
.load(function(records
, operation
, success
) {
543 if (success
&& records
.length
> 0) {
544 let data
= records
[0].data
;
545 let files
= data
.files
;
546 errors
= data
.errors
;
547 digest
= data
.digest
;
550 for (const info
of data
.infos
) {
551 let path
= info
.path
;
552 let idx
= info
.index
;
557 if (!infos
[path
][idx
]) {
564 if (info
.kind
=== 'origin') {
565 infos
[path
][idx
].origin
= info
.message
;
566 } else if (info
.kind
=== 'warning' ||
567 (info
.kind
=== 'ignore-pre-upgrade-warning' && !repoGrid
.majorUpgradeAllowed
)
569 infos
[path
][idx
].warnings
.push(info
);
571 throw 'unknown info';
576 files
.forEach(function(file
) {
577 for (let n
= 0; n
< file
.repositories
.length
; n
++) {
578 let repo
= file
.repositories
[n
];
579 repo
.Path
= file
.path
;
581 if (infos
[file
.path
] && infos
[file
.path
][n
]) {
582 repo
.Origin
= infos
[file
.path
][n
].origin
|| Proxmox
.Utils
.UnknownText
;
583 repo
.warnings
= infos
[file
.path
][n
].warnings
|| [];
589 repoGrid
.store
.loadData(gridData
);
591 me
.updateStandardRepos(data
['standard-repos']);
596 vm
.set('errorCount', errors
.length
);
597 errorGrid
.store
.loadData(errors
);
600 me
.check_subscription();
604 activate: function() {
610 initComponent: function() {
614 throw "no node name specified";
617 let store
= Ext
.create('Ext.data.Store', {
620 url
: `/api2/json/nodes/${me.nodename}/apt/repositories`,
624 Ext
.apply(me
, { store
: store
});
626 Proxmox
.Utils
.monStoreErrors(me
, me
.store
, true);
630 me
.getViewModel().set('product', me
.product
);