3 # Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece
\r
5 # Permission is hereby granted, free of charge, to any person obtaining a copy
\r
6 # of this software and associated documentation files (the "Software"), to deal
\r
7 # in the Software without restriction, including without limitation the rights
\r
8 # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
\r
9 # copies of the Software, and to permit persons to whom the Software is
\r
10 # furnished to do so, subject to the following conditions:
\r
12 # The above copyright notice and this permission notice shall be included in
\r
13 # all copies or substantial portions of the Software.
\r
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
\r
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
\r
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
\r
18 # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
\r
19 # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
20 # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
\r
24 # This is a MySlice plugin for the NITOS Scheduler
\r
25 # Nitos Scheduler v1
\r
29 // XXX groupid = all slots those that go with a min granularity
\r
33 var scheduler2Instance;
\r
34 //is ctrl keyboard button pressed
\r
35 var schedulerCtrlPressed = false;
\r
37 var schedulerTblId = "scheduler-reservation-table";
\r
38 var SCHEDULER_FIRST_COLWIDTH = 150;
\r
41 /* Number of scheduler slots per hour. Used to define granularity. Should be inferred from resources XXX */
\r
42 var schedulerSlotsPerHour = 6;
\r
43 var RESOURCE_DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information
\r
44 var DEFAULT_GRANULARITY = 1800 /* s */; // should be computed automatically from resource information. Test with 600
\r
45 var DEFAULT_PAGE_RANGE = 5;
\r
47 var schedulerMaxRows = 12;
\r
50 var SchedulerData = [];
\r
53 var SchedulerSlots = [];
\r
55 var SchedulerDateSelected = new Date();
\r
56 // Round to midnight
\r
57 SchedulerDateSelected.setHours(0,0,0,0);
\r
59 /* Filtered resources */
\r
60 var SchedulerDataViewData = [];
\r
62 var SchedulerSlotsViewData = [];
\r
64 var _schedulerCurrentCellPosition = 0;
\r
66 var schedulerDebug = true;
\r
68 var tmpSchedulerLeases = [];
\r
70 var SCHEDULER_COLWIDTH = 50;
\r
73 scheduler2 = Plugin.extend({
\r
76 * @brief Plugin constructor
\r
77 * @param options : an associative array of setting values
\r
79 * @return : a jQuery collection of objects on which the plugin is
\r
80 * applied, which allows to maintain chainability of calls
\r
82 init: function(options, element) {
\r
83 this.classname = "scheduler2";
\r
84 // Call the parent constructor, see FAQ when forgotten
\r
85 this._super(options, element);
\r
87 var scope = this._get_scope()
\r
90 scheduler2Instance = this;
\r
92 // We need to remember the active filter for datatables filtering
\r
94 this.filters = Array();
\r
97 $(window).delegate('*', 'keypress', function (evt){
\r
101 $(window).keydown(function(evt) {
\r
102 if (evt.which == 17) { // ctrl
\r
103 schedulerCtrlPressed = true;
\r
105 }).keyup(function(evt) {
\r
106 if (evt.which == 17) { // ctrl
\r
107 schedulerCtrlPressed = false;
\r
112 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);
\r
114 this._resources_received = false;
\r
115 this._leases_received = false;
\r
117 scope._leases_by_resource = {};
\r
119 /* Listening to queries */
\r
120 this.listen_query(options.query_uuid, 'resources');
\r
121 this.listen_query(options.query_lease_uuid, 'leases');
\r
123 /* Generate slots according to the default granularity. Should
\r
124 * be updated when resources arrive. Should be the pgcd in fact XXX */
\r
125 this._granularity = DEFAULT_GRANULARITY;
\r
126 scope.granularity = this._granularity;
\r
127 this._all_slots = this._generate_all_slots();
\r
129 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);
\r
130 //this get width might need fix depending on the template
\r
131 var tblwidth = $('#scheduler-tab').parent().outerWidth();
\r
133 /* Number of visible cells...*/
\r
134 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);
\r
135 /* ...should be a multiple of the lcm of all encountered granularities. */
\r
136 // XXX Should be updated everytime a new resource is added
\r
137 this._lcm_colspan = this._lcm(this._granularity, RESOURCE_DEFAULT_GRANULARITY) / this._granularity;
\r
138 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % this._lcm_colspan;
\r
139 /* scope also needs this value */
\r
140 scope.num_visible_cells = this._num_visible_cells;
\r
141 scope.lcm_colspan = this._lcm_colspan;
\r
143 scope.options = this.options;
\r
146 // A list of {id, time} dictionaries representing the slots for the given day
\r
147 scope.slots = this._all_slots;
\r
148 this.scope_resources_by_key = {};
\r
156 _get_scope : function()
\r
158 return angular.element(document.getElementById('SchedulerCtrl')).scope();
\r
161 _scope_set_resources : function()
\r
164 var scope = this._get_scope();
\r
166 var records = manifold.query_store.get_records(this.options.query_uuid);
\r
168 scope.resources = [];
\r
170 $.each(records, function(i, record) {
\r
171 if (!record.exclusive)
\r
172 return true; // ~ continue
\r
174 // copy not to modify original record
\r
175 var resource = jQuery.extend(true, {}, record);
\r
178 resource.granularity = typeof(resource.granularity) == "number" ? resource.granularity : RESOURCE_DEFAULT_GRANULARITY;
\r
179 resource.leases = []; // a list of occupied timeslots
\r
181 self.scope_resources_by_key[resource['urn']] = resource;
\r
182 scope.resources.push(resource);
\r
186 _scope_clear_leases: function()
\r
189 var scope = this._get_scope();
\r
191 // Setup leases with a default free status...
\r
192 $.each(this.scope_resources_by_key, function(resource_key, resource) {
\r
193 resource.leases = [];
\r
194 var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells
\r
195 for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity
\r
196 resource.leases.push({
\r
198 status: 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??
\r
205 _scope_set_leases: function()
\r
208 var scope = this._get_scope();
\r
210 var leases = manifold.query_store.get_records(this.options.query_lease_uuid);
\r
211 $.each(leases, function(i, lease) {
\r
213 console.log("SET LEASES", new Date(lease.start_time* 1000));
\r
214 console.log(" ", new Date(lease.end_time* 1000));
\r
215 // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work
\r
217 // Populate leases by resource array: this will help us merging leases later
\r
218 if (!(lease.resource in scope._leases_by_resource))
\r
219 scope._leases_by_resource[lease.resource] = [];
\r
220 scope._leases_by_resource[lease.resource].push(lease);
\r
222 var resource = self.scope_resources_by_key[lease.resource];
\r
223 var day_timestamp = SchedulerDateSelected.getTime() / 1000;
\r
225 var id_start = (lease.start_time - day_timestamp) / resource.granularity;
\r
226 if (id_start < 0) {
\r
227 /* Some leases might be in the past */
\r
231 var id_end = (lease.end_time - day_timestamp) / resource.granularity - 1;
\r
232 var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells
\r
233 if (id_end >= self._all_slots.length / colspan_lease) {
\r
234 /* Limit the display to the current day */
\r
235 id_end = self._all_slots.length / colspan_lease
\r
238 for (i = id_start; i <= id_end; i++)
\r
239 // the same slots might be affected multiple times.
\r
240 // PENDING_IN + PENDING_OUT => IN
\r
242 // RESERVED vs SELECTED !
\r
245 resource.leases[i].status = 'selected';
\r
249 on_resources_query_done: function(data)
\r
251 this._resources_received = true;
\r
252 this._scope_set_resources();
\r
253 this._scope_clear_leases();
\r
254 if (this._leases_received)
\r
255 this._scope_set_leases();
\r
257 this._get_scope().$apply();
\r
260 on_leases_query_done: function(data)
\r
262 this._leases_received = true;
\r
263 if (this._resources_received) {
\r
264 this._scope_set_leases();
\r
265 this._get_scope().$apply();
\r
269 /* Filters on resources */
\r
270 on_resources_filter_added: function(filter) { this._get_scope().$apply(); },
\r
271 on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },
\r
272 on_resources_filter_clear: function() { this._get_scope().$apply(); },
\r
274 /* Filters on leases ? */
\r
275 on_leases_filter_added: function(filter) { this._get_scope().$apply(); },
\r
276 on_leases_filter_removed: function(filter) { this._get_scope().$apply(); },
\r
277 on_leases_filter_clear: function() { this._get_scope().$apply(); },
\r
279 /* INTERNAL FUNCTIONS */
\r
281 /* XXX IN TEMPLATE XXX
\r
282 if (SchedulerDataViewData.length == 0) {
\r
283 $("#plugin-scheduler").hide();
\r
284 $("#plugin-scheduler-empty").show();
\r
285 tmpScope.clearStuff();
\r
287 $("#plugin-scheduler-empty").hide();
\r
288 $("#plugin-scheduler").show();
\r
289 // initSchedulerResources
\r
290 tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);
\r
295 * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.
\r
297 _initUI: function()
\r
301 $("#DateToRes").datepicker({
\r
302 dateFormat: "yy-mm-dd",
\r
305 }).change(function() {
\r
306 // the selected date
\r
307 SchedulerDateSelected = $("#DateToRes").datepicker("getDate");
\r
308 if (SchedulerDateSelected == null || SchedulerDateSelected == '') {
\r
309 alert("Please select a date, so the scheduler can reserve leases.");
\r
312 // Set slider to origin
\r
313 $('#tblSlider').slider('value', 0);
\r
315 self._scope_clear_leases();
\r
316 self._scope_set_leases();
\r
318 self._get_scope().$apply();
\r
319 }).datepicker('setDate', SchedulerDateSelected);
\r
322 $('#tblSlider').slider({
\r
324 max: (this._all_slots.length - self._num_visible_cells) / self._lcm_colspan,
\r
326 slide: function(event, ui) {
\r
327 var scope = self._get_scope();
\r
328 scope.from = ui.value * self._lcm_colspan;
\r
333 $('#btnSchedulerSubmit').click(this._on_submit);
\r
335 $("#plugin-scheduler-loader").hide();
\r
336 $("#plugin-scheduler").show();
\r
342 _on_submit : function()
\r
344 var leasesForCommit = new Array();
\r
345 var tmpDateTime = SchedulerDateSelected;
\r
346 for (var i = 0; i < SchedulerData.length; i++)
\r
348 var tpmR = SchedulerData[i];
\r
349 //for capturing start and end of the lease
\r
350 var newLeaseStarted = false;
\r
351 for (var j = 0; j < tpmR.leases.length; j++) {
\r
352 var tpmL = tpmR.leases[j];
\r
353 if (newLeaseStarted == false && tpmL.status == 'selected') {
\r
354 //get date of the slot
\r
355 tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime);
\r
356 var unixStartTime = tmpDateTime.getTime() / 1000;
\r
358 leasesForCommit.push({
\r
360 //granularity: tpmR.granularity,
\r
361 //lease_type: null,
\r
363 start_time: unixStartTime,
\r
367 console.log(tpmR.id);
\r
368 newLeaseStarted = true;
\r
369 } else if (newLeaseStarted == true && tpmL.status != 'selected') {
\r
370 //get date of the slot
\r
371 tmpDateTime = schedulerGetDateTimeFromSlotId(tpmL.id, tmpDateTime);
\r
372 var unixEndTime = tmpDateTime.getTime() / 1000;
\r
374 var tmpCL = leasesForCommit[leasesForCommit.length - 1];
\r
375 tmpCL.end_time = unixEndTime;
\r
376 //tmpCL.duration = schedulerFindDuration(tmpCL.start_time, tmpCL.end_time, tmpCL.granularity);
\r
377 newLeaseStarted = false;
\r
381 console.log(leasesForCommit);
\r
382 for (var i = 0; i < leasesForCommit.length; i++) {
\r
383 manifold.raise_event(scheduler2Instance.options.query_lease_uuid, SET_ADD, leasesForCommit[i]);
\r
390 * Greatest common divisor
\r
392 _gcd : function(x, y)
\r
394 return (y==0) ? x : this._gcd(y, x % y);
\r
398 * Least common multiple
\r
400 _lcm : function(x, y)
\r
402 return x * y / this._gcd(x, y);
\r
405 _pad_str : function(i)
\r
407 return (i < 10) ? "0" + i : "" + i;
\r
411 * Member variables used:
\r
415 * A list of {id, time} dictionaries.
\r
417 _generate_all_slots: function()
\r
420 // Start with a random date (a first of a month), only time will matter
\r
421 var d = new Date(2014, 1, 1, 0, 0, 0, 0);
\r
423 // Loop until we change the day
\r
424 while (d.getDate() == 1) {
\r
425 // Nicely format the time...
\r
426 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());
\r
427 /// ...and add the slot to the list of results
\r
428 slots.push({ id: i, time: tmpTime });
\r
429 // Increment the date with the granularity
\r
430 d = new Date(d.getTime() + this._granularity * 1000);
\r
438 /* Plugin registration */
\r
439 $.plugin('Scheduler2', scheduler2);
\r