2017-01-31 19:32:18 +03:00
// 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' + "<br>" + 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' + "<br>" + 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' ) + ': vmbr<b>N</b>, where 0 <= <b>N</b> <= 9999' ,
BondName : function ( v ) {
return ( /^bond\d{1,4}$/ ) . test ( v ) ;
} ,
BondNameText : gettext ( 'Format' ) + ': bond<b>N</b>, where 0 <= <b>N</b> <= 9999' ,
InterfaceName : function ( v ) {
return ( /^[a-z][a-z0-9_]{1,20}$/ ) . test ( v ) ;
} ,
InterfaceNameText : gettext ( "Allowed characters" ) + ": 'a-z', '0-9', '_'" + "<br />" +
gettext ( "Minimum characters" ) + ": 2" + "<br />" +
gettext ( "Maximum characters" ) + ": 21" + "<br />" +
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', '-', '_', '.'" + "<br />" +
gettext ( "Minimum characters" ) + ": 2" + "<br />" +
gettext ( "Must start with" ) + ": 'A-Z', 'a-z'<br />" +
gettext ( "Must end with" ) + ": 'A-Z', 'a-z', '0-9'<br />" ,
ConfigId : function ( v ) {
return ( /^[a-z][a-z0-9\_]+$/i ) . test ( v ) ;
} ,
ConfigIdText : gettext ( "Allowed characters" ) + ": 'A-Z', 'a-z', '0-9', '_'" + "<br />" +
gettext ( "Minimum characters" ) + ": 2" + "<br />" +
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 ;
} ,
2017-03-29 18:50:05 +03:00
HostListText : gettext ( 'Not a valid list of hosts' ) ,
password : function ( val , field ) {
if ( field . initialPassField ) {
var pwd = field . up ( 'form' ) . down (
'[name=' + field . initialPassField + ']' ) ;
return ( val == pwd . getValue ( ) ) ;
}
return true ;
} ,
passwordText : gettext ( 'Passwords do not match' )
2017-01-31 19:32:18 +03:00
} ) ;
2017-12-19 10:16:12 +03:00
// we always want the number in x.y format and never in, e.g., x,y
Ext . define ( 'PVE.form.field.Number' , {
override : 'Ext.form.field.Number' ,
submitLocaleSeparator : false
} ) ;
2017-01-31 19:32:18 +03:00
// 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 ) ;
//<debug>
// tell the spec runner to ignore this element when checking if the dom is clean
el . dom . setAttribute ( 'data-sticky' , true ) ;
//</debug>
}
return el ;
}
} ) ;
2017-10-16 15:02:23 +03:00
// 'Enter' in Textareas and aria multiline fields should not activate the
// defaultbutton, fixed in extjs 6.0.2
Ext . define ( 'PVE.panel.Panel' , {
override : 'Ext.panel.Panel' ,
fireDefaultButton : function ( e ) {
if ( e . target . getAttribute ( 'aria-multiline' ) === 'true' ||
e . target . tagName === "TEXTAREA" ) {
return true ;
}
return this . callParent ( arguments ) ;
}
} ) ;
2017-01-31 19:32:18 +03:00
// 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 ( ) ;
// 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 ) ;
}
}
} ) ;
2017-12-19 10:16:13 +03:00
// when refreshing a grid/tree view, restoring the focus moves the view back to
// the previously focused item. Save scroll position before refocusing.
Ext . define ( null , {
override : 'Ext.view.Table' ,
jumpToFocus : false ,
saveFocusState : function ( ) {
var me = this ,
store = me . dataSource ,
actionableMode = me . actionableMode ,
navModel = me . getNavigationModel ( ) ,
focusPosition = actionableMode ? me . actionPosition : navModel . getPosition ( true ) ,
refocusRow , refocusCol ;
if ( focusPosition ) {
// Separate this from the instance that the nav model is using.
focusPosition = focusPosition . clone ( ) ;
// Exit actionable mode.
// We must inform any Actionables that they must relinquish control.
// Tabbability must be reset.
if ( actionableMode ) {
me . ownerGrid . setActionableMode ( false ) ;
}
// Blur the focused descendant, but do not trigger focusLeave.
me . el . dom . focus ( ) ;
// Exiting actionable mode navigates to the owning cell, so in either focus mode we must
// clear the navigation position
navModel . setPosition ( ) ;
// The following function will attempt to refocus back in the same mode to the same cell
// as it was at before based upon the previous record (if it's still inthe store), or the row index.
return function ( ) {
// If we still have data, attempt to refocus in the same mode.
if ( store . getCount ( ) ) {
// Adjust expectations of where we are able to refocus according to what kind of destruction
// might have been wrought on this view's DOM during focus save.
refocusRow = Math . min ( focusPosition . rowIdx , me . all . getCount ( ) - 1 ) ;
refocusCol = Math . min ( focusPosition . colIdx , me . getVisibleColumnManager ( ) . getColumns ( ) . length - 1 ) ;
focusPosition = new Ext . grid . CellContext ( me ) . setPosition (
store . contains ( focusPosition . record ) ? focusPosition . record : refocusRow , refocusCol ) ;
if ( actionableMode ) {
me . ownerGrid . setActionableMode ( true , focusPosition ) ;
} else {
me . cellFocused = true ;
// we sometimes want to scroll back to where we were
var x = me . getScrollX ( ) ;
var y = me . getScrollY ( ) ;
// Pass "preventNavigation" as true so that that does not cause selection.
navModel . setPosition ( focusPosition , null , null , null , true ) ;
if ( ! me . jumpToFocus ) {
me . scrollTo ( x , y ) ;
}
}
}
// No rows - focus associated column header
else {
focusPosition . column . focus ( ) ;
}
} ;
}
return Ext . emptyFn ;
}
} ) ;
2017-01-31 19:32:18 +03:00
// 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'
} ) ;
2017-03-30 07:30:14 +03:00
// ExtJS 6.0.1 has no setSubmitValue() (although you find it in the docs).
// Note: this.submitValue is a boolean flag, whereas getSubmitValue() returns
// data to be submitted.
2017-03-29 18:46:57 +03:00
Ext . define ( 'Proxmox.form.field.Text' , {
override : 'Ext.form.field.Text' ,
setSubmitValue : function ( v ) {
this . submitValue = v ;
} ,
} ) ;
2017-12-19 10:16:14 +03:00
// this should be fixed with ExtJS 6.0.2
// make mousescrolling work in firefox in the containers overflowhandler
Ext . define ( null , {
override : 'Ext.layout.container.boxOverflow.Scroller' ,
createWheelListener : function ( ) {
var me = this ;
if ( Ext . isFirefox ) {
me . wheelListener = me . layout . innerCt . on ( 'wheel' , me . onMouseWheelFirefox , me , { destroyable : true } ) ;
} else {
me . wheelListener = me . layout . innerCt . on ( 'mousewheel' , me . onMouseWheel , me , { destroyable : true } ) ;
}
} ,
// special wheel handler for firefox. differs from the default onMouseWheel
// handler by using deltaY instead of wheelDeltaY and no normalizing,
// because it is already
onMouseWheelFirefox : function ( e ) {
e . stopEvent ( ) ;
var delta = e . browserEvent . deltaY || 0 ;
this . scrollBy ( delta * this . wheelIncrement , false ) ;
}
} ) ;
2017-01-31 19:32:18 +03:00
// 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 : [
'<iframe src="{src}" id="{id}-iframeEl" data-ref="iframeEl" name="{frameName}" width="100%" height="100%" frameborder="0" allowfullscreen="true"></iframe>'
] ,
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 ) ;
}
}
} ) ;