From: Jordan Augé Date: Sun, 6 Jul 2014 17:56:52 +0000 (+0200) Subject: Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into onelab X-Git-Tag: myslice-1.1~60 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=d81f5f5ca87b6eba05adb93cd27ac3c9952cc294;hp=ec5bde32fc8594f89a5b07035d39bffc4608cb0d;p=myslice.git Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into onelab Conflicts: portal/sliceresourceview.py --- diff --git a/README.manifold-tables b/README.manifold-tables new file mode 100644 index 00000000..6852dee0 --- /dev/null +++ b/README.manifold-tables @@ -0,0 +1 @@ +manifold-tables -A -o authority -f \* -a R -j CACHE diff --git a/manifoldapi/static/js/hashtable.js b/manifoldapi/static/js/hashtable.js new file mode 100644 index 00000000..3922671f --- /dev/null +++ b/manifoldapi/static/js/hashtable.js @@ -0,0 +1,402 @@ +/** + * @license jahashtable, a JavaScript implementation of a hash table. It creates a single constructor function called + * Hashtable in the global scope. + * + * http://www.timdown.co.uk/jshashtable/ + * Copyright %%build:year%% Tim Down. + * Version: %%build:version%% + * Build date: %%build:date%% + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +var Hashtable = (function(UNDEFINED) { + var FUNCTION = "function", STRING = "string", UNDEF = "undefined"; + + // Require Array.prototype.splice, Object.prototype.hasOwnProperty and encodeURIComponent. In environments not + // having these (e.g. IE <= 5), we bail out now and leave Hashtable null. + if (typeof encodeURIComponent == UNDEF || + Array.prototype.splice === UNDEFINED || + Object.prototype.hasOwnProperty === UNDEFINED) { + return null; + } + + function toStr(obj) { + return (typeof obj == STRING) ? obj : "" + obj; + } + + function hashObject(obj) { + var hashCode; + if (typeof obj == STRING) { + return obj; + } else if (typeof obj.hashCode == FUNCTION) { + // Check the hashCode method really has returned a string + hashCode = obj.hashCode(); + return (typeof hashCode == STRING) ? hashCode : hashObject(hashCode); + } else { + return toStr(obj); + } + } + + function merge(o1, o2) { + for (var i in o2) { + if (o2.hasOwnProperty(i)) { + o1[i] = o2[i]; + } + } + } + + function equals_fixedValueHasEquals(fixedValue, variableValue) { + return fixedValue.equals(variableValue); + } + + function equals_fixedValueNoEquals(fixedValue, variableValue) { + return (typeof variableValue.equals == FUNCTION) ? + variableValue.equals(fixedValue) : (fixedValue === variableValue); + } + + function createKeyValCheck(kvStr) { + return function(kv) { + if (kv === null) { + throw new Error("null is not a valid " + kvStr); + } else if (kv === UNDEFINED) { + throw new Error(kvStr + " must not be undefined"); + } + }; + } + + var checkKey = createKeyValCheck("key"), checkValue = createKeyValCheck("value"); + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Bucket(hash, firstKey, firstValue, equalityFunction) { + this[0] = hash; + this.entries = []; + this.addEntry(firstKey, firstValue); + + if (equalityFunction !== null) { + this.getEqualityFunction = function() { + return equalityFunction; + }; + } + } + + var EXISTENCE = 0, ENTRY = 1, ENTRY_INDEX_AND_VALUE = 2; + + function createBucketSearcher(mode) { + return function(key) { + var i = this.entries.length, entry, equals = this.getEqualityFunction(key); + while (i--) { + entry = this.entries[i]; + if ( equals(key, entry[0]) ) { + switch (mode) { + case EXISTENCE: + return true; + case ENTRY: + return entry; + case ENTRY_INDEX_AND_VALUE: + return [ i, entry[1] ]; + } + } + } + return false; + }; + } + + function createBucketLister(entryProperty) { + return function(aggregatedArr) { + var startIndex = aggregatedArr.length; + for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { + aggregatedArr[startIndex + i] = entries[i][entryProperty]; + } + }; + } + + Bucket.prototype = { + getEqualityFunction: function(searchValue) { + return (typeof searchValue.equals == FUNCTION) ? equals_fixedValueHasEquals : equals_fixedValueNoEquals; + }, + + getEntryForKey: createBucketSearcher(ENTRY), + + getEntryAndIndexForKey: createBucketSearcher(ENTRY_INDEX_AND_VALUE), + + removeEntryForKey: function(key) { + var result = this.getEntryAndIndexForKey(key); + if (result) { + this.entries.splice(result[0], 1); + return result[1]; + } + return null; + }, + + addEntry: function(key, value) { + this.entries.push( [key, value] ); + }, + + keys: createBucketLister(0), + + values: createBucketLister(1), + + getEntries: function(destEntries) { + var startIndex = destEntries.length; + for (var i = 0, entries = this.entries, len = entries.length; i < len; ++i) { + // Clone the entry stored in the bucket before adding to array + destEntries[startIndex + i] = entries[i].slice(0); + } + }, + + containsKey: createBucketSearcher(EXISTENCE), + + containsValue: function(value) { + var entries = this.entries, i = entries.length; + while (i--) { + if ( value === entries[i][1] ) { + return true; + } + } + return false; + } + }; + + /*----------------------------------------------------------------------------------------------------------------*/ + + // Supporting functions for searching hashtable buckets + + function searchBuckets(buckets, hash) { + var i = buckets.length, bucket; + while (i--) { + bucket = buckets[i]; + if (hash === bucket[0]) { + return i; + } + } + return null; + } + + function getBucketForHash(bucketsByHash, hash) { + var bucket = bucketsByHash[hash]; + + // Check that this is a genuine bucket and not something inherited from the bucketsByHash's prototype + return ( bucket && (bucket instanceof Bucket) ) ? bucket : null; + } + + /*----------------------------------------------------------------------------------------------------------------*/ + + function Hashtable() { + var buckets = []; + var bucketsByHash = {}; + var properties = { + replaceDuplicateKey: true, + hashCode: hashObject, + equals: null + }; + + var arg0 = arguments[0], arg1 = arguments[1]; + if (arg1 !== UNDEFINED) { + properties.hashCode = arg0; + properties.equals = arg1; + } else if (arg0 !== UNDEFINED) { + merge(properties, arg0); + } + + var hashCode = properties.hashCode, equals = properties.equals; + + this.properties = properties; + + this.put = function(key, value) { + checkKey(key); + checkValue(value); + var hash = hashCode(key), bucket, bucketEntry, oldValue = null; + + // Check if a bucket exists for the bucket key + bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it already contains this key + bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so replace the old value. + // Also, we optionally replace the key so that the latest key is stored. + if (properties.replaceDuplicateKey) { + bucketEntry[0] = key; + } + oldValue = bucketEntry[1]; + bucketEntry[1] = value; + } else { + // The bucket does not contain an entry for this key, so add one + bucket.addEntry(key, value); + } + } else { + // No bucket exists for the key, so create one and put our key/value mapping in + bucket = new Bucket(hash, key, value, equals); + buckets.push(bucket); + bucketsByHash[hash] = bucket; + } + return oldValue; + }; + + this.get = function(key) { + checkKey(key); + + var hash = hashCode(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + if (bucket) { + // Check this bucket to see if it contains this key + var bucketEntry = bucket.getEntryForKey(key); + if (bucketEntry) { + // This bucket entry is the current mapping of key to value, so return the value. + return bucketEntry[1]; + } + } + return null; + }; + + this.containsKey = function(key) { + checkKey(key); + var bucketKey = hashCode(key); + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, bucketKey); + + return bucket ? bucket.containsKey(key) : false; + }; + + this.containsValue = function(value) { + checkValue(value); + var i = buckets.length; + while (i--) { + if (buckets[i].containsValue(value)) { + return true; + } + } + return false; + }; + + this.clear = function() { + buckets.length = 0; + bucketsByHash = {}; + }; + + this.isEmpty = function() { + return !buckets.length; + }; + + var createBucketAggregator = function(bucketFuncName) { + return function() { + var aggregated = [], i = buckets.length; + while (i--) { + buckets[i][bucketFuncName](aggregated); + } + return aggregated; + }; + }; + + this.keys = createBucketAggregator("keys"); + this.values = createBucketAggregator("values"); + this.entries = createBucketAggregator("getEntries"); + + this.remove = function(key) { + checkKey(key); + + var hash = hashCode(key), bucketIndex, oldValue = null; + + // Check if a bucket exists for the bucket key + var bucket = getBucketForHash(bucketsByHash, hash); + + if (bucket) { + // Remove entry from this bucket for this key + oldValue = bucket.removeEntryForKey(key); + if (oldValue !== null) { + // Entry was removed, so check if bucket is empty + if (bucket.entries.length == 0) { + // Bucket is empty, so remove it from the bucket collections + bucketIndex = searchBuckets(buckets, hash); + buckets.splice(bucketIndex, 1); + delete bucketsByHash[hash]; + } + } + } + return oldValue; + }; + + this.size = function() { + var total = 0, i = buckets.length; + while (i--) { + total += buckets[i].entries.length; + } + return total; + }; + } + + Hashtable.prototype = { + each: function(callback) { + var entries = this.entries(), i = entries.length, entry; + while (i--) { + entry = entries[i]; + callback(entry[0], entry[1]); + } + }, + + equals: function(hashtable) { + var keys, key, val, count = this.size(); + if (count == hashtable.size()) { + keys = this.keys(); + while (count--) { + key = keys[count]; + val = hashtable.get(key); + if (val === null || val !== this.get(key)) { + return false; + } + } + return true; + } + return false; + }, + + putAll: function(hashtable, conflictCallback) { + var entries = hashtable.entries(); + var entry, key, value, thisValue, i = entries.length; + var hasConflictCallback = (typeof conflictCallback == FUNCTION); + while (i--) { + entry = entries[i]; + key = entry[0]; + value = entry[1]; + + // Check for a conflict. The default behaviour is to overwrite the value for an existing key + if ( hasConflictCallback && (thisValue = this.get(key)) ) { + value = conflictCallback(key, thisValue, value); + } + this.put(key, value); + } + }, + + clone: function() { + var clone = new Hashtable(this.properties); + clone.putAll(this); + return clone; + } + }; + + Hashtable.prototype.toQueryString = function() { + var entries = this.entries(), i = entries.length, entry; + var parts = []; + while (i--) { + entry = entries[i]; + parts[i] = encodeURIComponent( toStr(entry[0]) ) + "=" + encodeURIComponent( toStr(entry[1]) ); + } + return parts.join("&"); + }; + + return Hashtable; +})(); diff --git a/manifoldapi/static/js/manifold-query.js b/manifoldapi/static/js/manifold-query.js index 3116f84d..64eb3a7a 100644 --- a/manifoldapi/static/js/manifold-query.js +++ b/manifoldapi/static/js/manifold-query.js @@ -1,3 +1,15 @@ +var guid = (function() { + function s4() { + return Math.floor((1 + Math.random()) * 0x10000) + .toString(16) + .substring(1); + } + return function() { + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + + s4() + '-' + s4() + s4() + s4(); + }; +})(); + function ManifoldQuery(action, object, timestamp, filters, params, fields, unique, query_uuid, aq, sq) { // get, update, delete, create var action; @@ -131,7 +143,7 @@ INSERT INTO object VALUES(field=value) }); }; - if (this.analyzed_query !== undefined) + if (!!this.analyzed_query) query = this.analyzed_query; else query = this; @@ -257,7 +269,10 @@ INSERT INTO object VALUES(field=value) else this.unique = unique; - this.query_uuid = query_uuid; + if (typeof unique == "undefined") + this.query_uuid = guid(); + else + this.query_uuid = query_uuid; if (typeof aq == "undefined") this.analyzed_query = null; diff --git a/manifoldapi/static/js/manifold.js b/manifoldapi/static/js/manifold.js index fa5415a8..b6d2d210 100644 --- a/manifoldapi/static/js/manifold.js +++ b/manifoldapi/static/js/manifold.js @@ -38,27 +38,71 @@ var FIELD_REMOVED = 5; var CLEAR_FIELDS = 6; var NEW_RECORD = 7; var CLEAR_RECORDS = 8; + +/** + * event: FIELD_STATE_CHANGED + * + * Parameters: + * dict : + * .request : ???? used to be FIELD_REQUEST_ADD / FIELD_REQUEST_REMOVE + * .key : ??? the key fields of the record + * .value : the key of the record who has received an update + * .status : the new state of the record + * TODO rename to state, and use values from STATE_SET + */ var FIELD_STATE_CHANGED = 9; var IN_PROGRESS = 101; var DONE = 102; /* Update requests related to subqueries */ + +/** + * event: SET_ADD + * + * Parameters: + * string : The key of the element being added + */ var SET_ADD = 201; + +/** + * event: SET_REMOVED + * + * Parameters: + * string : The key of the element being removed + */ var SET_REMOVED = 202; + // request var FIELD_REQUEST_CHANGE = 301; var FIELD_REQUEST_ADD = 302; var FIELD_REQUEST_REMOVE = 303; var FIELD_REQUEST_ADD_RESET = 304; var FIELD_REQUEST_REMOVE_RESET = 305; -// status +// status (XXX Should be deprecated) var FIELD_REQUEST_PENDING = 401; var FIELD_REQUEST_SUCCESS = 402; var FIELD_REQUEST_FAILURE = 403; +var STATUS_OKAY = 404; +var STATUS_SET_WARNING = 405; +var STATUS_ADD_WARNING = 406; +var STATUS_REMOVE_WARNING = 407; +var STATUS_RESET = 408; + +/* Requests for query cycle */ +var RUN_UPDATE = 601; + +/* MANIFOLD types */ +var TYPE_VALUE = 1; +var TYPE_RECORD = 2; +var TYPE_LIST_OF_VALUES = 3; +var TYPE_LIST_OF_RECORDS = 4; + +/****************************************************************************** + * QUERY STATUS (for manifold events) + ******************************************************************************/ -/* Query status */ var STATUS_NONE = 500; // Query has not been started yet var STATUS_GET_IN_PROGRESS = 501; // Query has been sent, no result has been received var STATUS_GET_RECEIVED = 502; // Success @@ -68,19 +112,47 @@ var STATUS_UPDATE_IN_PROGRESS = 505; var STATUS_UPDATE_RECEIVED = 506; var STATUS_UPDATE_ERROR = 507; -/* Requests for query cycle */ -var RUN_UPDATE = 601; +/****************************************************************************** + * QUERY STATE (for query_store) + ******************************************************************************/ -/* MANIFOLD types */ -var TYPE_VALUE = 1; -var TYPE_RECORD = 2; -var TYPE_LIST_OF_VALUES = 3; -var TYPE_LIST_OF_RECORDS = 4; +// XXX Rendundant with query status ? + +var QUERY_STATE_INIT = 0; +var QUERY_STATE_INPROGRESS = 1; +var QUERY_STATE_DONE = 2; + +/****************************************************************************** + * RECORD STATES (for query_store) + ******************************************************************************/ + +var STATE_SET = 0; +var STATE_WARNINGS = 1; +var STATE_VISIBLE = 2; + +// STATE_SET : enum +var STATE_SET_IN = 0; +var STATE_SET_OUT = 1; +var STATE_SET_IN_PENDING = 2; +var STATE_SET_OUT_PENDING = 3; +var STATE_SET_IN_SUCCESS = 4; +var STATE_SET_OUT_SUCCESS = 5; +var STATE_SET_IN_FAILURE = 6; +var STATE_SET_OUT_FAILURE = 7; + +// STATE_WARNINGS : dict + +// STATE_VISIBLE : boolean + +/****************************************************************************** + * CONSTRAINTS + ******************************************************************************/ +var CONSTRAINT_RESERVABLE_LEASE = 0; // A structure for storing queries -function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled) { +function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, disabled, domain_query_ext) { /* Constructor */ if (typeof query == "undefined") @@ -90,10 +162,36 @@ function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, dis this.main_query_ext = (typeof main_query_ext == "undefined") ? null : main_query_ext; this.update_query_ext = (typeof update_query_ext == "undefined") ? null : update_query_ext; this.update_query_orig_ext = (typeof update_query_orig_ext == "undefined") ? null : update_query_orig_ext; - this.disabled = (typeof update_query_ext == "undefined") ? false : disabled; + this.disabled = (typeof disabled == "undefined") ? false : disabled; + + // A domain query is a query that is issued to retrieve all possible values for a set + // eg. all resources that can be attached to a slice + // It is null unless we are a subquery for which a domain query has been issued + this.domain_query_ext = (typeof domain_query_ext == "undefined") ? null : domain_query_ext; + + // Set members to buffer until the domain query is completed + // A list of keys + this.set_members = []; + + // The set query is the query for which the domain query has been issued. + // It is null unless the query is a domain query + this.set_query_ext = (typeof set_query_ext == "undefined") ? null : domain_query_ext; - this.status = null; - this.results = null; + this.query_state = QUERY_STATE_INIT; + + // Results from a query consists in a dict that maps keys to records + this.records = new Hashtable(); + + // Status is a dict that maps keys to record status + this.state = new Hashtable(); + + // Filters that impact visibility in the local interface + this.filters = []; + + // XXX Until we find a better solution + this.num_pending = 0; + this.num_unconfigured = 0; + // update_query null unless we are a main_query (aka parent_query == null); only main_query_fields can be updated... } @@ -142,13 +240,37 @@ function QueryStore() { // We also need to insert all queries and subqueries from the analyzed_query // XXX We need the root of all subqueries query.iter_subqueries(function(sq, data, parent_query) { - if (parent_query) + var parent_query_ext; + if (parent_query) { parent_query_ext = manifold.query_store.find_analyzed_query_ext(parent_query.query_uuid); - else + } else { parent_query_ext = null; + } // XXX parent_query_ext == false // XXX main.subqueries = {} # Normal, we need analyzed_query sq_ext = new QueryExt(sq, parent_query_ext, query_ext) + + if (parent_query) { + /* Let's issue a query for the subquery domain. This query will not need any update etc. + eg. for resources in a slice, we also query all resources */ + var all_fields = manifold.metadata.get_field_names(sq.object); + var domain_query = new ManifoldQuery('get', sq.object, 'now', [], {}, all_fields); + //var domain_query = new ManifoldQuery('get', sq.object); + + console.log("Created domain query", domain_query); + var domain_query_ext = new QueryExt(domain_query); + + domain_query_ext.set_query_ext = sq_ext; + sq_ext.domain_query_ext = domain_query_ext; + + // One of these two is useless ? + manifold.query_store.main_queries[domain_query.query_uuid] = domain_query_ext; + manifold.query_store.analyzed_queries[domain_query.query_uuid] = domain_query_ext; + + // XXX This query is run before the plugins are initialized and listening + manifold.run_query(domain_query); + } + manifold.query_store.analyzed_queries[sq.query_uuid] = sq_ext; }); @@ -157,21 +279,343 @@ function QueryStore() { /* Searching */ - this.find_query_ext = function(query_uuid) { + this.find_query_ext = function(query_uuid) + { return this.main_queries[query_uuid]; } - this.find_query = function(query_uuid) { + this.find_query = function(query_uuid) + { return this.find_query_ext(query_uuid).query; } - this.find_analyzed_query_ext = function(query_uuid) { + this.find_analyzed_query_ext = function(query_uuid) + { return this.analyzed_queries[query_uuid]; } - this.find_analyzed_query = function(query_uuid) { + this.find_analyzed_query = function(query_uuid) + { return this.find_analyzed_query_ext(query_uuid).query; } + + this.state_dict_create = function(default_set) + { + default_set = (default_set === undefined) ? STATE_SET_OUT : default_set; + var state_dict = {}; + // We cannot use constants in literal definition, so... + state_dict[STATE_WARNINGS] = {}; + state_dict[STATE_SET] = default_set; + state_dict[STATE_VISIBLE] = true; + return state_dict; + } + + // RECORDS + + this.set_records = function(query_uuid, records, default_set) + { + default_set = (default_set === undefined) ? STATE_SET_OUT : default_set; + + var self = this; + var query_ext = this.find_analyzed_query_ext(query_uuid); + var record_key = manifold.metadata.get_key(query_ext.query.object); + $.each(records, function(i, record) { + var key = manifold.metadata.get_key(query_ext.query.object); + // ["start_time", "resource", "end_time"] + // ["urn"] + + var record_key_value = manifold.record_get_value(record, record_key); + query_ext.records.put(record_key_value, record); + + if (!(query_ext.state.get(record_key_value))) + query_ext.state.put(record_key_value, self.state_dict_create(default_set)); + }); + } + + this.get_records = function(query_uuid) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + return query_ext.records.values(); + } + + this.get_record = function(query_uuid, record_key) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + return query_ext.records.get(record_key); + } + + this.add_record = function(query_uuid, record, new_state) + { + var query_ext, key, record_key; + query_ext = this.find_analyzed_query_ext(query_uuid); + + if (typeof(record) == 'object') { + key = manifold.metadata.get_key(query_ext.query.object); + record_key = manifold.record_get_value(record, key); + } else { + record_key = record; + } + + var record_entry = query_ext.records.get(record_key); + if (!record_entry) + query_ext.records.put(record_key, record); + + manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state); + } + + this.remove_record = function(query_uuid, record, new_state) + { + var query_ext, key, record_key; + query_ext = this.find_analyzed_query_ext(query_uuid); + + if (typeof(record) == 'object') { + key = manifold.metadata.get_key(query_ext.query.object); + record_key = manifold.record_get_value(record, key); + } else { + record_key = record; + } + + manifold.query_store.set_record_state(query_uuid, record_key, STATE_SET, new_state); + } + + this.iter_records = function(query_uuid, callback) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + query_ext.records.each(callback); + //callback = function(record_key, record) + } + + this.iter_visible_records = function(query_uuid, callback) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + query_ext.records.each(function(record_key, record) { + if (query_ext.state.get(record_key)[STATE_VISIBLE]) // .STATE_VISIBLE would be for the string key + callback(record_key, record); + }); + //callback = function(record_key, record) + + } + + // STATE + + this.set_record_state = function(query_uuid, result_key, state, value) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + var state_dict = query_ext.state.get(result_key); + if (!state_dict) + state_dict = this.state_dict_create(); + + state_dict[state] = value; + + query_ext.state.put(result_key, state_dict); + } + + this.get_record_state = function(query_uuid, result_key, state) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + var state_dict = query_ext.state.get(result_key); + if (!state_dict) + return null; + return state_dict[state]; + } + + // FILTERS + + this.add_filter = function(query_uuid, filter) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + // XXX When we update a filter + query_ext.filters.push(filter); + + this.apply_filters(query_uuid); + + } + + this.update_filter = function(query_uuid, filter) + { + // XXX + + this.apply_filters(query_uuid); + } + + this.remove_filter = function(query_uuid, filter) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + query_ext.filters = $.grep(query_ext.filters, function(x) { + return x == filter; + }); + + this.apply_filters(query_uuid); + } + + this.get_filters = function(query_uuid) + { + var query_ext = this.find_analyzed_query_ext(query_uuid); + return query_ext.filters; + } + + this.recount = function(query_uuid) + { + var query_ext; + var is_reserved, is_pending, in_set, is_unconfigured; + + query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid); + query_ext.num_pending = 0; + query_ext.num_unconfigured = 0; + + this.iter_records(query_uuid, function(record_key, record) { + var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET); + var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS); + + is_reserved = (record_state == STATE_SET_IN) + || (record_state == STATE_SET_OUT_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); + + is_pending = (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_OUT_PENDING); + + in_set = (record_state == STATE_SET_IN) // should not have warnings + || (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings + + is_unconfigured = (in_set && !$.isEmptyObject(record_warnings)); + + /* Let's update num_pending and num_unconfigured at this stage */ + if (is_pending) + query_ext.num_pending++; + if (is_unconfigured) + query_ext.num_unconfigured++; + }); + + } + + this.apply_filters = function(query_uuid) + { + var start = new Date().getTime(); + + // Toggle visibility of records according to the different filters. + + var self = this; + var filters = this.get_filters(query_uuid); + var col_value; + /* Let's update num_pending and num_unconfigured at this stage */ + + // Adapted from querytable._querytable_filter() + + this.iter_records(query_uuid, function(record_key, record) { + var is_reserved, is_pending, in_set, is_unconfigured; + var visible = true; + + var record_state = manifold.query_store.get_record_state(query_uuid, record_key, STATE_SET); + var record_warnings = manifold.query_store.get_record_state(query_uuid, record_key, STATE_WARNINGS); + + is_reserved = (record_state == STATE_SET_IN) + || (record_state == STATE_SET_OUT_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); + + is_pending = (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_OUT_PENDING); + + in_set = (record_state == STATE_SET_IN) // should not have warnings + || (record_state == STATE_SET_IN_PENDING) + || (record_state == STATE_SET_IN_SUCCESS) + || (record_state == STATE_SET_OUT_FAILURE); // should not have warnings + + is_unconfigured = (in_set && !$.isEmptyObject(record_warnings)); + + // We go through each filter and decide whether it affects the visibility of the record + $.each(filters, function(index, filter) { + var key = filter[0]; + var op = filter[1]; + var value = filter[2]; + + + /* We do some special handling for the manifold:status filter + * predicates. */ + + if (key == 'manifold:status') { + if (op != '=' && op != '==') { + // Unsupported filter, let's ignore it + console.log("Unsupported filter on manifold:status. Should be EQUAL only."); + return true; // ~ continue + } + + switch (value) { + case 'reserved': + // true => ~ continue + // false => ~ break + visible = is_reserved; + return visible; + case 'unconfigured': + visible = is_unconfigured; + return visible; + case 'pending': + visible = is_pending; + return visible; + } + return false; // ~ break + } + + /* Normal filtering behaviour (according to the record content) follows... */ + col_value = manifold.record_get_value(record, key); + + // When the filter does not match, we hide the column by default + if (col_value === 'undefined') { + visible = false; + return false; // ~ break + } + + // XXX This should accept pluggable filtering functions. + + + /* Test whether current filter is compatible with the column */ + if (op == '=' || op == '==') { + if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + }else if (op == 'included') { + $.each(value, function(i,x) { + if(x == col_value){ + visible = true; + return false; // ~ break + }else{ + visible = false; + } + }); + }else if (op == '!=') { + if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='<') { + if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='>') { + if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='<=' || op=='≤') { + if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + } else if(op=='>=' || op=='≥') { + if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a") + visible = false; + }else{ + // How to break out of a loop ? + alert("filter not supported"); + return false; // break + } + + }); + + // Set the visibility status in the query store + self.set_record_state(query_uuid, record_key, STATE_VISIBLE, visible); + }); + + var end = new Date().getTime(); + console.log("APPLY FILTERS took", end - start, "ms"); + + } + } /*! @@ -201,6 +645,108 @@ var manifold = { } }, + /** + * Args: + * fields: A String instance (field name), or a set of String instances + * (field names) # XXX tuple !! + * Returns: + * If fields is a String, return the corresponding value. + * If fields is a set, return a tuple of corresponding value. + * + * Raises: + * KeyError if at least one of the fields is not found + */ + record_get_value: function(record, fields) + { + if (typeof(fields) === 'string') { + if (fields.indexOf('.') != -1) { + key_subkey = key.split('.', 2); + key = key_subkey[0]; + subkey = key_subkey[1]; + + if (record.indexOf(key) == -1) { + return null; + } + // Tests if the following is an array (typeof would give object) + if (Object.prototype.toString.call(record[key]) === '[object Array]') { + // Records + return $.map(record[key], function(subrecord) { return manifold.record_get_value(subrecord, subkey) }); + } else if (typeof(record) == 'object') { + // Record + return manifold.record_get_value(record[key], subkey); + } else { + console.log('Unknown field'); + } + } else { + return record[fields]; + } + } else { + // see. get_map_entries + if (fields.length == 1) + return manifold.record_get_value(record, fields[0]) + + // Build a new record + var ret = {}; + $.each(fields, function(i, field) { + ret[field] = manifold.record_get_value(record, field); + }); + ret.hashCode = record.hashCode; + ret.equals = record.equals; + return ret; + // this was an array, we want a dictionary + //return $.map(fields, function(x) { manifold.record_get_value(record, x) }); + + } + }, + + record_hashcode: function(key_fields) + { + return function() { + ret = ""; + for (i=0; i < key_fields.length; i++) + ret += "@@" + this[key_fields[i]]; + return ret; + }; + }, + + record_equals: function(key_fields) + { + var self = this; + + return function(other) { + for (i=0; i < key_fields.length; i++) { + var this_value = this[key_fields[i]]; + var other_value = other[key_fields[i]]; + + var this_type = self.get_type(this_value); + var other_type = self.get_type(other_value); + if (this_type != other_type) + return false; + + switch (this_type) { + case TYPE_VALUE: + case TYPE_LIST_OF_VALUES: + if (this_value != other_value) + return false; + break; + case TYPE_RECORD: + if (!(record_equals(this_value, other_value))) + return false; + break; + case TYPE_LIST_OF_RECORDS: + if (this_value.length != other_value.length) + return false; + for (i = 0; i < this_value.length; i++) + if (!(record_equals(this_value, other_value))) + return false; + break; + } + } + return true; + }; + }, + + /************************************************************************** * Metadata management **************************************************************************/ @@ -221,6 +767,14 @@ var manifold = { return (typeof table.column === 'undefined') ? null : table.column; }, + get_field_names: function(method) + { + var columns = this.get_columns(method); + if (!columns) + return null; + return $.map(columns, function (x) { return x.name }); + }, + get_key: function(method) { var table = this.get_table(method); if (!table) @@ -247,6 +801,12 @@ var manifold = { if (!table) return null; + var match = $.grep(table.column, function(x) { return x.name == name }); + if (match.length == 0) { + return undefined; + } else { + return match[0].type; + } return (typeof table.type === 'undefined') ? null : table.type; } @@ -277,6 +837,11 @@ var manifold = { // NEW API manifold.query_store.insert(query); + // Run + $(document).ready(function() { + manifold.run_query(query); + }); + // FORMER API if (query.analyzed_query == null) { query.analyze_subqueries(); @@ -316,21 +881,30 @@ var manifold = { } }, - run_query: function(query, callback) { + run_query: function(query, callback) + { // default value for callback = null if (typeof callback === 'undefined') callback = null; + var query_ext = manifold.query_store.find_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_INPROGRESS; + var query_json = JSON.stringify(query); - /* Nothing related to pubsub here... for the moment at least. */ - //query.iter_subqueries(function (sq) { - // manifold.raise_record_event(sq.query_uuid, IN_PROGRESS); - //}); + // Inform plugins about the progress + query.iter_subqueries(function (sq) { + var sq_query_ext = manifold.query_store.find_analyzed_query_ext(sq.query_uuid); + sq_query_ext.query_state = QUERY_STATE_INPROGRESS; + + manifold.raise_record_event(sq.query_uuid, IN_PROGRESS); + }); + $.post(manifold.proxy_url, {'json': query_json} , manifold.success_closure(query, null, callback)); }, + // XXX DEPRECATED // Executes all async. queries - intended for the javascript header to initialize queries // input queries are specified as a list of {'query_uuid': } // each plugin is responsible for managing its spinner through on_query_in_progress @@ -421,6 +995,29 @@ var manifold = { messages.debug(".. publish_result (5) END q=" + query.__repr()); }, + store_records: function(query, records) { + // Store records + var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid); + if (query_ext.set_query_ext) { + // We have a domain query + // The results are stored in the corresponding set_query + manifold.query_store.set_records(query_ext.set_query_ext.query.query_uuid, records) + + } else if (query_ext.domain_query_ext) { + // We have a set query, it is only used to determine which objects are in the set, we should only retrieve the key + // Has it a domain query, and has it completed ? + $.each(records, function(i, record) { + var key = manifold.metadata.get_key(query.object); + var record_key = manifold.record_get_value(record, key); + manifold.query_store.set_record_state(query.query_uuid, record_key, STATE_SET, STATE_SET_IN); + }); + + } else { + // We have a normal query + manifold.query_store.set_records(query.query_uuid, records, STATE_SET_IN); + } + }, + /*! * Recursively publish result * \fn publish_result_rec(query, result) @@ -430,24 +1027,43 @@ var manifold = { * * Note: this function works on the analyzed query */ - publish_result_rec: function(query, result) { + publish_result_rec: function(query, records) { /* If the result is not unique, only publish the top query; * otherwise, publish the main object as well as subqueries * XXX how much recursive are we ? */ if (manifold.pubsub_debug) - messages.debug (">>>>> publish_result_rec " + query.object); + messages.debug (">>>>> publish_result_rec " + query.object); if (manifold.query_expects_unique_result(query)) { /* Also publish subqueries */ $.each(query.subqueries, function(object, subquery) { - manifold.publish_result_rec(subquery, result[0][object]); + manifold.publish_result_rec(subquery, records[0][object]); /* TODO remove object from result */ }); } if (manifold.pubsub_debug) messages.debug ("===== publish_result_rec " + query.object); - manifold.publish_result(query, result); + var query_ext = manifold.query_store.find_analyzed_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_DONE; + + this.store_records(query, records); + + var pub_query; + + if (query_ext.set_query_ext) { + if (query_ext.set_query_ext.query_state != QUERY_STATE_DONE) + return; + pub_query = query_ext.set_query_ext.query; + } else if (query_ext.domain_query_ext) { + if (query_ext.domain_query_ext.query_state != QUERY_STATE_DONE) + return; + pub_query = query; + } else { + pub_query = query; + } + // We can only publish results if the query (and its related domain query) is complete + manifold.publish_result(pub_query, records); if (manifold.pubsub_debug) messages.debug ("<<<<< publish_result_rec " + query.object); @@ -462,6 +1078,11 @@ var manifold = { var record = records[0]; var update_query_ext = query_ext.update_query_ext; + + console.log("Update case not handled yet!"); + if (!update_query_ext) + return; + var update_query = update_query_ext.query; var update_query_ext = query_ext.update_query_ext; var update_query_orig = query_ext.update_query_orig_ext.query; @@ -480,13 +1101,7 @@ var manifold = { if (!subrecords) continue $.each(subrecords, function (i, subrecord) { - if (key.length == 1){ - key = key[0]; - sq_keys.push(subrecord[key]); - }else{ - // more than what's necessary, but should work - sq_keys.push(subrecord); - } + sq_keys.push(manifold.record_get_value(subrecord, key)); }); update_query.params[method] = sq_keys; update_query_orig.params[method] = sq_keys.slice(); @@ -502,10 +1117,47 @@ var manifold = { process_get_query_records: function(query, records) { this.setup_update_query(query, records); + + var query_ext = manifold.query_store.find_query_ext(query.query_uuid); + query_ext.query_state = QUERY_STATE_DONE; /* Publish full results */ - var tmp_query = manifold.find_query(query.query_uuid); - manifold.publish_result_rec(tmp_query.analyzed_query, records); + var tmp_query = manifold.query_store.find_analyzed_query(query.query_uuid); + manifold.publish_result_rec(tmp_query, records); + }, + + make_records: function(object, records) + { + $.each(records, function(i, record) { + manifold.make_record(object, record); + }); + }, + + make_record: function(object, record) + { + // To make an object a record, we just add the hash function + var key = manifold.metadata.get_key(object); + record.hashCode = manifold.record_hashcode(key.sort()); + record.equals = manifold.record_equals(key); + + // Looking after subrecords + for (var field in record) { + var result_value = record[field]; + + switch (this.get_type(result_value)) { + case TYPE_RECORD: + var subobject = manifold.metadata.get_type(object, field); + // if (subobject) XXX Bugs with fields declared string while they are not : network.version is a dict in fact + if (subobject && subobject != 'string') + manifold.make_record(subobject, result_value); + break; + case TYPE_LIST_OF_RECORDS: + var subobject = manifold.metadata.get_type(object, field); + if (subobject) + manifold.make_records(subobject, result_value); + break; + } + } }, /** @@ -523,6 +1175,21 @@ var manifold = { * previous 'process_get_query_records' function. */ process_update_query_records: function(query, records) { + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX + // XXX XXX XXX XXX // First issue: we request everything, and not only what we modify, so will will have to ignore some fields var query_uuid = query.query_uuid; var query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid); @@ -534,7 +1201,8 @@ var manifold = { // Let's iterate over the object properties for (var field in record) { - switch (this.get_type(record[field])) { + var result_value = record[field]; + switch (this.get_type(result_value)) { case TYPE_VALUE: // Did we ask for a change ? var update_value = update_query[field]; @@ -544,7 +1212,6 @@ var manifold = { // We assume it won't have changed continue; - var result_value = record[field]; if (!result_value) throw "Internal error"; @@ -563,7 +1230,6 @@ var manifold = { case TYPE_LIST_OF_VALUES: // Same as list of records, but we don't have to extract keys - var result_keys = record[field] // The rest of exactly the same (XXX factorize) var update_keys = update_query_orig.params[field]; @@ -573,7 +1239,7 @@ var manifold = { $.each(added_keys, function(i, key) { - if ($.inArray(key, result_keys) == -1) { + if ($.inArray(key, result_value) == -1) { data = { request: FIELD_REQUEST_ADD, key : field, @@ -626,7 +1292,7 @@ var manifold = { key = key[0]; /* XXX should be modified for multiple keys */ - var result_keys = $.map(record[field], function(x) { return x[key]; }); + var result_keys = $.map(record[field], function(x) { return manifold.record_get_value(x, key); }); var update_keys = update_query_orig.params[field]; var query_keys = update_query.params[field]; @@ -735,6 +1401,7 @@ var manifold = { var result=data.value; if (result) { /* Eventually update the content of related queries (update, etc) */ + manifold.make_records(query.object, result); this.process_query_records(query, result); /* Publish results: disabled here, done in the previous call */ @@ -789,11 +1456,14 @@ var manifold = { raise_event: function(query_uuid, event_type, value) { + var query, query_ext; + // Query uuid has been updated with the key of a new element query_ext = manifold.query_store.find_analyzed_query_ext(query_uuid); query = query_ext.query; switch(event_type) { + case FIELD_STATE_CHANGED: // value is an object (request, key, value, status) // update is only possible is the query is not pending, etc @@ -855,14 +1525,179 @@ var manifold = { }); value.key = value_key; + manifold.query_store.recount(cur_query.query_uuid); manifold.raise_record_event(cur_query.query_uuid, event_type, value); + // XXX make this DOT a global variable... could be '/' break; case SET_ADD: case SET_REMOVED: - + + /* An object has been added to / removed from a set : its + * status become pending or reset to the original state. We + * update the record status in the analyzed queries. + * + * XXX Shall we update something in the main_query ? + */ + var prev_state, new_state; + + prev_state = manifold.query_store.get_record_state(query_uuid, value, STATE_SET); + if (prev_state === null) + prev_state = STATE_SET_OUT; + + if (event_type == SET_ADD) { + switch (prev_state) { + case STATE_SET_OUT: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + new_state = STATE_SET_IN_PENDING; + break; + + case STATE_SET_OUT_PENDING: + new_state = STATE_SET_IN; + break; + + case STATE_SET_IN: + case STATE_SET_IN_PENDING: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + console.log("Inconsistent state: already in"); + return; + } + } else { // SET_REMOVE + switch (prev_state) { + case STATE_SET_IN: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + new_state = STATE_SET_OUT_PENDING; + break; + + case STATE_SET_IN_PENDING: + new_state = STATE_SET_OUT; + break; + + case STATE_SET_OUT: + case STATE_SET_OUT_PENDING: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + console.log("Inconsistent state: already out"); + return; + } + } + + + var resource_key = value; + + if (event_type == SET_ADD) + manifold.query_store.add_record(query_uuid, resource_key, new_state); + else + manifold.query_store.remove_record(query_uuid, resource_key, new_state); + + var record = manifold.query_store.get_record(query_uuid, resource_key); + + /* CONSTRAINTS */ + + // XXX When we add a lease we must update the warnings + + switch(query.object) { + + case 'resource': + // CONSTRAINT_RESERVABLE_LEASE + // + // +) If a reservable node is added to the slice, then it should have a corresponding lease + // XXX Not always a resource + var is_reservable = (record.exclusive == true); + if (is_reservable) { + var warnings = manifold.query_store.get_record_state(query_uuid, resource_key, STATE_WARNINGS); + + if (event_type == SET_ADD) { + // We should have a lease_query associated + var lease_query = query_ext.parent_query_ext.query.subqueries['lease']; // in options + var lease_query_ext = manifold.query_store.find_analyzed_query_ext(lease_query.query_uuid); + // Do we have lease records with this resource + var lease_records = $.grep(lease_query_ext.records, function(lease_key, lease) { + return lease['resource'] == value; + }); + if (lease_records.length == 0) { + // Sets a warning + // XXX Need for a better function to manage warnings + var warn = "No lease defined for this reservable resource."; + warnings[CONSTRAINT_RESERVABLE_LEASE] = warn; + } else { + // Lease are defined, delete the warning in case it was set previously + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } + } else { + // Remove warnings attached to this resource + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } + + manifold.query_store.set_record_state(query_uuid, resource_key, STATE_WARNINGS, warnings); + break; + } + + // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark) + data = { + request: null, + key : null, + value : resource_key, + status: STATE_WARNINGS + }; + manifold.raise_record_event(query_uuid, FIELD_STATE_CHANGED, data); + + case 'lease': + /* + var resource_key = record.resource; + var resource_query = query_ext.parent_query_ext.query.subqueries['resource']; + var warnings = manifold.query_store.get_record_state(resource_query.query_uuid, resource_key, STATE_WARNINGS); + + if (event_type == SET_ADD) { + // A lease is added, it removes the constraint + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } else { + // A lease is removed, it might trigger the warning + var lease_records = $.grep(query_ext.records, function(lease_key, lease) { + return lease['resource'] == value; + }); + if (lease_records.length == 0) { // XXX redundant cases + // Sets a warning + // XXX Need for a better function to manage warnings + var warn = "No lease defined for this reservable resource."; + warnings[CONSTRAINT_RESERVABLE_LEASE] = warn; + } else { + // Lease are defined, delete the warning in case it was set previously + delete warnings[CONSTRAINT_RESERVABLE_LEASE]; + } + + } + + // Signal the change to plugins (even if the constraint does not apply, so that the plugin can display a checkmark) + data = { + request: null, + key : null, + value : resource_key, + status: STATE_WARNINGS + }; + manifold.raise_record_event(resource_query.query_uuid, FIELD_STATE_CHANGED, data); + break; + */ + } + + + // -) When a lease is added, it might remove the warning associated to a reservable node + + // If a NITOS node is reserved, then at least a NITOS channel should be reserved + // - When a NITOS channel is added, it might remove a warning associated to all NITOS nodes + + // If a NITOS channel is reserved, then at least a NITOS node should be reserved + // - When a NITOS node is added, it might remove a warning associated to all NITOS channels + + // A lease is present while the resource has been removed => Require warnings on nodes not in set ! + + /* END CONSTRAINTS */ + // update is only possible is the query is not pending, etc // CHECK status ! @@ -888,7 +1723,7 @@ var manifold = { request: (event_type == SET_ADD) ? FIELD_REQUEST_ADD : FIELD_REQUEST_REMOVE, key : path, value : value, - status: FIELD_REQUEST_PENDING, + status: STATE_SET, // XXX used to be FIELD_REQUEST_PENDING, and not new_state }; this.raise_event(main_query.query_uuid, FIELD_STATE_CHANGED, data); @@ -911,12 +1746,26 @@ var manifold = { manifold.run_query(query_ext.main_query_ext.update_query_ext.query); break; + /* FILTERS */ + case FILTER_ADDED: + /* Update internal record state */ + manifold.query_store.add_filter(query_uuid, value); + + /* Propagate the message to plugins */ manifold.raise_query_event(query_uuid, event_type, value); + break; + case FILTER_REMOVED: + /* Update internal record state */ + manifold.query_store.remove_filter(query_uuid, value); + + /* Propagate the message to plugins */ manifold.raise_query_event(query_uuid, event_type, value); + break; + case FIELD_ADDED: main_query = query_ext.main_query_ext.query; main_update_query = query_ext.main_query_ext.update_query; diff --git a/manifoldapi/static/js/plugin.js b/manifoldapi/static/js/plugin.js index 24d41129..76e1cb52 100644 --- a/manifoldapi/static/js/plugin.js +++ b/manifoldapi/static/js/plugin.js @@ -1,3 +1,24 @@ +// Common parts for angularjs plugins +// only one ng-app is allowed + +var ManifoldApp = angular.module('ManifoldApp', []); +ManifoldApp.config(function ($interpolateProvider) { + $interpolateProvider.startSymbol('{[{').endSymbol('}]}'); +}); + +ManifoldApp.factory('$exceptionHandler', function () { + return function (exception, cause) { + console.log(exception.message); + }; +}); + +ManifoldApp.filter('offset', function() { + return function(input, start) { + start = parseInt(start, 10); + return input.slice(start); + }; +}); + // INHERITANCE // http://alexsexton.com/blog/2010/02/using-inheritance-patterns-to-organize-large-jquery-applications/ // We will use John Resig's proposal @@ -220,7 +241,7 @@ var Plugin = Class.extend({ id_from_key: function(key_field, value) { - return key_field + manifold.separator + this.escape_id(value).replace(/\\/g, ''); + return key_field + manifold.separator + this.escape_id(value); //.replace(/\\/g, ''); }, // NOTE diff --git a/plugins/apply/__init__.py b/plugins/apply/__init__.py new file mode 100644 index 00000000..ceb3c9ad --- /dev/null +++ b/plugins/apply/__init__.py @@ -0,0 +1,48 @@ +from unfold.plugin import Plugin +from plugins.queryupdater import QueryUpdaterPlugin + +class ApplyPlugin(Plugin): + + def __init__ (self, query=None, **settings): + Plugin.__init__ (self, **settings) + self.query = query + + def template_file (self): + return "apply.html" + + def template_env(self, request): + query_updater = QueryUpdaterPlugin( + page = self.page, + title = 'Pending operations', + query = self.query, + togglable = False, + # start turned off, it will open up itself when stuff comes in + toggled = True, + domid = 'pending', + outline_complete = True, + username = request.user, # XXX ??? + ) + + env = Plugin.template_env(self, request) + env.update({'query_updater': query_updater.render(request)}) + return env + + def requirements (self): + reqs = { + 'js_files' : [ + 'js/apply.js' + ], + 'css_files' : [ + 'css/apply.css' + ], + } + return reqs + + def json_settings_list (self): + # query_uuid will pass self.query results to the javascript + # and will be available as "record" in : + # on_new_record: function(record) + return ['plugin_uuid', 'domid', 'query_uuid'] + + def export_json_settings (self): + return True diff --git a/plugins/apply/static/css/apply.css b/plugins/apply/static/css/apply.css new file mode 100644 index 00000000..97b2d59b --- /dev/null +++ b/plugins/apply/static/css/apply.css @@ -0,0 +1,3 @@ +.modal-dialog-large { + width: 800px; +} diff --git a/plugins/apply/static/js/apply.js b/plugins/apply/static/js/apply.js new file mode 100644 index 00000000..47dc8d84 --- /dev/null +++ b/plugins/apply/static/js/apply.js @@ -0,0 +1,36 @@ +/** + * TestbedsPlugin: List of testbeds plugin + * Version: 0.1 + * Description: TODO -> generalize to a list of possible filters + * This file is part of the Manifold project + * Requires: js/plugin.js + * URL: http://www.myslice.info + * Author: Loïc Baron + * Copyright: Copyright 2012-2013 UPMC Sorbonne Universités + * License: GPLv3 + */ + +(function($){ + + var ApplyPlugin = Plugin.extend({ + + /** + * @brief Plugin constructor + * @param options : an associative array of setting values + * @param element : + * @return : a jQuery collection of objects on which the plugin is + * applied, which allows to maintain chainability of calls + */ + init: function(options, element) + { + // Call the parent constructor, see FAQ when forgotten + this._super(options, element); + }, + + + }) + + /* Plugin registration */ + $.plugin('ApplyPlugin', ApplyPlugin); + +})(jQuery); diff --git a/plugins/apply/templates/apply.html b/plugins/apply/templates/apply.html new file mode 100644 index 00000000..2e5bab02 --- /dev/null +++ b/plugins/apply/templates/apply.html @@ -0,0 +1,24 @@ +
+ + + + + + + +
diff --git a/plugins/filter_status/__init__.py b/plugins/filter_status/__init__.py new file mode 100644 index 00000000..a0d269b3 --- /dev/null +++ b/plugins/filter_status/__init__.py @@ -0,0 +1,30 @@ +from unfold.plugin import Plugin + +class FilterStatusPlugin(Plugin): + + def __init__ (self, query=None, **settings): + Plugin.__init__ (self, **settings) + self.query = query + + def template_file (self): + return "filter_status.html" + + def requirements (self): + reqs = { + 'js_files' : [ + 'js/filter_status.js' + ], +# 'css_files': [ +# 'css/myplugin.css', +# ] + } + return reqs + + def json_settings_list (self): + # query_uuid will pass self.query results to the javascript + # and will be available as "record" in : + # on_new_record: function(record) + return ['plugin_uuid', 'domid', 'query_uuid'] + + def export_json_settings (self): + return True diff --git a/plugins/filter_status/static/js/filter_status.js b/plugins/filter_status/static/js/filter_status.js new file mode 100644 index 00000000..d5a13be6 --- /dev/null +++ b/plugins/filter_status/static/js/filter_status.js @@ -0,0 +1,137 @@ +/** + * TestbedsPlugin: List of testbeds plugin + * Version: 0.1 + * Description: TODO -> generalize to a list of possible filters + * This file is part of the Manifold project + * Requires: js/plugin.js + * URL: http://www.myslice.info + * Author: Loïc Baron + * Copyright: Copyright 2012-2013 UPMC Sorbonne Universités + * License: GPLv3 + */ + +(function($){ + + var FilterStatusPlugin = Plugin.extend({ + + /** + * @brief Plugin constructor + * @param options : an associative array of setting values + * @param element : + * @return : a jQuery collection of objects on which the plugin is + * applied, which allows to maintain chainability of calls + */ + init: function(options, element) + { + // Call the parent constructor, see FAQ when forgotten + this._super(options, element); + + /* Setup query and record handlers */ + this.listen_query(options.query_uuid); + + /* Setup click handlers */ + this.elts('list-group-item').click({'instance': this}, this._on_click); + + this.prev_filter_status = null; + + /* Initialize tooltips */ + $("[rel='tooltip']").tooltip(); + + }, + + /************************************************************************** + * GUI MANAGEMENT * + **************************************************************************/ + + select_tab: function(tab) + { + this.elts('list-group-item').removeClass('active'); + this.elmt(tab).addClass('active'); + }, + + /************************************************************************** + * EVENT HANDLERS * + **************************************************************************/ + + // These functions are here to react on external filters, which we don't + // use at the moment + + on_filter_added: function(filter) + { + // XXX + }, + + on_filter_removed: function(filter) + { + // XXX + }, + + on_field_state_changed: function(data) + { + var query_ext; + + switch (data.status) { + case STATE_SET: + /* Get the number of pending / unconfigured resources */ + /* Let's store it in query_ext */ + query_ext = manifold.query_store.find_analyzed_query_ext(this.options.query_uuid); + + $('#badge-pending').text(query_ext.num_pending); + if (query_ext.num_pending > 0) { + $('#badge-pending').show(); + } else { + $('#badge-pending').hide(); + } + + $('#badge-unconfigured').text(query_ext.num_unconfigured); + if (query_ext.num_unconfigured > 0) { + $('#badge-unconfigured').show(); + } else { + $('#badge-unconfigured').hide(); + } + default: + break; + } + }, + + /************************************************************************** + * PRIVATE METHODS * + **************************************************************************/ + + /** + * @brief : Click event handler + */ + _on_click: function(e) + { + var filter_status; + var filter; + + // A pointer to the plugin instance, since 'this' is overriden here + self = e.data.instance; + + + // Select the tab... + filter_status = this.dataset['status']; + self.select_tab(filter_status); + + // ... and communicate the appropriate filters to the manager + // NOTE: we use the manifold namespace for internal filters + if (self.prev_filter_status) + manifold.raise_event(self.options.query_uuid, FILTER_REMOVED, self.prev_filter_status); + + // XXX The datatables will be refreshed twice ! + if (filter_status != 'all') { + // No filter for 'all' + var filter = ['manifold:status', '==', filter_status]; + manifold.raise_event(self.options.query_uuid, FILTER_ADDED, filter); + } + + self.prev_filter_status = filter_status; + }, + + }); + + /* Plugin registration */ + $.plugin('FilterStatusPlugin', FilterStatusPlugin); + +})(jQuery); diff --git a/plugins/filter_status/templates/filter_status.html b/plugins/filter_status/templates/filter_status.html new file mode 100644 index 00000000..b6ce25da --- /dev/null +++ b/plugins/filter_status/templates/filter_status.html @@ -0,0 +1,43 @@ + diff --git a/plugins/googlemap/__init__.py b/plugins/googlemap/__init__.py index a769f7f8..08f2a7bc 100644 --- a/plugins/googlemap/__init__.py +++ b/plugins/googlemap/__init__.py @@ -8,12 +8,10 @@ class GoogleMap (Plugin): # googlemap_key : mandatory googlemap API v3 key # latitude,longitude, zoom : the starting point # apparently at some point there has been support for a boolean 'checkboxes' input arg but seems dropped - def __init__ (self, query, query_all, googlemap_api_key=None, latitude=43., longitude=7., zoom=4, **settings): + def __init__ (self, query, googlemap_api_key=None, latitude=43., longitude=7., zoom=4, **settings): Plugin.__init__ (self, **settings) self.query=query - self.query_all = query_all self.googlemap_api_key=googlemap_api_key - self.query_all_uuid = query_all.query_uuid if query_all else None self.latitude=latitude self.longitude=longitude self.zoom=zoom @@ -45,7 +43,7 @@ class GoogleMap (Plugin): # the list of things passed to the js plugin def json_settings_list (self): - return [ 'plugin_uuid', 'query_uuid', 'query_all_uuid', + return [ 'plugin_uuid', 'query_uuid', 'init_key', 'latitude', 'longitude', 'zoom', ] diff --git a/plugins/maddash/static/css/maddash.css b/plugins/maddash/static/css/maddash.css index 9b8abbce..d591a5b0 100644 --- a/plugins/maddash/static/css/maddash.css +++ b/plugins/maddash/static/css/maddash.css @@ -21,7 +21,7 @@ .gtop{} .gsubcell{float:left;} rect{fill:none;} -.tooltip{text-align:left;font-size:12px;} +.Btooltip{text-align:left;font-size:12px;} .tooltip .top-tip{padding-bottom:2px;border-bottom:1px solid #fff} .tooltip .bottom-tip{padding-top:2px;} .legends{ diff --git a/plugins/querytable/__init__.py b/plugins/querytable/__init__.py index 5a80bb9f..2182e709 100644 --- a/plugins/querytable/__init__.py +++ b/plugins/querytable/__init__.py @@ -33,6 +33,12 @@ Current implementation makes the following assumptions as we use 'aoColumnDefs' instead. """ + MAP = { + 'network_hrn' : 'Testbed', + 'hostname' : 'Resource name', + 'type' : 'Type', + } + def __init__ (self, query=None, query_all=None, checkboxes=False, columns=None, init_key=None, @@ -43,20 +49,28 @@ Current implementation makes the following assumptions self.query_all = query_all self.query_all_uuid = query_all.query_uuid if query_all else None self.checkboxes = checkboxes + # XXX We need to have some hidden columns until we properly handle dynamic queries if columns is not None: - self.columns=columns - self.hidden_columns = [] + _columns = columns + _hidden_columns = [] elif self.query: - self.columns = self.query.fields + _columns = [field for field in self.query.fields if not field == 'urn'] if query_all: # We need a list because sets are not JSON-serializable - self.hidden_columns = list(self.query_all.fields - self.query.fields) + _hidden_columns = list(self.query_all.fields - self.query.fields) + _hidden_columns.append('urn') else: - self.hidden_columns = [] + _hidden_columns = [] else: - self.columns = [] - self.hidden_columns = [] + _columns = [] + _hidden_columns = [] + + print "_columns=", _columns + self.columns = { self.MAP.get(c, c) : c for c in _columns } + self.hidden_columns = { self.MAP.get(c, c) : c for c in _hidden_columns } + print "self.columns", self.columns + self.init_key=init_key self.datatables_options=datatables_options # if checkboxes were required, we tell datatables about this column's type @@ -96,7 +110,7 @@ Current implementation makes the following assumptions # hopefully temporary, when/if datatables supports sPaginationType=bootstrap3 # for now we use full_numbers, with our own ad hoc css #"css/dataTables.full_numbers.css", - #"css/querytable.css" , + "css/querytable.css" , ], } return reqs diff --git a/plugins/querytable/static/css/querytable.css b/plugins/querytable/static/css/querytable.css index a42ab1fe..68793219 100644 --- a/plugins/querytable/static/css/querytable.css +++ b/plugins/querytable/static/css/querytable.css @@ -1,61 +1,61 @@ -/* the bottom of the datatable needs more space */ -div.querytable-spacer { padding: 8px 4px 15px 4px; } - -div.QueryTable table.dataTable th { - font: bold 12px/22px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; - color: #4f6b72; - border-right: 1px solid #C1DAD7; - border-bottom: 1px solid #C1DAD7; - border-top: 1px solid #C1DAD7; - letter-spacing: 1px; - text-transform: uppercase; - text-align: left; - padding: 8px 12px 4px 20px; - vertical-align:middle; -/* background: #CAE8EA url(../img/tablesort-header.jpg) no-repeat; */ -} - -div.QueryTable table.dataTable th.checkbox { -} - -div.QueryTable table.dataTable td, div.QueryTable table.dataTable textarea, div.QueryTable table.dataTable input [type="text"] { - font: normal 12px "Trebuchet MS", Verdana, Arial, Helvetica, sans-serif; - border-right: 1px solid #C1DAD7; - border-bottom: 1px solid #C1DAD7; -} -div.QueryTable table.dataTable td { - padding: 4px 8px 4px 8px; - /* this applies on even rows only, odd ones have a setting in bootstrap of rbg 249.249.249 */ - background-color: #f4f4f4; -} -div.QueryTable table.dataTable td a { - font-weight:normal; -} -/* these come from bootstrap */ -div.QueryTable div.dataTables_info { - font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; -} - -/* one could think or using repeat-x here but that's not working because of the arrows - * we might need to make these wider some day - * and/or to add background-color: #caebea - * which would look less conspicuous in case of overflow -*/ - -div.QueryTable table.dataTable thead .sorting { background: url('../img/tablesort-header-sortable.png') no-repeat; } -div.QueryTable table.dataTable thead .sorting_asc { background: url('../img/tablesort-header-up.png') no-repeat; } -div.QueryTable table.dataTable thead .sorting_desc { background: url('../img/tablesort-header-down.png') no-repeat; } -/* this icons set does not have that exact equivalent - using an approximation for now */ -div.QueryTable table.dataTable thead .sorting_asc_disabled { background: url('../img/tablesort-header.png') repeat-x; } -div.QueryTable table.dataTable thead .sorting_desc_disabled { background: url('../img/tablesort-header.png') repeat-x; } - -/* the footers are not active */ -div.QueryTable table.dataTable tfoot { - background: url('../img/tablesort-header.png') repeat-x; - background-color: #caebea; -} -/* and when sorting is turned off it's useful to set this on header too */ -div.QueryTable table.dataTable thead { - background: url('../img/tablesort-header.png') repeat-x; - background-color: #caebea; +tr.even { + background-color: #FAFAFA; +} +div .added { + background-color: #FFFF99; +} + +div .removed { + background-color: #E8E8E8; +} + +.sidebar-nav { + padding: 9px 0; +} + +.dropdown-menu .sub-menu { + left: 100%; + top: 0; + position: absolute; + visibility: hidden; + margin-top: -1px; +} + +.dropdown-menu li:hover .sub-menu { + visibility: visible; +} + +.dropdown:hover .dropdown-menu { + display: block; +} + +.nav-tabs .dropdown-menu, .nav-pills .dropdown-menu, .navbar .dropdown-menu { + margin-top: 0; +} + +.navbar .sub-menu:before { + border-bottom: 7px solid transparent; + border-left: none; + border-right: 7px solid rgba(0, 0, 0, 0.2); + border-top: 7px solid transparent; + left: -7px; + top: 10px; +} +.navbar .sub-menu:after { + border-top: 6px solid transparent; + border-left: none; + border-right: 6px solid #fff; + border-bottom: 6px solid transparent; + left: 10px; + top: 11px; + left: -6px; +} + +.dropdown-menu-right { + left: auto; /* Reset the default from `.dropdown-menu` */ + right: 0; +} + +.nav > li > a.nopadding { + padding: 0 0; } diff --git a/plugins/querytable/static/js/querytable.js b/plugins/querytable/static/js/querytable.js index 97fa4b45..47e0a97a 100644 --- a/plugins/querytable/static/js/querytable.js +++ b/plugins/querytable/static/js/querytable.js @@ -4,8 +4,19 @@ * License: GPLv3 */ +BGCOLOR_RESET = 0; +BGCOLOR_ADDED = 1; +BGCOLOR_REMOVED = 2; + (function($){ + + var QUERYTABLE_MAP = { + 'Testbed': 'network_hrn', + 'Resource name': 'hostname', + 'Type': 'type', + }; + var debug=false; // debug=true @@ -20,17 +31,11 @@ // query_uuid refers to a single object (typically a slice) // query_all_uuid refers to a list (typically resources or users) // these can return in any order so we keep track of which has been received yet - this.received_all_query = false; this.received_query = false; // We need to remember the active filter for datatables filtering this.filters = Array(); - // an internal buffer for records that are 'in' and thus need to be checked - this.buffered_records_to_check = []; - // an internal buffer for keeping lines and display them in one call to fnAddData - this.buffered_lines = []; - /* Events */ // xx somehow non of these triggers at all for now this.elmt().on('show', this, this.on_show); @@ -60,7 +65,7 @@ /* Setup query and record handlers */ this.listen_query(options.query_uuid); - this.listen_query(options.query_all_uuid, 'all'); + //this.listen_query(options.query_all_uuid, 'all'); /* GUI setup and event binding */ this.initialize_table(); @@ -103,7 +108,24 @@ // WARNING: this one causes tables in a 'tabs' that are not exposed at the time this is run to show up empty // sScrollX: '100%', /* Horizontal scrolling */ bProcessing: true, /* Loading */ - fnDrawCallback: function() { self._querytable_draw_callback.call(self); } + fnDrawCallback: function() { self._querytable_draw_callback.call(self); }, + fnRowCallback: function( nRow, aData, iDisplayIndex, iDisplayIndexFull ) { + // This function is called on fnAddData to set the TR id. What about fnUpdate ? + + // Get the key from the raw data array aData + var key = self.canonical_key; + + // Get the index of the key in the columns + var cols = self._get_columns(); + var index = self.getColIndex(key, cols); + if (index != -1) { + // The key is found in the table, set the TR id after the data + $(nRow).attr('id', self.id_from_key(key, aData[index])); + } + + // That's the usual return value + return nRow; + } // XXX use $.proxy here ! }; // the intention here is that options.datatables_options as coming from the python object take precedence @@ -156,7 +178,8 @@ * @param cols */ getColIndex: function(key, cols) { - var tabIndex = $.map(cols, function(x, i) { if (x.sTitle == key) return i; }); + var self = this; + var tabIndex = $.map(cols, function(x, i) { if (self._get_map(x.sTitle) == key) return i; }); return (tabIndex.length > 0) ? tabIndex[0] : -1; }, // getColIndex @@ -188,13 +211,15 @@ new_record: function(record) { + var self = this; + // this models a line in dataTables, each element in the line describes a cell line = new Array(); // go through table headers to get column names we want // in order (we have temporarily hack some adjustments in names) - var cols = this.table.fnSettings().aoColumns; - var colnames = cols.map(function(x) {return x.sTitle}) + var cols = this._get_columns(); + var colnames = cols.map(function(x) {return self._get_map(x.sTitle)}) var nb_col = cols.length; /* if we've requested checkboxes, then forget about the checkbox column for now */ //if (this.options.checkboxes) nb_col -= 1; @@ -203,9 +228,10 @@ // Use a key instead of hostname (hard coded...) line.push(this.checkbox_html(record)); } + line.push(''); // STATUS /* fill in stuff depending on the column name */ - for (var j = 1; j < nb_col; j++) { + for (var j = 2; j < nb_col - 1; j++) { // nb_col includes status if (typeof colnames[j] == 'undefined') { line.push('...'); } else if (colnames[j] == 'hostname') { @@ -238,11 +264,9 @@ } } - - // adding an array in one call is *much* more efficient // this.table.fnAddData(line); - this.buffered_lines.push(line); + return line; }, clear_table: function() @@ -276,11 +300,16 @@ // this is used at init-time, at which point only init_key can make sense // (because the argument record, if it comes from query, might not have canonical_key set set_checkbox_from_record: function (record, checked) { - if (checked === undefined) checked = true; + if (checked === undefined) checked = true; var init_id = record[this.init_key]; - if (debug) messages.debug("querytable.set_checkbox_from_record, init_id="+init_id); + this.set_checkbox_from_record_key(init_id, checked); + }, + + set_checkbox_from_record_key: function (record_key, checked) { + if (checked === undefined) checked = true; + if (debug) messages.debug("querytable.set_checkbox_from_record, record_key="+record_key); // using table.$ to search inside elements that are not visible - var element = this.table.$('[init_id="'+init_id+'"]'); + var element = this.table.$('[init_id="'+record_key+'"]'); element.attr('checked',checked); }, @@ -293,97 +322,138 @@ element.attr('checked',checked); }, - /*************************** QUERY HANDLER ****************************/ - - on_filter_added: function(filter) + /** + * Arguments + * + * key_value: the key from which we deduce the id + * request: STATUS_OKAY, etc. + * content: some HTML content + */ + change_status: function(key_value, warnings) { - this.filters.push(filter); - this.redraw_table(); - }, + var msg; + + if ($.isEmptyObject(warnings)) { + var state = manifold.query_store.get_record_state(this.options.query_uuid, key_value, STATE_SET); + switch(state) { + case STATE_SET_IN: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + case STATE_SET_IN_PENDING: + // Checkmark sign if no warning for an object in the set + msg = '✓'; + break; + default: + // Nothing is the object is not in the set + msg = ''; + break; + } + } else { + msg = ''; + } - on_filter_removed: function(filter) - { - // Remove corresponding filters - this.filters = $.grep(this.filters, function(x) { - return x == filter; - }); - this.redraw_table(); - }, - - on_filter_clear: function() - { - // XXX - this.redraw_table(); - }, + $(document.getElementById(this.id_from_key('status', key_value))).html(msg); + $('[data-toggle="tooltip"]').tooltip({'placement': 'bottom'}); - on_field_added: function(field) - { - this.show_column(field); }, - on_field_removed: function(field) + set_bgcolor: function(key_value, class_name) { - this.hide_column(field); + var elt = $(document.getElementById(this.id_from_key(this.canonical_key, key_value))) + if (class_name == BGCOLOR_RESET) + elt.removeClass('added removed'); + else + elt.addClass((class_name == BGCOLOR_ADDED ? 'added' : 'removed')); }, - on_field_clear: function() + populate_table: function() { - alert('QueryTable::clear_fields() not implemented'); + // Let's clear the table and only add lines that are visible + var self = this; + this.clear_table(); + + // XXX Here we have lost checkboxes + // set checkbox from record. + // only the current plugin known that we have an element in a set + + lines = Array(); + var record_keys = []; + manifold.query_store.iter_records(this.options.query_uuid, function (record_key, record) { + lines.push(self.new_record(record)); + record_keys.push(record_key); + }); + this.table.fnAddData(lines); + $.each(record_keys, function(i, record_key) { + var state = manifold.query_store.get_record_state(self.options.query_uuid, record_key, STATE_SET); + var warnings = manifold.query_store.get_record_state(self.options.query_uuid, record_key, STATE_WARNINGS); + switch(state) { + // XXX The row and checkbox still does not exists !!!! + case STATE_SET_IN: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + self.set_checkbox_from_record_key(record_key, true); + break; + case STATE_SET_OUT: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + //self.set_checkbox_from_record_key(record_key, false); + break; + case STATE_SET_IN_PENDING: + self.set_checkbox_from_record_key(record_key, true); + self.set_bgcolor(record_key, BGCOLOR_ADDED); + break; + case STATE_SET_OUT_PENDING: + //self.set_checkbox_from_record_key(record_key, false); + self.set_bgcolor(record_key, BGCOLOR_REMOVED); + break; + } + self.change_status(record_key, warnings); // XXX will retrieve status again + }); }, - /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */ - /*************************** ALL QUERY HANDLER ****************************/ + /*************************** QUERY HANDLER ****************************/ - on_all_filter_added: function(filter) + on_filter_added: function(filter) { - // XXX this.redraw_table(); }, - on_all_filter_removed: function(filter) + on_filter_removed: function(filter) { - // XXX this.redraw_table(); }, - on_all_filter_clear: function() + on_filter_clear: function() { - // XXX this.redraw_table(); }, - on_all_field_added: function(field) + on_field_added: function(field) { this.show_column(field); }, - on_all_field_removed: function(field) + on_field_removed: function(field) { this.hide_column(field); }, - on_all_field_clear: function() + on_field_clear: function() { alert('QueryTable::clear_fields() not implemented'); }, - /*************************** RECORD HANDLER ***************************/ - on_new_record: function(record) - { - if (this.received_all_query) { - // if the 'all' query has been dealt with already we may turn on the checkbox - this.set_checkbox_from_record(record, true); - } else { - this.buffered_records_to_check.push(record); - } - }, - - on_clear_records: function() - { - }, - // Could be the default in parent on_query_in_progress: function() { @@ -392,141 +462,79 @@ on_query_done: function() { - this.received_query = true; - // unspin once we have received both - if (this.received_all_query && this.received_query) this.unspin(); + this.populate_table(); + this.spin(false); }, on_field_state_changed: function(data) { - switch(data.request) { - case FIELD_REQUEST_ADD: - case FIELD_REQUEST_ADD_RESET: - this.set_checkbox_from_data(data.value, true); - break; - case FIELD_REQUEST_REMOVE: - case FIELD_REQUEST_REMOVE_RESET: - this.set_checkbox_from_data(data.value, false); - break; - default: + var state = manifold.query_store.get_record_state(this.options.query_uuid, data.value, data.status); + switch(data.status) { + case STATE_SET: + switch(state) { + case STATE_SET_IN: + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_FAILURE: + this.set_checkbox_from_data(data.value, true); + this.set_bgcolor(data.value, BGCOLOR_RESET); + break; + case STATE_SET_OUT: + case STATE_SET_OUT_SUCCESS: + case STATE_SET_IN_FAILURE: + this.set_checkbox_from_data(data.value, false); + this.set_bgcolor(data.value, BGCOLOR_RESET); + break; + case STATE_SET_IN_PENDING: + this.set_checkbox_from_data(data.value, true); + this.set_bgcolor(data.value, BGCOLOR_ADDED); + break; + case STATE_SET_OUT_PENDING: + this.set_checkbox_from_data(data.value, false); + this.set_bgcolor(data.value, BGCOLOR_REMOVED); + break; + } break; - } - }, - /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */ - // all - on_all_field_state_changed: function(data) - { - switch(data.request) { - case FIELD_REQUEST_ADD: - case FIELD_REQUEST_ADD_RESET: - this.set_checkbox_from_data(data.value, true); - break; - case FIELD_REQUEST_REMOVE: - case FIELD_REQUEST_REMOVE_RESET: - this.set_checkbox_from_data(data.value, false); - break; - default: + case STATE_WARNINGS: + this.change_status(data.value, state); break; } }, - on_all_new_record: function(record) - { - this.new_record(record); - }, + /************************** PRIVATE METHODS ***************************/ - on_all_clear_records: function() + _get_columns: function() { - this.clear_table(); - + return this.table.fnSettings().aoColumns; + // XXX return $.map(table.fnSettings().aoColumns, function(x, i) { return QUERYTABLE_MAP[x]; }); }, - on_all_query_in_progress: function() - { - // XXX parent - this.spin(); - }, // on_all_query_in_progress - - on_all_query_done: function() + _get_map: function(column_title) { + return (column_title in QUERYTABLE_MAP) ? QUERYTABLE_MAP[column_title] : column_title; + }, + /** + * @brief QueryTable filtering function, called for every line in the datatable. + * + * Return value: + * boolean determining whether the column is visible or not. + */ + _querytable_filter: function(oSettings, aData, iDataIndex) { - if (debug) messages.debug("1-shot initializing dataTables content with " + this.buffered_lines.length + " lines"); - this.table.fnAddData (this.buffered_lines); - this.buffered_lines=[]; - var self = this; - // if we've already received the slice query, we have not been able to set - // checkboxes on the fly at that time (dom not yet created) - $.each(this.buffered_records_to_check, function(i, record) { - if (debug) messages.debug ("querytable delayed turning on checkbox " + i + " record= " + record); - self.set_checkbox_from_record(record, true); - }); - this.buffered_records_to_check = []; + var key_col, record_key_value; - this.received_all_query = true; - // unspin once we have received both - if (this.received_all_query && this.received_query) this.unspin(); + /* Determine index of key in the table columns */ + key_col = $.map(oSettings.aoColumns, function(x, i) {if (self._get_map(x.sTitle) == self.canonical_key) return i;})[0]; - }, // on_all_query_done - - /************************** PRIVATE METHODS ***************************/ + /* Unknown key: no filtering */ + if (typeof(key_col) == 'undefined') { + console.log("Unknown key"); + return true; + } - /** - * @brief QueryTable filtering function - */ - _querytable_filter: function(oSettings, aData, iDataIndex) - { - var ret = true; - $.each (this.filters, function(index, filter) { - /* XXX How to manage checkbox ? */ - var key = filter[0]; - var op = filter[1]; - var value = filter[2]; - - /* Determine index of key in the table columns */ - var col = $.map(oSettings.aoColumns, function(x, i) {if (x.sTitle == key) return i;})[0]; - - /* Unknown key: no filtering */ - if (typeof(col) == 'undefined') - return; - - col_value=unfold.get_value(aData[col]); - /* Test whether current filter is compatible with the column */ - if (op == '=' || op == '==') { - if ( col_value != value || col_value==null || col_value=="" || col_value=="n/a") - ret = false; - }else if (op == 'included') { - $.each(value, function(i,x) { - if(x == col_value){ - ret = true; - return false; - }else{ - ret = false; - } - }); - }else if (op == '!=') { - if ( col_value == value || col_value==null || col_value=="" || col_value=="n/a") - ret = false; - } else if(op=='<') { - if ( parseFloat(col_value) >= value || col_value==null || col_value=="" || col_value=="n/a") - ret = false; - } else if(op=='>') { - if ( parseFloat(col_value) <= value || col_value==null || col_value=="" || col_value=="n/a") - ret = false; - } else if(op=='<=' || op=='≤') { - if ( parseFloat(col_value) > value || col_value==null || col_value=="" || col_value=="n/a") - ret = false; - } else if(op=='>=' || op=='≥') { - if ( parseFloat(col_value) < value || col_value==null || col_value=="" || col_value=="n/a") - ret = false; - }else{ - // How to break out of a loop ? - alert("filter not supported"); - return false; - } + record_key_value = unfold.get_value(aData[key_col]); - }); - return ret; + return manifold.query_store.get_record_state(this.options.query_uuid, record_key_value, STATE_VISIBLE); }, _querytable_draw_callback: function() diff --git a/plugins/querytable/templates/querytable.html b/plugins/querytable/templates/querytable.html index d0f18c05..a1792b44 100644 --- a/plugins/querytable/templates/querytable.html +++ b/plugins/querytable/templates/querytable.html @@ -2,18 +2,20 @@ - {% if checkboxes %}{% endif %} - {% for column in columns %} {% endfor %} - {% for column in hidden_columns %} {% endfor %} + {% if checkboxes %}{% endif %} + + {% for column, field in columns.items %} {% endfor %} + {% for column, field in hidden_columns.items %} {% endfor %} - {% if checkboxes %} {% endif %} - {% for column in columns %} {% endfor %} - {% for column in hidden_columns %} {% endfor %} + {% if checkboxes %} {% endif %} + + {% for column, field in columns.items %} {% endfor %} + {% for column, field in hidden_columns.items %} {% endfor %}
+/-{{ column }}{{ column }}{{ column }}{{ column }}
+/-{{ column }}{{ column }}{{ column }}{{ column }}
diff --git a/plugins/queryupdater/__init__.py b/plugins/queryupdater/__init__.py index fa09ffb8..30dbcee5 100644 --- a/plugins/queryupdater/__init__.py +++ b/plugins/queryupdater/__init__.py @@ -1,6 +1,6 @@ from unfold.plugin import Plugin -class QueryUpdater(Plugin): +class QueryUpdaterPlugin(Plugin): def __init__ (self, query=None, **settings): Plugin.__init__ (self, **settings) diff --git a/plugins/queryupdater/static/js/queryupdater.js b/plugins/queryupdater/static/js/queryupdater.js index 3057cf5b..4bd5189f 100644 --- a/plugins/queryupdater/static/js/queryupdater.js +++ b/plugins/queryupdater/static/js/queryupdater.js @@ -26,7 +26,7 @@ // Record state through the query cycle - var QueryUpdater = Plugin.extend({ + var QueryUpdaterPlugin = Plugin.extend({ init: function(options, element) { this.classname="queryupdater"; @@ -164,7 +164,6 @@ // XXX check that the query is not disabled self.spin(); - console.log("do_update"); // XXX check that the query is not disabled manifold.raise_event(self.options.query_uuid, RUN_UPDATE); return; @@ -365,7 +364,6 @@ set_state: function(data) { - console.log("function set_state"); var action; var msg; var button = ''; @@ -378,50 +376,56 @@ // XXX we don't want to show automaticaly the pending when a checkbox is checked //this.toggle_on(); - - switch(data.request) { - case FIELD_REQUEST_ADD_RESET: - case FIELD_REQUEST_REMOVE_RESET: + + switch (data.status) { + case STATE_SET_IN_PENDING: + action = 'ADD'; + msg = 'PENDING'; + button = ""; + break; + case STATE_SET_OUT_PENDING: + action = 'REMOVE'; + msg = 'PENDING'; + button = ""; + break; + case STATE_SET_IN: + case STATE_SET_OUT: // find line and delete it + // XXX Naming is incorrect for badge-pending !!!! + // XXX What is this badge ? row = this.find_row(data.value); if (row) this.table.fnDeleteRow(row.nTr); + /* indent was wrong !! $("#badge-pending").data('number', $("#badge-pending").data('number') - 1 ); $("#badge-pending").text($("#badge-pending").data('number')); + */ return; - case FIELD_REQUEST_CHANGE: - action = 'UPDATE'; - break; - case FIELD_REQUEST_ADD: - action = 'ADD'; - break; - case FIELD_REQUEST_REMOVE: - action = 'REMOVE'; - break; - } - - switch(data.status) { - case FIELD_REQUEST_PENDING: - msg = 'PENDING'; - button = ""; - break; - case FIELD_REQUEST_SUCCESS: + break; + case STATE_SET_IN_SUCCESS: + case STATE_SET_OUT_SUCCESS: msg = 'SUCCESS'; break; - case FIELD_REQUEST_FAILURE: + case STATE_SET_IN_FAILURE: + case STATE_SET_OUT_FAILURE: msg = 'FAILURE'; break; + case STATE_CHANGE: + action = 'UPDATE'; + break; + } var status = msg + status; - - // find line // if no, create it, else replace it // XXX it's not just about adding lines, but sometimes removing some // XXX how do we handle status reset ? - data.value = JSON.stringify(data.value); + + // Jordan : I don't understand this. I added this test otherwise we have string = ""..."" double quoted twice. + if (typeof(data.value) !== "string") + data.value = JSON.stringify(data.value); data.selected_resources = this.selected_resources; row = this.find_row(data.value); newline = [ @@ -435,8 +439,10 @@ // XXX second parameter refresh = false can improve performance. todo in querytable also this.table.fnAddData(newline); row = this.find_row(data.value); + /* $("#badge-pending").data('number', $("#badge-pending").data('number') + 1 ); $("#badge-pending").text($("#badge-pending").data('number')); + */ } else { // Update row text... this.table.fnUpdate(newline, row.nTr); @@ -459,8 +465,6 @@ on_new_record: function(record) { - console.log("query_updater on_new_record"); - console.log(record); // if (not and update) { @@ -546,7 +550,6 @@ on_query_done: function() { - console.log("on_query_done"); this.unspin(); }, @@ -554,14 +557,12 @@ // NOTE: record_key could be sufficient on_added_record: function(record) { - console.log("on_added_record = ",record); this.set_record_state(record, RECORD_STATE_ADDED); // update pending number }, on_removed_record: function(record_key) { - console.log("on_removed_record = ",record_key); this.set_record_state(RECORD_STATE_REMOVED); }, @@ -577,8 +578,6 @@ on_field_state_changed: function(result) { - console.log("on_field_state_changed"); - console.log(result); if(result.request == FIELD_REQUEST_ADD){ this.selected_resources.push(result.value); } else if(result.request == FIELD_REQUEST_REMOVE_RESET){ @@ -587,9 +586,8 @@ this.selected_resources.splice(i,1); } } - console.log("Resources: " + self.selected_resources); messages.debug(result) - /* this.set_state(result.request, result.key, result.value, result.status); */ + this.set_state(result); }, @@ -719,6 +717,6 @@ }); - $.plugin('QueryUpdater', QueryUpdater); + $.plugin('QueryUpdaterPlugin', QueryUpdaterPlugin); })(jQuery); diff --git a/plugins/scheduler2/__init__.py b/plugins/scheduler2/__init__.py index 040b2740..abe8eea9 100755 --- a/plugins/scheduler2/__init__.py +++ b/plugins/scheduler2/__init__.py @@ -5,15 +5,10 @@ from datetime import timedelta class Scheduler2 (Plugin): - def __init__ (self, query, query_lease, query_all_resources, query_all_leases, **settings): + def __init__ (self, query, query_lease, **settings): Plugin.__init__ (self, **settings) self.query=query - self.query_all_resources = query_all_resources - self.query_all_resources_uuid = query_all_resources.query_uuid - - self.query_all_leases = query_all_leases - self.query_all_leases_uuid = query_all_leases.query_uuid self.query_lease = query_lease self.query_lease_uuid = query_lease.query_uuid @@ -35,12 +30,9 @@ class Scheduler2 (Plugin): def requirements (self): reqs = { 'js_files' : [ - 'js/angular/angular.min.js', 'js/scheduler2.js', - 'js/scheduler-SchedulerCtrl.js', #'js/slider/jquery-ui-1.10.3.slider.min.js', 'js/scheduler-helpers.js', - 'js/scheduler-table-selector.js', ], 'css_files': [ 'css/scheduler2.css', @@ -54,7 +46,7 @@ class Scheduler2 (Plugin): # query_uuid will pass self.query results to the javascript # and will be available as "record" in : # on_new_record: function(record) - return ['plugin_uuid', 'domid', 'query_uuid', 'time_slots', 'nodes', 'query_lease_uuid', 'query_all_resources_uuid', 'query_all_leases_uuid'] + return ['plugin_uuid', 'domid', 'query_uuid', 'time_slots', 'nodes', 'query_lease_uuid'] def export_json_settings (self): diff --git a/plugins/scheduler2/asdf.txt b/plugins/scheduler2/asdf.txt deleted file mode 100755 index 10500012..00000000 --- a/plugins/scheduler2/asdf.txt +++ /dev/null @@ -1 +0,0 @@ -asd \ No newline at end of file diff --git a/plugins/scheduler2/static/css/scheduler2.css b/plugins/scheduler2/static/css/scheduler2.css index e4de904a..cb844a87 100755 --- a/plugins/scheduler2/static/css/scheduler2.css +++ b/plugins/scheduler2/static/css/scheduler2.css @@ -83,6 +83,13 @@ /** tables css **/ +#scheduler-reservation-table > tbody > tr > th { + font-weight: normal; +} +#scheduler-reservation-table > thead > tr > th { + font-weight: normal; +} + /*#ShedulerNodes-scroll-container { float: left; overflow-x: scroll; @@ -189,12 +196,12 @@ border: none !important; } #scheduler-reservation-table tbody tr td{ - background-color: #A6C9E2 ; - border: 1px solid #111111; + background-color: #FFFFFF; /*#A6C9E2 ;*/ +/* border: 1px solid #111111;*/ } #scheduler-reservation-table tbody tr.even td{ - background-color: #E0E0E0 ; + background-color: #FAFAFA; /*E0E0E0 ;*/ } #scheduler-reservation-table tbody tr th::selection {color: #000000;background:transparent;} @@ -204,7 +211,7 @@ } #scheduler-reservation-table tbody tr td.reserved { - background: url("../img/closed-lock-15.png") no-repeat scroll 50% 50% #DD4444; + background: url("../img/closed-lock-15.png") no-repeat scroll 50% 50%; /* #DD4444;*/ cursor: not-allowed; } @@ -212,6 +219,15 @@ background: url("../img/tools-15.png") no-repeat scroll 50% 50% #EDA428; } +#scheduler-reservation-table tbody tr td.pendingin { + background: #FFFF99; +} + + +#scheduler-reservation-table tbody tr td.pendingout { + background: #E8E8E8; +} + #scheduler-reservation-table tbody tr td.free:hover ,#scheduler-reservation-table tbody tr td.selected, #scheduler-reservation-table tbody tr td.selected_tmp { background: #25BA25; } @@ -266,4 +282,4 @@ td.no-image { } .table-responsive{ overflow: hidden !important; -} \ No newline at end of file +} diff --git a/plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js b/plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js deleted file mode 100755 index d89d8c0e..00000000 --- a/plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js +++ /dev/null @@ -1,178 +0,0 @@ -var myApp = angular.module('myApp', []); -myApp.config(function ($interpolateProvider) { - $interpolateProvider.startSymbol('{[{').endSymbol('}]}'); -}); -myApp.factory('$exceptionHandler', function () { - return function (exception, cause) { - if (exception.message.contains('leases')) { - console.log(exception.message); - - var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - //tmpScope.initSlots(_schedulerCurrentCellPosition, _schedulerCurrentCellPosition + SchedulerTotalVisibleCells); - } - - }; -}); - -// Create a private execution space for our controller. When -// executing this function expression, we're going to pass in -// the Angular reference and our application module. -(function (ng, app) { - - - // Define our Controller constructor. - function Controller($scope) { - - // Store the scope so we can reference it in our - // class methods - this.scope = $scope; - - // Set up the default scope value. - this.scope.errorMessage = null; - this.scope.name = ""; - //Pagin - $scope.totalPages = 4; - $scope.curPage = 0; - this.scope.pageSize = 25; - - $scope.resources = new Array(); - $scope.slots = SchedulerSlotsViewData; - //$scope.msg = "hello"; - - angular.element(document).ready(function() { - //console.log('Hello World'); - //alert('Hello World'); - //afterAngularRendered(); - }); - - $scope.clearStuff = function() { - $scope.resources = new Array(); - $scope.$apply(); - } - - $scope.initSchedulerResources = function (pageSize) { - $scope.resources = new Array(); - - for (var k = 0; k < pageSize; k++) { - $scope.resources.push(jQuery.extend(true, {}, SchedulerDataViewData[k])); - $scope.resources[k].leases = []; - } - $scope.pageSize = pageSize; - $scope.curPage = 0; - $scope.totalPages = parseInt(Math.ceil(SchedulerDataViewData.length / $scope.pageSize)); - $scope.initSlots(0, SchedulerTotalVisibleCells); - }; - - $scope.setPage = function(page) { - var tmpFrm = $scope.pageSize * page; - var tmpTo = tmpFrm + $scope.pageSize; - tmpTo = SchedulerDataViewData.length < tmpTo ? SchedulerDataViewData.length : tmpTo; - $scope.curPage = page; - $scope.resources = []; - var j = 0; - for (var k = tmpFrm; k < tmpTo; k++) { - $scope.resources.push(jQuery.extend(true, {}, SchedulerDataViewData[k])); - $scope.resources[j].leases = []; - j++; - } - //fix slider - $('#tblSlider').slider('value', 0); - //init Slots - $scope.initSlots(0, SchedulerTotalVisibleCells); - }; - - $scope.initSlots = function (from, to) { - //init - $scope.slots = []; - - var resourceIndex; //gia to paging - //set - for (var i = from; i < to; i++) { - $scope.slots.push(SchedulerSlots[i]); - resourceIndex = $scope.pageSize * $scope.curPage; - for (var j = 0; j < $scope.resources.length; j++) { - if (i == from) { - $scope.resources[j].leases = []; - } - $scope.resources[j].leases.push(SchedulerDataViewData[resourceIndex].leases[i]); - resourceIndex++; - } - } - //apply - $scope.$apply(); - }; - - $scope.moveFrontSlot = function(from, to) { - //$scope.slots.shift(); - //$scope.slots.push(SchedulerSlots[to]); - //for (var j = 0; j < $scope.resources.length; j++) { - // $scope.resources[j].leases.shift(); - // $scope.resources[j].leases.push(SchedulerData[j].leases[to]); - //} - //try { - // $scope.$digest(); - // //$scope.$apply(); - //} catch (err) { - // $scope.initSlots(from, to); - //} - $scope.initSlots(from, to); - }; - - $scope.moveBackSlot = function(from, to) { - //$scope.$apply(function() { - //try { - // $scope.slots.pop(); - // $scope.slots.unshift(SchedulerSlots[from]); - // for (var j = 0; j < $scope.resources.length; j++) { - // $scope.resources[j].leases.pop(); - // $scope.resources[j].leases.unshift(SchedulerData[j].leases[from]); - // } - //} catch (err) { - // alert("error"); - //} - - $scope.initSlots(from, to); - //}); - }; - - $scope.getPageNumbers = function () { - var totalNumbersShowned = ($scope.totalPages > 10 ? 10 : $scope.totalPages + 1 ); - var tmtNumDiv = totalNumbersShowned / 2; - //local - var numFrom = 1; - var numTo = totalNumbersShowned; - var rtrnArr = new Array(); - - if (totalNumbersShowned > 1) { - //set from - to - if ($scope.totalPages > totalNumbersShowned) { - if ($scope.curPage <= tmtNumDiv) { - //nothing - } else if ($scope.curPage >= $scope.totalPages - tmtNumDiv) { - numTo = $scope.totalPages; - numFrom = numTo - totalNumbersShowned; - } else { - numFrom = $scope.curPage - tmtNumDiv; - numTo = numFrom + totalNumbersShowned; - } - } - - for (var i = numFrom; i < numTo; i++) - rtrnArr.push(i); - } else { - rtrnArr.push(1); - } - return rtrnArr; - }; - - // Return this object reference. - return (this); - - } - - - // Define the Controller as the constructor function. - app.controller("SchedulerCtrl", Controller); - - -})(angular, myApp); \ No newline at end of file diff --git a/plugins/scheduler2/static/js/scheduler-helpers.js b/plugins/scheduler2/static/js/scheduler-helpers.js index 1daeee00..7275b755 100755 --- a/plugins/scheduler2/static/js/scheduler-helpers.js +++ b/plugins/scheduler2/static/js/scheduler-helpers.js @@ -46,20 +46,6 @@ function schedulerCloneArray(originalArray) { return clonedArray; } -function schedulerGetSlots(slotSpan) { - if (slotSpan == 0) slotSpan = 10; - var slots = []; - var d = new Date(2014, 1, 1, 0, 0, 0, 0); - var i = 0; - while (d.getDate() == 1) { - var tmpTime = schedulerPadStr(d.getHours()) + ':' + schedulerPadStr(d.getMinutes()); - slots.push({ id: i, time: tmpTime }); - d = schedulerAddMinutes(d, slotSpan); - i++; - } - return slots; -} - function schedulerGetLeases(slotSpan, granularity) { granularity = granularity / 60; if (slotSpan == 0) slotSpan = 10; @@ -187,7 +173,16 @@ function schedulerAddMinutes(date, minutes) { return new Date(date.getTime() + minutes * 60000); } -function schedulerCompareOnDay(dateOne, dateTwo) { +/** + * Compares two dates + * + * Returns: + * 0 if they are equal + * -1 if the first is less than the second + * 1 if the first is more than the second + */ +function schedulerCompareOnDay(dateOne, dateTwo) +{ if (dateOne.getYear() == dateTwo.getYear() && dateOne.getMonth() == dateTwo.getMonth() && dateOne.getDate() == dateTwo.getDate()) { diff --git a/plugins/scheduler2/static/js/scheduler-table-selector.js b/plugins/scheduler2/static/js/scheduler-table-selector.js deleted file mode 100755 index d69a2d45..00000000 --- a/plugins/scheduler2/static/js/scheduler-table-selector.js +++ /dev/null @@ -1,191 +0,0 @@ -////version 3 -var scheduler_table_dragStart_td = 0; -var scheduler_table_dragStart_tr = 0; -var scheduler_table_dragEnd_td = 0; -var scheduler_table_dragEnd_tr = 0; -//tmp gia ta loops & check gia to last state -var tmp_scheduler_table_dragStart_td; -var tmp_scheduler_table_dragStart_tr; -var tmp_scheduler_table_dragEnd_td; -var tmp_scheduler_table_dragEnd_tr; -var schedulerTableIsDragging = false; -// try stop -var continueExecuting = false; -var isExecuting = false; - - - -function rangeMouseDown(e) { - if (SchedulerData) console.time("mouse:rangeMouseDown"); - if (schedulerIsRightClick(e)) { - return false; - } else { - scheduler_table_dragStart_tr = $(this).parent().index(); - scheduler_table_dragStart_td = $(this).index() -1; - scheduler_table_dragEnd_tr = scheduler_table_dragStart_tr; - scheduler_table_dragEnd_td = scheduler_table_dragStart_td; - //alert(scheduler_table_dragStart_tr); - //var allCells = $("#tblReservation td"); - //dragStart = allCells.index($(this)); - - if ( $(this).hasClass("free")){ - $(this).addClass("selected_tmp"); - $(this).siblings("td[data-groupid='" + $(this).data('groupid') + "']").addClass("selected_tmp"); - } - schedulerTableIsDragging = true; - //selectRange(); - - if (typeof e.preventDefault != 'undefined') { e.preventDefault(); } - document.documentElement.onselectstart = function () { return false; }; - } - if (SchedulerData) console.timeEnd("mouse:rangeMouseDown"); -} - -function rangeMouseUp(e) { - if (SchedulerData) console.time("mouse:rangeMouseUp"); - if (schedulerIsRightClick(e)) { - return false; - } else { - //var allCells = $("#tblReservation td"); - //dragEnd = allCells.index($(this)); - - scheduler_table_dragEnd_tr = $(this).parent().index(); - scheduler_table_dragEnd_td = $(this).index() -1; - - schedulerTableIsDragging = false; - selectRange(false); - - document.documentElement.onselectstart = function () { return true; }; - } - if (SchedulerData) console.timeEnd("mouse:rangeMouseUp"); -} - -function rangeMouseMove(e) { - //if (SchedulerData) console.time("mouse:rangeMouseMove"); - if (schedulerTableIsDragging) { - scheduler_table_dragEnd_tr = $(this).parent().attr('data-trindex'); - scheduler_table_dragEnd_td = $(this).attr('data-tdindex'); - - //if (SchedulerData) this.SchedulerData('foo'); - - if ((scheduler_table_dragEnd_tr != tmp_scheduler_table_dragEnd_tr) || (scheduler_table_dragEnd_td != tmp_scheduler_table_dragEnd_td)) { - //console.log(scheduler_table_dragEnd_tr + " - " + tmp_scheduler_table_dragEnd_tr); - //console.log(scheduler_table_dragEnd_td + " - " + tmp_scheduler_table_dragEnd_td); - //selectRange(true); - } - } - //if (SchedulerData) console.timeEnd("mouse:rangeMouseMove"); -} - -function selectRange(isTemp) { - if (SchedulerData) console.time("mouse:---selectRange"); - - if (!schedulerCtrlPressed) - $("#" + schedulerTblId + " td.selected, #" + schedulerTblId + " td.selected_tmp").each(function() { - $(this).removeClass('selected selected_tmp').addClass('free'); - $(this).siblings("td[data-groupid='" + $(this).data('groupid') + "']").removeClass('selected selected_tmp').addClass("free"); - schedulerFreeSlot($(this).data('slotid'), $(this).siblings('th').data('rowindex'), $(this).siblings('th').data('resourceindex')); - }); - - tmp_scheduler_table_dragStart_td = scheduler_table_dragStart_td; - tmp_scheduler_table_dragStart_tr = scheduler_table_dragStart_tr; - tmp_scheduler_table_dragEnd_td = scheduler_table_dragEnd_td; - tmp_scheduler_table_dragEnd_tr = scheduler_table_dragEnd_tr; - - if (tmp_scheduler_table_dragStart_td > tmp_scheduler_table_dragEnd_td) { - var tmp = tmp_scheduler_table_dragStart_td; - tmp_scheduler_table_dragStart_td = tmp_scheduler_table_dragEnd_td; - tmp_scheduler_table_dragEnd_td = tmp; - } - - if (tmp_scheduler_table_dragStart_tr > tmp_scheduler_table_dragEnd_tr) { - var tmp = tmp_scheduler_table_dragStart_tr; - tmp_scheduler_table_dragStart_tr = tmp_scheduler_table_dragEnd_tr; - tmp_scheduler_table_dragEnd_tr = tmp; - } - //var angularScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - //alert("tmp_scheduler_table_dragStart_td:" + tmp_scheduler_table_dragStart_td + "\n tmp_scheduler_table_dragStart_tr:" + tmp_scheduler_table_dragStart_tr + "\n tmp_scheduler_table_dragEnd_td:" + tmp_scheduler_table_dragEnd_td + "\n tmp_scheduler_table_dragEnd_tr:" + tmp_scheduler_table_dragEnd_tr); - - - for (var i = tmp_scheduler_table_dragStart_tr; i <= tmp_scheduler_table_dragEnd_tr; i++) { - for (var j = tmp_scheduler_table_dragStart_td; j <= tmp_scheduler_table_dragEnd_td; j++) { - //alert("i:" + i + "j:" + j); - var cell = $('#' + schedulerTblId + ' tbody tr:eq(' + i + ') td:eq(' + j + ')'); - //$(cell) - var curClass = $(cell).attr("class"); - curClass = curClass.replace('ng-scope','').trim(); - //alert(curClass); - switch (curClass) { - case "free_tmp": - $(cell).removeClass('selected_tmp selected free_tmp free'); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free"); - if (isTemp){ - $(cell).addClass("free_tmp"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free"); - } else { - schedulerFreeSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex')); - $(cell).addClass("free"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free"); - } - break; - case "free": - $(cell).removeClass('selected_tmp selected free_tmp free'); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free"); - if (isTemp){ - $(cell).addClass("selected_tmp"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected_tmp"); - }else { - schedulerSelectSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex')); - $(cell).addClass("selected"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected"); - } - break; - case "selected_tmp": - $(cell).removeClass('selected_tmp selected free_tmp free'); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free"); - if (isTemp){ - $(cell).addClass("selected_tmp"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected_tmp"); - } else { - schedulerSelectSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex')); - $(cell).addClass("selected"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("selected"); - } - break; - case "selected": - $(cell).removeClass('selected_tmp selected free_tmp free'); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").removeClass("selected_tmp selected free_tmp free"); - if (isTemp){ - $(cell).addClass("free_tmp"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free_tmp"); - } else { - schedulerFreeSlot($(cell).data('slotid'), $(cell).siblings('th').data('rowindex'), $(cell).siblings('th').data('resourceindex')); - $(cell).addClass("free"); - $(cell).siblings("td[data-groupid='" + $(cell).data('groupid') + "']").addClass("free"); - } - break; - case "closed": - //do nothing - //alert("not allowed!"); - break; - } - } - } - - - /*if (dragEnd + 1 < dragStart) { // reverse select - //alert(1); - $("#tblReservation td:not([class='info'])").slice(dragEnd, dragStart + 1).addClass('selected'); - } else { - alert(dragStart + "-" + dragEnd); - $("#tblReservation td:not([class='info'])").slice(dragStart, dragEnd).addClass('selected'); - }*/ - - if (SchedulerData) console.timeEnd("mouse:---selectRange"); -} - -function ClearTableSelection(){ - $('#' + schedulerTblId + ' .selected').addClass("free").removeClass("selected"); -} - - diff --git a/plugins/scheduler2/static/js/scheduler2.js b/plugins/scheduler2/static/js/scheduler2.js index 10eaa9a8..5f35a0fd 100755 --- a/plugins/scheduler2/static/js/scheduler2.js +++ b/plugins/scheduler2/static/js/scheduler2.js @@ -26,6 +26,8 @@ # */ +// XXX groupid = all slots those that go with a min granularity + /* some params */ var scheduler2; var scheduler2Instance; @@ -33,26 +35,365 @@ var scheduler2Instance; var schedulerCtrlPressed = false; //table Id var schedulerTblId = "scheduler-reservation-table"; -var schedulerTblFirstColWidth = 150; -//Some Data +var SCHEDULER_FIRST_COLWIDTH = 200; + + +/* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */ var schedulerSlotsPerHour = 6; +var RESOURCE_DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information +var DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information. Test with 600 +var DEFAULT_PAGE_RANGE = 5; + var schedulerMaxRows = 12; + +/* All resources */ var SchedulerData = []; + +/* ??? */ var SchedulerSlots = []; + var SchedulerDateSelected = new Date(); +// Round to midnight +SchedulerDateSelected.setHours(0,0,0,0); + +/* Filtered resources */ var SchedulerDataViewData = []; + var SchedulerSlotsViewData = []; -var SchedulerTotalCells; -var SchedulerTotalVisibleCells; //Help Variables var _schedulerCurrentCellPosition = 0; -var _leasesDone = false; -var _resourcesDone = false; //Enable Debug var schedulerDebug = true; //tmp to delete var tmpSchedulerLeases = []; +var SCHEDULER_COLWIDTH = 50; + + +/****************************************************************************** + * ANGULAR CONTROLLER * + ******************************************************************************/ + +// Create a private execution space for our controller. When +// executing this function expression, we're going to pass in +// the Angular reference and our application module. +(function (ng, app) { + + // Define our Controller constructor. + function Controller($scope) { + + // Store the scope so we can reference it in our + // class methods + this.scope = $scope; + + // Set up the default scope value. + this.scope.errorMessage = null; + this.scope.name = ""; + + //Pagin + $scope.current_page = 1; + this.scope.items_per_page = 10; + $scope.from = 0; // JORDAN + + $scope.instance = null; + $scope.resources = new Array(); + $scope.slots = SchedulerSlotsViewData; + $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */ + //$scope.msg = "hello"; + + angular.element(document).ready(function() { + //console.log('Hello World'); + //alert('Hello World'); + //afterAngularRendered(); + }); + + // Pagination + + $scope.range = function() { + var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count(); + var ret = []; + var start; + + start = $scope.current_page; + if ( start > $scope.page_count()-range_size ) { + start = $scope.page_count()-range_size+1; + } + + for (var i=start; i 1) { + $scope.current_page--; + } + }; + + $scope.prevPageDisabled = function() { + return $scope.current_page === 1 ? "disabled" : ""; + }; + + $scope.page_count = function() + { + // XXX need visible resources only + var query_ext, visible_resources_length; + if (!$scope.instance) + return 0; + query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid); + var visible_resources_length = 0; + query_ext.state.each(function(i, state) { + if (state[STATE_VISIBLE]) + visible_resources_length++; + }); + return Math.ceil(visible_resources_length/$scope.items_per_page); + }; + + $scope.nextPage = function() { + if ($scope.current_page < $scope.page_count()) { + $scope.current_page++; + } + }; + + $scope.nextPageDisabled = function() { + return $scope.current_page === $scope.page_count() ? "disabled" : ""; + }; + + $scope.setPage = function(n) { + $scope.current_page = n; + }; + // END pagination + + // FILTER + + $scope.filter_visible = function(resource) + { + return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE); + }; + + // SELECTION + + $scope._create_new_lease = function(resource_urn, start_time, end_time) + { + var lease_key, new_lease; + + lease_key = manifold.metadata.get_key('lease'); + + new_lease = { + resource: resource_urn, + start_time: start_time, + end_time: end_time, + }; + + // This is needed to create a hashable object + new_lease.hashCode = manifold.record_hashcode(lease_key.sort()); + new_lease.equals = manifold.record_equals(lease_key); + + manifold.raise_event($scope.instance.options.query_lease_uuid, SET_ADD, new_lease); + /* Add to local cache also, unless we listen to events from outside */ + if (!(resource_urn in $scope._leases_by_resource)) + $scope._leases_by_resource[resource_urn] = []; + $scope._leases_by_resource[resource_urn].push(new_lease); + } + + $scope._remove_lease = function(other) + { + var lease_key, other_key; + + lease_key = manifold.metadata.get_key('lease'); + + // XXX This could be a manifold.record_get_value + other_key = { + resource: other.resource, + start_time: other.start_time, + end_time: other.end_time + } + other_key.hashCode = manifold.record_hashcode(lease_key.sort()); + other_key.equals = manifold.record_equals(lease_key); + + manifold.raise_event($scope.instance.options.query_lease_uuid, SET_REMOVED, other_key); + /* Remove from local cache also, unless we listen to events from outside */ + $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; }); + + } + + $scope.select = function(index, model_lease, model_resource) + { + console.log("Selected", index, model_lease, model_resource); + + var day_timestamp = SchedulerDateSelected.getTime() / 1000; + var start_time = day_timestamp + index * model_resource.granularity; + var end_time = day_timestamp + (index + 1) * model_resource.granularity; + var start_date = new Date(start_time * 1000); + var end_date = new Date(end_time * 1000); + + var lease_key = manifold.metadata.get_key('lease'); + + // We search for leases in the cache we previously constructed + var resource_leases = $scope._leases_by_resource[model_resource.urn]; + + switch (model_lease.status) + { + case 'free': // out + case 'pendingout': + if (resource_leases) { + /* Search for leases before */ + $.each(resource_leases, function(i, other) { + if (other.end_time != start_time) + return true; // ~ continue + + /* The lease 'other' is just before, and there should not exist + * any other lease before it */ + start_time = other.start_time; + + other_key = { + resource: other.resource, + start_time: other.start_time, + end_time: other.end_time + } + // This is needed to create a hashable object + other_key.hashCode = manifold.record_hashcode(lease_key.sort()); + other_key.equals = manifold.record_equals(lease_key); + + manifold.raise_event($scope.instance.options.query_lease_uuid, SET_REMOVED, other_key); + /* Remove from local cache also, unless we listen to events from outside */ + $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; }); + return false; // ~ break + }); + + /* Search for leases after */ + $.each(resource_leases, function(i, other) { + if (other.start_time != end_time) + return true; // ~ continue + + /* The lease 'other' is just after, and there should not exist + * any other lease after it */ + end_time = other.end_time; + // XXX SET_ADD and SET_REMOVE should accept full objects + other_key = { + resource: other.resource, + start_time: other.start_time, + end_time: other.end_time + } + // This is needed to create a hashable object + other_key.hashCode = manifold.record_hashcode(lease_key.sort()); + other_key.equals = manifold.record_equals(lease_key); + + manifold.raise_event($scope.instance.options.query_lease_uuid, SET_REMOVED, other_key); + /* Remove from local cache also, unless we listen to events from outside */ + $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; }); + return false; // ~ break + }); + } + + $scope._create_new_lease(model_resource.urn, start_time, end_time); + model_lease.status = 'pendingin'; + // unless the exact same lease already existed (pending_out status for the lease, not the cell !!) + + break; + + case 'selected': + case 'pendingin': + // We remove the cell + + /* We search for leases including this cell. Either 0, 1 or 2. + * 0 : NOT POSSIBLE, should be checked. + * 1 : either IN or OUT, we have make no change in the session + * 2 : both will be pending, since we have made a change in the session + * /!\ need to properly remove pending_in leases when removed again + */ + if (resource_leases) { + $.each(resource_leases, function(i, other) { + if ((other.start_time <= start_time) && (other.end_time >= end_time)) { + // The cell is part of this lease. + + // If the cell is not at the beginning of the lease, we recreate a lease with cells before + if (start_time > other.start_time) { + $scope._create_new_lease(model_resource.urn, other.start_time, start_time); + } + + // If the cell is not at the end of the lease, we recreate a lease with cells after + if (end_time < other.end_time) { + $scope._create_new_lease(model_resource.urn, end_time, other.end_time); + } + + // The other lease will be removed + $scope._remove_lease(other); + } + // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status). + }); + } + + // cf comment in previous switch case + model_lease.status = 'pendingout'; + + break; + + case 'reserved': + case 'maintainance': + // Do nothing + break; + } + + + //$scope._dump_leases(); + }; + + $scope._dump_leases = function() + { + // DEBUG: display all leases and their status in the log + var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid); + console.log("--------------------"); + $.each(leases, function(i, lease) { + var key = manifold.metadata.get_key('lease'); + var lease_key = manifold.record_get_value(lease, key); + var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET); + var state_str; + switch(state) { + case STATE_SET_IN: + state_str = 'STATE_SET_IN'; + break; + case STATE_SET_OUT: + state_str = 'STATE_SET_OUT'; + break; + case STATE_SET_IN_PENDING: + state_str = 'STATE_SET_IN_PENDING'; + break; + case STATE_SET_OUT_PENDING: + state_str = 'STATE_SET_OUT_PENDING'; + break; + case STATE_SET_IN_SUCCESS: + state_str = 'STATE_SET_IN_SUCCESS'; + break; + case STATE_SET_OUT_SUCCESS: + state_str = 'STATE_SET_OUT_SUCCESS'; + break; + case STATE_SET_IN_FAILURE: + state_str = 'STATE_SET_IN_FAILURE'; + break; + case STATE_SET_OUT_FAILURE: + state_str = 'STATE_SET_OUT_FAILURE'; + break; + } + console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str); + }); + }; + + // Return this object reference. + return (this); + + } + + // Define the Controller as the constructor function. + app.controller("SchedulerCtrl", Controller); + +})(angular, ManifoldApp); + +/****************************************************************************** + * MANIFOLD PLUGIN * + ******************************************************************************/ + (function($) { scheduler2 = Plugin.extend({ @@ -64,16 +405,24 @@ var tmpSchedulerLeases = []; * applied, which allows to maintain chainability of calls */ init: function(options, element) { - this.classname = "scheduler2"; // Call the parent constructor, see FAQ when forgotten this._super(options, element); + + var scope = this._get_scope() + scope.instance = this; + + // XXX not needed scheduler2Instance = this; + // We need to remember the active filter for datatables filtering + // XXX not needed this.filters = Array(); + // XXX BETTER !!!! + $(window).delegate('*', 'keypress', function (evt){ + alert("erm"); + }); - SchedulerSlots = schedulerGetSlots(60 / schedulerSlotsPerHour); - //selection from table $(window).keydown(function(evt) { if (evt.which == 17) { // ctrl schedulerCtrlPressed = true; @@ -83,154 +432,210 @@ var tmpSchedulerLeases = []; schedulerCtrlPressed = false; } }); - $("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove); - // Explain this will allow query events to be handled - // What happens when we don't define some events ? - // Some can be less efficient + // XXX naming + //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove); + + this._resources_received = false; + this._leases_received = false; + + scope._leases_by_resource = {}; - if (schedulerDebug) console.time("Listening_to_queries"); /* Listening to queries */ + this.listen_query(options.query_uuid, 'resources'); + this.listen_query(options.query_lease_uuid, 'leases'); - this.listen_query(options.query_uuid); - //this.listen_query(options.query_all_uuid, 'all'); - this.listen_query(options.query_all_resources_uuid, 'all_resources'); - this.listen_query(options.query_lease_uuid, 'lease'); - this.listen_query(options.query_all_leases_uuid, 'all_leases'); - if (schedulerDebug) console.timeEnd("Listening_to_queries"); + this.elmt().on('show', this, this.on_show); + this.elmt().on('shown.bs.tab', this, this.on_show); + this.elmt().on('resize', this, this.on_resize); - }, + /* Generate slots according to the default granularity. Should + * be updated when resources arrive. Should be the pgcd in fact XXX */ + this._granularity = DEFAULT_GRANULARITY; + scope.granularity = this._granularity; + this._all_slots = this._generate_all_slots(); - /* Handlers */ + // A list of {id, time} dictionaries representing the slots for the given day + scope.slots = this._all_slots; + this.scope_resources_by_key = {}; + + this.do_resize(); + + scope.from = 0; + + this._initUI(); - /* all_ev QUERY HANDLERS Start */ - on_all_ev_clear_records: function(data) { - //alert('all_ev clear_records'); - }, - on_all_ev_query_in_progress: function(data) { - // alert('all_ev query_in_progress'); - }, - on_all_ev_new_record: function(data) { - //alert('all_ev new_record'); - }, - on_all_ev_query_done: function(data) { - //alert('all_ev query_done'); - }, - //another plugin has modified something, that requires you to update your display. - on_all_ev_field_state_changed: function(data) { - //alert('all_ev query_done'); - }, - /* all_ev QUERY HANDLERS End */ - /* all_resources QUERY HANDLERS Start */ - on_all_resources_clear_records: function(data) { - //data is empty on load - }, - on_all_resources_query_in_progress: function(data) { - //data is empty on load }, - on_all_resources_new_record: function(data) { - //alert(data.toSource()); - if (data.exclusive == true) { - var tmpGran = schedulerDebug && data.granularity == null ? 1800 : data.granularity; - SchedulerData.push({ - id: data.urn, - index: SchedulerData.length, - name: data.hrn, - granularity: tmpGran, - leases: schedulerGetLeases(60 / schedulerSlotsPerHour, tmpGran), - type: data.type, - org_resource: data - }); - /*if (schedulerDebug && SchedulerData[SchedulerData.length - 1].org_resource.network_hrn == 'omf') { - SchedulerData[SchedulerData.length - 1].granularity = 1800; - }*/ + + do_resize: function() + { + var scope = this._get_scope(); + + $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH); + //self get width might need fix depending on the template + var tblwidth = $('#scheduler-reservation-table').parent().outerWidth(); + + /* Number of visible cells...*/ + this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH); + /* ...should be a multiple of the lcm of all encountered granularities. */ + // XXX Should be updated everytime a new resource is added + this._lcm_colspan = this._lcm(this._granularity, RESOURCE_DEFAULT_GRANULARITY) / this._granularity; + this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % this._lcm_colspan; + /* scope also needs this value */ + scope.num_visible_cells = this._num_visible_cells; + scope.lcm_colspan = this._lcm_colspan; + + // Slider max value + + if ($('#tblSlider').data('slider') != undefined) { + var new_max = (this._all_slots.length - this._num_visible_cells) / this._lcm_colspan; + $('#tblSlider').slider('setAttribute', 'max', new_max); } - //alert(data.toSource()); }, - on_all_resources_query_done: function(data) { - _resourcesDone = true; - this._initScheduler(); + + on_show: function(e) + { + var self = e.data; + self.do_resize(); + self._get_scope().$apply(); }, - //another plugin has modified something, that requires you to update your display. - on_all_resources_field_state_changed: function(data) { - //alert('all_resources query_done'); + + on_resize: function(e) + { + var self = e.data; + self.do_resize(); + self._get_scope().$apply(); }, - /* all_resources QUERY HANDLERS End */ - /* lease QUERY HANDLERS Start */ - on_lease_clear_records: function(data) { console.log('clear_records'); }, - on_lease_query_in_progress: function(data) { console.log('lease_query_in_progress'); }, - on_all_leases_new_record: function(data) { - if (data.resource.indexOf("nitos") > -1) { - tmpSchedulerLeases.push({ - id: schedulerGetSlotId(data.start_time, data.duration, data.granularity), - end_id: schedulerGetSlotId(data.end_time, data.duration, data.granularity), - slice: data.slice, - status: 'reserved', - resource: data.resource, - network: data.network, - start_time: new Date(data.start_time * 1000), - start_time_unixtimestamp: data.start_time, - end_time: new Date(data.end_time * 1000), - end_time_unixtimestamp: data.end_time, - lease_type: data.lease_type, - granularity: data.granularity, - duration: data.duration - }); - } - //console.log(data.toSource()); console.log('lease_new_record'); + + /* Handlers */ + + _get_scope : function() + { + return angular.element(document.getElementById('SchedulerCtrl')).scope(); }, - on_all_leases_query_done: function(data) { - _leasesDone = true; - this._initScheduler(); - // console.log('lease_query_done'); + + _scope_set_resources : function() + { + var self = this; + var scope = this._get_scope(); + + var records = manifold.query_store.get_records(this.options.query_uuid); + + scope.resources = []; + + $.each(records, function(i, record) { + if (!record.exclusive) + return true; // ~ continue + + // copy not to modify original record + var resource = jQuery.extend(true, {}, record); + + // Fix granularity + resource.granularity = typeof(resource.granularity) == "number" ? resource.granularity : RESOURCE_DEFAULT_GRANULARITY; + resource.leases = []; // a list of occupied timeslots + + self.scope_resources_by_key[resource['urn']] = resource; + scope.resources.push(resource); + }); }, - //another plugin has modified something, that requires you to update your display. - on_lease_field_state_changed: function(data) { console.log('lease_field_state_changed'); }, - /* lease QUERY HANDLERS End */ + _scope_clear_leases: function() + { + var self = this; + var scope = this._get_scope(); + + // Setup leases with a default free status... + $.each(this.scope_resources_by_key, function(resource_key, resource) { + resource.leases = []; + var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells + for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity + resource.leases.push({ + id: 'coucou', + status: 'free', // 'selected', 'reserved', 'maintenance' XXX pending ?? + }); + } + }); - // no prefix - on_filter_added: function(filter) { - this.filters.push(filter); - this._SetFiletredResources(this.filters); - //angular and UI - var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - if (SchedulerDataViewData.length == 0) { - $("#plugin-scheduler").hide(); - $("#plugin-scheduler-empty").show(); - tmpScope.clearStuff(); - } else { - $("#plugin-scheduler-empty").hide(); - $("#plugin-scheduler").show(); - tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length); - } }, - on_filter_removed: function(filter) { - // Remove corresponding filters - this.filters = $.grep(this.filters, function(x) { - return x == filter; + _scope_set_leases: function() + { + var self = this; + var scope = this._get_scope(); + + var leases = manifold.query_store.get_records(this.options.query_lease_uuid); + $.each(leases, function(i, lease) { + + console.log("SET LEASES", new Date(lease.start_time* 1000)); + console.log(" ", new Date(lease.end_time* 1000)); + // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work + + // Populate leases by resource array: this will help us merging leases later + if (!(lease.resource in scope._leases_by_resource)) + scope._leases_by_resource[lease.resource] = []; + scope._leases_by_resource[lease.resource].push(lease); + + var resource = self.scope_resources_by_key[lease.resource]; + var day_timestamp = SchedulerDateSelected.getTime() / 1000; + + var id_start = (lease.start_time - day_timestamp) / resource.granularity; + if (id_start < 0) { + /* Some leases might be in the past */ + id_start = 0; + } + + var id_end = (lease.end_time - day_timestamp) / resource.granularity - 1; + var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells + if (id_end >= self._all_slots.length / colspan_lease) { + /* Limit the display to the current day */ + id_end = self._all_slots.length / colspan_lease + } + + for (i = id_start; i <= id_end; i++) + // the same slots might be affected multiple times. + // PENDING_IN + PENDING_OUT => IN + // + // RESERVED vs SELECTED ! + // + // PENDING !! + resource.leases[i].status = 'selected'; }); - this._SetFiletredResources(this.filters); - //angular and UI - var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - if (SchedulerDataViewData.length == 0) { - $("#plugin-scheduler").hide(); - $("#plugin-scheduler-empty").show(); - tmpScope.clearStuff(); - } else { - $("#plugin-scheduler-empty").hide(); - $("#plugin-scheduler").show(); - tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length); + }, + + on_resources_query_done: function(data) + { + this._resources_received = true; + this._scope_set_resources(); + this._scope_clear_leases(); + if (this._leases_received) + this._scope_set_leases(); + + this._get_scope().$apply(); + }, + + on_leases_query_done: function(data) + { + this._leases_received = true; + if (this._resources_received) { + this._scope_set_leases(); + this._get_scope().$apply(); } }, - on_filter_clear: function() { - this.filters = []; - this._SetFiletredResources(this.filters); - //angular and UI - var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); + /* Filters on resources */ + on_resources_filter_added: function(filter) { this._get_scope().$apply(); }, + on_resources_filter_removed: function(filter) { this._get_scope().$apply(); }, + on_resources_filter_clear: function() { this._get_scope().$apply(); }, + + /* Filters on leases ? */ + on_leases_filter_added: function(filter) { this._get_scope().$apply(); }, + on_leases_filter_removed: function(filter) { this._get_scope().$apply(); }, + on_leases_filter_clear: function() { this._get_scope().$apply(); }, + + /* INTERNAL FUNCTIONS */ + +/* XXX IN TEMPLATE XXX if (SchedulerDataViewData.length == 0) { $("#plugin-scheduler").hide(); $("#plugin-scheduler-empty").show(); @@ -238,296 +643,151 @@ var tmpSchedulerLeases = []; } else { $("#plugin-scheduler-empty").hide(); $("#plugin-scheduler").show(); + // initSchedulerResources tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length); } - }, - - on_all_leases_filter_added: function(filter) { - console.log("Filter on Leases added !"); - }, - - // ... be sure to list all events here - - /* RECORD HANDLERS */ - on_all_new_record: function(record) { - //alert('on_all_new_record'); - }, - - debug: function(logTxt) { - if (typeof window.console != 'undefined') { - console.debug(logTxt); - } - }, +*/ - /* INTERNAL FUNCTIONS */ - _initScheduler: function() { - if (_resourcesDone && _leasesDone) { - SchedulerDataViewData = SchedulerData; - /* GUI setup and event binding */ - this._FixLeases(); - this._initUI(); - } - }, + /** + * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler. + */ + _initUI: function() + { + var self = this; - _initUI: function() { - //alert(1); - if (schedulerDebug) console.time("_initUI"); - //init DatePicker Start $("#DateToRes").datepicker({ - dateFormat: "yy-mm-dd", - minDate: 0, - numberOfMonths: 3 - }).change(function() { - //Scheduler2.loadWithDate(); - SchedulerDateSelected = $("#DateToRes").datepicker("getDate"); - if (SchedulerDateSelected != null && SchedulerDateSelected != '') { - for (var i = 0; i < SchedulerData.length; i++) { - SchedulerData[i].leases = schedulerGetLeases(60 / schedulerSlotsPerHour, SchedulerData[i].granularity); - } - scheduler2Instance._FixLeases(); - $('#tblSlider').slider('value', 0); - var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length); - - //console.log(SchedulerDateSelected); - //console.log(SchedulerDateSelected.getTime()/1000); - var tomorrow = new Date(SchedulerDateSelected); - tomorrow.setDate(SchedulerDateSelected.getDate()+1); - //console.log(tomorrow); - //console.log(tomorrow.getTime()/1000); - - // Remove previous date interval - manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_REMOVED, ['start_time', '>']); - manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_REMOVED, ['start_time', '<']); - - // Add new date interval - manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_ADDED, ['start_time', '>', SchedulerDateSelected.getTime()/1000]); - manifold.raise_event(scheduler2Instance.options.query_all_leases_uuid, FILTER_ADDED, ['start_time', '<', tomorrow.getTime()/1000]); - } else { - alert("Please select a date, so the scheduler can reserve leases."); + onRender: function(date) { + return date.valueOf() < now.valueOf() ? 'disabled' : ''; } - }).datepicker('setDate', SchedulerDateSelected); - /*.click(function () { - $("#ui-datepicker-div").css("z-index", 5); - })*/ - //End init DatePicker - - //init Table - this._FixTable(); - //End init Table + }).on('changeDate', function(ev) { + SchedulerDateSelected = new Date(ev.date); + SchedulerDateSelected.setHours(0,0,0,0); + // Set slider to origin + $('#tblSlider').slider('setValue', 0); // XXX + // Refresh leases + self._scope_clear_leases(); + self._scope_set_leases(); + // Refresh display + self._get_scope().$apply(); + }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker'); //init Slider $('#tblSlider').slider({ min: 0, - max: SchedulerTotalCells - SchedulerTotalVisibleCells, + max: (self._all_slots.length - self._num_visible_cells) / self._lcm_colspan, value: 0, - slide: function(event, ui) { - //$("#amount").val("$" + ui.values[0] + " - $" + ui.values[1]); - //console.log(ui.value); - var angScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - if (_schedulerCurrentCellPosition > ui.value) { - angScope.moveBackSlot(ui.value, ui.value + SchedulerTotalVisibleCells); - } else if (_schedulerCurrentCellPosition < ui.value) { - angScope.moveFrontSlot(ui.value, ui.value + SchedulerTotalVisibleCells); - } - _schedulerCurrentCellPosition = ui.value; - } - }); - //End init Slider - - - //btn Submit leases - $('#btnSchedulerSubmit').click(function () { - console.log("click btnSchedulerSubmit"); - var leasesForCommit = new Array(); - var tmpDateTime = SchedulerDateSelected; - console.log(SchedulerData); - for (var i = 0; i < SchedulerData.length; i++) - { - var tpmR = SchedulerData[i]; - //for capturing start and end of the lease - var newLeaseStarted = false; - for (var j = 0; j < tpmR.leases.length; j++) { - var tpmL = tpmR.leases[j]; - if (newLeaseStarted == false && tpmL.status == 'selected') { - //get date of the slot - tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime); - var unixStartTime = tmpDateTime.getTime() / 1000; - //add lease object - leasesForCommit.push({ - resource: tpmR.id, - //granularity: tpmR.granularity, - //lease_type: null, - //slice: null, - start_time: unixStartTime, - end_time: null, - //duration: null - }); - console.log(tpmR.id); - newLeaseStarted = true; - } else if (newLeaseStarted == true && tpmL.status != 'selected') { - //get date of the slot - tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime); - var unixEndTime = tmpDateTime.getTime() / 1000; - //upate end_time - var tmpCL = leasesForCommit[leasesForCommit.length - 1]; - tmpCL.end_time = unixEndTime; - //tmpCL.duration = schedulerFindDuration(tmpCL.start_time, tmpCL.end_time, tmpCL.granularity); - newLeaseStarted = false; - } - } - } - console.log(leasesForCommit); - for (var i = 0; i < leasesForCommit.length; i++) { - manifold.raise_event(scheduler2Instance.options.query_lease_uuid, SET_ADD, leasesForCommit[i]); - } + }).on('slide', function(ev) { + var scope = self._get_scope(); + scope.from = ev.value * self._lcm_colspan; + scope.$apply(); }); - // - - //End btn Submit leases - - //other stuff $("#plugin-scheduler-loader").hide(); $("#plugin-scheduler").show(); - //fixOddEvenClasses(); - //$("#" + schedulerTblId + " td:not([class])").addClass("free"); - if (schedulerDebug) console.timeEnd("_initUI"); }, - _FixLeases : function () { - for (var i = 0; i < tmpSchedulerLeases.length; i++) { - var tmpLea = tmpSchedulerLeases[i]; - if ((schedulerCompareOnDay(tmpLea.start_time, SchedulerDateSelected) == 0) || - (tmpLea.start_time <= SchedulerDateSelected && SchedulerDateSelected <= tmpLea.end_time) || - (schedulerCompareOnDay(tmpLea.end_time, SchedulerDateSelected) == 0)) { - var tmpRes = schedulerFindResourceById(SchedulerData, tmpLea.resource); - if (tmpRes != null) { - //Replace Lease with current lease from the manifold - var orgLease = tmpRes.leases[tmpLea.id]; - tmpLea['groupid'] = orgLease.groupid; - tmpLea['groupIndex'] = orgLease.groupIndex; - if (orgLease.groupIndex != 0) { - if (!window.console) { - console.warn('there is an error with the leases of the resource :' + tmpRes.name + '\n The lease start in the middle of the granularity!' + '\n The Scheduler plugin might not work!'); - } - } - tmpRes.leases[tmpLea.id] = tmpLea; - this._ExtractLeaseSlots(tmpRes, tmpRes.leases[tmpLea.id]); + // GUI EVENTS + + // TO BE REMOVED + _on_submit : function() + { + var leasesForCommit = new Array(); + var tmpDateTime = SchedulerDateSelected; + for (var i = 0; i < SchedulerData.length; i++) + { + var tpmR = SchedulerData[i]; + //for capturing start and end of the lease + var newLeaseStarted = false; + for (var j = 0; j < tpmR.leases.length; j++) { + var tpmL = tpmR.leases[j]; + if (newLeaseStarted == false && tpmL.status == 'selected') { + //get date of the slot + tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime); + var unixStartTime = tmpDateTime.getTime() / 1000; + //add lease object + leasesForCommit.push({ + resource: tpmR.id, + //granularity: tpmR.granularity, + //lease_type: null, + //slice: null, + start_time: unixStartTime, + end_time: null, + //duration: null + }); + console.log(tpmR.id); + newLeaseStarted = true; + } else if (newLeaseStarted == true && tpmL.status != 'selected') { + //get date of the slot + tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime); + var unixEndTime = tmpDateTime.getTime() / 1000; + //upate end_time + var tmpCL = leasesForCommit[leasesForCommit.length - 1]; + tmpCL.end_time = unixEndTime; + //tmpCL.duration = schedulerFindDuration(tmpCL.start_time, tmpCL.end_time, tmpCL.granularity); + newLeaseStarted = false; } } } - }, - - _ExtractLeaseSlots: function (tmpRes, lease) { - var tmpStartDate = lease.start_time; - var tmpEndDate = lease.end_time; - var startLoop; var toLoop; - if (schedulerCompareOnDay(lease.start_time,lease.end_time) == 0) { - //in the same date - startLoop = lease.id; - toLoop = lease.end_id; - } else if (lease.start_time < SchedulerDateSelected && SchedulerDateSelected < lease.end_time) { - //one hole day (more than 3days) - startLoop = 0; - toLoop = tmpRes.leases.length; - } else if (schedulerCompareOnDay(lease.start_time, SchedulerDateSelected) == 0) { - //the same day and extends - startLoop = lease.id; - toLoop = tmpRes.leases.length; - } else if (schedulerCompareOnDay(lease.end_time, SchedulerDateSelected) == 0) { - //extends to the last say - startLoop = 0; - toLoop = lease.end_id; + console.log(leasesForCommit); + for (var i = 0; i < leasesForCommit.length; i++) { + manifold.raise_event(scheduler2Instance.options.query_lease_uuid, SET_ADD, leasesForCommit[i]); } - //var minutGran = tmpRes.granularity * 60; - for (var li = lease.id; li < toLoop; li++) { - tmpRes.leases[li].status = 'reserved'; - } - - //reserved - //tmpRes.leases[tmpLea.id }, + + // PRIVATE METHODS - _FixTable: function () { - var colWidth = 50; - SchedulerTotalCells = SchedulerSlots.length; - $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", schedulerTblFirstColWidth); //.css("display", "block"); - //this get width might need fix depending on the template - var tblwidth = $('#scheduler-tab').parent().outerWidth(); - SchedulerTotalVisibleCells = parseInt((tblwidth - schedulerTblFirstColWidth) / colWidth); - - //if (SchedulerData.length == 0) { - // //puth some test data - // SchedulerData.push({ name: 'xyz+aaa', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+aaa', type: 'node' }); - // SchedulerData.push({ name: 'xyz+bbb', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+bbb', type: 'node' }); - // SchedulerData.push({ name: 'xyz+ccc', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'xyz+ccc', type: 'node' }); - // SchedulerData.push({ name: 'nitos1', leases: schedulerGetLeases(60 / schedulerSlotsPerHour), urn: 'nitos1', type: 'node' }); - //} - var tmpScope = angular.element(document.getElementById('SchedulerCtrl')).scope(); - tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length); - + /** + * Greatest common divisor + */ + _gcd : function(x, y) + { + return (y==0) ? x : this._gcd(y, x % y); }, - _SetFiletredResources : function (filters) { - if (filters.length > 0) { - SchedulerDataViewData = new Array(); - var tmpAddIt = true; - for (var i = 0; i < SchedulerData.length; i++) { - loopfilters: - for (var f = 0; f < filters.length; f++) { - tmpAddIt = this._FilterResource(SchedulerData[i], filters[f]); - if (tmpAddIt == false) break loopfilters; - } - if (tmpAddIt) { - SchedulerDataViewData.push(SchedulerData[i]); - } - } - } else { - SchedulerDataViewData = SchedulerData; - } + /** + * Least common multiple + */ + _lcm : function(x, y) + { + return x * y / this._gcd(x, y); + }, + + _pad_str : function(i) + { + return (i < 10) ? "0" + i : "" + i; }, - _FilterResource: function (resource, filter) { - var key = filter[0]; - var op = filter[1]; - var value = filter[2]; - var colValue = resource.org_resource[key]; - var ret = true; - if (schedulerDebug && colValue == 'omf') colValue = 'nitos'; - - if (op == '=' || op == '==') { - if (colValue != value || colValue == null || colValue == "" || colValue == "n/a") - ret = false; - } else if (op == 'included') { - $.each(value, function (i, x) { - if (x == colValue) { - ret = true; - return false; - } else { - ret = false; - } - }); - } else if (op == '!=') { - if (colValue == value || colValue == null || colValue == "" || colValue == "n/a") - ret = false; + /** + * Member variables used: + * _granularity + * + * Returns: + * A list of {id, time} dictionaries. + */ + _generate_all_slots: function() + { + var slots = []; + // Start with a random date (a first of a month), only time will matter + var d = new Date(2014, 1, 1, 0, 0, 0, 0); + var i = 0; + // Loop until we change the day + while (d.getDate() == 1) { + // Nicely format the time... + var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes()); + /// ...and add the slot to the list of results + slots.push({ id: i, time: tmpTime }); + // Increment the date with the granularity + d = new Date(d.getTime() + this._granularity * 1000); + i++; } + return slots; - return ret; }, - - _SetPeriodInPage: function (start, end) { - } }); - //Sched2 = new Scheduler2(); - /* Plugin registration */ $.plugin('Scheduler2', scheduler2); - // TODO Here use cases for instanciating plugins in different ways like in the pastie. - - })(jQuery); diff --git a/plugins/scheduler2/static/js/scheduler2_old.js b/plugins/scheduler2/static/js/scheduler2_old.js deleted file mode 100755 index ee833bf3..00000000 --- a/plugins/scheduler2/static/js/scheduler2_old.js +++ /dev/null @@ -1,325 +0,0 @@ -/* -# -# Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece -# -# Permission is hereby granted, free of charge, to any person obtaining a copy -# of this software and associated documentation files (the "Software"), to deal -# in the Software without restriction, including without limitation the rights -# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -# copies of the Software, and to permit persons to whom the Software is -# furnished to do so, subject to the following conditions: -# -# The above copyright notice and this permission notice shall be included in -# all copies or substantial portions of the Software. -# -# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -# THE SOFTWARE. -# -# -# This is a MySlice plugin for the NITOS Scheduler -# Nitos Scheduler v1 -# -*/ - -/* some params */ -var init_start_visible_index = 10; -var init_end_visible_index = 21; -var rsvrTblNm = "scheduler-reservation-table"; -var SchedulerResources = []; -var schdlr_totalColums = 0; -var SetPerFun = null; -var Sched2 = null; -var Debug = true; -var schdlr_PartsInOneHour = 6; - -(function ($) { - var Scheduler2 = Plugin.extend({ - - /** XXX to check - * @brief Plugin constructor - * @param options : an associative array of setting values - * @param element : - * @return : a jQuery collection of objects on which the plugin is - * applied, which allows to maintain chainability of calls - */ - init: function (options, element) { - this.classname="scheduler2"; - // Call the parent constructor, see FAQ when forgotten - this._super(options, element); - - schdlr_totalColums = $("#scheduler-reservation-table th").length; - - //selection from table - $(window).keydown(function (evt) { - if (evt.which == 17) { // ctrl - ctrlPressed = true; - } - }).keyup(function (evt) { - if (evt.which == 17) { // ctrl - ctrlPressed = false; - } - }); - $("#" + rsvrTblNm).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove); - - // Explain this will allow query events to be handled - // What happens when we don't define some events ? - // Some can be less efficient - - if (Debug) console.time("Listening_to_queries"); - /* Listening to queries */ - this.listen_query(options.query_uuid, 'all_ev'); - this.listen_query(options.query_all_resources_uuid, 'all_resources'); - this.listen_query(options.query_lease_uuid, 'lease'); - //this.listen_query(options.query_lease_uuid, 'lease'); - if (Debug) console.timeEnd("Listening_to_queries"); - - $("#ShedulerNodes tbody").html(''); - }, - - /* PLUGIN EVENTS */ - // on_show like in querytable - - - /* GUI EVENTS */ - - // a function to bind events here: click change - // how to raise manifold events - - - /* GUI MANIPULATION */ - - // We advise you to write function to change behaviour of the GUI - // Will use naming helpers to access content _inside_ the plugin - // always refer to these functions in the remaining of the code - - show_hide_button: function () { - // this.id, this.el, this.cl, this.elts - // same output as a jquery selector with some guarantees - }, - - //drawResources: function () { - drawLeases: function () { - - //if (Debug) this.debug('foo'); - if (Debug) console.time("each:SchedulerResources"); - - //scheduler-reservation-table main table columns - totalColums = $("#scheduler-reservation-table thead tr th").length; - //var totalCell = []; - //for (var i = 0; i < totalColums; i++) { totalCell.push(""); } - //var srt_body = []; - var totalCell = ""; - for (var i = 0; i < totalColums; i++) totalCell +=""; - var srt_body = ""; - /* - $.each(SchedulerResources, function (i, group) { - console.log(group.groupName); - //var groupTR = $("#ShedulerNodes tbody").html('
' + group.groupName + '
'); - //var groupTR = $("#ShedulerNodes tbody").html('
' + group.groupName + '
'); - var groupTR = $("#ShedulerNodes tbody").html(''); - - //$.each(group.resources.slice(0,30), function (i, resource) { - $.each(group.resources, function (i, resource) { - if (i == 0) { - //$("#ShedulerNodes tbody tr:first").append('' + resource.hostname + ''); - $(groupTR).find("#schdlr_frstTD").html(resource.urn); - //$(srt_body).html("" + totalCell + ""); - } else { - $(groupTR).find("tr:last").after('' + resource.urn + ''); - //$(srt_body).find("tr:last").after("" + totalCell + ""); - } - srt_body += "" + totalCell + ""; - //srt_body.push(''); srt_body = srt_body.concat(totalCell.concat()); srt_body.push('/'); - }); - }); - */ - - srt_body += "" + totalCell + ""; - //$("#scheduler-reservation-table tbody").html(srt_body.join("")); - //$("#scheduler-reservation-table tbody").append(srt_body); - - if (Debug) console.timeEnd("each:SchedulerResources"); - - - $("#" + rsvrTblNm + " tbody tr").each(function (index) { $(this).attr("data-trindex", index); }); - - }, - - /* TEMPLATES */ - - // see in the html template - // How to load a template, use of mustache - - /* QUERY HANDLERS */ - loadWithDate: function () { - // only convention, not strictly enforced at the moment - }, - // How to make sure the plugin is not desynchronized - // He should manifest its interest in filters, fields or records - // functions triggered only if the proper listen is done - - /* all_ev QUERY HANDLERS Start */ - on_all_ev_clear_records: function (data) { - //alert('all_ev clear_records'); - }, - on_all_ev_query_in_progress: function (data) { - // alert('all_ev query_in_progress'); - }, - on_all_ev_new_record: function (data) { - //alert('all_ev new_record'); - }, - on_all_ev_query_done: function (data) { - //alert('all_ev query_done'); - }, - //another plugin has modified something, that requires you to update your display. - on_all_ev_field_state_changed: function (data) { - //alert('all_ev query_done'); - }, - /* all_ev QUERY HANDLERS End */ - /* all_resources QUERY HANDLERS Start */ - on_all_resources_clear_records: function (data) { - //data is empty on load - }, - on_all_resources_query_in_progress: function (data) { - //data is empty on load - }, - on_all_resources_new_record: function (data) { - $("#ShedulerNodes tbody").find("tr:last").after('' + data.urn + ''); - this.drawLeases(); - //console.log(data); - var tmpGroup = lookup(SchedulerResources, 'groupName', data.type); - if (tmpGroup == null) { - tmpGroup = { groupName: data.type, resources: [] }; - SchedulerResources.push(tmpGroup); - //if (data.type != "node") alert('not all node'); - } - tmpGroup.resources.push(data); - //alert('new_record'); - }, - on_all_resources_query_done: function (data) { - //this.drawResources(); - //data is empty on load - /* GUI setup and event binding */ - this._initUI(); - this._SetPeriodInPage(init_start_visible_index, init_end_visible_index); - this.loadWithDate(); - }, - //another plugin has modified something, that requires you to update your display. - on_all_resources_field_state_changed: function (data) { - //alert('all_resources query_done'); - }, - /* all_resources QUERY HANDLERS End */ - /* lease QUERY HANDLERS Start */ - on_lease_clear_records: function (data) { console.log('clear_records'); }, - on_lease_query_in_progress: function (data) { console.log('lease_query_in_progress'); }, - on_lease_new_record: function (data) { console.log('lease_new_record'); }, - on_lease_query_done: function (data) { console.log('lease_query_done'); }, - //another plugin has modified something, that requires you to update your display. - on_lease_field_state_changed: function (data) { console.log('lease_field_state_changed'); }, - /* lease QUERY HANDLERS End */ - - - // no prefix - - on_filter_added: function (filter) { - - }, - - // ... be sure to list all events here - - /* RECORD HANDLERS */ - on_all_new_record: function (record) { - // - alert('on_all_new_record'); - }, - - debug : function (log_txt) { - if (typeof window.console != 'undefined') { - console.debug(log_txt); - } - }, - - /* INTERNAL FUNCTIONS */ - _initUI: function () { - if (Debug) console.time("_initUI"); - //fix margins in tables - mtNodesTbl = $("#" + rsvrTblNm + " tr:first").outerHeight() + 6; - mtSchrollCon = $("#nodes").outerWidth(); - $("#nodes").css("margin-top", mtNodesTbl); - $("#reservation-table-scroll-container").css("margin-left", mtSchrollCon); - SetPerFun = this._SetPeriodInPage; - //slider - $("#time-range").slider({ - range: true, - min: 0, - max: 24, - step: 0.5, - values: [init_start_visible_index, init_end_visible_index], - slide: function (event, ui) { - SetPerFun(ui.values[0], ui.values[1]); - } - }); - $("#DateToRes").datepicker({ - dateFormat: "yy-mm-dd", - minDate: 0, - numberOfMonths: 3 - }).change(function () { - //Scheduler2.loadWithDate(); - }).click(function () { - $("#ui-datepicker-div").css("z-index", 5); - }); - //other stuff - fixOddEvenClasses(); - $("#" + rsvrTblNm + " td:not([class])").addClass("free"); - if (Debug) console.timeEnd("_initUI"); - }, - _SetPeriodInPage: function (start, end) { - if (Debug) console.time("_SetPeriodInPage"); - ClearTableSelection(); - $("#lbltime").html(GetTimeFromInt(start) + " - " + GetTimeFromInt(end)); - - var start_visible_index = (start * schdlr_PartsInOneHour) + 1; - var end_visible_index = (end * schdlr_PartsInOneHour); - - //hide - show - for (i = 0; i < start_visible_index; i++) { - $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").hide(); - } - for (i = end_visible_index + 1; i <= schdlr_totalColums; i++) { - $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").hide(); - } - /*$("#" + rsvrTblNm + " td:not([class*='info']), #" + rsvrTblNm + " th:not([class*='fixed'])").hide();*/ - for (i = start_visible_index; i <= end_visible_index; i++) { - $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").show(); - } - - if ($("#" + rsvrTblNm + " th:visible:first").width() > 105) { - $("#" + rsvrTblNm + " th span").css("display", "inline") - } else { - $("#" + rsvrTblNm + " th span").css("display", "block"); - } - mtNodesTbl = $("#" + rsvrTblNm + " tr:first").outerHeight() + 6; - $("#nodes").css("margin-top", mtNodesTbl); - //$("#scroll_container").width($("#Search").width() - $("#nodes").width()); - //$("#nodes th").height($("#tblReservation th:visible:first").height() - 2); - if (Debug) console.timeEnd("_SetPeriodInPage"); - } - }); - - //Sched2 = new Scheduler2(); - - /* Plugin registration */ - $.plugin('Scheduler2', Scheduler2); - - // TODO Here use cases for instanciating plugins in different ways like in the pastie. - - -})(jQuery); - - - diff --git a/plugins/scheduler2/static/js/selectRangeWorker.js b/plugins/scheduler2/static/js/selectRangeWorker.js deleted file mode 100755 index 5f282702..00000000 --- a/plugins/scheduler2/static/js/selectRangeWorker.js +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/plugins/scheduler2/templates/scheduler.html b/plugins/scheduler2/templates/scheduler.html index d7cd16e6..81efd746 100755 --- a/plugins/scheduler2/templates/scheduler.html +++ b/plugins/scheduler2/templates/scheduler.html @@ -6,78 +6,71 @@ no data found

no data found...

-