]> 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 7bef6f21245e292be2160458977fb04c35a7b713..c7f9a0c45d231ea34f318d5ab14e996bc046cfbb 100644 (file)
@@ -1,8 +1,6 @@
-/*global IP4_match, IP4_cidr_match, IP6_match, IP6_cidr_match, IP64_match, DnsName_match, DnsName_REGEXP, IPV4_REGEXP, IPV6_REGEXP*/
-/*global HostPort_match, HostPortBrackets_match, IP6_dotnotation_match*/
 // ExtJS related things
 
-PVE.Utils.toolkit = 'extjs';
+Proxmox.Utils.toolkit = 'extjs';
 
  // do not send '_dc' parameter
 Ext.Ajax.disableCaching = false;
@@ -10,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);
@@ -25,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);
@@ -46,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\.:]/,
@@ -57,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);
     },
@@ -70,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);
@@ -81,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);
@@ -94,7 +105,7 @@ Ext.apply(Ext.form.field.VTypes, {
     HttpProxyText: gettext('Example') + ": http://username:password&#64;host:port/",
 
     DnsName: function(v) {
-       return DnsName_match.test(v);
+       return Proxmox.Utils.DnsName_match.test(v);
     },
     DnsNameText: gettext('This is not a valid DNS name'),
 
@@ -112,9 +123,9 @@ Ext.apply(Ext.form.field.VTypes, {
                continue;
            }
 
-           if (!HostPort_match.test(list[i]) &&
-               !HostPortBrackets_match.test(list[i]) &&
-               !IP6_dotnotation_match.test(list[i])) {
+           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;
            }
        }
@@ -124,6 +135,13 @@ Ext.apply(Ext.form.field.VTypes, {
     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
 // see https://www.sencha.com/forum/showthread.php?308989
 Ext.define('PVE.UnderlayPool', {
@@ -157,6 +175,20 @@ 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)
@@ -169,16 +201,90 @@ Ext.define('PVE.form.ComboBox', {
        // copied from combobox
        var me = this;
        me.callParent();
-       me.applyEmptyText();
 
        // clear and set when not the same
-       if (Ext.isArray(me.originalValue) && !Ext.Array.equals(me.getValue(), me.originalValue)) {
+       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', {
@@ -186,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