]> git.proxmox.com Git - proxmox-widget-toolkit.git/commitdiff
initial import
authorDietmar Maurer <dietmar@proxmox.com>
Mon, 30 Jan 2017 08:45:41 +0000 (09:45 +0100)
committerDietmar Maurer <dietmar@proxmox.com>
Mon, 30 Jan 2017 08:45:41 +0000 (09:45 +0100)
copied from pve-manager, remove pve-manager specific code and
rename 'pve' to 'proxmox'.

Utils.js [new file with mode: 0644]
data/DiffStore.js [new file with mode: 0644]
data/ObjectStore.js [new file with mode: 0644]
data/ProxmoxProxy.js [new file with mode: 0644]
data/TimezoneStore.js [new file with mode: 0644]
data/UpdateQueue.js [new file with mode: 0644]
data/UpdateStore.js [new file with mode: 0644]
data/reader/JsonObject.js [new file with mode: 0644]

diff --git a/Utils.js b/Utils.js
new file mode 100644 (file)
index 0000000..043eab0
--- /dev/null
+++ b/Utils.js
@@ -0,0 +1,226 @@
+Ext.ns('Proxmox');
+Ext.ns('Proxmox.Setup');
+
+// TODO: implement gettext
+function gettext(buf) { return buf; }
+
+
+if (!Ext.isDefined(Proxmox.Setup.auth_cookie)) {
+    trow "Proxmox library not initialize";
+}
+
+// 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)) {
+    var console = {
+       dir: function() {},
+       log: function() {}
+    };
+}
+
+Ext.Ajax.defaultHeaders = {
+    'Accept': 'application/json'
+};
+
+Ext.Ajax.on('beforerequest', function(conn, options) {
+    if (Proxmox.CSRFPreventionToken) {
+       if (!options.headers) {
+           options.headers = {};
+       }
+       options.headers.CSRFPreventionToken = Proxmox.CSRFPreventionToken;
+    }
+});
+
+Ext.define('Proxmox.Utils', { utilities: {
+
+    // this singleton contains miscellaneous utilities
+
+    authOK: function() {
+       return (Proxmox.UserName !== '') && Ext.util.Cookies.get(Proxmox.Setup.auth_cookie_name);
+    },
+
+    authClear: function() {
+       Ext.util.Cookies.clear(Proxmox.Setup.auth_cookie_name);
+    },
+
+    // comp.setLoading() is buggy in ExtJS 4.0.7, so we
+    // use el.mask() instead
+    setErrorMask: function(comp, msg) {
+       var el = comp.el;
+       if (!el) {
+           return;
+       }
+       if (!msg) {
+           el.unmask();
+       } else {
+           if (msg === true) {
+               el.mask(gettext("Loading..."));
+           } else {
+               el.mask(msg);
+           }
+       }
+    },
+
+    monStoreErrors: function(me, store) {
+       me.mon(store, 'beforeload', function(s, operation, eOpts) {
+           if (!me.loadCount) {
+               me.loadCount = 0; // make sure it is numeric
+               Proxmox.Utils.setErrorMask(me, true);
+           }
+       });
+
+       // only works with 'proxmox' proxy
+       me.mon(store.proxy, 'afterload', function(proxy, request, success) {
+           me.loadCount++;
+
+           if (success) {
+               Proxmox.Utils.setErrorMask(me, false);
+               return;
+           }
+
+           var msg;
+           /*jslint nomen: true */
+           var operation = request._operation;
+           var error = operation.getError();
+           if (error.statusText) {
+               msg = error.statusText + ' (' + error.status + ')';
+           } else {
+               msg = gettext('Connection error');
+           }
+           Proxmox.Utils.setErrorMask(me, msg);
+       });
+    },
+
+    extractRequestError: function(result, verbose) {
+       var msg = gettext('Successful');
+
+       if (!result.success) {
+           msg = gettext("Unknown error");
+           if (result.message) {
+               msg = result.message;
+               if (result.status) {
+                   msg += ' (' + result.status + ')';
+               }
+           }
+           if (verbose && Ext.isObject(result.errors)) {
+               msg += "<br>";
+               Ext.Object.each(result.errors, function(prop, desc) {
+                   msg += "<br><b>" + Ext.htmlEncode(prop) + "</b>: " +
+                       Ext.htmlEncode(desc);
+               });
+           }
+       }
+
+       return msg;
+    },
+
+    // Ext.Ajax.request
+    API2Request: function(reqOpts) {
+
+       var newopts = Ext.apply({
+           waitMsg: gettext('Please wait...')
+       }, reqOpts);
+
+       if (!newopts.url.match(/^\/api2/)) {
+           newopts.url = '/api2/extjs' + newopts.url;
+       }
+       delete newopts.callback;
+
+       var createWrapper = function(successFn, callbackFn, failureFn) {
+           Ext.apply(newopts, {
+               success: function(response, options) {
+                   if (options.waitMsgTarget) {
+                       options.waitMsgTarget.setLoading(false);
+                   }
+                   var result = Ext.decode(response.responseText);
+                   response.result = result;
+                   if (!result.success) {
+                       response.htmlStatus = Proxmox.Utils.extractRequestError(result, true);
+                       Ext.callback(callbackFn, options.scope, [options, false, response]);
+                       Ext.callback(failureFn, options.scope, [response, options]);
+                       return;
+                   }
+                   Ext.callback(callbackFn, options.scope, [options, true, response]);
+                   Ext.callback(successFn, options.scope, [response, options]);
+               },
+               failure: function(response, options) {
+                   if (options.waitMsgTarget) {
+                       options.waitMsgTarget.setLoading(false);
+                   }
+                   response.result = {};
+                   try {
+                       response.result = Ext.decode(response.responseText);
+                   } catch(e) {}
+                   var msg = gettext('Connection error') + ' - server offline?';
+                   if (response.aborted) {
+                       msg = gettext('Connection error') + ' - aborted.';
+                   } else if (response.timedout) {
+                       msg = gettext('Connection error') + ' - Timeout.';
+                   } else if (response.status && response.statusText) {
+                       msg = gettext('Connection error') + ' ' + response.status + ': ' + response.statusText;
+                   }
+                   response.htmlStatus = msg;
+                   Ext.callback(callbackFn, options.scope, [options, false, response]);
+                   Ext.callback(failureFn, options.scope, [response, options]);
+               }
+           });
+       };
+
+       createWrapper(reqOpts.success, reqOpts.callback, reqOpts.failure);
+
+       var target = newopts.waitMsgTarget;
+       if (target) {
+           // Note: ExtJS bug - this does not work when component is not rendered
+           target.setLoading(newopts.waitMsg);
+       }
+       Ext.Ajax.request(newopts);
+    },
+
+    },
+                         
+    singleton: true,
+    constructor: function() {
+       var me = this;
+       Ext.apply(me, me.utilities);
+
+       var IPV4_OCTET = "(?:25[0-5]|(?:[1-9]|1[0-9]|2[0-4])?[0-9])";
+       var IPV4_REGEXP = "(?:(?:" + IPV4_OCTET + "\\.){3}" + IPV4_OCTET + ")";
+       var IPV6_H16 = "(?:[0-9a-fA-F]{1,4})";
+       var IPV6_LS32 = "(?:(?:" + IPV6_H16 + ":" + IPV6_H16 + ")|" + IPV4_REGEXP + ")";
+
+
+       me.IP4_match = new RegExp("^(?:" + IPV4_REGEXP + ")$");
+       me.IP4_cidr_match = new RegExp("^(?:" + IPV4_REGEXP + ")\/([0-9]{1,2})$");
+
+       var IPV6_REGEXP = "(?:" +
+           "(?:(?:"                                                  + "(?:" + IPV6_H16 + ":){6})" + IPV6_LS32 + ")|" +
+           "(?:(?:"                                         +   "::" + "(?:" + IPV6_H16 + ":){5})" + IPV6_LS32 + ")|" +
+           "(?:(?:(?:"                           + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){4})" + IPV6_LS32 + ")|" +
+           "(?:(?:(?:(?:" + IPV6_H16 + ":){0,1}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){3})" + IPV6_LS32 + ")|" +
+           "(?:(?:(?:(?:" + IPV6_H16 + ":){0,2}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){2})" + IPV6_LS32 + ")|" +
+           "(?:(?:(?:(?:" + IPV6_H16 + ":){0,3}" + IPV6_H16 + ")?::" + "(?:" + IPV6_H16 + ":){1})" + IPV6_LS32 + ")|" +
+           "(?:(?:(?:(?:" + IPV6_H16 + ":){0,4}" + IPV6_H16 + ")?::" +                         ")" + IPV6_LS32 + ")|" +
+           "(?:(?:(?:(?:" + IPV6_H16 + ":){0,5}" + IPV6_H16 + ")?::" +                         ")" + IPV6_H16  + ")|" +
+           "(?:(?:(?:(?:" + IPV6_H16 + ":){0,7}" + IPV6_H16 + ")?::" +                         ")"             + ")"  +
+           ")";
+
+       me.IP6_match = new RegExp("^(?:" + IPV6_REGEXP + ")$");
+       me.IP6_cidr_match = new RegExp("^(?:" + IPV6_REGEXP + ")\/([0-9]{1,3})$");
+       me.IP6_bracket_match = new RegExp("^\\[(" + IPV6_REGEXP + ")\\]");
+
+       me.IP64_match = new RegExp("^(?:" + IPV6_REGEXP + "|" + IPV4_REGEXP + ")$");
+
+       var 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+)?$");
+    }
+});
diff --git a/data/DiffStore.js b/data/DiffStore.js
new file mode 100644 (file)
index 0000000..22b84a6
--- /dev/null
@@ -0,0 +1,114 @@
+/*
+ * The DiffStore is a in-memory store acting as proxy between a real store
+ * instance and a component.
+ * Its purpose is to redisplay the component *only* if the data has been changed
+ * inside the real store, to avoid the annoying visual flickering of using
+ * the real store directly.
+ *
+ * Implementation:
+ * The DiffStore monitors via mon() the 'load' events sent by the real store.
+ * On each 'load' event, the DiffStore compares its own content with the target
+ * store (call to cond_add_item()) and then fires a 'refresh' event.
+ * The 'refresh' event will automatically trigger a view refresh on the component
+ * who binds to this store.
+ */
+
+/* Config properties:
+ * rstore: the realstore which will autorefresh its content from the API
+ * Only works if rstore has a model and use 'idProperty'
+ * sortAfterUpdate: sort the diffstore before rendering the view
+ */
+Ext.define('Proxmox.data.DiffStore', {
+    extend: 'Ext.data.Store',
+    alias: 'store.diff',
+
+    sortAfterUpdate: false,
+    
+    constructor: function(config) {
+       var me = this;
+
+       config = config || {};
+
+       if (!config.rstore) {
+           throw "no rstore specified";
+       }
+
+       if (!config.rstore.model) {
+           throw "no rstore model specified";
+       }
+
+       var rstore = config.rstore;
+
+       Ext.apply(config, {
+           model: rstore.model,
+           proxy: { type: 'memory' }
+       });
+
+       me.callParent([config]);
+
+       var first_load = true;
+
+       var cond_add_item = function(data, id) {
+           var olditem = me.getById(id);
+           if (olditem) {
+               olditem.beginEdit();
+               Ext.Array.each(me.model.prototype.fields, function(field) {
+                   if (olditem.data[field.name] !== data[field.name]) {
+                       olditem.set(field.name, data[field.name]);
+                   }
+               });
+               olditem.endEdit(true);
+               olditem.commit(); 
+           } else {
+               var newrec = Ext.create(me.model, data);
+               var pos = (me.appendAtStart && !first_load) ? 0 : me.data.length;
+               me.insert(pos, newrec);
+           }
+       };
+
+       var loadFn = function(s, records, success) {
+
+           if (!success) {
+               return;
+           }
+
+           me.suspendEvents();
+
+           // getSource returns null if data is not filtered
+           // if it is filtered it returns all records
+           var allItems = me.getData().getSource() || me.getData();
+
+           // remove vanished items
+           allItems.each(function(olditem) {
+               var item = rstore.getById(olditem.getId());
+               if (!item) {
+                   me.remove(olditem);
+               }
+           });
+
+           rstore.each(function(item) {
+               cond_add_item(item.data, item.getId());
+           });
+
+           me.filter();
+
+           if (me.sortAfterUpdate) {
+               me.sort();
+           }
+
+           first_load = false;
+
+           me.resumeEvents();
+           me.fireEvent('refresh', me);
+           me.fireEvent('datachanged', me);
+       };
+
+       if (rstore.isLoaded()) {
+           // if store is already loaded,
+           // insert items instantly
+           loadFn(rstore, [], true);
+       }
+
+       me.mon(rstore, 'load', loadFn);
+    }
+});
diff --git a/data/ObjectStore.js b/data/ObjectStore.js
new file mode 100644 (file)
index 0000000..1e3771f
--- /dev/null
@@ -0,0 +1,35 @@
+/* This store encapsulates data items which are organized as an Array of key-values Objects
+ * ie data[0] contains something like {key: "keyboard", value: "da"}
+*
+* Designed to work with the KeyValue model and the JsonObject data reader
+*/
+Ext.define('Proxmox.data.ObjectStore',  {
+    extend: 'Proxmox.data.UpdateStore',
+
+    constructor: function(config) {
+       var me = this;
+
+        config = config || {};
+
+       if (!config.storeid) {
+           config.storeid =  'proxmox-store-' + (++Ext.idSeed);
+       }
+
+        Ext.applyIf(config, {
+           model: 'KeyValue',
+            proxy: {
+                type: 'proxmox',
+               url: config.url,
+               extraParams: config.extraParams,
+                reader: {
+                   type: 'jsonobject',
+                   rows: config.rows,
+                   readArray: config.readArray,
+                   rootProperty: config.root || 'data'
+               }
+            }
+        });
+
+        me.callParent([config]);
+    }
+});
diff --git a/data/ProxmoxProxy.js b/data/ProxmoxProxy.js
new file mode 100644 (file)
index 0000000..6fe1303
--- /dev/null
@@ -0,0 +1,29 @@
+Ext.define('Proxmox.RestProxy', {
+    extend: 'Ext.data.RestProxy',
+    alias : 'proxy.proxmox',
+    
+    pageParam : null,
+    startParam: null,
+    limitParam: null,
+    groupParam: null,
+    sortParam: null,
+    filterParam: null,
+    noCache : false,
+
+    afterRequest: function(request, success) {
+       this.fireEvent('afterload', this, request, success);
+       return;
+    },
+
+    constructor: function(config) {
+
+       Ext.applyIf(config, {       
+           reader: {
+               type: 'json',
+               rootProperty: config.root || 'data'
+           }
+       });
+
+       this.callParent([config]); 
+    }
+});
diff --git a/data/TimezoneStore.js b/data/TimezoneStore.js
new file mode 100644 (file)
index 0000000..2b26f23
--- /dev/null
@@ -0,0 +1,418 @@
+Ext.define('Timezone', {
+    extend: 'Ext.data.Model',
+    fields: ['zone']
+});
+
+Ext.define('Proxmox.data.TimezoneStore', {
+    extend: 'Ext.data.Store',
+    model: 'Timezone',
+    data: [
+           ['Africa/Abidjan'],
+           ['Africa/Accra'],
+           ['Africa/Addis_Ababa'],
+           ['Africa/Algiers'],
+           ['Africa/Asmara'],
+           ['Africa/Bamako'],
+           ['Africa/Bangui'],
+           ['Africa/Banjul'],
+           ['Africa/Bissau'],
+           ['Africa/Blantyre'],
+           ['Africa/Brazzaville'],
+           ['Africa/Bujumbura'],
+           ['Africa/Cairo'],
+           ['Africa/Casablanca'],
+           ['Africa/Ceuta'],
+           ['Africa/Conakry'],
+           ['Africa/Dakar'],
+           ['Africa/Dar_es_Salaam'],
+           ['Africa/Djibouti'],
+           ['Africa/Douala'],
+           ['Africa/El_Aaiun'],
+           ['Africa/Freetown'],
+           ['Africa/Gaborone'],
+           ['Africa/Harare'],
+           ['Africa/Johannesburg'],
+           ['Africa/Kampala'],
+           ['Africa/Khartoum'],
+           ['Africa/Kigali'],
+           ['Africa/Kinshasa'],
+           ['Africa/Lagos'],
+           ['Africa/Libreville'],
+           ['Africa/Lome'],
+           ['Africa/Luanda'],
+           ['Africa/Lubumbashi'],
+           ['Africa/Lusaka'],
+           ['Africa/Malabo'],
+           ['Africa/Maputo'],
+           ['Africa/Maseru'],
+           ['Africa/Mbabane'],
+           ['Africa/Mogadishu'],
+           ['Africa/Monrovia'],
+           ['Africa/Nairobi'],
+           ['Africa/Ndjamena'],
+           ['Africa/Niamey'],
+           ['Africa/Nouakchott'],
+           ['Africa/Ouagadougou'],
+           ['Africa/Porto-Novo'],
+           ['Africa/Sao_Tome'],
+           ['Africa/Tripoli'],
+           ['Africa/Tunis'],
+           ['Africa/Windhoek'],
+           ['America/Adak'],
+           ['America/Anchorage'],
+           ['America/Anguilla'],
+           ['America/Antigua'],
+           ['America/Araguaina'],
+           ['America/Argentina/Buenos_Aires'],
+           ['America/Argentina/Catamarca'],
+           ['America/Argentina/Cordoba'],
+           ['America/Argentina/Jujuy'],
+           ['America/Argentina/La_Rioja'],
+           ['America/Argentina/Mendoza'],
+           ['America/Argentina/Rio_Gallegos'],
+           ['America/Argentina/Salta'],
+           ['America/Argentina/San_Juan'],
+           ['America/Argentina/San_Luis'],
+           ['America/Argentina/Tucuman'],
+           ['America/Argentina/Ushuaia'],
+           ['America/Aruba'],
+           ['America/Asuncion'],
+           ['America/Atikokan'],
+           ['America/Bahia'],
+           ['America/Bahia_Banderas'],
+           ['America/Barbados'],
+           ['America/Belem'],
+           ['America/Belize'],
+           ['America/Blanc-Sablon'],
+           ['America/Boa_Vista'],
+           ['America/Bogota'],
+           ['America/Boise'],
+           ['America/Cambridge_Bay'],
+           ['America/Campo_Grande'],
+           ['America/Cancun'],
+           ['America/Caracas'],
+           ['America/Cayenne'],
+           ['America/Cayman'],
+           ['America/Chicago'],
+           ['America/Chihuahua'],
+           ['America/Costa_Rica'],
+           ['America/Cuiaba'],
+           ['America/Curacao'],
+           ['America/Danmarkshavn'],
+           ['America/Dawson'],
+           ['America/Dawson_Creek'],
+           ['America/Denver'],
+           ['America/Detroit'],
+           ['America/Dominica'],
+           ['America/Edmonton'],
+           ['America/Eirunepe'],
+           ['America/El_Salvador'],
+           ['America/Fortaleza'],
+           ['America/Glace_Bay'],
+           ['America/Godthab'],
+           ['America/Goose_Bay'],
+           ['America/Grand_Turk'],
+           ['America/Grenada'],
+           ['America/Guadeloupe'],
+           ['America/Guatemala'],
+           ['America/Guayaquil'],
+           ['America/Guyana'],
+           ['America/Halifax'],
+           ['America/Havana'],
+           ['America/Hermosillo'],
+           ['America/Indiana/Indianapolis'],
+           ['America/Indiana/Knox'],
+           ['America/Indiana/Marengo'],
+           ['America/Indiana/Petersburg'],
+           ['America/Indiana/Tell_City'],
+           ['America/Indiana/Vevay'],
+           ['America/Indiana/Vincennes'],
+           ['America/Indiana/Winamac'],
+           ['America/Inuvik'],
+           ['America/Iqaluit'],
+           ['America/Jamaica'],
+           ['America/Juneau'],
+           ['America/Kentucky/Louisville'],
+           ['America/Kentucky/Monticello'],
+           ['America/La_Paz'],
+           ['America/Lima'],
+           ['America/Los_Angeles'],
+           ['America/Maceio'],
+           ['America/Managua'],
+           ['America/Manaus'],
+           ['America/Marigot'],
+           ['America/Martinique'],
+           ['America/Matamoros'],
+           ['America/Mazatlan'],
+           ['America/Menominee'],
+           ['America/Merida'],
+           ['America/Mexico_City'],
+           ['America/Miquelon'],
+           ['America/Moncton'],
+           ['America/Monterrey'],
+           ['America/Montevideo'],
+           ['America/Montreal'],
+           ['America/Montserrat'],
+           ['America/Nassau'],
+           ['America/New_York'],
+           ['America/Nipigon'],
+           ['America/Nome'],
+           ['America/Noronha'],
+           ['America/North_Dakota/Center'],
+           ['America/North_Dakota/New_Salem'],
+           ['America/Ojinaga'],
+           ['America/Panama'],
+           ['America/Pangnirtung'],
+           ['America/Paramaribo'],
+           ['America/Phoenix'],
+           ['America/Port-au-Prince'],
+           ['America/Port_of_Spain'],
+           ['America/Porto_Velho'],
+           ['America/Puerto_Rico'],
+           ['America/Rainy_River'],
+           ['America/Rankin_Inlet'],
+           ['America/Recife'],
+           ['America/Regina'],
+           ['America/Resolute'],
+           ['America/Rio_Branco'],
+           ['America/Santa_Isabel'],
+           ['America/Santarem'],
+           ['America/Santiago'],
+           ['America/Santo_Domingo'],
+           ['America/Sao_Paulo'],
+           ['America/Scoresbysund'],
+           ['America/Shiprock'],
+           ['America/St_Barthelemy'],
+           ['America/St_Johns'],
+           ['America/St_Kitts'],
+           ['America/St_Lucia'],
+           ['America/St_Thomas'],
+           ['America/St_Vincent'],
+           ['America/Swift_Current'],
+           ['America/Tegucigalpa'],
+           ['America/Thule'],
+           ['America/Thunder_Bay'],
+           ['America/Tijuana'],
+           ['America/Toronto'],
+           ['America/Tortola'],
+           ['America/Vancouver'],
+           ['America/Whitehorse'],
+           ['America/Winnipeg'],
+           ['America/Yakutat'],
+           ['America/Yellowknife'],
+           ['Antarctica/Casey'],
+           ['Antarctica/Davis'],
+           ['Antarctica/DumontDUrville'],
+           ['Antarctica/Macquarie'],
+           ['Antarctica/Mawson'],
+           ['Antarctica/McMurdo'],
+           ['Antarctica/Palmer'],
+           ['Antarctica/Rothera'],
+           ['Antarctica/South_Pole'],
+           ['Antarctica/Syowa'],
+           ['Antarctica/Vostok'],
+           ['Arctic/Longyearbyen'],
+           ['Asia/Aden'],
+           ['Asia/Almaty'],
+           ['Asia/Amman'],
+           ['Asia/Anadyr'],
+           ['Asia/Aqtau'],
+           ['Asia/Aqtobe'],
+           ['Asia/Ashgabat'],
+           ['Asia/Baghdad'],
+           ['Asia/Bahrain'],
+           ['Asia/Baku'],
+           ['Asia/Bangkok'],
+           ['Asia/Beirut'],
+           ['Asia/Bishkek'],
+           ['Asia/Brunei'],
+           ['Asia/Choibalsan'],
+           ['Asia/Chongqing'],
+           ['Asia/Colombo'],
+           ['Asia/Damascus'],
+           ['Asia/Dhaka'],
+           ['Asia/Dili'],
+           ['Asia/Dubai'],
+           ['Asia/Dushanbe'],
+           ['Asia/Gaza'],
+           ['Asia/Harbin'],
+           ['Asia/Ho_Chi_Minh'],
+           ['Asia/Hong_Kong'],
+           ['Asia/Hovd'],
+           ['Asia/Irkutsk'],
+           ['Asia/Jakarta'],
+           ['Asia/Jayapura'],
+           ['Asia/Jerusalem'],
+           ['Asia/Kabul'],
+           ['Asia/Kamchatka'],
+           ['Asia/Karachi'],
+           ['Asia/Kashgar'],
+           ['Asia/Kathmandu'],
+           ['Asia/Kolkata'],
+           ['Asia/Krasnoyarsk'],
+           ['Asia/Kuala_Lumpur'],
+           ['Asia/Kuching'],
+           ['Asia/Kuwait'],
+           ['Asia/Macau'],
+           ['Asia/Magadan'],
+           ['Asia/Makassar'],
+           ['Asia/Manila'],
+           ['Asia/Muscat'],
+           ['Asia/Nicosia'],
+           ['Asia/Novokuznetsk'],
+           ['Asia/Novosibirsk'],
+           ['Asia/Omsk'],
+           ['Asia/Oral'],
+           ['Asia/Phnom_Penh'],
+           ['Asia/Pontianak'],
+           ['Asia/Pyongyang'],
+           ['Asia/Qatar'],
+           ['Asia/Qyzylorda'],
+           ['Asia/Rangoon'],
+           ['Asia/Riyadh'],
+           ['Asia/Sakhalin'],
+           ['Asia/Samarkand'],
+           ['Asia/Seoul'],
+           ['Asia/Shanghai'],
+           ['Asia/Singapore'],
+           ['Asia/Taipei'],
+           ['Asia/Tashkent'],
+           ['Asia/Tbilisi'],
+           ['Asia/Tehran'],
+           ['Asia/Thimphu'],
+           ['Asia/Tokyo'],
+           ['Asia/Ulaanbaatar'],
+           ['Asia/Urumqi'],
+           ['Asia/Vientiane'],
+           ['Asia/Vladivostok'],
+           ['Asia/Yakutsk'],
+           ['Asia/Yekaterinburg'],
+           ['Asia/Yerevan'],
+           ['Atlantic/Azores'],
+           ['Atlantic/Bermuda'],
+           ['Atlantic/Canary'],
+           ['Atlantic/Cape_Verde'],
+           ['Atlantic/Faroe'],
+           ['Atlantic/Madeira'],
+           ['Atlantic/Reykjavik'],
+           ['Atlantic/South_Georgia'],
+           ['Atlantic/St_Helena'],
+           ['Atlantic/Stanley'],
+           ['Australia/Adelaide'],
+           ['Australia/Brisbane'],
+           ['Australia/Broken_Hill'],
+           ['Australia/Currie'],
+           ['Australia/Darwin'],
+           ['Australia/Eucla'],
+           ['Australia/Hobart'],
+           ['Australia/Lindeman'],
+           ['Australia/Lord_Howe'],
+           ['Australia/Melbourne'],
+           ['Australia/Perth'],
+           ['Australia/Sydney'],
+           ['Europe/Amsterdam'],
+           ['Europe/Andorra'],
+           ['Europe/Athens'],
+           ['Europe/Belgrade'],
+           ['Europe/Berlin'],
+           ['Europe/Bratislava'],
+           ['Europe/Brussels'],
+           ['Europe/Bucharest'],
+           ['Europe/Budapest'],
+           ['Europe/Chisinau'],
+           ['Europe/Copenhagen'],
+           ['Europe/Dublin'],
+           ['Europe/Gibraltar'],
+           ['Europe/Guernsey'],
+           ['Europe/Helsinki'],
+           ['Europe/Isle_of_Man'],
+           ['Europe/Istanbul'],
+           ['Europe/Jersey'],
+           ['Europe/Kaliningrad'],
+           ['Europe/Kiev'],
+           ['Europe/Lisbon'],
+           ['Europe/Ljubljana'],
+           ['Europe/London'],
+           ['Europe/Luxembourg'],
+           ['Europe/Madrid'],
+           ['Europe/Malta'],
+           ['Europe/Mariehamn'],
+           ['Europe/Minsk'],
+           ['Europe/Monaco'],
+           ['Europe/Moscow'],
+           ['Europe/Oslo'],
+           ['Europe/Paris'],
+           ['Europe/Podgorica'],
+           ['Europe/Prague'],
+           ['Europe/Riga'],
+           ['Europe/Rome'],
+           ['Europe/Samara'],
+           ['Europe/San_Marino'],
+           ['Europe/Sarajevo'],
+           ['Europe/Simferopol'],
+           ['Europe/Skopje'],
+           ['Europe/Sofia'],
+           ['Europe/Stockholm'],
+           ['Europe/Tallinn'],
+           ['Europe/Tirane'],
+           ['Europe/Uzhgorod'],
+           ['Europe/Vaduz'],
+           ['Europe/Vatican'],
+           ['Europe/Vienna'],
+           ['Europe/Vilnius'],
+           ['Europe/Volgograd'],
+           ['Europe/Warsaw'],
+           ['Europe/Zagreb'],
+           ['Europe/Zaporozhye'],
+           ['Europe/Zurich'],
+           ['Indian/Antananarivo'],
+           ['Indian/Chagos'],
+           ['Indian/Christmas'],
+           ['Indian/Cocos'],
+           ['Indian/Comoro'],
+           ['Indian/Kerguelen'],
+           ['Indian/Mahe'],
+           ['Indian/Maldives'],
+           ['Indian/Mauritius'],
+           ['Indian/Mayotte'],
+           ['Indian/Reunion'],
+           ['Pacific/Apia'],
+           ['Pacific/Auckland'],
+           ['Pacific/Chatham'],
+           ['Pacific/Chuuk'],
+           ['Pacific/Easter'],
+           ['Pacific/Efate'],
+           ['Pacific/Enderbury'],
+           ['Pacific/Fakaofo'],
+           ['Pacific/Fiji'],
+           ['Pacific/Funafuti'],
+           ['Pacific/Galapagos'],
+           ['Pacific/Gambier'],
+           ['Pacific/Guadalcanal'],
+           ['Pacific/Guam'],
+           ['Pacific/Honolulu'],
+           ['Pacific/Johnston'],
+           ['Pacific/Kiritimati'],
+           ['Pacific/Kosrae'],
+           ['Pacific/Kwajalein'],
+           ['Pacific/Majuro'],
+           ['Pacific/Marquesas'],
+           ['Pacific/Midway'],
+           ['Pacific/Nauru'],
+           ['Pacific/Niue'],
+           ['Pacific/Norfolk'],
+           ['Pacific/Noumea'],
+           ['Pacific/Pago_Pago'],
+           ['Pacific/Palau'],
+           ['Pacific/Pitcairn'],
+           ['Pacific/Pohnpei'],
+           ['Pacific/Port_Moresby'],
+           ['Pacific/Rarotonga'],
+           ['Pacific/Saipan'],
+           ['Pacific/Tahiti'],
+           ['Pacific/Tarawa'],
+           ['Pacific/Tongatapu'],
+           ['Pacific/Wake'],
+           ['Pacific/Wallis']
+       ]
+});
diff --git a/data/UpdateQueue.js b/data/UpdateQueue.js
new file mode 100644 (file)
index 0000000..19bd3df
--- /dev/null
@@ -0,0 +1,67 @@
+// Serialize load (avoid too many parallel connections)
+Ext.define('Proxmox.data.UpdateQueue', {
+    singleton: true,
+
+    constructor : function(){
+        var me = this;
+
+       var queue = [];
+       var queue_idx = {};
+
+       var idle = true;
+
+       var start_update = function() {
+           if (!idle) {
+               return;
+           }
+
+           var storeid = queue.shift();
+           if (!storeid) {
+               return;
+           }
+           var info = queue_idx[storeid];
+           queue_idx[storeid] = null;
+
+           info.updatestart = new Date();
+
+           idle = false;
+           info.store.load({
+               callback: function(records, operation, success) {
+                   idle = true;
+                   if (info.callback) {
+                       var runtime = (new Date()).getTime() - info.updatestart.getTime();
+                       info.callback(runtime, success);
+                   }
+                   start_update();
+               }
+           });
+       };
+
+       Ext.apply(me, {
+           queue: function(store, cb) {
+               var storeid = store.storeid;
+               if (!storeid) {
+                   throw "unable to queue store without storeid";
+               }
+               if (!queue_idx[storeid]) {
+                   queue_idx[storeid] = {
+                       store: store,
+                       callback: cb
+                   };
+                   queue.push(storeid);
+               }
+               start_update();
+           },
+           unqueue: function(store) {
+               var storeid = store.storeid;
+               if (!storeid) {
+                   throw "unabel to unqueue store without storeid";
+               }
+               if (queue_idx[storeid]) {
+                   Ext.Array.remove(queue,storeid);
+                   queue_idx[storeid] = null;
+               }
+           }
+       });
+    }
+});
diff --git a/data/UpdateStore.js b/data/UpdateStore.js
new file mode 100644 (file)
index 0000000..0fb3dad
--- /dev/null
@@ -0,0 +1,64 @@
+/* Extends the Ext.data.Store type
+ * with  startUpdate() and stopUpdate() methods
+ * to refresh the store data in the background
+ * Components using this store directly will flicker
+ * due to the redisplay of the element ater 'config.interval' ms
+ *
+ * Note that you have to call yourself startUpdate() for the background load
+ * to begin
+ */
+Ext.define('Proxmox.data.UpdateStore', {
+    extend: 'Ext.data.Store',
+
+    isStopped: true,
+
+    constructor: function(config) {
+       var me = this;
+
+       config = config || {};
+
+       if (!config.interval) {
+           config.interval = 3000;
+       }
+
+       if (!config.storeid) {
+           throw "no storeid specified";
+       }
+
+       var load_task = new Ext.util.DelayedTask();
+
+       var run_load_task = function() {
+           if (me.isStopped) {
+               return;
+           }
+
+           if (Proxmox.Utils.authOK()) {
+               Proxmox.data.UpdateQueue.queue(me, function(runtime, success) {
+                   var interval = config.interval + runtime*2;
+                   load_task.delay(interval, run_load_task);
+               });
+           } else {
+               load_task.delay(200, run_load_task);
+           }
+       };
+
+       Ext.apply(config, {
+           startUpdate: function() {
+               me.isStopped = false;
+               run_load_task();
+           },
+           stopUpdate: function() {
+               me.isStopped = true;
+               load_task.cancel();
+               Proxmox.data.UpdateQueue.unqueue(me);
+           }
+       });
+
+       me.callParent([config]);
+
+       me.on('destroy', function() {
+           load_task.cancel();
+           Proxmox.data.UpdateQueue.unqueue(me);
+       });
+    }
+});
diff --git a/data/reader/JsonObject.js b/data/reader/JsonObject.js
new file mode 100644 (file)
index 0000000..db2646c
--- /dev/null
@@ -0,0 +1,127 @@
+/* A reader to store a single JSON Object (hash) into a storage.
+ * Also accepts an array containing a single hash. 
+ *
+ * So it can read:
+ *
+ * example1: {data1: "xyz", data2: "abc"} 
+ * returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
+ *
+ * example2: [ {data1: "xyz", data2: "abc"} ] 
+ * returns [{key: "data1", value: "xyz"}, {key: "data2", value: "abc"}]
+ *
+ * If you set 'readArray', the reader expexts the object as array:
+ *
+ * example3: [ { key: "data1", value: "xyz", p2: "cde" },  { key: "data2", value: "abc", p2: "efg" }]
+ * returns [{key: "data1", value: "xyz", p2: "cde}, {key: "data2", value: "abc", p2: "efg"}]
+ *
+ * Note: The records can contain additional properties (like 'p2' above) when you use 'readArray'
+ *
+ * Additional feature: specify allowed properties with default values with 'rows' object
+ *
+ * var rows = {
+ *   memory: {
+ *     required: true,
+ *     defaultValue: 512
+ *   }
+ * }
+ *
+ */
+
+Ext.define('Proxmox.data.reader.JsonObject', {
+    extend: 'Ext.data.reader.Json',
+    alias : 'reader.jsonobject',
+    
+    readArray: false,
+
+    rows: undefined,
+
+    constructor: function(config) {
+        var me = this;
+
+        Ext.apply(me, config || {});
+
+       me.callParent([config]);
+    },
+
+    getResponseData: function(response) {
+       var me = this;
+
+       var data = [];
+        try {
+        var result = Ext.decode(response.responseText);
+        // get our data items inside the server response
+        var root = result[me.getRootProperty()];
+
+           if (me.readArray) {
+
+               var rec_hash = {};
+               Ext.Array.each(root, function(rec) {
+                   if (Ext.isDefined(rec.key)) {
+                       rec_hash[rec.key] = rec;
+                   }
+               });
+
+               if (me.rows) {
+                   Ext.Object.each(me.rows, function(key, rowdef) {
+                       var rec = rec_hash[key];
+                       if (Ext.isDefined(rec)) {
+                           if (!Ext.isDefined(rec.value)) {
+                               rec.value = rowdef.defaultValue;
+                           }
+                           data.push(rec);
+                       } else if (Ext.isDefined(rowdef.defaultValue)) {
+                           data.push({key: key, value: rowdef.defaultValue} );
+                       } else if (rowdef.required) {
+                           data.push({key: key, value: undefined });
+                       }
+                   });
+               } else {
+                   Ext.Array.each(root, function(rec) {
+                       if (Ext.isDefined(rec.key)) {
+                           data.push(rec);
+                       }
+                   });
+               }
+               
+           } else { 
+               
+               var org_root = root;
+
+               if (Ext.isArray(org_root)) {
+                   if (root.length == 1) {
+                       root = org_root[0];
+                   } else {
+                       root = {};
+                   }
+               }
+
+               if (me.rows) {
+                   Ext.Object.each(me.rows, function(key, rowdef) {
+                       if (Ext.isDefined(root[key])) {
+                           data.push({key: key, value: root[key]});
+                       } else if (Ext.isDefined(rowdef.defaultValue)) {
+                           data.push({key: key, value: rowdef.defaultValue});
+                       } else if (rowdef.required) {
+                           data.push({key: key, value: undefined});
+                       }
+                   });
+               } else {
+                   Ext.Object.each(root, function(key, value) {
+                       data.push({key: key, value: value });
+                   });
+               }
+           }
+       }
+        catch (ex) {
+            Ext.Error.raise({
+                response: response,
+                json: response.responseText,
+                parseError: ex,
+                msg: 'Unable to parse the JSON returned by the server: ' + ex.toString()
+            });
+        }
+
+       return data;
+    }
+});
+