]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/node/APTRepositories.js
css: increase padding for Debian and Proxmox symbol logos
[proxmox-widget-toolkit.git] / src / node / APTRepositories.js
CommitLineData
24313a9d
FE
1Ext.define('apt-repolist', {
2 extend: 'Ext.data.Model',
3 fields: [
4 'Path',
5 'Index',
d91987a5 6 'Origin',
24313a9d
FE
7 'FileType',
8 'Enabled',
9 'Comment',
10 'Types',
11 'URIs',
12 'Suites',
13 'Components',
14 'Options',
15 ],
16});
17
18Ext.define('Proxmox.node.APTRepositoriesErrors', {
19 extend: 'Ext.grid.GridPanel',
20
21 xtype: 'proxmoxNodeAPTRepositoriesErrors',
22
23 title: gettext('Errors'),
24
25 store: {},
26
27 viewConfig: {
28 stripeRows: false,
29 getRowClass: () => 'proxmox-invalid-row',
30 },
31
32 columns: [
33 {
34 header: gettext('File'),
35 dataIndex: 'path',
af48de6b 36 renderer: value => `<i class='pve-grid-fa fa fa-fw fa-exclamation-triangle'></i>${value}`,
24313a9d
FE
37 width: 350,
38 },
39 {
40 header: gettext('Error'),
41 dataIndex: 'error',
42 flex: 1,
43 },
44 ],
45});
46
47Ext.define('Proxmox.node.APTRepositoriesGrid', {
48 extend: 'Ext.grid.GridPanel',
24313a9d
FE
49 xtype: 'proxmoxNodeAPTRepositoriesGrid',
50
51 title: gettext('APT Repositories'),
52
994fe897
TL
53 cls: 'proxmox-apt-repos', // to allow applying styling to general components with local effect
54
24313a9d
FE
55 tbar: [
56 {
57 text: gettext('Reload'),
58 iconCls: 'fa fa-refresh',
59 handler: function() {
60 let me = this;
61 me.up('proxmoxNodeAPTRepositories').reload();
62 },
63 },
d76eedb4
FE
64 {
65 text: gettext('Add'),
66 menu: {
67 plain: true,
68 itemId: "addMenu",
69 items: [],
70 },
71 },
af48de6b 72 '-',
d76eedb4
FE
73 {
74 xtype: 'proxmoxButton',
bb64cd03
TL
75 text: gettext('Enable'),
76 defaultText: gettext('Enable'),
77 altText: gettext('Disable'),
78 id: 'repoEnableButton',
d76eedb4
FE
79 disabled: true,
80 handler: function(button, event, record) {
81 let me = this;
82 let panel = me.up('proxmoxNodeAPTRepositories');
83
84 let params = {
85 path: record.data.Path,
86 index: record.data.Index,
87 enabled: record.data.Enabled ? 0 : 1, // invert
88 };
89
90 if (panel.digest !== undefined) {
91 params.digest = panel.digest;
92 }
93
94 Proxmox.Utils.API2Request({
95 url: `/nodes/${panel.nodename}/apt/repositories`,
96 method: 'POST',
97 params: params,
98 failure: function(response, opts) {
99 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
100 panel.reload();
101 },
102 success: function(response, opts) {
103 panel.reload();
104 },
105 });
106 },
bb64cd03
TL
107 listeners: {
108 render: function(btn) {
109 // HACK: calculate the max button width on first render to avoid toolbar glitches
110 let defSize = btn.getSize().width;
111
112 btn.setText(btn.altText);
113 let altSize = btn.getSize().width;
114
115 btn.setText(btn.defaultText);
116 btn.setSize({ width: altSize > defSize ? altSize : defSize });
117 },
118 },
d76eedb4 119 },
24313a9d
FE
120 ],
121
122 sortableColumns: false,
123
124 columns: [
125 {
03c4c65b 126 xtype: 'checkcolumn',
24313a9d
FE
127 header: gettext('Enabled'),
128 dataIndex: 'Enabled',
03c4c65b
TL
129 listeners: {
130 beforecheckchange: () => false, // veto, we don't want to allow inline change - to subtle
131 },
24313a9d
FE
132 width: 90,
133 },
134 {
135 header: gettext('Types'),
136 dataIndex: 'Types',
137 renderer: function(types, cell, record) {
138 return types.join(' ');
139 },
140 width: 100,
141 },
142 {
143 header: gettext('URIs'),
144 dataIndex: 'URIs',
145 renderer: function(uris, cell, record) {
146 return uris.join(' ');
147 },
148 width: 350,
149 },
150 {
151 header: gettext('Suites'),
152 dataIndex: 'Suites',
153 renderer: function(suites, cell, record) {
154 return suites.join(' ');
155 },
156 width: 130,
157 },
158 {
159 header: gettext('Components'),
160 dataIndex: 'Components',
161 renderer: function(components, cell, record) {
162 return components.join(' ');
163 },
164 width: 170,
165 },
166 {
167 header: gettext('Options'),
168 dataIndex: 'Options',
169 renderer: function(options, cell, record) {
170 if (!options) {
171 return '';
172 }
173
174 let filetype = record.data.FileType;
175 let text = '';
176
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>`;
185 } else {
186 throw "unkown file type";
187 }
188 });
189 return text;
190 },
191 flex: 1,
192 },
03c4c65b 193 {
d91987a5
FE
194 header: gettext('Origin'),
195 dataIndex: 'Origin',
196 width: 100,
03c4c65b 197 },
24313a9d
FE
198 {
199 header: gettext('Comment'),
200 dataIndex: 'Comment',
201 flex: 2,
202 },
203 ],
204
205 addAdditionalInfos: function(gridData, infos) {
206 let me = this;
207
208 let warnings = {};
d91987a5 209 let origins = {};
24313a9d
FE
210
211 let addLine = function(obj, key, line) {
212 if (obj[key]) {
213 obj[key] += "\n";
214 obj[key] += line;
215 } else {
216 obj[key] = line;
217 }
218 };
219
220 for (const info of infos) {
221 const key = `${info.path}:${info.index}`;
222 if (info.kind === 'warning' ||
af48de6b
TL
223 (info.kind === 'ignore-pre-upgrade-warning' && !me.majorUpgradeAllowed)
224 ) {
24313a9d 225 addLine(warnings, key, gettext('Warning') + ": " + info.message);
d91987a5
FE
226 } else if (info.kind === 'origin') {
227 origins[key] = info.message;
24313a9d
FE
228 }
229 }
230
231 gridData.forEach(function(record) {
232 const key = `${record.Path}:${record.Index}`;
d91987a5 233 record.Origin = origins[key];
24313a9d
FE
234 });
235
236 me.rowBodyFeature.getAdditionalData = function(innerData, rowIndex, record, orig) {
237 let headerCt = this.view.headerCt;
238 let colspan = headerCt.getColumnCount();
239
240 const key = `${innerData.Path}:${innerData.Index}`;
241 const warning_text = warnings[key];
242
243 return {
244 rowBody: '<div style="color: red; white-space: pre-line">' +
245 Ext.String.htmlEncode(warning_text) + '</div>',
246 rowBodyCls: warning_text ? '' : Ext.baseCSSPrefix + 'grid-row-body-hidden',
247 rowBodyColspan: colspan,
248 };
249 };
250 },
251
252 initComponent: function() {
253 let me = this;
254
255 if (!me.nodename) {
256 throw "no node name specified";
257 }
258
259 let store = Ext.create('Ext.data.Store', {
260 model: 'apt-repolist',
261 groupField: 'Path',
262 sorters: [
263 {
264 property: 'Index',
265 direction: 'ASC',
266 },
267 ],
268 });
269
270 let rowBodyFeature = Ext.create('Ext.grid.feature.RowBody', {});
271
272 let groupingFeature = Ext.create('Ext.grid.feature.Grouping', {
273 groupHeaderTpl: '{[ "File: " + values.name ]} ({rows.length} ' +
274 'repositor{[values.rows.length > 1 ? "ies" : "y"]})',
275 enableGroupingMenu: false,
276 });
277
278 let sm = Ext.create('Ext.selection.RowModel', {});
279
280 Ext.apply(me, {
281 store: store,
282 selModel: sm,
283 rowBodyFeature: rowBodyFeature,
284 features: [groupingFeature, rowBodyFeature],
285 });
286
287 me.callParent();
288 },
bb64cd03
TL
289
290 listeners: {
291 selectionchange: function() {
292 let me = this;
293
294 if (me.onSelectionChange) {
295 let sm = me.getSelectionModel();
296 let rec = sm.getSelection()[0];
297
298 me.onSelectionChange(rec, sm);
299 }
300 },
301 },
24313a9d
FE
302});
303
304Ext.define('Proxmox.node.APTRepositories', {
305 extend: 'Ext.panel.Panel',
24313a9d
FE
306 xtype: 'proxmoxNodeAPTRepositories',
307 mixins: ['Proxmox.Mixin.CBind'],
308
309 digest: undefined,
310
3fc020f4
TL
311 product: 'Proxmox VE', // default
312
24313a9d
FE
313 viewModel: {
314 data: {
3fc020f4 315 product: 'Proxmox VE', // default
24313a9d
FE
316 errorCount: 0,
317 subscriptionActive: '',
318 noSubscriptionRepo: '',
319 enterpriseRepo: '',
320 },
321 formulas: {
322 noErrors: (get) => get('errorCount') === 0,
323 mainWarning: function(get) {
324 // Not yet initialized
325 if (get('subscriptionActive') === '' ||
326 get('enterpriseRepo') === '') {
327 return '';
328 }
329
3fc020f4
TL
330 let icon = `<i class='fa fa-fw fa-exclamation-triangle critical'></i>`;
331 let fmt = (msg) => `<div class="black">${icon}${gettext('Warning')}: ${msg}</div>`;
24313a9d
FE
332
333 if (!get('subscriptionActive') && get('enterpriseRepo')) {
3fc020f4 334 return fmt(gettext('The enterprise repository is enabled, but there is no active subscription!'));
24313a9d
FE
335 }
336
337 if (get('noSubscriptionRepo')) {
3fc020f4 338 return fmt(gettext('The no-subscription repository is not recommended for production use!'));
24313a9d
FE
339 }
340
341 if (!get('enterpriseRepo') && !get('noSubscriptionRepo')) {
3fc020f4
TL
342 let msg = Ext.String.format(gettext('No {0} repository is enabled!'), get('product'));
343 return fmt(msg);
24313a9d
FE
344 }
345
346 return '';
347 },
348 },
349 },
350
351 items: [
352 {
353 title: gettext('Warning'),
354 name: 'repositoriesMainWarning',
355 xtype: 'panel',
356 bind: {
357 title: '{mainWarning}',
358 hidden: '{!mainWarning}',
359 },
360 },
361 {
362 xtype: 'proxmoxNodeAPTRepositoriesErrors',
363 name: 'repositoriesErrors',
364 hidden: true,
365 bind: {
366 hidden: '{noErrors}',
367 },
368 },
369 {
370 xtype: 'proxmoxNodeAPTRepositoriesGrid',
371 name: 'repositoriesGrid',
372 cbind: {
373 nodename: '{nodename}',
374 },
375 majorUpgradeAllowed: false, // TODO get release information from an API call?
bb64cd03
TL
376 onSelectionChange: function(rec, sm) {
377 let me = this;
378 if (rec) {
379 let btn = me.up('proxmoxNodeAPTRepositories').down('#repoEnableButton');
380 btn.setText(rec.get('Enabled') ? gettext('Disable') : gettext('Enable'));
381 }
382 },
24313a9d
FE
383 },
384 ],
385
386 check_subscription: function() {
387 let me = this;
388 let vm = me.getViewModel();
389
390 Proxmox.Utils.API2Request({
391 url: `/nodes/${me.nodename}/subscription`,
392 method: 'GET',
af48de6b 393 failure: (response, opts) => Ext.Msg.alert(gettext('Error'), response.htmlStatus),
24313a9d
FE
394 success: function(response, opts) {
395 const res = response.result;
af48de6b 396 const subscription = !(!res || !res.data || res.data.status.toLowerCase() !== 'active');
24313a9d
FE
397 vm.set('subscriptionActive', subscription);
398 },
399 });
400 },
401
402 updateStandardRepos: function(standardRepos) {
403 let me = this;
404 let vm = me.getViewModel();
405
d76eedb4
FE
406 let menu = me.down('#addMenu');
407 menu.removeAll();
408
24313a9d
FE
409 for (const standardRepo of standardRepos) {
410 const handle = standardRepo.handle;
411 const status = standardRepo.status;
412
413 if (handle === "enterprise") {
414 vm.set('enterpriseRepo', status);
415 } else if (handle === "no-subscription") {
416 vm.set('noSubscriptionRepo', status);
417 }
d76eedb4
FE
418
419 let status_text = '';
420 if (status !== undefined && status !== null) {
421 status_text = Ext.String.format(
422 ' ({0}, {1})',
423 gettext('configured'),
424 status ? gettext('enabled') : gettext('disabled'),
425 );
426 }
427
428 menu.add({
429 text: standardRepo.name + status_text,
430 disabled: status !== undefined && status !== null,
431 repoHandle: handle,
432 handler: function(menuItem) {
0e79ce21
TL
433 Proxmox.Utils.checked_command(() => {
434 let params = {
435 handle: menuItem.repoHandle,
436 };
437
438 if (me.digest !== undefined) {
439 params.digest = me.digest;
440 }
441
442 Proxmox.Utils.API2Request({
443 url: `/nodes/${me.nodename}/apt/repositories`,
444 method: 'PUT',
445 params: params,
446 failure: function(response, opts) {
447 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
448 me.reload();
449 },
450 success: function(response, opts) {
451 me.reload();
452 },
453 });
d76eedb4
FE
454 });
455 },
456 });
24313a9d
FE
457 }
458 },
459
460 reload: function() {
461 let me = this;
462 let vm = me.getViewModel();
463 let repoGrid = me.down('proxmoxNodeAPTRepositoriesGrid');
464 let errorGrid = me.down('proxmoxNodeAPTRepositoriesErrors');
465
466 me.store.load(function(records, operation, success) {
467 let gridData = [];
468 let errors = [];
469 let digest;
470
471 if (success && records.length > 0) {
472 let data = records[0].data;
473 let files = data.files;
474 errors = data.errors;
475 digest = data.digest;
476
477 files.forEach(function(file) {
478 for (let n = 0; n < file.repositories.length; n++) {
479 let repo = file.repositories[n];
480 repo.Path = file.path;
481 repo.Index = n;
482 gridData.push(repo);
483 }
484 });
485
486 repoGrid.addAdditionalInfos(gridData, data.infos);
487 repoGrid.store.loadData(gridData);
488
489 me.updateStandardRepos(data['standard-repos']);
490 }
491
492 me.digest = digest;
493
494 vm.set('errorCount', errors.length);
495 errorGrid.store.loadData(errors);
496 });
497
498 me.check_subscription();
499 },
500
501 listeners: {
502 activate: function() {
503 let me = this;
504 me.reload();
505 },
506 },
507
508 initComponent: function() {
509 let me = this;
510
511 if (!me.nodename) {
512 throw "no node name specified";
513 }
514
515 let store = Ext.create('Ext.data.Store', {
516 proxy: {
517 type: 'proxmox',
518 url: `/api2/json/nodes/${me.nodename}/apt/repositories`,
519 },
520 });
521
522 Ext.apply(me, { store: store });
523
524 Proxmox.Utils.monStoreErrors(me, me.store, true);
525
526 me.callParent();
3fc020f4
TL
527
528 me.getViewModel().set('product', me.product);
24313a9d
FE
529 },
530});