From 1d7b0fb8b69c6eca9880a3966acf0352341882bb Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jordan=20Aug=C3=A9?= Date: Fri, 4 Jul 2014 11:09:46 +0200 Subject: [PATCH] major updates to slice reservation page and plugins --- manifoldapi/static/js/manifold.js | 244 +++-- manifoldapi/static/js/plugin.js | 21 + .../filter_status/static/js/filter_status.js | 38 +- .../templates/filter_status.html | 46 +- plugins/querytable/__init__.py | 28 +- plugins/querytable/static/css/querytable.css | 4 +- plugins/querytable/static/js/querytable.js | 180 +--- plugins/querytable/templates/querytable.html | 16 +- .../queryupdater/static/js/queryupdater.js | 4 + plugins/scheduler2/__init__.py | 12 +- plugins/scheduler2/static/css/scheduler2.css | 26 +- .../static/js/scheduler-SchedulerCtrl.js | 403 -------- plugins/scheduler2/static/js/scheduler2.js | 408 +++++++- .../scheduler2/static/js/selectRangeWorker.js | 1 - plugins/scheduler2/templates/scheduler.html | 42 +- plugins/testbeds/__init__.py | 8 +- plugins/testbeds/static/js/testbeds.js | 151 +-- plugins/testbeds/templates/testbeds.html | 15 +- portal/sliceresourceview.py | 12 +- portal/static/js/myslice-ui.js | 8 +- portal/templates/base.html | 7 +- portal/templates/fed4fire/fed4fire_base.html | 7 +- portal/templates/onelab/onelab_base.html | 7 +- portal/templates/slice-resource-view.html | 42 +- third-party/bootstrap-datepicker | 1 + .../bootstrap-datepicker.js | 962 ++++++++++++++++++ .../bootstrap-datepicker-1/datepicker.css | 514 ++++++++++ third-party/bootstrap-slider | 1 + .../bootstrap-slider-1/bootstrap-slider.js | 776 ++++++++++++++ third-party/bootstrap-slider-1/slider.css | 138 +++ ui/templates/messages-transient-header.html | 10 +- 31 files changed, 3323 insertions(+), 809 deletions(-) delete mode 100755 plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js delete mode 100755 plugins/scheduler2/static/js/selectRangeWorker.js create mode 120000 third-party/bootstrap-datepicker create mode 100644 third-party/bootstrap-datepicker-1/bootstrap-datepicker.js create mode 100644 third-party/bootstrap-datepicker-1/datepicker.css create mode 120000 third-party/bootstrap-slider create mode 100644 third-party/bootstrap-slider-1/bootstrap-slider.js create mode 100644 third-party/bootstrap-slider-1/slider.css diff --git a/manifoldapi/static/js/manifold.js b/manifoldapi/static/js/manifold.js index 15f2123b..b9cd225a 100644 --- a/manifoldapi/static/js/manifold.js +++ b/manifoldapi/static/js/manifold.js @@ -188,6 +188,10 @@ function QueryExt(query, parent_query_ext, main_query_ext, update_query_ext, dis // 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... } @@ -342,10 +346,15 @@ function QueryStore() { this.add_record = function(query_uuid, record, new_state) { - var query_ext = this.find_analyzed_query_ext(query_uuid); + var query_ext, key, record_key; + query_ext = this.find_analyzed_query_ext(query_uuid); - var key = manifold.metadata.get_key(query_ext.query.object); - var record_key = manifold.record_get_value(record, key); + 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) @@ -356,10 +365,15 @@ function QueryStore() { this.remove_record = function(query_uuid, record, new_state) { - var query_ext = this.find_analyzed_query_ext(query_uuid); + var query_ext, key, record_key; + query_ext = this.find_analyzed_query_ext(query_uuid); - var key = manifold.metadata.get_key(query_ext.query.object); - var record_key = manifold.record_get_value(record, key); + 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); } @@ -440,19 +454,78 @@ function QueryStore() { 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]; @@ -470,37 +543,24 @@ function QueryStore() { return true; // ~ continue } - 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); - switch (value) { case 'reserved': - visible = (record_state == STATE_SET_IN) - || (record_state == STATE_SET_OUT_PENDING) - || (record_state == STATE_SET_IN_SUCCESS) - || (record_state == STATE_SET_OUT_FAILURE); - // visible = true => ~ continue - // visible = false => ~ break - return visible; - + // true => ~ continue + // false => ~ break + visible = is_reserved; + return visible; case 'unconfigured': - var 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 - visible = (in_set && !$.isEmptyObject(record_warnings)); - return visible; - + visible = is_unconfigured; + return visible; case 'pending': - visible = (record_state == STATE_SET_IN_PENDING) - || (record_state == STATE_SET_OUT_PENDING); - return visible; + visible = is_pending; + return visible; } return false; // ~ break } /* Normal filtering behaviour (according to the record content) follows... */ - col_value = manifold.record_get_value(record, record_key); + col_value = manifold.record_get_value(record, key); // When the filter does not match, we hide the column by default if (col_value === 'undefined') { @@ -551,6 +611,9 @@ function QueryStore() { self.set_record_state(query_uuid, record_key, STATE_VISIBLE, visible); }); + var end = new Date().getTime(); + console.log("APPLY FILTERS took", end - start, "ms"); + } } @@ -775,7 +838,9 @@ var manifold = { manifold.query_store.insert(query); // Run + $(document).ready(function() { manifold.run_query(query); + }); // FORMER API if (query.analyzed_query == null) { @@ -1082,7 +1147,8 @@ var manifold = { switch (this.get_type(result_value)) { case TYPE_RECORD: var subobject = manifold.metadata.get_type(object, field); - if (subobject) + // 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: @@ -1390,6 +1456,8 @@ 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; @@ -1457,8 +1525,10 @@ 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; @@ -1529,46 +1599,90 @@ var manifold = { /* CONSTRAINTS */ - // CONSTRAINT_RESERVABLE_LEASE - // - // +) If a reservable node is added to the slice, then it should have a corresponding lease - 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']; - 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]; + // 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; } - } 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); + // 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; } - // 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); + // -) When a lease is added, it might remove the warning associated to a reservable node diff --git a/manifoldapi/static/js/plugin.js b/manifoldapi/static/js/plugin.js index da26f1f3..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 diff --git a/plugins/filter_status/static/js/filter_status.js b/plugins/filter_status/static/js/filter_status.js index 9c3fe4fe..d5a13be6 100644 --- a/plugins/filter_status/static/js/filter_status.js +++ b/plugins/filter_status/static/js/filter_status.js @@ -33,6 +33,10 @@ this.elts('list-group-item').click({'instance': this}, this._on_click); this.prev_filter_status = null; + + /* Initialize tooltips */ + $("[rel='tooltip']").tooltip(); + }, /************************************************************************** @@ -52,14 +56,44 @@ // These functions are here to react on external filters, which we don't // use at the moment - on_filter_added: function(filter) { + on_filter_added: function(filter) + { // XXX }, - on_filter_removed: function(filter) { + 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 * **************************************************************************/ diff --git a/plugins/filter_status/templates/filter_status.html b/plugins/filter_status/templates/filter_status.html index 0023796f..b6ce25da 100644 --- a/plugins/filter_status/templates/filter_status.html +++ b/plugins/filter_status/templates/filter_status.html @@ -1,7 +1,43 @@
-Filter on status: -

All

-

Reserved

-

Unconfigured

-

Pending

+ View: + + +

Available

+
+ + +

Reserved

+
+ + +

Unconfigured

+
+ + + +

Pending

+
+
diff --git a/plugins/querytable/__init__.py b/plugins/querytable/__init__.py index 29229064..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 diff --git a/plugins/querytable/static/css/querytable.css b/plugins/querytable/static/css/querytable.css index e4a3308e..68793219 100644 --- a/plugins/querytable/static/css/querytable.css +++ b/plugins/querytable/static/css/querytable.css @@ -1,4 +1,6 @@ - +tr.even { + background-color: #FAFAFA; +} div .added { background-color: #FFFF99; } diff --git a/plugins/querytable/static/js/querytable.js b/plugins/querytable/static/js/querytable.js index 3731f7a9..47e0a97a 100644 --- a/plugins/querytable/static/js/querytable.js +++ b/plugins/querytable/static/js/querytable.js @@ -10,6 +10,13 @@ BGCOLOR_REMOVED = 2; (function($){ + + var QUERYTABLE_MAP = { + 'Testbed': 'network_hrn', + 'Resource name': 'hostname', + 'Type': 'type', + }; + var debug=false; // debug=true @@ -24,17 +31,11 @@ BGCOLOR_REMOVED = 2; // 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); @@ -115,7 +116,7 @@ BGCOLOR_REMOVED = 2; var key = self.canonical_key; // Get the index of the key in the columns - var cols = self.table.fnSettings().aoColumns; + 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 @@ -177,7 +178,8 @@ BGCOLOR_REMOVED = 2; * @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 @@ -209,13 +211,15 @@ BGCOLOR_REMOVED = 2; 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; @@ -224,9 +228,10 @@ BGCOLOR_REMOVED = 2; // 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 - 1; j++) { // nb_col includes status + 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') { @@ -258,7 +263,6 @@ BGCOLOR_REMOVED = 2; line.push(''); } } - line.push(''); // STATUS // adding an array in one call is *much* more efficient // this.table.fnAddData(line); @@ -371,7 +375,7 @@ BGCOLOR_REMOVED = 2; elt.addClass((class_name == BGCOLOR_ADDED ? 'added' : 'removed')); }, - do_filter: function() + populate_table: function() { // Let's clear the table and only add lines that are visible var self = this; @@ -383,7 +387,7 @@ BGCOLOR_REMOVED = 2; lines = Array(); var record_keys = []; - manifold.query_store.iter_visible_records(this.options.query_uuid, function (record_key, record) { + manifold.query_store.iter_records(this.options.query_uuid, function (record_key, record) { lines.push(self.new_record(record)); record_keys.push(record_key); }); @@ -420,29 +424,17 @@ BGCOLOR_REMOVED = 2; on_filter_added: function(filter) { - this.do_filter(); - - /* - this.filters.push(filter); this.redraw_table(); - */ }, on_filter_removed: function(filter) { - this.do_filter(); - /* - // Remove corresponding filters - this.filters = $.grep(this.filters, function(x) { - return x == filter; - }); this.redraw_table(); - */ }, on_filter_clear: function() { - this.do_filter(); + this.redraw_table(); }, on_field_added: function(field) @@ -460,56 +452,8 @@ BGCOLOR_REMOVED = 2; alert('QueryTable::clear_fields() not implemented'); }, - /* XXX TODO: make this generic a plugin has to subscribe to a set of Queries to avoid duplicated code ! */ - /*************************** ALL QUERY HANDLER ****************************/ - - on_all_filter_added: function(filter) - { - this.do_filter(); - }, - - on_all_filter_removed: function(filter) - { - this.do_filter(); - }, - - on_all_filter_clear: function() - { - this.do_filter(); - }, - - on_all_field_added: function(field) - { - this.show_column(field); - }, - - on_all_field_removed: function(field) - { - this.hide_column(field); - }, - - on_all_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() { @@ -518,12 +462,8 @@ BGCOLOR_REMOVED = 2; on_query_done: function() { - this.do_filter(); -/* - 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) @@ -563,62 +503,38 @@ BGCOLOR_REMOVED = 2; /************************** PRIVATE METHODS ***************************/ + _get_columns: function() + { + return this.table.fnSettings().aoColumns; + // XXX return $.map(table.fnSettings().aoColumns, function(x, i) { return QUERYTABLE_MAP[x]; }); + }, + + _get_map: function(column_title) { + return (column_title in QUERYTABLE_MAP) ? QUERYTABLE_MAP[column_title] : column_title; + }, /** - * @brief QueryTable filtering function + * @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) { - 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; - } + var self = this; + var key_col, record_key_value; - }); - return ret; + /* 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]; + + /* Unknown key: no filtering */ + if (typeof(key_col) == 'undefined') { + console.log("Unknown key"); + return true; + } + + record_key_value = unfold.get_value(aData[key_col]); + + 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 ddeb9cf4..a1792b44 100644 --- a/plugins/querytable/templates/querytable.html +++ b/plugins/querytable/templates/querytable.html @@ -2,20 +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 }}status{{ column }}{{ column }}
+/-{{ column }}{{ column }}status{{ column }}{{ column }}
diff --git a/plugins/queryupdater/static/js/queryupdater.js b/plugins/queryupdater/static/js/queryupdater.js index 167e3818..4bd5189f 100644 --- a/plugins/queryupdater/static/js/queryupdater.js +++ b/plugins/queryupdater/static/js/queryupdater.js @@ -396,8 +396,10 @@ 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; break; case STATE_SET_IN_SUCCESS: @@ -437,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); 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/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 27320447..00000000 --- a/plugins/scheduler2/static/js/scheduler-SchedulerCtrl.js +++ /dev/null @@ -1,403 +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); - } - - }; -}); - -myApp.filter('offset', function() { - return function(input, start) { - start = parseInt(start, 10); - return input.slice(start); - }; -}); - -// 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.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(); - }); - - // Jordan -/* - $scope.redraw = function() - { - - // Refresh slots - $scope.slots = []; - for (var i = $scope.from; i < $scope.from + SchedulerTotalVisibleCells; i++) - $scope.slots.push(SchedulerSlots[i]); - - - // Collect lease information. This could be made once if no refresh... - lease_by_resource = {}; - manifold.query_store.iter_visible_records($scope.options.query_lease_uuid, function (record_key, record) { - lease_by_resource[record['resource']] = record; - // Need something to interrupt the loop - }); - - // Create resources - $scope.resources = []; - // current_page, items_per_page indicates which resources to show - manifold.query_store.iter_visible_records($scope.options.query_uuid, function (record_key, record) { - // copy not to modify original record - var resource = jQuery.extend(true, {}, record); - resource.leases = []; // a list of occupied timeslots - - // How many timeslots ? SchedulerTotalVisibleCells - // SchedulerDateSelected - // from : to ?? - // slot duration ? - for (i=0; i < SchedulerTotalVisibleCells; i++) { - resource.leases.push({ - 'id': 'coucou', - 'status': 'free', // 'selected', 'reserved', 'maintenance' - }); - } - - // For each lease we need to mark slots appropriately - if (lease_by_resource[resource['urn']]) { - $.each(lease_by_resource[resource['urn']], function(i, lease) { - // $scope.from * GRANULARITY minutes since start - $scope.from * GRANULARITY - from_date = new Date(date.getTime() + ($scope.from * GRANULARITY) * 60000); - to_date = new Date(date.getTime() + (($scope.from + SchedulerTotalVisibleCells) * GRANULARITY) * 60000); - // start_time, end_time - }); - } - - $scope.resources.push(resource); - $scope.$apply(); - }); - } -*/ - $scope.clearStuff = function() { - $scope.resources = new Array(); - $scope.$apply(); - } - - // Called at initialization, after filtering, and after changing the date. - // this is like setpage(1) ??? -/* - $scope.initSchedulerResources = function (items_per_page) { - $scope.resources = new Array(); - - for (var k = 0; k < items_per_page; k++) { - $scope.resources.push(jQuery.extend(true, {}, SchedulerDataViewData[k])); - $scope.resources[k].leases = []; - } - $scope.items_per_page = items_per_page; - $scope.current_page = 0; - $scope.totalPages = parseInt(Math.ceil(SchedulerDataViewData.length / $scope.items_per_page)); - $scope.initSlots(0, SchedulerTotalVisibleCells); - }; -*/ - - // 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() { - return Math.ceil($scope.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.options.query_uuid, resource['urn'], STATE_VISIBLE); - }; - - // SELECTION - - $scope.select = function(index, model_lease, model_resource) - { - // XXX - // XXX Events won't work until we properly handle sets with composite keys - // XXX - console.log("Selected", index, model_lease, model_resource); - - if (model_lease.status != 'free') { - console.log("Already selected slot"); - return; - } - - 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]; - 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.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.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 - }); - } - - /* Create a new lease */ - new_lease = { - resource: model_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.options.query_lease_uuid, SET_ADD, new_lease); - /* Add to local cache also, unless we listen to events from outside */ - if (!(model_resource.urn in $scope._leases_by_resource)) - $scope._leases_by_resource[model_resource.urn] = []; - $scope._leases_by_resource[model_resource.urn].push(new_lease); - - // XXX Shall we set it or wait for manifold event ? - model_lease.status = 'reserved'; // XXX pending - - // DEBUG: display all leases and their status in the log - var leases = manifold.query_store.get_records($scope.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.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); - }); - }; - - -/* - $scope.setPage = function(page) { - var tmpFrm = $scope.items_per_page * page; - var tmpTo = tmpFrm + $scope.items_per_page; - tmpTo = SchedulerDataViewData.length < tmpTo ? SchedulerDataViewData.length : tmpTo; - $scope.current_page = 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); - };*/ - - // Typically we will only init visible slots - $scope.initSlots = function (from, to) { - return; // JORDAN !!! - - //init - $scope.slots = []; - - var resourceIndex; //gia to paging - //set - for (var i = from; i < to; i++) { - $scope.slots.push(SchedulerSlots[i]); - resourceIndex = $scope.items_per_page * $scope.current_page; - 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.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.current_page <= tmtNumDiv) { - //nothing - } else if ($scope.current_page >= $scope.totalPages - tmtNumDiv) { - numTo = $scope.totalPages; - numFrom = numTo - totalNumbersShowned; - } else { - numFrom = $scope.current_page - 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); diff --git a/plugins/scheduler2/static/js/scheduler2.js b/plugins/scheduler2/static/js/scheduler2.js index fe8e3e8c..5f35a0fd 100755 --- a/plugins/scheduler2/static/js/scheduler2.js +++ b/plugins/scheduler2/static/js/scheduler2.js @@ -35,7 +35,7 @@ var scheduler2Instance; var schedulerCtrlPressed = false; //table Id var schedulerTblId = "scheduler-reservation-table"; -var SCHEDULER_FIRST_COLWIDTH = 150; +var SCHEDULER_FIRST_COLWIDTH = 200; /* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */ @@ -69,6 +69,331 @@ 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({ @@ -80,11 +405,11 @@ var SCHEDULER_COLWIDTH = 50; * 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; @@ -120,15 +445,35 @@ var SCHEDULER_COLWIDTH = 50; this.listen_query(options.query_uuid, 'resources'); this.listen_query(options.query_lease_uuid, 'leases'); + 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(); + // 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(); + + }, + + do_resize: function() + { + var scope = this._get_scope(); + $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH); - //this get width might need fix depending on the template - var tblwidth = $('#scheduler-tab').parent().outerWidth(); + //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); @@ -140,15 +485,27 @@ var SCHEDULER_COLWIDTH = 50; scope.num_visible_cells = this._num_visible_cells; scope.lcm_colspan = this._lcm_colspan; - scope.options = this.options; - scope.from = 0; + // Slider max value - // A list of {id, time} dictionaries representing the slots for the given day - scope.slots = this._all_slots; - this.scope_resources_by_key = {}; + 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); + } - this._initUI(); + }, + on_show: function(e) + { + var self = e.data; + self.do_resize(); + self._get_scope().$apply(); + }, + + on_resize: function(e) + { + var self = e.data; + self.do_resize(); + self._get_scope().$apply(); }, /* Handlers */ @@ -299,39 +656,32 @@ var SCHEDULER_COLWIDTH = 50; var self = this; $("#DateToRes").datepicker({ - dateFormat: "yy-mm-dd", - minDate: 0, - numberOfMonths: 3 - }).change(function() { - // the selected date - SchedulerDateSelected = $("#DateToRes").datepicker("getDate"); - if (SchedulerDateSelected == null || SchedulerDateSelected == '') { - alert("Please select a date, so the scheduler can reserve leases."); - return; + onRender: function(date) { + return date.valueOf() < now.valueOf() ? 'disabled' : ''; } + }).on('changeDate', function(ev) { + SchedulerDateSelected = new Date(ev.date); + SchedulerDateSelected.setHours(0,0,0,0); // Set slider to origin - $('#tblSlider').slider('value', 0); + $('#tblSlider').slider('setValue', 0); // XXX // Refresh leases self._scope_clear_leases(); self._scope_set_leases(); // Refresh display self._get_scope().$apply(); - }).datepicker('setDate', SchedulerDateSelected); + }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker'); //init Slider $('#tblSlider').slider({ min: 0, - max: (this._all_slots.length - self._num_visible_cells) / self._lcm_colspan, + max: (self._all_slots.length - self._num_visible_cells) / self._lcm_colspan, value: 0, - slide: function(event, ui) { - var scope = self._get_scope(); - scope.from = ui.value * self._lcm_colspan; - scope.$apply(); - } + }).on('slide', function(ev) { + var scope = self._get_scope(); + scope.from = ev.value * self._lcm_colspan; + scope.$apply(); }); - $('#btnSchedulerSubmit').click(this._on_submit); - $("#plugin-scheduler-loader").hide(); $("#plugin-scheduler").show(); }, 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 95c6ba7c..81efd746 100755 --- a/plugins/scheduler2/templates/scheduler.html +++ b/plugins/scheduler2/templates/scheduler.html @@ -6,55 +6,27 @@ no data found

no data found...

-