// ExtJS related things // do not send '_dc' parameter Ext.Ajax.disableCaching = false; // custom Vtypes Ext.apply(Ext.form.field.VTypes, { IPAddress: function(v) { return Proxmox.Utils.IP4_match.test(v); }, IPAddressText: gettext('Example') + ': 192.168.1.1', IPAddressMask: /[\d\.]/i, IPCIDRAddress: function(v) { var result = Proxmox.Utils.IP4_cidr_match.exec(v); // limits according to JSON Schema see // pve-common/src/PVE/JSONSchema.pm return (result !== null && result[1] >= 8 && result[1] <= 32); }, IPCIDRAddressText: gettext('Example') + ': 192.168.1.1/24' + "
" + gettext('Valid CIDR Range') + ': 8-32', IPCIDRAddressMask: /[\d\.\/]/i, IP6Address: function(v) { return Proxmox.Utils.IP6_match.test(v); }, IP6AddressText: gettext('Example') + ': 2001:DB8::42', IP6AddressMask: /[A-Fa-f0-9:]/, IP6CIDRAddress: function(v) { var result = Proxmox.Utils.IP6_cidr_match.exec(v); // limits according to JSON Schema see // pve-common/src/PVE/JSONSchema.pm return (result !== null && result[1] >= 8 && result[1] <= 120); }, IP6CIDRAddressText: gettext('Example') + ': 2001:DB8::42/64' + "
" + gettext('Valid CIDR Range') + ': 8-120', IP6CIDRAddressMask: /[A-Fa-f0-9:\/]/, IP6PrefixLength: function(v) { return v >= 0 && v <= 128; }, IP6PrefixLengthText: gettext('Example') + ': X, where 0 <= X <= 128', IP6PrefixLengthMask: /[0-9]/, IP64Address: function(v) { return Proxmox.Utils.IP64_match.test(v); }, IP64AddressText: gettext('Example') + ': 192.168.1.1 2001:DB8::42', IP64AddressMask: /[A-Fa-f0-9\.:]/, MacAddress: function(v) { return (/^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$/).test(v); }, MacAddressMask: /[a-fA-F0-9:]/, MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab', BridgeName: function(v) { return (/^vmbr\d{1,4}$/).test(v); }, BridgeNameText: gettext('Format') + ': vmbrN, where 0 <= N <= 9999', BondName: function(v) { return (/^bond\d{1,4}$/).test(v); }, BondNameText: gettext('Format') + ': bondN, where 0 <= N <= 9999', InterfaceName: function(v) { return (/^[a-z][a-z0-9_]{1,20}$/).test(v); }, InterfaceNameText: gettext("Allowed characters") + ": 'a-z', '0-9', '_'" + "
" + gettext("Minimum characters") + ": 2" + "
" + gettext("Maximum characters") + ": 21" + "
" + gettext("Must start with") + ": 'a-z'", QemuStartDate: function(v) { return (/^(now|\d{4}-\d{1,2}-\d{1,2}(T\d{1,2}:\d{1,2}:\d{1,2})?)$/).test(v); }, QemuStartDateText: gettext('Format') + ': "now" or "2006-06-17T16:01:21" or "2006-06-17"', StorageId: function(v) { return (/^[a-z][a-z0-9\-\_\.]*[a-z0-9]$/i).test(v); }, StorageIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '-', '_', '.'" + "
" + gettext("Minimum characters") + ": 2" + "
" + gettext("Must start with") + ": 'A-Z', 'a-z'
" + gettext("Must end with") + ": 'A-Z', 'a-z', '0-9'
", ConfigId: function(v) { return (/^[a-z][a-z0-9\_]+$/i).test(v); }, ConfigIdText: gettext("Allowed characters") + ": 'A-Z', 'a-z', '0-9', '_'" + "
" + gettext("Minimum characters") + ": 2" + "
" + gettext("Must start with") + ": " + gettext("letter"), HttpProxy: function(v) { return (/^http:\/\/.*$/).test(v); }, HttpProxyText: gettext('Example') + ": http://username:password@host:port/", DnsName: function(v) { return Proxmox.Utils.DnsName_match.test(v); }, DnsNameText: gettext('This is not a valid DNS name'), // workaround for https://www.sencha.com/forum/showthread.php?302150 proxmoxMail: function(v) { return (/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,63}$/).test(v); }, proxmoxMailText: gettext('Example') + ": user@example.com", HostList: function(v) { var list = v.split(/[\ \,\;]+/); var i; for (i = 0; i < list.length; i++) { if (list[i] == "") { continue; } if (!Proxmox.Utils.HostPort_match.test(list[i]) && !Proxmox.Utils.HostPortBrackets_match.test(list[i]) && !Proxmox.Utils.IP6_dotnotation_match.test(list[i])) { return false; } } return true; }, HostListText: gettext('Not a valid list of hosts') }); // ExtJs 5-6 has an issue with caching // see https://www.sencha.com/forum/showthread.php?308989 Ext.define('Proxmox.UnderlayPool', { override: 'Ext.dom.UnderlayPool', checkOut: function () { var cache = this.cache, len = cache.length, el; // do cleanup because some of the objects might have been destroyed while (len--) { if (cache[len].destroyed) { cache.splice(len, 1); } } // end do cleanup el = cache.shift(); if (!el) { el = Ext.Element.create(this.elementConfig); el.setVisibilityMode(2); // // tell the spec runner to ignore this element when checking if the dom is clean el.dom.setAttribute('data-sticky', true); // } return el; } }); // if the order of the values are not the same in originalValue and value // extjs will not overwrite value, but marks the field dirty and thus // the reset button will be enabled (but clicking it changes nothing) // so if the arrays are not the same after resetting, we // clear and set it Ext.define('Proxmox.form.ComboBox', { override: 'Ext.form.field.ComboBox', reset: function() { // copied from combobox var me = this; me.callParent(); me.applyEmptyText(); // clear and set when not the same var value = me.getValue(); if (Ext.isArray(me.originalValue) && Ext.isArray(value) && !Ext.Array.equals(value, me.originalValue)) { me.clearValue(); me.setValue(me.originalValue); } } }); // should be fixed with ExtJS 6.0.2, see: // https://www.sencha.com/forum/showthread.php?307244-Bug-with-datefield-in-window-with-scroll Ext.define('Proxmox.Datepicker', { override: 'Ext.picker.Date', hideMode: 'visibility' }); // force alert boxes to be rendered with an Error Icon // since Ext.Msg is an object and not a prototype, we need to override it // after the framework has been initiated Ext.onReady(function() { /*jslint confusion: true */ Ext.override(Ext.Msg, { alert: function(title, message, fn, scope) { if (Ext.isString(title)) { var config = { title: title, message: message, icon: this.ERROR, buttons: this.OK, fn: fn, scope : scope, minWidth: this.minWidth }; return this.show(config); } } }); /*jslint confusion: false */ }); Ext.define('Ext.ux.IFrame', { extend: 'Ext.Component', alias: 'widget.uxiframe', loadMask: 'Loading...', src: 'about:blank', renderTpl: [ '' ], childEls: ['iframeEl'], initComponent: function () { this.callParent(); this.frameName = this.frameName || this.id + '-frame'; }, initEvents : function() { var me = this; me.callParent(); me.iframeEl.on('load', me.onLoad, me); }, initRenderData: function() { return Ext.apply(this.callParent(), { src: this.src, frameName: this.frameName }); }, getBody: function() { var doc = this.getDoc(); return doc.body || doc.documentElement; }, getDoc: function() { try { return this.getWin().document; } catch (ex) { return null; } }, getWin: function() { var me = this, name = me.frameName, win = Ext.isIE ? me.iframeEl.dom.contentWindow : window.frames[name]; return win; }, getFrame: function() { var me = this; return me.iframeEl.dom; }, beforeDestroy: function () { this.cleanupListeners(true); this.callParent(); }, cleanupListeners: function(destroying){ var doc, prop; if (this.rendered) { try { doc = this.getDoc(); if (doc) { /*jslint nomen: true*/ Ext.get(doc).un(this._docListeners); /*jslint nomen: false*/ if (destroying && doc.hasOwnProperty) { for (prop in doc) { if (doc.hasOwnProperty(prop)) { delete doc[prop]; } } } } } catch(e) { } } }, onLoad: function() { var me = this, doc = me.getDoc(), fn = me.onRelayedEvent; if (doc) { try { // These events need to be relayed from the inner document (where they stop // bubbling) up to the outer document. This has to be done at the DOM level so // the event reaches listeners on elements like the document body. The effected // mechanisms that depend on this bubbling behavior are listed to the right // of the event. /*jslint nomen: true*/ Ext.get(doc).on( me._docListeners = { mousedown: fn, // menu dismisal (MenuManager) and Window onMouseDown (toFront) mousemove: fn, // window resize drag detection mouseup: fn, // window resize termination click: fn, // not sure, but just to be safe dblclick: fn, // not sure again scope: me } ); /*jslint nomen: false*/ } catch(e) { // cannot do this xss } // We need to be sure we remove all our events from the iframe on unload or we're going to LEAK! Ext.get(this.getWin()).on('beforeunload', me.cleanupListeners, me); this.el.unmask(); this.fireEvent('load', this); } else if (me.src) { this.el.unmask(); this.fireEvent('error', this); } }, onRelayedEvent: function (event) { // relay event from the iframe's document to the document that owns the iframe... var iframeEl = this.iframeEl, // Get the left-based iframe position iframeXY = iframeEl.getTrueXY(), originalEventXY = event.getXY(), // Get the left-based XY position. // This is because the consumer of the injected event will // perform its own RTL normalization. eventXY = event.getTrueXY(); // the event from the inner document has XY relative to that document's origin, // so adjust it to use the origin of the iframe in the outer document: event.xy = [iframeXY[0] + eventXY[0], iframeXY[1] + eventXY[1]]; event.injectEvent(iframeEl); // blame the iframe for the event... event.xy = originalEventXY; // restore the original XY (just for safety) }, load: function (src) { var me = this, text = me.loadMask, frame = me.getFrame(); if (me.fireEvent('beforeload', me, src) !== false) { if (text && me.el) { me.el.mask(text); } frame.src = me.src = (src || me.src); } } });