]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/OptionView.js
ui: implement tag ordering as configured in datacenter config
[pve-manager.git] / www / manager6 / dc / OptionView.js
1 Ext.define('PVE.dc.OptionView', {
2 extend: 'Proxmox.grid.ObjectGrid',
3 alias: ['widget.pveDcOptionView'],
4
5 onlineHelp: 'datacenter_configuration_file',
6
7 monStoreErrors: true,
8 userCls: 'proxmox-tags-full',
9
10 add_inputpanel_row: function(name, text, opts) {
11 var me = this;
12
13 opts = opts || {};
14 me.rows = me.rows || {};
15
16 let canEdit = !Object.prototype.hasOwnProperty.call(opts, 'caps') || opts.caps;
17 me.rows[name] = {
18 required: true,
19 defaultValue: opts.defaultValue,
20 header: text,
21 renderer: opts.renderer,
22 editor: canEdit ? {
23 xtype: 'proxmoxWindowEdit',
24 width: opts.width || 350,
25 subject: text,
26 onlineHelp: opts.onlineHelp,
27 fieldDefaults: {
28 labelWidth: opts.labelWidth || 100,
29 },
30 setValues: function(values) {
31 var edit_value = values[name];
32
33 if (opts.parseBeforeSet) {
34 edit_value = PVE.Parser.parsePropertyString(edit_value);
35 }
36
37 Ext.Array.each(this.query('inputpanel'), function(panel) {
38 panel.setValues(edit_value);
39 });
40 },
41 url: opts.url,
42 items: [{
43 xtype: 'inputpanel',
44 onGetValues: function(values) {
45 if (values === undefined || Object.keys(values).length === 0) {
46 return { 'delete': name };
47 }
48 var ret_val = {};
49 ret_val[name] = PVE.Parser.printPropertyString(values);
50 return ret_val;
51 },
52 items: opts.items,
53 }],
54 } : undefined,
55 };
56 },
57
58 render_bwlimits: function(value) {
59 if (!value) {
60 return gettext("None");
61 }
62
63 let parsed = PVE.Parser.parsePropertyString(value);
64 return Object.entries(parsed)
65 .map(([k, v]) => k + ": " + Proxmox.Utils.format_size(v * 1024) + "/s")
66 .join(',');
67 },
68
69 initComponent: function() {
70 var me = this;
71
72 me.add_combobox_row('keyboard', gettext('Keyboard Layout'), {
73 renderer: PVE.Utils.render_kvm_language,
74 comboItems: Object.entries(PVE.Utils.kvm_keymaps),
75 defaultValue: '__default__',
76 deleteEmpty: true,
77 });
78 me.add_text_row('http_proxy', gettext('HTTP proxy'), {
79 defaultValue: Proxmox.Utils.noneText,
80 vtype: 'HttpProxy',
81 deleteEmpty: true,
82 });
83 me.add_combobox_row('console', gettext('Console Viewer'), {
84 renderer: PVE.Utils.render_console_viewer,
85 comboItems: Object.entries(PVE.Utils.console_map),
86 defaultValue: '__default__',
87 deleteEmpty: true,
88 });
89 me.add_text_row('email_from', gettext('Email from address'), {
90 deleteEmpty: true,
91 vtype: 'proxmoxMail',
92 defaultValue: 'root@$hostname',
93 });
94 me.add_text_row('mac_prefix', gettext('MAC address prefix'), {
95 deleteEmpty: true,
96 vtype: 'MacPrefix',
97 defaultValue: Proxmox.Utils.noneText,
98 });
99 me.add_inputpanel_row('migration', gettext('Migration Settings'), {
100 renderer: PVE.Utils.render_as_property_string,
101 labelWidth: 120,
102 url: "/api2/extjs/cluster/options",
103 defaultKey: 'type',
104 items: [{
105 xtype: 'displayfield',
106 name: 'type',
107 fieldLabel: gettext('Type'),
108 value: 'secure',
109 submitValue: true,
110 }, {
111 xtype: 'proxmoxNetworkSelector',
112 name: 'network',
113 fieldLabel: gettext('Network'),
114 value: null,
115 emptyText: Proxmox.Utils.defaultText,
116 autoSelect: false,
117 skipEmptyText: true,
118 }],
119 });
120 me.add_inputpanel_row('ha', gettext('HA Settings'), {
121 renderer: PVE.Utils.render_dc_ha_opts,
122 labelWidth: 120,
123 url: "/api2/extjs/cluster/options",
124 onlineHelp: 'ha_manager_shutdown_policy',
125 items: [{
126 xtype: 'proxmoxKVComboBox',
127 name: 'shutdown_policy',
128 fieldLabel: gettext('Shutdown Policy'),
129 deleteEmpty: false,
130 value: '__default__',
131 comboItems: [
132 ['__default__', Proxmox.Utils.defaultText + ' (conditional)'],
133 ['freeze', 'freeze'],
134 ['failover', 'failover'],
135 ['migrate', 'migrate'],
136 ['conditional', 'conditional'],
137 ],
138 defaultValue: '__default__',
139 }],
140 });
141 me.add_inputpanel_row('u2f', gettext('U2F Settings'), {
142 renderer: v => !v ? Proxmox.Utils.NoneText : PVE.Parser.printPropertyString(v),
143 width: 450,
144 url: "/api2/extjs/cluster/options",
145 onlineHelp: 'pveum_configure_u2f',
146 items: [{
147 xtype: 'textfield',
148 name: 'appid',
149 fieldLabel: gettext('U2F AppID URL'),
150 emptyText: gettext('Defaults to origin'),
151 value: '',
152 deleteEmpty: true,
153 skipEmptyText: true,
154 submitEmptyText: false,
155 }, {
156 xtype: 'textfield',
157 name: 'origin',
158 fieldLabel: gettext('U2F Origin'),
159 emptyText: gettext('Defaults to requesting host URI'),
160 value: '',
161 deleteEmpty: true,
162 skipEmptyText: true,
163 submitEmptyText: false,
164 },
165 {
166 xtype: 'box',
167 height: 25,
168 html: `<span class='pmx-hint'>${gettext('Note:')}</span> `
169 + Ext.String.format(gettext('{0} is deprecated, use {1}'), 'U2F', 'WebAuthn'),
170 },
171 {
172 xtype: 'displayfield',
173 userCls: 'pmx-hint',
174 value: gettext('NOTE: Changing an AppID breaks existing U2F registrations!'),
175 }],
176 });
177 me.add_inputpanel_row('webauthn', gettext('WebAuthn Settings'), {
178 renderer: v => !v ? Proxmox.Utils.NoneText : PVE.Parser.printPropertyString(v),
179 width: 450,
180 url: "/api2/extjs/cluster/options",
181 onlineHelp: 'pveum_configure_webauthn',
182 items: [{
183 xtype: 'textfield',
184 fieldLabel: gettext('Name'),
185 name: 'rp', // NOTE: relying party consists of name and id, this is the name
186 allowBlank: false,
187 },
188 {
189 xtype: 'textfield',
190 fieldLabel: gettext('Origin'),
191 emptyText: Ext.String.format(gettext("Domain Lockdown (e.g., {0})"), document.location.origin),
192 name: 'origin',
193 allowBlank: true,
194 },
195 {
196 xtype: 'textfield',
197 fieldLabel: 'ID',
198 name: 'id',
199 allowBlank: false,
200 listeners: {
201 dirtychange: (f, isDirty) =>
202 f.up('panel').down('box[id=idChangeWarning]').setHidden(!f.originalValue || !isDirty),
203 },
204 },
205 {
206 xtype: 'container',
207 layout: 'hbox',
208 items: [
209 {
210 xtype: 'box',
211 flex: 1,
212 },
213 {
214 xtype: 'button',
215 text: gettext('Auto-fill'),
216 iconCls: 'fa fa-fw fa-pencil-square-o',
217 handler: function(button, ev) {
218 let panel = this.up('panel');
219 let fqdn = document.location.hostname;
220
221 panel.down('field[name=rp]').setValue(fqdn);
222
223 let idField = panel.down('field[name=id]');
224 let currentID = idField.getValue();
225 if (!currentID || currentID.length === 0) {
226 idField.setValue(fqdn);
227 }
228 },
229 },
230 ],
231 },
232 {
233 xtype: 'box',
234 height: 25,
235 html: `<span class='pmx-hint'>${gettext('Note:')}</span> `
236 + gettext('WebAuthn requires using a trusted certificate.'),
237 },
238 {
239 xtype: 'box',
240 id: 'idChangeWarning',
241 hidden: true,
242 padding: '5 0 0 0',
243 html: '<i class="fa fa-exclamation-triangle warning"></i> '
244 + gettext('Changing the ID breaks existing WebAuthn TFA entries.'),
245 }],
246 });
247 me.add_inputpanel_row('bwlimit', gettext('Bandwidth Limits'), {
248 renderer: me.render_bwlimits,
249 width: 450,
250 url: "/api2/extjs/cluster/options",
251 parseBeforeSet: true,
252 labelWidth: 120,
253 items: [{
254 xtype: 'pveBandwidthField',
255 name: 'default',
256 fieldLabel: gettext('Default'),
257 emptyText: gettext('none'),
258 backendUnit: "KiB",
259 },
260 {
261 xtype: 'pveBandwidthField',
262 name: 'restore',
263 fieldLabel: gettext('Backup Restore'),
264 emptyText: gettext('default'),
265 backendUnit: "KiB",
266 },
267 {
268 xtype: 'pveBandwidthField',
269 name: 'migration',
270 fieldLabel: gettext('Migration'),
271 emptyText: gettext('default'),
272 backendUnit: "KiB",
273 },
274 {
275 xtype: 'pveBandwidthField',
276 name: 'clone',
277 fieldLabel: gettext('Clone'),
278 emptyText: gettext('default'),
279 backendUnit: "KiB",
280 },
281 {
282 xtype: 'pveBandwidthField',
283 name: 'move',
284 fieldLabel: gettext('Disk Move'),
285 emptyText: gettext('default'),
286 backendUnit: "KiB",
287 }],
288 });
289 me.add_integer_row('max_workers', gettext('Maximal Workers/bulk-action'), {
290 deleteEmpty: true,
291 defaultValue: 4,
292 minValue: 1,
293 maxValue: 64, // arbitrary but generous limit as limits are good
294 });
295 me.add_inputpanel_row('next-id', gettext('Next Free VMID Range'), {
296 renderer: PVE.Utils.render_as_property_string,
297 url: "/api2/extjs/cluster/options",
298 items: [{
299 xtype: 'proxmoxintegerfield',
300 name: 'lower',
301 fieldLabel: gettext('Lower'),
302 emptyText: '100',
303 minValue: 100,
304 maxValue: 1000 * 1000 * 1000 - 1,
305 submitValue: true,
306 }, {
307 xtype: 'proxmoxintegerfield',
308 name: 'upper',
309 fieldLabel: gettext('Upper'),
310 emptyText: '1.000.000',
311 minValue: 100,
312 maxValue: 1000 * 1000 * 1000 - 1,
313 submitValue: true,
314 }],
315 });
316 me.rows['tag-style'] = {
317 required: true,
318 renderer: (value) => {
319 if (value === undefined) {
320 return gettext('No Overrides');
321 }
322 let colors = PVE.Utils.parseTagOverrides(value?.['color-map']);
323 let shape = value.shape;
324 let shapeText = PVE.Utils.tagTreeStyles[shape ?? '__default__'];
325 let txt = Ext.String.format(gettext("Tree Shape: {0}"), shapeText);
326 let orderText = PVE.Utils.tagOrderOptions[value.ordering ?? '__default__'];
327 txt += `, ${Ext.String.format(gettext("Ordering: {0}"), orderText)}`;
328 if (Object.keys(colors).length > 0) {
329 txt += ', ';
330 }
331 for (const tag of Object.keys(colors)) {
332 txt += Proxmox.Utils.getTagElement(tag, colors);
333 }
334 return txt;
335 },
336 header: gettext('Tag Style Override'),
337 editor: {
338 xtype: 'proxmoxWindowEdit',
339 width: 800,
340 subject: gettext('Tag Color Override'),
341 onlineHelp: 'datacenter_configuration_file',
342 fieldDefaults: {
343 labelWidth: 100,
344 },
345 url: '/api2/extjs/cluster/options',
346 items: [
347 {
348 xtype: 'inputpanel',
349 setValues: function(values) {
350 if (values === undefined) {
351 return undefined;
352 }
353 values = values?.['tag-style'] ?? {};
354 values.shape = values.shape || '__default__';
355 values.colors = values['color-map'];
356 return Proxmox.panel.InputPanel.prototype.setValues.call(this, values);
357 },
358 onGetValues: function(values) {
359 let style = {};
360 if (values.colors) {
361 style['color-map'] = values.colors;
362 }
363 if (values.shape) {
364 style.shape = values.shape;
365 }
366 if (values.ordering) {
367 style.ordering = values.ordering;
368 }
369 let value = PVE.Parser.printPropertyString(style);
370 if (value === '') {
371 return {
372 'delete': 'tag-style',
373 };
374 }
375 return {
376 'tag-style': value,
377 };
378 },
379 items: [
380 {
381 name: 'shape',
382 xtype: 'proxmoxKVComboBox',
383 fieldLabel: gettext('Tree Shape'),
384 comboItems: Object.entries(PVE.Utils.tagTreeStyles),
385 defaultValue: '__default__',
386 deleteEmpty: true,
387 },
388 {
389 name: 'ordering',
390 xtype: 'proxmoxKVComboBox',
391 fieldLabel: gettext('Ordering'),
392 comboItems: Object.entries(PVE.Utils.tagOrderOptions),
393 defaultValue: '__default__',
394 value: '__default__',
395 deleteEmpty: true,
396 },
397 {
398 xtype: 'displayfield',
399 fieldLabel: gettext('Color Overrides'),
400 },
401 {
402 name: 'colors',
403 xtype: 'pveTagColorGrid',
404 deleteEmpty: true,
405 height: 300,
406 },
407 ],
408 },
409 ],
410 },
411 };
412
413 me.rows['user-tag-access'] = {
414 required: true,
415 renderer: (value) => {
416 if (value === undefined) {
417 return Ext.String.format(gettext('Mode: {0}'), 'free');
418 }
419 let mode = value?.['user-allow'] ?? 'free';
420 let list = value?.['user-allow-list'].join(',');
421 let modeTxt = Ext.String.format(gettext('Mode {0}'), mode);
422 let overrides = PVE.Utils.tagOverrides;
423 let tags = PVE.Utils.renderTags(list, overrides);
424
425 return `${modeTxt}, ${gettext('Pre-defined:')} ${tags}`;
426 },
427 header: gettext('User Tag Access'),
428 editor: {
429 xtype: 'pveUserTagAccessEdit',
430 },
431 };
432
433 me.rows['registered-tags'] = {
434 required: true,
435 renderer: (value) => {
436 if (value === undefined) {
437 return gettext('No Registered Tags');
438 }
439 let overrides = PVE.Utils.tagOverrides;
440 return PVE.Utils.renderTags(value.join(','), overrides);
441 },
442 header: gettext('Registered Tags'),
443 editor: {
444 xtype: 'pveRegisteredTagEdit',
445 },
446 };
447
448 me.selModel = Ext.create('Ext.selection.RowModel', {});
449
450 Ext.apply(me, {
451 tbar: [{
452 text: gettext('Edit'),
453 xtype: 'proxmoxButton',
454 disabled: true,
455 handler: function() { me.run_editor(); },
456 selModel: me.selModel,
457 }],
458 url: "/api2/json/cluster/options",
459 editorConfig: {
460 url: "/api2/extjs/cluster/options",
461 },
462 interval: 5000,
463 cwidth1: 200,
464 listeners: {
465 itemdblclick: me.run_editor,
466 },
467 });
468
469 me.callParent();
470
471 // set the new value for the default console
472 me.mon(me.rstore, 'load', function(store, records, success) {
473 if (!success) {
474 return;
475 }
476
477 var rec = store.getById('console');
478 PVE.UIOptions.console = rec.data.value;
479 if (rec.data.value === '__default__') {
480 delete PVE.UIOptions.console;
481 }
482
483 PVE.UIOptions['tag-style'] = store.getById('tag-style')?.data?.value;
484 PVE.Utils.updateTagSettings(PVE.UIOptions['tag-style']);
485 });
486
487 me.on('activate', me.rstore.startUpdate);
488 me.on('destroy', me.rstore.stopUpdate);
489 me.on('deactivate', me.rstore.stopUpdate);
490 },
491 });