#\r
*/\r
\r
+// XXX groupid = all slots those that go with a min granularity\r
+\r
/* some params */\r
-var init_start_visible_index = 10;\r
-var init_end_visible_index = 21;\r
-var rsvrTblNm = "scheduler-reservation-table";\r
-var SchedulerResources = [];\r
-var schdlr_totalColums = 0;\r
-var SetPerFun = null;\r
-var Sched2 = null;\r
-var Debug = true;\r
-var schdlr_PartsInOneHour = 6;\r
-\r
-(function ($) {\r
- var Scheduler2 = Plugin.extend({\r
-\r
- /** XXX to check\r
+var scheduler2;\r
+var scheduler2Instance;\r
+//is ctrl keyboard button pressed\r
+var schedulerCtrlPressed = false;\r
+//table Id\r
+var schedulerTblId = "scheduler-reservation-table";\r
+var SCHEDULER_FIRST_COLWIDTH = 200;\r
+\r
+\r
+/* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */\r
+var schedulerSlotsPerHour = 6;\r
+var RESOURCE_DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information\r
+var DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information. Test with 600\r
+var DEFAULT_PAGE_RANGE = 5;\r
+\r
+var schedulerMaxRows = 12;\r
+\r
+/* All resources */\r
+var SchedulerData = [];\r
+\r
+/* ??? */\r
+var SchedulerSlots = [];\r
+\r
+var SchedulerDateSelected = new Date();\r
+// Round to midnight\r
+SchedulerDateSelected.setHours(0,0,0,0);\r
+\r
+/* Filtered resources */\r
+var SchedulerDataViewData = [];\r
+\r
+var SchedulerSlotsViewData = [];\r
+//Help Variables\r
+var _schedulerCurrentCellPosition = 0;\r
+//Enable Debug\r
+var schedulerDebug = true;\r
+//tmp to delete\r
+var tmpSchedulerLeases = [];\r
+\r
+var SCHEDULER_COLWIDTH = 50;\r
+\r
+\r
+/******************************************************************************\r
+ * ANGULAR CONTROLLER *\r
+ ******************************************************************************/\r
+\r
+// Create a private execution space for our controller. When\r
+// executing this function expression, we're going to pass in\r
+// the Angular reference and our application module.\r
+(function (ng, app) {\r
+\r
+ // Define our Controller constructor.\r
+ function Controller($scope) {\r
+\r
+ // Store the scope so we can reference it in our\r
+ // class methods\r
+ this.scope = $scope;\r
+\r
+ // Set up the default scope value.\r
+ this.scope.errorMessage = null;\r
+ this.scope.name = "";\r
+\r
+ //Pagin\r
+ $scope.current_page = 1;\r
+ this.scope.items_per_page = 10;\r
+ $scope.from = 0; // JORDAN\r
+\r
+ $scope.instance = null;\r
+ $scope.resources = new Array();\r
+ $scope.slots = SchedulerSlotsViewData;\r
+ $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */\r
+ //$scope.msg = "hello";\r
+\r
+ angular.element(document).ready(function() {\r
+ //console.log('Hello World');\r
+ //alert('Hello World');\r
+ //afterAngularRendered();\r
+ });\r
+\r
+ // Pagination\r
+\r
+ $scope.range = function() {\r
+ var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();\r
+ var ret = [];\r
+ var start;\r
+\r
+ start = $scope.current_page;\r
+ if ( start > $scope.page_count()-range_size ) {\r
+ start = $scope.page_count()-range_size+1;\r
+ }\r
+\r
+ for (var i=start; i<start+range_size; i++) {\r
+ ret.push(i);\r
+ }\r
+ return ret;\r
+ };\r
+\r
+ $scope.prevPage = function() {\r
+ if ($scope.current_page > 1) {\r
+ $scope.current_page--;\r
+ }\r
+ };\r
+\r
+ $scope.prevPageDisabled = function() {\r
+ return $scope.current_page === 1 ? "disabled" : "";\r
+ };\r
+ \r
+ $scope.page_count = function()\r
+ {\r
+ // XXX need visible resources only\r
+ var query_ext, visible_resources_length;\r
+ if (!$scope.instance)\r
+ return 0;\r
+ query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid);\r
+ var visible_resources_length = 0;\r
+ query_ext.state.each(function(i, state) {\r
+ if (state[STATE_VISIBLE])\r
+ visible_resources_length++;\r
+ });\r
+ return Math.ceil(visible_resources_length/$scope.items_per_page);\r
+ };\r
+ \r
+ $scope.nextPage = function() {\r
+ if ($scope.current_page < $scope.page_count()) {\r
+ $scope.current_page++;\r
+ }\r
+ };\r
+ \r
+ $scope.nextPageDisabled = function() {\r
+ return $scope.current_page === $scope.page_count() ? "disabled" : "";\r
+ }; \r
+\r
+ $scope.setPage = function(n) {\r
+ $scope.current_page = n;\r
+ };\r
+ // END pagination\r
+\r
+ // FILTER\r
+\r
+ $scope.filter_visible = function(resource)\r
+ {\r
+ return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);\r
+ };\r
+\r
+ // SELECTION\r
+\r
+ $scope._create_new_lease = function(resource_urn, start_time, end_time)\r
+ {\r
+ var lease_key, new_lease, data;\r
+\r
+ lease_key = manifold.metadata.get_key('lease');\r
+\r
+ new_lease = {\r
+ resource: resource_urn,\r
+ start_time: start_time,\r
+ end_time: end_time,\r
+ };\r
+\r
+ // This is needed to create a hashable object\r
+ new_lease.hashCode = manifold.record_hashcode(lease_key.sort());\r
+ new_lease.equals = manifold.record_equals(lease_key);\r
+\r
+ data = {\r
+ state: STATE_SET,\r
+ key : null,\r
+ op : STATE_SET_ADD,\r
+ value: new_lease\r
+ }\r
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+ /* Add to local cache also, unless we listen to events from outside */\r
+ if (!(resource_urn in $scope._leases_by_resource))\r
+ $scope._leases_by_resource[resource_urn] = [];\r
+ $scope._leases_by_resource[resource_urn].push(new_lease);\r
+ }\r
+\r
+ $scope._remove_lease = function(other)\r
+ {\r
+ var lease_key, other_key, data;\r
+\r
+ lease_key = manifold.metadata.get_key('lease');\r
+\r
+ // XXX This could be a manifold.record_get_value\r
+ other_key = {\r
+ resource: other.resource,\r
+ start_time: other.start_time,\r
+ end_time: other.end_time\r
+ }\r
+ other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+ other_key.equals = manifold.record_equals(lease_key);\r
+\r
+ data = {\r
+ state: STATE_SET,\r
+ key : null,\r
+ op : STATE_SET_REMOVE,\r
+ value: other_key\r
+ }\r
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+ /* Remove from local cache also, unless we listen to events from outside */\r
+ $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });\r
+\r
+ }\r
+\r
+ $scope.select = function(index, model_lease, model_resource)\r
+ {\r
+ var data, resource_granularity;\r
+\r
+ //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;\r
+\r
+ console.log("Selected", index, model_lease, model_resource);\r
+\r
+ var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
+ var start_time = day_timestamp + index * model_resource.granularity; // XXX resource_granularity\r
+ var end_time = day_timestamp + (index + 1) * model_resource.granularity; //\r
+ var start_date = new Date(start_time * 1000);\r
+ var end_date = new Date(end_time * 1000);\r
+\r
+ var lease_key = manifold.metadata.get_key('lease');\r
+\r
+ // We search for leases in the cache we previously constructed\r
+ var resource_leases = $scope._leases_by_resource[model_resource.urn];\r
+\r
+ switch (model_lease.status)\r
+ {\r
+ case 'free': // out\r
+ case 'pendingout':\r
+ if (resource_leases) {\r
+ /* Search for leases before */\r
+ $.each(resource_leases, function(i, other) {\r
+ if (other.end_time != start_time)\r
+ return true; // ~ continue\r
+ \r
+ /* The lease 'other' is just before, and there should not exist\r
+ * any other lease before it */\r
+ start_time = other.start_time;\r
+ \r
+ other_key = {\r
+ resource: other.resource,\r
+ start_time: other.start_time,\r
+ end_time: other.end_time\r
+ }\r
+ // This is needed to create a hashable object\r
+ other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+ other_key.equals = manifold.record_equals(lease_key);\r
+ \r
+ data = {\r
+ state: STATE_SET,\r
+ key : null,\r
+ op : STATE_SET_REMOVE,\r
+ value: other_key\r
+ }\r
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+ /* Remove from local cache also, unless we listen to events from outside */\r
+ $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
+ return false; // ~ break\r
+ });\r
+ \r
+ /* Search for leases after */\r
+ $.each(resource_leases, function(i, other) {\r
+ if (other.start_time != end_time)\r
+ return true; // ~ continue\r
+ \r
+ /* The lease 'other' is just after, and there should not exist\r
+ * any other lease after it */\r
+ end_time = other.end_time;\r
+ other_key = {\r
+ resource: other.resource,\r
+ start_time: other.start_time,\r
+ end_time: other.end_time\r
+ }\r
+ // This is needed to create a hashable object\r
+ other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
+ other_key.equals = manifold.record_equals(lease_key);\r
+ \r
+ data = {\r
+ state: STATE_SET,\r
+ key : null,\r
+ op : STATE_SET_REMOVE,\r
+ value: other_key\r
+ }\r
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
+ /* Remove from local cache also, unless we listen to events from outside */\r
+ $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
+ return false; // ~ break\r
+ });\r
+ }\r
+ \r
+ $scope._create_new_lease(model_resource.urn, start_time, end_time);\r
+ model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';\r
+ // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)\r
+\r
+ break;\r
+\r
+ case 'selected':\r
+ case 'pendingin':\r
+ // We remove the cell\r
+\r
+ /* We search for leases including this cell. Either 0, 1 or 2.\r
+ * 0 : NOT POSSIBLE, should be checked.\r
+ * 1 : either IN or OUT, we have make no change in the session\r
+ * 2 : both will be pending, since we have made a change in the session\r
+ * /!\ need to properly remove pending_in leases when removed again\r
+ */\r
+ if (resource_leases) {\r
+ $.each(resource_leases, function(i, other) {\r
+ if ((other.start_time <= start_time) && (other.end_time >= end_time)) {\r
+ // The cell is part of this lease.\r
+\r
+ // If the cell is not at the beginning of the lease, we recreate a lease with cells before\r
+ if (start_time > other.start_time) {\r
+ $scope._create_new_lease(model_resource.urn, other.start_time, start_time);\r
+ }\r
+\r
+ // If the cell is not at the end of the lease, we recreate a lease with cells after\r
+ if (end_time < other.end_time) {\r
+ $scope._create_new_lease(model_resource.urn, end_time, other.end_time);\r
+ }\r
+ \r
+ // The other lease will be removed\r
+ $scope._remove_lease(other);\r
+ }\r
+ // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).\r
+ });\r
+ }\r
+ \r
+ // cf comment in previous switch case\r
+ model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';\r
+\r
+ break;\r
+\r
+ case 'reserved':\r
+ case 'maintainance':\r
+ // Do nothing\r
+ break;\r
+ }\r
+ \r
+\r
+ $scope._dump_leases();\r
+ };\r
+ \r
+ $scope._dump_leases = function()\r
+ {\r
+ // DEBUG: display all leases and their status in the log\r
+ var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);\r
+ console.log("--------------------");\r
+ $.each(leases, function(i, lease) {\r
+ var key = manifold.metadata.get_key('lease');\r
+ var lease_key = manifold.record_get_value(lease, key);\r
+ var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);\r
+ var state_str;\r
+ switch(state) {\r
+ case STATE_SET_IN:\r
+ state_str = 'STATE_SET_IN';\r
+ break;\r
+ case STATE_SET_OUT:\r
+ state_str = 'STATE_SET_OUT';\r
+ break;\r
+ case STATE_SET_IN_PENDING:\r
+ state_str = 'STATE_SET_IN_PENDING';\r
+ break;\r
+ case STATE_SET_OUT_PENDING:\r
+ state_str = 'STATE_SET_OUT_PENDING';\r
+ break;\r
+ case STATE_SET_IN_SUCCESS:\r
+ state_str = 'STATE_SET_IN_SUCCESS';\r
+ break;\r
+ case STATE_SET_OUT_SUCCESS:\r
+ state_str = 'STATE_SET_OUT_SUCCESS';\r
+ break;\r
+ case STATE_SET_IN_FAILURE:\r
+ state_str = 'STATE_SET_IN_FAILURE';\r
+ break;\r
+ case STATE_SET_OUT_FAILURE:\r
+ state_str = 'STATE_SET_OUT_FAILURE';\r
+ break;\r
+ }\r
+ console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);\r
+ });\r
+ };\r
+\r
+ // Return this object reference.\r
+ return (this);\r
+\r
+ }\r
+\r
+ // Define the Controller as the constructor function.\r
+ app.controller("SchedulerCtrl", Controller);\r
+\r
+})(angular, ManifoldApp);\r
+\r
+/******************************************************************************\r
+ * MANIFOLD PLUGIN *\r
+ ******************************************************************************/\r
+\r
+(function($) {\r
+ scheduler2 = Plugin.extend({\r
+\r
+ /** XXX to check\r
* @brief Plugin constructor\r
* @param options : an associative array of setting values\r
* @param element : \r
* @return : a jQuery collection of objects on which the plugin is\r
* applied, which allows to maintain chainability of calls\r
*/\r
- init: function (options, element) {\r
- this.classname="scheduler2";\r
- // Call the parent constructor, see FAQ when forgotten\r
- this._super(options, element);\r
+ init: function(options, element) {\r
+ // Call the parent constructor, see FAQ when forgotten\r
+ this._super(options, element);\r
\r
- schdlr_totalColums = $("#scheduler-reservation-table th").length;\r
+ var scope = this._get_scope()\r
+ scope.instance = this;\r
\r
- //selection from table \r
- $(window).keydown(function (evt) {\r
- if (evt.which == 17) { // ctrl\r
- ctrlPressed = true;\r
- }\r
- }).keyup(function (evt) {\r
- if (evt.which == 17) { // ctrl\r
- ctrlPressed = false;\r
- }\r
- });\r
- $("#" + rsvrTblNm).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
+ // XXX not needed\r
+ scheduler2Instance = this;\r
\r
- // Explain this will allow query events to be handled\r
- // What happens when we don't define some events ?\r
- // Some can be less efficient\r
+ // We need to remember the active filter for datatables filtering\r
+ // XXX not needed\r
+ this.filters = Array();\r
\r
- if (Debug) console.time("Listening_to_queries");\r
- /* Listening to queries */\r
- this.listen_query(options.query_uuid, 'all_ev');\r
- this.listen_query(options.query_all_resources_uuid, 'all_resources');\r
- this.listen_query(options.query_lease_uuid, 'lease');\r
- //this.listen_query(options.query_lease_uuid, 'lease');\r
- if (Debug) console.timeEnd("Listening_to_queries");\r
+ // XXX BETTER !!!!\r
+ $(window).delegate('*', 'keypress', function (evt){\r
+ alert("erm");\r
+ });\r
\r
- $("#ShedulerNodes tbody").html('<tr><td id="schdlr_frstTD" style="background:transparent; height:45px; border:none;"></td></tr>');\r
- },\r
+ $(window).keydown(function(evt) {\r
+ if (evt.which == 17) { // ctrl\r
+ schedulerCtrlPressed = true;\r
+ }\r
+ }).keyup(function(evt) {\r
+ if (evt.which == 17) { // ctrl\r
+ schedulerCtrlPressed = false;\r
+ }\r
+ });\r
+\r
+ // XXX naming\r
+ //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
\r
- /* PLUGIN EVENTS */\r
- // on_show like in querytable\r
+ this._resources_received = false;\r
+ this._leases_received = false;\r
+ \r
+ scope._leases_by_resource = {};\r
\r
+ /* Listening to queries */\r
+ this.listen_query(options.query_uuid, 'resources');\r
+ this.listen_query(options.query_lease_uuid, 'leases');\r
\r
- /* GUI EVENTS */\r
+ this.elmt().on('show', this, this.on_show);\r
+ this.elmt().on('shown.bs.tab', this, this.on_show);\r
+ this.elmt().on('resize', this, this.on_resize);\r
\r
- // a function to bind events here: click change\r
- // how to raise manifold events\r
+ /* Generate slots according to the default granularity. Should\r
+ * be updated when resources arrive. Should be the pgcd in fact XXX */\r
+ this._granularity = DEFAULT_GRANULARITY;\r
+ scope.granularity = this._granularity;\r
+ this.scope_resources_by_key = {};\r
\r
+ this.do_resize();\r
+ \r
+ scope.from = 0;\r
\r
- /* GUI MANIPULATION */\r
+ this._initUI();\r
\r
- // We advise you to write function to change behaviour of the GUI\r
- // Will use naming helpers to access content _inside_ the plugin\r
- // always refer to these functions in the remaining of the code\r
+ },\r
\r
- show_hide_button: function () {\r
- // this.id, this.el, this.cl, this.elts\r
- // same output as a jquery selector with some guarantees\r
- },\r
+ do_resize: function()\r
+ {\r
+ var scope = this._get_scope();\r
+ var num_hidden_cells, new_max, lcm;\r
+\r
+ // do_resize has to be called when the window is resized, or one parameter changes\r
+ // e.g. when new resources have been received\r
+ //\r
+ this.resource_granularities = [3600, 1800]; //, 2400]; /* s */\r
+\r
+ /* Compute the slot length to accommodate all resources. This\r
+ * is the GCD of all resource granularities. */\r
+ this._slot_length = this._gcdn(this.resource_granularities);\r
+\r
+ $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);\r
+ //self get width might need fix depending on the template \r
+ var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();\r
\r
- //drawResources: function () {\r
- drawLeases: function () {\r
+ /* Number of visible cells...*/\r
+ this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);\r
+\r
+ /* ...should be a multiple of the lcm of all encountered granularities. */\r
+ lcm = this._lcmn(this.resource_granularities) / this._slot_length;\r
+ this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;\r
+\r
+ // A list of {id, time} dictionaries representing the slots for the given day\r
+ this._all_slots = this._generate_all_slots();\r
+\r
+ /* scope also needs this value */\r
+ scope.slots = this._all_slots;\r
+ scope.slot_length = this._slot_length;\r
+ scope.num_visible_cells = this._num_visible_cells;\r
+ scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?\r
+\r
+ /* Redraw... */\r
+ this._scope_clear_leases();\r
+ this._set_all_lease_slots();\r
+\r
+ // Slider max value\r
+ if ($('#tblSlider').data('slider') != undefined) {\r
+ num_hidden_cells = this._all_slots.length - this._num_visible_cells;\r
+\r
+ $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);\r
+ $('#tblSlider').slider('setValue', scope.from, true);\r
+ }\r
+ this._get_scope().$apply();\r
+\r
+\r
+ },\r
+\r
+ on_show: function(e)\r
+ {\r
+ var self = e.data;\r
+ self.do_resize();\r
+ self._get_scope().$apply();\r
+ },\r
+\r
+ on_resize: function(e)\r
+ {\r
+ var self = e.data;\r
+ self.do_resize();\r
+ self._get_scope().$apply();\r
+ },\r
+\r
+ /* Handlers */\r
+\r
+ _get_scope : function()\r
+ {\r
+ return angular.element(document.getElementById('SchedulerCtrl')).scope();\r
+ },\r
\r
- //if (Debug) this.debug('foo');\r
- if (Debug) console.time("each:SchedulerResources");\r
-\r
- //scheduler-reservation-table main table columns\r
- totalColums = $("#scheduler-reservation-table thead tr th").length;\r
- //var totalCell = [];\r
- //for (var i = 0; i < totalColums; i++) { totalCell.push("<td></td>"); }\r
- //var srt_body = [];\r
- var totalCell = "";\r
- for (var i = 0; i < totalColums; i++) totalCell +="<td></td>"; \r
- var srt_body = "";\r
- /*\r
- $.each(SchedulerResources, function (i, group) {\r
- console.log(group.groupName);\r
- //var groupTR = $("#ShedulerNodes tbody").html('<tr><td class="no-image verticalIndex" rowspan="' + group.resources.length + '"><div class="verticalText">' + group.groupName + '</div></td><td id="schdlr_frstTD" class="info fixed"></td></tr>');\r
- //var groupTR = $("#ShedulerNodes tbody").html('<tr><td class="no-image verticalIndex" rowspan="' + 30 + '"><div class="verticalText">' + group.groupName + '</div></td><td id="schdlr_frstTD" class="info fixed"></td></tr>');\r
- var groupTR = $("#ShedulerNodes tbody").html('<tr><td id="schdlr_frstTD" class="info fixed"></td></tr>');\r
- \r
- //$.each(group.resources.slice(0,30), function (i, resource) {\r
- $.each(group.resources, function (i, resource) {\r
- if (i == 0) {\r
- //$("#ShedulerNodes tbody tr:first").append('<td class="info fixed">' + resource.hostname + '</td>');\r
- $(groupTR).find("#schdlr_frstTD").html(resource.urn);\r
- //$(srt_body).html("<tr>" + totalCell + "</tr>");\r
- } else {\r
- $(groupTR).find("tr:last").after('<tr><td class="info fixed">' + resource.urn + '</td></tr>');\r
- //$(srt_body).find("tr:last").after("<tr>" + totalCell + "</tr>");\r
+ _scope_set_resources : function()\r
+ {\r
+ var self = this;\r
+ var scope = this._get_scope();\r
+\r
+ var records = manifold.query_store.get_records(this.options.query_uuid);\r
+\r
+ scope.resources = [];\r
+\r
+ $.each(records, function(i, record) {\r
+ if (!record.exclusive)\r
+ return true; // ~ continue\r
+\r
+ // copy not to modify original record\r
+ var resource = jQuery.extend(true, {}, record);\r
+\r
+ // Fix granularity\r
+ //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;\r
+ if (typeof(resource.granularity) != "number")\r
+ resource.granularity = RESOURCE_DEFAULT_GRANULARITY;\r
+ resource.leases = []; // a list of occupied timeslots\r
+\r
+ self.scope_resources_by_key[resource['urn']] = resource;\r
+ scope.resources.push(resource);\r
+ });\r
+ },\r
+\r
+ _scope_clear_leases: function()\r
+ {\r
+ var time, now;\r
+ var self = this;\r
+ var scope = this._get_scope();\r
+\r
+ now = new Date().getTime();\r
+\r
+ // Setup leases with a default free status...\r
+ $.each(this.scope_resources_by_key, function(resource_key, resource) {\r
+ resource.leases = [];\r
+ var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells\r
+ time = SchedulerDateSelected.getTime();\r
+ for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity\r
+ resource.leases.push({\r
+ id: 'coucou',\r
+ status: (time < now) ? 'disabled': 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??\r
+ });\r
+ time += resource.granularity * 1000;\r
}\r
- srt_body += "<tr>" + totalCell + "</tr>";\r
- //srt_body.push('<tr>'); srt_body = srt_body.concat(totalCell.concat()); srt_body.push('/<tr>');\r
});\r
- });\r
- */\r
- srt_body += "<tr>" + totalCell + "</tr>";\r
- //$("#scheduler-reservation-table tbody").html(srt_body.join(""));\r
- $("#scheduler-reservation-table tbody").append(srt_body);\r
\r
- if (Debug) console.timeEnd("each:SchedulerResources");\r
+ },\r
+\r
+ _scope_set_leases: function()\r
+ {\r
+ var status;\r
+ var self = this;\r
+ var scope = this._get_scope();\r
\r
+ manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {\r
+ console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));\r
+ // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work\r
\r
- $("#" + rsvrTblNm + " tbody tr").each(function (index) { $(this).attr("data-trindex", index); });\r
+ // Populate leases by resource array: this will help us merging leases later\r
\r
- },\r
+ // let's only put _our_ leases\r
+ lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);\r
+ if (lease_status != STATE_SET_IN)\r
+ return true; // ~continue\r
+ if (!(lease.resource in scope._leases_by_resource))\r
+ scope._leases_by_resource[lease.resource] = [];\r
+ scope._leases_by_resource[lease.resource].push(lease);\r
\r
- /* TEMPLATES */\r
+ });\r
\r
- // see in the html template\r
- // How to load a template, use of mustache\r
+ this._set_all_lease_slots();\r
+ },\r
\r
- /* QUERY HANDLERS */\r
- loadWithDate: function () {\r
- // only convention, not strictly enforced at the moment\r
- },\r
- // How to make sure the plugin is not desynchronized\r
- // He should manifest its interest in filters, fields or records\r
- // functions triggered only if the proper listen is done\r
+ _set_all_lease_slots: function()\r
+ {\r
+ var self = this;\r
+ \r
+ manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {\r
+ self._set_lease_slots(lease_key, lease);\r
+ });\r
+ },\r
+\r
+ on_resources_query_done: function(data)\r
+ {\r
+ this._resources_received = true;\r
+ this._scope_set_resources();\r
+ this._scope_clear_leases();\r
+ if (this._leases_received)\r
+ this._scope_set_leases();\r
+ \r
+ this._get_scope().$apply();\r
+ },\r
+\r
+ on_leases_query_done: function(data)\r
+ {\r
+ this._leases_received = true;\r
+ if (this._resources_received) {\r
+ this._scope_set_leases();\r
+ this._get_scope().$apply();\r
+ }\r
+ },\r
+\r
+ /* Filters on resources */\r
+ on_resources_filter_added: function(filter) { this._get_scope().$apply(); },\r
+ on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },\r
+ on_resources_filter_clear: function() { this._get_scope().$apply(); },\r
+\r
+ /* Filters on leases ? */\r
+ on_leases_filter_added: function(filter) { this._get_scope().$apply(); },\r
+ on_leases_filter_removed: function(filter) { this._get_scope().$apply(); },\r
+ on_leases_filter_clear: function() { this._get_scope().$apply(); },\r
+\r
+ on_field_state_changed: function(data)\r
+ {\r
+ /*\r
+ this._set_lease_slots(lease_key, lease);\r
+\r
+ switch(data.state) {\r
+ case STATE_SET:\r
+ switch(data.op) {\r
+ case STATE_SET_IN:\r
+ case STATE_SET_IN_SUCCESS:\r
+ case STATE_SET_OUT_FAILURE:\r
+ this.set_checkbox_from_data(data.value, true);\r
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);\r
+ break; \r
+ case STATE_SET_OUT:\r
+ case STATE_SET_OUT_SUCCESS:\r
+ case STATE_SET_IN_FAILURE:\r
+ this.set_checkbox_from_data(data.value, false);\r
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);\r
+ break;\r
+ case STATE_SET_IN_PENDING:\r
+ this.set_checkbox_from_data(data.key, true);\r
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);\r
+ break; \r
+ case STATE_SET_OUT_PENDING:\r
+ this.set_checkbox_from_data(data.key, false);\r
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);\r
+ break;\r
+ }\r
+ break;\r
+\r
+ case STATE_WARNINGS:\r
+ this.change_status(data.key, data.value);\r
+ break;\r
+ }\r
+ */\r
+ },\r
\r
- /* all_ev QUERY HANDLERS Start */\r
- on_all_ev_clear_records: function (data) {\r
- //alert('all_ev clear_records');\r
- },\r
- on_all_ev_query_in_progress: function (data) {\r
- // alert('all_ev query_in_progress');\r
- },\r
- on_all_ev_new_record: function (data) {\r
- //alert('all_ev new_record');\r
- },\r
- on_all_ev_query_done: function (data) {\r
- //alert('all_ev query_done');\r
- },\r
- //another plugin has modified something, that requires you to update your display. \r
- on_all_ev_field_state_changed: function (data) {\r
- //alert('all_ev query_done');\r
- },\r
- /* all_ev QUERY HANDLERS End */\r
- /* all_resources QUERY HANDLERS Start */\r
- on_all_resources_clear_records: function (data) {\r
- //data is empty on load\r
- },\r
- on_all_resources_query_in_progress: function (data) {\r
- //data is empty on load\r
- },\r
- on_all_resources_new_record: function (data) {\r
- $("#ShedulerNodes tbody").find("tr:last").after('<tr><td class="info fixed">' + data.urn + '</td></tr>');\r
- this.drawLeases();\r
- //console.log(data);\r
- var tmpGroup = lookup(SchedulerResources, 'groupName', data.type);\r
- if (tmpGroup == null) {\r
- tmpGroup = { groupName: data.type, resources: [] };\r
- SchedulerResources.push(tmpGroup);\r
- //if (data.type != "node") alert('not all node');\r
- }\r
- tmpGroup.resources.push(data);\r
- //alert('new_record');\r
- },\r
- on_all_resources_query_done: function (data) {\r
- //this.drawResources();\r
- //data is empty on load\r
- /* GUI setup and event binding */\r
- this._initUI();\r
- this._SetPeriodInPage(init_start_visible_index, init_end_visible_index);\r
- this.loadWithDate();\r
- },\r
- //another plugin has modified something, that requires you to update your display. \r
- on_all_resources_field_state_changed: function (data) {\r
- //alert('all_resources query_done');\r
- },\r
- /* all_resources QUERY HANDLERS End */\r
- /* lease QUERY HANDLERS Start */\r
- on_lease_clear_records: function (data) { console.log('clear_records'); },\r
- on_lease_query_in_progress: function (data) { console.log('lease_query_in_progress'); },\r
- on_lease_new_record: function (data) { console.log('lease_new_record'); },\r
- on_lease_query_done: function (data) { console.log('lease_query_done'); },\r
- //another plugin has modified something, that requires you to update your display. \r
- on_lease_field_state_changed: function (data) { console.log('lease_field_state_changed'); },\r
- /* lease QUERY HANDLERS End */\r
\r
+ /* INTERNAL FUNCTIONS */\r
\r
- // no prefix\r
+ _set_lease_slots: function(lease_key, lease)\r
+ {\r
+ var resource, lease_status, lease_class;\r
+ var day_timestamp, id_start, id_end, colspan_lease;\r
\r
- on_filter_added: function (filter) {\r
+ resource = this.scope_resources_by_key[lease.resource];\r
+ day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
+ id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);\r
\r
- },\r
+ /* Some leases might be in the past */\r
+ if (id_start < 0)\r
+ id_start = 0;\r
+ /* Leases in the future: ignore */\r
+ if (id_start >= this._all_slots.length)\r
+ return true; // ~ continue\r
+\r
+ id_end = Math.ceil((lease.end_time - day_timestamp) / resource.granularity);\r
+ colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells\r
+ if (id_end >= this._all_slots.length / colspan_lease) {\r
+ /* Limit the display to the current day */\r
+ id_end = this._all_slots.length / colspan_lease\r
+ }\r
+ lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);\r
+ // the same slots might be affected multiple times.\r
+ // PENDING_IN + PENDING_OUT => IN \r
+ //\r
+ // RESERVED vs SELECTED !\r
+ //\r
+ // PENDING !!\r
+ switch(lease_status) {\r
+ case STATE_SET_IN:\r
+ lease_class = 'selected'; // my leases\r
+ lease_success = '';\r
+ break;\r
+ case STATE_SET_IN_SUCCESS:\r
+ lease_class = 'selected'; // my leases\r
+ lease_success = 'success';\r
+ case STATE_SET_OUT_FAILURE:\r
+ lease_class = 'selected'; // my leases\r
+ lease_success = 'failure';\r
+ break;\r
+ case STATE_SET_OUT:\r
+ lease_class = 'reserved'; // other leases\r
+ lease_success = '';\r
+ break;\r
+ case STATE_SET_OUT_SUCCESS:\r
+ lease_class = 'free'; // other leases\r
+ lease_success = 'success';\r
+ break;\r
+ case STATE_SET_IN_FAILURE:\r
+ lease_class = 'free'; // other leases\r
+ lease_success = 'failure';\r
+ break;\r
+ case STATE_SET_IN_PENDING:\r
+ lease_class = 'pendingin';\r
+ lease_success = '';\r
+ break;\r
+ case STATE_SET_OUT_PENDING:\r
+ // pending_in & pending_out == IN == replacement\r
+ if (resource.leases[i].status == 'pendingin')\r
+ lease_class = 'pendingin'\r
+ else\r
+ lease_class = 'pendingout';\r
+ lease_success = '';\r
+ break;\r
+ \r
+ }\r
+\r
+ for (i = id_start; i < id_end; i++) {\r
+ resource.leases[i].status = lease_class;\r
+ resource.leases[i].success = lease_success;\r
+ }\r
+ },\r
+\r
+/* XXX IN TEMPLATE XXX\r
+ if (SchedulerDataViewData.length == 0) {\r
+ $("#plugin-scheduler").hide();\r
+ $("#plugin-scheduler-empty").show();\r
+ tmpScope.clearStuff();\r
+ } else {\r
+ $("#plugin-scheduler-empty").hide();\r
+ $("#plugin-scheduler").show();\r
+ // initSchedulerResources\r
+ tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
+ }\r
+*/\r
+\r
+ /**\r
+ * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.\r
+ */\r
+ _initUI: function() \r
+ {\r
+ var self = this;\r
+ var scope = self._get_scope();\r
\r
- // ... be sure to list all events here\r
+ var num_hidden_cells;\r
\r
- /* RECORD HANDLERS */\r
- on_all_new_record: function (record) {\r
- //\r
- alert('on_all_new_record');\r
+ $("#DateToRes").datepicker({\r
+ dateFormat: "D, d M yy",\r
+ onRender: function(date) {\r
+ return date.valueOf() < now.valueOf() ? 'disabled' : '';\r
+ }\r
+ }).on('changeDate', function(ev) {\r
+ SchedulerDateSelected = new Date(ev.date);\r
+ SchedulerDateSelected.setHours(0,0,0,0);\r
+ // Set slider to origin\r
+ //$('#tblSlider').slider('setValue', 0); // XXX\r
+ // Refresh leases\r
+ self._scope_clear_leases();\r
+ self._set_all_lease_slots();\r
+ // Refresh display\r
+ self._get_scope().$apply();\r
+ }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');\r
+\r
+ //init Slider\r
+ num_hidden_cells = self._all_slots.length - self._num_visible_cells;\r
+ init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;\r
+ if (init_cell > num_hidden_cells)\r
+ init_cell = num_hidden_cells;\r
+\r
+ $('#tblSlider').slider({\r
+ min: 0,\r
+ max: num_hidden_cells,\r
+ value: init_cell,\r
+ }).on('slide', function(ev) {\r
+ var scope = self._get_scope();\r
+ scope.from = ev.value;\r
+ scope.$apply();\r
+ });\r
+ scope.from = init_cell;\r
+ scope.$apply();\r
+\r
+ $("#plugin-scheduler-loader").hide();\r
+ $("#plugin-scheduler").show();\r
+ },\r
+\r
+ // PRIVATE METHODS\r
+\r
+ /**\r
+ * Greatest common divisor\r
+ */\r
+ _gcd : function(x, y)\r
+ {\r
+ return (y==0) ? x : this._gcd(y, x % y);\r
},\r
\r
- debug : function (log_txt) {\r
- if (typeof window.console != 'undefined') {\r
- console.debug(log_txt);\r
- }\r
+ _gcdn : function(array)\r
+ {\r
+ var self = this;\r
+ return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });\r
},\r
\r
- /* INTERNAL FUNCTIONS */\r
- _initUI: function () {\r
- if (Debug) console.time("_initUI");\r
- //fix margins in tables\r
- mtNodesTbl = $("#" + rsvrTblNm + " tr:first").outerHeight() + 6;\r
- mtSchrollCon = $("#nodes").outerWidth();\r
- $("#nodes").css("margin-top", mtNodesTbl);\r
- $("#reservation-table-scroll-container").css("margin-left", mtSchrollCon);\r
- SetPerFun = this._SetPeriodInPage;\r
- //slider\r
- $("#time-range").slider({\r
- range: true,\r
- min: 0,\r
- max: 24,\r
- step: 0.5,\r
- values: [init_start_visible_index, init_end_visible_index],\r
- slide: function (event, ui) {\r
- SetPerFun(ui.values[0], ui.values[1]);\r
- }\r
- });\r
- $("#DateToRes").datepicker({\r
- dateFormat: "yy-mm-dd",\r
- minDate: 0,\r
- numberOfMonths: 3\r
- }).change(function () {\r
- //Scheduler2.loadWithDate();\r
- }).click(function () {\r
- $("#ui-datepicker-div").css("z-index", 5);\r
- });\r
- //other stuff\r
- fixOddEvenClasses();\r
- $("#" + rsvrTblNm + " td:not([class])").addClass("free");\r
- if (Debug) console.timeEnd("_initUI");\r
+ /**\r
+ * Least common multiple\r
+ */\r
+ _lcm : function(x, y)\r
+ {\r
+ return x * y / this._gcd(x, y);\r
},\r
- _SetPeriodInPage: function (start, end) {\r
- if (Debug) console.time("_SetPeriodInPage");\r
- ClearTableSelection();\r
- $("#lbltime").html(GetTimeFromInt(start) + " - " + GetTimeFromInt(end));\r
- \r
- var start_visible_index = (start * schdlr_PartsInOneHour) + 1;\r
- var end_visible_index = (end * schdlr_PartsInOneHour);\r
\r
- //hide - show\r
- for (i = 0; i < start_visible_index; i++) {\r
- $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").hide();\r
- }\r
- for (i = end_visible_index + 1; i <= schdlr_totalColums; i++) {\r
- $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").hide();\r
- }\r
- /*$("#" + rsvrTblNm + " td:not([class*='info']), #" + rsvrTblNm + " th:not([class*='fixed'])").hide();*/\r
- for (i = start_visible_index; i <= end_visible_index; i++) {\r
- $("#" + rsvrTblNm + " td:nth-child(" + i + "), #" + rsvrTblNm + " th:nth-child(" + i + ")").show();\r
- }\r
+ _lcmn : function(array)\r
+ {\r
+ var self = this;\r
+ return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });\r
+ },\r
+ \r
+ _pad_str : function(i)\r
+ {\r
+ return (i < 10) ? "0" + i : "" + i;\r
+ },\r
\r
- if ($("#" + rsvrTblNm + " th:visible:first").width() > 105) {\r
- $("#" + rsvrTblNm + " th span").css("display", "inline")\r
- } else {\r
- $("#" + rsvrTblNm + " th span").css("display", "block");\r
+ /**\r
+ * Member variables used:\r
+ * _granularity\r
+ * \r
+ * Returns:\r
+ * A list of {id, time} dictionaries.\r
+ */\r
+ _generate_all_slots: function()\r
+ {\r
+ var slots = [];\r
+ // Start with a random date (a first of a month), only time will matter\r
+ var d = new Date(2014, 1, 1, 0, 0, 0, 0);\r
+ var i = 0;\r
+ // Loop until we change the day\r
+ while (d.getDate() == 1) {\r
+ // Nicely format the time...\r
+ var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());\r
+ /// ...and add the slot to the list of results\r
+ slots.push({ id: i, time: tmpTime });\r
+ // Increment the date with the granularity\r
+ d = new Date(d.getTime() + this._slot_length * 1000);\r
+ i++;\r
}\r
- mtNodesTbl = $("#" + rsvrTblNm + " tr:first").outerHeight() + 6;\r
- $("#nodes").css("margin-top", mtNodesTbl);\r
- //$("#scroll_container").width($("#Search").width() - $("#nodes").width());\r
- //$("#nodes th").height($("#tblReservation th:visible:first").height() - 2);\r
- if (Debug) console.timeEnd("_SetPeriodInPage");\r
- }\r
- });\r
+ return slots;\r
\r
- //Sched2 = new Scheduler2();\r
+ },\r
+ });\r
\r
/* Plugin registration */\r
- $.plugin('Scheduler2', Scheduler2);\r
-\r
- // TODO Here use cases for instanciating plugins in different ways like in the pastie.\r
-\r
+ $.plugin('Scheduler2', scheduler2);\r
\r
})(jQuery);\r
\r