]> git.proxmox.com Git - pve-manager.git/blobdiff - www/manager6/Toolkit.js
dc/OptionView: cleanup & use new features from ObjectGrid
[pve-manager.git] / www / manager6 / Toolkit.js
index e3a4e2396485baabed27ad7c6e7325d016626a21..c7f9a0c45d231ea34f318d5ab14e996bc046cfbb 100644 (file)
@@ -1,6 +1,6 @@
 // ExtJS related things
 
-PVE.Utils.toolkit = 'extjs',
+Proxmox.Utils.toolkit = 'extjs';
 
  // do not send '_dc' parameter
 Ext.Ajax.disableCaching = false;
@@ -8,13 +8,13 @@ Ext.Ajax.disableCaching = false;
 // custom Vtypes
 Ext.apply(Ext.form.field.VTypes, {
     IPAddress:  function(v) {
-       return IP4_match.test(v);
+       return Proxmox.Utils.IP4_match.test(v);
     },
     IPAddressText:  gettext('Example') + ': 192.168.1.1',
     IPAddressMask: /[\d\.]/i,
 
     IPCIDRAddress:  function(v) {
-       var result = IP4_cidr_match.exec(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);
@@ -23,13 +23,13 @@ Ext.apply(Ext.form.field.VTypes, {
     IPCIDRAddressMask: /[\d\.\/]/i,
 
     IP6Address:  function(v) {
-        return IP6_match.test(v);
+        return Proxmox.Utils.IP6_match.test(v);
     },
     IP6AddressText:  gettext('Example') + ': 2001:DB8::42',
     IP6AddressMask: /[A-Fa-f0-9:]/,
 
     IP6CIDRAddress:  function(v) {
-       var result = IP6_cidr_match.exec(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);
@@ -44,7 +44,7 @@ Ext.apply(Ext.form.field.VTypes, {
     IP6PrefixLengthMask:  /[0-9]/,
 
     IP64Address:  function(v) {
-        return IP64_match.test(v);
+        return Proxmox.Utils.IP64_match.test(v);
     },
     IP64AddressText:  gettext('Example') + ': 192.168.1.1 2001:DB8::42',
     IP64AddressMask: /[A-Fa-f0-9\.:]/,
@@ -55,6 +55,12 @@ Ext.apply(Ext.form.field.VTypes, {
     MacAddressMask: /[a-fA-F0-9:]/,
     MacAddressText: gettext('Example') + ': 01:23:45:67:89:ab',
 
+    MacPrefix:  function(v) {
+       return (/^[a-f0-9]{2}(?::[a-f0-9]{2}){0,2}:?$/i).test(v);
+    },
+    MacPrefixMask: /[a-fA-F0-9:]/,
+    MacPrefixText: gettext('Example') + ': 02:8f',
+
     BridgeName: function(v) {
         return (/^vmbr\d{1,4}$/).test(v);
     },
@@ -68,8 +74,10 @@ Ext.apply(Ext.form.field.VTypes, {
     InterfaceName: function(v) {
         return (/^[a-z][a-z0-9_]{1,20}$/).test(v);
     },
-    InterfaceNameText: gettext('Format') + ': [a-z][a-z0-9_]{1,20}',
-
+    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);
@@ -79,12 +87,17 @@ Ext.apply(Ext.form.field.VTypes, {
     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', '-', '_', '.'",
+    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', '_'",
+    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);
@@ -92,7 +105,7 @@ Ext.apply(Ext.form.field.VTypes, {
     HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
 
     DnsName: function(v) {
-       return (/^(([a-zA-Z0-9]([a-zA-Z0-9\-]*[a-zA-Z0-9])?)\.)*([A-Za-z0-9]([A-Za-z0-9\-]*[A-Za-z0-9])?)$/).test(v);
+       return Proxmox.Utils.DnsName_match.test(v);
     },
     DnsNameText: gettext('This is not a valid DNS name'),
 
@@ -101,6 +114,32 @@ Ext.apply(Ext.form.field.VTypes, {
         return (/^(\w+)([\-+.][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,63}$/).test(v);
     },
     pveMailText: 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')
+});
+
+// since 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
 });
 
 // ExtJs 5-6 has an issue with caching
@@ -136,6 +175,116 @@ Ext.define('PVE.UnderlayPool', {
     }
 });
 
+// '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);
+    }
+});
+
+// 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('PVE.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);
+       }
+    }
+});
+
+// when refreshing the view of a grid/tree
+// the restoring of the focus brings the
+// focused item back in the view, even when we scrolled away
+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;
+    }
+});
+
 // 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('PVE.Datepicker', {
@@ -143,6 +292,33 @@ Ext.define('PVE.Datepicker', {
     hideMode: 'visibility'
 });
 
+// this should be fixed with ExtJS 6.0.2
+// this makes mousescrolling work in firefox in the overflowhandler
+// and does not change behaviour in any other browser
+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
+    // nearly the same as the default onMouseWheel handler,
+    // but using deltaY instead of wheelDeltaY
+    // and no normalizing, because it is already normalized
+    onMouseWheelFirefox: function(e) {
+       e.stopEvent();
+       var delta = e.browserEvent.deltaY || 0;
+       this.scrollBy(delta * this.wheelIncrement, false);
+    }
+
+});
+
 // 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
@@ -238,10 +414,12 @@ Ext.define('Ext.ux.IFrame', {
             try {
                 doc = this.getDoc();
                 if (doc) {
+                   /*jslint nomen: true*/
                     Ext.get(doc).un(this._docListeners);
-                    if (destroying) {
+                   /*jslint nomen: false*/
+                    if (destroying && doc.hasOwnProperty) {
                         for (prop in doc) {
-                            if (doc.hasOwnProperty && doc.hasOwnProperty(prop)) {
+                            if (doc.hasOwnProperty(prop)) {
                                 delete doc[prop];
                             }
                         }
@@ -263,6 +441,7 @@ Ext.define('Ext.ux.IFrame', {
                 // 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)
@@ -273,6 +452,7 @@ Ext.define('Ext.ux.IFrame', {
                         scope: me
                     }
                 );
+               /*jslint nomen: false*/
             } catch(e) {
                 // cannot do this xss
             }