Statistics
| Branch: | Tag: | Revision:

root / ui / static / synnefo.js @ 40a87146

History | View | Annotate | Download (81 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

    
40

    
41
var error_timeout = 20000;
42
$.ajaxSetup({
43
    'beforeSend': function(xhr) {
44
          xhr.setRequestHeader("X-Auth-Token", $.cookie("X-Auth-Token"));
45
    },
46

    
47
    // catch uncaught error requests
48
    // stop interaction and show only for the 5xx errors
49
    // refresh the page after 20secs
50
    error: function(jqXHR, textStatus, errorThrown) {
51
        // stop interaction for important (aka 500) error codes only
52
        if (jqXHR.status >= 500 && jqXHR.status < 600)
53
        {
54
            try {
55
                ajax_error(jqXHR.status, undefined, 'Unknown', jqXHR.responseText);
56
            } catch(err) {
57
                ajax_error(-5, "UI Error", 'Unknown', err);
58
            }
59
        }
60

    
61
        // refresh after 10 seconds
62
        window.setTimeout("window.location.reload()", window.error_timeout);
63
    }
64
});
65

    
66
Object.prototype.toString = function(o){
67
    
68
    var parse = function(_o){
69
        var a = [], t;
70
        for(var p in _o){
71
            if(_o.hasOwnProperty(p)){
72
                t = _o[p];
73
                if(t && typeof t == "object"){
74
                    a[a.length]= p + ":{ " + arguments.callee(t).join(", ") + "}";
75
                }
76
                else {
77
                    if(typeof t == "string"){
78
                        a[a.length] = [ p+ ": \"" + t.toString() + "\"" ];
79
                    }
80
                    else{
81
                        a[a.length] = [ p+ ": " + t.toString()];
82
                    }
83
                }
84
            }
85
        }
86
        return a;
87
        
88
    }
89
    return "{" + parse(o).join(", ") + "}";
90
   
91
}
92

    
93
// http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area 
94
$.fn.setCursorPosition = function(pos) {
95
    if ($(this).get(0).setSelectionRange) {
96
      $(this).get(0).setSelectionRange(pos, pos);
97
    } else if ($(this).get(0).createTextRange) {
98
      var range = $(this).get(0).createTextRange();
99
      range.collapse(true);
100
      range.moveEnd('character', pos);
101
      range.moveStart('character', pos);
102
      range.select();
103
    }
104
}
105

    
106
// jquery show/hide events
107
var _oldshow = $.fn.show;
108
$.fn.show = function(speed, callback) {
109
    $(this).trigger('show');
110
    return _oldshow.apply(this,arguments);
111
}
112
var _oldhide = $.fn.hide;
113
$.fn.hide = function(speed, callback) {
114
    $(this).trigger('hide');
115
    return _oldhide.apply(this,arguments);
116
}
117

    
118
function ISODateString(d){
119
    //return a date in an ISO 8601 format using UTC.
120
    //do not include time zone info (Z) at the end
121
    //taken from the Mozilla Developer Center
122
    function pad(n){ return n<10 ? '0'+n : n }
123
    return  d.getUTCFullYear()+ '-' +
124
            pad(d.getUTCMonth()+1) + '-' +
125
            pad(d.getUTCDate()) + 'T' +
126
            pad(d.getUTCHours()) + ':' +
127
            pad(d.getUTCMinutes()) + ':' +
128
            pad(d.getUTCSeconds()) +'Z'
129
}
130

    
131
function parse_error(responseText, errorCode){
132
    var errors = [];
133
    try {
134
        responseObj = JSON.parse(responseText);
135
    }
136
    catch(err) {
137
        errors[0] = {'code': errorCode};
138
        return errors;
139
    }
140
    for (var err in responseObj){
141
        errors[errors.length] = responseObj[err];
142
    }
143
    return errors;
144
}
145

    
146
// indexOf prototype for IE
147
if (!Array.prototype.indexOf) {
148
  Array.prototype.indexOf = function(elt /*, from*/) {
149
    var len = this.length;
150
    var from = Number(arguments[1]) || 0;
151
    from = (from < 0)
152
         ? Math.ceil(from)
153
         : Math.floor(from);
154
    if (from < 0)
155
      from += len;
156

    
157
    for (; from < len; from++) {
158
      if (from in this &&
159
          this[from] === elt)
160
        return from;
161
    }
162
    return -1;
163
  };
164
}
165

    
166
// trim prototype for IE
167
if(typeof String.prototype.trim !== 'function') {
168
    String.prototype.trim = function() {
169
        return this.replace(/^\s+|\s+$/g, '');
170
    }
171
}
172

    
173
function update_confirmations() {
174
    // hide all confirm boxes to begin with
175
    $('#machines-pane div.confirm_single').hide();
176
    $('#machines-pane div.confirm_multiple').hide();
177
    var action_type = [];
178
    // standard view or single view
179
    if ($.cookie("view") == '0' || $.cookie("view") == '2') {
180
        for (var i=0; i<pending_actions.length; i++) {
181
            // show single confirms
182
            if (pending_actions[i][0] == reboot) {
183
                action_type = "reboot";
184
            } else if (pending_actions[i][0] == shutdown) {
185
                action_type = "shutdown";
186
            } else if (pending_actions[i][0] == start) {
187
                action_type = "start";
188
            } else if (pending_actions[i][0] == open_console) {
189
                action_type = "console";
190
            } else {
191
                action_type = "destroy";
192
            }
193
            $("#machines-pane #" + pending_actions[i][1] +
194
            " div.action-container." + action_type + " div.confirm_single").show();
195
        }
196
    }
197
    // if more than one pending action show multiple confirm box
198
    if (pending_actions.length>1 || $.cookie("view") == '1' && pending_actions.length == 1){
199
        $('#machines-pane div.confirm_multiple span.actionLen').text(pending_actions.length);
200
        $('#machines-pane div.confirm_multiple').show();
201
    }
202
}
203

    
204
function update_network_confirmations(){
205
    // hide all confirm boxes to begin with
206
    $('#networks-pane div.confirm_multiple').hide();
207

    
208
    for (var i=0;i<pending_actions.length;i++){
209
        // show single confirms depending on the action
210
        if (pending_actions[i][0] == delete_network) {
211
            $("#networks-pane div.network#net-"+pending_actions[i][1]).children('.confirm_single').show();
212
        } else if (pending_actions[i][0] == remove_server_from_network) {
213
            $("#networks-pane div.network #net-"+pending_actions[i][1]+"-server-"+pending_actions[i][2]).children('.confirm_single').show();
214
        } // else {}
215
    }
216

    
217
    // if more than one pending action show multiple confirm box
218
    if (pending_actions.length > 1){
219
        $('#networks-pane div.confirm_multiple span.actionLen').text(pending_actions.length);
220
        $('#networks-pane div.confirm_multiple').show();
221
    }
222
}
223

    
224
function list_view() {
225
    changes_since = 0; // to reload full list
226
    pending_actions = []; // clear pending actions
227
    update_confirmations();
228
    clearTimeout(deferred);    // clear old deferred calls
229
    try {
230
        update_request.abort(); // cancel pending ajax updates
231
        load_request.abort();
232
    }catch(err){}
233
    $.cookie("view", '1'); // set list cookie
234
    uri = $("a#list").attr("href");
235
    load_request = $.ajax({
236
        url: uri,
237
        type: "GET",
238
        timeout: TIMEOUT,
239
        dataType: "html",
240
        error: function(jqXHR, textStatus, errorThrown) {
241
            return false;
242
        },
243
        success: function(data, textStatus, jqXHR) {
244
            $("a#list")[0].className += ' activelink';
245
            $("a#standard")[0].className = '';
246
            $("a#single")[0].className = '';
247
            $("div#machinesview").html(data);
248
        }
249
    });
250
    return false;
251
}
252

    
253
function single_view() {
254
    changes_since = 0; // to reload full list
255
    pending_actions = []; // clear pending actions
256
    update_confirmations();
257
    clearTimeout(deferred);    // clear old deferred calls
258
    try {
259
        update_request.abort(); // cancel pending ajax updates
260
        load_request.abort();
261
    }catch(err){}
262
    $.cookie("view", '2'); // set list cookie
263
    uri = $("a#single").attr("href");
264
    load_request = $.ajax({
265
        url: uri,
266
        type: "GET",
267
        timeout: TIMEOUT,
268
        dataType: "html",
269
        error: function(jqXHR, textStatus, errorThrown) {
270
            return false;
271
        },
272
        success: function(data, textStatus, jqXHR) {
273
            $("a#single")[0].className += ' activelink';
274
            $("a#standard")[0].className = '';
275
            $("a#list")[0].className = '';
276
            $("div#machinesview").html(data);
277
        }
278
    });
279
    return false;
280
}
281

    
282
function standard_view() {
283
    changes_since = 0; // to reload full list
284
    pending_actions = []; // clear pending actions
285
    update_confirmations();
286
    clearTimeout(deferred);    // clear old deferred calls
287
    try {
288
        update_request.abort() // cancel pending ajax updates
289
        load_request.abort();
290
    }catch(err){}
291
    $.cookie("view", '0');
292
    uri = $("a#standard").attr("href");
293
    load_request = $.ajax({
294
        url: uri,
295
        type: "GET",
296
        timeout: TIMEOUT,
297
        dataType: "html",
298
        error: function(jqXHR, textStatus, errorThrown) {
299
            return false;
300
        },
301
        success: function(data, textStatus, jqXHR) {
302
            $("a#standard")[0].className += ' activelink';
303
            $("a#list")[0].className = '';
304
            $("a#single")[0].className = '';
305
            $("div#machinesview").html(data);
306
        }
307
    });
308
    return false;
309
}
310

    
311
function choose_view() {
312
    if ($.cookie("view")=='1') {
313
        list_view();
314
    } else if ($.cookie("view")=='2'){
315
        single_view();
316
    } else {
317
        standard_view();
318
    }
319
}
320

    
321
// return value from metadata key "OS", if it exists
322
function os_icon(metadata) {
323
    if (!metadata) {
324
        return 'okeanos';
325
    }
326
    if (metadata.values.OS == undefined || metadata.values.OS == '') {
327
        return 'okeanos';
328
    } else {
329
        if (os_icons.indexOf(metadata.values.OS) == -1) {
330
            return 'okeanos';
331
        } else {
332
            return metadata.values.OS;
333
        }
334
    }
335
}
336

    
337
function os_icon_from_value(metadata) {
338
    if (!metadata) {
339
        return 'okeanos';
340
    }
341
if (metadata == undefined || metadata == '') {
342
        return 'okeanos';
343
    } else {
344
        if (os_icons.indexOf(metadata) == -1) {
345
            return 'okeanos';
346
        } else {
347
            return metadata;
348
        }
349
    }
350
}
351

    
352
// get and show a list of running and terminated machines
353
function update_vms(interval) {
354
    try{ console.info('updating machines'); } catch(err){}
355
    var uri= API_URL + '/servers/detail';
356

    
357
    if (changes_since != 0)
358
        uri+='?changes-since='+changes_since
359

    
360
    update_request = $.ajax({
361
        cache: false,
362
        url: uri,
363
        type: "GET",
364
        timeout: TIMEOUT,
365
        dataType: "json",
366
        error: function(jqXHR, textStatus, errorThrown) {
367
            // don't forget to try again later
368
            if (interval) {
369
                clearTimeout(deferred);    // clear old deferred calls
370
                deferred = setTimeout(function() {update_vms(interval);},interval,interval);
371
            }
372
            // as for now, just show an error message
373
            try { console.info('update_vms errback:' + jqXHR.status ) } catch(err) {}
374
            try {
375
                ajax_error(jqXHR.status, undefined, 'Update VMs', jqXHR.responseText);
376
            } catch(err) {
377
                ajax_error(-5, "UI Error", 'Update VMs', err);
378
            }
379
            return false;
380
            },
381
        success: function(data, textStatus, jqXHR) {
382
            // create changes_since string if necessary
383
            if (jqXHR.getResponseHeader('Date') != null){
384
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
385
                changes_since = ISODateString(changes_since_date);
386
            }
387

    
388
            if (interval) {
389
                clearTimeout(deferred);    // clear old deferred calls
390
                deferred = setTimeout(function() {update_vms(interval);},interval,interval);
391
            }
392

    
393
            if (jqXHR.status == 200 || jqXHR.status == 203) {
394
                try {
395
                    //servers = data.servers.values;
396
                    update_servers_data(data.servers.values, data);
397
                    update_machines_view(data);
398
                } catch(err) { ajax_error(-5, "UI Error", 'Update VMs', err);}
399
            } else if (jqXHR.status != 304){
400
                try { console.info('update_vms callback:' + jqXHR.status ) } catch(err) {}
401
                /*
402
                FIXME:  Here it should return the error, however Opera does not support 304.
403
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
404
                        304, which should be corrected (Bug #317).
405
                */
406
                // ajax_error(jqXHR.status, "Ajax error", 'Update VMs', jqXHR.responseText);
407
            }
408
            return false;
409
        }
410
    });
411
    return false;
412
}
413

    
414
function update_servers_data(servers_update, data) {
415
    $(window).trigger("vm:update", servers_update, data);
416

    
417
    // first call
418
    if (!window.servers || window.servers.length == 0) {
419
        window.servers = servers_update;
420
        return;
421
    }
422
    
423
    // server exists helper
424
    server_exists = function(server) {
425
        var id = server.id;
426
        var found = false;
427
        var index = 0;
428
        $.each(servers, function(i, s) {
429
            if (s.id == id) { found = true, index = i };
430
        });
431
        if (found)
432
            return [found, index];
433

    
434
        return false;
435
    }
436

    
437
    // merge object properties
438
    merge = function() {
439
        var initial = arguments[0];
440
        var status_changed = undefined;
441
        $.each(arguments, function(index, el) {
442
            $.each(el, function(key,v) {
443
                // new attribute added
444
                var previous_value = initial[key];
445
                var v = v;
446
                if (initial[key] == undefined) {
447
                    $(window).trigger("vm:attr:add", initial, key, v);
448
                } else {
449
                    // value changed
450
                    if (initial[key] != v) {
451
                        if (key == "status") {
452
                            // dont change if in destroy state
453
                            if (initial.status == "DESTROY") {
454
                                v = "DESTROY";
455
                            }
456
                            status_changed = {'old': previous_value, 'new': v}; 
457
                        }
458
                        $(window).trigger("vm:attr:change", {'initial': initial, 'attr': key, 'newvalue': v});
459
                    }
460
                }
461
                initial[key] = v;
462
            });
463
        });
464
        if (status_changed !== undefined) {
465
            $(window).trigger('vm:status:change', {'vm': initial, 'old': status_changed['old'], 'new': status_changed['new']});
466
        }
467
        return initial;
468
    }
469
    
470
    // server removed
471
    var remove = [];
472
    $.each(servers_update, function(index, server) {
473
        if (server.status == "DELETED") {
474
            remove.push(server.id);
475
        }
476
    });
477
    
478
    // check server, if exists merge it with new values else add it
479
    $.each(servers_update, function(index, server) {
480
        var exists = server_exists(server);
481
        if (exists !== false) {
482
            try {
483
                servers[exists[1]] = merge(servers[exists[1]], server);
484
            } catch (err) {
485
            }
486
        } else {
487
            servers.push(server);
488
            $(window).trigger("vm:add", server);
489
        }
490
        if (remove.indexOf(server.id) > -1) {
491
            var remove_exists = server_exists(server);
492
            servers.splice(remove_exists[1], 1);
493
            $(window).trigger("vm:remove", server);
494
        }
495
    });
496
}
497

    
498
// get a list of running and terminated machines, used in network view
499
function update_networks(interval) {
500
    try{ console.info('updating networks'); } catch(err){}
501
    var uri= API_URL + '/servers/detail';
502

    
503
    if (changes_since != 0)
504
        uri+='?changes-since='+changes_since
505

    
506
    update_request = $.ajax({
507
        cache: false,
508
        url: uri,
509
        type: "GET",
510
        timeout: TIMEOUT,
511
        dataType: "json",
512
        error: function(jqXHR, textStatus, errorThrown) {
513
            // don't forget to try again later
514
            if (interval) {
515
                clearTimeout(deferred);    // clear old deferred calls
516
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
517
            }
518
            // as for now, just show an error message
519
            try { console.info('update_networks errback:' + jqXHR.status ) } catch(err) {}
520
            try {
521
                ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
522
            } catch(err) {
523
                ajax_error(-5, "UI Error", 'Update networks', err);
524
            }
525
            return false;
526
            },
527
        success: function(data, textStatus, jqXHR) {
528
            // create changes_since string if necessary
529
            if (jqXHR.getResponseHeader('Date') != null){
530
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
531
                changes_since = ISODateString(changes_since_date);
532
            }
533

    
534
            if (interval) {
535
                clearTimeout(deferred);    // clear old deferred calls
536
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
537
            }
538

    
539
            if (jqXHR.status == 200 || jqXHR.status == 203) {
540
                try {
541
                    //servers = data.servers.values;
542
                    update_servers_data(data.servers.values, data);
543
                    jQuery.parseJSON(data);
544
                    update_network_names(data);
545
                } catch(err) { ajax_error(-5, "UI Error", 'Update networks', err);}
546
            } else if (jqXHR.status == 304) {
547
                update_network_names();
548
            }
549
            else {
550
                try { console.info('update_networks callback:' + jqXHR.status ) } catch(err) {}
551
                /*
552
                FIXME:  Here it should return the error, however Opera does not support 304.
553
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
554
                        304, which should be corrected (Bug #317).
555
                */
556
                //ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
557
                update_network_names();
558
            }
559
            return false;
560
        }
561
    });
562
    return false;
563
}
564

    
565
// get and show a list of public and private networks
566
function update_network_names(servers_data) {
567
    try{ console.info('updating network names'); } catch(err){}
568
    var uri= API_URL + '/networks/detail';
569

    
570
    if (networks_changes_since != 0)
571
        //FIXME: Comment out the following, until metadata do not 304 when changed
572
        uri+='?changes-since=' + networks_changes_since
573

    
574
    update_request = $.ajax({
575
        cache: false,
576
        url: uri,
577
        type: "GET",
578
        timeout: TIMEOUT,
579
        dataType: "json",
580
        error: function(jqXHR, textStatus, errorThrown) {
581
            // as for now, just show an error message
582
            try {
583
                console.info('update_network names errback:' + jqXHR.status )
584
            } catch(err) {}
585
            try {
586
                ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
587
            } catch(err) {
588
                ajax_error(-5, "UI Error", 'Update network names', err);
589
            }
590
            return false;
591
            },
592
        success: function(data, textStatus, jqXHR) {
593
            // create changes_since string if necessary
594
            if (jqXHR.getResponseHeader('Date') != null){
595
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
596
                networks_changes_since = ISODateString(changes_since_date);
597
            }
598

    
599
            if (jqXHR.status == 200 || jqXHR.status == 203) {
600
                try {
601
                    networks = data.networks.values;
602
                    jQuery.parseJSON(data);
603
                    update_networks_view(servers_data, data);
604
                } catch(err) {
605
                    ajax_error(-5, "UI Error", 'Update network names', err);
606
                }
607
            } else if (jqXHR.status == 304) {
608
                update_networks_view(servers_data);
609
            } else if (jqXHR.status != 304){
610
                try { console.info('update_network_names callback:' + jqXHR.status ) } catch(err) {}
611
                /*
612
                FIXME:  Here it should return the error, however Opera does not support 304.
613
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
614
                        304, which should be corrected (Bug #317).
615
                */
616
                //ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
617
                update_networks_view(servers_data);
618
            }
619
            return false;
620
        }
621
    });
622
    return false;
623
}
624

    
625
// get and show a list of available standard and custom images
626
function update_images() {
627
    $.ajax({
628
        url: API_URL + '/images/detail',
629
        type: "GET",
630
        //async: false,
631
        dataType: "json",
632
        timeout: TIMEOUT,
633
        error: function(jqXHR, textStatus, errorThrown) {
634
                    try {
635
                        ajax_error(jqXHR.status, undefined, 'Update Images', jqXHR.responseText);
636
                    } catch(err) {
637
                        ajax_error(-5, "UI error", 'Update Images', err);
638
                    }
639
                },
640
        success: function(data, textStatus, jqXHR) {
641
            try {
642
                images = data.images.values;
643
                jQuery.parseJSON(data);
644
                update_wizard_images();
645
            } catch(err){
646
                ajax_error("NO_IMAGES");
647
            }
648
        }
649
    });
650
    return false;
651
}
652

    
653
function update_wizard_images() {
654
    if ($("ul#standard-images li").toArray().length + $("ul#custom-images li").toArray().length == 0) {
655
        $.each(images, function(i,image){
656
            var img = $('#image-template').clone().attr("id","img-"+image.id).fadeIn("slow");
657
            img.find("label").attr('for',"img-radio-" + image.id);
658
            img.find(".image-title").text(image.name);
659
            if (image.metadata) {
660
                if (image.metadata.values.description != undefined) {
661
                    img.find(".description").text(image.metadata.values.description);
662
                }
663
                if (image.metadata.values.size != undefined) {
664
                    img.find("#size").text(image.metadata.values.size);
665
                }
666
            }
667
            img.find("input.radio").attr('id',"img-radio-" + image.id);
668
            if (i==0) img.find("input.radio").attr("checked","checked");
669
            var image_logo = os_icon(image.metadata);
670
            img.find("img.image-logo").attr('src','static/icons/os/'+image_logo+'.png');
671
            if (image.metadata) {
672
                if (image.metadata.values.serverId != undefined) {
673
                    img.appendTo("ul#custom-images");
674
                } else {
675
                    img.appendTo("ul#standard-images");
676
                }
677
            } else {
678
                img.appendTo("ul#standard-images");
679
            }
680
        });
681
    }
682
}
683

    
684
function update_wizard_flavors(){
685
    // sliders for selecting VM flavor
686
    $("#cpu:range").rangeinput({min:0,
687
                               value:0,
688
                               step:1,
689
                               progress: true,
690
                               max:cpus.length-1});
691

    
692
    $("#storage:range").rangeinput({min:0,
693
                               value:0,
694
                               step:1,
695
                               progress: true,
696
                               max:disks.length-1});
697

    
698
    $("#ram:range").rangeinput({min:0,
699
                               value:0,
700
                               step:1,
701
                               progress: true,
702
                               max:ram.length-1});
703
    $("#small").click();
704

    
705
    // update the indicators when sliding
706
    $("#cpu:range").data().rangeinput.onSlide(function(event,value){
707
        $("#cpu-indicator")[0].value = cpus[Number(value)];
708
        $("#cpu-indicator").addClass('selectedrange');
709
    });
710
    $("#cpu:range").data().rangeinput.change(function(event,value){
711
        $("#cpu-indicator")[0].value = cpus[Number(value)];
712
        $("#custom").click();
713
        $("#cpu-indicator").removeClass('selectedrange');
714
    });
715
    $("#ram:range").data().rangeinput.onSlide(function(event,value){
716
        $("#ram-indicator")[0].value = ram[Number(value)];
717
        $("#ram-indicator").addClass('selectedrange');
718
    });
719
    $("#ram:range").data().rangeinput.change(function(event,value){
720
        $("#ram-indicator")[0].value = ram[Number(value)];
721
        $("#custom").click();
722
        $("#ram-indicator").removeClass('selectedrange');
723
    });
724
    $("#storage:range").data().rangeinput.onSlide(function(event,value){
725
        $("#storage-indicator")[0].value = disks[Number(value)];
726
        $("#storage-indicator").addClass('selectedrange');
727
    });
728
    $("#storage:range").data().rangeinput.change(function(event,value){
729
        $("#storage-indicator")[0].value = disks[Number(value)];
730
        $("#custom").click();
731
        $("#storage-indicator").removeClass('selectedrange');
732
    });
733
}
734

    
735
Array.prototype.unique = function () {
736
    var r = new Array();
737
    o:for(var i = 0, n = this.length; i < n; i++)
738
    {
739
        for(var x = 0, y = r.length; x < y; x++)
740
        {
741
            if(r[x]==this[i])
742
            {
743
                continue o;
744
            }
745
        }
746
        r[r.length] = this[i];
747
    }
748
    return r;
749
}
750

    
751
// get and configure flavor selection
752
function update_flavors() {
753
    $.ajax({
754
        url: API_URL + '/flavors/detail',
755
        type: "GET",
756
        //async: false,
757
        dataType: "json",
758
        timeout: TIMEOUT,
759
        error: function(jqXHR, textStatus, errorThrown) {
760
            try {
761
                ajax_error(jqXHR.status, undefined, 'Update Flavors', jqXHR.responseText);
762
            } catch (err) {
763
                ajax_error(-5, "UI Error", "Update Flavors", err);
764
            }
765
            // start updating vm list
766
            update_vms(UPDATE_INTERVAL);
767
        },
768
        success: function(data, textStatus, jqXHR) {
769

    
770
            try {
771
                flavors = data.flavors.values;
772
                jQuery.parseJSON(data);
773
                $.each(flavors, function(i, flavor) {
774
                    cpus[i] = flavor['cpu'];
775
                    disks[i] = flavor['disk'];
776
                    ram[i] = flavor['ram'];
777
                });
778
                cpus = cpus.unique();
779
                disks = disks.unique();
780
                ram = ram.unique();
781
                update_wizard_flavors();
782
            } catch(err){
783
                ajax_error("NO_FLAVORS");
784
            }
785
            // start updating vm list
786
            update_vms(UPDATE_INTERVAL);
787
        }
788
    });
789
    return false;
790
}
791

    
792
// return flavorRef from cpu, disk, ram values
793
function identify_flavor(cpu, disk, ram){
794
    for (i=0;i<flavors.length;i++){
795
        if (flavors[i]['cpu'] == cpu && flavors[i]['disk']==disk && flavors[i]['ram']==ram) {
796
            return flavors[i]['id']
797
        }
798
    }
799
    return 0;
800
}
801

    
802
// return image entry from imageRef
803
function get_image(imageRef) {
804
    for (i=0;i<images.length;i++){
805
        if (images[i]['id'] == imageRef) {
806
            return images[i];
807
        }
808
    }
809
    return 0;
810
}
811

    
812
// return machine entry from serverID
813
function get_machine(serverID) {
814
    for (i=0;i<servers.length;i++){
815
        if (servers[i]['id'] == serverID) {
816
            return servers[i];
817
        }
818
    }
819
    return 0;
820
}
821

    
822
// update the actions in icon view, per server
823
function update_iconview_actions(serverID, server_status) {
824
    if ($.cookie("view")=='2') {
825
        // remove .disable from all actions to begin with
826
        $('#machinesview-single #' + serverID + ' div.single-action').show();
827
        // decide which actions should be disabled
828
        for (current_action in actions) {
829
            if (actions[current_action].indexOf(server_status) == -1 ) {
830
                $('#machinesview-single #' + serverID + ' div.action-' + current_action).hide();
831
            }
832
        }
833
    } else {
834
        // remove .disable from all actions to begin with
835
        $('#machinesview-icon.standard #' + serverID + ' div.actions').find('a').removeClass('disabled');
836
        // decide which actions should be disabled
837
        for (current_action in actions) {
838
            if (actions[current_action].indexOf(server_status) == -1 ) {
839
                $('#machinesview-icon.standard #' + serverID + ' a.action-' + current_action).addClass('disabled');
840
            }
841
        }
842
    }
843
}
844

    
845
// update the actions in list view
846
function update_listview_actions() {
847
    var states = [];
848
    var on = [];
849
    var checked = $("table.list-machines tbody input[type='checkbox']:checked");
850
    // disable all actions to begin with
851
    $('#machinesview .list div.actions').children().removeClass('enabled');
852

    
853
    // are there multiple machines selected?
854
    if (checked.length>1)
855
        states[0] = 'multiple';
856

    
857
    // check the states of selected machines
858
    checked.each(function(i,checkbox) {
859
        states[states.length] = checkbox.className;
860
        var ip = $("#" + checkbox.id.replace('input-','') + ".ip span.public").text();
861
        if (ip.replace('undefined','').length)
862
            states[states.length] = 'network';
863
    });
864

    
865
    // decide which actions should be enabled
866
    for (a in actions) {
867
        var enabled = false;
868
        for (var s =0; s<states.length; s++) {
869
            if (actions[a].indexOf(states[s]) != -1 ) {
870
                enabled = true;
871
            } else {
872
                enabled = false;
873
                break;
874
            }
875
        }
876
        if (enabled)
877
            on[on.length]=a;
878
    }
879
    // enable those actions
880
    for (action in on) {
881
        $("#action-" + on[action]).addClass('enabled');
882
    }
883
}
884

    
885
//create server action
886
function create_vm(machineName, imageRef, flavorRef){
887
    var image_logo = os_icon(get_image(imageRef).metadata);
888
    var uri = API_URL + '/servers';
889
    var payload = {
890
        "server": {
891
            "name": machineName,
892
            "imageRef": imageRef,
893
            "flavorRef" : flavorRef,
894
            "metadata" : {
895
                "OS" : image_logo
896
            }
897
        }
898
    };
899

    
900
    $.ajax({
901
    url: uri,
902
    type: "POST",
903
    contentType: "application/json",
904
    dataType: "json",
905
    data: JSON.stringify(payload),
906
    timeout: TIMEOUT,
907
    error: function(jqXHR, textStatus, errorThrown) {
908
                // close wizard and show error box
909
                $('#machines-pane a#create').data('overlay').close();
910
                    try {
911
                        ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
912
                    } catch(err) {
913
                        ajax_error(-5, "UI Error", 'Create VM', err);
914
                    }
915
           },
916
    success: function(data, textStatus, jqXHR) {
917
                if ( jqXHR.status == '202') {
918
                    ajax_success("CREATE_VM_SUCCESS", data.server.adminPass);
919
                } else {
920
                    // close wizard and show error box
921
                    $('#machines-pane a#create').data('overlay').close();
922
                    ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
923
                }
924
            }
925
    });
926
}
927

    
928
// reboot action
929
function reboot(serverIDs){
930
    if (!serverIDs.length){
931
        //ajax_success('DEFAULT');
932
        return false;
933
    }
934
    // ajax post reboot call
935
    var payload = {
936
        "reboot": {"type" : "HARD"}
937
    };
938

    
939
    var serverID = serverIDs.pop();
940

    
941
    $.ajax({
942
        url: API_URL + '/servers/' + serverID + '/action',
943
        type: "POST",
944
        contentType: "application/json",
945
        dataType: "json",
946
        data: JSON.stringify(payload),
947
        timeout: TIMEOUT,
948
        error: function(jqXHR, textStatus, errorThrown) {
949
                    // in machine views
950
                    if ( $.cookie("pane") == 0) {
951
                        try {
952
                            display_failure(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
953
                        } catch (err) {
954
                            display_failure(0, serverID, 'Reboot', jqXHR.responseText);
955
                        }
956
                    }
957
                    // in network view
958
                    else {
959
                        try {
960
                            display_reboot_failure(jqXHR.status, serverID, jqXHR.responseText);
961
                        } catch (err) {
962
                            display_reboot_failure(0, serverID, jqXHR.responseText);
963
                        }
964
                    }
965
                },
966
        success: function(data, textStatus, jqXHR) {
967
                    if ( jqXHR.status == '202') {
968
                        try {
969
                            console.info('rebooted ' + serverID);
970
                        } catch(err) {}
971
                        // indicate that the action succeeded
972
                        // in machine views
973
                        if ( $.cookie("pane") == 0) {
974
                            display_success(serverID);
975
                        }
976
                        // in network view
977
                        else {
978
                            display_reboot_success(serverID);
979
                        }
980
                        // continue with the rest of the servers
981
                        reboot(serverIDs);
982
                    } else {
983
                        ajax_error(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
984
                    }
985
                }
986
    });
987
    return false;
988
}
989

    
990
// shutdown action
991
function shutdown(serverIDs) {
992
    if (!serverIDs.length){
993
        //ajax_success('DEFAULT');
994
        return false;
995
    }
996
    // ajax post shutdown call
997
    var payload = {
998
        "shutdown": {}
999
    };
1000

    
1001
    var serverID = serverIDs.pop();
1002

    
1003
    $.ajax({
1004
        url: API_URL + '/servers/' + serverID + '/action',
1005
        type: "POST",
1006
        contentType: "application/json",
1007
        dataType: "json",
1008
        data: JSON.stringify(payload),
1009
        timeout: TIMEOUT,
1010
        error: function(jqXHR, textStatus, errorThrown) {
1011
                    try {
1012
                        display_failure(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1013
                    } catch(err) {
1014
                        display_failure(0, serverID, 'Shutdown', jqXHR.responseText);
1015
                    }
1016
                },
1017
        success: function(data, textStatus, jqXHR) {
1018
                    if ( jqXHR.status == '202') {
1019
                        try {
1020
                            console.info('suspended ' + serverID);
1021
                        } catch(err) {}
1022
                        // indicate that the action succeeded
1023
                        display_success(serverID);
1024
                        // continue with the rest of the servers
1025
                        shutdown(serverIDs);
1026
                    } else {
1027
                        ajax_error(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1028
                    }
1029
                }
1030
    });
1031
    return false;
1032
}
1033

    
1034
// destroy action
1035
function destroy(serverIDs) {
1036
    if (!serverIDs.length){
1037
        //ajax_success('DEFAULT');
1038
        return false;
1039
    }
1040
    // ajax post destroy call can have an empty request body
1041
    var payload = {};
1042

    
1043
    var serverID = serverIDs.pop();
1044

    
1045
    $.ajax({
1046
        url: API_URL + '/servers/' + serverID,
1047
        type: "DELETE",
1048
        contentType: "application/json",
1049
        dataType: "json",
1050
        data: JSON.stringify(payload),
1051
        timeout: TIMEOUT,
1052
        error: function(jqXHR, textStatus, errorThrown) {
1053
                    try {
1054
                        display_failure(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1055
                    } catch(err) {
1056
                        display_failure(0, serverID, 'Destroy', jqXHR.responseText);
1057
                    }
1058
                },
1059
        success: function(data, textStatus, jqXHR) {
1060
                    if ( jqXHR.status == '204') {
1061
                        try {
1062
                            console.info('destroyed ' + serverID);
1063
                        } catch (err) {}
1064

    
1065
                        // update status on local storage object
1066
                        vm = get_machine(serverID);
1067
                        vm.status = "DESTROY";
1068

    
1069
                        // indicate that the action succeeded
1070
                        display_success(serverID);
1071
                        // continue with the rest of the servers
1072
                        destroy(serverIDs);
1073
                    } else {
1074
                        ajax_error(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1075
                    }
1076
                }
1077
    });
1078
    return false;
1079
}
1080

    
1081
// start action
1082
function start(serverIDs){
1083
    if (!serverIDs.length){
1084
        //ajax_success('DEFAULT');
1085
        return false;
1086
    }
1087
    // ajax post start call
1088
    var payload = {
1089
        "start": {}
1090
    };
1091

    
1092
    var serverID = serverIDs.pop();
1093

    
1094
    $.ajax({
1095
        url: API_URL + '/servers/' + serverID + '/action',
1096
        type: "POST",
1097
        contentType: "application/json",
1098
        dataType: "json",
1099
        data: JSON.stringify(payload),
1100
        timeout: TIMEOUT,
1101
        error: function(jqXHR, textStatus, errorThrown) {
1102
                    try {
1103
                        display_failure(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1104
                    } catch(err) {
1105
                        display_failure(0, serverID, 'Start', jqXHR.responseText);
1106
                    }
1107
                },
1108
        success: function(data, textStatus, jqXHR) {
1109
                    if ( jqXHR.status == '202') {
1110
                        try {
1111
                            console.info('started ' + serverID);
1112
                        } catch(err) {}
1113
                        // indicate that the action succeeded
1114
                        display_success(serverID);
1115
                        // continue with the rest of the servers
1116
                        start(serverIDs);
1117
                    } else {
1118
                        ajax_error(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1119
                    }
1120
                }
1121
    });
1122
    return false;
1123
}
1124

    
1125
// Show VNC console
1126
function vnc_attachment(host, port, password) {
1127
    // FIXME: Must be made into parameters, in settings.py
1128
    //vnc = open("", "displayWindow",
1129
    //    "status=yes,toolbar=yes,menubar=yes");
1130
    vd = document.open("application/x-vnc");
1131

    
1132
    vd.writeln("[connection]");
1133
    vd.writeln("host=" + host);
1134
    vd.writeln("port=" + port);
1135
    vd.writeln("password=" + password);
1136

    
1137
    vd.close();
1138
}
1139

    
1140
// Show VNC console
1141
function show_vnc_console(serverID, serverName, serverIP, host, port, password) {
1142
    var params_url = '?machine=' + serverName + '&host_ip=' + serverIP.v4 + '&host_ip_v6=' + serverIP.v6 + '&host=' + host + '&port=' + port + '&password=' + password;
1143
    var params_window = 'scrollbars=no,' +
1144
                        'menubar=no,' +
1145
                        'toolbar=no,' +
1146
                        'status=no,' +
1147
                        'top=0,' +
1148
                        'left=0,' +
1149
                        'height=' + screen.height + ',' +
1150
                        'width=' + screen.width + ',' +
1151
                        'fullscreen=yes';
1152
    
1153
    var url = 'machines/console' + params_url;
1154
    window.open(url, 'formresult' + serverID, params_window);
1155

    
1156
    // Restore os icon in list view
1157
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1158
    osIcon.attr('src',osIcon.attr('os'));
1159
    return false;
1160
}
1161

    
1162
// console action
1163
function open_console(serverIDs){
1164
    if (!serverIDs.length){
1165
        //ajax_success('DEFAULT');
1166
        return false;
1167
    }
1168
    // ajax post start call
1169
    var payload = {
1170
        "console": {"type": "vnc"}
1171
    };
1172

    
1173
    var serverID = serverIDs.pop();
1174

    
1175
    var machine = get_machine(serverID);
1176
    var serverName = machine.name;
1177
    try {
1178
        var serverIP = {};
1179
        serverIP.v4 = machine.addresses.values[0].values[0].addr;
1180
        serverIP.v6 = machine.addresses.values[0].values[1].addr;
1181
    } catch(err) { var serverIP = 'undefined'; }
1182

    
1183
    $.ajax({
1184
        url: API_URL + '/servers/' + serverID + '/action',
1185
        type: "POST",
1186
        contentType: "application/json",
1187
        dataType: "json",
1188
        data: JSON.stringify(payload),
1189
        timeout: TIMEOUT,
1190
        error: function(jqXHR, textStatus, errorThrown) {
1191
                    try {
1192
                        display_failure(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1193
                    } catch(err) {
1194
                        display_failure(0, serverID, 'Console', jqXHR.responseText);
1195
                    }
1196
                },
1197
        success: function(data, textStatus, jqXHR) {
1198
                    if ( jqXHR.status == '200') {
1199
                        try {
1200
                            console.info('got_console ' + serverID);
1201
                        } catch(err) {}
1202
                        // indicate that the action succeeded
1203
                        // show_vnc_console(serverID, serverName, serverIP,
1204
                        // data.console.host,data.console.port,data.console.password);
1205
                        show_vnc_console(serverID, serverName, serverIP,
1206
                                         data.console.host,data.console.port,data.console.password);
1207
                        display_success(serverID);
1208
                        // hide spinner
1209
                        $('#' + serverID + ' .spinner').hide();
1210
                        // continue with the rest of the servers
1211
                        open_console(serverIDs);
1212
                    } else {
1213
                        ajax_error(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1214
                    }
1215
                }
1216
    });
1217
    return false;
1218
}
1219

    
1220
function vm_has_address(vmId) {
1221
    var vm = get_machine(vmId);
1222

    
1223
    if (!vm) return false;
1224

    
1225
    try {
1226
        var ip = vm.addresses.values[0].values[0].addr;
1227
    } catch (err) {
1228
        return false;
1229
    }
1230
    return ip;
1231
}
1232

    
1233
// connect to machine action
1234
function machine_connect(serverIDs){
1235
    if (!serverIDs.length){
1236
        //ajax_success('DEFAULT');
1237
        return false;
1238
    }
1239

    
1240
    var username_meta_key = 'User';
1241

    
1242
    var serverID = serverIDs.pop();
1243
    var machine = get_machine(serverID);
1244
    var serverName = machine.name;
1245
    
1246
    try {
1247
        var serverIP = machine.addresses.values[0].values[0].addr;
1248
    } catch(err) { var serverIP = 'undefined'; }
1249

    
1250
    try {
1251
        var os = os_icon(machine.metadata);
1252
    } catch(err) { var os = 'undefined'; }
1253

    
1254
    var username = "";
1255
    try {
1256
        username = machine.metadata.values[username_meta_key];
1257
    } catch(err){ username = undefined}
1258

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

    
1261
    if (username) {
1262
        params_url += "&username=" + username;
1263
    }
1264
    
1265
    if ($.client.os == "Windows" && os == "windows") {
1266
        window.open('machines/connect' + params_url + "&rdp=1");
1267
        return;
1268
    }
1269
    
1270
    var title = 'Connect to: ' + '<span class="machine-title"><img src="static/icons/machines/small/'+os+'-on.png" /> '+serverName+'</span>';
1271

    
1272
    try {
1273
        msg_box({title:title, content:'loading...',extra:'',
1274
        'ajax':'machines/connect' + params_url,
1275
        parse_data:function(data){
1276
            var box_content = "<a href='"+data.link.url+"'>"+data.link.title+"</a>";
1277
            if (!data.link.url) {
1278
                box_content = "<span class='cmd'>"+data.link.title+"</span>";
1279
            }
1280
            data.title = false;
1281
            data.content = data.info;
1282
            data.extra = box_content;
1283
            return data;
1284
        }
1285
        });
1286
    } catch (error) {
1287
        window.open('machines/connect' + params_url);
1288
    }
1289

    
1290

    
1291
    // Restore os icon in list view
1292
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1293
    osIcon.attr('src',osIcon.attr('os'));
1294

    
1295
    return false;
1296
}
1297

    
1298

    
1299
// rename server
1300
function rename(serverID, serverName){
1301
    if (!serverID.length){
1302
        //ajax_success('DEFAULT');
1303
        return false;
1304
    }
1305
    // ajax post rename call
1306
    var payload = {
1307
        "server": {"name": serverName}
1308
    };
1309

    
1310
    $.ajax({
1311
        url: API_URL + '/servers/' + serverID,
1312
        type: "PUT",
1313
        contentType: "application/json",
1314
        dataType: "json",
1315
        data: JSON.stringify(payload),
1316
        timeout: TIMEOUT,
1317
        error: function(jqXHR, textStatus, errorThrown) {
1318
                    try {
1319
                        display_failure(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1320
                    } catch(err) {
1321
                        display_failure(0, serverID, 'Rename', jqXHR.responseText);
1322
                    }
1323
                },
1324
        success: function(data, textStatus, jqXHR) {
1325
                    if ( jqXHR.status == '204' || jqXHR.status == '1223') {
1326
                        try {
1327
                            console.info('renamed ' + serverID);
1328
                        } catch(err) {}
1329
                        // indicate that the action succeeded
1330
                        display_success(serverID);
1331
                    } else {
1332
                        ajax_error(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1333
                    }
1334
                }
1335
    });
1336
    return false;
1337
}
1338

    
1339
// get server metadata
1340
function get_metadata(serverID, keys_only) {
1341
    $.ajax({
1342
        url: API_URL + '/servers/' + serverID + '/meta',
1343
        cache: false,
1344
        type: "GET",
1345
        //async: false,
1346
        dataType: "json",
1347
        timeout: TIMEOUT,
1348
        error: function(jqXHR, textStatus, errorThrown) {
1349
            try {
1350
                // close wizard and show error box
1351
                $("a#metadata-scrollable").data('overlay').close();
1352
                ajax_error(jqXHR.status, undefined, 'Get metadata', jqXHR.responseText);
1353
            } catch (err) {
1354
                ajax_error(-5, "UI Error", "Get metadata", err);
1355
            }
1356
        },
1357
        success: function(data, textStatus, jqXHR) {
1358
            // to list the new results in the edit dialog
1359
            if (keys_only) {
1360
                list_metadata_keys(serverID, data.metadata.values);
1361
            } else {
1362
                list_metadata(data);
1363
                list_metadata_keys(serverID, data.metadata.values);
1364
            }
1365
            //hide spinner
1366
            $('#metadata-wizard .large-spinner').hide();
1367
        }
1368
    });
1369
    return false;
1370
}
1371

    
1372
// delete metadata key-value pair
1373
function delete_metadata(serverID, meta_key) {
1374
    $.ajax({
1375
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1376
        type: "DELETE",
1377
        //async: false,
1378
        dataType: "json",
1379
        timeout: TIMEOUT,
1380
        error: function(jqXHR, textStatus, errorThrown) {
1381
            try {
1382
                // close wizard and show error box
1383
                $("a#metadata-scrollable").data('overlay').close();
1384
                ajax_error(jqXHR.status, undefined, 'Delete metadata', jqXHR.responseText);
1385
            } catch (err) {
1386
                ajax_error(-5, "UI Error", "Delete metadata", err);
1387
            }
1388
        },
1389
        success: function(data, textStatus, jqXHR) {
1390
                    // success: Do nothing, the UI is already updated
1391
        }
1392
    });
1393
    return false;
1394
}
1395

    
1396
// add metadata key-value pair
1397
function update_metadata(serverID, meta_key, meta_value) {
1398
    var payload = {
1399
        "meta": {
1400
        }
1401
    };
1402
    payload["meta"][meta_key] = meta_value;
1403

    
1404
    $.ajax({
1405
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1406
        type: "PUT",
1407
        contentType: "application/json",
1408
        dataType: "json",
1409
        data: JSON.stringify(payload),
1410
        timeout: TIMEOUT,
1411
        error: function(jqXHR, textStatus, errorThrown) {
1412
            try {
1413
                // close wizard and show error box
1414
                $("a#metadata-scrollable").data('overlay').close();
1415
                ajax_error(jqXHR.status, undefined, 'add metadata', jqXHR.responseText);
1416
            } catch (err) {
1417
                ajax_error(-5, "UI Error", "add metadata", err);
1418
            }
1419
        },
1420
        success: function(data, textStatus, jqXHR) {
1421
            // success: Update icons if meta key is OS
1422
            if (meta_key == "OS") {
1423
                $("#metadata-wizard .machine-icon").attr("src","static/icons/machines/small/" + os_icon_from_value(meta_value) + '-' + $("#metadata-wizard div#on-off").text() + '.png');
1424
                var machine_icon = $("#machinesview-icon").find("div#" + serverID);
1425
                var machine_single = $("#machinesview-single").find("div#" + serverID);
1426

    
1427
                var os = os_icon_from_value(meta_value);
1428
                var state = $("#metadata-wizard div#on-off").text()
1429
                var state_single = $(".state", machine_single).hasClass("terminated-state") ? "off" : "on";
1430
                set_machine_os_image(machine_icon, "icon", state, os);
1431
                set_machine_os_image(machine_single, "single", state_single, os);
1432
            }
1433
        }
1434
    });
1435
    return false;
1436
}
1437

    
1438
// get stats
1439
function get_server_stats(serverID) {
1440
    $.ajax({
1441
        url: API_URL + '/servers/' + serverID + '/stats',
1442
        cache: false,
1443
        type: "GET",
1444
        //async: false,
1445
        dataType: "json",
1446
        timeout: TIMEOUT,
1447
        error: function(jqXHR, textStatus, errorThrown) {
1448
                // report error as text inline
1449
                $('#' + serverID + ' img.busy').hide();
1450
                $('#' + serverID + ' div.stat-error').show();
1451
        },
1452
        success: function(data, textStatus, jqXHR) {
1453
            // in icon view
1454
            if ( $.cookie('view') == 0 ) {
1455
                $('#' + serverID + ' img.busy').removeClass('busy');
1456
                $('#' + serverID + ' img.cpu').attr("src", data.stats.cpuBar);
1457
                $('#' + serverID + ' img.net').attr("src", data.stats.netBar);
1458
            }
1459
            // in single view
1460
            else if ( $.cookie('view') == 2 ) {
1461
                $('#' + serverID + ' div.cpu-graph img.stats').attr("src", data.stats.cpuTimeSeries);
1462
                $('#' + serverID + ' div.network-graph img.stats').attr("src", data.stats.netTimeSeries);
1463
            }
1464
        }
1465
    });
1466
    return false;
1467
}
1468

    
1469
// create network
1470
function create_network(networkName){
1471
    // ajax post start call
1472
    var payload = {
1473
        "network": { "name": networkName }
1474
    };
1475

    
1476
    $.ajax({
1477
        url: API_URL + '/networks',
1478
        type: "POST",
1479
        contentType: "application/json",
1480
        dataType: "json",
1481
        data: JSON.stringify(payload),
1482
        timeout: TIMEOUT,
1483
        error: function(jqXHR, textStatus, errorThrown) {
1484
            try {
1485
                // close wizard and show error box
1486
                $("a#networkscreate").overlay().close();
1487
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1488
            } catch (err) {
1489
                ajax_error(-5, "UI Error", "Create network", err);
1490
            }
1491
        },
1492
        success: function(data, textStatus, jqXHR) {
1493
            if ( jqXHR.status == '202') {
1494
                try {
1495
                    console.info('created network ' + networkName);
1496
                } catch(err) {}
1497
                /*
1498
                On success of this call nothing happens.
1499
                When the UI gets the first update containing the created server,
1500
                the creation wizard is closed and the new network is inserted
1501
                to the DOM. This is done in update_networks_view()
1502
                */
1503
            } else {
1504
                // close wizard and show error box
1505
                $("a#networkscreate").overlay().close();
1506
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1507
            }
1508
        }
1509
    });
1510
    return false;
1511
}
1512

    
1513
// rename network
1514
function rename_network(networkID, networkName){
1515
    if (!networkID.length){
1516
        //ajax_success('DEFAULT');
1517
        return false;
1518
    }
1519
    // prepare payload
1520
    var payload = {
1521
        "network": {"name": networkName}
1522
    };
1523
    // ajax call
1524
    $.ajax({
1525
        url: API_URL + '/networks/' + networkID,
1526
        type: "PUT",
1527
        contentType: "application/json",
1528
        dataType: "json",
1529
        data: JSON.stringify(payload),
1530
        timeout: TIMEOUT,
1531
        error: function(jqXHR, textStatus, errorThrown) {
1532
            try {
1533
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1534
            } catch (err) {
1535
                ajax_error(-1, "UI Error", 'Rename network', err);
1536
            }
1537
        },
1538
        success: function(data, textStatus, jqXHR) {
1539
            if ( jqXHR.status == '204') {
1540
                try {
1541
                    console.info('renamed network' + networkID);
1542
                } catch(err) {}
1543
            } else {
1544
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1545
            }
1546
        }
1547
    });
1548
    return false;
1549
}
1550

    
1551
function delete_network(networkIDs){
1552
    if (!networkIDs.length){
1553
        //ajax_success('DEFAULT');
1554
        return false;
1555
    }
1556
    // get a network
1557
    var networkID = networkIDs.pop();
1558
    // ajax post destroy call can have an empty request body
1559
    var payload = {};
1560
    // ajax call
1561
    $.ajax({
1562
        url: API_URL + '/networks/' + networkID,
1563
        type: "DELETE",
1564
        contentType: "application/json",
1565
        dataType: "json",
1566
        data: JSON.stringify(payload),
1567
        timeout: TIMEOUT,
1568
        error: function(jqXHR, textStatus, errorThrown) {
1569
            try {
1570
                display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1571
            } catch (err) {
1572
                display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1573
            }
1574
        },
1575
        success: function(data, textStatus, jqXHR) {
1576
            if ( jqXHR.status == '204') {
1577
                try {
1578
                    console.info('deleted network ' + networkID);
1579
                } catch(err) {}
1580
                // continue with the rest of the servers
1581
                delete_network(networkIDs);
1582
            } else {
1583
                try {
1584
                    display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1585
                } catch (err) {
1586
                    display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1587
                }
1588
            }
1589
        }
1590
    });
1591
    return false;
1592
}
1593

    
1594
function add_server_to_network(networkID, serverIDs, serverNames, serverStates) {
1595
    if (!serverIDs.length){
1596
        // close the overlay when all the calls are made
1597
        $("a#add-machines-overlay").overlay().close();
1598
        return false;
1599
    }
1600
    // get a server
1601
    var serverID = serverIDs.pop();
1602
    var serverName = serverNames.pop();
1603
    var serverState = serverStates.pop();
1604
    // prepare payload
1605
    var payload = {
1606
            "add": { "serverRef": serverID }
1607
        };
1608
    // prepare ajax call
1609
    $.ajax({
1610
        url: API_URL + '/networks/' + networkID + '/action',
1611
        type: "POST",
1612
        contentType: "application/json",
1613
        dataType: "json",
1614
        data: JSON.stringify(payload),
1615
        timeout: TIMEOUT,
1616
        error: function(jqXHR, textStatus, errorThrown) {
1617
            try {
1618
                // close wizard and show error box
1619
                $("a#add-machines-overlay").data('overlay').close();
1620
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1621
            } catch (err) {
1622
                ajax_error(-5, "UI Error", 'Add server to network', err);
1623
            }
1624
        },
1625
        success: function(data, textStatus, jqXHR) {
1626
            if ( jqXHR.status == '202') {
1627
                try {
1628
                    console.info('added server ' + serverID + ' to network ' + networkID);
1629
                } catch(err) {}
1630
                // toggle the reboot dialog
1631
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1632
                // continue with the rest of the servers
1633
                add_server_to_network(networkID, serverIDs, serverNames, serverStates);
1634
            } else {
1635
                // close wizard and show error box
1636
                $("a#add-machines-overlay").data('overlay').close();
1637
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1638
            }
1639
        }
1640
    });
1641
    return false;
1642
}
1643

    
1644
function remove_server_from_network(networkIDs, serverIDs, serverNames, serverStates) {
1645
    if (!networkIDs.length){
1646
        //ajax_success('DEFAULT');
1647
        return false;
1648
    }
1649
    // get a network and a server
1650
    var networkID = networkIDs.pop();
1651
    var serverID = serverIDs.pop();
1652
    var serverName = serverNames.pop();
1653
    var serverState = serverStates.pop();
1654
    // prepare payload
1655
    var payload = {
1656
            "remove": { "serverRef": serverID }
1657
        };
1658
    // prepare ajax call
1659
    $.ajax({
1660
        url: API_URL + '/networks/' + networkID + '/action',
1661
        type: "POST",
1662
        contentType: "application/json",
1663
        dataType: "json",
1664
        data: JSON.stringify(payload),
1665
        timeout: TIMEOUT,
1666
        error: function(jqXHR, textStatus, errorThrown) {
1667
            try {
1668
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1669
            } catch (err) {
1670
                ajax_error(-5, "UI Error", 'Remove server form network', err);
1671
            }
1672
        },
1673
        success: function(data, textStatus, jqXHR) {
1674
            if ( jqXHR.status == '202') {
1675
                try {
1676
                    console.info('deleted server ' + serverID + ' from network ' + networkID);
1677
                } catch(err) {}
1678
                // toggle the reboot dialog
1679
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1680
                // continue with the rest of the servers
1681
                remove_server_form_network(networkIDs, serverIDs, serverNames, serverStates);
1682
            } else {
1683
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1684
            }
1685
        }
1686
    });
1687
    return false;
1688
}
1689

    
1690
function set_firewall(networkID, serverID, profile) {
1691
    if (!networkID.length || !serverID.length || !profile.length){
1692
        return false;
1693
    }
1694
    // prepare payload
1695
    var payload = {
1696
            "firewallProfile": { "profile": profile }
1697
        };
1698
    // prepare ajax call
1699
    $.ajax({
1700
        url: API_URL + '/servers/' + serverID + '/action',
1701
        type: "POST",
1702
        contentType: "application/json",
1703
        dataType: "json",
1704
        data: JSON.stringify(payload),
1705
        timeout: TIMEOUT,
1706
        error: function(jqXHR, textStatus, errorThrown) {
1707
            try {
1708
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1709
            } catch (err) {
1710
                ajax_error(-5, "UI Error", 'Set firewall profile', err);
1711
            }
1712
        },
1713
        success: function(data, textStatus, jqXHR) {
1714
            if ( jqXHR.status == '202') {
1715
                try {
1716
                    console.info('for server ' + serverID + ' set firewall profile to ' + profile);
1717
                } catch(err) {}
1718
                //remove progress gif and toggle the content
1719
                $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').html(VARIOUS["APPLY"]);
1720
                $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').attr("disabled", false);
1721
                $('div#net-' + networkID + '-server-' + serverID + ' div.firewall-header').click();
1722
                // toggle the reboot dialog
1723
                var serverName = $('div#net-' + networkID + '-server-' + serverID + ' div.machine-name-div span.name').text();
1724
                var serverState = $('div#net-' + networkID + '-server-' + serverID + ' img.logo').attr('src').split('-')[1];
1725
                serverState = serverState.split('.')[0];
1726
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1727
            } else {
1728
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1729
            }
1730
        }
1731
    });
1732
    return false;
1733
}
1734

    
1735
// show the welcome screen
1736
function showWelcome() {
1737
    $("#view-select").fadeOut("fast");
1738
    $("#emptymachineslist").fadeIn("fast");
1739
    $("#machinesview").hide();
1740
}
1741

    
1742
// hide the welcome screen
1743
function hideWelcome() {
1744
    $("#emptymachineslist").fadeOut("fast");
1745
    $("#view-select").fadeIn("fast");
1746
    $("div#view-select").show();
1747
    $("#machinesview").show();
1748
}
1749

    
1750
function log_server_status_change(server_entry, new_status) {
1751
    // firebug console logging
1752
    try {
1753
        if ($("#machinesview-single").length > 0) {
1754
            console.info(server_entry.find("div.machine-details div.name").text() +
1755
                        ' from ' + server_entry.find(".state-label").text() +
1756
                        ' to ' + STATUSES[new_status]);
1757
        } else {
1758
            console.info(server_entry.find("div.name span.name").text() +
1759
                        ' from ' + server_entry.find(".status").text() +
1760
                        ' to ' + STATUSES[new_status]);
1761
        }
1762
    } catch(err) {}
1763
}
1764

    
1765
function get_flavor_params(flavorRef) {
1766
    var cpus, ram, disk;
1767
    if ( flavors.length > 0 ) {
1768
        var current_flavor = '';
1769
        for (i=0; i<flavors.length; i++) {
1770
            if (flavors[i]['id'] == flavorRef) {
1771
                current_flavor = flavors[i];
1772
            }
1773
        }
1774
        cpus = current_flavor['cpu'];
1775
        ram = current_flavor['ram'];
1776
        disk = current_flavor['disk'];
1777
    } else {
1778
        cpus = 'undefined';
1779
        ram = 'undefined';
1780
        disk = 'undefined';
1781
    }
1782
    return {'cpus': cpus, 'ram': ram, 'disk': disk};
1783
}
1784

    
1785
function get_image_params(imageRef) {
1786
    var image_name, image_size;
1787
    if ( images.length > 0 ) {
1788
        var current_image = '';
1789
        for (i=0; i<images.length; i++) {
1790
            if (images[i]['id'] == imageRef) {
1791
                current_image = images[i];
1792
            }
1793
        }
1794
        try {
1795
            image_name = current_image['name'];
1796
        } catch(err) { image_name = 'undefined'; }
1797
        try{
1798
            image_size = current_image['metadata']['values']['size'];
1799
        } catch(err) { image_size = 'undefined'; }
1800
    } else {
1801
        image_name = 'undefined';
1802
        image_size = 'undefined';
1803
    }
1804
    return {'name': image_name,'size': image_size};
1805
}
1806

    
1807
function get_public_ips(server) {
1808
    var ip4, ip6;
1809
    try {
1810
        if (server.addresses.values) {
1811
            $.each (server.addresses.values, function(i, value) {
1812
                if (value.id == 'public') {
1813
                    try {
1814
                        $.each (value.values, function(i, ip) {
1815
                            if (ip.version == '4') {
1816
                                ip4 = ip.addr;
1817
                            } else if (ip.version == '6') {
1818
                                ip6 = ip.addr;
1819
                            } else {
1820
                                ip4 = 'pending';
1821
                                ip6 = 'pending';
1822
                            }
1823
                        });
1824
                    } catch (err){
1825
                        try{console.info('Server ' + server.id + ' has invalid ips')}catch(err){};
1826
                        ip4 = 'pending';
1827
                        ip6 = 'pending';
1828
                    }
1829
                }
1830
            });
1831
        }
1832
    } catch (err) {
1833
        try{console.info('Server ' + server.id + ' has no network addresses')}catch(err){};
1834
        ip4 = 'pending';
1835
        ip6 = 'pending';
1836
    }
1837
    return {'ip4': ip4, 'ip6': ip6};
1838
}
1839

    
1840
function get_private_ips(server) {
1841

    
1842
}
1843

    
1844
function close_all_overlays() {
1845
        try {
1846
                $("a#networkscreate").overlay().close();
1847
        } catch(err) {}
1848
        try {
1849
                $('a#create').overlay().close();
1850
        } catch(err) {}
1851
        try {
1852
                $("a#add-machines-overlay").overlay().close();
1853
        } catch(err) {}
1854
        try {
1855
                $("a#metadata-scrollable").overlay().close();
1856
        } catch(err) {}
1857
        try {
1858
                $("a#msgbox").overlay().close();
1859
        } catch(err) {}
1860
        try {
1861
                $("a#feedbackbox").overlay().close();
1862
        } catch(err) {}
1863
}
1864

    
1865
// logout
1866
function user_session_logout() {
1867
    $.cookie("X-Auth-Token", null);
1868
    if (window.LOGOUT_REDIRECT !== undefined)
1869
    {
1870
        window.location = window.LOGOUT_REDIRECT;
1871
    } else {
1872
        window.location.reload();
1873
    }
1874
}
1875

    
1876
// action indicators
1877
function init_action_indicator_handlers(machines_view)
1878
{
1879
    // init once for each view
1880
    if (window.ACTION_ICON_HANDLERS == undefined)
1881
    {
1882
        window.ACTION_ICON_HANDLERS = {};
1883
    }
1884

    
1885
    if (machines_view in window.ACTION_ICON_HANDLERS)
1886
    {
1887
        return;
1888
    }
1889
    window.ACTION_ICON_HANDLERS[machines_view] = 1;
1890

    
1891
    if (machines_view == "list")
1892
    {
1893
        // totally different logic for list view
1894
        init_action_indicator_list_handlers();
1895
        return;
1896
    }
1897

    
1898
    function update_action_icon_indicators(force)
1899
    {
1900
        function show(el, action) {
1901
            $(".action-indicator", $(el)).attr("class", "action-indicator " + action);
1902
            $(".action-indicator", $(el)).show();
1903
        }
1904

    
1905
        function hide(el) {
1906
            $(".action-indicator", $(el)).hide();
1907
        }
1908

    
1909
        function get_pending_actions(el) {
1910
            return $(".confirm_single:visible", $(el));
1911
        }
1912

    
1913
        function other_indicators(el) {
1914
           return $("img.wave:visible, img.spinner:visible", $(el))
1915
        }
1916

    
1917
        $("div.machine:visible, div.single-container").each(function(index, el){
1918
            var el = $(el);
1919
            var pending = get_pending_actions(el);
1920
            var other = other_indicators(el);
1921
            var action = undefined;
1922
            var force_action = force;
1923
            var visible = $(el).css("display") == "block";
1924

    
1925
            if (force_action !==undefined && force_action.el !== el[0]) {
1926
                // force action for other vm
1927
                // skipping force action
1928
                force_action = undefined;
1929
            }
1930

    
1931
            if (force_action !==undefined && force_action.el === el[0]) {
1932
                action = force_action.action;
1933
            }
1934

    
1935
            if (other.length >= 1) {
1936
                return;
1937
            }
1938

    
1939
            if (pending.length >= 1 && force_action === undefined) {
1940
                action = $(pending.parent()).attr("class").replace("action-container","");
1941
            }
1942

    
1943
            if (action in {'console':''}) {
1944
                return;
1945
            }
1946

    
1947
            if (action !== undefined) {
1948
                show(el, action);
1949
            } else {
1950
                try {
1951
                    if (el.attr('id') == pending_actions[0][1])
1952
                    {
1953
                        return;
1954
                    }
1955
                } catch (err) {
1956
                }
1957
                hide(el);
1958
            }
1959

    
1960
        });
1961
    }
1962

    
1963
    // action indicators
1964
    $(".action-container").live('mouseover', function(evn) {
1965
        force_action = {'el': $(evn.currentTarget).parent().parent()[0], 'action':$(evn.currentTarget).attr("class").replace("action-container","")};
1966
        // single view case
1967
        if ($(force_action.el).attr("class") == "upper")
1968
        {
1969
            force_action.el = $(evn.currentTarget).parent().parent().parent()[0]
1970
        };
1971
        update_action_icon_indicators(force_action);
1972
    });
1973

    
1974
    $("img.spinner, img.wave").live('hide', function(){
1975
        update_action_icon_indicators();
1976
    });
1977
    // register events where icons should get updated
1978

    
1979
    // hide action indicator image on mouse out, spinner appear, wave appear
1980
    $(".action-container").live("mouseout", function(evn){
1981
        update_action_icon_indicators();
1982
    });
1983

    
1984
    $(".confirm_single").live("click", function(evn){
1985
        update_action_icon_indicators();
1986
    });
1987

    
1988
    $("img.spinner, img.wave").live('show', function(){
1989
        $("div.action-indicator").hide();
1990
    });
1991

    
1992
    $(".confirm_single button.no").live('click', function(evn){
1993
        $("div.action-indicator", $(evn.currentTarget).parent().parent()).hide();
1994
    });
1995

    
1996
    $(".confirm_multiple button.no").click(function(){
1997
        $("div.action-indicator").hide();
1998
    });
1999

    
2000
    $(".confirm_multiple button.yes").click(function(){
2001
        $("div.action-indicator").hide();
2002
    });
2003
}
2004

    
2005
function init_action_indicator_list_handlers()
2006
{
2007
    var skip_actions = { 'connect':'','details':'' };
2008

    
2009
    var has_pending_confirmation = function()
2010
    {
2011
        return $(".confirm_multiple:visible").length >= 1
2012
    }
2013

    
2014
    function update_action_indicator_icons(force_action, skip_pending)
2015
    {
2016
        // pending action based on the element class
2017
        var pending_action = $(".selected", $(".actions"))[0];
2018
        var selected = get_list_view_selected_machine_rows();
2019

    
2020
        // reset previous state
2021
        list_view_hide_action_indicators();
2022

    
2023
        if (pending_action == undefined && !force_action)
2024
        {
2025
            // no action selected
2026
            return;
2027
        }
2028

    
2029
        if (force_action != undefined)
2030
        {
2031
            // user forced action choice
2032
            var action_class = force_action;
2033
        } else {
2034
            // retrieve action name (reboot, stop, etc..)
2035
            var action_class = $(pending_action).attr("id").replace("action-","");
2036
        }
2037

    
2038
        selected.each(function(index, el) {
2039
            if (has_pending_confirmation() && skip_pending)
2040
            {
2041
                return;
2042
            }
2043
            var el = $(el);
2044
            var logo = $("img.list-logo", el);
2045
            $(".action-indicator", el).remove();
2046
            var cls = "action-indicator " + action_class;
2047
            // add icon div
2048
            logo.after('<div class="' + cls + '"></div>');
2049
            // hide os logo
2050
            $("img.list-logo", el).hide();
2051
        });
2052
    }
2053

    
2054
    // on mouseover we force the images to the hovered action
2055
    $(".actions a").live("mouseover", function(evn) {
2056
        var el = $(evn.currentTarget);
2057
        if (!el.hasClass("enabled"))
2058
        {
2059
            return;
2060
        }
2061
        var action_class = el.attr("id").replace("action-","");
2062
        if (action_class in skip_actions)
2063
        {
2064
            return;
2065
        }
2066
        update_action_indicator_icons(action_class, false);
2067
    });
2068

    
2069

    
2070
    // register events where icons should get updated
2071
    $(".actions a.enabled").live("click", function(evn) {
2072
        // clear previous selections
2073
        $("a.selected").removeClass("selected");
2074

    
2075
        var el = $(evn.currentTarget);
2076
        el.addClass("selected");
2077
        update_action_indicator_icons(undefined, false);
2078
    });
2079

    
2080
    $(".actions a").live("mouseout", function(evn) {
2081
        update_action_indicator_icons(undefined, false);
2082
    });
2083

    
2084
    $(".confirm_multiple button.no").click(function(){
2085
        list_view_hide_action_indicators();
2086
    });
2087

    
2088
    $(".confirm_multiple button.yes").click(function(){
2089
        list_view_hide_action_indicators();
2090
    });
2091

    
2092
    $("input[type=checkbox]").live('change', function(){
2093
        // pending_actions will become empty on every checkbox click/change
2094
        // line 154 machines_list.html
2095
        pending_actions = [];
2096
        if (pending_actions.length == 0)
2097
        {
2098
            $(".confirm_multiple").hide();
2099
            $("a.selected").each(function(index, el){$(el).removeClass("selected")});
2100
        }
2101
        update_action_indicator_icons(undefined, false);
2102
    });
2103

    
2104
}
2105

    
2106
function list_view_hide_action_indicators()
2107
{
2108
    $("tr td .action-indicator").remove();
2109
    $("tr td img.list-logo").show();
2110
}
2111

    
2112
function get_list_view_selected_machine_rows()
2113
{
2114
    var table = $("table.list-machines");
2115
    var rows = $("tr:has(input[type=checkbox]:checked)",table);
2116
    return rows;
2117
}
2118

    
2119
// machines images utils
2120
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) {
2121
    var views_map = {'single': '.single-image', 'icon': '.logo'};
2122
    var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'}
2123
    var sizes_map = {'single': 'large', 'icon': 'medium'}
2124

    
2125
    var size = sizes_map[machines_view];
2126
    var img_selector = views_map[machines_view];
2127
    var cls = states_map[state];
2128

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

    
2132
    var el = $(img_selector, machine);
2133
    var current_img = el.css("backgroundImage");
2134
    if (os == undefined){
2135
        new_img = current_img;
2136
    }
2137

    
2138
    // os changed
2139
    el.css("backgroundImage", new_img);
2140

    
2141
    // reset current state
2142
    if (skip_reset_states === undefined)
2143
    {
2144
        el.removeClass("single-image-state1");
2145
        el.removeClass("single-image-state2");
2146
        el.removeClass("single-image-state3");
2147
        el.removeClass("single-image-state4");
2148
    }
2149

    
2150
    if (remove_state !== undefined)
2151
    {
2152
        remove_state = "single-image-" + states_map[remove_state];
2153
        el.removeClass(remove_state);
2154
        return;
2155
    }
2156
    
2157
    // set proper state
2158
    el.addClass("single-image-" + cls);
2159
}
2160

    
2161

    
2162
// generic info box
2163
function show_feedback_form(msg, from_error) {
2164
    var box = $("#feedback-form");
2165
    box.addClass("notification-box");
2166

    
2167
    // initialize
2168
    box.find(".form-container").show();
2169
    box.find("textarea").val("");
2170
    box.find(".message").hide();
2171
    
2172
    var initial_msg = msg || undefined;
2173
    
2174
    var triggers = $("a#feedbackbox").overlay({
2175
        // some mask tweaks suitable for modal dialogs
2176
        mask: '#666',
2177
        top: '10px',
2178
        fixed: false,
2179
        closeOnClick: false,
2180
        oneInstance: false,
2181
        load: false
2182
    });
2183

    
2184
    
2185
    if (initial_msg && from_error) {
2186
        // feedback form from ajax_error window
2187
        box.find("textarea").val(initial_msg);
2188
        $("a#feedbackbox").overlay().onClose(function(){window.location.reload()});
2189
        box.find("textarea").height(200);
2190
        $("a#feedbackbox").overlay().onLoad(function(){box.find("textarea").focus().setCursorPosition(500);});
2191
        
2192
    }
2193

    
2194
    $("#feedback-form form").unbind("submit");
2195
    $("#feedback-form form").submit(function(event) {
2196
        event.preventDefault();
2197
            
2198
        // empty msg
2199
        if ($("textarea.feedback-text").val().replace(/^\s*|\s*$/,"") == "") {
2200
            alert($(".empty-error-msg", this).text());
2201
            return;
2202
        }
2203

    
2204
        $("textarea.data-text", this).val("").val(JSON.stringify(get_user_data()));
2205

    
2206
        $.ajax({
2207
            url: FEEDBACK_URL,
2208
            data: $(this).serialize(),
2209
            type: "POST",
2210
            // show loading
2211
            beforeSend: function() {box.find(".form-container").hide(); box.find(".sending").fadeIn() },
2212
            // hide form
2213
            complete: function() { box.find(".form-container").hide(); box.find(".sending").hide() },
2214
            // on success display success message
2215
            success: function() { box.find(".success").fadeIn(); box.find(".sending").hide() },
2216
            // display error message
2217
            error: function() { box.find(".errormsg").fadeIn(); box.find(".sending").hide() }
2218
        })
2219
    });
2220
    
2221
    $("a#feedbackbox").data('overlay').load();
2222

    
2223
    // reset feedback_pending for ajax_errors
2224
    window.FEEDBACK_PENDING = false;
2225
    return false;
2226
}
2227

    
2228
function get_user_data(extra_data) {
2229
    return $.extend({
2230
        'servers': $.extend({}, servers),
2231
        'client': {'browser': $.browser, 'screen': $.extend({}, screen), 'client': $.client},
2232
        'dates': {'now': new Date, 'lastUpdate': changes_since_date}
2233
    }, extra_data);
2234
}
2235

    
2236
function msg_box(config) {
2237
    var config = $.extend({'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false}, config);
2238
    // prepare the error message
2239
    // bring up success notification
2240

    
2241
    var box = $("#notification-box");
2242
    box.addClass("notification-box");
2243
    box.addClass('success');
2244
    box.removeClass('error');
2245

    
2246
    var sel = function(s){return $(s, box)};
2247
    // reset texts
2248
    sel("h3 span.header-box").html("");
2249
    sel(".sub-text").html("");
2250
    sel(".password-container .password").html("");
2251
    sel("div.machine-now-building").html("");
2252
    
2253

    
2254
    // apply msg box contents
2255
    sel("h3 span.header-box").html(config.title);
2256
    sel("div.machine-now-building").html(config.content);
2257
    sel(".popup-header").removeClass("popup-header-error");
2258
    box.removeClass("popup-border-error");
2259
    sel(".popup-details").removeClass("popup-details-error");
2260
    sel(".popup-separator").removeClass("popup-separator-error");
2261
    
2262
    sel(".password-container").hide();
2263
    if (config.extra) {
2264
        sel(".password-container .password").html(config.extra);
2265
        sel(".password-container").show();
2266
    }
2267

    
2268
    var triggers = $("a#msgbox").overlay({
2269
        // some mask tweaks suitable for modal dialogs
2270
        mask: '#666',
2271
        top: '10px',
2272
        closeOnClick: false,
2273
        oneInstance: false,
2274
        load: false,
2275
        fixed: false,
2276
        onClose: function () {
2277
            // With partial refresh working properly,
2278
            // it is no longer necessary to refresh the whole page
2279
            // choose_view();
2280
        }
2281
    });
2282
    $("a#msgbox").data('overlay').load();
2283
    
2284
    var parse_data = config.parse_data || false;
2285
    var load_html = config.html || false;
2286
    var user_success = config.success || false;
2287
    config.ajax = config.ajax || {};
2288

    
2289
    // requested to show remote data in msg_box
2290
    if (config.ajax) {
2291
        $.ajax($.extend({ 
2292
            url:config.ajax, 
2293
            success: function(data){
2294
                // we want to get our data parsed before
2295
                // placing them in content
2296
                if (parse_data) {
2297
                    data = parse_data(data);
2298
                }
2299

    
2300
                // no json response
2301
                // load html body
2302
                if (load_html) {
2303
                    sel("div.machine-now-building").html(data);
2304
                } else {
2305

    
2306
                    if (data.title) {
2307
                        sel("h3 span.header-box").text(data.title);
2308
                    }
2309

    
2310
                    if (data.content) {
2311
                        sel("div.machine-now-building").html(data.content);
2312
                    }
2313
                    if (data.extra) {
2314
                        sel(".password-container .password").html(data.extra);
2315
                        sel(".password-container").show();
2316
                    }
2317
                    if (data.subinfo) {
2318
                        sel(".sub-text").html(data.subinfo);
2319
                    } else {
2320
                        sel(".sub-text").html("");
2321
                    }
2322
                }
2323

    
2324
                if (user_success) {
2325
                    user_success($("div.machine-now-building"));
2326
                }
2327
            },
2328
            error: function(xhr, status, err) {
2329
                ajax_error(-5, "UI Error", "Machine connect", err);
2330
            }
2331
        }, config.ajax_config));
2332
    }
2333
    return false;
2334
}
2335

    
2336

    
2337
function show_invitations() {
2338

    
2339
    handle_invitations = function(el) {
2340
        el.addClass("invitations");
2341
        var cont = el;
2342
        var form = $(el).find("form");
2343

    
2344
        // remove garbage rows that stay in DOM between requests
2345
        $(".removable-field-row:hidden").remove();
2346

    
2347
        try {
2348
            $("#invform #removable-name-container-1").dynamicField();
2349
        } catch (err) {
2350
            close_all_overlays();
2351
        }
2352
        
2353
        $(".invitations-left").hide();
2354
        $("#notification-box .header-box").html("");
2355
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
2356

    
2357
        form.submit(function(evn){
2358
            evn.preventDefault();
2359
            $.post(form.attr("action"), form.serialize(), function(data) {
2360
                if (data.redirect) {
2361
                    $.ajax({url: data.redirect, success: function(){
2362
                        handle_invitations(el)
2363
                    }});
2364
                } else {
2365
                    $(cont).html(data); 
2366
                    handle_invitations(cont);
2367
                }
2368
            });
2369
            return false;
2370
        });
2371
    }
2372

    
2373
    msg_box({title:window.INVITATIONS_TITLE, content:'', ajax:INVITATIONS_URL, html:true, success: function(el){ 
2374
        handle_invitations(el)}
2375
    });
2376
}
2377

    
2378

    
2379
function get_short_v6(v6, parts_to_keep) {
2380
    var parts = v6.split(":");
2381
    var new_parts = parts.slice(parts.length - parts_to_keep);
2382
    return new_parts.join(":");
2383
}
2384

    
2385
function fix_v6_addresses() {
2386

    
2387
    // what to prepend
2388
    var match = "...";
2389
    // long ip min length
2390
    var limit = 20;
2391
    // parts to show after the transformation
2392
    // (from the end)
2393
    var parts_to_keep_from_end = 4;
2394

    
2395
    $(".ipv6-text").each(function(index, el){
2396
        var el = $(el);
2397
        var ip = $(el).text();
2398
            
2399
        // transformation not applyied
2400
        // FIXME: use $.data for the condition
2401
        if (ip.indexOf(match) == -1 && ip != "pending") {
2402
            
2403
            // only too long ips
2404
            if (ip.length > 20) {
2405
                $(el).data("ipstring", ip);
2406
                $(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
2407
                $(el).attr("title", ip);
2408
                $(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'});
2409
            }
2410
        } else {
2411
            if (ip.indexOf(match) == 0) {
2412
            } else {
2413
                // not a long ip anymore
2414
                $(el).data("ipstring", undefined);
2415
                $(el).css({'text-decoration':'none'});
2416

    
2417
                if ($(el).data('tooltip')) {
2418
                    $(el).data('tooltip').show = function () {};
2419
                }
2420
            }
2421
        }
2422
    });
2423
}
2424

    
2425
function fix_server_name(str, limit, append) {
2426
    limit = limit || 30;
2427
    append = append || "...";
2428

    
2429
    if (str.length > limit) {
2430
        str = str.substring(0,limit-append.length) + append;
2431
    }
2432
    return str;
2433
}