]> git.proxmox.com Git - proxmox-widget-toolkit.git/blame - src/Utils.js
panel/acme-domains: fix cyclic dependency in view model
[proxmox-widget-toolkit.git] / src / Utils.js
CommitLineData
ecabd437
TL
1Ext.ns('Proxmox');
2Ext.ns('Proxmox.Setup');
3
4if (!Ext.isDefined(Proxmox.Setup.auth_cookie_name)) {
5 throw "Proxmox library not initialized";
6}
7
ecabd437
TL
8// avoid errors when running without development tools
9if (!Ext.isDefined(Ext.global.console)) {
10 let console = {
11 dir: function() {
12 // do nothing
13 },
14 log: function() {
15 // do nothing
16 },
17 warn: function() {
18 // do nothing
19 },
20 };
21 Ext.global.console = console;
22}
23
24Ext.Ajax.defaultHeaders = {
25 'Accept': 'application/json',
26};
27
28Ext.Ajax.on('beforerequest', function(conn, options) {
29 if (Proxmox.CSRFPreventionToken) {
30 if (!options.headers) {
31 options.headers = {};
32 }
33 options.headers.CSRFPreventionToken = Proxmox.CSRFPreventionToken;
34 }
b6d1735a
TL
35 let storedAuth = Proxmox.Utils.getStoredAuth();
36 if (storedAuth.token) {
37 options.headers.Authorization = storedAuth.token;
63252ab6 38 }
ecabd437
TL
39});
40
41Ext.define('Proxmox.Utils', { // a singleton
42utilities: {
43
44 yesText: gettext('Yes'),
45 noText: gettext('No'),
46 enabledText: gettext('Enabled'),
47 disabledText: gettext('Disabled'),
48 noneText: gettext('none'),
49 NoneText: gettext('None'),
50 errorText: gettext('Error'),
51 unknownText: gettext('Unknown'),
52 defaultText: gettext('Default'),
53 daysText: gettext('days'),
54 dayText: gettext('day'),
55 runningText: gettext('running'),
56 stoppedText: gettext('stopped'),
57 neverText: gettext('never'),
58 totalText: gettext('Total'),
59 usedText: gettext('Used'),
60 directoryText: gettext('Directory'),
61 stateText: gettext('State'),
62 groupText: gettext('Group'),
63
64 language_map: {
65 ar: 'Arabic',
66 ca: 'Catalan',
998237fc
TL
67 zh_CN: 'Chinese (Simplified)',
68 zh_TW: 'Chinese (Traditional)',
ecabd437 69 da: 'Danish',
6127ba88 70 nl: 'Dutch',
ecabd437 71 en: 'English',
ecabd437 72 eu: 'Euskera (Basque)',
ecabd437 73 fr: 'French',
998237fc 74 de: 'German',
ecabd437
TL
75 he: 'Hebrew',
76 it: 'Italian',
77 ja: 'Japanese',
abfc1857 78 kr: 'Korean',
ecabd437
TL
79 nb: 'Norwegian (Bokmal)',
80 nn: 'Norwegian (Nynorsk)',
998237fc 81 fa: 'Persian (Farsi)',
ecabd437
TL
82 pl: 'Polish',
83 pt_BR: 'Portuguese (Brazil)',
84 ru: 'Russian',
85 sl: 'Slovenian',
998237fc 86 es: 'Spanish',
ecabd437
TL
87 sv: 'Swedish',
88 tr: 'Turkish',
ecabd437
TL
89 },
90
91 render_language: function(value) {
92 if (!value) {
93 return Proxmox.Utils.defaultText + ' (English)';
94 }
95 let text = Proxmox.Utils.language_map[value];
96 if (text) {
97 return text + ' (' + value + ')';
98 }
99 return value;
100 },
101
102 language_array: function() {
103 let data = [['__default__', Proxmox.Utils.render_language('')]];
104 Ext.Object.each(Proxmox.Utils.language_map, function(key, value) {
105 data.push([key, Proxmox.Utils.render_language(value)]);
106 });
107
108 return data;
109 },
110
111 bond_mode_gettext_map: {
112 '802.3ad': 'LACP (802.3ad)',
113 'lacp-balance-slb': 'LACP (balance-slb)',
114 'lacp-balance-tcp': 'LACP (balance-tcp)',
115 },
116
117 render_bond_mode: value => Proxmox.Utils.bond_mode_gettext_map[value] || value || '',
118
119 bond_mode_array: function(modes) {
120 return modes.map(mode => [mode, Proxmox.Utils.render_bond_mode(mode)]);
121 },
122
123 getNoSubKeyHtml: function(url) {
124 // url http://www.proxmox.com/products/proxmox-ve/subscription-service-plans
125 return Ext.String.format('You do not have a valid subscription for this server. Please visit <a target="_blank" href="{0}">www.proxmox.com</a> to get a list of available options.', url || 'https://www.proxmox.com');
126 },
127
128 format_boolean_with_default: function(value) {
129 if (Ext.isDefined(value) && value !== '__default__') {
130 return value ? Proxmox.Utils.yesText : Proxmox.Utils.noText;
131 }
132 return Proxmox.Utils.defaultText;
133 },
134
135 format_boolean: function(value) {
136 return value ? Proxmox.Utils.yesText : Proxmox.Utils.noText;
137 },
138
139 format_neg_boolean: function(value) {
140 return !value ? Proxmox.Utils.yesText : Proxmox.Utils.noText;
141 },
142
143 format_enabled_toggle: function(value) {
144 return value ? Proxmox.Utils.enabledText : Proxmox.Utils.disabledText;
145 },
146
147 format_expire: function(date) {
148 if (!date) {
149 return Proxmox.Utils.neverText;
150 }
151 return Ext.Date.format(date, "Y-m-d");
152 },
153
154 // somewhat like a human would tell durations, omit zero values and do not
155 // give seconds precision if we talk days already
156 format_duration_human: function(ut) {
157 let seconds = 0, minutes = 0, hours = 0, days = 0;
158
b2d7d422
DC
159 if (ut <= 0.1) {
160 return '<0.1s';
ecabd437
TL
161 }
162
163 let remaining = ut;
164 seconds = Number((remaining % 60).toFixed(1));
165 remaining = Math.trunc(remaining / 60);
166 if (remaining > 0) {
167 minutes = remaining % 60;
168 remaining = Math.trunc(remaining / 60);
169 if (remaining > 0) {
170 hours = remaining % 24;
171 remaining = Math.trunc(remaining / 24);
172 if (remaining > 0) {
173 days = remaining;
174 }
175 }
176 }
177
178 let res = [];
179 let add = (t, unit) => {
180 if (t > 0) res.push(t + unit);
181 return t > 0;
182 };
183
184 let addSeconds = !add(days, 'd');
185 add(hours, 'h');
186 add(minutes, 'm');
187 if (addSeconds) {
188 add(seconds, 's');
189 }
190 return res.join(' ');
191 },
192
193 format_duration_long: function(ut) {
194 let days = Math.floor(ut / 86400);
195 ut -= days*86400;
196 let hours = Math.floor(ut / 3600);
197 ut -= hours*3600;
198 let mins = Math.floor(ut / 60);
199 ut -= mins*60;
200
201 let hours_str = '00' + hours.toString();
202 hours_str = hours_str.substr(hours_str.length - 2);
203 let mins_str = "00" + mins.toString();
204 mins_str = mins_str.substr(mins_str.length - 2);
205 let ut_str = "00" + ut.toString();
206 ut_str = ut_str.substr(ut_str.length - 2);
207
208 if (days) {
209 let ds = days > 1 ? Proxmox.Utils.daysText : Proxmox.Utils.dayText;
210 return days.toString() + ' ' + ds + ' ' +
211 hours_str + ':' + mins_str + ':' + ut_str;
212 } else {
213 return hours_str + ':' + mins_str + ':' + ut_str;
214 }
215 },
216
217 format_subscription_level: function(level) {
218 if (level === 'c') {
219 return 'Community';
220 } else if (level === 'b') {
221 return 'Basic';
222 } else if (level === 's') {
223 return 'Standard';
224 } else if (level === 'p') {
225 return 'Premium';
226 } else {
227 return Proxmox.Utils.noneText;
228 }
229 },
230
231 compute_min_label_width: function(text, width) {
232 if (width === undefined) { width = 100; }
233
234 let tm = new Ext.util.TextMetrics();
235 let min = tm.getWidth(text + ':');
236
237 return min < width ? width : min;
238 },
239
63252ab6
TM
240 getStoredAuth: function() {
241 let storedAuth = JSON.parse(window.localStorage.getItem('ProxmoxUser'));
242 return storedAuth || {};
243 },
244
ecabd437 245 setAuthData: function(data) {
ecabd437
TL
246 Proxmox.UserName = data.username;
247 Proxmox.LoggedOut = data.LoggedOut;
248 // creates a session cookie (expire = null)
249 // that way the cookie gets deleted after the browser window is closed
63252ab6
TM
250 if (data.ticket) {
251 Proxmox.CSRFPreventionToken = data.CSRFPreventionToken;
252 Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true);
253 }
254
255 if (data.token) {
256 window.localStorage.setItem('ProxmoxUser', JSON.stringify(data));
257 }
ecabd437
TL
258 },
259
260 authOK: function() {
261 if (Proxmox.LoggedOut) {
262 return undefined;
263 }
b6d1735a 264 let storedAuth = Proxmox.Utils.getStoredAuth();
ecabd437 265 let cookie = Ext.util.Cookies.get(Proxmox.Setup.auth_cookie_name);
63252ab6
TM
266 if ((Proxmox.UserName !== '' && cookie && !cookie.startsWith("PVE:tfa!")) || storedAuth.token) {
267 return cookie || storedAuth.token;
ecabd437
TL
268 } else {
269 return false;
270 }
271 },
272
273 authClear: function() {
274 if (Proxmox.LoggedOut) {
275 return;
276 }
277 Ext.util.Cookies.clear(Proxmox.Setup.auth_cookie_name);
63252ab6 278 window.localStorage.removeItem("ProxmoxUser");
ecabd437
TL
279 },
280
281 // comp.setLoading() is buggy in ExtJS 4.0.7, so we
282 // use el.mask() instead
283 setErrorMask: function(comp, msg) {
284 let el = comp.el;
285 if (!el) {
286 return;
287 }
288 if (!msg) {
289 el.unmask();
290 } else if (msg === true) {
291 el.mask(gettext("Loading..."));
292 } else {
293 el.mask(msg);
294 }
295 },
296
297 getResponseErrorMessage: (err) => {
298 if (!err.statusText) {
299 return gettext('Connection error');
300 }
301 let msg = [`${err.statusText} (${err.status})`];
302 if (err.response && err.response.responseText) {
303 let txt = err.response.responseText;
304 try {
305 let res = JSON.parse(txt);
306 if (res.errors && typeof res.errors === 'object') {
307 for (let [key, value] of Object.entries(res.errors)) {
308 msg.push(Ext.String.htmlEncode(`${key}: ${value}`));
309 }
310 }
311 } catch (e) {
312 // fallback to string
313 msg.push(Ext.String.htmlEncode(txt));
314 }
315 }
316 return msg.join('<br>');
317 },
318
319 monStoreErrors: function(component, store, clearMaskBeforeLoad) {
320 if (clearMaskBeforeLoad) {
321 component.mon(store, 'beforeload', function(s, operation, eOpts) {
322 Proxmox.Utils.setErrorMask(component, false);
323 });
324 } else {
325 component.mon(store, 'beforeload', function(s, operation, eOpts) {
326 if (!component.loadCount) {
327 component.loadCount = 0; // make sure it is nucomponent.ic
328 Proxmox.Utils.setErrorMask(component, true);
329 }
330 });
331 }
332
333 // only works with 'proxmox' proxy
334 component.mon(store.proxy, 'afterload', function(proxy, request, success) {
335 component.loadCount++;
336
337 if (success) {
338 Proxmox.Utils.setErrorMask(component, false);
339 return;
340 }
341
342 let error = request._operation.getError();
343 let msg = Proxmox.Utils.getResponseErrorMessage(error);
344 Proxmox.Utils.setErrorMask(component, msg);
345 });
346 },
347
348 extractRequestError: function(result, verbose) {
349 let msg = gettext('Successful');
350
351 if (!result.success) {
352 msg = gettext("Unknown error");
353 if (result.message) {
354 msg = result.message;
355 if (result.status) {
356 msg += ' (' + result.status + ')';
357 }
358 }
359 if (verbose && Ext.isObject(result.errors)) {
360 msg += "<br>";
361 Ext.Object.each(result.errors, function(prop, desc) {
362 msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
363 Ext.htmlEncode(desc);
364 });
365 }
366 }
367
368 return msg;
369 },
370
371 // Ext.Ajax.request
372 API2Request: function(reqOpts) {
373 let newopts = Ext.apply({
374 waitMsg: gettext('Please wait...'),
375 }, reqOpts);
376
377 if (!newopts.url.match(/^\/api2/)) {
378 newopts.url = '/api2/extjs' + newopts.url;
379 }
380 delete newopts.callback;
381
382 let createWrapper = function(successFn, callbackFn, failureFn) {
383 Ext.apply(newopts, {
384 success: function(response, options) {
385 if (options.waitMsgTarget) {
386 if (Proxmox.Utils.toolkit === 'touch') {
387 options.waitMsgTarget.setMasked(false);
388 } else {
389 options.waitMsgTarget.setLoading(false);
390 }
391 }
392 let result = Ext.decode(response.responseText);
393 response.result = result;
394 if (!result.success) {
395 response.htmlStatus = Proxmox.Utils.extractRequestError(result, true);
396 Ext.callback(callbackFn, options.scope, [options, false, response]);
397 Ext.callback(failureFn, options.scope, [response, options]);
398 return;
399 }
400 Ext.callback(callbackFn, options.scope, [options, true, response]);
401 Ext.callback(successFn, options.scope, [response, options]);
402 },
403 failure: function(response, options) {
404 if (options.waitMsgTarget) {
405 if (Proxmox.Utils.toolkit === 'touch') {
406 options.waitMsgTarget.setMasked(false);
407 } else {
408 options.waitMsgTarget.setLoading(false);
409 }
410 }
411 response.result = {};
412 try {
413 response.result = Ext.decode(response.responseText);
414 } catch (e) {
415 // ignore
416 }
417 let msg = gettext('Connection error') + ' - server offline?';
418 if (response.aborted) {
419 msg = gettext('Connection error') + ' - aborted.';
420 } else if (response.timedout) {
421 msg = gettext('Connection error') + ' - Timeout.';
422 } else if (response.status && response.statusText) {
423 msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
424 }
425 response.htmlStatus = msg;
426 Ext.callback(callbackFn, options.scope, [options, false, response]);
427 Ext.callback(failureFn, options.scope, [response, options]);
428 },
429 });
430 };
431
432 createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
433
434 let target = newopts.waitMsgTarget;
435 if (target) {
436 if (Proxmox.Utils.toolkit === 'touch') {
437 target.setMasked({ xtype: 'loadmask', message: newopts.waitMsg });
438 } else {
439 // Note: ExtJS bug - this does not work when component is not rendered
440 target.setLoading(newopts.waitMsg);
441 }
442 }
443 Ext.Ajax.request(newopts);
444 },
445
106fe29e
TL
446 // can be useful for catching displaying errors from the API, e.g.:
447 // Proxmox.Async.api2({
448 // ...
449 // }).catch(Proxmox.Utils.alertResponseFailure);
450 alertResponseFailure: (response) => {
451 Ext.Msg.alert(
452 gettext('Error'),
453 response.htmlStatus || response.result.message,
454 );
455 },
456
ecabd437 457 checked_command: function(orig_cmd) {
a718654e
TL
458 Proxmox.Utils.API2Request(
459 {
460 url: '/nodes/localhost/subscription',
461 method: 'GET',
462 failure: function(response, opts) {
463 Ext.Msg.alert(gettext('Error'), response.htmlStatus);
464 },
465 success: function(response, opts) {
466 let res = response.result;
467 if (res === null || res === undefined || !res || res
57ba4cdc 468 .data.status.toLowerCase() !== 'active') {
a718654e
TL
469 Ext.Msg.show({
470 title: gettext('No valid subscription'),
471 icon: Ext.Msg.WARNING,
472 message: Proxmox.Utils.getNoSubKeyHtml(res.data.url),
473 buttons: Ext.Msg.OK,
474 callback: function(btn) {
475 if (btn !== 'ok') {
476 return;
477 }
478 orig_cmd();
479 },
480 });
481 } else {
482 orig_cmd();
483 }
484 },
ecabd437 485 },
a718654e 486 );
ecabd437
TL
487 },
488
489 assemble_field_data: function(values, data) {
490 if (!Ext.isObject(data)) {
491 return;
492 }
493 Ext.Object.each(data, function(name, val) {
494 if (Object.prototype.hasOwnProperty.call(values, name)) {
495 let bucket = values[name];
496 if (!Ext.isArray(bucket)) {
497 bucket = values[name] = [bucket];
498 }
499 if (Ext.isArray(val)) {
500 values[name] = bucket.concat(val);
501 } else {
502 bucket.push(val);
503 }
504 } else {
505 values[name] = val;
506 }
507 });
508 },
509
510 updateColumnWidth: function(container) {
511 let mode = Ext.state.Manager.get('summarycolumns') || 'auto';
512 let factor;
513 if (mode !== 'auto') {
514 factor = parseInt(mode, 10);
515 if (Number.isNaN(factor)) {
516 factor = 1;
517 }
518 } else {
519 factor = container.getSize().width < 1600 ? 1 : 2;
520 }
521
522 if (container.oldFactor === factor) {
523 return;
524 }
525
526 let items = container.query('>'); // direct childs
527 factor = Math.min(factor, items.length);
528 container.oldFactor = factor;
529
530 items.forEach((item) => {
531 item.columnWidth = 1 / factor;
532 });
533
534 // we have to update the layout twice, since the first layout change
535 // can trigger the scrollbar which reduces the amount of space left
536 container.updateLayout();
537 container.updateLayout();
538 },
539
540 dialog_title: function(subject, create, isAdd) {
541 if (create) {
542 if (isAdd) {
543 return gettext('Add') + ': ' + subject;
544 } else {
545 return gettext('Create') + ': ' + subject;
546 }
547 } else {
548 return gettext('Edit') + ': ' + subject;
549 }
550 },
551
552 network_iface_types: {
553 eth: gettext("Network Device"),
554 bridge: 'Linux Bridge',
555 bond: 'Linux Bond',
556 vlan: 'Linux VLAN',
557 OVSBridge: 'OVS Bridge',
558 OVSBond: 'OVS Bond',
559 OVSPort: 'OVS Port',
560 OVSIntPort: 'OVS IntPort',
561 },
562
563 render_network_iface_type: function(value) {
564 return Proxmox.Utils.network_iface_types[value] ||
565 Proxmox.Utils.unknownText;
566 },
567
568 task_desc_table: {
3571d713 569 aptupdate: ['', gettext('Update package database')],
ecabd437 570 diskinit: ['Disk', gettext('Initialize Disk with GPT')],
ecabd437 571 spiceshell: ['', gettext('Shell') + ' (Spice)'],
3571d713
DC
572 srvreload: ['SRV', gettext('Reload')],
573 srvrestart: ['SRV', gettext('Restart')],
ecabd437
TL
574 srvstart: ['SRV', gettext('Start')],
575 srvstop: ['SRV', gettext('Stop')],
3571d713
DC
576 termproxy: ['', gettext('Console') + ' (xterm.js)'],
577 vncshell: ['', gettext('Shell')],
ecabd437
TL
578 },
579
580 // to add or change existing for product specific ones
581 override_task_descriptions: function(extra) {
582 for (const [key, value] of Object.entries(extra)) {
583 Proxmox.Utils.task_desc_table[key] = value;
584 }
585 },
586
587 format_task_description: function(type, id) {
588 let farray = Proxmox.Utils.task_desc_table[type];
589 let text;
590 if (!farray) {
591 text = type;
592 if (id) {
593 type += ' ' + id;
594 }
595 return text;
596 } else if (Ext.isFunction(farray)) {
597 return farray(type, id);
598 }
599 let prefix = farray[0];
600 text = farray[1];
601 if (prefix && id !== undefined) {
602 return prefix + ' ' + id + ' - ' + text;
603 }
604 return text;
605 },
606
607 format_size: function(size) {
608 let units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi'];
609 let num = 0;
610 while (size >= 1024 && num++ <= units.length) {
611 size = size / 1024;
612 }
613
614 return size.toFixed(num > 0?2:0) + " " + units[num] + "B";
615 },
616
617 render_upid: function(value, metaData, record) {
618 let task = record.data;
619 let type = task.type || task.worker_type;
620 let id = task.id || task.worker_id;
621
622 return Proxmox.Utils.format_task_description(type, id);
623 },
624
625 render_uptime: function(value) {
626 let uptime = value;
627
628 if (uptime === undefined) {
629 return '';
630 }
631
632 if (uptime <= 0) {
633 return '-';
634 }
635
636 return Proxmox.Utils.format_duration_long(uptime);
637 },
638
6221e40c 639 systemd_unescape: function(string_value) {
6221e40c
DM
640 const charcode_0 = '0'.charCodeAt(0);
641 const charcode_9 = '9'.charCodeAt(0);
642 const charcode_A = 'A'.charCodeAt(0);
643 const charcode_F = 'F'.charCodeAt(0);
644 const charcode_a = 'a'.charCodeAt(0);
645 const charcode_f = 'f'.charCodeAt(0);
646 const charcode_x = 'x'.charCodeAt(0);
647 const charcode_minus = '-'.charCodeAt(0);
648 const charcode_slash = '/'.charCodeAt(0);
649 const charcode_backslash = '\\'.charCodeAt(0);
650
651 let parse_hex_digit = function(d) {
652 if (d >= charcode_0 && d <= charcode_9) {
653 return d - charcode_0;
654 }
655 if (d >= charcode_A && d <= charcode_F) {
656 return d - charcode_A + 10;
657 }
658 if (d >= charcode_a && d <= charcode_f) {
659 return d - charcode_a + 10;
660 }
661 throw "got invalid hex digit";
662 };
663
664 let value = new TextEncoder().encode(string_value);
665 let result = new Uint8Array(value.length);
666
667 let i = 0;
668 let result_len = 0;
669
670 while (i < value.length) {
671 let c0 = value[i];
672 if (c0 === charcode_minus) {
673 result.set([charcode_slash], result_len);
674 result_len += 1;
675 i += 1;
676 continue;
677 }
678 if ((i + 4) < value.length) {
679 let c1 = value[i+1];
680 if (c0 === charcode_backslash && c1 === charcode_x) {
681 let h1 = parse_hex_digit(value[i+2]);
682 let h0 = parse_hex_digit(value[i+3]);
683 let ord = h1*16+h0;
684 result.set([ord], result_len);
685 result_len += 1;
686 i += 4;
687 continue;
688 }
689 }
690 result.set([c0], result_len);
691 result_len += 1;
692 i += 1;
693 }
694
695 return new TextDecoder().decode(result.slice(0, result.len));
696 },
697
ecabd437
TL
698 parse_task_upid: function(upid) {
699 let task = {};
700
701 let res = upid.match(/^UPID:([^\s:]+):([0-9A-Fa-f]{8}):([0-9A-Fa-f]{8,9}):(([0-9A-Fa-f]{8,16}):)?([0-9A-Fa-f]{8}):([^:\s]+):([^:\s]*):([^:\s]+):$/);
702 if (!res) {
703 throw "unable to parse upid '" + upid + "'";
704 }
705 task.node = res[1];
706 task.pid = parseInt(res[2], 16);
707 task.pstart = parseInt(res[3], 16);
708 if (res[5] !== undefined) {
709 task.task_id = parseInt(res[5], 16);
710 }
711 task.starttime = parseInt(res[6], 16);
712 task.type = res[7];
6221e40c 713 task.id = Proxmox.Utils.systemd_unescape(res[8]);
ecabd437
TL
714 task.user = res[9];
715
716 task.desc = Proxmox.Utils.format_task_description(task.type, task.id);
717
718 return task;
719 },
720
b1d446d0
DC
721 parse_task_status: function(status) {
722 if (status === 'OK') {
723 return 'ok';
724 }
725
726 if (status === 'unknown') {
727 return 'unknown';
728 }
729
730 let match = status.match(/^WARNINGS: (.*)$/);
731 if (match) {
732 return 'warning';
733 }
734
735 return 'error';
736 },
737
ecabd437
TL
738 render_duration: function(value) {
739 if (value === undefined) {
740 return '-';
741 }
742 return Proxmox.Utils.format_duration_human(value);
743 },
744
745 render_timestamp: function(value, metaData, record, rowIndex, colIndex, store) {
746 let servertime = new Date(value * 1000);
747 return Ext.Date.format(servertime, 'Y-m-d H:i:s');
748 },
749
f29e6a66
DC
750 render_zfs_health: function(value) {
751 if (typeof value === 'undefined') {
752 return "";
753 }
754 var iconCls = 'question-circle';
755 switch (value) {
756 case 'AVAIL':
757 case 'ONLINE':
758 iconCls = 'check-circle good';
759 break;
760 case 'REMOVED':
761 case 'DEGRADED':
762 iconCls = 'exclamation-circle warning';
763 break;
764 case 'UNAVAIL':
765 case 'FAULTED':
766 case 'OFFLINE':
767 iconCls = 'times-circle critical';
768 break;
769 default: //unknown
770 }
771
772 return '<i class="fa fa-' + iconCls + '"></i> ' + value;
773 },
774
ecabd437
TL
775 get_help_info: function(section) {
776 let helpMap;
777 if (typeof proxmoxOnlineHelpInfo !== 'undefined') {
778 helpMap = proxmoxOnlineHelpInfo; // eslint-disable-line no-undef
779 } else if (typeof pveOnlineHelpInfo !== 'undefined') {
780 // be backward compatible with older pve-doc-generators
781 helpMap = pveOnlineHelpInfo; // eslint-disable-line no-undef
782 } else {
783 throw "no global OnlineHelpInfo map declared";
784 }
785
7f56fd0c
TL
786 if (helpMap[section]) {
787 return helpMap[section];
788 }
789 // try to normalize - and _ separators, to support asciidoc and sphinx
790 // references at the same time.
63ec56e5 791 let section_minus_normalized = section.replace(/_/g, '-');
7f56fd0c
TL
792 if (helpMap[section_minus_normalized]) {
793 return helpMap[section_minus_normalized];
794 }
63ec56e5 795 let section_underscore_normalized = section.replace(/-/g, '_');
7f56fd0c 796 return helpMap[section_underscore_normalized];
ecabd437
TL
797 },
798
799 get_help_link: function(section) {
800 let info = Proxmox.Utils.get_help_info(section);
801 if (!info) {
802 return undefined;
803 }
804 return window.location.origin + info.link;
805 },
806
807 openXtermJsViewer: function(vmtype, vmid, nodename, vmname, cmd) {
808 let url = Ext.Object.toQueryString({
809 console: vmtype, // kvm, lxc, upgrade or shell
810 xtermjs: 1,
811 vmid: vmid,
812 vmname: vmname,
813 node: nodename,
814 cmd: cmd,
815
816 });
817 let nw = window.open("?" + url, '_blank', 'toolbar=no,location=no,status=no,menubar=no,resizable=yes,width=800,height=420');
818 if (nw) {
819 nw.focus();
820 }
821 },
822
25680ef5
WB
823 render_optional_url: function(value) {
824 if (value && value.match(/^https?:\/\//) !== null) {
825 return '<a target="_blank" href="' + value + '">' + value + '</a>';
826 }
827 return value;
828 },
829
830 render_san: function(value) {
831 var names = [];
832 if (Ext.isArray(value)) {
833 value.forEach(function(val) {
834 if (!Ext.isNumber(val)) {
835 names.push(val);
836 }
837 });
838 return names.join('<br>');
839 }
840 return value;
841 },
842
843 loadTextFromFile: function(file, callback, maxBytes) {
844 let maxSize = maxBytes || 8192;
845 if (file.size > maxSize) {
846 Ext.Msg.alert(gettext('Error'), gettext("Invalid file size: ") + file.size);
847 return;
848 }
849 let reader = new FileReader();
850 reader.onload = evt => callback(evt.target.result);
851 reader.readAsText(file);
852 },
853
854 parsePropertyString: function(value, defaultKey) {
855 var res = {},
856 error;
857
858 if (typeof value !== 'string' || value === '') {
859 return res;
860 }
861
862 Ext.Array.each(value.split(','), function(p) {
863 var kv = p.split('=', 2);
864 if (Ext.isDefined(kv[1])) {
865 res[kv[0]] = kv[1];
866 } else if (Ext.isDefined(defaultKey)) {
867 if (Ext.isDefined(res[defaultKey])) {
868 error = 'defaultKey may be only defined once in propertyString';
869 return false; // break
870 }
871 res[defaultKey] = kv[0];
872 } else {
873 error = 'invalid propertyString, not a key=value pair and no defaultKey defined';
874 return false; // break
875 }
876 return true;
877 });
878
879 if (error !== undefined) {
880 console.error(error);
881 return undefined;
882 }
883
884 return res;
885 },
886
887 printPropertyString: function(data, defaultKey) {
888 var stringparts = [],
889 gotDefaultKeyVal = false,
890 defaultKeyVal;
891
892 Ext.Object.each(data, function(key, value) {
893 if (defaultKey !== undefined && key === defaultKey) {
894 gotDefaultKeyVal = true;
895 defaultKeyVal = value;
896 } else if (Ext.isArray(value)) {
897 stringparts.push(key + '=' + value.join(';'));
898 } else if (value !== '') {
899 stringparts.push(key + '=' + value);
900 }
901 });
902
903 stringparts = stringparts.sort();
904 if (gotDefaultKeyVal) {
905 stringparts.unshift(defaultKeyVal);
906 }
907
908 return stringparts.join(',');
909 },
910
911 acmedomain_count: 5,
912
913 parseACMEPluginData: function(data) {
914 let res = {};
915 let extradata = [];
916 data.split('\n').forEach((line) => {
917 // capture everything after the first = as value
918 let [key, value] = line.split('=');
919 if (value !== undefined) {
920 res[key] = value;
921 } else {
922 extradata.push(line);
923 }
924 });
925 return [res, extradata];
926 },
927
928 delete_if_default: function(values, fieldname, default_val, create) {
929 if (values[fieldname] === '' || values[fieldname] === default_val) {
930 if (!create) {
931 if (values.delete) {
932 if (Ext.isArray(values.delete)) {
933 values.delete.push(fieldname);
934 } else {
935 values.delete += ',' + fieldname;
936 }
937 } else {
938 values.delete = fieldname;
939 }
940 }
941
942 delete values[fieldname];
943 }
944 },
945
946 printACME: function(value) {
947 if (Ext.isArray(value.domains)) {
948 value.domains = value.domains.join(';');
949 }
950 return Proxmox.Utils.printPropertyString(value);
951 },
952
953 parseACME: function(value) {
954 if (!value) {
955 return {};
956 }
957
958 var res = {};
959 var error;
960
961 Ext.Array.each(value.split(','), function(p) {
962 var kv = p.split('=', 2);
963 if (Ext.isDefined(kv[1])) {
964 res[kv[0]] = kv[1];
965 } else {
966 error = 'Failed to parse key-value pair: '+p;
967 return false;
968 }
969 return true;
970 });
971
972 if (error !== undefined) {
973 console.error(error);
974 return undefined;
975 }
976
977 if (res.domains !== undefined) {
978 res.domains = res.domains.split(/;/);
979 }
980
981 return res;
982 },
983
984 add_domain_to_acme: function(acme, domain) {
985 if (acme.domains === undefined) {
986 acme.domains = [domain];
987 } else {
988 acme.domains.push(domain);
989 acme.domains = acme.domains.filter((value, index, self) => self.indexOf(value) === index);
990 }
991 return acme;
992 },
993
994 remove_domain_from_acme: function(acme, domain) {
995 if (acme.domains !== undefined) {
996 acme.domains = acme.domains.filter(
997 (value, index, self) => self.indexOf(value) === index && value !== domain,
998 );
999 }
1000 return acme;
1001 },
ecabd437
TL
1002},
1003
1004 singleton: true,
1005 constructor: function() {
1006 let me = this;
1007 Ext.apply(me, me.utilities);
1008
1009 let IPV4_OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
1010 let IPV4_REGEXP = "(?:(?:" + IPV4_OCTET + "\\.){3}" + IPV4_OCTET + ")";
1011 let IPV6_H16 = "(?:[0-9a-fA-F]{1,4})";
1012 let IPV6_LS32 = "(?:(?:" + IPV6_H16 + ":" + IPV6_H16 + ")|" + IPV4_REGEXP + ")";
1013 let IPV4_CIDR_MASK = "([0-9]{1,2})";
1014 let IPV6_CIDR_MASK = "([0-9]{1,3})";
1015
1016
1017 me.IP4_match = new RegExp("^(?:" + IPV4_REGEXP + ")$");
1018 me.IP4_cidr_match = new RegExp("^(?:" + IPV4_REGEXP + ")/" + IPV4_CIDR_MASK + "$");
1019
1020 /* eslint-disable no-useless-concat,no-multi-spaces */
1021 let IPV6_REGEXP = "(?:" +
1022 "(?:(?:" + "(?:" + IPV6_H16 + ":){6})" + IPV6_LS32 + ")|" +
1023 "(?:(?:" + "::" + "(?:" + IPV6_H16 + ":){5})" + IPV6_LS32 + ")|" +
1024 "(?:(?:(?:" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){4})" + IPV6_LS32 + ")|" +
1025 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,1}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){3})" + IPV6_LS32 + ")|" +
1026 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,2}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){2})" + IPV6_LS32 + ")|" +
1027 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,3}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){1})" + IPV6_LS32 + ")|" +
1028 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,4}" + IPV6_H16 + ")?::" + ")" + IPV6_LS32 + ")|" +
1029 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,5}" + IPV6_H16 + ")?::" + ")" + IPV6_H16 + ")|" +
1030 "(?:(?:(?:(?:" + IPV6_H16 + ":){0,7}" + IPV6_H16 + ")?::" + ")" + ")" +
1031 ")";
1032 /* eslint-enable no-useless-concat,no-multi-spaces */
1033
1034 me.IP6_match = new RegExp("^(?:" + IPV6_REGEXP + ")$");
1035 me.IP6_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + ")/" + IPV6_CIDR_MASK + "$");
1036 me.IP6_bracket_match = new RegExp("^\\[(" + IPV6_REGEXP + ")\\]");
1037
1038 me.IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$");
1039 me.IP64_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + "/" + IPV6_CIDR_MASK + ")|(?:" + IPV4_REGEXP + "/" + IPV4_CIDR_MASK + ")$");
1040
a1d40d37 1041 let DnsName_REGEXP = "(?:(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9\\-]*[a-zA-Z0-9])?)\\.)*(?:[A-Za-z0-9](?:[A-Za-z0-9\\-]*[A-Za-z0-9])?))";
ecabd437
TL
1042 me.DnsName_match = new RegExp("^" + DnsName_REGEXP + "$");
1043
a1d40d37
DC
1044 me.HostPort_match = new RegExp("^(" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")(?::(\\d+))?$");
1045 me.HostPortBrackets_match = new RegExp("^\\[(" + IPV6_REGEXP + "|" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")\\](?::(\\d+))?$");
1046 me.IP6_dotnotation_match = new RegExp("^(" + IPV6_REGEXP + ")(?:\\.(\\d+))?$");
7bff067a
DJ
1047 me.Vlan_match = /^vlan(\d+)/;
1048 me.VlanInterface_match = /(\w+)\.(\d+)/;
ecabd437
TL
1049 },
1050});
c2c12542
TL
1051
1052Ext.define('Proxmox.Async', {
1053 singleton: true,
1054
106fe29e
TL
1055 // Returns a Promise resolving to the result of an `API2Request` or rejecting to the error
1056 // repsonse on failure
c2c12542
TL
1057 api2: function(reqOpts) {
1058 return new Promise((resolve, reject) => {
1059 delete reqOpts.callback; // not allowed in this api
1060 reqOpts.success = response => resolve(response);
106fe29e 1061 reqOpts.failure = response => reject(response);
c2c12542
TL
1062 Proxmox.Utils.API2Request(reqOpts);
1063 });
1064 },
1065
1066 // Delay for a number of milliseconds.
1067 sleep: function(millis) {
1068 return new Promise((resolve, _reject) => setTimeout(resolve, millis));
1069 },
1070});