Revised version of the resource page + related plugins
[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 = 150;\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 (function($) {\r
73         scheduler2 = Plugin.extend({\r
74 \r
75             /** XXX to check\r
76          * @brief Plugin constructor\r
77          * @param options : an associative array of setting values\r
78          * @param element : \r
79          * @return : a jQuery collection of objects on which the plugin is\r
80          *     applied, which allows to maintain chainability of calls\r
81          */\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
86 \r
87                 var scope = this._get_scope()\r
88 \r
89                 // XXX not needed\r
90                 scheduler2Instance = this;\r
91 \r
92                 // We need to remember the active filter for datatables filtering\r
93                 // XXX not needed\r
94                 this.filters = Array();\r
95 \r
96                 // XXX BETTER !!!!\r
97                 $(window).delegate('*', 'keypress', function (evt){\r
98                         alert("erm");\r
99                       });\r
100 \r
101                 $(window).keydown(function(evt) {\r
102                     if (evt.which == 17) { // ctrl\r
103                         schedulerCtrlPressed = true;\r
104                     }\r
105                 }).keyup(function(evt) {\r
106                     if (evt.which == 17) { // ctrl\r
107                         schedulerCtrlPressed = false;\r
108                     }\r
109                 });\r
110 \r
111                 // XXX naming\r
112                 //$("#" + schedulerTblId).on('mousedown', 'td', rangeMouseDown).on('mouseup', 'td', rangeMouseUp).on('mousemove', 'td', rangeMouseMove);\r
113 \r
114                 this._resources_received = false;\r
115                 this._leases_received = false;\r
116                 \r
117                 scope._leases_by_resource = {};\r
118 \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
122 \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
128 \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
132 \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
142 \r
143                 scope.options = this.options;\r
144                 scope.from = 0;\r
145 \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
149 \r
150                 this._initUI();\r
151 \r
152             },\r
153 \r
154             /* Handlers */\r
155 \r
156             _get_scope : function()\r
157             {\r
158                 return angular.element(document.getElementById('SchedulerCtrl')).scope();\r
159             },\r
160             \r
161             _scope_set_resources : function()\r
162             {\r
163                 var self = this;\r
164                 var scope = this._get_scope();\r
165 \r
166                 var records = manifold.query_store.get_records(this.options.query_uuid);\r
167 \r
168                 scope.resources = [];\r
169 \r
170                 $.each(records, function(i, record) {\r
171                     if (!record.exclusive)\r
172                         return true; // ~ continue\r
173 \r
174                     // copy not to modify original record\r
175                     var resource = jQuery.extend(true, {}, record);\r
176 \r
177                     // Fix granularity\r
178                     resource.granularity = typeof(resource.granularity) == "number" ? resource.granularity : RESOURCE_DEFAULT_GRANULARITY;\r
179                     resource.leases = []; // a list of occupied timeslots\r
180 \r
181                     self.scope_resources_by_key[resource['urn']] = resource;\r
182                     scope.resources.push(resource);\r
183                 });\r
184             },\r
185 \r
186             _scope_clear_leases: function()\r
187             {\r
188                 var self = this;\r
189                 var scope = this._get_scope();\r
190 \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
197                             id:     'coucou',\r
198                             status: 'free', // 'selected', 'reserved', 'maintenance' XXX pending ??\r
199                         });\r
200                     }\r
201                 });\r
202 \r
203             },\r
204 \r
205             _scope_set_leases: function()\r
206             {\r
207                 var self = this;\r
208                 var scope = this._get_scope();\r
209             \r
210                 var leases = manifold.query_store.get_records(this.options.query_lease_uuid);\r
211                 $.each(leases, function(i, lease) {\r
212 \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
216 \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
221 \r
222                     var resource = self.scope_resources_by_key[lease.resource];\r
223                     var day_timestamp = SchedulerDateSelected.getTime() / 1000;\r
224 \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
228                         id_start = 0;\r
229                     }\r
230     \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
236                     }\r
237 \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
241                         //\r
242                         // RESERVED vs SELECTED !\r
243                         //\r
244                         // PENDING !!\r
245                         resource.leases[i].status = 'selected'; \r
246                 });\r
247             },\r
248 \r
249             on_resources_query_done: function(data)\r
250             {\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
256                     \r
257                 this._get_scope().$apply();\r
258             },\r
259 \r
260             on_leases_query_done: function(data)\r
261             {\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
266                 }\r
267             },\r
268 \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
273 \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
278 \r
279             /* INTERNAL FUNCTIONS */\r
280 \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
286                 } else {\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
291                 }\r
292 */\r
293 \r
294             /**\r
295              * Initialize the date picker, the table, the slider and the buttons. Once done, display scheduler.\r
296              */\r
297             _initUI: function() \r
298             {\r
299                 var self = this;\r
300 \r
301                 $("#DateToRes").datepicker({\r
302                     dateFormat: "yy-mm-dd",\r
303                     minDate: 0,\r
304                     numberOfMonths: 3\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
310                         return;\r
311                     }\r
312                     // Set slider to origin\r
313                     $('#tblSlider').slider('value', 0);\r
314                     // Refresh leases\r
315                     self._scope_clear_leases();\r
316                     self._scope_set_leases();\r
317                     // Refresh display\r
318                     self._get_scope().$apply();\r
319                 }).datepicker('setDate', SchedulerDateSelected);\r
320 \r
321                 //init Slider\r
322                 $('#tblSlider').slider({\r
323                     min: 0,\r
324                     max: (this._all_slots.length - self._num_visible_cells) / self._lcm_colspan,\r
325                     value: 0,\r
326                     slide: function(event, ui) {\r
327                         var scope = self._get_scope();\r
328                         scope.from = ui.value * self._lcm_colspan;\r
329                         scope.$apply();\r
330                    }\r
331                 });\r
332 \r
333                 $('#btnSchedulerSubmit').click(this._on_submit);\r
334 \r
335                 $("#plugin-scheduler-loader").hide();\r
336                 $("#plugin-scheduler").show();\r
337             },\r
338 \r
339         // GUI EVENTS\r
340 \r
341         // TO BE REMOVED\r
342         _on_submit : function()\r
343         {\r
344             var leasesForCommit = new Array();\r
345             var tmpDateTime = SchedulerDateSelected;\r
346             for (var i = 0; i < SchedulerData.length; i++)\r
347             {\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
357                         //add lease object\r
358                         leasesForCommit.push({\r
359                             resource: tpmR.id,\r
360                             //granularity: tpmR.granularity,\r
361                             //lease_type: null,\r
362                             //slice: null,\r
363                             start_time: unixStartTime,\r
364                             end_time: null,\r
365                             //duration: null\r
366                         });\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
373                         //upate end_time\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
378                     }\r
379                 }\r
380             }\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
384             }\r
385         },\r
386         \r
387         // PRIVATE METHODS\r
388 \r
389         /**\r
390          * Greatest common divisor\r
391          */\r
392         _gcd : function(x, y)\r
393         {\r
394             return (y==0) ? x : this._gcd(y, x % y);\r
395         },\r
396 \r
397         /**\r
398          * Least common multiple\r
399          */\r
400         _lcm : function(x, y)\r
401         {\r
402             return x * y / this._gcd(x, y);\r
403         },\r
404     \r
405         _pad_str : function(i)\r
406         {\r
407             return (i < 10) ? "0" + i : "" + i;\r
408         },\r
409 \r
410         /**\r
411          * Member variables used:\r
412          *   _granularity\r
413          * \r
414          * Returns:\r
415          *   A list of {id, time} dictionaries.\r
416          */\r
417         _generate_all_slots: function()\r
418         {\r
419             var slots = [];\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
422             var i = 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
431                 i++;\r
432             }\r
433             return slots;\r
434 \r
435         },\r
436     });\r
437 \r
438     /* Plugin registration */\r
439     $.plugin('Scheduler2', scheduler2);\r
440 \r
441 })(jQuery);\r
442 \r
443 \r
444 \r