From: Dietmar Maurer Date: Mon, 30 Jan 2017 08:45:41 +0000 (+0100) Subject: initial import X-Git-Url: https://git.proxmox.com/?p=proxmox-widget-toolkit.git;a=commitdiff_plain;h=0bb29d3556d127e3f32967915dce0981f92deab4 initial import copied from pve-manager, remove pve-manager specific code and rename 'pve' to 'proxmox'. --- 0bb29d3556d127e3f32967915dce0981f92deab4 diff --git a/Utils.js b/Utils.js new file mode 100644 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 += "
"; + Ext.Object.each(result.errors, function(prop, desc) { + msg += "
" + Ext.htmlEncode(prop) + ": " + + 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 index 0000000..22b84a6 --- /dev/null +++ b/data/DiffStore.js @@ -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 index 0000000..1e3771f --- /dev/null +++ b/data/ObjectStore.js @@ -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 index 0000000..6fe1303 --- /dev/null +++ b/data/ProxmoxProxy.js @@ -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 index 0000000..2b26f23 --- /dev/null +++ b/data/TimezoneStore.js @@ -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 index 0000000..19bd3df --- /dev/null +++ b/data/UpdateQueue.js @@ -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 index 0000000..0fb3dad --- /dev/null +++ b/data/UpdateStore.js @@ -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 index 0000000..db2646c --- /dev/null +++ b/data/reader/JsonObject.js @@ -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; + } +}); +