root / ui / static / synnefo.js @ 978648f4
History | View | Annotate | Download (15.2 kB)
1 |
//
|
---|---|
2 |
// Copyright 2011 GRNET S.A. All rights reserved.
|
3 |
//
|
4 |
// Redistribution and use in source and binary forms, with or
|
5 |
// without modification, are permitted provided that the following
|
6 |
// conditions are met:
|
7 |
//
|
8 |
// 1. Redistributions of source code must retain the above
|
9 |
// copyright notice, this list of conditions and the following
|
10 |
// disclaimer.
|
11 |
//
|
12 |
// 2. Redistributions in binary form must reproduce the above
|
13 |
// copyright notice, this list of conditions and the following
|
14 |
// disclaimer in the documentation and/or other materials
|
15 |
// provided with the distribution.
|
16 |
//
|
17 |
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
18 |
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
19 |
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
20 |
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
21 |
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
22 |
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
23 |
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
24 |
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
25 |
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
26 |
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
27 |
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
28 |
// POSSIBILITY OF SUCH DAMAGE.
|
29 |
//
|
30 |
// The views and conclusions contained in the software and
|
31 |
// documentation are those of the authors and should not be
|
32 |
// interpreted as representing official policies, either expressed
|
33 |
// or implied, of GRNET S.A.
|
34 |
//
|
35 |
var API_URL = "/api/v1.1"; |
36 |
var changes_since = 0, deferred = 0, update_request = false, load_request = false, pending_actions = []; |
37 |
var flavors = [], images = [], servers = [], disks = [], cpus = [], ram = [];
|
38 |
var networks = [], networks_changes_since = 0; |
39 |
var error_timeout = 20000; |
40 |
var last_request = {};
|
41 |
|
42 |
|
43 |
Object.prototype.toString = function(o){ |
44 |
|
45 |
var parse = function(_o){ |
46 |
var a = [], t;
|
47 |
for(var p in _o){ |
48 |
if(_o.hasOwnProperty(p)){
|
49 |
t = _o[p]; |
50 |
if(t && typeof t == "object"){ |
51 |
a[a.length]= p + ":{ " + arguments.callee(t).join(", ") + "}"; |
52 |
} |
53 |
else {
|
54 |
if(typeof t == "string"){ |
55 |
a[a.length] = [ p+ ": \"" + t.toString() + "\"" ]; |
56 |
} |
57 |
else{
|
58 |
a[a.length] = [ p+ ": " + t.toString()];
|
59 |
} |
60 |
} |
61 |
} |
62 |
} |
63 |
return a;
|
64 |
|
65 |
} |
66 |
return "{" + parse(o).join(", ") + "}"; |
67 |
|
68 |
} |
69 |
|
70 |
function msg_box(user_config) { |
71 |
var defaults = {'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false}; |
72 |
var config = $.extend(defaults, user_config); |
73 |
|
74 |
// prepare the error message
|
75 |
// bring up success notification
|
76 |
var box = $("#notification-box"); |
77 |
box.addClass("notification-box");
|
78 |
box.addClass('success');
|
79 |
box.addClass(config.cls || '');
|
80 |
box.removeClass('error');
|
81 |
|
82 |
var sel = function(s){return $(s, box)}; |
83 |
// reset texts
|
84 |
sel("h3 span.header-box").html(""); |
85 |
sel(".sub-text").html(""); |
86 |
sel(".password-container .password").html(""); |
87 |
sel("div.machine-now-building").html(""); |
88 |
|
89 |
// apply msg box contents
|
90 |
sel("h3 span.header-box").html(config.title);
|
91 |
sel("div.machine-now-building").html(config.content);
|
92 |
sel(".sub-text").html(config.sub_content || ''); |
93 |
sel(".popup-header").removeClass("popup-header-error"); |
94 |
box.removeClass("popup-border-error");
|
95 |
sel(".popup-details").removeClass("popup-details-error"); |
96 |
sel(".popup-separator").removeClass("popup-separator-error"); |
97 |
|
98 |
sel(".password-container").hide();
|
99 |
if (config.extra) {
|
100 |
sel(".password-container .password").html(config.extra);
|
101 |
sel(".password-container").show();
|
102 |
} |
103 |
|
104 |
var conf = {
|
105 |
// some mask tweaks suitable for modal dialogs
|
106 |
mask: '#666', |
107 |
top: '10px', |
108 |
closeOnClick: false, |
109 |
oneInstance: false, |
110 |
load: false, |
111 |
onLoad: config.onLoad || false, |
112 |
fixed: config.fixed || false, |
113 |
onClose: function () { |
114 |
// With partial refresh working properly,
|
115 |
// it is no longer necessary to refresh the whole page
|
116 |
// choose_view();
|
117 |
} |
118 |
} |
119 |
|
120 |
var triggers = $("a#msgbox").overlay(conf); |
121 |
|
122 |
try {
|
123 |
conf = $("a#msgbox").data('overlay').getConf(); |
124 |
conf.fixed = config.fixed || false;
|
125 |
} catch (err) {}
|
126 |
$("a#msgbox").data('overlay').load(); |
127 |
|
128 |
var parse_data = config.parse_data || false; |
129 |
var load_html = config.html || false; |
130 |
var user_success = config.success || false; |
131 |
config.ajax = config.ajax || {}; |
132 |
|
133 |
// requested to show remote data in msg_box
|
134 |
if (config.ajax && !$.isEmptyObject(config.ajax)) { |
135 |
$.ajax($.extend({ |
136 |
url:config.ajax,
|
137 |
success: function(data){ |
138 |
// we want to get our data parsed before
|
139 |
// placing them in content
|
140 |
if (parse_data) {
|
141 |
data = parse_data(data); |
142 |
} |
143 |
|
144 |
// no json response
|
145 |
// load html body
|
146 |
if (load_html) {
|
147 |
sel("div.machine-now-building").html(data);
|
148 |
} else {
|
149 |
|
150 |
if (data.title) {
|
151 |
sel("h3 span.header-box").text(data.title);
|
152 |
} |
153 |
|
154 |
if (data.content) {
|
155 |
sel("div.machine-now-building").html(data.content);
|
156 |
} |
157 |
if (data.extra) {
|
158 |
sel(".password-container .password").html(data.extra);
|
159 |
sel(".password-container").show();
|
160 |
} |
161 |
if (data.subinfo) {
|
162 |
sel(".sub-text").html(data.subinfo);
|
163 |
} else {
|
164 |
sel(".sub-text").html(""); |
165 |
} |
166 |
} |
167 |
|
168 |
if (user_success) {
|
169 |
user_success($("div.machine-now-building")); |
170 |
} |
171 |
}, |
172 |
error: function(xhr, status, err) { |
173 |
ajax_error(-519, "UI Error", "Machine connect", err, this); |
174 |
} |
175 |
}, config.ajax_config)); |
176 |
} |
177 |
return false; |
178 |
} |
179 |
|
180 |
function show_invitations() { |
181 |
|
182 |
function display_resend_success(msg) { |
183 |
clear_resend_messages(); |
184 |
$("#invsent .message.success").text(msg).show(); |
185 |
} |
186 |
|
187 |
function display_resend_error(msg) { |
188 |
clear_resend_messages(); |
189 |
$("#invsent .message.errormsg").text(msg).show(); |
190 |
} |
191 |
|
192 |
// clear resent messages
|
193 |
function clear_resend_messages() { |
194 |
$("#invsent .message").hide(); |
195 |
} |
196 |
|
197 |
// register resent click handlers
|
198 |
function register_invitation_resends() { |
199 |
$(".invitations .resend-invitation").click(function() { |
200 |
var invid = $(this).attr("id"); |
201 |
|
202 |
if (invid == null) |
203 |
return;
|
204 |
|
205 |
var id = invid.split("-")[1]; |
206 |
|
207 |
if (id == null) |
208 |
return;
|
209 |
|
210 |
var child = $(this).find("img"); |
211 |
child.attr('src', '/static/progress-tiny.gif'); |
212 |
|
213 |
$.ajax({
|
214 |
type: "POST", |
215 |
url : "/invitations/resend", |
216 |
data : {invid : id}, |
217 |
success: function(msg) { |
218 |
display_resend_success("Invitation has been resent");
|
219 |
child.attr('src', '/static/resend.png'); |
220 |
}, |
221 |
error : function(xhr, status, error) { |
222 |
display_resend_error("Something seems to have gone wrong. " +
|
223 |
"Please try again in a few minutes.");
|
224 |
child.attr('src', '/static/resend.png'); |
225 |
} |
226 |
}); |
227 |
}); |
228 |
} |
229 |
|
230 |
handle_invitations = function(el) { |
231 |
|
232 |
// proper class to identify the overlay block
|
233 |
el.addClass("invitations");
|
234 |
|
235 |
var cont = el;
|
236 |
var form = $(el).find("form"); |
237 |
|
238 |
// remove garbage rows that stay in DOM between requests
|
239 |
$(".removable-field-row:hidden").remove(); |
240 |
|
241 |
// avoid buggy behaviour, close all overlays if something went wrong
|
242 |
try {
|
243 |
// form is in content (form is not displayed if user has no invitations)
|
244 |
if ($("#invform #removable-name-container-1").length) { |
245 |
$("#invform #removable-name-container-1").dynamicField(); |
246 |
} |
247 |
} catch (err) {
|
248 |
close_all_overlays(); |
249 |
} |
250 |
|
251 |
// we copy/paste it on the title no need to show it twice
|
252 |
$(".invitations-left").hide(); |
253 |
|
254 |
// sending finished or first invitations view
|
255 |
$(".invitations .sending").hide(); |
256 |
$(".invitations .submit").show(); |
257 |
$(".invitations #fieldheaders").show(); |
258 |
$(".invitations #fields").show(); |
259 |
|
260 |
// reset title
|
261 |
$("#notification-box .header-box").html(""); |
262 |
$("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text()); |
263 |
|
264 |
// resend buttons
|
265 |
register_invitation_resends(); |
266 |
clear_resend_messages(); |
267 |
|
268 |
// handle form submit
|
269 |
form.submit(function(evn){
|
270 |
evn.preventDefault(); |
271 |
|
272 |
// sending...
|
273 |
$(".invitations .sending").show(); |
274 |
$(".invitations .submit").hide(); |
275 |
$(".invitations #fieldheaders").hide(); |
276 |
$(".invitations #fields").hide(); |
277 |
|
278 |
// do the post
|
279 |
$.post(form.attr("action"), form.serialize(), function(data) { |
280 |
// replace data
|
281 |
$(cont).html(data);
|
282 |
|
283 |
// append all handlers again (new html data need to redo all changes)
|
284 |
handle_invitations(cont); |
285 |
}); |
286 |
|
287 |
return false; |
288 |
}); |
289 |
} |
290 |
|
291 |
// first time clicked (show the msg box with /invitations content)
|
292 |
msg_box({ |
293 |
title:window.INVITATIONS_TITLE,
|
294 |
content:'', |
295 |
fixed: false, |
296 |
ajax:INVITATIONS_URL,
|
297 |
html:true, |
298 |
success: function(el){ |
299 |
handle_invitations(el) |
300 |
} |
301 |
}); |
302 |
} |
303 |
|
304 |
|
305 |
function get_short_v6(v6, parts_to_keep) { |
306 |
var parts = v6.split(":"); |
307 |
var new_parts = parts.slice(parts.length - parts_to_keep);
|
308 |
return new_parts.join(":"); |
309 |
} |
310 |
|
311 |
function fix_v6_addresses() { |
312 |
|
313 |
// what to prepend
|
314 |
var match = "..."; |
315 |
// long ip min length
|
316 |
var limit = 20; |
317 |
// parts to show after the transformation
|
318 |
// (from the end)
|
319 |
var parts_to_keep_from_end = 4; |
320 |
|
321 |
$(".machine .ipv6-text").each(function(index, el){ |
322 |
var el = $(el); |
323 |
var ip = $(el).text(); |
324 |
|
325 |
// transformation not applyied
|
326 |
// FIXME: use $.data for the condition
|
327 |
if (ip.indexOf(match) == -1 && ip != "pending") { |
328 |
|
329 |
// only too long ips
|
330 |
if (ip.length > 20) { |
331 |
$(el).data("ipstring", ip); |
332 |
$(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
|
333 |
$(el).attr("title", ip); |
334 |
$(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'}); |
335 |
} |
336 |
} else {
|
337 |
if (ip.indexOf(match) == 0) { |
338 |
} else {
|
339 |
// not a long ip anymore
|
340 |
$(el).data("ipstring", undefined); |
341 |
$(el).css({'text-decoration':'none'}); |
342 |
|
343 |
if ($(el).data('tooltip')) { |
344 |
$(el).data('tooltip').show = function () {}; |
345 |
} |
346 |
} |
347 |
} |
348 |
}); |
349 |
} |
350 |
|
351 |
// get stats
|
352 |
function get_server_stats(serverID) { |
353 |
|
354 |
// do not update stats if machine in build state
|
355 |
var vm = get_machine(serverID);
|
356 |
if (vm.status == "BUILD" && vm.stats_timeout) { |
357 |
els = get_current_view_stats_elements(vm.id); |
358 |
els.cpu.img.hide(); |
359 |
els.net.img.hide(); |
360 |
|
361 |
els.cpu.busy.show(); |
362 |
els.net.busy.show(); |
363 |
return;
|
364 |
} |
365 |
|
366 |
$.ajax({
|
367 |
repeated: true, |
368 |
url: API_URL + '/servers/' + serverID + '/stats', |
369 |
cache: false, |
370 |
type: "GET", |
371 |
//async: false,
|
372 |
dataType: "json", |
373 |
timeout: TIMEOUT,
|
374 |
error: function(jqXHR, textStatus, errorThrown) { |
375 |
handle_api_error(-21, undefined, 'Get server stats', jqXHR, textStatus, errorThrown, this); |
376 |
}, |
377 |
success: function(data, textStatus, jqXHR) { |
378 |
//update_machine_stats(serverID, data);
|
379 |
}, |
380 |
|
381 |
// pass server id to ajax settings
|
382 |
serverID: serverID
|
383 |
}); |
384 |
return false; |
385 |
} |
386 |
|
387 |
function get_progress_details(id) { |
388 |
var vm = get_machine(id);
|
389 |
var progress = vm.progress;
|
390 |
|
391 |
// no details for active machines
|
392 |
if (!vm.status == "BUILD") { |
393 |
return false; |
394 |
} |
395 |
|
396 |
// check if images not loaded yet
|
397 |
try {
|
398 |
var image = synnefo.storage.images.get(vm.imageRef).attributes;
|
399 |
var size = image.size;
|
400 |
} catch (err) {
|
401 |
// images not loaded yet (can this really happen ??)
|
402 |
return;
|
403 |
} |
404 |
|
405 |
var to_copy = size;
|
406 |
var copied = (size * progress / 100).toFixed(2); |
407 |
var status = "INIT" |
408 |
|
409 |
// apply state
|
410 |
if (progress > 0) { status = "IMAGE_COPY" } |
411 |
if (progress >= 100) { status = "FINISH" } |
412 |
|
413 |
// user information
|
414 |
var msg = BUILDING_MESSAGES[status];
|
415 |
|
416 |
// image copy state display extended user information
|
417 |
//if (status == "IMAGE_COPY") {
|
418 |
//msg = msg.format(readablizeBytes(copied*(1024*1024)), readablizeBytes(to_copy*(1024*1024)), progress)
|
419 |
//}
|
420 |
|
421 |
var progress_data = {
|
422 |
'percent': vm.progress,
|
423 |
'build_status': status,
|
424 |
'copied': copied,
|
425 |
'to_copy': size,
|
426 |
'msg': msg
|
427 |
} |
428 |
|
429 |
return progress_data;
|
430 |
} |
431 |
|
432 |
// display user friendly bytes amount
|
433 |
function readablizeBytes(bytes) { |
434 |
var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB']; |
435 |
var e = Math.floor(Math.log(bytes)/Math.log(1024)); |
436 |
return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e]; |
437 |
} |
438 |
|
439 |
// machines images utils
|
440 |
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) { |
441 |
var views_map = {'single': '.single-image', 'icon': '.logo'}; |
442 |
var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'} |
443 |
var sizes_map = {'single': 'large', 'icon': 'medium'} |
444 |
|
445 |
var size = sizes_map[machines_view];
|
446 |
var img_selector = views_map[machines_view];
|
447 |
var cls = states_map[state];
|
448 |
|
449 |
if (os === "unknown") { os = "okeanos" } ; |
450 |
var new_img = 'url("./static/icons/machines/' + size + '/' + os + '-sprite.png")'; |
451 |
|
452 |
var el = $(img_selector, machine); |
453 |
var current_img = el.css("backgroundImage"); |
454 |
if (os == undefined){ |
455 |
new_img = current_img; |
456 |
} |
457 |
|
458 |
// os changed
|
459 |
el.css("backgroundImage", new_img);
|
460 |
|
461 |
// reset current state
|
462 |
if (skip_reset_states === undefined) |
463 |
{ |
464 |
el.removeClass("single-image-state1");
|
465 |
el.removeClass("single-image-state2");
|
466 |
el.removeClass("single-image-state3");
|
467 |
el.removeClass("single-image-state4");
|
468 |
} |
469 |
|
470 |
if (remove_state !== undefined) |
471 |
{ |
472 |
remove_state = "single-image-" + states_map[remove_state];
|
473 |
el.removeClass(remove_state); |
474 |
return;
|
475 |
} |
476 |
|
477 |
// set proper state
|
478 |
el.addClass("single-image-" + cls);
|
479 |
} |
480 |
|
481 |
// return machine entry from serverID
|
482 |
function get_machine(serverID) { |
483 |
return synnefo.storage.vms.get(serverID).attributes;
|
484 |
} |
485 |
|