]>
Commit | Line | Data |
---|---|---|
24313a9d FE |
1 | Ext.define('apt-repolist', { |
2 | extend: 'Ext.data.Model', | |
3 | fields: [ | |
4 | 'Path', | |
5 | 'Index', | |
6 | 'OfficialHost', | |
7 | 'FileType', | |
8 | 'Enabled', | |
9 | 'Comment', | |
10 | 'Types', | |
11 | 'URIs', | |
12 | 'Suites', | |
13 | 'Components', | |
14 | 'Options', | |
15 | ], | |
16 | }); | |
17 | ||
18 | Ext.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', | |
36 | renderer: function(value, cell, record) { | |
37 | return "<i class='pve-grid-fa fa fa-fw " + | |
38 | "fa-exclamation-triangle'></i>" + value; | |
39 | }, | |
40 | width: 350, | |
41 | }, | |
42 | { | |
43 | header: gettext('Error'), | |
44 | dataIndex: 'error', | |
45 | flex: 1, | |
46 | }, | |
47 | ], | |
48 | }); | |
49 | ||
50 | Ext.define('Proxmox.node.APTRepositoriesGrid', { | |
51 | extend: 'Ext.grid.GridPanel', | |
52 | ||
53 | xtype: 'proxmoxNodeAPTRepositoriesGrid', | |
54 | ||
55 | title: gettext('APT Repositories'), | |
56 | ||
57 | tbar: [ | |
58 | { | |
59 | text: gettext('Reload'), | |
60 | iconCls: 'fa fa-refresh', | |
61 | handler: function() { | |
62 | let me = this; | |
63 | me.up('proxmoxNodeAPTRepositories').reload(); | |
64 | }, | |
65 | }, | |
66 | ], | |
67 | ||
68 | sortableColumns: false, | |
69 | ||
70 | columns: [ | |
71 | { | |
72 | header: gettext('Official'), | |
73 | dataIndex: 'OfficialHost', | |
74 | renderer: function(value, cell, record) { | |
75 | let icon = (cls) => `<i class="fa fa-fw ${cls}"></i>`; | |
76 | ||
77 | const enabled = record.data.Enabled; | |
78 | ||
79 | if (value === undefined || value === null) { | |
80 | return icon('fa-question-circle-o'); | |
81 | } | |
82 | if (!value) { | |
83 | return icon('fa-times ' + (enabled ? 'critical' : 'faded')); | |
84 | } | |
85 | return icon('fa-check ' + (enabled ? 'good' : 'faded')); | |
86 | }, | |
87 | width: 70, | |
88 | }, | |
89 | { | |
90 | header: gettext('Enabled'), | |
91 | dataIndex: 'Enabled', | |
92 | renderer: Proxmox.Utils.format_enabled_toggle, | |
93 | width: 90, | |
94 | }, | |
95 | { | |
96 | header: gettext('Types'), | |
97 | dataIndex: 'Types', | |
98 | renderer: function(types, cell, record) { | |
99 | return types.join(' '); | |
100 | }, | |
101 | width: 100, | |
102 | }, | |
103 | { | |
104 | header: gettext('URIs'), | |
105 | dataIndex: 'URIs', | |
106 | renderer: function(uris, cell, record) { | |
107 | return uris.join(' '); | |
108 | }, | |
109 | width: 350, | |
110 | }, | |
111 | { | |
112 | header: gettext('Suites'), | |
113 | dataIndex: 'Suites', | |
114 | renderer: function(suites, cell, record) { | |
115 | return suites.join(' '); | |
116 | }, | |
117 | width: 130, | |
118 | }, | |
119 | { | |
120 | header: gettext('Components'), | |
121 | dataIndex: 'Components', | |
122 | renderer: function(components, cell, record) { | |
123 | return components.join(' '); | |
124 | }, | |
125 | width: 170, | |
126 | }, | |
127 | { | |
128 | header: gettext('Options'), | |
129 | dataIndex: 'Options', | |
130 | renderer: function(options, cell, record) { | |
131 | if (!options) { | |
132 | return ''; | |
133 | } | |
134 | ||
135 | let filetype = record.data.FileType; | |
136 | let text = ''; | |
137 | ||
138 | options.forEach(function(option) { | |
139 | let key = option.Key; | |
140 | if (filetype === 'list') { | |
141 | let values = option.Values.join(','); | |
142 | text += `${key}=${values} `; | |
143 | } else if (filetype === 'sources') { | |
144 | let values = option.Values.join(' '); | |
145 | text += `${key}: ${values}<br>`; | |
146 | } else { | |
147 | throw "unkown file type"; | |
148 | } | |
149 | }); | |
150 | return text; | |
151 | }, | |
152 | flex: 1, | |
153 | }, | |
154 | { | |
155 | header: gettext('Comment'), | |
156 | dataIndex: 'Comment', | |
157 | flex: 2, | |
158 | }, | |
159 | ], | |
160 | ||
161 | addAdditionalInfos: function(gridData, infos) { | |
162 | let me = this; | |
163 | ||
164 | let warnings = {}; | |
165 | let officialHosts = {}; | |
166 | ||
167 | let addLine = function(obj, key, line) { | |
168 | if (obj[key]) { | |
169 | obj[key] += "\n"; | |
170 | obj[key] += line; | |
171 | } else { | |
172 | obj[key] = line; | |
173 | } | |
174 | }; | |
175 | ||
176 | for (const info of infos) { | |
177 | const key = `${info.path}:${info.index}`; | |
178 | if (info.kind === 'warning' || | |
179 | (info.kind === 'ignore-pre-upgrade-warning' && !me.majorUpgradeAllowed)) { | |
180 | addLine(warnings, key, gettext('Warning') + ": " + info.message); | |
181 | } else if (info.kind === 'badge' && info.message === 'official host name') { | |
182 | officialHosts[key] = true; | |
183 | } | |
184 | } | |
185 | ||
186 | gridData.forEach(function(record) { | |
187 | const key = `${record.Path}:${record.Index}`; | |
188 | record.OfficialHost = !!officialHosts[key]; | |
189 | }); | |
190 | ||
191 | me.rowBodyFeature.getAdditionalData = function(innerData, rowIndex, record, orig) { | |
192 | let headerCt = this.view.headerCt; | |
193 | let colspan = headerCt.getColumnCount(); | |
194 | ||
195 | const key = `${innerData.Path}:${innerData.Index}`; | |
196 | const warning_text = warnings[key]; | |
197 | ||
198 | return { | |
199 | rowBody: '<div style="color: red; white-space: pre-line">' + | |
200 | Ext.String.htmlEncode(warning_text) + '</div>', | |
201 | rowBodyCls: warning_text ? '' : Ext.baseCSSPrefix + 'grid-row-body-hidden', | |
202 | rowBodyColspan: colspan, | |
203 | }; | |
204 | }; | |
205 | }, | |
206 | ||
207 | initComponent: function() { | |
208 | let me = this; | |
209 | ||
210 | if (!me.nodename) { | |
211 | throw "no node name specified"; | |
212 | } | |
213 | ||
214 | let store = Ext.create('Ext.data.Store', { | |
215 | model: 'apt-repolist', | |
216 | groupField: 'Path', | |
217 | sorters: [ | |
218 | { | |
219 | property: 'Index', | |
220 | direction: 'ASC', | |
221 | }, | |
222 | ], | |
223 | }); | |
224 | ||
225 | let rowBodyFeature = Ext.create('Ext.grid.feature.RowBody', {}); | |
226 | ||
227 | let groupingFeature = Ext.create('Ext.grid.feature.Grouping', { | |
228 | groupHeaderTpl: '{[ "File: " + values.name ]} ({rows.length} ' + | |
229 | 'repositor{[values.rows.length > 1 ? "ies" : "y"]})', | |
230 | enableGroupingMenu: false, | |
231 | }); | |
232 | ||
233 | let sm = Ext.create('Ext.selection.RowModel', {}); | |
234 | ||
235 | Ext.apply(me, { | |
236 | store: store, | |
237 | selModel: sm, | |
238 | rowBodyFeature: rowBodyFeature, | |
239 | features: [groupingFeature, rowBodyFeature], | |
240 | }); | |
241 | ||
242 | me.callParent(); | |
243 | }, | |
244 | }); | |
245 | ||
246 | Ext.define('Proxmox.node.APTRepositories', { | |
247 | extend: 'Ext.panel.Panel', | |
248 | ||
249 | xtype: 'proxmoxNodeAPTRepositories', | |
250 | mixins: ['Proxmox.Mixin.CBind'], | |
251 | ||
252 | digest: undefined, | |
253 | ||
254 | viewModel: { | |
255 | data: { | |
256 | errorCount: 0, | |
257 | subscriptionActive: '', | |
258 | noSubscriptionRepo: '', | |
259 | enterpriseRepo: '', | |
260 | }, | |
261 | formulas: { | |
262 | noErrors: (get) => get('errorCount') === 0, | |
263 | mainWarning: function(get) { | |
264 | // Not yet initialized | |
265 | if (get('subscriptionActive') === '' || | |
266 | get('enterpriseRepo') === '') { | |
267 | return ''; | |
268 | } | |
269 | ||
270 | let withStyle = (msg) => "<div style='color:red;'><i class='fa fa-fw " + | |
271 | "fa-exclamation-triangle'></i>" + gettext('Warning') + ': ' + msg + "</div>"; | |
272 | ||
273 | if (!get('subscriptionActive') && get('enterpriseRepo')) { | |
274 | return withStyle(gettext('The enterprise repository is ' + | |
275 | 'enabled, but there is no active subscription!')); | |
276 | } | |
277 | ||
278 | if (get('noSubscriptionRepo')) { | |
279 | return withStyle(gettext('The no-subscription repository is ' + | |
280 | 'not recommended for production use!')); | |
281 | } | |
282 | ||
283 | if (!get('enterpriseRepo') && !get('noSubscriptionRepo')) { | |
284 | return withStyle(gettext('No Proxmox repository is enabled!')); | |
285 | } | |
286 | ||
287 | return ''; | |
288 | }, | |
289 | }, | |
290 | }, | |
291 | ||
292 | items: [ | |
293 | { | |
294 | title: gettext('Warning'), | |
295 | name: 'repositoriesMainWarning', | |
296 | xtype: 'panel', | |
297 | bind: { | |
298 | title: '{mainWarning}', | |
299 | hidden: '{!mainWarning}', | |
300 | }, | |
301 | }, | |
302 | { | |
303 | xtype: 'proxmoxNodeAPTRepositoriesErrors', | |
304 | name: 'repositoriesErrors', | |
305 | hidden: true, | |
306 | bind: { | |
307 | hidden: '{noErrors}', | |
308 | }, | |
309 | }, | |
310 | { | |
311 | xtype: 'proxmoxNodeAPTRepositoriesGrid', | |
312 | name: 'repositoriesGrid', | |
313 | cbind: { | |
314 | nodename: '{nodename}', | |
315 | }, | |
316 | majorUpgradeAllowed: false, // TODO get release information from an API call? | |
317 | }, | |
318 | ], | |
319 | ||
320 | check_subscription: function() { | |
321 | let me = this; | |
322 | let vm = me.getViewModel(); | |
323 | ||
324 | Proxmox.Utils.API2Request({ | |
325 | url: `/nodes/${me.nodename}/subscription`, | |
326 | method: 'GET', | |
327 | failure: function(response, opts) { | |
328 | Ext.Msg.alert(gettext('Error'), response.htmlStatus); | |
329 | }, | |
330 | success: function(response, opts) { | |
331 | const res = response.result; | |
332 | const subscription = !(res === null || res === undefined || | |
333 | !res || res.data.status.toLowerCase() !== 'active'); | |
334 | vm.set('subscriptionActive', subscription); | |
335 | }, | |
336 | }); | |
337 | }, | |
338 | ||
339 | updateStandardRepos: function(standardRepos) { | |
340 | let me = this; | |
341 | let vm = me.getViewModel(); | |
342 | ||
343 | for (const standardRepo of standardRepos) { | |
344 | const handle = standardRepo.handle; | |
345 | const status = standardRepo.status; | |
346 | ||
347 | if (handle === "enterprise") { | |
348 | vm.set('enterpriseRepo', status); | |
349 | } else if (handle === "no-subscription") { | |
350 | vm.set('noSubscriptionRepo', status); | |
351 | } | |
352 | } | |
353 | }, | |
354 | ||
355 | reload: function() { | |
356 | let me = this; | |
357 | let vm = me.getViewModel(); | |
358 | let repoGrid = me.down('proxmoxNodeAPTRepositoriesGrid'); | |
359 | let errorGrid = me.down('proxmoxNodeAPTRepositoriesErrors'); | |
360 | ||
361 | me.store.load(function(records, operation, success) { | |
362 | let gridData = []; | |
363 | let errors = []; | |
364 | let digest; | |
365 | ||
366 | if (success && records.length > 0) { | |
367 | let data = records[0].data; | |
368 | let files = data.files; | |
369 | errors = data.errors; | |
370 | digest = data.digest; | |
371 | ||
372 | files.forEach(function(file) { | |
373 | for (let n = 0; n < file.repositories.length; n++) { | |
374 | let repo = file.repositories[n]; | |
375 | repo.Path = file.path; | |
376 | repo.Index = n; | |
377 | gridData.push(repo); | |
378 | } | |
379 | }); | |
380 | ||
381 | repoGrid.addAdditionalInfos(gridData, data.infos); | |
382 | repoGrid.store.loadData(gridData); | |
383 | ||
384 | me.updateStandardRepos(data['standard-repos']); | |
385 | } | |
386 | ||
387 | me.digest = digest; | |
388 | ||
389 | vm.set('errorCount', errors.length); | |
390 | errorGrid.store.loadData(errors); | |
391 | }); | |
392 | ||
393 | me.check_subscription(); | |
394 | }, | |
395 | ||
396 | listeners: { | |
397 | activate: function() { | |
398 | let me = this; | |
399 | me.reload(); | |
400 | }, | |
401 | }, | |
402 | ||
403 | initComponent: function() { | |
404 | let me = this; | |
405 | ||
406 | if (!me.nodename) { | |
407 | throw "no node name specified"; | |
408 | } | |
409 | ||
410 | let store = Ext.create('Ext.data.Store', { | |
411 | proxy: { | |
412 | type: 'proxmox', | |
413 | url: `/api2/json/nodes/${me.nodename}/apt/repositories`, | |
414 | }, | |
415 | }); | |
416 | ||
417 | Ext.apply(me, { store: store }); | |
418 | ||
419 | Proxmox.Utils.monStoreErrors(me, me.store, true); | |
420 | ||
421 | me.callParent(); | |
422 | }, | |
423 | }); |