Statistics
| Branch: | Tag: | Revision:

root / ui / static / synnefo.js @ 8d08f18a

History | View | Annotate | Download (19.9 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
// http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area 
71
$.fn.setCursorPosition = function(pos) {
72
    if ($(this).get(0).setSelectionRange) {
73
      $(this).get(0).setSelectionRange(pos, pos);
74
    } else if ($(this).get(0).createTextRange) {
75
      var range = $(this).get(0).createTextRange();
76
      range.collapse(true);
77
      range.moveEnd('character', pos);
78
      range.moveStart('character', pos);
79
      range.select();
80
    }
81
}
82

    
83
// indexOf prototype for IE
84
if (!Array.prototype.indexOf) {
85
  Array.prototype.indexOf = function(elt /*, from*/) {
86
    var len = this.length;
87
    var from = Number(arguments[1]) || 0;
88
    from = (from < 0)
89
         ? Math.ceil(from)
90
         : Math.floor(from);
91
    if (from < 0)
92
      from += len;
93

    
94
    for (; from < len; from++) {
95
      if (from in this &&
96
          this[from] === elt)
97
        return from;
98
    }
99
    return -1;
100
  };
101
}
102

    
103
// trim prototype for IE
104
if(typeof String.prototype.trim !== 'function') {
105
    String.prototype.trim = function() {
106
        return this.replace(/^\s+|\s+$/g, '');
107
    }
108
}
109

    
110
// simple string format helper (http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format)
111
String.prototype.format = function() {
112
    var formatted = this;
113
    for (var i = 0; i < arguments.length; i++) {
114
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
115
        formatted = formatted.replace(regexp, arguments[i]);
116
    }
117
    return formatted;
118
};
119

    
120

    
121
// Show VNC console
122
function vnc_attachment(host, port, password) {
123
    // FIXME: Must be made into parameters, in settings.py
124
    //vnc = open("", "displayWindow",
125
    //    "status=yes,toolbar=yes,menubar=yes");
126
    vd = document.open("application/x-vnc");
127

    
128
    vd.writeln("[connection]");
129
    vd.writeln("host=" + host);
130
    vd.writeln("port=" + port);
131
    vd.writeln("password=" + password);
132

    
133
    vd.close();
134
}
135

    
136
// connect to machine action
137
function machine_connect(serverIDs){
138
    if (!serverIDs.length){
139
        //ajax_success('DEFAULT');
140
        return false;
141
    }
142
    
143
    // prefer metadata values for specific options (username, domain)
144
    var username_meta_key = 'user';
145
    var domain_meta_key = "domain";
146

    
147
    var serverID = serverIDs.pop();
148
    var machine = get_machine(serverID);
149
    var serverName = machine.name;
150

    
151
    try {
152
        var serverIP = machine.addresses.values[0].values[0].addr;
153
    } catch (err) { var serverIP = 'undefined'; }
154

    
155
    try {
156
        var os = os_icon(machine.metadata);
157
    } catch (err) { var os = 'undefined'; }
158

    
159
    var username = "";
160
    try {
161
        username = machine.metadata.values[username_meta_key];
162
    } catch (err) { username = undefined }
163

    
164
    var domain = "";
165
    try {
166
        domain = machine.metadata.values[domain_meta_key];
167
    } catch (erro) { domain = undefined }
168

    
169
    var params_url = '?ip_address=' + serverIP + '&os=' + os + "&host_os=" + $.client.os + "&srv=" + serverID;
170

    
171
    if (username) {
172
        params_url += "&username=" + username;
173
    }
174

    
175
    if (domain) {
176
        params_url += "&domain=" + domain;
177
    }
178
    
179
    //if ($.client.os == "Windows" && os == "windows") {
180
        //// request rdp file
181
        //window.open('machines/connect' + params_url + "&rdp=1");
182
        //return;
183
    //}
184
    
185
    // FIXME: I18n ???
186
    var title = 'Connect to: ' + '<span class="machine-title"><img src="static/icons/machines/small/'+os+'-on.png" /> ' + fix_server_name(serverName) + '</span>';
187
    
188
    // open msg box and fill it with json data retrieved from connect machine view
189
    try {
190
        // open msg box
191
        msg_box({
192
            title:title, 
193
            fixed: true,
194
            content:'loading...',
195
            extra:'', 'ajax':'machines/connect' + params_url,
196
            parse_data:function(data){
197
                var box_content = "<a href='"+data.link.url+"'>"+data.link.title+"</a>";
198
                if (!data.link.url) {
199
                    box_content = "<span class='cmd'>"+data.link.title+"</span>";
200
                }
201
                data.title = false;
202
                data.content = data.info;
203
                data.extra = box_content;
204
                return data;
205
            }
206
        });
207
    } catch (error) {
208
        // if msg box fails fallback redirecting the user to the connect url
209
        window.open('machines/connect' + params_url);
210
    }
211

    
212

    
213
    // Restore os icon in list view
214
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
215
    osIcon.attr('src',osIcon.attr('os'));
216

    
217
    return false;
218
}
219

    
220

    
221
function msg_box(user_config) {
222
    var defaults = {'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false};
223
    var config = $.extend(defaults, user_config);
224

    
225
    // prepare the error message
226
    // bring up success notification
227
    var box = $("#notification-box");
228
    box.addClass("notification-box");
229
    box.addClass('success');
230
    box.addClass(config.cls || '');
231
    box.removeClass('error');
232

    
233
    var sel = function(s){return $(s, box)};
234
    // reset texts
235
    sel("h3 span.header-box").html("");
236
    sel(".sub-text").html("");
237
    sel(".password-container .password").html("");
238
    sel("div.machine-now-building").html("");
239

    
240
    // apply msg box contents
241
    sel("h3 span.header-box").html(config.title);
242
    sel("div.machine-now-building").html(config.content);
243
    sel(".sub-text").html(config.sub_content || '');
244
    sel(".popup-header").removeClass("popup-header-error");
245
    box.removeClass("popup-border-error");
246
    sel(".popup-details").removeClass("popup-details-error");
247
    sel(".popup-separator").removeClass("popup-separator-error");
248
    
249
    sel(".password-container").hide();
250
    if (config.extra) {
251
        sel(".password-container .password").html(config.extra);
252
        sel(".password-container").show();
253
    }
254
    
255
    var conf = {
256
        // some mask tweaks suitable for modal dialogs
257
        mask: '#666',
258
        top: '10px',
259
        closeOnClick: false,
260
        oneInstance: false,
261
        load: false,
262
        onLoad: config.onLoad || false,
263
        fixed: config.fixed || false,
264
        onClose: function () {
265
            // With partial refresh working properly,
266
            // it is no longer necessary to refresh the whole page
267
            // choose_view();
268
        }
269
    }
270

    
271
    var triggers = $("a#msgbox").overlay(conf);
272

    
273
    try {
274
        conf = $("a#msgbox").data('overlay').getConf();
275
        conf.fixed = config.fixed || false;
276
    } catch (err) {}
277
    $("a#msgbox").data('overlay').load();
278
    
279
    var parse_data = config.parse_data || false;
280
    var load_html = config.html || false;
281
    var user_success = config.success || false;
282
    config.ajax = config.ajax || {};
283

    
284
    // requested to show remote data in msg_box
285
    if (config.ajax && !$.isEmptyObject(config.ajax)) {
286
        $.ajax($.extend({ 
287
            url:config.ajax, 
288
            success: function(data){
289
                // we want to get our data parsed before
290
                // placing them in content
291
                if (parse_data) {
292
                    data = parse_data(data);
293
                }
294

    
295
                // no json response
296
                // load html body
297
                if (load_html) {
298
                    sel("div.machine-now-building").html(data);
299
                } else {
300

    
301
                    if (data.title) {
302
                        sel("h3 span.header-box").text(data.title);
303
                    }
304

    
305
                    if (data.content) {
306
                        sel("div.machine-now-building").html(data.content);
307
                    }
308
                    if (data.extra) {
309
                        sel(".password-container .password").html(data.extra);
310
                        sel(".password-container").show();
311
                    }
312
                    if (data.subinfo) {
313
                        sel(".sub-text").html(data.subinfo);
314
                    } else {
315
                        sel(".sub-text").html("");
316
                    }
317
                }
318

    
319
                if (user_success) {
320
                    user_success($("div.machine-now-building"));
321
                }
322
            },
323
            error: function(xhr, status, err) {
324
                ajax_error(-519, "UI Error", "Machine connect", err, this);
325
            }
326
        }, config.ajax_config));
327
    }
328
    return false;
329
}
330

    
331

    
332
function show_api_overlay() {
333
    var config = {
334
        title: window.API_OVERLAY_TITLE,
335
        content: $(".api_overlay_content").html().replace("$api_key", $.cookie("X-Auth-Token")),
336
        extra: $.cookie("X-Auth-Token"),
337
        sub_content: window.API_OVERLAY_SUBCONTENT,
338
        cls: "api_content",
339
        ajax: false
340
    }
341
    msg_box(config);
342
}
343

    
344
function show_invitations() {
345
    
346
    function display_resend_success(msg) {
347
        clear_resend_messages();
348
        $("#invsent .message.success").text(msg).show();
349
    }
350

    
351
    function display_resend_error(msg) {
352
        clear_resend_messages();
353
        $("#invsent .message.errormsg").text(msg).show();
354
    }
355

    
356
    // clear resent messages
357
    function clear_resend_messages() {
358
        $("#invsent .message").hide();
359
    }
360

    
361
    // register resent click handlers
362
    function register_invitation_resends() {
363
        $(".invitations .resend-invitation").click(function() {
364
            var invid = $(this).attr("id");
365

    
366
            if (invid == null)
367
                return;
368

    
369
            var id = invid.split("-")[1];
370

    
371
            if (id == null)
372
                return;
373

    
374
            var child = $(this).find("img");
375
            child.attr('src', '/static/progress-tiny.gif');
376

    
377
            $.ajax({
378
                type: "POST",
379
                url : "/invitations/resend",
380
                data : {invid : id},
381
                success: function(msg) {
382
                    display_resend_success("Invitation has been resent");
383
                    child.attr('src', '/static/resend.png');
384
                },
385
                error : function(xhr, status, error) {
386
                    display_resend_error("Something seems to have gone wrong. " +
387
                          "Please try again in a few minutes.");
388
                    child.attr('src', '/static/resend.png');
389
                }
390
            });
391
        });
392
    }
393

    
394
    handle_invitations = function(el) {
395

    
396
        // proper class to identify the overlay block
397
        el.addClass("invitations");
398

    
399
        var cont = el;
400
        var form = $(el).find("form");
401

    
402
        // remove garbage rows that stay in DOM between requests
403
        $(".removable-field-row:hidden").remove();
404

    
405
        // avoid buggy behaviour, close all overlays if something went wrong
406
        try {
407
            // form is in content (form is not displayed if user has no invitations)
408
            if ($("#invform #removable-name-container-1").length) {
409
                $("#invform #removable-name-container-1").dynamicField();
410
            }
411
        } catch (err) {
412
            close_all_overlays();
413
        }
414
        
415
        // we copy/paste it on the title no need to show it twice
416
        $(".invitations-left").hide();
417
        
418
        // sending finished or first invitations view
419
        $(".invitations .sending").hide();
420
        $(".invitations .submit").show();
421
        $(".invitations #fieldheaders").show();
422
        $(".invitations #fields").show();
423

    
424
        // reset title
425
        $("#notification-box .header-box").html("");
426
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
427
    
428
        // resend buttons
429
        register_invitation_resends();
430
        clear_resend_messages();
431

    
432
        // handle form submit
433
        form.submit(function(evn){
434
            evn.preventDefault();
435
            
436
            // sending...
437
            $(".invitations .sending").show();
438
            $(".invitations .submit").hide();
439
            $(".invitations #fieldheaders").hide();
440
            $(".invitations #fields").hide();
441

    
442
            // do the post
443
            $.post(form.attr("action"), form.serialize(), function(data) {
444
                // replace data
445
                $(cont).html(data); 
446

    
447
                // append all handlers again (new html data need to redo all changes)
448
                handle_invitations(cont);
449
            });
450

    
451
            return false;
452
        });
453
    }
454
    
455
    // first time clicked (show the msg box with /invitations content)
456
    msg_box({
457
        title:window.INVITATIONS_TITLE, 
458
        content:'', 
459
        fixed: false,
460
        ajax:INVITATIONS_URL, 
461
        html:true, 
462
        success: function(el){ 
463
            handle_invitations(el)
464
        }
465
    });
466
}
467

    
468

    
469
function get_short_v6(v6, parts_to_keep) {
470
    var parts = v6.split(":");
471
    var new_parts = parts.slice(parts.length - parts_to_keep);
472
    return new_parts.join(":");
473
}
474

    
475
function fix_v6_addresses() {
476

    
477
    // what to prepend
478
    var match = "...";
479
    // long ip min length
480
    var limit = 20;
481
    // parts to show after the transformation
482
    // (from the end)
483
    var parts_to_keep_from_end = 4;
484

    
485
    $(".machine .ipv6-text").each(function(index, el){
486
        var el = $(el);
487
        var ip = $(el).text();
488
            
489
        // transformation not applyied
490
        // FIXME: use $.data for the condition
491
        if (ip.indexOf(match) == -1 && ip != "pending") {
492
            
493
            // only too long ips
494
            if (ip.length > 20) {
495
                $(el).data("ipstring", ip);
496
                $(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
497
                $(el).attr("title", ip);
498
                $(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'});
499
            }
500
        } else {
501
            if (ip.indexOf(match) == 0) {
502
            } else {
503
                // not a long ip anymore
504
                $(el).data("ipstring", undefined);
505
                $(el).css({'text-decoration':'none'});
506

    
507
                if ($(el).data('tooltip')) {
508
                    $(el).data('tooltip').show = function () {};
509
                }
510
            }
511
        }
512
    });
513
}
514

    
515
// get stats
516
function get_server_stats(serverID) {
517
    
518
    // do not update stats if machine in build state
519
    var vm = get_machine(serverID);
520
    if (vm.status == "BUILD" && vm.stats_timeout) {
521
        els = get_current_view_stats_elements(vm.id);
522
        els.cpu.img.hide();
523
        els.net.img.hide();
524

    
525
        els.cpu.busy.show();
526
        els.net.busy.show();
527
        return;
528
    }
529

    
530
    $.ajax({
531
        repeated: true,
532
        url: API_URL + '/servers/' + serverID + '/stats',
533
        cache: false,
534
        type: "GET",
535
        //async: false,
536
        dataType: "json",
537
        timeout: TIMEOUT,
538
        error: function(jqXHR, textStatus, errorThrown) {
539
            handle_api_error(-21, undefined, 'Get server stats', jqXHR, textStatus, errorThrown, this);
540
        },
541
        success: function(data, textStatus, jqXHR) {
542
            update_machine_stats(serverID, data);
543
        },
544

    
545
        // pass server id to ajax settings
546
        serverID: serverID
547
    });
548
    return false;
549
}
550

    
551
function get_progress_details(id) {
552
    var vm = get_machine(id);
553
    var progress = vm.progress;
554

    
555
    // no details for active machines
556
    if (!vm.status == "BUILD") {
557
        return false;
558
    }
559
    
560
    // check if images not loaded yet
561
    try {
562
        var image = synnefo.storage.images.get(vm.imageRef).attributes;
563
        var size = image.size;
564
    } catch (err) {
565
        // images not loaded yet (can this really happen ??)
566
        return;
567
    }
568
    
569
    var to_copy = size;
570
    var copied = (size * progress / 100).toFixed(2);
571
    var status = "INIT"
572

    
573
    // apply state
574
    if (progress > 0) { status = "IMAGE_COPY" }
575
    if (progress >= 100) { status = "FINISH" }
576
    
577
    // user information
578
    var msg = BUILDING_MESSAGES[status];
579

    
580
    // image copy state display extended user information
581
    //if (status == "IMAGE_COPY") {
582
        //msg = msg.format(readablizeBytes(copied*(1024*1024)), readablizeBytes(to_copy*(1024*1024)), progress)
583
    //}
584

    
585
    var progress_data = {
586
        'percent': vm.progress,
587
        'build_status': status,
588
        'copied': copied,
589
        'to_copy': size,
590
        'msg': msg
591
    }
592

    
593
    return progress_data;
594
}
595

    
596
// display user friendly bytes amount
597
function readablizeBytes(bytes) {
598
    var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
599
    var e = Math.floor(Math.log(bytes)/Math.log(1024));
600
    return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
601
}
602

    
603
// machines images utils
604
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) {
605
    var views_map = {'single': '.single-image', 'icon': '.logo'};
606
    var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'}
607
    var sizes_map = {'single': 'large', 'icon': 'medium'}
608

    
609
    var size = sizes_map[machines_view];
610
    var img_selector = views_map[machines_view];
611
    var cls = states_map[state];
612

    
613
    if (os === "unknown") { os = "okeanos" } ;
614
    var new_img = 'url("./static/icons/machines/' + size + '/' + os + '-sprite.png")';
615

    
616
    var el = $(img_selector, machine);
617
    var current_img = el.css("backgroundImage");
618
    if (os == undefined){
619
        new_img = current_img;
620
    }
621

    
622
    // os changed
623
    el.css("backgroundImage", new_img);
624

    
625
    // reset current state
626
    if (skip_reset_states === undefined)
627
    {
628
        el.removeClass("single-image-state1");
629
        el.removeClass("single-image-state2");
630
        el.removeClass("single-image-state3");
631
        el.removeClass("single-image-state4");
632
    }
633

    
634
    if (remove_state !== undefined)
635
    {
636
        remove_state = "single-image-" + states_map[remove_state];
637
        el.removeClass(remove_state);
638
        return;
639
    }
640

    
641
    // set proper state
642
    el.addClass("single-image-" + cls);
643
}
644

    
645
// return machine entry from serverID
646
function get_machine(serverID) {
647
    return synnefo.storage.vms.get(serverID).attributes;
648
}
649