Merge branch 'onelab' of ssh://git.onelab.eu/git/myslice into onelab
[unfold.git] / plugins / scheduler2 / static / js / scheduler2.js
1 /*\r
2 #\r
3 # Copyright (c) 2013 NITLab, University of Thessaly, CERTH, Greece\r
4 #\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
11 #\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
14 #\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
21 # THE SOFTWARE.\r
22 #\r
23 #\r
24 # This is a MySlice plugin for the NITOS Scheduler\r
25 # Nitos Scheduler v1\r
26 #\r
27 */\r
28 \r
29 // XXX groupid = all slots those that go with a min granularity\r
30 \r
31 /* some params */\r
32 var scheduler2;\r
33 var scheduler2Instance;\r
34 //is ctrl keyboard button pressed\r
35 var schedulerCtrlPressed = false;\r
36 //table Id\r
37 var schedulerTblId = "scheduler-reservation-table";\r
38 var SCHEDULER_FIRST_COLWIDTH = 200;\r
39 \r
40 \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
46 \r
47 var schedulerMaxRows = 12;\r
48 \r
49 /* All resources */\r
50 var SchedulerData = [];\r
51 \r
52 /* ??? */\r
53 var SchedulerSlots = [];\r
54 \r
55 var SchedulerDateSelected = new Date();\r
56 // Round to midnight\r
57 SchedulerDateSelected.setHours(0,0,0,0);\r
58 \r
59 /* Filtered resources */\r
60 var SchedulerDataViewData = [];\r
61 \r
62 var SchedulerSlotsViewData = [];\r
63 //Help Variables\r
64 var _schedulerCurrentCellPosition = 0;\r
65 //Enable Debug\r
66 var schedulerDebug = true;\r
67 //tmp to delete\r
68 var tmpSchedulerLeases = [];\r
69 \r
70 var SCHEDULER_COLWIDTH = 50;\r
71 \r
72 \r
73 /******************************************************************************\r
74  *                             ANGULAR CONTROLLER                             *\r
75  ******************************************************************************/\r
76 \r
77 // Create a private execution space for our controller. When\r
78 // executing this function expression, we're going to pass in\r
79 // the Angular reference and our application module.\r
80 (function (ng, app) {\r
81 \r
82     // Define our Controller constructor.\r
83     function Controller($scope) {\r
84 \r
85         // Store the scope so we can reference it in our\r
86         // class methods\r
87         this.scope = $scope;\r
88 \r
89         // Set up the default scope value.\r
90         this.scope.errorMessage = null;\r
91         this.scope.name = "";\r
92 \r
93         //Pagin\r
94         $scope.current_page = 1;\r
95         this.scope.items_per_page = 10;\r
96         $scope.from = 0; // JORDAN\r
97 \r
98         $scope.instance = null;\r
99         $scope.resources = new Array();\r
100         $scope.slots = SchedulerSlotsViewData;\r
101         $scope.granularity = DEFAULT_GRANULARITY; /* Will be setup */\r
102         //$scope.msg = "hello";\r
103 \r
104         angular.element(document).ready(function() {\r
105             //console.log('Hello World');\r
106             //alert('Hello World');\r
107             //afterAngularRendered();\r
108         });\r
109 \r
110         // Pagination\r
111 \r
112         $scope.range = function() {\r
113             var range_size = $scope.page_count() > DEFAULT_PAGE_RANGE ? DEFAULT_PAGE_RANGE : $scope.page_count();\r
114             var ret = [];\r
115             var start;\r
116 \r
117             start = $scope.current_page;\r
118             if ( start > $scope.page_count()-range_size ) {\r
119               start = $scope.page_count()-range_size+1;\r
120             }\r
121 \r
122             for (var i=start; i<start+range_size; i++) {\r
123               ret.push(i);\r
124             }\r
125             return ret;\r
126         };\r
127 \r
128         $scope.prevPage = function() {\r
129           if ($scope.current_page > 1) {\r
130             $scope.current_page--;\r
131           }\r
132         };\r
133 \r
134         $scope.prevPageDisabled = function() {\r
135           return $scope.current_page === 1 ? "disabled" : "";\r
136         };\r
137   \r
138         $scope.page_count = function()\r
139         {\r
140             // XXX need visible resources only\r
141             var query_ext, visible_resources_length;\r
142             if (!$scope.instance)\r
143                 return 0;\r
144             query_ext = manifold.query_store.find_analyzed_query_ext($scope.instance.options.query_uuid);\r
145             var visible_resources_length = 0;\r
146             query_ext.state.each(function(i, state) {\r
147                 if (state[STATE_VISIBLE])\r
148                     visible_resources_length++;\r
149             });\r
150             return Math.ceil(visible_resources_length/$scope.items_per_page);\r
151         };\r
152   \r
153         $scope.nextPage = function() {\r
154           if ($scope.current_page < $scope.page_count()) {\r
155             $scope.current_page++;\r
156           }\r
157         };\r
158   \r
159         $scope.nextPageDisabled = function() {\r
160           return $scope.current_page === $scope.page_count() ? "disabled" : "";\r
161         }; \r
162 \r
163         $scope.setPage = function(n) {\r
164             $scope.current_page = n;\r
165         };\r
166         // END pagination\r
167 \r
168         // FILTER\r
169 \r
170         $scope.filter_visible = function(resource)\r
171         {\r
172             return manifold.query_store.get_record_state($scope.instance.options.query_uuid, resource['urn'], STATE_VISIBLE);\r
173         };\r
174 \r
175         // SELECTION\r
176 \r
177         $scope._create_new_lease = function(resource_urn, start_time, end_time)\r
178         {\r
179             var lease_key, new_lease, data;\r
180 \r
181             lease_key = manifold.metadata.get_key('lease');\r
182 \r
183             new_lease = {\r
184                 resource:   resource_urn,\r
185                 start_time: start_time,\r
186                 end_time:   end_time,\r
187             };\r
188 \r
189             // This is needed to create a hashable object\r
190             new_lease.hashCode = manifold.record_hashcode(lease_key.sort());\r
191             new_lease.equals   = manifold.record_equals(lease_key);\r
192 \r
193             data = {\r
194                 state: STATE_SET,\r
195                 key  : null,\r
196                 op   : STATE_SET_ADD,\r
197                 value: new_lease\r
198             }\r
199             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
200             /* Add to local cache also, unless we listen to events from outside */\r
201             if (!(resource_urn in $scope._leases_by_resource))\r
202                 $scope._leases_by_resource[resource_urn] = [];\r
203             $scope._leases_by_resource[resource_urn].push(new_lease);\r
204         }\r
205 \r
206         $scope._remove_lease = function(other)\r
207         {\r
208             var lease_key, other_key, data;\r
209 \r
210             lease_key = manifold.metadata.get_key('lease');\r
211 \r
212             // XXX This could be a manifold.record_get_value\r
213             other_key = {\r
214                 resource:   other.resource,\r
215                 start_time: other.start_time,\r
216                 end_time:   other.end_time\r
217             }\r
218             other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
219             other_key.equals   = manifold.record_equals(lease_key);\r
220 \r
221             data = {\r
222                 state: STATE_SET,\r
223                 key  : null,\r
224                 op   : STATE_SET_REMOVE,\r
225                 value: other_key\r
226             }\r
227             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
228             /* Remove from local cache also, unless we listen to events from outside */\r
229             $scope._leases_by_resource[other.resource] = $.grep($scope._leases_by_resource[other.resource], function(x) { return x != other; });\r
230 \r
231         }\r
232 \r
233         $scope.select = function(index, model_lease, model_resource)\r
234         {\r
235             var data;\r
236 \r
237             console.log("Selected", index, model_lease, model_resource);\r
238 \r
239             var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
240             var start_time = day_timestamp + index       * model_resource.granularity;\r
241             var end_time   = day_timestamp + (index + 1) * model_resource.granularity;\r
242             var start_date = new Date(start_time * 1000);\r
243             var end_date   = new Date(end_time   * 1000);\r
244 \r
245             var lease_key = manifold.metadata.get_key('lease');\r
246 \r
247             // We search for leases in the cache we previously constructed\r
248             var resource_leases = $scope._leases_by_resource[model_resource.urn];\r
249 \r
250             switch (model_lease.status)\r
251             {\r
252                 case 'free': // out\r
253                 case 'pendingout':\r
254                     if (resource_leases) {\r
255                         /* Search for leases before */\r
256                         $.each(resource_leases, function(i, other) {\r
257                             if (other.end_time != start_time)\r
258                                 return true; // ~ continue\r
259         \r
260                             /* The lease 'other' is just before, and there should not exist\r
261                              * any other lease before it */\r
262                             start_time = other.start_time;\r
263         \r
264                             other_key = {\r
265                                 resource:   other.resource,\r
266                                 start_time: other.start_time,\r
267                                 end_time:   other.end_time\r
268                             }\r
269                             // This is needed to create a hashable object\r
270                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
271                             other_key.equals   = manifold.record_equals(lease_key);\r
272         \r
273                             data = {\r
274                                 state: STATE_SET,\r
275                                 key  : null,\r
276                                 op   : STATE_SET_REMOVE,\r
277                                 value: other_key\r
278                             }\r
279                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
280                             /* Remove from local cache also, unless we listen to events from outside */\r
281                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
282                             return false; // ~ break\r
283                         });\r
284         \r
285                         /* Search for leases after */\r
286                         $.each(resource_leases, function(i, other) {\r
287                             if (other.start_time != end_time)\r
288                                 return true; // ~ continue\r
289         \r
290                             /* The lease 'other' is just after, and there should not exist\r
291                              * any other lease after it */\r
292                             end_time = other.end_time;\r
293                             other_key = {\r
294                                 resource:   other.resource,\r
295                                 start_time: other.start_time,\r
296                                 end_time:   other.end_time\r
297                             }\r
298                             // This is needed to create a hashable object\r
299                             other_key.hashCode = manifold.record_hashcode(lease_key.sort());\r
300                             other_key.equals   = manifold.record_equals(lease_key);\r
301         \r
302                             data = {\r
303                                 state: STATE_SET,\r
304                                 key  : null,\r
305                                 op   : STATE_SET_REMOVE,\r
306                                 value: other_key\r
307                             }\r
308                             manifold.raise_event($scope.instance.options.query_lease_uuid, FIELD_STATE_CHANGED, data);\r
309                             /* Remove from local cache also, unless we listen to events from outside */\r
310                             $scope._leases_by_resource[model_resource.urn] = $.grep($scope._leases_by_resource[model_resource.urn], function(x) { return x != other; });\r
311                             return false; // ~ break\r
312                         });\r
313                     }\r
314         \r
315                     $scope._create_new_lease(model_resource.urn, start_time, end_time);\r
316                     model_lease.status = (model_lease.status == 'free') ? 'pendingin' : 'selected';\r
317                     // unless the exact same lease already existed (pending_out status for the lease, not the cell !!)\r
318 \r
319                     break;\r
320 \r
321                 case 'selected':\r
322                 case 'pendingin':\r
323                     // We remove the cell\r
324 \r
325                     /* We search for leases including this cell. Either 0, 1 or 2.\r
326                      * 0 : NOT POSSIBLE, should be checked.\r
327                      * 1 : either IN or OUT, we have make no change in the session\r
328                      * 2 : both will be pending, since we have made a change in the session\r
329                     * /!\ need to properly remove pending_in leases when removed again\r
330                      */\r
331                     if (resource_leases) {\r
332                         $.each(resource_leases, function(i, other) {\r
333                             if ((other.start_time <= start_time) && (other.end_time >= end_time)) {\r
334                                 // The cell is part of this lease.\r
335 \r
336                                 // If the cell is not at the beginning of the lease, we recreate a lease with cells before\r
337                                 if (start_time > other.start_time) {\r
338                                     $scope._create_new_lease(model_resource.urn, other.start_time, start_time);\r
339                                 }\r
340 \r
341                                 // If the cell is not at the end of the lease, we recreate a lease with cells after\r
342                                 if (end_time < other.end_time) {\r
343                                     $scope._create_new_lease(model_resource.urn, end_time, other.end_time);\r
344                                 }\r
345                                 \r
346                                 // The other lease will be removed\r
347                                 $scope._remove_lease(other);\r
348                             }\r
349                             // NOTE: We can interrupt the search if we know that there is a single lease (depending on the status).\r
350                         });\r
351                     }\r
352                 \r
353                     // cf comment in previous switch case\r
354                     model_lease.status = (model_lease.status == 'selected') ? 'pendingout' : 'free';\r
355 \r
356                     break;\r
357 \r
358                 case 'reserved':\r
359                 case 'maintainance':\r
360                     // Do nothing\r
361                     break;\r
362             }\r
363             \r
364 \r
365             $scope._dump_leases();\r
366         };\r
367   \r
368         $scope._dump_leases = function()\r
369         {\r
370             // DEBUG: display all leases and their status in the log\r
371             var leases = manifold.query_store.get_records($scope.instance.options.query_lease_uuid);\r
372             console.log("--------------------");\r
373             $.each(leases, function(i, lease) {\r
374                 var key = manifold.metadata.get_key('lease');\r
375                 var lease_key = manifold.record_get_value(lease, key);\r
376                 var state = manifold.query_store.get_record_state($scope.instance.options.query_lease_uuid, lease_key, STATE_SET);\r
377                 var state_str;\r
378                 switch(state) {\r
379                     case STATE_SET_IN:\r
380                         state_str = 'STATE_SET_IN';\r
381                         break;\r
382                     case STATE_SET_OUT:\r
383                         state_str = 'STATE_SET_OUT';\r
384                         break;\r
385                     case STATE_SET_IN_PENDING:\r
386                         state_str = 'STATE_SET_IN_PENDING';\r
387                         break;\r
388                     case STATE_SET_OUT_PENDING:\r
389                         state_str = 'STATE_SET_OUT_PENDING';\r
390                         break;\r
391                     case STATE_SET_IN_SUCCESS:\r
392                         state_str = 'STATE_SET_IN_SUCCESS';\r
393                         break;\r
394                     case STATE_SET_OUT_SUCCESS:\r
395                         state_str = 'STATE_SET_OUT_SUCCESS';\r
396                         break;\r
397                     case STATE_SET_IN_FAILURE:\r
398                         state_str = 'STATE_SET_IN_FAILURE';\r
399                         break;\r
400                     case STATE_SET_OUT_FAILURE:\r
401                         state_str = 'STATE_SET_OUT_FAILURE';\r
402                         break;\r
403                 }\r
404                 console.log("LEASE", new Date(lease.start_time * 1000), new Date(lease.end_time * 1000), lease.resource, state_str);\r
405             });\r
406         };\r
407 \r
408         // Return this object reference.\r
409         return (this);\r
410 \r
411     }\r
412 \r
413     // Define the Controller as the constructor function.\r
414     app.controller("SchedulerCtrl", Controller);\r
415 \r
416 })(angular, ManifoldApp);\r
417 \r
418 /******************************************************************************\r
419  *                              MANIFOLD PLUGIN                               *\r
420  ******************************************************************************/\r
421 \r
422 (function($) {\r
423         scheduler2 = Plugin.extend({\r
424 \r
425             /** XXX to check\r
426          * @brief Plugin constructor\r
427          * @param options : an associative array of setting values\r
428          * @param element : \r
429          * @return : a jQuery collection of objects on which the plugin is\r
430          *     applied, which allows to maintain chainability of calls\r
431          */\r
432             init: function(options, element) {\r
433                 // Call the parent constructor, see FAQ when forgotten\r
434                 this._super(options, element);\r
435 \r
436                 var scope = this._get_scope()\r
437                 scope.instance = this;\r
438 \r
439                 // XXX not needed\r
440                 scheduler2Instance = this;\r
441 \r
442                 // We need to remember the active filter for datatables filtering\r
443                 // XXX not needed\r
444                 this.filters = Array();\r
445 \r
446                 // XXX BETTER !!!!\r
447                 $(window).delegate('*', 'keypress', function (evt){\r
448                         alert("erm");\r
449                       });\r
450 \r
451                 $(window).keydown(function(evt) {\r
452                     if (evt.which == 17) { // ctrl\r
453                         schedulerCtrlPressed = true;\r
454                     }\r
455                 }).keyup(function(evt) {\r
456                     if (evt.which == 17) { // ctrl\r
457                         schedulerCtrlPressed = false;\r
458                     }\r
459                 });\r
460 \r
461                 // XXX naming\r
462                 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
463 \r
464                 this._resources_received = false;\r
465                 this._leases_received = false;\r
466                 \r
467                 scope._leases_by_resource = {};\r
468 \r
469                 /* Listening to queries */\r
470                 this.listen_query(options.query_uuid, 'resources');\r
471                 this.listen_query(options.query_lease_uuid, 'leases');\r
472 \r
473                 this.elmt().on('show', this, this.on_show);\r
474                 this.elmt().on('shown.bs.tab', this, this.on_show);\r
475                 this.elmt().on('resize', this, this.on_resize);\r
476 \r
477                 /* Generate slots according to the default granularity. Should\r
478                  * be updated when resources arrive.  Should be the pgcd in fact XXX */\r
479                 this._granularity = DEFAULT_GRANULARITY;\r
480                 scope.granularity = this._granularity;\r
481                 this._all_slots = this._generate_all_slots();\r
482 \r
483                 // A list of {id, time} dictionaries representing the slots for the given day\r
484                 scope.slots = this._all_slots;\r
485                 this.scope_resources_by_key = {};\r
486 \r
487                 this.do_resize();\r
488     \r
489                 scope.from = 0;\r
490 \r
491                 this._initUI();\r
492 \r
493             },\r
494 \r
495             do_resize: function()\r
496             {\r
497                 var scope = this._get_scope();\r
498                 var num_hidden_cells, new_max;\r
499 \r
500                 $('#' + schedulerTblId + ' thead tr th:eq(0)').css("width", SCHEDULER_FIRST_COLWIDTH);\r
501                 //self get width might need fix depending on the template \r
502                 var tblwidth = $('#scheduler-reservation-table').parent().outerWidth();\r
503 \r
504                 /* Number of visible cells...*/\r
505                 this._num_visible_cells = parseInt((tblwidth - SCHEDULER_FIRST_COLWIDTH) / SCHEDULER_COLWIDTH);\r
506                 /* ...should be a multiple of the lcm of all encountered granularities. */\r
507                 // XXX Should be updated everytime a new resource is added\r
508                 this._lcm_colspan = this._lcm(this._granularity, RESOURCE_DEFAULT_GRANULARITY) / this._granularity;\r
509                 this._num_visible_cells = this._num_visible_cells - this._num_visible_cells % this._lcm_colspan;\r
510                 /* scope also needs this value */\r
511                 scope.num_visible_cells = this._num_visible_cells;\r
512                 scope.lcm_colspan = this._lcm_colspan;\r
513 \r
514                 // Slider max value\r
515                 if ($('#tblSlider').data('slider') != undefined) {\r
516                     num_hidden_cells = this._all_slots.length - this._num_visible_cells;\r
517 \r
518                     $('#tblSlider').slider('setAttribute', 'max', num_hidden_cells);\r
519                     $('#tblSlider').slider('setValue', scope.from, true);\r
520                     this._get_scope().$apply();\r
521                 }\r
522 \r
523 \r
524             },\r
525 \r
526             on_show: function(e)\r
527             {\r
528                 var self = e.data;\r
529                 self.do_resize();\r
530                 self._get_scope().$apply();\r
531             },\r
532 \r
533             on_resize: function(e)\r
534             {\r
535                 var self = e.data;\r
536                 self.do_resize();\r
537                 self._get_scope().$apply();\r
538             },\r
539 \r
540             /* Handlers */\r
541 \r
542             _get_scope : function()\r
543             {\r
544                 return angular.element(document.getElementById('SchedulerCtrl')).scope();\r
545             },\r
546             \r
547             _scope_set_resources : function()\r
548             {\r
549                 var self = this;\r
550                 var scope = this._get_scope();\r
551 \r
552                 var records = manifold.query_store.get_records(this.options.query_uuid);\r
553 \r
554                 scope.resources = [];\r
555 \r
556                 $.each(records, function(i, record) {\r
557                     if (!record.exclusive)\r
558                         return true; // ~ continue\r
559 \r
560                     // copy not to modify original record\r
561                     var resource = jQuery.extend(true, {}, record);\r
562 \r
563                     // Fix granularity\r
564                     resource.granularity = typeof(resource.granularity) == "number" ? resource.granularity : RESOURCE_DEFAULT_GRANULARITY;\r
565                     resource.leases = []; // a list of occupied timeslots\r
566 \r
567                     self.scope_resources_by_key[resource['urn']] = resource;\r
568                     scope.resources.push(resource);\r
569                 });\r
570             },\r
571 \r
572             _scope_clear_leases: function()\r
573             {\r
574                 var time, now;\r
575                 var self = this;\r
576                 var scope = this._get_scope();\r
577 \r
578                 now = new Date().getTime();\r
579 \r
580                 // Setup leases with a default free status...\r
581                 $.each(this.scope_resources_by_key, function(resource_key, resource) {\r
582                     resource.leases = [];\r
583                     var colspan_lease = resource.granularity / self._granularity; //eg. 3600 / 1800 => 2 cells\r
584                     time = SchedulerDateSelected.getTime();\r
585                     for (i=0; i < self._all_slots.length / colspan_lease; i++) { // divide by granularity\r
586                         resource.leases.push({\r
587                             id:     'coucou',\r
588                             status: (time < now) ? 'disabled':  'free', // 'selected', 'reserved', 'maintenance' XXX pending ??\r
589                         });\r
590                         time += resource.granularity * 1000;\r
591                     }\r
592                 });\r
593 \r
594             },\r
595 \r
596             _scope_set_leases: function()\r
597             {\r
598                     var status;\r
599                 var self = this;\r
600                 var scope = this._get_scope();\r
601             \r
602                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {\r
603                     console.log("SET LEASES", lease.resource, new Date(lease.start_time* 1000), new Date(lease.end_time* 1000));\r
604                     // XXX We should ensure leases are correctly merged, otherwise our algorithm won't work\r
605 \r
606                     // Populate leases by resource array: this will help us merging leases later\r
607 \r
608                     // let's only put _our_ leases\r
609                     lease_status = manifold.query_store.get_record_state(self.options.query_lease_uuid, lease_key, STATE_SET);\r
610                     if (lease_status != STATE_SET_IN)\r
611                         return true; // ~continue\r
612                     if (!(lease.resource in scope._leases_by_resource))\r
613                         scope._leases_by_resource[lease.resource] = [];\r
614                     scope._leases_by_resource[lease.resource].push(lease);\r
615 \r
616                 });\r
617 \r
618                 this._set_all_lease_slots();\r
619             },\r
620 \r
621             _set_all_lease_slots: function()\r
622             {\r
623                 var self = this;\r
624             \r
625                 manifold.query_store.iter_records(this.options.query_lease_uuid, function(lease_key, lease) {\r
626                     self._set_lease_slots(lease_key, lease);\r
627                 });\r
628             },\r
629 \r
630             on_resources_query_done: function(data)\r
631             {\r
632                 this._resources_received = true;\r
633                 this._scope_set_resources();\r
634                 this._scope_clear_leases();\r
635                 if (this._leases_received)\r
636                     this._scope_set_leases();\r
637                     \r
638                 this._get_scope().$apply();\r
639             },\r
640 \r
641             on_leases_query_done: function(data)\r
642             {\r
643                 this._leases_received = true;\r
644                 if (this._resources_received) {\r
645                     this._scope_set_leases();\r
646                     this._get_scope().$apply();\r
647                 }\r
648             },\r
649 \r
650             /* Filters on resources */\r
651             on_resources_filter_added:   function(filter) { this._get_scope().$apply(); },\r
652             on_resources_filter_removed: function(filter) { this._get_scope().$apply(); },\r
653             on_resources_filter_clear:   function()       { this._get_scope().$apply(); },\r
654 \r
655             /* Filters on leases ? */\r
656             on_leases_filter_added:      function(filter) { this._get_scope().$apply(); },\r
657             on_leases_filter_removed:    function(filter) { this._get_scope().$apply(); },\r
658             on_leases_filter_clear:      function()       { this._get_scope().$apply(); },\r
659 \r
660             on_field_state_changed: function(data)\r
661             {\r
662                 /*\r
663                 this._set_lease_slots(lease_key, lease);\r
664 \r
665                 switch(data.state) {\r
666                     case STATE_SET:\r
667                         switch(data.op) {\r
668                             case STATE_SET_IN:\r
669                             case STATE_SET_IN_SUCCESS:\r
670                             case STATE_SET_OUT_FAILURE:\r
671                                 this.set_checkbox_from_data(data.value, true);\r
672                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);\r
673                                 break;  \r
674                             case STATE_SET_OUT:\r
675                             case STATE_SET_OUT_SUCCESS:\r
676                             case STATE_SET_IN_FAILURE:\r
677                                 this.set_checkbox_from_data(data.value, false);\r
678                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_RESET);\r
679                                 break;\r
680                             case STATE_SET_IN_PENDING:\r
681                                 this.set_checkbox_from_data(data.key, true);\r
682                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_ADDED);\r
683                                 break;  \r
684                             case STATE_SET_OUT_PENDING:\r
685                                 this.set_checkbox_from_data(data.key, false);\r
686                                 this.set_bgcolor(data.value, QUERYTABLE_BGCOLOR_REMOVED);\r
687                                 break;\r
688                         }\r
689                         break;\r
690 \r
691                     case STATE_WARNINGS:\r
692                         this.change_status(data.key, data.value);\r
693                         break;\r
694                 }\r
695                 */\r
696             },\r
697 \r
698 \r
699             /* INTERNAL FUNCTIONS */\r
700 \r
701             _set_lease_slots: function(lease_key, lease)\r
702             {\r
703                 var resource, lease_status, lease_class;\r
704                 var day_timestamp, id_start, id_end, colspan_lease;\r
705 \r
706                 resource = this.scope_resources_by_key[lease.resource];\r
707                 day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
708                 id_start = Math.floor((lease.start_time - day_timestamp) / resource.granularity);\r
709 \r
710                 /* Some leases might be in the past */\r
711                 if (id_start < 0)\r
712                     id_start = 0;\r
713                 /* Leases in the future: ignore */\r
714                 if (id_start >= this._all_slots.length)\r
715                     return true; // ~ continue\r
716 \r
717                 id_end   = Math.ceil((lease.end_time   - day_timestamp) / resource.granularity);\r
718                 colspan_lease = resource.granularity / this._granularity; //eg. 3600 / 1800 => 2 cells\r
719                 if (id_end >= this._all_slots.length / colspan_lease) {\r
720                     /* Limit the display to the current day */\r
721                     id_end = this._all_slots.length / colspan_lease\r
722                 }\r
723 \r
724                 for (i = id_start; i < id_end; i++) {\r
725                     // the same slots might be affected multiple times.\r
726                     // PENDING_IN + PENDING_OUT => IN \r
727                     //\r
728                     // RESERVED vs SELECTED !\r
729                     //\r
730                     // PENDING !!\r
731                     lease_status = manifold.query_store.get_record_state(this.options.query_lease_uuid, lease_key, STATE_SET);\r
732                     switch(lease_status) {\r
733                         case STATE_SET_IN:\r
734                             lease_class = 'selected'; // my leases\r
735                             lease_success = '';\r
736                             break;\r
737                         case STATE_SET_IN_SUCCESS:\r
738                             lease_class = 'selected'; // my leases\r
739                             lease_success = 'success';\r
740                         case STATE_SET_OUT_FAILURE:\r
741                             lease_class = 'selected'; // my leases\r
742                             lease_success = 'failure';\r
743                             break;\r
744                         case STATE_SET_OUT:\r
745                             lease_class = 'reserved'; // other leases\r
746                             lease_success = '';\r
747                             break;\r
748                         case STATE_SET_OUT_SUCCESS:\r
749                             lease_class = 'free'; // other leases\r
750                             lease_success = 'success';\r
751                             break;\r
752                         case STATE_SET_IN_FAILURE:\r
753                             lease_class = 'free'; // other leases\r
754                             lease_success = 'failure';\r
755                             break;\r
756                         case STATE_SET_IN_PENDING:\r
757                             lease_class = 'pendingin';\r
758                             lease_success = '';\r
759                             break;\r
760                         case STATE_SET_OUT_PENDING:\r
761                             // pending_in & pending_out == IN == replacement\r
762                             if (resource.leases[i].status == 'pendingin')\r
763                                 lease_class = 'pendingin'\r
764                             else\r
765                                 lease_class = 'pendingout';\r
766                             lease_success = '';\r
767                             break;\r
768                     \r
769                     }\r
770                     resource.leases[i].status = lease_class;\r
771                     resource.leases[i].success = lease_success;\r
772                 }\r
773             },\r
774 \r
775 /* XXX IN TEMPLATE XXX\r
776                 if (SchedulerDataViewData.length == 0) {\r
777                     $("#plugin-scheduler").hide();\r
778                     $("#plugin-scheduler-empty").show();\r
779                     tmpScope.clearStuff();\r
780                 } else {\r
781                     $("#plugin-scheduler-empty").hide();\r
782                     $("#plugin-scheduler").show();\r
783                     // initSchedulerResources\r
784                     tmpScope.initSchedulerResources(schedulerMaxRows < SchedulerDataViewData.length ? schedulerMaxRows : SchedulerDataViewData.length);\r
785                 }\r
786 */\r
787 \r
788             /**\r
789              * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.\r
790              */\r
791             _initUI: function() \r
792             {\r
793                 var self = this;\r
794                 var scope = self._get_scope();\r
795 \r
796                 var num_hidden_cells;\r
797 \r
798                 $("#DateToRes").datepicker({\r
799                     onRender: function(date) {\r
800                         return date.valueOf() < now.valueOf() ? 'disabled' : '';\r
801                     }\r
802                 }).on('changeDate', function(ev) {\r
803                     SchedulerDateSelected = new Date(ev.date);\r
804                     SchedulerDateSelected.setHours(0,0,0,0);\r
805                     // Set slider to origin\r
806                     //$('#tblSlider').slider('setValue', 0); // XXX\r
807                     // Refresh leases\r
808                     self._scope_clear_leases();\r
809                     self._set_all_lease_slots();\r
810                     // Refresh display\r
811                     self._get_scope().$apply();\r
812                 }).datepicker('setValue', SchedulerDateSelected); //.data('datepicker');\r
813 \r
814                 //init Slider\r
815                 num_hidden_cells = self._all_slots.length - self._num_visible_cells;\r
816                 init_cell = (new Date().getHours() - 1) * 3600 / self._granularity;\r
817                 if (init_cell > num_hidden_cells)\r
818                     init_cell = num_hidden_cells;\r
819                 \r
820                 $('#tblSlider').slider({\r
821                     min: 0,\r
822                     max: num_hidden_cells, // / self._lcm_colspan,\r
823                     value: init_cell,\r
824                 }).on('slide', function(ev) {\r
825                     var scope = self._get_scope();\r
826                     scope.from = ev.value * self._lcm_colspan;\r
827                     scope.$apply();\r
828                 });\r
829                 scope.from = init_cell;\r
830                 scope.$apply();\r
831 \r
832                 $("#plugin-scheduler-loader").hide();\r
833                 $("#plugin-scheduler").show();\r
834             },\r
835 \r
836         // PRIVATE METHODS\r
837 \r
838         /**\r
839          * Greatest common divisor\r
840          */\r
841         _gcd : function(x, y)\r
842         {\r
843             return (y==0) ? x : this._gcd(y, x % y);\r
844         },\r
845 \r
846         /**\r
847          * Least common multiple\r
848          */\r
849         _lcm : function(x, y)\r
850         {\r
851             return x * y / this._gcd(x, y);\r
852         },\r
853     \r
854         _pad_str : function(i)\r
855         {\r
856             return (i < 10) ? "0" + i : "" + i;\r
857         },\r
858 \r
859         /**\r
860          * Member variables used:\r
861          *   _granularity\r
862          * \r
863          * Returns:\r
864          *   A list of {id, time} dictionaries.\r
865          */\r
866         _generate_all_slots: function()\r
867         {\r
868             var slots = [];\r
869             // Start with a random date (a first of a month), only time will matter\r
870             var d = new Date(2014, 1, 1, 0, 0, 0, 0);\r
871             var i = 0;\r
872             // Loop until we change the day\r
873             while (d.getDate() == 1) {\r
874                 // Nicely format the time...\r
875                 var tmpTime = this._pad_str(d.getHours()) + ':' + this._pad_str(d.getMinutes());\r
876                 /// ...and add the slot to the list of results\r
877                 slots.push({ id: i, time: tmpTime });\r
878                 // Increment the date with the granularity\r
879                 d = new Date(d.getTime() + this._granularity * 1000);\r
880                 i++;\r
881             }\r
882             return slots;\r
883 \r
884         },\r
885     });\r
886 \r
887     /* Plugin registration */\r
888     $.plugin('Scheduler2', scheduler2);\r
889 \r
890 })(jQuery);\r
891 \r
892 \r
893 \r