X-Git-Url: https://git.proxmox.com/?a=blobdiff_plain;f=src%2FUtils.js;h=546cd64339e77da34b035963fe7b63bcaf243368;hb=5b2485ace3a8a54809b0b68cdc2d53b23e029f0e;hp=2163794fe9b7a881e267ef4acc8f0968dffe83e7;hpb=b1d446d0b2c747621639336c0c9567b87d47ab9c;p=proxmox-widget-toolkit.git diff --git a/src/Utils.js b/src/Utils.js index 2163794..546cd64 100644 --- a/src/Utils.js +++ b/src/Utils.js @@ -5,13 +5,6 @@ if (!Ext.isDefined(Proxmox.Setup.auth_cookie_name)) { throw "Proxmox library not initialized"; } -// avoid errors related to Accessible Rich Internet Applications -// (access for people with disabilities) -// TODO reenable after all components are upgraded -Ext.enableAria = false; -Ext.enableAriaButtons = false; -Ext.enableAriaPanels = false; - // avoid errors when running without development tools if (!Ext.isDefined(Ext.global.console)) { let console = { @@ -39,6 +32,10 @@ Ext.Ajax.on('beforerequest', function(conn, options) { } options.headers.CSRFPreventionToken = Proxmox.CSRFPreventionToken; } + let storedAuth = Proxmox.Utils.getStoredAuth(); + if (storedAuth.token) { + options.headers.Authorization = storedAuth.token; + } }); Ext.define('Proxmox.Utils', { // a singleton @@ -51,6 +48,7 @@ utilities: { noneText: gettext('none'), NoneText: gettext('None'), errorText: gettext('Error'), + warningsText: gettext('Warnings'), unknownText: gettext('Unknown'), defaultText: gettext('Default'), daysText: gettext('days'), @@ -67,26 +65,28 @@ utilities: { language_map: { ar: 'Arabic', ca: 'Catalan', + zh_CN: 'Chinese (Simplified)', + zh_TW: 'Chinese (Traditional)', da: 'Danish', - de: 'German', + nl: 'Dutch', en: 'English', - es: 'Spanish', eu: 'Euskera (Basque)', - fa: 'Persian (Farsi)', fr: 'French', + de: 'German', he: 'Hebrew', it: 'Italian', ja: 'Japanese', + kr: 'Korean', nb: 'Norwegian (Bokmal)', nn: 'Norwegian (Nynorsk)', + fa: 'Persian (Farsi)', pl: 'Polish', pt_BR: 'Portuguese (Brazil)', ru: 'Russian', sl: 'Slovenian', + es: 'Spanish', sv: 'Swedish', tr: 'Turkish', - zh_CN: 'Chinese (Simplified)', - zh_TW: 'Chinese (Traditional)', }, render_language: function(value) { @@ -157,8 +157,8 @@ utilities: { format_duration_human: function(ut) { let seconds = 0, minutes = 0, hours = 0, days = 0; - if (ut <= 0) { - return '0s'; + if (ut <= 0.1) { + return '<0.1s'; } let remaining = ut; @@ -238,22 +238,58 @@ utilities: { return min < width ? width : min; }, + // returns username + realm + parse_userid: function(userid) { + if (!Ext.isString(userid)) { + return [undefined, undefined]; + } + + let match = userid.match(/^(.+)@([^@]+)$/); + if (match !== null) { + return [match[1], match[2]]; + } + + return [undefined, undefined]; + }, + + render_username: function(userid) { + let username = Proxmox.Utils.parse_userid(userid)[0] || ""; + return Ext.htmlEncode(username); + }, + + render_realm: function(userid) { + let username = Proxmox.Utils.parse_userid(userid)[1] || ""; + return Ext.htmlEncode(username); + }, + + getStoredAuth: function() { + let storedAuth = JSON.parse(window.localStorage.getItem('ProxmoxUser')); + return storedAuth || {}; + }, + setAuthData: function(data) { - Proxmox.CSRFPreventionToken = data.CSRFPreventionToken; Proxmox.UserName = data.username; Proxmox.LoggedOut = data.LoggedOut; // creates a session cookie (expire = null) // that way the cookie gets deleted after the browser window is closed - Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true); + if (data.ticket) { + Proxmox.CSRFPreventionToken = data.CSRFPreventionToken; + Ext.util.Cookies.set(Proxmox.Setup.auth_cookie_name, data.ticket, null, '/', null, true); + } + + if (data.token) { + window.localStorage.setItem('ProxmoxUser', JSON.stringify(data)); + } }, authOK: function() { if (Proxmox.LoggedOut) { return undefined; } + let storedAuth = Proxmox.Utils.getStoredAuth(); let cookie = Ext.util.Cookies.get(Proxmox.Setup.auth_cookie_name); - if (Proxmox.UserName !== '' && cookie && !cookie.startsWith("PVE:tfa!")) { - return cookie; + if ((Proxmox.UserName !== '' && cookie && !cookie.startsWith("PVE:tfa!")) || storedAuth.token) { + return cookie || storedAuth.token; } else { return false; } @@ -264,6 +300,17 @@ utilities: { return; } Ext.util.Cookies.clear(Proxmox.Setup.auth_cookie_name); + window.localStorage.removeItem("ProxmoxUser"); + }, + + // The End-User gets redirected back here after login on the OpenID auth. portal, and in the + // redirection URL the state and auth.code are passed as URL GET params, this helper parses those + getOpenIDRedirectionAuthorization: function() { + const auth = Ext.Object.fromQueryString(window.location.search); + if (auth.state !== undefined && auth.code !== undefined) { + return auth; + } + return undefined; }, // comp.setLoading() is buggy in ExtJS 4.0.7, so we @@ -304,7 +351,7 @@ utilities: { return msg.join('
'); }, - monStoreErrors: function(component, store, clearMaskBeforeLoad) { + monStoreErrors: function(component, store, clearMaskBeforeLoad, errorCallback) { if (clearMaskBeforeLoad) { component.mon(store, 'beforeload', function(s, operation, eOpts) { Proxmox.Utils.setErrorMask(component, false); @@ -329,7 +376,9 @@ utilities: { let error = request._operation.getError(); let msg = Proxmox.Utils.getResponseErrorMessage(error); - Proxmox.Utils.setErrorMask(component, msg); + if (!errorCallback || !errorCallback(error, msg)) { + Proxmox.Utils.setErrorMask(component, msg); + } }); }, @@ -431,33 +480,47 @@ utilities: { Ext.Ajax.request(newopts); }, + // can be useful for catching displaying errors from the API, e.g.: + // Proxmox.Async.api2({ + // ... + // }).catch(Proxmox.Utils.alertResponseFailure); + alertResponseFailure: (response) => { + Ext.Msg.alert( + gettext('Error'), + response.htmlStatus || response.result.message, + ); + }, + checked_command: function(orig_cmd) { - Proxmox.Utils.API2Request({ - url: '/nodes/localhost/subscription', - method: 'GET', - failure: function(response, opts) { - Ext.Msg.alert(gettext('Error'), response.htmlStatus); - }, - success: function(response, opts) { - let data = response.result.data; - if (data.status !== 'Active') { - Ext.Msg.show({ - title: gettext('No valid subscription'), - icon: Ext.Msg.WARNING, - message: Proxmox.Utils.getNoSubKeyHtml(data.url), - buttons: Ext.Msg.OK, - callback: function(btn) { - if (btn !== 'ok') { - return; - } - orig_cmd(); - }, - }); - } else { - orig_cmd(); - } + Proxmox.Utils.API2Request( + { + url: '/nodes/localhost/subscription', + method: 'GET', + failure: function(response, opts) { + Ext.Msg.alert(gettext('Error'), response.htmlStatus); + }, + success: function(response, opts) { + let res = response.result; + if (res === null || res === undefined || !res || res + .data.status.toLowerCase() !== 'active') { + Ext.Msg.show({ + title: gettext('No valid subscription'), + icon: Ext.Msg.WARNING, + message: Proxmox.Utils.getNoSubKeyHtml(res.data.url), + buttons: Ext.Msg.OK, + callback: function(btn) { + if (btn !== 'ok') { + return; + } + orig_cmd(); + }, + }); + } else { + orig_cmd(); + } + }, }, - }); + ); }, assemble_field_data: function(values, data) { @@ -481,7 +544,7 @@ utilities: { }); }, - updateColumnWidth: function(container) { + updateColumnWidth: function(container, tresholdWidth) { let mode = Ext.state.Manager.get('summarycolumns') || 'auto'; let factor; if (mode !== 'auto') { @@ -490,7 +553,8 @@ utilities: { factor = 1; } } else { - factor = container.getSize().width < 1600 ? 1 : 2; + tresholdWidth = (tresholdWidth || 1400) + 1; + factor = Math.ceil(container.getSize().width / tresholdWidth); } if (container.oldFactor === factor) { @@ -511,6 +575,9 @@ utilities: { container.updateLayout(); }, + // NOTE: depreacated, use updateColumnWidth + updateColumns: container => Proxmox.Utils.updateColumnWidth(container), + dialog_title: function(subject, create, isAdd) { if (create) { if (isAdd) { @@ -539,94 +606,17 @@ utilities: { Proxmox.Utils.unknownText; }, + // NOTE: only add general, product agnostic, ones here! Else use override helper in product repos task_desc_table: { - acmenewcert: ['SRV', gettext('Order Certificate')], - acmeregister: ['ACME Account', gettext('Register')], - acmedeactivate: ['ACME Account', gettext('Deactivate')], - acmeupdate: ['ACME Account', gettext('Update')], - acmerefresh: ['ACME Account', gettext('Refresh')], - acmerenew: ['SRV', gettext('Renew Certificate')], - acmerevoke: ['SRV', gettext('Revoke Certificate')], - 'auth-realm-sync': [gettext('Realm'), gettext('Sync')], - 'auth-realm-sync-test': [gettext('Realm'), gettext('Sync Preview')], - 'move_volume': ['CT', gettext('Move Volume')], - clustercreate: ['', gettext('Create Cluster')], - clusterjoin: ['', gettext('Join Cluster')], + aptupdate: ['', gettext('Update package database')], diskinit: ['Disk', gettext('Initialize Disk with GPT')], - vncproxy: ['VM/CT', gettext('Console')], - spiceproxy: ['VM/CT', gettext('Console') + ' (Spice)'], - vncshell: ['', gettext('Shell')], spiceshell: ['', gettext('Shell') + ' (Spice)'], - qmsnapshot: ['VM', gettext('Snapshot')], - qmrollback: ['VM', gettext('Rollback')], - qmdelsnapshot: ['VM', gettext('Delete Snapshot')], - qmcreate: ['VM', gettext('Create')], - qmrestore: ['VM', gettext('Restore')], - qmdestroy: ['VM', gettext('Destroy')], - qmigrate: ['VM', gettext('Migrate')], - qmclone: ['VM', gettext('Clone')], - qmmove: ['VM', gettext('Move disk')], - qmtemplate: ['VM', gettext('Convert to template')], - qmstart: ['VM', gettext('Start')], - qmstop: ['VM', gettext('Stop')], - qmreset: ['VM', gettext('Reset')], - qmshutdown: ['VM', gettext('Shutdown')], - qmreboot: ['VM', gettext('Reboot')], - qmsuspend: ['VM', gettext('Hibernate')], - qmpause: ['VM', gettext('Pause')], - qmresume: ['VM', gettext('Resume')], - qmconfig: ['VM', gettext('Configure')], - vzsnapshot: ['CT', gettext('Snapshot')], - vzrollback: ['CT', gettext('Rollback')], - vzdelsnapshot: ['CT', gettext('Delete Snapshot')], - vzcreate: ['CT', gettext('Create')], - vzrestore: ['CT', gettext('Restore')], - vzdestroy: ['CT', gettext('Destroy')], - vzmigrate: ['CT', gettext('Migrate')], - vzclone: ['CT', gettext('Clone')], - vztemplate: ['CT', gettext('Convert to template')], - vzstart: ['CT', gettext('Start')], - vzstop: ['CT', gettext('Stop')], - vzmount: ['CT', gettext('Mount')], - vzumount: ['CT', gettext('Unmount')], - vzshutdown: ['CT', gettext('Shutdown')], - vzreboot: ['CT', gettext('Reboot')], - vzsuspend: ['CT', gettext('Suspend')], - vzresume: ['CT', gettext('Resume')], - push_file: ['CT', gettext('Push file')], - pull_file: ['CT', gettext('Pull file')], - hamigrate: ['HA', gettext('Migrate')], - hastart: ['HA', gettext('Start')], - hastop: ['HA', gettext('Stop')], - hashutdown: ['HA', gettext('Shutdown')], + srvreload: ['SRV', gettext('Reload')], + srvrestart: ['SRV', gettext('Restart')], srvstart: ['SRV', gettext('Start')], srvstop: ['SRV', gettext('Stop')], - srvrestart: ['SRV', gettext('Restart')], - srvreload: ['SRV', gettext('Reload')], - cephcreatemgr: ['Ceph Manager', gettext('Create')], - cephdestroymgr: ['Ceph Manager', gettext('Destroy')], - cephcreatemon: ['Ceph Monitor', gettext('Create')], - cephdestroymon: ['Ceph Monitor', gettext('Destroy')], - cephcreateosd: ['Ceph OSD', gettext('Create')], - cephdestroyosd: ['Ceph OSD', gettext('Destroy')], - cephcreatepool: ['Ceph Pool', gettext('Create')], - cephdestroypool: ['Ceph Pool', gettext('Destroy')], - cephfscreate: ['CephFS', gettext('Create')], - cephcreatemds: ['Ceph Metadata Server', gettext('Create')], - cephdestroymds: ['Ceph Metadata Server', gettext('Destroy')], - imgcopy: ['', gettext('Copy data')], - imgdel: ['', gettext('Erase data')], - unknownimgdel: ['', gettext('Destroy image from unknown guest')], - download: ['', gettext('Download')], - vzdump: (type, id) => id ? `VM/CT ${id} - ${gettext('Backup')}` : gettext('Backup Job'), - aptupdate: ['', gettext('Update package database')], - startall: ['', gettext('Start all VMs and Containers')], - stopall: ['', gettext('Stop all VMs and Containers')], - migrateall: ['', gettext('Migrate all VMs and Containers')], - dircreate: [gettext('Directory Storage'), gettext('Create')], - lvmcreate: [gettext('LVM Storage'), gettext('Create')], - lvmthincreate: [gettext('LVM-Thin Storage'), gettext('Create')], - zfscreate: [gettext('ZFS Storage'), gettext('Create')], + termproxy: ['', gettext('Console') + ' (xterm.js)'], + vncshell: ['', gettext('Shell')], }, // to add or change existing for product specific ones @@ -656,14 +646,22 @@ utilities: { return text; }, - format_size: function(size) { - let units = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi', 'Yi']; - let num = 0; - while (size >= 1024 && num++ <= units.length) { - size = size / 1024; + format_size: function(size, useSI) { + let units = ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; + let order = 0; + const baseValue = useSI ? 1000 : 1024; + while (size >= baseValue && order < units.length) { + size = size / baseValue; + order++; } - return size.toFixed(num > 0?2:0) + " " + units[num] + "B"; + let unit = units[order], commaDigits = 2; + if (order === 0) { + commaDigits = 0; + } else if (!useSI) { + unit += 'i'; + } + return `${size.toFixed(commaDigits)} ${unit}B`; }, render_upid: function(value, metaData, record) { @@ -688,6 +686,65 @@ utilities: { return Proxmox.Utils.format_duration_long(uptime); }, + systemd_unescape: function(string_value) { + const charcode_0 = '0'.charCodeAt(0); + const charcode_9 = '9'.charCodeAt(0); + const charcode_A = 'A'.charCodeAt(0); + const charcode_F = 'F'.charCodeAt(0); + const charcode_a = 'a'.charCodeAt(0); + const charcode_f = 'f'.charCodeAt(0); + const charcode_x = 'x'.charCodeAt(0); + const charcode_minus = '-'.charCodeAt(0); + const charcode_slash = '/'.charCodeAt(0); + const charcode_backslash = '\\'.charCodeAt(0); + + let parse_hex_digit = function(d) { + if (d >= charcode_0 && d <= charcode_9) { + return d - charcode_0; + } + if (d >= charcode_A && d <= charcode_F) { + return d - charcode_A + 10; + } + if (d >= charcode_a && d <= charcode_f) { + return d - charcode_a + 10; + } + throw "got invalid hex digit"; + }; + + let value = new TextEncoder().encode(string_value); + let result = new Uint8Array(value.length); + + let i = 0; + let result_len = 0; + + while (i < value.length) { + let c0 = value[i]; + if (c0 === charcode_minus) { + result.set([charcode_slash], result_len); + result_len += 1; + i += 1; + continue; + } + if ((i + 4) < value.length) { + let c1 = value[i+1]; + if (c0 === charcode_backslash && c1 === charcode_x) { + let h1 = parse_hex_digit(value[i+2]); + let h0 = parse_hex_digit(value[i+3]); + let ord = h1*16+h0; + result.set([ord], result_len); + result_len += 1; + i += 4; + continue; + } + } + result.set([c0], result_len); + result_len += 1; + i += 1; + } + + return new TextDecoder().decode(result.slice(0, result.len)); + }, + parse_task_upid: function(upid) { let task = {}; @@ -703,7 +760,7 @@ utilities: { } task.starttime = parseInt(res[6], 16); task.type = res[7]; - task.id = res[8]; + task.id = Proxmox.Utils.systemd_unescape(res[8]); task.user = res[9]; task.desc = Proxmox.Utils.format_task_description(task.type, task.id); @@ -728,6 +785,17 @@ utilities: { return 'error'; }, + format_task_status: function(status) { + let parsed = Proxmox.Utils.parse_task_status(status); + switch (parsed) { + case 'unknown': return Proxmox.Utils.unknownText; + case 'error': return Proxmox.Utils.errorText + ': ' + status; + case 'warning': return status.replace('WARNINGS', Proxmox.Utils.warningsText); + case 'ok': // fall-through + default: return status; + } + }, + render_duration: function(value) { if (value === undefined) { return '-'; @@ -740,6 +808,31 @@ utilities: { return Ext.Date.format(servertime, 'Y-m-d H:i:s'); }, + render_zfs_health: function(value) { + if (typeof value === 'undefined') { + return ""; + } + var iconCls = 'question-circle'; + switch (value) { + case 'AVAIL': + case 'ONLINE': + iconCls = 'check-circle good'; + break; + case 'REMOVED': + case 'DEGRADED': + iconCls = 'exclamation-circle warning'; + break; + case 'UNAVAIL': + case 'FAULTED': + case 'OFFLINE': + iconCls = 'times-circle critical'; + break; + default: //unknown + } + + return ' ' + value; + }, + get_help_info: function(section) { let helpMap; if (typeof proxmoxOnlineHelpInfo !== 'undefined') { @@ -751,7 +844,17 @@ utilities: { throw "no global OnlineHelpInfo map declared"; } - return helpMap[section]; + if (helpMap[section]) { + return helpMap[section]; + } + // try to normalize - and _ separators, to support asciidoc and sphinx + // references at the same time. + let section_minus_normalized = section.replace(/_/g, '-'); + if (helpMap[section_minus_normalized]) { + return helpMap[section_minus_normalized]; + } + let section_underscore_normalized = section.replace(/-/g, '_'); + return helpMap[section_underscore_normalized]; }, get_help_link: function(section) { @@ -778,6 +881,275 @@ utilities: { } }, + render_optional_url: function(value) { + if (value && value.match(/^https?:\/\//) !== null) { + return '' + value + ''; + } + return value; + }, + + render_san: function(value) { + var names = []; + if (Ext.isArray(value)) { + value.forEach(function(val) { + if (!Ext.isNumber(val)) { + names.push(val); + } + }); + return names.join('
'); + } + return value; + }, + + render_usage: val => (val * 100).toFixed(2) + '%', + + render_cpu_usage: function(val, max) { + return Ext.String.format( + `${gettext('{0}% of {1}')} ${gettext('CPU(s)')}`, + (val*100).toFixed(2), + max, + ); + }, + + render_size_usage: function(val, max, useSI) { + if (max === 0) { + return gettext('N/A'); + } + let fmt = v => Proxmox.Utils.format_size(v, useSI); + let ratio = (val * 100 / max).toFixed(2); + return ratio + '% (' + Ext.String.format(gettext('{0} of {1}'), fmt(val), fmt(max)) + ')'; + }, + + render_cpu: function(value, metaData, record, rowIndex, colIndex, store) { + if (!(record.data.uptime && Ext.isNumeric(value))) { + return ''; + } + + let maxcpu = record.data.maxcpu || 1; + if (!Ext.isNumeric(maxcpu) || maxcpu < 1) { + return ''; + } + let cpuText = maxcpu > 1 ? 'CPUs' : 'CPU'; + let ratio = (value * 100).toFixed(1); + return `${ratio}% of ${maxcpu.toString()} ${cpuText}`; + }, + + render_size: function(value, metaData, record, rowIndex, colIndex, store) { + if (!Ext.isNumeric(value)) { + return ''; + } + return Proxmox.Utils.format_size(value); + }, + + render_cpu_model: function(cpu) { + let socketText = cpu.sockets > 1 ? gettext('Sockets') : gettext('Socket'); + return `${cpu.cpus} x ${cpu.model} (${cpu.sockets.toString()} ${socketText})`; + }, + + /* this is different for nodes */ + render_node_cpu_usage: function(value, record) { + return Proxmox.Utils.render_cpu_usage(value, record.cpus); + }, + + render_node_size_usage: function(record) { + return Proxmox.Utils.render_size_usage(record.used, record.total); + }, + + loadTextFromFile: function(file, callback, maxBytes) { + let maxSize = maxBytes || 8192; + if (file.size > maxSize) { + Ext.Msg.alert(gettext('Error'), gettext("Invalid file size: ") + file.size); + return; + } + let reader = new FileReader(); + reader.onload = evt => callback(evt.target.result); + reader.readAsText(file); + }, + + parsePropertyString: function(value, defaultKey) { + var res = {}, + error; + + if (typeof value !== 'string' || value === '') { + return res; + } + + Ext.Array.each(value.split(','), function(p) { + var kv = p.split('=', 2); + if (Ext.isDefined(kv[1])) { + res[kv[0]] = kv[1]; + } else if (Ext.isDefined(defaultKey)) { + if (Ext.isDefined(res[defaultKey])) { + error = 'defaultKey may be only defined once in propertyString'; + return false; // break + } + res[defaultKey] = kv[0]; + } else { + error = 'invalid propertyString, not a key=value pair and no defaultKey defined'; + return false; // break + } + return true; + }); + + if (error !== undefined) { + console.error(error); + return undefined; + } + + return res; + }, + + printPropertyString: function(data, defaultKey) { + var stringparts = [], + gotDefaultKeyVal = false, + defaultKeyVal; + + Ext.Object.each(data, function(key, value) { + if (defaultKey !== undefined && key === defaultKey) { + gotDefaultKeyVal = true; + defaultKeyVal = value; + } else if (Ext.isArray(value)) { + stringparts.push(key + '=' + value.join(';')); + } else if (value !== '') { + stringparts.push(key + '=' + value); + } + }); + + stringparts = stringparts.sort(); + if (gotDefaultKeyVal) { + stringparts.unshift(defaultKeyVal); + } + + return stringparts.join(','); + }, + + acmedomain_count: 5, + + parseACMEPluginData: function(data) { + let res = {}; + let extradata = []; + data.split('\n').forEach((line) => { + // capture everything after the first = as value + let [key, value] = line.split('='); + if (value !== undefined) { + res[key] = value; + } else { + extradata.push(line); + } + }); + return [res, extradata]; + }, + + delete_if_default: function(values, fieldname, default_val, create) { + if (values[fieldname] === '' || values[fieldname] === default_val) { + if (!create) { + if (values.delete) { + if (Ext.isArray(values.delete)) { + values.delete.push(fieldname); + } else { + values.delete += ',' + fieldname; + } + } else { + values.delete = fieldname; + } + } + + delete values[fieldname]; + } + }, + + printACME: function(value) { + if (Ext.isArray(value.domains)) { + value.domains = value.domains.join(';'); + } + return Proxmox.Utils.printPropertyString(value); + }, + + parseACME: function(value) { + if (!value) { + return {}; + } + + var res = {}; + var error; + + Ext.Array.each(value.split(','), function(p) { + var kv = p.split('=', 2); + if (Ext.isDefined(kv[1])) { + res[kv[0]] = kv[1]; + } else { + error = 'Failed to parse key-value pair: '+p; + return false; + } + return true; + }); + + if (error !== undefined) { + console.error(error); + return undefined; + } + + if (res.domains !== undefined) { + res.domains = res.domains.split(/;/); + } + + return res; + }, + + add_domain_to_acme: function(acme, domain) { + if (acme.domains === undefined) { + acme.domains = [domain]; + } else { + acme.domains.push(domain); + acme.domains = acme.domains.filter((value, index, self) => self.indexOf(value) === index); + } + return acme; + }, + + remove_domain_from_acme: function(acme, domain) { + if (acme.domains !== undefined) { + acme.domains = acme.domains.filter( + (value, index, self) => self.indexOf(value) === index && value !== domain, + ); + } + return acme; + }, + + get_health_icon: function(state, circle) { + if (circle === undefined) { + circle = false; + } + + if (state === undefined) { + state = 'uknown'; + } + + var icon = 'faded fa-question'; + switch (state) { + case 'good': + icon = 'good fa-check'; + break; + case 'upgrade': + icon = 'warning fa-upload'; + break; + case 'old': + icon = 'warning fa-refresh'; + break; + case 'warning': + icon = 'warning fa-exclamation'; + break; + case 'critical': + icon = 'critical fa-times'; + break; + default: break; + } + + if (circle) { + icon += '-circle'; + } + + return icon; + }, }, singleton: true, @@ -817,13 +1189,33 @@ utilities: { me.IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$"); me.IP64_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + "/" + IPV6_CIDR_MASK + ")|(?:" + IPV4_REGEXP + "/" + IPV4_CIDR_MASK + ")$"); - 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])?))"; + 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])?))"; me.DnsName_match = new RegExp("^" + DnsName_REGEXP + "$"); - me.HostPort_match = new RegExp("^(" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")(:\\d+)?$"); - me.HostPortBrackets_match = new RegExp("^\\[(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")\\](:\\d+)?$"); - me.IP6_dotnotation_match = new RegExp("^" + IPV6_REGEXP + "(\\.\\d+)?$"); - me.Vlan_match = /^vlan(\\d+)/; - me.VlanInterface_match = /(\\w+)\\.(\\d+)/; + me.HostPort_match = new RegExp("^(" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")(?::(\\d+))?$"); + me.HostPortBrackets_match = new RegExp("^\\[(" + IPV6_REGEXP + "|" + IPV4_REGEXP + "|" + DnsName_REGEXP + ")\\](?::(\\d+))?$"); + me.IP6_dotnotation_match = new RegExp("^(" + IPV6_REGEXP + ")(?:\\.(\\d+))?$"); + me.Vlan_match = /^vlan(\d+)/; + me.VlanInterface_match = /(\w+)\.(\d+)/; + }, +}); + +Ext.define('Proxmox.Async', { + singleton: true, + + // Returns a Promise resolving to the result of an `API2Request` or rejecting to the error + // repsonse on failure + api2: function(reqOpts) { + return new Promise((resolve, reject) => { + delete reqOpts.callback; // not allowed in this api + reqOpts.success = response => resolve(response); + reqOpts.failure = response => reject(response); + Proxmox.Utils.API2Request(reqOpts); + }); + }, + + // Delay for a number of milliseconds. + sleep: function(millis) { + return new Promise((resolve, _reject) => setTimeout(resolve, millis)); }, });