]> git.proxmox.com Git - pve-manager.git/blob - www/manager6/dc/OptionView.js
ui: datacenter options: add Cluster Resource Scheduling editor
[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('crs', gettext('Cluster Resource Scheduling'), {
142 renderer: PVE.Utils.render_as_property_string,
143 labelWidth: 120,
144 url: "/api2/extjs/cluster/options",
145 onlineHelp: 'ha_manager_crs',
146 items: [{
147 xtype: 'proxmoxKVComboBox',
148 name: 'ha',
149 fieldLabel: gettext('HA Scheduling'),
150 deleteEmpty: false,
151 value: '__default__',
152 comboItems: [
153 ['__default__', Proxmox.Utils.defaultText + ' (basic)'],
154 ['basic', 'Basic (Resource Count)'],
155 ['static', 'Static Load'],
156 ],
157 defaultValue: '__default__',
158 }],
159 });
160 me.add_inputpanel_row('u2f', gettext('U2F Settings'), {
161 renderer: v => !v ? Proxmox.Utils.NoneText : PVE.Parser.printPropertyString(v),
162 width: 450,
163 url: "/api2/extjs/cluster/options",
164 onlineHelp: 'pveum_configure_u2f',
165 items: [{
166 xtype: 'textfield',
167 name: 'appid',
168 fieldLabel: gettext('U2F AppID URL'),
169 emptyText: gettext('Defaults to origin'),
170 value: '',
171 deleteEmpty: true,
172 skipEmptyText: true,
173 submitEmptyText: false,
174 }, {
175 xtype: 'textfield',
176 name: 'origin',
177 fieldLabel: gettext('U2F Origin'),
178 emptyText: gettext('Defaults to requesting host URI'),
179 value: '',
180 deleteEmpty: true,
181 skipEmptyText: true,
182 submitEmptyText: false,
183 },
184 {
185 xtype: 'box',
186 height: 25,
187 html: `<span class='pmx-hint'>${gettext('Note:')}</span> `
188 + Ext.String.format(gettext('{0} is deprecated, use {1}'), 'U2F', 'WebAuthn'),
189 },
190 {
191 xtype: 'displayfield',
192 userCls: 'pmx-hint',
193 value: gettext('NOTE: Changing an AppID breaks existing U2F registrations!'),
194 }],
195 });
196 me.add_inputpanel_row('webauthn', gettext('WebAuthn Settings'), {
197 renderer: v => !v ? Proxmox.Utils.NoneText : PVE.Parser.printPropertyString(v),
198 width: 450,
199 url: "/api2/extjs/cluster/options",
200 onlineHelp: 'pveum_configure_webauthn',
201 items: [{
202 xtype: 'textfield',
203 fieldLabel: gettext('Name'),
204 name: 'rp', // NOTE: relying party consists of name and id, this is the name
205 allowBlank: false,
206 },
207 {
208 xtype: 'textfield',
209 fieldLabel: gettext('Origin'),
210 emptyText: Ext.String.format(gettext("Domain Lockdown (e.g., {0})"), document.location.origin),
211 name: 'origin',
212 allowBlank: true,
213 },
214 {
215 xtype: 'textfield',
216 fieldLabel: 'ID',
217 name: 'id',
218 allowBlank: false,
219 listeners: {
220 dirtychange: (f, isDirty) =>
221 f.up('panel').down('box[id=idChangeWarning]').setHidden(!f.originalValue || !isDirty),
222 },
223 },
224 {
225 xtype: 'container',
226 layout: 'hbox',
227 items: [
228 {
229 xtype: 'box',
230 flex: 1,
231 },
232 {
233 xtype: 'button',
234 text: gettext('Auto-fill'),
235 iconCls: 'fa fa-fw fa-pencil-square-o',
236 handler: function(button, ev) {
237 let panel = this.up('panel');
238 let fqdn = document.location.hostname;
239
240 panel.down('field[name=rp]').setValue(fqdn);
241
242 let idField = panel.down('field[name=id]');
243 let currentID = idField.getValue();
244 if (!currentID || currentID.length === 0) {
245 idField.setValue(fqdn);
246 }
247 },
248 },
249 ],
250 },
251 {
252 xtype: 'box',
253 height: 25,
254 html: `<span class='pmx-hint'>${gettext('Note:')}</span> `
255 + gettext('WebAuthn requires using a trusted certificate.'),
256 },
257 {
258 xtype: 'box',
259 id: 'idChangeWarning',
260 hidden: true,
261 padding: '5 0 0 0',
262 html: '<i class="fa fa-exclamation-triangle warning"></i> '
263 + gettext('Changing the ID breaks existing WebAuthn TFA entries.'),
264 }],
265 });
266 me.add_inputpanel_row('bwlimit', gettext('Bandwidth Limits'), {
267 renderer: me.render_bwlimits,
268 width: 450,
269 url: "/api2/extjs/cluster/options",
270 parseBeforeSet: true,
271 labelWidth: 120,
272 items: [{
273 xtype: 'pveBandwidthField',
274 name: 'default',
275 fieldLabel: gettext('Default'),
276 emptyText: gettext('none'),
277 backendUnit: "KiB",
278 },
279 {
280 xtype: 'pveBandwidthField',
281 name: 'restore',
282 fieldLabel: gettext('Backup Restore'),
283 emptyText: gettext('default'),
284 backendUnit: "KiB",
285 },
286 {
287 xtype: 'pveBandwidthField',
288 name: 'migration',
289 fieldLabel: gettext('Migration'),
290 emptyText: gettext('default'),
291 backendUnit: "KiB",
292 },
293 {
294 xtype: 'pveBandwidthField',
295 name: 'clone',
296 fieldLabel: gettext('Clone'),
297 emptyText: gettext('default'),
298 backendUnit: "KiB",
299 },
300 {
301 xtype: 'pveBandwidthField',
302 name: 'move',
303 fieldLabel: gettext('Disk Move'),
304 emptyText: gettext('default'),
305 backendUnit: "KiB",
306 }],
307 });
308 me.add_integer_row('max_workers', gettext('Maximal Workers/bulk-action'), {
309 deleteEmpty: true,
310 defaultValue: 4,
311 minValue: 1,
312 maxValue: 64, // arbitrary but generous limit as limits are good
313 });
314 me.add_inputpanel_row('next-id', gettext('Next Free VMID Range'), {
315 renderer: PVE.Utils.render_as_property_string,
316 url: "/api2/extjs/cluster/options",
317 items: [{
318 xtype: 'proxmoxintegerfield',
319 name: 'lower',
320 fieldLabel: gettext('Lower'),
321 emptyText: '100',
322 minValue: 100,
323 maxValue: 1000 * 1000 * 1000 - 1,
324 submitValue: true,
325 }, {
326 xtype: 'proxmoxintegerfield',
327 name: 'upper',
328 fieldLabel: gettext('Upper'),
329 emptyText: '1.000.000',
330 minValue: 100,
331 maxValue: 1000 * 1000 * 1000 - 1,
332 submitValue: true,
333 }],
334 });
335 me.rows['tag-style'] = {
336 required: true,
337 renderer: (value) => {
338 if (value === undefined) {
339 return gettext('No Overrides');
340 }
341 let colors = PVE.Utils.parseTagOverrides(value?.['color-map']);
342 let shape = value.shape;
343 let shapeText = PVE.Utils.tagTreeStyles[shape ?? '__default__'];
344 let txt = Ext.String.format(gettext("Tree Shape: {0}"), shapeText);
345 let orderText = PVE.Utils.tagOrderOptions[value.ordering ?? '__default__'];
346 txt += `, ${Ext.String.format(gettext("Ordering: {0}"), orderText)}`;
347 if (Object.keys(colors).length > 0) {
348 txt += ', ';
349 }
350 for (const tag of Object.keys(colors)) {
351 txt += Proxmox.Utils.getTagElement(tag, colors);
352 }
353 return txt;
354 },
355 header: gettext('Tag Style Override'),
356 editor: {
357 xtype: 'proxmoxWindowEdit',
358 width: 800,
359 subject: gettext('Tag Color Override'),
360 onlineHelp: 'datacenter_configuration_file',
361 fieldDefaults: {
362 labelWidth: 100,
363 },
364 url: '/api2/extjs/cluster/options',
365 items: [
366 {
367 xtype: 'inputpanel',
368 setValues: function(values) {
369 if (values === undefined) {
370 return undefined;
371 }
372 values = values?.['tag-style'] ?? {};
373 values.shape = values.shape || '__default__';
374 values.colors = values['color-map'];
375 return Proxmox.panel.InputPanel.prototype.setValues.call(this, values);
376 },
377 onGetValues: function(values) {
378 let style = {};
379 if (values.colors) {
380 style['color-map'] = values.colors;
381 }
382 if (values.shape && values.shape !== '__default__') {
383 style.shape = values.shape;
384 }
385 if (values.ordering) {
386 style.ordering = values.ordering;
387 }
388 let value = PVE.Parser.printPropertyString(style);
389 if (value === '') {
390 return {
391 'delete': 'tag-style',
392 };
393 }
394 return {
395 'tag-style': value,
396 };
397 },
398 items: [
399 {
400
401 name: 'shape',
402 xtype: 'proxmoxComboGrid',
403 fieldLabel: gettext('Tree Shape'),
404 valueField: 'value',
405 displayField: 'display',
406 allowBlank: false,
407 listConfig: {
408 columns: [
409 {
410 header: gettext('Option'),
411 dataIndex: 'display',
412 flex: 1,
413 },
414 {
415 header: gettext('Preview'),
416 dataIndex: 'value',
417 renderer: function(value) {
418 let cls = value ?? '__default__';
419 if (value === '__default__') {
420 cls = 'circle';
421 }
422 let tags = PVE.Utils.renderTags('preview');
423 return `<div class="proxmox-tags-${cls}">${tags}</div>`;
424 },
425 flex: 1,
426 },
427 ],
428 },
429 store: {
430 data: Object.entries(PVE.Utils.tagTreeStyles).map(v => ({
431 value: v[0],
432 display: v[1],
433 })),
434 },
435 deleteDefault: true,
436 defaultValue: '__default__',
437 deleteEmpty: true,
438 },
439 {
440 name: 'ordering',
441 xtype: 'proxmoxKVComboBox',
442 fieldLabel: gettext('Ordering'),
443 comboItems: Object.entries(PVE.Utils.tagOrderOptions),
444 defaultValue: '__default__',
445 value: '__default__',
446 deleteEmpty: true,
447 },
448 {
449 xtype: 'displayfield',
450 fieldLabel: gettext('Color Overrides'),
451 },
452 {
453 name: 'colors',
454 xtype: 'pveTagColorGrid',
455 deleteEmpty: true,
456 height: 300,
457 },
458 ],
459 },
460 ],
461 },
462 };
463
464 me.rows['user-tag-access'] = {
465 required: true,
466 renderer: (value) => {
467 if (value === undefined) {
468 return Ext.String.format(gettext('Mode: {0}'), 'free');
469 }
470 let mode = value?.['user-allow'] ?? 'free';
471 let list = value?.['user-allow-list']?.join(',') ?? '';
472 let modeTxt = Ext.String.format(gettext('Mode: {0}'), mode);
473 let overrides = PVE.Utils.tagOverrides;
474 let tags = PVE.Utils.renderTags(list, overrides);
475 let listTxt = tags !== '' ? `, ${gettext('Pre-defiend:')} ${tags}` : '';
476 return `${modeTxt}${listTxt}`;
477 },
478 header: gettext('User Tag Access'),
479 editor: {
480 xtype: 'pveUserTagAccessEdit',
481 },
482 };
483
484 me.rows['registered-tags'] = {
485 required: true,
486 renderer: (value) => {
487 if (value === undefined) {
488 return gettext('No Registered Tags');
489 }
490 let overrides = PVE.Utils.tagOverrides;
491 return PVE.Utils.renderTags(value.join(','), overrides);
492 },
493 header: gettext('Registered Tags'),
494 editor: {
495 xtype: 'pveRegisteredTagEdit',
496 },
497 };
498
499 me.selModel = Ext.create('Ext.selection.RowModel', {});
500
501 Ext.apply(me, {
502 tbar: [{
503 text: gettext('Edit'),
504 xtype: 'proxmoxButton',
505 disabled: true,
506 handler: function() { me.run_editor(); },
507 selModel: me.selModel,
508 }],
509 url: "/api2/json/cluster/options",
510 editorConfig: {
511 url: "/api2/extjs/cluster/options",
512 },
513 interval: 5000,
514 cwidth1: 200,
515 listeners: {
516 itemdblclick: me.run_editor,
517 },
518 });
519
520 me.callParent();
521
522 // set the new value for the default console
523 me.mon(me.rstore, 'load', function(store, records, success) {
524 if (!success) {
525 return;
526 }
527
528 var rec = store.getById('console');
529 PVE.UIOptions.console = rec.data.value;
530 if (rec.data.value === '__default__') {
531 delete PVE.UIOptions.console;
532 }
533
534 PVE.UIOptions['tag-style'] = store.getById('tag-style')?.data?.value;
535 PVE.Utils.updateTagSettings(PVE.UIOptions['tag-style']);
536 });
537
538 me.on('activate', me.rstore.startUpdate);
539 me.on('destroy', me.rstore.stopUpdate);
540 me.on('deactivate', me.rstore.stopUpdate);
541 },
542 });