]>
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.node.APTRepositoriesErrors', {
19 extend
: 'Ext.grid.GridPanel',
21 xtype
: 'proxmoxNodeAPTRepositoriesErrors',
23 title
: gettext('Errors'),
29 getRowClass
: () => 'proxmox-invalid-row',
34 header
: gettext('File'),
36 renderer
: value
=> `<i class='pve-grid-fa fa fa-fw fa-exclamation-triangle'></i>${value}`,
40 header
: gettext('Error'),
47 Ext
.define('Proxmox.node.APTRepositoriesGrid', {
48 extend
: 'Ext.grid.GridPanel',
49 xtype
: 'proxmoxNodeAPTRepositoriesGrid',
51 title
: gettext('APT Repositories'),
53 cls
: 'proxmox-apt-repos', // to allow applying styling to general components with local effect
57 text
: gettext('Reload'),
58 iconCls
: 'fa fa-refresh',
61 me
.up('proxmoxNodeAPTRepositories').reload();
74 xtype
: 'proxmoxButton',
75 text
: gettext('Enable'),
76 defaultText
: gettext('Enable'),
77 altText
: gettext('Disable'),
78 id
: 'repoEnableButton',
80 handler: function(button
, event
, record
) {
82 let panel
= me
.up('proxmoxNodeAPTRepositories');
85 path
: record
.data
.Path
,
86 index
: record
.data
.Index
,
87 enabled
: record
.data
.Enabled
? 0 : 1, // invert
90 if (panel
.digest
!== undefined) {
91 params
.digest
= panel
.digest
;
94 Proxmox
.Utils
.API2Request({
95 url
: `/nodes/${panel.nodename}/apt/repositories`,
98 failure: function(response
, opts
) {
99 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
102 success: function(response
, opts
) {
108 render: function(btn
) {
109 // HACK: calculate the max button width on first render to avoid toolbar glitches
110 let defSize
= btn
.getSize().width
;
112 btn
.setText(btn
.altText
);
113 let altSize
= btn
.getSize().width
;
115 btn
.setText(btn
.defaultText
);
116 btn
.setSize({ width
: altSize
> defSize
? altSize
: defSize
});
122 sortableColumns
: false,
126 xtype
: 'checkcolumn',
127 header
: gettext('Enabled'),
128 dataIndex
: 'Enabled',
130 beforecheckchange
: () => false, // veto, we don't want to allow inline change - to subtle
135 header
: gettext('Types'),
137 renderer: function(types
, cell
, record
) {
138 return types
.join(' ');
143 header
: gettext('URIs'),
145 renderer: function(uris
, cell
, record
) {
146 return uris
.join(' ');
151 header
: gettext('Suites'),
153 renderer: function(suites
, cell
, record
) {
154 return suites
.join(' ');
159 header
: gettext('Components'),
160 dataIndex
: 'Components',
161 renderer: function(components
, cell
, record
) {
162 return components
.join(' ');
167 header
: gettext('Options'),
168 dataIndex
: 'Options',
169 renderer: function(options
, cell
, record
) {
174 let filetype
= record
.data
.FileType
;
177 options
.forEach(function(option
) {
178 let key
= option
.Key
;
179 if (filetype
=== 'list') {
180 let values
= option
.Values
.join(',');
181 text
+= `${key}=${values} `;
182 } else if (filetype
=== 'sources') {
183 let values
= option
.Values
.join(' ');
184 text
+= `${key}: ${values}<br>`;
186 throw "unkown file type";
194 header
: gettext('Official'),
195 dataIndex
: 'OfficialHost',
196 renderer: function(value
, cell
, record
) {
197 let icon
= (cls
) => `<i class="fa fa-fw ${cls}"></i>`;
199 if (value
=== undefined || value
=== null) {
200 return icon('fa-question-circle-o');
202 const enabled
= record
.data
.Enabled
;
204 return icon('fa-question ' + (enabled
? 'warning' : 'faded'));
206 return icon('fa-check ' + (enabled
? 'good' : 'faded'));
211 header
: gettext('Comment'),
212 dataIndex
: 'Comment',
217 addAdditionalInfos: function(gridData
, infos
) {
221 let officialHosts
= {};
223 let addLine = function(obj
, key
, line
) {
232 for (const info
of infos
) {
233 const key
= `${info.path}:${info.index}`;
234 if (info
.kind
=== 'warning' ||
235 (info
.kind
=== 'ignore-pre-upgrade-warning' && !me
.majorUpgradeAllowed
)
237 addLine(warnings
, key
, gettext('Warning') + ": " + info
.message
);
238 } else if (info
.kind
=== 'badge' && info
.message
=== 'official host name') {
239 officialHosts
[key
] = true;
243 gridData
.forEach(function(record
) {
244 const key
= `${record.Path}:${record.Index}`;
245 record
.OfficialHost
= !!officialHosts
[key
];
248 me
.rowBodyFeature
.getAdditionalData = function(innerData
, rowIndex
, record
, orig
) {
249 let headerCt
= this.view
.headerCt
;
250 let colspan
= headerCt
.getColumnCount();
252 const key
= `${innerData.Path}:${innerData.Index}`;
253 const warning_text
= warnings
[key
];
256 rowBody
: '<div style="color: red; white-space: pre-line">' +
257 Ext
.String
.htmlEncode(warning_text
) + '</div>',
258 rowBodyCls
: warning_text
? '' : Ext
.baseCSSPrefix
+ 'grid-row-body-hidden',
259 rowBodyColspan
: colspan
,
264 initComponent: function() {
268 throw "no node name specified";
271 let store
= Ext
.create('Ext.data.Store', {
272 model
: 'apt-repolist',
282 let rowBodyFeature
= Ext
.create('Ext.grid.feature.RowBody', {});
284 let groupingFeature
= Ext
.create('Ext.grid.feature.Grouping', {
285 groupHeaderTpl
: '{[ "File: " + values.name ]} ({rows.length} ' +
286 'repositor{[values.rows.length > 1 ? "ies" : "y"]})',
287 enableGroupingMenu
: false,
290 let sm
= Ext
.create('Ext.selection.RowModel', {});
295 rowBodyFeature
: rowBodyFeature
,
296 features
: [groupingFeature
, rowBodyFeature
],
303 selectionchange: function() {
306 if (me
.onSelectionChange
) {
307 let sm
= me
.getSelectionModel();
308 let rec
= sm
.getSelection()[0];
310 me
.onSelectionChange(rec
, sm
);
316 Ext
.define('Proxmox.node.APTRepositories', {
317 extend
: 'Ext.panel.Panel',
318 xtype
: 'proxmoxNodeAPTRepositories',
319 mixins
: ['Proxmox.Mixin.CBind'],
323 product
: 'Proxmox VE', // default
327 product
: 'Proxmox VE', // default
329 subscriptionActive
: '',
330 noSubscriptionRepo
: '',
334 noErrors
: (get) => get('errorCount') === 0,
335 mainWarning: function(get) {
336 // Not yet initialized
337 if (get('subscriptionActive') === '' ||
338 get('enterpriseRepo') === '') {
342 let icon
= `<i class='fa fa-fw fa-exclamation-triangle critical'></i>`;
343 let fmt
= (msg
) => `<div class="black">${icon}${gettext('Warning')}: ${msg}</div>`;
345 if (!get('subscriptionActive') && get('enterpriseRepo')) {
346 return fmt(gettext('The enterprise repository is enabled, but there is no active subscription!'));
349 if (get('noSubscriptionRepo')) {
350 return fmt(gettext('The no-subscription repository is not recommended for production use!'));
353 if (!get('enterpriseRepo') && !get('noSubscriptionRepo')) {
354 let msg
= Ext
.String
.format(gettext('No {0} repository is enabled!'), get('product'));
365 title
: gettext('Warning'),
366 name
: 'repositoriesMainWarning',
369 title
: '{mainWarning}',
370 hidden
: '{!mainWarning}',
374 xtype
: 'proxmoxNodeAPTRepositoriesErrors',
375 name
: 'repositoriesErrors',
378 hidden
: '{noErrors}',
382 xtype
: 'proxmoxNodeAPTRepositoriesGrid',
383 name
: 'repositoriesGrid',
385 nodename
: '{nodename}',
387 majorUpgradeAllowed
: false, // TODO get release information from an API call?
388 onSelectionChange: function(rec
, sm
) {
391 let btn
= me
.up('proxmoxNodeAPTRepositories').down('#repoEnableButton');
392 btn
.setText(rec
.get('Enabled') ? gettext('Disable') : gettext('Enable'));
398 check_subscription: function() {
400 let vm
= me
.getViewModel();
402 Proxmox
.Utils
.API2Request({
403 url
: `/nodes/${me.nodename}/subscription`,
405 failure
: (response
, opts
) => Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
),
406 success: function(response
, opts
) {
407 const res
= response
.result
;
408 const subscription
= !(!res
|| !res
.data
|| res
.data
.status
.toLowerCase() !== 'active');
409 vm
.set('subscriptionActive', subscription
);
414 updateStandardRepos: function(standardRepos
) {
416 let vm
= me
.getViewModel();
418 let menu
= me
.down('#addMenu');
421 for (const standardRepo
of standardRepos
) {
422 const handle
= standardRepo
.handle
;
423 const status
= standardRepo
.status
;
425 if (handle
=== "enterprise") {
426 vm
.set('enterpriseRepo', status
);
427 } else if (handle
=== "no-subscription") {
428 vm
.set('noSubscriptionRepo', status
);
431 let status_text
= '';
432 if (status
!== undefined && status
!== null) {
433 status_text
= Ext
.String
.format(
435 gettext('configured'),
436 status
? gettext('enabled') : gettext('disabled'),
441 text
: standardRepo
.name
+ status_text
,
442 disabled
: status
!== undefined && status
!== null,
444 handler: function(menuItem
) {
445 Proxmox
.Utils
.checked_command(() => {
447 handle
: menuItem
.repoHandle
,
450 if (me
.digest
!== undefined) {
451 params
.digest
= me
.digest
;
454 Proxmox
.Utils
.API2Request({
455 url
: `/nodes/${me.nodename}/apt/repositories`,
458 failure: function(response
, opts
) {
459 Ext
.Msg
.alert(gettext('Error'), response
.htmlStatus
);
462 success: function(response
, opts
) {
474 let vm
= me
.getViewModel();
475 let repoGrid
= me
.down('proxmoxNodeAPTRepositoriesGrid');
476 let errorGrid
= me
.down('proxmoxNodeAPTRepositoriesErrors');
478 me
.store
.load(function(records
, operation
, success
) {
483 if (success
&& records
.length
> 0) {
484 let data
= records
[0].data
;
485 let files
= data
.files
;
486 errors
= data
.errors
;
487 digest
= data
.digest
;
489 files
.forEach(function(file
) {
490 for (let n
= 0; n
< file
.repositories
.length
; n
++) {
491 let repo
= file
.repositories
[n
];
492 repo
.Path
= file
.path
;
498 repoGrid
.addAdditionalInfos(gridData
, data
.infos
);
499 repoGrid
.store
.loadData(gridData
);
501 me
.updateStandardRepos(data
['standard-repos']);
506 vm
.set('errorCount', errors
.length
);
507 errorGrid
.store
.loadData(errors
);
510 me
.check_subscription();
514 activate: function() {
520 initComponent: function() {
524 throw "no node name specified";
527 let store
= Ext
.create('Ext.data.Store', {
530 url
: `/api2/json/nodes/${me.nodename}/apt/repositories`,
534 Ext
.apply(me
, { store
: store
});
536 Proxmox
.Utils
.monStoreErrors(me
, me
.store
, true);
540 me
.getViewModel().set('product', me
.product
);