1/* DOKUWIKI:include_once raphael.js */
2/* DOKUWIKI:include_once jquery.qtip.js */
3
4jQuery(function () {
5	console.log('---------------------');
6	console.log('ADVRack Plugin v0.0.2');
7        console.log('jQuery ' + jQuery().jquery);
8	console.log('---------------------');
9
10	jQuery('div.advrack').each(function () {
11		console.log('ADVRack: parsing advrack stanza');
12		drawRack(this);
13	});
14});
15
16// define a few global variables
17var drawing_wscale = 2.5, drawing_hscale = 1.5;
18var h_regexp = /^(\d{1,2})([u|in|cm])*/i, w_regexp = /^(\d{1,2})([in|cm])/i;
19
20Raphael.fn.roundedRectangle = function (x, y, w, h, r1, r2, r3, r4){
21  var array = [];
22  array = array.concat(["M",x,r1+y, "Q",x,y, x+r1,y]); //A
23  array = array.concat(["L",x+w-r2,y, "Q",x+w,y, x+w,y+r2]); //B
24  array = array.concat(["L",x+w,y+h-r3, "Q",x+w,y+h, x+w-r3,y+h]); //C
25  array = array.concat(["L",x+r4,y+h, "Q",x,y+h, x,y+h-r4, "Z"]); //D
26
27  return this.path(array);
28};
29
30function drawRack (el) {
31	var racks = jQuery.parseJSON(jQuery.text(el));
32	el.innerHTML = ""; // hide syntax from page.
33
34
35	// define the Raphael paper object
36	var paper = Raphael(el,0,0);
37	paper.setViewBox(0,0,400,300,true);
38	paper.setSize('100%', '100%');
39
40	// iterate over how many racks we have defined.
41	jQuery.each(racks, function(idx, rack) {
42		if (! rack['options']['height']) {
43			drawError({'paper': paper, msg: "No height property found at rack index: " + rack['options']['index']});
44		}
45
46		var rack_height = rack['options']['height'].match(h_regexp);
47		var rack_width = rack['options']['width'].match(w_regexp);
48
49		// calculate total height
50		var servers_height = calcHeight(rack['servers']);
51		var cages_height = calcHeight(rack['cages']);
52
53		if ((servers_height['errors'].length > 0) || (cages_height['errors'].length > 0)) {
54			var errors = new Array();
55			jQuery.each(servers_height['errors'], function(idx, server_err) {
56				errors.push({"msg": server_err['error']});
57			});
58
59			jQuery.each(cages_height['errors'], function(idx,cage_err) {
60				errors.push({"msg": cage_err['error']});
61			});
62
63			if (! jQuery.isEmptyObject(errors))
64				drawError(errors);
65		}
66
67		if ((servers_height['height'] + cages_height['height']) <= rack_height[1] * 4.445) {
68			// draw the rack first: we're scaling 1:1 cm to pixel, we're also converting in to cm for the width and u to cm for the height
69			var raph_rack = paper.rect(6,6, rack_width[1] * 2.54 * drawing_wscale, rack_height[1] * 4.445 * drawing_hscale);
70			raph_rack.attr({"stroke-width": 4});
71
72			// index the equipment from top to bottom
73			var equipment = new Array(Number(rack_height[1]));
74			e_length = equipment.length;
75			for (var i = 0; i < e_length; i++) {
76				equipment[i] = {'type': 'FILLER', 'index': 1+i, 'height': '1U'};
77			}
78
79			jQuery.each(rack['servers'], function(idx, server) {
80				server['type'] = 'SERVER'; // stamp equipment as type: 'SERVER'
81				equipment = deleteFillerPanels(server, equipment);
82			});
83
84			jQuery.each(rack['cages'], function(idx, cage) {
85				cage['type'] = 'CAGE'; // stamp equipment as type: 'CAGE'
86				equipment = deleteFillerPanels(cage, equipment);
87			});
88
89			// draw each equipment
90			jQuery.each(equipment, function(idx, item) {
91				if (item)
92					drawEquipment(paper, raph_rack, item);
93			});
94		} else {
95			drawError({'paper': paper, 'msg': 'Sum of servers and cages height is more than rack ' + rack['options']['index'] + '\'s U space!'});
96		}
97	});
98}
99
100function drawEquipment(paper, rack, equipment) {
101	if (equipment['type']) {
102		switch (equipment['type']) {
103			case "SERVER":
104				var boundaries = rack.getBBox();
105				var h = equipment['height'].match(h_regexp);
106				var equipment_height = h[1] * 4.445 * drawing_hscale;
107				var server = paper.set();
108				var server_block = paper.rect(boundaries.x, boundaries.y + ((equipment['index']-1) * 4.445 * drawing_hscale), boundaries.width, equipment_height, 2.0);
109				// set server attributes
110				var attributes = {'fill': 'white'};
111				if (equipment['fill-color'])
112					attributes['fill'] = equipment['fill-color'];
113				server_block.attr(attributes);
114
115				// create cosmetic effects
116				setupGlowEffect(server_block);
117
118				// set server description
119				if (equipment['tooltip'])
120					setupTooltip(server_block, equipment['tooltip']);
121
122				// bundle stuff into one raphael object
123				server.push(server_block);
124
125				// set server label
126				if (equipment['label']) {
127					var boundaries = server_block.getBBox();
128					var text = paper.text(boundaries.width/2+6, boundaries.y + boundaries.height/2, equipment['label']).attr({'font-size': 4, 'fill': 'black', 'text-anchor': 'middle'});
129					server.push(text);
130				}
131				break;
132			case "CAGE":
133				var boundaries = rack.getBBox();
134				var h = equipment['height'].match(h_regexp);
135				var equipment_height = h[1] * 4.445 * drawing_hscale;
136				var cage = paper.set();
137				var cage_block = paper.rect(boundaries.x, boundaries.y + ((equipment['index']-1) * 4.445 * drawing_hscale), boundaries.width, equipment_height, 2.0);
138				// set cage attributes
139				var attributes = {'fill': 'white'};
140				if (equipment['fill-color'])
141					attributes['fill'] = equipment['fill-color'];
142				cage_block.attr(attributes);
143
144				// set cage label: might be overwritten by modules
145				if (equipment['label']) {
146					var boundaries = cage_block.getBBox();
147					var text = paper.text(boundaries.width/2+6, boundaries.y + boundaries.height/2, equipment['label']).attr({'font-size': 4, 'fill': 'black', 'text-anchor': 'middle'});
148					cage.push(text);
149				}
150
151				// draw cage decks
152				if (equipment['decks']) {
153					var previous_deck_height = 0;
154					var dl = equipment['decks'];
155					for (var d = 0; d < dl.length; d++) {
156						var deck = equipment['decks'][d];
157						var boundaries = cage_block.getBBox();
158						if (! deck['height'])
159							drawError({'paper': paper, 'msg': 'Height not defined in one of cage ' + equipment['index'] + ' decks'});
160						var h = deck['height'].match(h_regexp);
161						var deck_height = h[1] * 4.445 * drawing_hscale;
162						var raph_deck = paper.set();
163						var deck_block, corner = [0,0,0,0];
164						if (d == 0) {
165							deck_block = paper.roundedRectangle(boundaries.x, boundaries.y + previous_deck_height, boundaries.width, deck_height, 2,2,0,0);
166							// set cornors for later use within sub-modules
167							corner = [2,2,0,0];
168						} else if (d < dl.length - 1) {
169							deck_block = paper.roundedRectangle(boundaries.x, boundaries.y + previous_deck_height, boundaries.width, deck_height, 0,0,0,0);
170							// set cornors for later use within sub-modules
171							corner = [0,0,0,0];
172						} else {
173							deck_block = paper.roundedRectangle(boundaries.x, boundaries.y + previous_deck_height, boundaries.width, deck_height, 0,0,2,2);
174							// set cornors for later use within sub-modules
175							corner = [0,0,2,2];
176						}
177
178						// fill the deck with white so mouse hover would have the correct effect
179						deck_block.attr('fill', 'white');
180
181						// setup the deck's tooltip
182						if (deck['tooltip'])
183							setupTooltip(deck_block, deck['tooltip']);
184
185						// set the deck's label
186						if (deck['label']) {
187							var boundaries = deck_block.getBBox();
188							var text = paper.text(boundaries.width/2+6, boundaries.y + boundaries.height/2, deck['label']).attr({'font-size': 4, 'fill': 'black', 'text-anchor': 'middle'});
189							raph_deck.push(text);
190						}
191
192						// create cosmetic effects
193						setupGlowEffect(deck_block);
194
195						// draw blocks and blades
196						if (deck['type'] === 'block') {
197							if (deck['blocks'])
198								drawDeckModules(paper, boundaries, corner, previous_deck_height, deck_height, deck['blocks'], deck, equipment);
199						} else if (deck['type'] === 'blade') {
200							if (deck['blades'])
201								drawDeckModules(paper, boundaries, corner, previous_deck_height, deck_height, deck['blades'], deck, equipment);
202						} else {
203							drawError({'paper': paper, 'msg': 'Invalid equipment type! Decks can have type \'block\' or \'blade\' only.'});
204						}
205
206						raph_deck.push(deck_block);
207						previous_deck_height += deck_height;
208					}
209				}
210
211				// create cosmetic effects
212				setupGlowEffect(cage_block);
213
214				cage.push(cage_block);
215				break;
216			case "FILLER":
217				var boundaries = rack.getBBox();
218				var h = equipment['height'].match(h_regexp);
219				var equipment_height = h[1] * 4.445 * drawing_hscale;
220				var panel = paper.set();
221				var filler = paper.rect(boundaries.x, boundaries.y + ((equipment['index']-1) * 4.445 * drawing_hscale), boundaries.width, equipment_height * 0.95).attr('fill','grey');
222				boundaries = filler.getBBox();
223				var text1 = paper.text(boundaries.x + boundaries.width - 6, boundaries.y + equipment_height/2, equipment['index']).attr({'text-anchor': 'start', 'font-size': 4, 'fill': '#ffffff'});
224				var text2 = paper.text(boundaries.x + 2, boundaries.y + boundaries.height/2, 'filler panel').attr({'text-anchor': 'start', 'font-size': 4, 'fill': '#ffffff'});
225				panel.push(filler, text1, text2);
226				break;
227			default:
228				drawError({'paper': paper, 'msg': 'Invalid equipment type! This is a possible bug in the ADVRack plugin.'});
229		}
230	} else {
231		drawError({'paper': paper, 'msg': 'No equipment type specified!'});
232	}
233}
234
235function calcHeight (items) {
236	var height = 0;
237	var errors = new Array();
238	jQuery.each(items, function(idx, item) {
239		if (! item['height']) {
240			errors.push({"error": "No height property found in item index: " + item['index']});
241		} else if (! h_regexp.test(item['height'])) {
242			errors.push({"error": "No height value specified or incorrect value. Item index: " + item['index']});
243		} else {
244			var h = item['height'].match(h_regexp);
245			switch (h[2].toLowerCase()) {
246				case "in":
247					h[1] *= 2.54; // convert to Centimeters
248					break;
249				case "cm":
250					break; // cm is the default measurement, we use 1:1 cm to px scale.
251				case "u":
252					h[1] *= 4.445; // convert from rack units to centimeters
253					break;
254				default:
255					// we'll assume rack units if nothing is defined
256					h[1] *= 4.445;
257			}
258			height += Number(h[1]);
259		}
260	});
261	return {"errors": errors, "height": height};
262}
263
264function deleteFillerPanels(unit, equipment) {
265	var unit_height = unit['height'].match(h_regexp);
266	unit_height = Number(unit_height[1]);
267	if (unit_height > 1) {
268		for (var h = (unit['index']-1); h < (unit['index'] - 1 + unit_height); h++) {
269			delete equipment[h];
270		}
271		equipment[unit['index']-1] = unit;
272	} else {
273		equipment[unit['index']-1] = unit;
274	}
275	return equipment;
276}
277
278function setupGlowEffect(unit) {
279	unit.hover(function(){ // hover in
280		this.glow_effect = this.glow({ width: 1, color: "blue" });
281		this.glow_effect.toFront();
282	}, function() { // hover out
283		this.glow_effect.remove();
284	});
285}
286
287function setupTooltip(raph_obj, tooltip) {
288	if (tooltip)
289		jQuery(raph_obj.node).qtip(tooltip);
290}
291
292function drawDeckModules(paper, rack_boundaries, corner, previous_deck_height, deck_height, modules, deck, equipment) {
293	// calculate module width
294	var module_width = (rack_boundaries.width)/(modules.length);
295	var previous_module_width = 0;
296	for (var b = 0; b < modules.length; b++) {
297		var module;
298		if (b == 0) {
299			module = paper.roundedRectangle(rack_boundaries.x + previous_module_width, rack_boundaries.y + previous_deck_height, module_width, deck_height, corner[0],0,0,corner[3]);
300			// draw a thin border around the module; useful to define our module edges if it has a fill color.
301			paper.roundedRectangle(rack_boundaries.x + previous_module_width, rack_boundaries.y + previous_deck_height, module_width, deck_height, corner[0],0,0,corner[3]).attr({'stroke-width': 0.2, 'stroke': 'black'});
302		} else if (b < modules.length - 1) {
303			module = paper.rect(rack_boundaries.x + previous_module_width, rack_boundaries.y + previous_deck_height, module_width, deck_height, 0,0,0,0);
304			// draw a thin border around the module; useful to define our module edges if it has a fill color.
305			paper.roundedRectangle(rack_boundaries.x + previous_module_width, rack_boundaries.y + previous_deck_height, module_width, deck_height,0,0,0,0).attr({'stroke-width': 0.2, 'stroke': 'black'});
306		} else {
307			module = paper.roundedRectangle(rack_boundaries.x + previous_module_width, rack_boundaries.y + previous_deck_height, module_width, deck_height, 0,corner[1],corner[2],0);
308			// draw a thin border around the module; useful to define our module edges if it has a fill color.
309			paper.roundedRectangle(rack_boundaries.x + previous_module_width, rack_boundaries.y + previous_deck_height, module_width, deck_height,0,corner[1],corner[2],0).attr({'stroke-width': 0.2, 'stroke': 'black'});
310		}
311
312		// a few cosmetic touches
313		if (modules[b]['fill-color']) {
314			module.attr('fill', modules[b]['fill-color']);
315		} else {
316			module.attr('fill', 'white');
317		}
318		if (modules[b]['tooltip']) {
319			setupTooltip(module, modules[b]['tooltip']);
320		} else if (deck['tooltip']) {
321			setupTooltip(module, deck['tooltip']);
322		} else {
323			if (equipment['tooltip'])
324				setupTooltip(module, equipment['tooltip']);
325		}
326
327		// set the deck's label
328		if (modules[b]['label']) {
329
330			var boundaries = module.getBBox();
331			var text = paper.text(boundaries.x + boundaries.width/2, boundaries.y + boundaries.height/2, modules[b]['label']).attr({'font-size': 4, 'fill': 'black', 'text-anchor': 'middle'});
332			if (deck['type'] === 'blade')
333				text.transform('r-90');
334		}
335
336		// add glow effect to modules
337		setupGlowEffect(module);
338
339		previous_module_width += module_width;
340	}
341}
342
343function drawError (error) {
344	// a function to show error messasge, instead of rack drawing
345	var paper = error.paper;
346	paper.clear();
347	var msg = error.msg;
348	paper.text(10,10, 'ERROR: ' + msg).attr('text-anchor', 'start');
349	exit();
350}
351