-/*\r
-#\r
-# Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece\r
-#\r
-# Permission is hereby granted, free of charge, to any person obtaining a copy\r
-# of this software and associated documentation files (the "Software"), to deal\r
-# in the Software without restriction, including without limitation the rights\r
-# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
-# copies of the Software, and to permit persons to whom the Software is\r
-# furnished to do so, subject to the following conditions:\r
-#\r
-# The above copyright notice and this permission notice shall be included in\r
-# all copies or substantial portions of the Software.\r
-#\r
-# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
-# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
-# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
-# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
-# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
-# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
-# THE SOFTWARE.\r
-#\r
-#\r
-# This is a MySlice plugin for the NITOS Scheduler\r
-# Nitos Scheduler v1\r
-#\r
-*/\r
-\r
-// XXX groupid = all slots those that go with a min granularity\r
-\r
-/* some params */\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;\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;\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
- // Call the parent constructor, see FAQ when forgotten\r
- this._super(options, element);\r
-\r
- var scope = this._get_scope()\r
- scope.instance = this;\r
-\r
- // XXX not needed\r
- scheduler2Instance = this;\r
-\r
- // We need to remember the active filter for datatables filtering\r
- // XXX not needed\r
- this.filters = Array();\r
-\r
- // XXX BETTER !!!!\r
- $(window).delegate('*', 'keypress', function (evt){\r
- alert("erm");\r
- });\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
- 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
- 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
- /* 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._all_slots = this._generate_all_slots();\r
-\r
- // A list of {id, time} dictionaries representing the slots for the given day\r
- scope.slots = this._all_slots;\r
- this.scope_resources_by_key = {};\r
-\r
- this.do_resize();\r
- \r
- scope.from = 0;\r
-\r
- this._initUI();\r
-\r
- },\r
-\r
- do_resize: function()\r
- {\r
- var scope = this._get_scope();\r
- var num_hidden_cells, new_max;\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
- /* Number of visible cells...*/\r
- this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);\r
- /* ...should be a multiple of the lcm of all encountered granularities. */\r
- // XXX Should be updated everytime a new resource is added\r
- this._lcm_colspan = this._lcm(this._granularity, RESOURCE_DEFAULT_GRANULARITY) / this._granularity;\r
- this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % this._lcm_colspan;\r
- /* scope also needs this value */\r
- scope.num_visible_cells = this._num_visible_cells;\r
- scope.lcm_colspan = this._lcm_colspan;\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
- this._get_scope().$apply();\r
- }\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
- _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 = typeof(resource.granularity) == "number" ? 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._granularity; //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
- });\r
-\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
- // Populate leases by resource array: this will help us merging leases later\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
- });\r
-\r
- this._set_all_lease_slots();\r
- },\r
-\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
-\r
- /* INTERNAL FUNCTIONS */\r
-\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
- 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
- /* 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._granularity; //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
-\r
- for (i = id_start; i < id_end; i++) {\r
- // the same slots might be affected multiple times.\r
- // PENDING_IN + PENDING_OUT => IN \r
- //\r
- // RESERVED vs SELECTED !\r
- //\r
- // PENDING !!\r
- lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);\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
- 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
- var num_hidden_cells;\r
-\r
- $("#DateToRes").datepicker({\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, // / self._lcm_colspan,\r
- value: init_cell,\r
- }).on('slide', function(ev) {\r
- var scope = self._get_scope();\r
- scope.from = ev.value * self._lcm_colspan;\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
- /**\r
- * Least common multiple\r
- */\r
- _lcm : function(x, y)\r
- {\r
- return x * y / this._gcd(x, y);\r
- },\r
- \r
- _pad_str : function(i)\r
- {\r
- return (i < 10) ? "0" + i : "" + i;\r
- },\r
-\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._granularity * 1000);\r
- i++;\r
- }\r
- return slots;\r
-\r
- },\r
- });\r
-\r
- /* Plugin registration */\r
- $.plugin('Scheduler2', scheduler2);\r
-\r
-})(jQuery);\r
-\r
-\r
-\r
+/*
+#
+# Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+# THE SOFTWARE.
+#
+#
+# This is a MySlice plugin for the NITOS Scheduler
+# Nitos Scheduler v1
+#
+*/
+
+// XXX groupid = all slots those that go with a min granularity
+
+/* some params */
+var scheduler2;
+var scheduler2Instance;
+//is ctrl keyboard button pressed
+var schedulerCtrlPressed = false;
+//table Id
+var schedulerTblId = "scheduler-reservation-table";
+var SCHEDULER_FIRST_COLWIDTH = 200;
+
+
+/* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */
+var schedulerSlotsPerHour = 6;
+var RESOURCE_DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information
+var DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information. Test with 600
+var DEFAULT_PAGE_RANGE = 5;
+
+var schedulerMaxRows = 12;
+
+/* All resources */
+var SchedulerData = [];
+
+/* ??? */
+var SchedulerSlots = [];
+
+var SchedulerDateSelected = new Date();
+// Round to midnight
+SchedulerDateSelected.setHours(0,0,0,0);
+
+/* Filtered resources */
+var SchedulerDataViewData = [];
+
+var SchedulerSlotsViewData = [];
+//Help Variables
+var _schedulerCurrentCellPosition = 0;
+//Enable Debug
+var schedulerDebug = true;
+//tmp to delete
+var tmpSchedulerLeases = [];
+
+var SCHEDULER_COLWIDTH = 50;
+
+
+/******************************************************************************
+ * ANGULAR CONTROLLER *
+ ******************************************************************************/
+
+// Create a private execution space for our controller. When
+// executing this function expression, we're going to pass in
+// the Angular reference and our application module.
+(function (ng, app) {
+
+ // Define our Controller constructor.
+ function Controller($scope) {
+
+ // Store the scope so we can reference it in our
+ // class methods
+ this.scope = $scope;
+
+ // Set up the default scope value.
+ this.scope.errorMessage = null;
+ this.scope.name = "";
+
+ //Pagin
+ $scope.current_page = 1;
+ this.scope.items_per_page = 10;
+ $scope.from = 0; // JORDAN
+
+ $scope.instance = null;
+ $scope.resources = new Array();
+ $scope.slots = SchedulerSlotsViewData;
+ $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */
+ //$scope.msg = "hello";
+
+ angular.element(document).ready(function() {
+ //console.log('Hello World');
+ //alert('Hello World');
+ //afterAngularRendered();
+ });
+
+ // Pagination
+
+ $scope.range = function() {
+ var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();
+ var ret = [];
+ var start;
+
+ start = $scope.current_page;
+ if ( start > $scope.page_count()-range_size ) {
+ start = $scope.page_count()-range_size+1;
+ }
+
+ for (var i=start; i<start+range_size; i++) {
+ ret.push(i);
+ }
+ return ret;
+ };
+
+ $scope.prevPage = function() {
+ if ($scope.current_page > 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, data;
+
+ 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);
+
+ data = {
+ state: STATE_SET,
+ key : null,
+ op : STATE_SET_ADD,
+ value: new_lease
+ }
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
+ /* 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, data;
+
+ 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);
+
+ data = {
+ state: STATE_SET,
+ key : null,
+ op : STATE_SET_REMOVE,
+ value: other_key
+ }
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
+ /* Remove from local cache also, unless we listen to events from outside */
+ $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });
+
+ }
+
+ $scope.select = function(index, model_lease, model_resource)
+ {
+ var data, resource_granularity;
+
+ //resource_granularity = model_resource.granularity === undefined ? RESOURCE_DEFAULT_GRANULARITY : model_resource.granularity;
+
+ console.log("Selected", index, model_lease, model_resource);
+
+ var day_timestamp = SchedulerDateSelected.getTime() / 1000;
+ var start_time = day_timestamp + index * model_resource.granularity; // XXX 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);
+
+ data = {
+ state: STATE_SET,
+ key : null,
+ op : STATE_SET_REMOVE,
+ value: other_key
+ }
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
+ /* Remove from local cache also, unless we listen to events from outside */
+ $scope._leases_by_resource[model_resource.urn] = $.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;
+ 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);
+
+ data = {
+ state: STATE_SET,
+ key : null,
+ op : STATE_SET_REMOVE,
+ value: other_key
+ }
+ manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);
+ /* Remove from local cache also, unless we listen to events from outside */
+ $scope._leases_by_resource[model_resource.urn] = $.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 = (model_lease.status == 'free') ? 'pendingin' : 'selected';
+ // 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 = (model_lease.status == 'selected') ? 'pendingout' : 'free';
+
+ 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({
+
+ /** XXX to check
+ * @brief Plugin constructor
+ * @param options : an associative array of setting values
+ * @param element :
+ * @return : a jQuery collection of objects on which the plugin is
+ * applied, which allows to maintain chainability of calls
+ */
+ init: function(options, element) {
+ // Call the parent constructor, see FAQ when forgotten
+ this._super(options, element);
+
+ var scope = this._get_scope()
+ scope.instance = this;
+
+ // XXX not needed
+ scheduler2Instance = this;
+
+ // We need to remember the active filter for datatables filtering
+ // XXX not needed
+ this.filters = Array();
+
+ // XXX BETTER !!!!
+ $(window).delegate('*', 'keypress', function (evt){
+ alert("erm");
+ });
+
+ $(window).keydown(function(evt) {
+ if (evt.which == 17) { // ctrl
+ schedulerCtrlPressed = true;
+ }
+ }).keyup(function(evt) {
+ if (evt.which == 17) { // ctrl
+ schedulerCtrlPressed = false;
+ }
+ });
+
+ // XXX naming
+ //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);
+
+ this._resources_received = false;
+ this._leases_received = false;
+
+ scope._leases_by_resource = {};
+
+ /* Listening to queries */
+ 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.scope_resources_by_key = {};
+
+ this.do_resize();
+
+ scope.from = 0;
+
+ this._initUI();
+
+ },
+
+ do_resize: function()
+ {
+ var scope = this._get_scope();
+ var num_hidden_cells, new_max, lcm;
+
+ // do_resize has to be called when the window is resized, or one parameter changes
+ // e.g. when new resources have been received
+ //
+ this.resource_granularities = [3600, 1800]; //, 2400]; /* s */
+
+ /* Compute the slot length to accommodate all resources. This
+ * is the GCD of all resource granularities. */
+ this._slot_length = this._gcdn(this.resource_granularities);
+
+ $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);
+ //self get width might need fix depending on the template
+ var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();
+
+ /* Number of visible cells...*/
+ this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);
+
+ /* ...should be a multiple of the lcm of all encountered granularities. */
+ lcm = this._lcmn(this.resource_granularities) / this._slot_length;
+ this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % lcm;
+
+ // A list of {id, time} dictionaries representing the slots for the given day
+ this._all_slots = this._generate_all_slots();
+
+ /* scope also needs this value */
+ scope.slots = this._all_slots;
+ scope.slot_length = this._slot_length;
+ scope.num_visible_cells = this._num_visible_cells;
+ scope.lcm_colspan = this._lcmn(this.resource_granularities); // XXX WHY ?
+
+ /* Redraw... */
+ this._scope_clear_leases();
+ this._set_all_lease_slots();
+
+ // Slider max value
+ if ($('#tblSlider').data('slider') != undefined) {
+ num_hidden_cells = this._all_slots.length - this._num_visible_cells;
+
+ $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);
+ $('#tblSlider').slider('setValue', scope.from, true);
+ }
+ this._get_scope().$apply();
+
+
+ },
+
+ 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 */
+
+ _get_scope : function()
+ {
+ return angular.element(document.getElementById('SchedulerCtrl')).scope();
+ },
+
+ _scope_set_resources : function()
+ {
+ var self = this;
+ var scope = this._get_scope();
+
+ var records = manifold.query_store.get_records(this.options.query_uuid);
+
+ scope.resources = [];
+
+ $.each(records, function(i, record) {
+ if (!record.exclusive)
+ return true; // ~ continue
+
+ // copy not to modify original record
+ var resource = jQuery.extend(true, {}, record);
+
+ // Fix granularity
+ //resource_granularity = ((resource.granularity === undefined) || (typeof(resource.granularity) != "number")) ? RESOURCE_DEFAULT_GRANULARITY : resource.granularity;
+ if (typeof(resource.granularity) != "number")
+ resource.granularity = RESOURCE_DEFAULT_GRANULARITY;
+ resource.leases = []; // a list of occupied timeslots
+
+ self.scope_resources_by_key[resource['urn']] = resource;
+ scope.resources.push(resource);
+ });
+ },
+
+ _scope_clear_leases: function()
+ {
+ var time, now;
+ var self = this;
+ var scope = this._get_scope();
+
+ now = new Date().getTime();
+
+ // Setup leases with a default free status...
+ $.each(this.scope_resources_by_key, function(resource_key, resource) {
+ resource.leases = [];
+ var colspan_lease = resource.granularity / self._slot_length; //eg. 3600 / 1800 => 2 cells
+ time = SchedulerDateSelected.getTime();
+ for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity
+ resource.leases.push({
+ id: 'coucou',
+ status: (time < now) ? 'disabled': 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??
+ });
+ time += resource.granularity * 1000;
+ }
+ });
+
+ },
+
+ _scope_set_leases: function()
+ {
+ var status;
+ var self = this;
+ var scope = this._get_scope();
+
+ manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
+ //console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));
+ // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work
+
+ // Populate leases by resource array: this will help us merging leases later
+
+ // let's only put _our_ leases
+ lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);
+ if (lease_status != STATE_SET_IN)
+ return true; // ~continue
+ if (!(lease.resource in scope._leases_by_resource))
+ scope._leases_by_resource[lease.resource] = [];
+ scope._leases_by_resource[lease.resource].push(lease);
+
+ });
+
+ this._set_all_lease_slots();
+ },
+
+ _set_all_lease_slots: function()
+ {
+ var self = this;
+
+ manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {
+ self._set_lease_slots(lease_key, lease);
+ });
+ },
+
+ on_resources_query_done: function(data)
+ {
+ this._resources_received = true;
+ this._scope_set_resources();
+ this._scope_clear_leases();
+ if (this._leases_received)
+ this._scope_set_leases();
+
+ this._get_scope().$apply();
+ },
+
+ on_leases_query_done: function(data)
+ {
+ this._leases_received = true;
+ if (this._resources_received) {
+ this._scope_set_leases();
+ this._get_scope().$apply();
+ }
+ },
+
+ /* Filters on resources */
+ on_resources_filter_added: function(filter) { this._get_scope().$apply(); },
+ on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },
+ on_resources_filter_clear: function() { this._get_scope().$apply(); },
+
+ /* Filters on leases ? */
+ on_leases_filter_added: function(filter) { this._get_scope().$apply(); },
+ on_leases_filter_removed: function(filter) { this._get_scope().$apply(); },
+ on_leases_filter_clear: function() { this._get_scope().$apply(); },
+
+ on_field_state_changed: function(data)
+ {
+ /*
+ this._set_lease_slots(lease_key, lease);
+
+ switch(data.state) {
+ case STATE_SET:
+ switch(data.op) {
+ case STATE_SET_IN:
+ case STATE_SET_IN_SUCCESS:
+ case STATE_SET_OUT_FAILURE:
+ this.set_checkbox_from_data(data.value, true);
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
+ break;
+ case STATE_SET_OUT:
+ case STATE_SET_OUT_SUCCESS:
+ case STATE_SET_IN_FAILURE:
+ this.set_checkbox_from_data(data.value, false);
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);
+ break;
+ case STATE_SET_IN_PENDING:
+ this.set_checkbox_from_data(data.key, true);
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);
+ break;
+ case STATE_SET_OUT_PENDING:
+ this.set_checkbox_from_data(data.key, false);
+ this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);
+ break;
+ }
+ break;
+
+ case STATE_WARNINGS:
+ this.change_status(data.key, data.value);
+ break;
+ }
+ */
+ },
+
+
+ /* INTERNAL FUNCTIONS */
+
+ _set_lease_slots: function(lease_key, lease)
+ {
+ var resource, lease_status, lease_class;
+ var day_timestamp, id_start, id_end, colspan_lease;
+
+ resource = this.scope_resources_by_key[lease.resource];
+ day_timestamp = SchedulerDateSelected.getTime() / 1000;
+ if(resource === undefined){
+ console.log('resource undefined = '+lease.resource);
+ return;
+ }
+ id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);
+
+ /* Some leases might be in the past */
+ if (id_start < 0)
+ id_start = 0;
+ /* Leases in the future: ignore */
+ if (id_start >= this._all_slots.length)
+ return true; // ~ continue
+
+ id_end = Math.ceil((lease.end_time - day_timestamp) / resource.granularity);
+ colspan_lease = resource.granularity / this._slot_length; //eg. 3600 / 1800 => 2 cells
+ if (id_end >= this._all_slots.length / colspan_lease) {
+ /* Limit the display to the current day */
+ id_end = this._all_slots.length / colspan_lease
+ }
+ lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);
+ // the same slots might be affected multiple times.
+ // PENDING_IN + PENDING_OUT => IN
+ //
+ // RESERVED vs SELECTED !
+ //
+ // PENDING !!
+ switch(lease_status) {
+ case STATE_SET_IN:
+ lease_class = 'selected'; // my leases
+ lease_success = '';
+ break;
+ case STATE_SET_IN_SUCCESS:
+ lease_class = 'selected'; // my leases
+ lease_success = 'success';
+ case STATE_SET_OUT_FAILURE:
+ lease_class = 'selected'; // my leases
+ lease_success = 'failure';
+ break;
+ case STATE_SET_OUT:
+ lease_class = 'reserved'; // other leases
+ lease_success = '';
+ break;
+ case STATE_SET_OUT_SUCCESS:
+ lease_class = 'free'; // other leases
+ lease_success = 'success';
+ break;
+ case STATE_SET_IN_FAILURE:
+ lease_class = 'free'; // other leases
+ lease_success = 'failure';
+ break;
+ case STATE_SET_IN_PENDING:
+ lease_class = 'pendingin';
+ lease_success = '';
+ break;
+ case STATE_SET_OUT_PENDING:
+ // pending_in & pending_out == IN == replacement
+ if (resource.leases[i].status == 'pendingin')
+ lease_class = 'pendingin'
+ else
+ lease_class = 'pendingout';
+ lease_success = '';
+ break;
+
+ }
+
+ for (i = id_start; i < id_end; i++) {
+ resource.leases[i].status = lease_class;
+ resource.leases[i].success = lease_success;
+ }
+ },
+
+/* XXX IN TEMPLATE XXX
+ if (SchedulerDataViewData.length == 0) {
+ $("#plugin-scheduler").hide();
+ $("#plugin-scheduler-empty").show();
+ tmpScope.clearStuff();
+ } else {
+ $("#plugin-scheduler-empty").hide();
+ $("#plugin-scheduler").show();
+ // initSchedulerResources
+ tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);
+ }
+*/
+
+ /**
+ * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.
+ */
+ _initUI: function()
+ {
+ var self = this;
+ var scope = self._get_scope();
+
+ var num_hidden_cells;
+
+ $("#DateToRes").datepicker({
+ dateFormat: "D, d M yy",
+ 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('setValue', 0); // XXX
+ // Refresh leases
+ self._scope_clear_leases();
+ self._set_all_lease_slots();
+ // Refresh display
+ self._get_scope().$apply();
+ }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');
+
+ //init Slider
+ num_hidden_cells = self._all_slots.length - self._num_visible_cells;
+ init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;
+ if (init_cell > num_hidden_cells)
+ init_cell = num_hidden_cells;
+
+ $('#tblSlider').slider({
+ min: 0,
+ max: num_hidden_cells,
+ value: init_cell,
+ }).on('slide', function(ev) {
+ var scope = self._get_scope();
+ scope.from = ev.value;
+ scope.$apply();
+ });
+ scope.from = init_cell;
+ scope.$apply();
+
+ $("#plugin-scheduler-loader").hide();
+ $("#plugin-scheduler").show();
+ },
+
+ // PRIVATE METHODS
+
+ /**
+ * Greatest common divisor
+ */
+ _gcd : function(x, y)
+ {
+ return (y==0) ? x : this._gcd(y, x % y);
+ },
+
+ _gcdn : function(array)
+ {
+ var self = this;
+ return array.reduce(function(prev, cur, idx, arr) { return self._gcd(prev, cur); });
+ },
+
+ /**
+ * Least common multiple
+ */
+ _lcm : function(x, y)
+ {
+ return x * y / this._gcd(x, y);
+ },
+
+ _lcmn : function(array)
+ {
+ var self = this;
+ return array.reduce(function(prev, cur, idx, arr) { return self._lcm(prev, cur); });
+ },
+
+ _pad_str : function(i)
+ {
+ return (i < 10) ? "0" + i : "" + i;
+ },
+
+ /**
+ * Member variables used:
+ * _granularity
+ *
+ * Returns:
+ * A list of {id, time} dictionaries.
+ */
+ _generate_all_slots: function()
+ {
+ var slots = [];
+ // Start with a random date (a first of a month), only time will matter
+ var d = new Date(2014, 1, 1, 0, 0, 0, 0);
+ var i = 0;
+ // Loop until we change the day
+ while (d.getDate() == 1) {
+ // Nicely format the time...
+ var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());
+ /// ...and add the slot to the list of results
+ slots.push({ id: i, time: tmpTime });
+ // Increment the date with the granularity
+ d = new Date(d.getTime() + this._slot_length * 1000);
+ i++;
+ }
+ return slots;
+
+ },
+ });
+
+ /* Plugin registration */
+ $.plugin('Scheduler2', scheduler2);
+
+})(jQuery);
+
+
+