1 /* need to put some place else in CSS ? */
3 // space for the nodenames
5 // right space after the nodename - removed from the above
7 // height for the (two) rows of timelabels
12 // 1-grain leases attributes
19 /* decorations / headers */
22 var attr_rules={'fill':"#888", 'stroke-dasharray':'- ', 'stroke-width':0.5};
23 var txt_timelabel = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-serif', stroke: "none", fill: "#008"};
24 var txt_allnodes = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-serif', stroke: "none", fill: "#404"};
25 var txt_nodelabel = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-serif', stroke: "none", fill: "#008"};
27 var attr_timebutton = {'fill':'#bbf', 'stroke': '#338','stroke-width':2,
28 'stroke-linecap':'round', 'stroke-linejoin':'miter', 'stroke-miterlimit':3};
30 /* lease dimensions and colors */
31 /* refrain from using gradient color, seems to not be animated properly */
32 /* lease was originally free and is still free */
33 var attr_lease_free_free={'fill':"#def", 'stroke-width':0.5, 'stroke-dasharray':''};
34 /* lease was originally free and is now set for our usage */
35 var attr_lease_free_mine={'fill':"green", 'stroke-width':1, 'stroke-dasharray':'-..'};
36 /* was mine and is still mine */
37 var attr_lease_mine_mine={'fill':"#beb", 'stroke-width':0.5, 'stroke-dasharray':''};
38 /* was mine and is about to be released */
39 var attr_lease_mine_free={'fill':"white", 'stroke-width':1, 'stroke-dasharray':'-..'};
40 var attr_lease_other={'fill':"#f88"};
42 /* other slices name */
43 var txt_slice = {"font": '"Trebuchet MS", Verdana, Arial, Helvetica, sans-serif', stroke: "none", fill: "#444",
46 ////////////////////////////////////////////////////////////
47 // the scheduler object
48 function Scheduler (slicename, axisx, axisy, data) {
50 // the data only contain slice names, we need this to find our own leases (mine)
51 this.slicename=slicename;
56 // utilities to keep track of all the leases
58 this.append_lease = function (lease) {
59 this.leases.push(lease);
62 // how many time slots
63 this.nb_grains = function () { return axisx.length;}
65 this.init = function (canvas_id) {
66 this.total_width = x_nodelabel + this.nb_grains()*x_grain;
67 this.total_height = 2*y_header /* the timelabels */
68 + 2*y_sep /* extra space */
69 + y_node /* all-nodes & timebuttons row */
70 + (this.axisy.length)*(y_node+y_sep); /* the regular nodes and preceding space */
71 paper = Raphael (canvas_id, this.total_width+x_sep, this.total_height);
73 // maintain the list of nodelabels for the 'all nodes' button
76 ////////// create the time slots legend
81 for (var i=0, len=axisx.length; i < len; ++i) {
82 // pick the printable part
83 timelabel=axisx[i][1];
85 if (col%2 == 0) y += y_header;
88 var timelabel=paper.text(left,y,timelabel).attr(txt_timelabel)
89 .attr({"font-size":y_header, "text-anchor":"middle"});
91 var path_spec="M"+left+" "+(y+y_header/2)+"L"+left+" "+this.total_height;
92 var rule=paper.path(path_spec).attr(attr_rules);
96 this.granularity=axisx[1][0]-axisx[0][0];
98 ////////// the row with the timeslot buttons
99 // move two lines down
100 top += 2*y_header+2*y_sep;
103 var allnodes = paper.text (x_nodelabel-x_sep,top+y_node/2,"All nodes").attr(txt_allnodes)
104 .attr ({"font-size":y_node, "text-anchor":"end","baseline":"bottom"});
105 allnodes.scheduler=this;
106 allnodes.click(allnodes_methods.click);
108 for (var i=0, len=axisx.length; i < len; ++i) {
109 var pathspec="M"+left+","+top;
110 pathspec+="L"+(left+x_grain)+","+top;
111 pathspec+="L"+(left+x_grain/2)+","+(top+y_node);
112 pathspec+="L"+left+","+top;
113 var timebutton=paper.path(pathspec).attr(attr_timebutton);
114 timebutton.from_time=axisx[i][0];
115 timebutton.scheduler=this;
116 timebutton.click(timebutton_methods.click);
120 //////// the body of the scheduler : loop on nodes
123 for (var i=0, len=axisy.length; i<len; ++i) {
126 var nodelabel = paper.text(x_nodelabel-x_sep,top+y_node/2,nodename).attr(txt_nodelabel)
127 .attr ({"font-size":y_node, "text-anchor":"end","baseline":"bottom"});
128 nodelabel_methods.selected(nodelabel,1);
129 nodelabel.click(nodelabel_methods.click);
130 this.nodelabels.push(nodelabel);
134 while (grain < this.nb_grains()) {
135 slicename=data[data_index][0];
136 duration=data[data_index][1];
137 var lease=paper.rect (left,top,x_grain*duration,y_node,radius);
138 lease.nodename=nodename;
139 lease.nodelabel=nodelabel;
140 if (slicename == "") {
141 lease.initial="free";
142 lease_methods.init_free(lease);
143 } else if (slicename == this.slicename) {
144 lease.initial="mine";
145 lease_methods.init_mine(lease);
147 lease_initial="other";
148 lease_methods.init_other(lease,slicename);
150 lease.from_time = axisx[grain%this.nb_grains()][0];
152 lease.until_time = axisx[grain%this.nb_grains()][0];
153 // record scheduler in lease
154 lease.scheduler=this;
156 this.append_lease(lease);
157 // move on with the loop
158 left += x_grain*duration;
161 top += y_node + y_sep;
165 this.submit = function () {
166 for (var i=0, len=this.leases.length; i<len; ++i) {
167 var lease=this.leases[i];
168 if (lease.current != lease.initial) {
169 var from_time=lease.from_time;
170 var until_time=lease.until_time;
171 /* scan the leases just after this one and merge if appropriate */
173 while (j<len && lease_methods.compare (lease, until_time, this.leases[j])) {
174 window.console.log('merged index='+j);
175 until_time=this.leases[j].until_time;
178 var method=(lease.current=='free') ? 'DeleteLeases' : 'AddLeases';
179 window.console.log(method + "(" +
180 "[" + lease.nodename + "]," +
181 this.slicename + "," +
188 this.clear = function () {
189 for (var i=0, len=this.leases.length; i<len; ++i) {
190 var lease=this.leases[i];
191 if (lease.current != lease.initial) {
192 if (lease.initial == 'free') lease_methods.init_free(lease,lease_methods.click_mine);
193 else lease_methods.init_mine(lease,lease_methods.click_free);
200 //////////////////////////////////////// couldn't find how to inhererit from the raphael objects...
202 //////////////////// the 'all nodes' button
203 var allnodes_methods = {
204 click: function (event) {
205 var scheduler=this.scheduler;
206 /* decide what to do */
208 for (var i=0, len=scheduler.nodelabels.length; i<len; ++i)
209 if (! scheduler.nodelabels[i].selected) unselected++;
210 /* if at least one is not selected : select all */
211 var new_state = (unselected >0) ? 1 : 0;
212 for (var i=0, len=scheduler.nodelabels.length; i<len; ++i)
213 nodelabel_methods.selected(scheduler.nodelabels[i],new_state);
217 //////////////////// the buttons for managing the whole timeslot
218 var timebutton_methods = {
221 click: function (event) {
222 var scheduler = this.scheduler;
223 var from_time = this.from_time;
224 var until_time = from_time + scheduler.granularity;
225 /* scan leases on selected nodes, store in two arrays */
226 var relevant_free=[], relevant_mine=[];
227 for (var i=0,len=scheduler.leases.length; i<len; ++i) {
228 var scan=scheduler.leases[i];
229 if ( ! scan.nodelabel.selected) continue;
231 if (scan.from_time<=from_time && scan.until_time>=until_time) {
232 if (scan.current == "free") relevant_free.push(scan);
233 else if (scan.current == "mine") relevant_mine.push(scan);
236 // window.console.log("Found " + relevant_free.length + " free and " + relevant_mine.length + " mine");
237 /* decide what to do, whether book or release */
238 if (relevant_mine.length==0 && relevant_free.length==0) {
239 alert ("Nothing to do in this timeslot on the selected nodes");
242 // if at least one is free, let's book
243 if (relevant_free.length > 0) {
244 for (var i=0, len=relevant_free.length; i<len; ++i) {
245 var lease=relevant_free[i];
246 lease_methods.init_mine(lease,lease_methods.click_free);
248 // otherwise we unselect
250 for (var i=0, len=relevant_mine.length; i<len; ++i) {
251 var lease=relevant_mine[i];
252 lease_methods.init_free(lease,lease_methods.click_mine);
258 //////////////////// the nodelabel buttons
259 var nodelabel_methods = {
261 // set selected mode and render visually
262 selected: function (nodelabel, flag) {
263 nodelabel.selected=flag;
264 nodelabel.attr({'font-weight': (flag ? 'bold' : 'normal')});
268 click: function (event) {
269 nodelabel_methods.selected( this, ! this.selected );
274 //////////////////// the lease buttons
275 var lease_methods = {
277 /* in the process of merging leases before posting to the API */
278 compare: function (lease, until_time, next_lease) {
279 return (next_lease['nodename'] == lease['nodename'] &&
280 next_lease['from_time'] == until_time &&
281 next_lease['initial'] == lease['initial'] &&
282 next_lease['current'] == lease['current']);
285 init_free: function (lease, unclick) {
286 lease.current="free";
288 lease.animate((lease.initial=="free") ? attr_lease_free_free : attr_lease_mine_free,anim_delay);
289 // keep track of the current status
291 lease.click (lease_methods.click_free);
292 if (unclick) lease.unclick(unclick);
295 // find out all the currently free leases that overlap this one
296 click_free: function (event) {
297 var scheduler = this.scheduler;
298 lease_methods.init_mine(this,lease_methods.click_free);
301 init_mine: function (lease, unclick) {
302 lease.current="mine";
303 lease.animate((lease.initial=="mine") ? attr_lease_mine_mine : attr_lease_free_mine,anim_delay);
304 lease.click (lease_methods.click_mine);
305 if (unclick) lease.unclick(unclick);
308 click_mine: function (event) {
309 var scheduler = this.scheduler;
310 // this lease was originally free but is now marked for booking
311 // we free just this lease
312 lease_methods.init_free(this, lease_methods.click_mine);
316 init_other: function (lease, slicename) {
317 lease.animate (attr_lease_other,anim_delay);
318 /* a text obj to display the name of the slice that owns that lease */
319 var otherslicelabel = paper.text (lease.attr("x")+lease.attr("width")/2,
321 lease.attr("y")+lease.attr("height")/2,slicename).attr(txt_slice);
322 /* hide it right away */
323 otherslicelabel.hide();
325 lease.label=otherslicelabel;
326 lease.hover ( function (e) { this.label.toFront(); this.label.show(); },
327 function (e) { this.label.hide(); } );
331 function init_scheduler () {
333 var data = [], axisx = [], axisy = [];
334 var table = $$("table#leases_data")[0];
335 // no reservable nodes - no data
336 if ( ! table) return;
338 table.getElementsBySelector("tbody>tr>th").each(function (cell) {
339 axisy.push(getInnerText(cell));
341 // the timeslot labels
342 table.getElementsBySelector("thead>tr>th").each(function (cell) {
343 /* [0]: timestamp -- [1]: displayable*/
344 axisx.push(getInnerText(cell).split("&"));
346 // leases - expect colspan to describe length in grains
347 table.getElementsBySelector("tbody>tr>td").each(function (cell) {
348 data.push(new Array (getInnerText(cell),cell.colSpan));
350 // slicename : the upper-left cell
351 var scheduler = new Scheduler (getInnerText(table.getElementsBySelector("thead>tr>td")[0]), axisx, axisy, data);
353 // leases_area is a <div> created by slice.php as a placeholder
354 scheduler.init ("leases_area");
356 var submit=$$("button#leases_submit")[0];
357 submit.onclick = function () { scheduler.submit(); }
358 var clear=$$("button#leases_clear")[0];
359 clear.onclick = function () { scheduler.clear(); }
363 Event.observe(window, 'load', init_scheduler);