Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / synnefo.js @ d42817ee

History | View | Annotate | Download (15.1 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
    var new_img = 'url("' + synnefo.config.machines_icons_url + size + '/' + os + '-sprite.png")';
450

    
451
    var el = $(img_selector, machine);
452
    var current_img = el.css("backgroundImage");
453
    if (os == undefined){
454
        new_img = current_img;
455
    }
456

    
457
    // os changed
458
    el.css("backgroundImage", new_img);
459

    
460
    // reset current state
461
    if (skip_reset_states === undefined)
462
    {
463
        el.removeClass("single-image-state1");
464
        el.removeClass("single-image-state2");
465
        el.removeClass("single-image-state3");
466
        el.removeClass("single-image-state4");
467
    }
468

    
469
    if (remove_state !== undefined)
470
    {
471
        remove_state = "single-image-" + states_map[remove_state];
472
        el.removeClass(remove_state);
473
        return;
474
    }
475

    
476
    // set proper state
477
    el.addClass("single-image-" + cls);
478
}
479

    
480
// return machine entry from serverID
481
function get_machine(serverID) {
482
    return synnefo.storage.vms.get(serverID).attributes;
483
}
484