Statistics
| Branch: | Tag: | Revision:

root / ui / static / synnefo.js @ 6caef1a3

History | View | Annotate | Download (90.4 kB)

1
//
2
// Copyright 2011 GRNET S.A. All rights reserved.
3
//
4
// Redistribution and use in source and binary forms, with or
5
// without modification, are permitted provided that the following
6
// conditions are met:
7
//
8
//   1. Redistributions of source code must retain the above
9
//      copyright notice, this list of conditions and the following
10
//      disclaimer.
11
//
12
//   2. Redistributions in binary form must reproduce the above
13
//      copyright notice, this list of conditions and the following
14
//      disclaimer in the documentation and/or other materials
15
//      provided with the distribution.
16
//
17
// THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
18
// OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
20
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
21
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
24
// USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
25
// AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
26
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
27
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28
// POSSIBILITY OF SUCH DAMAGE.
29
//
30
// The views and conclusions contained in the software and
31
// documentation are those of the authors and should not be
32
// interpreted as representing official policies, either expressed
33
// or implied, of GRNET S.A.
34
//
35
var API_URL = "/api/v1.1";
36
var changes_since = 0, deferred = 0, update_request = false, load_request = false, pending_actions = [];
37
var flavors = [], images = [], servers = [], disks = [], cpus = [], ram = [];
38
var networks = [], networks_changes_since = 0;
39
var error_timeout = 20000;
40
var last_request = {};
41

    
42
$.ajaxSetup({
43
    'beforeSend': function(xhr) {
44
          // save ajax settings, we might need them for error reporting
45
          last_request = this;
46
          xhr.setRequestHeader("X-Auth-Token", $.cookie("X-Auth-Token"));
47
    },
48

    
49
    // catch uncaught error requests
50
    // stop interaction and show only for the 5xx errors
51
    // refresh the page after 20secs
52
    error: function(jqXHR, textStatus, errorThrown) {
53

    
54
        // check if xhr is in valid state (no status property)
55
        try {
56
            var status = jqXHR.status;
57
        } catch (err) {
58
            return false;
59
        }
60

    
61
        // stop interaction for important (aka 500) error codes only
62
        if (jqXHR.status >= 500 && jqXHR.status < 600)
63
        {
64
            try {
65
                ajax_error(jqXHR.status, undefined, 'Unknown', jqXHR.responseText);
66
            } catch(err) {
67
                ajax_error(-1, textStatus, 'Unknown', "NETWORK ERROR");
68
            }
69
        }
70

    
71
        // refresh after 10 seconds
72
        window.setTimeout("window.location.reload()", window.error_timeout);
73
    }
74
});
75

    
76
function isXhrException(err) {
77

    
78
    DOM_EXCEPTION_NAMES = [
79
        "NS_ERROR_NOT_AVAILABLE", // Firefox
80
        "INVALID_STATE_ERR" // Chrome
81
    ];
82

    
83
    try {
84
        if (DOM_EXCEPTION_NAMES.indexOf(err.name) != -1) {
85
            return true;
86
        } 
87
        
88
        // ie !!!!
89
        if (err.number == -2147467259) {
90
            return true;
91
        }
92

    
93
    } catch(err) {
94
        return false;
95
    }
96

    
97
    return false;
98
}
99

    
100
Object.prototype.toString = function(o){
101
    
102
    var parse = function(_o){
103
        var a = [], t;
104
        for(var p in _o){
105
            if(_o.hasOwnProperty(p)){
106
                t = _o[p];
107
                if(t && typeof t == "object"){
108
                    a[a.length]= p + ":{ " + arguments.callee(t).join(", ") + "}";
109
                }
110
                else {
111
                    if(typeof t == "string"){
112
                        a[a.length] = [ p+ ": \"" + t.toString() + "\"" ];
113
                    }
114
                    else{
115
                        a[a.length] = [ p+ ": " + t.toString()];
116
                    }
117
                }
118
            }
119
        }
120
        return a;
121
        
122
    }
123
    return "{" + parse(o).join(", ") + "}";
124
   
125
}
126

    
127
// http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area 
128
$.fn.setCursorPosition = function(pos) {
129
    if ($(this).get(0).setSelectionRange) {
130
      $(this).get(0).setSelectionRange(pos, pos);
131
    } else if ($(this).get(0).createTextRange) {
132
      var range = $(this).get(0).createTextRange();
133
      range.collapse(true);
134
      range.moveEnd('character', pos);
135
      range.moveStart('character', pos);
136
      range.select();
137
    }
138
}
139

    
140
// jquery show/hide events
141
var _oldshow = $.fn.show;
142
$.fn.show = function(speed, callback) {
143
    $(this).trigger('show');
144
    return _oldshow.apply(this,arguments);
145
}
146
var _oldhide = $.fn.hide;
147
$.fn.hide = function(speed, callback) {
148
    $(this).trigger('hide');
149
    return _oldhide.apply(this,arguments);
150
}
151

    
152
function ISODateString(d){
153
    //return a date in an ISO 8601 format using UTC.
154
    //do not include time zone info (Z) at the end
155
    //taken from the Mozilla Developer Center
156
    function pad(n){ return n<10 ? '0'+n : n }
157
    return  d.getUTCFullYear()+ '-' +
158
            pad(d.getUTCMonth()+1) + '-' +
159
            pad(d.getUTCDate()) + 'T' +
160
            pad(d.getUTCHours()) + ':' +
161
            pad(d.getUTCMinutes()) + ':' +
162
            pad(d.getUTCSeconds()) +'Z'
163
}
164

    
165
function parse_error(responseText, errorCode){
166
    var errors = [];
167
    try {
168
        responseObj = JSON.parse(responseText);
169
    }
170
    catch(err) {
171
        errors[0] = {'code': errorCode};
172
        return errors;
173
    }
174
    for (var err in responseObj){
175
        errors[errors.length] = responseObj[err];
176
    }
177
    return errors;
178
}
179

    
180
// indexOf prototype for IE
181
if (!Array.prototype.indexOf) {
182
  Array.prototype.indexOf = function(elt /*, from*/) {
183
    var len = this.length;
184
    var from = Number(arguments[1]) || 0;
185
    from = (from < 0)
186
         ? Math.ceil(from)
187
         : Math.floor(from);
188
    if (from < 0)
189
      from += len;
190

    
191
    for (; from < len; from++) {
192
      if (from in this &&
193
          this[from] === elt)
194
        return from;
195
    }
196
    return -1;
197
  };
198
}
199

    
200
// trim prototype for IE
201
if(typeof String.prototype.trim !== 'function') {
202
    String.prototype.trim = function() {
203
        return this.replace(/^\s+|\s+$/g, '');
204
    }
205
}
206

    
207
// simple string format helper (http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format)
208
String.prototype.format = function() {
209
    var formatted = this;
210
    for (var i = 0; i < arguments.length; i++) {
211
        var regexp = new RegExp('\\{'+i+'\\}', 'gi');
212
        formatted = formatted.replace(regexp, arguments[i]);
213
    }
214
    return formatted;
215
};
216

    
217

    
218
function update_confirmations() {
219
    // hide all confirm boxes to begin with
220
    $('#machines-pane div.confirm_single').hide();
221
    $('#machines-pane div.confirm_multiple').hide();
222
    var action_type = [];
223
    // standard view or single view
224
    if ($.cookie("view") == '0' || $.cookie("view") == '2') {
225
        for (var i=0; i<pending_actions.length; i++) {
226
            // show single confirms
227
            if (pending_actions[i][0] == reboot) {
228
                action_type = "reboot";
229
            } else if (pending_actions[i][0] == shutdown) {
230
                action_type = "shutdown";
231
            } else if (pending_actions[i][0] == start) {
232
                action_type = "start";
233
            } else if (pending_actions[i][0] == open_console) {
234
                action_type = "console";
235
            } else {
236
                action_type = "destroy";
237
            }
238
            $("#machines-pane #" + pending_actions[i][1] +
239
            " div.action-container." + action_type + " div.confirm_single").show();
240
        }
241
    }
242
    // if more than one pending action show multiple confirm box
243
    if (pending_actions.length>1 || $.cookie("view") == '1' && pending_actions.length == 1){
244
        $('#machines-pane div.confirm_multiple span.actionLen').text(pending_actions.length);
245
        $('#machines-pane div.confirm_multiple').show();
246
    }
247
}
248

    
249
function update_network_confirmations(){
250
    // hide all confirm boxes to begin with
251
    $('#networks-pane div.confirm_multiple').hide();
252

    
253
    for (var i=0;i<pending_actions.length;i++){
254
        // show single confirms depending on the action
255
        if (pending_actions[i][0] == delete_network) {
256
            $("#networks-pane div.network#net-"+pending_actions[i][1]).children('.confirm_single').show();
257
        } else if (pending_actions[i][0] == remove_server_from_network) {
258
            $("#networks-pane div.network #net-"+pending_actions[i][1]+"-server-"+pending_actions[i][2]).children('.confirm_single').show();
259
        } // else {}
260
    }
261

    
262
    // if more than one pending action show multiple confirm box
263
    if (pending_actions.length > 1){
264
        $('#networks-pane div.confirm_multiple span.actionLen').text(pending_actions.length);
265
        $('#networks-pane div.confirm_multiple').show();
266
    }
267
}
268

    
269
function list_view() {
270
    changes_since = 0; // to reload full list
271
    pending_actions = []; // clear pending actions
272
    update_confirmations();
273
    clearTimeout(deferred);    // clear old deferred calls
274
    try {
275
        update_request.abort(); // cancel pending ajax updates
276
        load_request.abort();
277
    }catch(err){}
278
    $.cookie("view", '1'); // set list cookie
279
    uri = $("a#list").attr("href");
280
    load_request = $.ajax({
281
        url: uri,
282
        type: "GET",
283
        timeout: TIMEOUT,
284
        dataType: "html",
285
        error: function(jqXHR, textStatus, errorThrown) {
286
            return false;
287
        },
288
        success: function(data, textStatus, jqXHR) {
289
            $("a#list")[0].className += ' activelink';
290
            $("a#standard")[0].className = '';
291
            $("a#single")[0].className = '';
292
            $("div#machinesview").html(data);
293
        }
294
    });
295
    return false;
296
}
297

    
298
function single_view() {
299
    changes_since = 0; // to reload full list
300
    pending_actions = []; // clear pending actions
301
    update_confirmations();
302
    clearTimeout(deferred);    // clear old deferred calls
303
    try {
304
        update_request.abort(); // cancel pending ajax updates
305
        load_request.abort();
306
    }catch(err){}
307
    $.cookie("view", '2'); // set list cookie
308
    uri = $("a#single").attr("href");
309
    load_request = $.ajax({
310
        url: uri,
311
        type: "GET",
312
        timeout: TIMEOUT,
313
        dataType: "html",
314
        error: function(jqXHR, textStatus, errorThrown) {
315
            return false;
316
        },
317
        success: function(data, textStatus, jqXHR) {
318
            $("a#single")[0].className += ' activelink';
319
            $("a#standard")[0].className = '';
320
            $("a#list")[0].className = '';
321
            $("div#machinesview").html(data);
322
        }
323
    });
324
    return false;
325
}
326

    
327
function standard_view() {
328
    changes_since = 0; // to reload full list
329
    pending_actions = []; // clear pending actions
330
    update_confirmations();
331
    clearTimeout(deferred);    // clear old deferred calls
332
    try {
333
        update_request.abort() // cancel pending ajax updates
334
        load_request.abort();
335
    }catch(err){}
336
    $.cookie("view", '0');
337
    uri = $("a#standard").attr("href");
338
    load_request = $.ajax({
339
        url: uri,
340
        type: "GET",
341
        timeout: TIMEOUT,
342
        dataType: "html",
343
        error: function(jqXHR, textStatus, errorThrown) {
344
            return false;
345
        },
346
        success: function(data, textStatus, jqXHR) {
347
            $("a#standard")[0].className += ' activelink';
348
            $("a#list")[0].className = '';
349
            $("a#single")[0].className = '';
350
            $("div#machinesview").html(data);
351
        }
352
    });
353
    return false;
354
}
355

    
356
function choose_view() {
357
    if ($.cookie("view")=='1') {
358
        list_view();
359
    } else if ($.cookie("view")=='2'){
360
        single_view();
361
    } else {
362
        standard_view();
363
    }
364
}
365

    
366
// return value from metadata key "OS", if it exists
367
function os_icon(metadata) {
368
    if (!metadata) {
369
        return 'okeanos';
370
    }
371
    if (metadata.values.OS == undefined || metadata.values.OS == '') {
372
        return 'okeanos';
373
    } else {
374
        if (os_icons.indexOf(metadata.values.OS) == -1) {
375
            return 'okeanos';
376
        } else {
377
            return metadata.values.OS;
378
        }
379
    }
380
}
381

    
382
function os_icon_from_value(metadata) {
383
    if (!metadata) {
384
        return 'okeanos';
385
    }
386
if (metadata == undefined || metadata == '') {
387
        return 'okeanos';
388
    } else {
389
        if (os_icons.indexOf(metadata) == -1) {
390
            return 'okeanos';
391
        } else {
392
            return metadata;
393
        }
394
    }
395
}
396

    
397
// get and show a list of running and terminated machines
398
function update_vms(interval) {
399
    try{ console.info('updating machines'); } catch(err){}
400
    var uri= API_URL + '/servers/detail';
401

    
402
    if (changes_since != 0)
403
        uri+='?changes-since='+changes_since
404

    
405
    update_request = $.ajax({
406
        cache: false,
407
        url: uri,
408
        type: "GET",
409
        timeout: TIMEOUT,
410
        dataType: "json",
411
        error: function(jqXHR, textStatus, errorThrown) {
412
            // don't forget to try again later
413
            if (interval) {
414
                clearTimeout(deferred);    // clear old deferred calls
415
                deferred = setTimeout(function() {update_vms(interval);},interval,interval);
416
            }
417
            // as for now, just show an error message
418
            try { console.info('update_vms errback:' + jqXHR.status ) } catch(err) {}
419
                try {
420
                    ajax_error(jqXHR.status, undefined, 'Update VMs', jqXHR.responseText);
421
                } catch(err) {
422
                    ajax_error(-1, textStatus, 'Update VMs', "NETWORK ERROR");
423
                }
424
                return false;
425
            },
426
        success: function(data, textStatus, jqXHR) {
427
            // create changes_since string if necessary
428
            if (jqXHR.getResponseHeader('Date') != null){
429
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
430
                changes_since = ISODateString(changes_since_date);
431
            }
432

    
433
            if (interval) {
434
                clearTimeout(deferred);    // clear old deferred calls
435
                deferred = setTimeout(function() {update_vms(interval);},interval,interval);
436
            }
437

    
438
            if (jqXHR.status == 200 || jqXHR.status == 203) {
439
                try {
440
                    //servers = data.servers.values;
441
                    update_servers_data(data.servers.values, data);
442
                    update_machines_view(data);
443
                } catch (err) { ajax_error(-503, "UI Error", 'Update VMs', err);}
444
            } else if (jqXHR.status != 304){
445
                try { console.info('update_vms callback:' + jqXHR.status ) } catch(err) {}
446
                /*
447
                FIXME:  Here it should return the error, however Opera does not support 304.
448
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
449
                        304, which should be corrected (Bug #317).
450
                */
451
                // ajax_error(jqXHR.status, "Ajax error", 'Update VMs', jqXHR.responseText);
452
            }
453
            return false;
454
        }
455
    });
456
    return false;
457
}
458

    
459
function update_servers_data(servers_update, data) {
460
    $(window).trigger("vm:update", servers_update, data);
461

    
462
    // first call
463
    if (!window.servers || window.servers.length == 0) {
464
        window.servers = servers_update;
465
        return;
466
    }
467
    
468
    // server exists helper
469
    server_exists = function(server) {
470
        var id = server.id;
471
        var found = false;
472
        var index = 0;
473
        $.each(servers, function(i, s) {
474
            if (s.id == id) { found = true, index = i };
475
        });
476
        if (found)
477
            return [found, index];
478

    
479
        return false;
480
    }
481

    
482
    // merge object properties
483
    merge = function() {
484
        var initial = arguments[0];
485
        var status_changed = undefined;
486
        $.each(arguments, function(index, el) {
487
            $.each(el, function(key,v) {
488
                // new attribute added
489
                var previous_value = initial[key];
490
                var v = v;
491
                if (initial[key] == undefined) {
492
                    $(window).trigger("vm:attr:add", initial, key, v);
493
                } else {
494
                    // value changed
495
                    if (initial[key] != v) {
496
                        if (key == "status") {
497
                            // dont change if in destroy state
498
                            if (initial.status == "DESTROY") {
499
                                v = "DESTROY";
500
                            }
501
                            status_changed = {'old': previous_value, 'new': v}; 
502
                        }
503

    
504
                        $(window).trigger("vm:attr:change", {'initial': initial, 'attr': key, 'newvalue': v});
505
                    }
506
                }
507
                initial[key] = v;
508
            });
509
        });
510
        if (status_changed !== undefined) {
511
            $(window).trigger('vm:status:change', {'vm': initial, 'old': status_changed['old'], 'new': status_changed['new']});
512
        }
513
        return initial;
514
    }
515
    
516
    // server removed
517
    var remove = [];
518
    $.each(servers_update, function(index, server) {
519
        if (server.status == "DELETED") {
520
            remove.push(server.id);
521
        }
522
    });
523
    
524
    // check server, if exists merge it with new values else add it
525
    $.each(servers_update, function(index, server) {
526
        var exists = server_exists(server);
527
        var old_server = servers[exists[1]];
528

    
529
        // reset network transition
530
        try {
531
            if (old_server.network_transition) {
532
                if (old_server.network_transition == "NETWORK_CHANGE") {
533
                    // network profile changed, servers data updated, so act if the change was made
534
                    // this flag will trigger ui to remove any transiiton indicators
535
                    // and hopefully apply the new value to the profile options
536
                    old_server.network_transition = "CHANGED"
537
                } else {
538
                    // nothing happened
539
                    old_server.network_transition = undefined;
540
                };
541
            }
542
        } catch (err) { console.info(err) }
543

    
544
        if (exists !== false) {
545
            try {
546
                servers[exists[1]] = merge(servers[exists[1]], server);
547
            } catch (err) {
548
            }
549
        } else {
550
            servers.push(server);
551
            $(window).trigger("vm:add", server);
552
        }
553
        if (remove.indexOf(server.id) > -1) {
554
            var remove_exists = server_exists(server);
555
            servers.splice(remove_exists[1], 1);
556
            $(window).trigger("vm:remove", server);
557
        }
558
    });
559
}
560

    
561
// get a list of running and terminated machines, used in network view
562
function update_networks(interval) {
563
    try{ console.info('updating networks'); } catch(err){}
564
    var uri= API_URL + '/servers/detail';
565

    
566
    if (changes_since != 0)
567
        uri+='?changes-since='+changes_since
568

    
569
    update_request = $.ajax({
570
        cache: false,
571
        url: uri,
572
        type: "GET",
573
        timeout: TIMEOUT,
574
        dataType: "json",
575
        error: function(jqXHR, textStatus, errorThrown) {
576
            // don't forget to try again later
577
            if (interval) {
578
                clearTimeout(deferred);    // clear old deferred calls
579
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
580
            }
581
            // as for now, just show an error message
582
            try { console.info('update_networks errback:' + jqXHR.status ) } catch(err) {}
583

    
584
            try {
585
                ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
586
            } catch(err) {
587
                ajax_error(-1, textStatus, 'Update networks', "NETWORK ERROR");
588
            }
589
            return false;
590
            },
591
        success: function(data, textStatus, jqXHR) {
592
            // create changes_since string if necessary
593
            if (jqXHR.getResponseHeader('Date') != null){
594
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
595
                changes_since = ISODateString(changes_since_date);
596
            }
597

    
598
            if (interval) {
599
                clearTimeout(deferred);    // clear old deferred calls
600
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
601
            }
602

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

    
628
// get and show a list of public and private networks
629
function update_network_names(servers_data) {
630
    try{ console.info('updating network names'); } catch(err){}
631
    var uri= API_URL + '/networks/detail';
632

    
633
    if (networks_changes_since != 0)
634
        //FIXME: Comment out the following, until metadata do not 304 when changed
635
        uri+='?changes-since=' + networks_changes_since
636

    
637
    update_request = $.ajax({
638
        cache: false,
639
        url: uri,
640
        type: "GET",
641
        timeout: TIMEOUT,
642
        dataType: "json",
643
        error: function(jqXHR, textStatus, errorThrown) {
644
            // as for now, just show an error message
645
            try {
646
                console.info('update_network names errback:' + jqXHR.status )
647
            } catch(err) {}
648
            try {
649
                ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
650
            } catch(err) {
651
                ajax_error(-1, textStatus, 'Update network names', "NETWORK ERROR");
652
            }
653
            return false;
654
            },
655
        success: function(data, textStatus, jqXHR) {
656
            // create changes_since string if necessary
657
            if (jqXHR.getResponseHeader('Date') != null){
658
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
659
                networks_changes_since = ISODateString(changes_since_date);
660
            }
661

    
662
            if (jqXHR.status == 200 || jqXHR.status == 203) {
663
                try {
664
                    networks = data.networks.values;
665
                    update_networks_view(servers_data, data);
666
                } catch(err) {
667
                    ajax_error(-507, "UI Error", 'Update network names', err);
668
                }
669
            } else if (jqXHR.status == 304) {
670
                    update_networks_view(servers_data);
671
            } else if (jqXHR.status != 304){
672
                try { console.info('update_network_names callback:' + jqXHR.status ) } catch(err) {}
673
                /*
674
                FIXME:  Here it should return the error, however Opera does not support 304.
675
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
676
                        304, which should be corrected (Bug #317).
677
                */
678
                //ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
679
                update_networks_view(servers_data);
680
            }
681
            return false;
682
        }
683
    });
684
    return false;
685
}
686

    
687
// get and show a list of available standard and custom images
688
function update_images() {
689
    $.ajax({
690
        url: API_URL + '/images/detail',
691
        type: "GET",
692
        //async: false,
693
        dataType: "json",
694
        timeout: TIMEOUT,
695
        error: function(jqXHR, textStatus, errorThrown) {
696
                    try {
697
                        ajax_error(jqXHR.status, undefined, 'Update Images', jqXHR.responseText);
698
                    } catch(err) {
699
                        ajax_error(-1, textStatus, 'Update Images', "NETWORK ERROR");
700
                    }
701
                },
702
        success: function(data, textStatus, jqXHR) {
703
            try {
704
                images = data.images.values;
705
                jQuery.parseJSON(data);
706
                update_wizard_images();
707
            } catch(err){
708
                ajax_error("NO_IMAGES");
709
            }
710
        }
711
    });
712
    return false;
713
}
714

    
715
function update_wizard_images() {
716
    if ($("ul#standard-images li").toArray().length + $("ul#custom-images li").toArray().length == 0) {
717
        $.each(images, function(i,image){
718
            var img = $('#image-template').clone().attr("id","img-"+image.id).fadeIn("slow");
719
            img.find("label").attr('for',"img-radio-" + image.id);
720
            img.find(".image-title").text(image.name);
721
            if (image.metadata) {
722
                if (image.metadata.values.description != undefined) {
723
                    img.find(".description").text(image.metadata.values.description);
724
                }
725
                if (image.metadata.values.size != undefined) {
726
                    img.find("#size").text(image.metadata.values.size);
727
                }
728
            }
729
            img.find("input.radio").attr('id',"img-radio-" + image.id);
730
            if (i==0) img.find("input.radio").attr("checked","checked");
731
            var image_logo = os_icon(image.metadata);
732
            img.find("img.image-logo").attr('src','static/icons/os/'+image_logo+'.png');
733
            if (image.metadata) {
734
                if (image.metadata.values.serverId != undefined) {
735
                    img.appendTo("ul#custom-images");
736
                } else {
737
                    img.appendTo("ul#standard-images");
738
                }
739
            } else {
740
                img.appendTo("ul#standard-images");
741
            }
742
        });
743
    }
744
}
745

    
746
function update_wizard_flavors(){
747
    // sliders for selecting VM flavor
748
    $("#cpu:range").rangeinput({min:0,
749
                               value:0,
750
                               step:1,
751
                               progress: true,
752
                               max:cpus.length-1});
753

    
754
    $("#storage:range").rangeinput({min:0,
755
                               value:0,
756
                               step:1,
757
                               progress: true,
758
                               max:disks.length-1});
759

    
760
    $("#ram:range").rangeinput({min:0,
761
                               value:0,
762
                               step:1,
763
                               progress: true,
764
                               max:ram.length-1});
765
    $("#small").click();
766

    
767
    // update the indicators when sliding
768
    $("#cpu:range").data().rangeinput.onSlide(function(event,value){
769
        $("#cpu-indicator")[0].value = cpus[Number(value)];
770
        $("#cpu-indicator").addClass('selectedrange');
771
    });
772
    $("#cpu:range").data().rangeinput.change(function(event,value){
773
        $("#cpu-indicator")[0].value = cpus[Number(value)];
774
        $("#custom").click();
775
        $("#cpu-indicator").removeClass('selectedrange');
776
    });
777
    $("#ram:range").data().rangeinput.onSlide(function(event,value){
778
        $("#ram-indicator")[0].value = ram[Number(value)];
779
        $("#ram-indicator").addClass('selectedrange');
780
    });
781
    $("#ram:range").data().rangeinput.change(function(event,value){
782
        $("#ram-indicator")[0].value = ram[Number(value)];
783
        $("#custom").click();
784
        $("#ram-indicator").removeClass('selectedrange');
785
    });
786
    $("#storage:range").data().rangeinput.onSlide(function(event,value){
787
        $("#storage-indicator")[0].value = disks[Number(value)];
788
        $("#storage-indicator").addClass('selectedrange');
789
    });
790
    $("#storage:range").data().rangeinput.change(function(event,value){
791
        $("#storage-indicator")[0].value = disks[Number(value)];
792
        $("#custom").click();
793
        $("#storage-indicator").removeClass('selectedrange');
794
    });
795
}
796

    
797
Array.prototype.unique = function () {
798
    var r = new Array();
799
    o:for(var i = 0, n = this.length; i < n; i++)
800
    {
801
        for(var x = 0, y = r.length; x < y; x++)
802
        {
803
            if(r[x]==this[i])
804
            {
805
                continue o;
806
            }
807
        }
808
        r[r.length] = this[i];
809
    }
810
    return r;
811
}
812

    
813
// get and configure flavor selection
814
function update_flavors() {
815
    $.ajax({
816
        url: API_URL + '/flavors/detail',
817
        type: "GET",
818
        //async: false,
819
        dataType: "json",
820
        timeout: TIMEOUT,
821
        error: function(jqXHR, textStatus, errorThrown) {
822
            try {
823
                ajax_error(jqXHR.status, undefined, 'Update Flavors', jqXHR.responseText);
824
            } catch (err) {
825
                ajax_error(-1, textStatus, "Update Flavors", "NETWORK ERROR");
826
            }
827
            // start updating vm list
828
            update_vms(UPDATE_INTERVAL);
829
        },
830
        success: function(data, textStatus, jqXHR) {
831

    
832
            try {
833
                flavors = data.flavors.values;
834
                jQuery.parseJSON(data);
835
                $.each(flavors, function(i, flavor) {
836
                    cpus[i] = flavor['cpu'];
837
                    disks[i] = flavor['disk'];
838
                    ram[i] = flavor['ram'];
839
                });
840
                cpus = cpus.unique();
841
                disks = disks.unique();
842
                ram = ram.unique();
843
                update_wizard_flavors();
844
            } catch(err){
845
                ajax_error("NO_FLAVORS");
846
            }
847
            // start updating vm list
848
            update_vms(UPDATE_INTERVAL);
849
        }
850
    });
851
    return false;
852
}
853

    
854
// return flavorRef from cpu, disk, ram values
855
function identify_flavor(cpu, disk, ram){
856
    for (i=0;i<flavors.length;i++){
857
        if (flavors[i]['cpu'] == cpu && flavors[i]['disk']==disk && flavors[i]['ram']==ram) {
858
            return flavors[i]['id']
859
        }
860
    }
861
    return 0;
862
}
863

    
864
// return image entry from imageRef
865
function get_image(imageRef) {
866
    for (i=0;i<images.length;i++){
867
        if (images[i]['id'] == imageRef) {
868
            return images[i];
869
        }
870
    }
871
    return 0;
872
}
873

    
874
// return machine entry from serverID
875
function get_machine(serverID) {
876
    for (i=0;i<servers.length;i++){
877
        if (servers[i]['id'] == serverID) {
878
            return servers[i];
879
        }
880
    }
881
    return 0;
882
}
883

    
884
// update the actions in icon view, per server
885
function update_iconview_actions(serverID, server_status) {
886
    if ($.cookie("view")=='2') {
887
        // remove .disable from all actions to begin with
888
        $('#machinesview-single #' + serverID + ' div.single-action').show();
889
        // decide which actions should be disabled
890
        for (current_action in actions) {
891
            if (actions[current_action].indexOf(server_status) == -1 ) {
892
                $('#machinesview-single #' + serverID + ' div.action-' + current_action).hide();
893
            }
894
        }
895
    } else {
896
        // remove .disable from all actions to begin with
897
        $('#machinesview-icon.standard #' + serverID + ' div.actions').find('a').removeClass('disabled');
898
        // decide which actions should be disabled
899
        for (current_action in actions) {
900
            if (actions[current_action].indexOf(server_status) == -1 ) {
901
                $('#machinesview-icon.standard #' + serverID + ' a.action-' + current_action).addClass('disabled');
902
            }
903
        }
904
    }
905
}
906

    
907
// update the actions in list view
908
function update_listview_actions() {
909
    var states = [];
910
    var on = [];
911
    var checked = $("table.list-machines tbody input[type='checkbox']:checked");
912
    // disable all actions to begin with
913
    $('#machinesview .list div.actions').children().removeClass('enabled');
914

    
915
    // are there multiple machines selected?
916
    if (checked.length>1)
917
        states[0] = 'multiple';
918

    
919
    // check the states of selected machines
920
    checked.each(function(i,checkbox) {
921
        states[states.length] = checkbox.className;
922
        var ip = $("#" + checkbox.id.replace('input-','') + ".ip span.public").text();
923
        if (ip.replace('undefined','').length)
924
            states[states.length] = 'network';
925
    });
926

    
927
    // decide which actions should be enabled
928
    for (a in actions) {
929
        var enabled = false;
930
        for (var s =0; s<states.length; s++) {
931
            if (actions[a].indexOf(states[s]) != -1 ) {
932
                enabled = true;
933
            } else {
934
                enabled = false;
935
                break;
936
            }
937
        }
938
        if (enabled)
939
            on[on.length]=a;
940
    }
941
    // enable those actions
942
    for (action in on) {
943
        $("#action-" + on[action]).addClass('enabled');
944
    }
945
}
946

    
947
//create server action
948
function create_vm(machineName, imageRef, flavorRef){
949
    var image_logo = os_icon(get_image(imageRef).metadata);
950
    var uri = API_URL + '/servers';
951
    var payload = {
952
        "server": {
953
            "name": machineName,
954
            "imageRef": imageRef,
955
            "flavorRef" : flavorRef,
956
            "metadata" : {
957
                "OS" : image_logo
958
            }
959
        }
960
    };
961

    
962
    $.ajax({
963
    url: uri,
964
    type: "POST",
965
    contentType: "application/json",
966
    dataType: "json",
967
    data: JSON.stringify(payload),
968
    timeout: TIMEOUT,
969
    error: function(jqXHR, textStatus, errorThrown) {
970
                // close wizard and show error box
971
                $('#machines-pane a#create').data('overlay').close();
972
                    try {
973
                        ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
974
                    } catch(err) {
975
                        ajax_error(-1, textStatus, 'Create VM', "NETWORK ERROR");
976
                    }
977
           },
978
    success: function(data, textStatus, jqXHR) {
979
                if ( jqXHR.status == '202') {
980
                    ajax_success("CREATE_VM_SUCCESS", data.server.adminPass);
981
                } else {
982
                    // close wizard and show error box
983
                    $('#machines-pane a#create').data('overlay').close();
984
                    ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
985
                }
986
            }
987
    });
988
}
989

    
990
// reboot action
991
function reboot(serverIDs){
992
    if (!serverIDs.length){
993
        //ajax_success('DEFAULT');
994
        return false;
995
    }
996
    // ajax post reboot call
997
    var payload = {
998
        "reboot": {"type" : "HARD"}
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
                    // in machine views
1012
                    if ( $.cookie("pane") == 0) {
1013
                        try {
1014
                            display_failure(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
1015
                        } catch (err) {
1016
                            display_failure(0, serverID, 'Reboot', jqXHR.responseText);
1017
                        }
1018
                    }
1019
                    // in network view
1020
                    else {
1021
                        try {
1022
                            display_reboot_failure(jqXHR.status, serverID, jqXHR.responseText);
1023
                        } catch (err) {
1024
                            display_reboot_failure(0, serverID, jqXHR.responseText);
1025
                        }
1026
                    }
1027
                },
1028
        success: function(data, textStatus, jqXHR) {
1029
                    if ( jqXHR.status == '202') {
1030
                        try {
1031
                            console.info('rebooted ' + serverID);
1032
                        } catch(err) {}
1033
                        // indicate that the action succeeded
1034
                        // in machine views
1035
                        if ( $.cookie("pane") == 0) {
1036
                            display_success(serverID);
1037
                        }
1038
                        // in network view
1039
                        else {
1040
                            display_reboot_success(serverID);
1041
                        }
1042
                        // continue with the rest of the servers
1043
                        reboot(serverIDs);
1044
                    } else {
1045
                        ajax_error(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
1046
                    }
1047
                }
1048
    });
1049
    return false;
1050
}
1051

    
1052
// shutdown action
1053
function shutdown(serverIDs) {
1054
    if (!serverIDs.length){
1055
        //ajax_success('DEFAULT');
1056
        return false;
1057
    }
1058
    // ajax post shutdown call
1059
    var payload = {
1060
        "shutdown": {}
1061
    };
1062

    
1063
    var serverID = serverIDs.pop();
1064

    
1065
    $.ajax({
1066
        url: API_URL + '/servers/' + serverID + '/action',
1067
        type: "POST",
1068
        contentType: "application/json",
1069
        dataType: "json",
1070
        data: JSON.stringify(payload),
1071
        timeout: TIMEOUT,
1072
        error: function(jqXHR, textStatus, errorThrown) {
1073
                    try {
1074
                        display_failure(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1075
                    } catch(err) {
1076
                        display_failure(0, serverID, 'Shutdown', jqXHR.responseText);
1077
                    }
1078
                },
1079
        success: function(data, textStatus, jqXHR) {
1080
                    if ( jqXHR.status == '202') {
1081
                        try {
1082
                            console.info('suspended ' + serverID);
1083
                        } catch(err) {}
1084
                        // indicate that the action succeeded
1085
                        display_success(serverID);
1086
                        // continue with the rest of the servers
1087
                        shutdown(serverIDs);
1088
                    } else {
1089
                        ajax_error(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1090
                    }
1091
                }
1092
    });
1093
    return false;
1094
}
1095

    
1096
// destroy action
1097
function destroy(serverIDs) {
1098
    if (!serverIDs.length){
1099
        //ajax_success('DEFAULT');
1100
        return false;
1101
    }
1102
    // ajax post destroy call can have an empty request body
1103
    var payload = {};
1104

    
1105
    var serverID = serverIDs.pop();
1106

    
1107
    $.ajax({
1108
        url: API_URL + '/servers/' + serverID,
1109
        type: "DELETE",
1110
        contentType: "application/json",
1111
        dataType: "json",
1112
        data: JSON.stringify(payload),
1113
        timeout: TIMEOUT,
1114
        error: function(jqXHR, textStatus, errorThrown) {
1115
                    try {
1116
                        display_failure(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1117
                    } catch(err) {
1118
                        display_failure(0, serverID, 'Destroy', jqXHR.responseText);
1119
                    }
1120
                },
1121
        success: function(data, textStatus, jqXHR) {
1122
                    if ( jqXHR.status == '204') {
1123
                        try {
1124
                            console.info('destroyed ' + serverID);
1125
                        } catch (err) {}
1126

    
1127
                        // update status on local storage object
1128
                        vm = get_machine(serverID);
1129
                        vm.status = "DESTROY";
1130

    
1131
                        // indicate that the action succeeded
1132
                        display_success(serverID);
1133
                        // continue with the rest of the servers
1134
                        destroy(serverIDs);
1135
                    } else {
1136
                        ajax_error(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1137
                    }
1138
                }
1139
    });
1140
    return false;
1141
}
1142

    
1143
// start action
1144
function start(serverIDs){
1145
    if (!serverIDs.length){
1146
        //ajax_success('DEFAULT');
1147
        return false;
1148
    }
1149
    // ajax post start call
1150
    var payload = {
1151
        "start": {}
1152
    };
1153

    
1154
    var serverID = serverIDs.pop();
1155

    
1156
    $.ajax({
1157
        url: API_URL + '/servers/' + serverID + '/action',
1158
        type: "POST",
1159
        contentType: "application/json",
1160
        dataType: "json",
1161
        data: JSON.stringify(payload),
1162
        timeout: TIMEOUT,
1163
        error: function(jqXHR, textStatus, errorThrown) {
1164
                    try {
1165
                        display_failure(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1166
                    } catch(err) {
1167
                        display_failure(0, serverID, 'Start', jqXHR.responseText);
1168
                    }
1169
                },
1170
        success: function(data, textStatus, jqXHR) {
1171
                    if ( jqXHR.status == '202') {
1172
                        try {
1173
                            console.info('started ' + serverID);
1174
                        } catch(err) {}
1175
                        // indicate that the action succeeded
1176
                        display_success(serverID);
1177
                        // continue with the rest of the servers
1178
                        start(serverIDs);
1179
                    } else {
1180
                        ajax_error(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1181
                    }
1182
                }
1183
    });
1184
    return false;
1185
}
1186

    
1187
// Show VNC console
1188
function vnc_attachment(host, port, password) {
1189
    // FIXME: Must be made into parameters, in settings.py
1190
    //vnc = open("", "displayWindow",
1191
    //    "status=yes,toolbar=yes,menubar=yes");
1192
    vd = document.open("application/x-vnc");
1193

    
1194
    vd.writeln("[connection]");
1195
    vd.writeln("host=" + host);
1196
    vd.writeln("port=" + port);
1197
    vd.writeln("password=" + password);
1198

    
1199
    vd.close();
1200
}
1201

    
1202
// Show VNC console
1203
function show_vnc_console(serverID, serverName, serverIP, host, port, password) {
1204
    var params_url = '?machine=' + serverName + '&host_ip=' + serverIP.v4 + '&host_ip_v6=' + serverIP.v6 + '&host=' + host + '&port=' + port + '&password=' + password;
1205
    var params_window = 'scrollbars=no,' +
1206
                        'menubar=no,' +
1207
                        'toolbar=no,' +
1208
                        'status=no,' +
1209
                        'top=0,' +
1210
                        'left=0,' +
1211
                        'height=' + screen.height + ',' +
1212
                        'width=' + screen.width + ',' +
1213
                        'fullscreen=yes';
1214
    
1215
    var url = 'machines/console' + params_url;
1216
    window.open(url, 'formresult' + serverID, params_window);
1217

    
1218
    // Restore os icon in list view
1219
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1220
    osIcon.attr('src',osIcon.attr('os'));
1221
    return false;
1222
}
1223

    
1224
// console action
1225
function open_console(serverIDs){
1226
    if (!serverIDs.length){
1227
        //ajax_success('DEFAULT');
1228
        return false;
1229
    }
1230
    // ajax post start call
1231
    var payload = {
1232
        "console": {"type": "vnc"}
1233
    };
1234

    
1235
    var serverID = serverIDs.pop();
1236

    
1237
    var machine = get_machine(serverID);
1238
    var serverName = machine.name;
1239
    try {
1240
        var serverIP = {};
1241
        serverIP.v4 = machine.addresses.values[0].values[0].addr;
1242
        serverIP.v6 = machine.addresses.values[0].values[1].addr;
1243
    } catch(err) { var serverIP = 'undefined'; }
1244

    
1245
    $.ajax({
1246
        url: API_URL + '/servers/' + serverID + '/action',
1247
        type: "POST",
1248
        contentType: "application/json",
1249
        dataType: "json",
1250
        data: JSON.stringify(payload),
1251
        timeout: TIMEOUT,
1252
        error: function(jqXHR, textStatus, errorThrown) {
1253
                    try {
1254
                        display_failure(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1255
                    } catch(err) {
1256
                        display_failure(0, serverID, 'Console', jqXHR.responseText);
1257
                    }
1258
                },
1259
        success: function(data, textStatus, jqXHR) {
1260
                    if ( jqXHR.status == '200') {
1261
                        try {
1262
                            console.info('got_console ' + serverID);
1263
                        } catch(err) {}
1264
                        // indicate that the action succeeded
1265
                        // show_vnc_console(serverID, serverName, serverIP,
1266
                        // data.console.host,data.console.port,data.console.password);
1267
                        show_vnc_console(serverID, serverName, serverIP,
1268
                                         data.console.host,data.console.port,data.console.password);
1269
                        display_success(serverID);
1270
                        // hide spinner
1271
                        $('#' + serverID + ' .spinner').hide();
1272
                        // continue with the rest of the servers
1273
                        open_console(serverIDs);
1274
                    } else {
1275
                        ajax_error(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1276
                    }
1277
                }
1278
    });
1279
    return false;
1280
}
1281

    
1282
function vm_has_address(vmId) {
1283
    var vm = get_machine(vmId);
1284

    
1285
    if (!vm) return false;
1286

    
1287
    try {
1288
        var ip = vm.addresses.values[0].values[0].addr;
1289
    } catch (err) {
1290
        return false;
1291
    }
1292
    return ip;
1293
}
1294

    
1295
// connect to machine action
1296
function machine_connect(serverIDs){
1297
    if (!serverIDs.length){
1298
        //ajax_success('DEFAULT');
1299
        return false;
1300
    }
1301
    
1302
    // prefer metadata values for specific options (username, domain)
1303
    var username_meta_key = 'user';
1304
    var domain_meta_key = "domain";
1305

    
1306
    var serverID = serverIDs.pop();
1307
    var machine = get_machine(serverID);
1308
    var serverName = machine.name;
1309
    
1310
    try {
1311
        var serverIP = machine.addresses.values[0].values[0].addr;
1312
    } catch (err) { var serverIP = 'undefined'; }
1313

    
1314
    try {
1315
        var os = os_icon(machine.metadata);
1316
    } catch (err) { var os = 'undefined'; }
1317

    
1318
    var username = "";
1319
    try {
1320
        username = machine.metadata.values[username_meta_key];
1321
    } catch (err) { username = undefined }
1322

    
1323
    var domain = "";
1324
    try {
1325
        domain = machine.metadata.values[domain_meta_key];
1326
    } catch (erro) { domain = undefined }
1327

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

    
1330
    if (username) {
1331
        params_url += "&username=" + username;
1332
    }
1333

    
1334
    if (domain) {
1335
        params_url += "&domain=" + domain;
1336
    }
1337
    
1338
    //if ($.client.os == "Windows" && os == "windows") {
1339
        //// request rdp file
1340
        //window.open('machines/connect' + params_url + "&rdp=1");
1341
        //return;
1342
    //}
1343
    
1344
    // FIXME: I18n ???
1345
    var title = 'Connect to: ' + '<span class="machine-title"><img src="static/icons/machines/small/'+os+'-on.png" /> ' + fix_server_name(serverName) + '</span>';
1346
    
1347
    // open msg box and fill it with json data retrieved from connect machine view
1348
    try {
1349
        // open msg box
1350
        msg_box({
1351
            title:title, 
1352
            fixed: true,
1353
            content:'loading...',
1354
            extra:'', 'ajax':'machines/connect' + params_url,
1355
            parse_data:function(data){
1356
                var box_content = "<a href='"+data.link.url+"'>"+data.link.title+"</a>";
1357
                if (!data.link.url) {
1358
                    box_content = "<span class='cmd'>"+data.link.title+"</span>";
1359
                }
1360
                data.title = false;
1361
                data.content = data.info;
1362
                data.extra = box_content;
1363
                return data;
1364
            }
1365
        });
1366
    } catch (error) {
1367
        // if msg box fails fallback redirecting the user to the connect url
1368
        window.open('machines/connect' + params_url);
1369
    }
1370

    
1371

    
1372
    // Restore os icon in list view
1373
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1374
    osIcon.attr('src',osIcon.attr('os'));
1375

    
1376
    return false;
1377
}
1378

    
1379

    
1380
// rename server
1381
function rename(serverID, serverName){
1382
    if (!serverID.length){
1383
        //ajax_success('DEFAULT');
1384
        return false;
1385
    }
1386
    // ajax post rename call
1387
    var payload = {
1388
        "server": {"name": serverName}
1389
    };
1390

    
1391
    $.ajax({
1392
        url: API_URL + '/servers/' + serverID,
1393
        type: "PUT",
1394
        contentType: "application/json",
1395
        dataType: "json",
1396
        data: JSON.stringify(payload),
1397
        timeout: TIMEOUT,
1398
        error: function(jqXHR, textStatus, errorThrown) {
1399
                    try {
1400
                        display_failure(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1401
                    } catch(err) {
1402
                        display_failure(0, serverID, 'Rename', jqXHR.responseText);
1403
                    }
1404
                },
1405
        success: function(data, textStatus, jqXHR) {
1406
                    if ( jqXHR.status == '204' || jqXHR.status == '1223') {
1407
                        try {
1408
                            console.info('renamed ' + serverID);
1409
                        } catch(err) {}
1410
                        // indicate that the action succeeded
1411
                        display_success(serverID);
1412
                    } else {
1413
                        ajax_error(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1414
                    }
1415
                }
1416
    });
1417
    return false;
1418
}
1419

    
1420
// get server metadata
1421
function get_metadata(serverID, keys_only) {
1422
    $.ajax({
1423
        url: API_URL + '/servers/' + serverID + '/meta',
1424
        cache: false,
1425
        type: "GET",
1426
        //async: false,
1427
        dataType: "json",
1428
        timeout: TIMEOUT,
1429
        error: function(jqXHR, textStatus, errorThrown) {
1430
            try {
1431
                // close wizard and show error box
1432
                $("a#metadata-scrollable").data('overlay').close();
1433
                ajax_error(jqXHR.status, undefined, 'Get metadata', jqXHR.responseText);
1434
            } catch (err) {
1435
                ajax_error(-1, textStatus, 'Get metadata', "NETWORK ERROR");
1436
            }
1437
        },
1438
        success: function(data, textStatus, jqXHR) {
1439
            // to list the new results in the edit dialog
1440
            if (keys_only) {
1441
                list_metadata_keys(serverID, data.metadata.values);
1442
            } else {
1443
                list_metadata(data);
1444
                list_metadata_keys(serverID, data.metadata.values);
1445
            }
1446
            //hide spinner
1447
            $('#metadata-wizard .large-spinner').hide();
1448
        }
1449
    });
1450
    return false;
1451
}
1452

    
1453
// delete metadata key-value pair
1454
function delete_metadata(serverID, meta_key) {
1455
    $.ajax({
1456
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1457
        type: "DELETE",
1458
        //async: false,
1459
        dataType: "json",
1460
        timeout: TIMEOUT,
1461
        error: function(jqXHR, textStatus, errorThrown) {
1462
            try {
1463
                // close wizard and show error box
1464
                $("a#metadata-scrollable").data('overlay').close();
1465
                ajax_error(jqXHR.status, undefined, 'Delete metadata', jqXHR.responseText);
1466
            } catch (err) {
1467
                ajax_error(-1, textStatus, 'Delete metadata', "NETWORK ERROR");
1468
            }
1469
        },
1470
        success: function(data, textStatus, jqXHR) {
1471
                    // success: Do nothing, the UI is already updated
1472
        }
1473
    });
1474
    return false;
1475
}
1476

    
1477
// add metadata key-value pair
1478
function update_metadata(serverID, meta_key, meta_value) {
1479
    var payload = {
1480
        "meta": {
1481
        }
1482
    };
1483
    payload["meta"][meta_key] = meta_value;
1484

    
1485
    $.ajax({
1486
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1487
        type: "PUT",
1488
        contentType: "application/json",
1489
        dataType: "json",
1490
        data: JSON.stringify(payload),
1491
        timeout: TIMEOUT,
1492
        error: function(jqXHR, textStatus, errorThrown) {
1493
            try {
1494
                // close wizard and show error box
1495
                $("a#metadata-scrollable").data('overlay').close();
1496
                ajax_error(jqXHR.status, undefined, 'Add metadata', jqXHR.responseText);
1497
            } catch (err) {
1498
                ajax_error(-1, textStatus, 'Add metadata', "NETWORK ERROR");
1499
            }
1500
        },
1501
        success: function(data, textStatus, jqXHR) {
1502
            // success: Update icons if meta key is OS
1503
            if (meta_key == "OS") {
1504
                $("#metadata-wizard .machine-icon").attr("src","static/icons/machines/small/" + os_icon_from_value(meta_value) + '-' + $("#metadata-wizard div#on-off").text() + '.png');
1505
                var machine_icon = $("#machinesview-icon").find("div#" + serverID);
1506
                var machine_single = $("#machinesview-single").find("div#" + serverID);
1507

    
1508
                var os = os_icon_from_value(meta_value);
1509
                var state = $("#metadata-wizard div#on-off").text()
1510
                var state_single = $(".state", machine_single).hasClass("terminated-state") ? "off" : "on";
1511

    
1512
                set_machine_os_image(machine_icon, "icon", state, os);
1513
                set_machine_os_image(machine_single, "single", state_single, os);
1514
            }
1515
        }
1516
    });
1517
    return false;
1518
}
1519

    
1520
// get stats
1521
function get_server_stats(serverID) {
1522
    
1523
    // do not update stats if machine in build state
1524
    var vm = get_machine(serverID);
1525
    if (vm.status == "BUILD" && vm.stats_timeout) {
1526
        els = get_current_view_stats_elements(vm.id);
1527
        els.cpu.img.hide();
1528
        els.net.img.hide();
1529

    
1530
        els.cpu.busy.show();
1531
        els.net.busy.show();
1532
        return;
1533
    }
1534

    
1535
    $.ajax({
1536
        url: API_URL + '/servers/' + serverID + '/stats',
1537
        cache: false,
1538
        type: "GET",
1539
        //async: false,
1540
        dataType: "json",
1541
        timeout: TIMEOUT,
1542
        error: function(jqXHR, textStatus, errorThrown) {
1543
            try {
1544
                ajax_error(jqXHR.status, undefined, 'Get server stats', jqXHR.responseText);
1545
            } catch(err) {
1546
                ajax_error(-1, textStatus, 'Get server stats', "NETWORK ERROR");
1547
            }
1548
        },
1549
        success: function(data, textStatus, jqXHR) {
1550
            update_machine_stats(serverID, data);
1551
        },
1552

    
1553
        // pass server id to ajax settings
1554
        serverID: serverID
1555
    });
1556
    return false;
1557
}
1558

    
1559
// set timeout function to update machine stats
1560
function set_stats_update_handler(vm_id, interval, clear) {
1561
    var vm = get_machine(vm_id);
1562

    
1563
    if (clear) {
1564
        window.clearInterval(vm.stats_timeout);
1565
        vm.stats_timeout = false;
1566
        return;
1567
    }
1568
    
1569
    if (!vm.stats_timeout) {
1570
        vm.stats_timeout = window.setInterval(function(){
1571
            get_server_stats(vm_id);
1572
        }, interval * 1000);
1573
    }
1574
}
1575

    
1576
// update machine stats
1577
// call set_stats_update_handler if machine stats are visible
1578
// to reupdate the stats (based on api interval)
1579
function update_machine_stats(vm_id, data) {
1580
    var els = get_current_view_stats_elements(vm_id);
1581
    var from_error = false;
1582
    var vm = get_machine(vm_id);    
1583
    var clear = false;
1584

    
1585
    // api error
1586
    if (!data) {
1587
        from_error = true;
1588
    }
1589

    
1590
    // hide helpers
1591
    function hide_imgs(els) {
1592
        els.cpu.img.hide();
1593
        els.net.img.hide();
1594
    }
1595

    
1596
    function hide_busy(els) {
1597
        els.cpu.busy.hide();
1598
        els.net.busy.hide();
1599
    }
1600

    
1601
    function hide_errors(els) {
1602
        els.cpu.error.hide();
1603
        els.net.error.hide();
1604
    }
1605

    
1606
    // apply logic
1607
    if (from_error) {
1608
        // api call returned error show error messages
1609
        clear = true;
1610
    } else {
1611
        // no need to show stats while machine in building state
1612
        if (vm.status == "BUILD") {
1613
            hide_imgs(els);
1614
            hide_errors(els);
1615
            els.cpu.busy.show();
1616
            els.net.busy.show();
1617
        } else {
1618
            hide_busy(els);
1619

    
1620
            // update stats, decide for series or bar image
1621
            // based on img class
1622
            if (els.cpu.img.hasClass("series")) {
1623
                els.cpu.img.attr("src", data.stats.cpuTimeSeries);
1624
            } else {
1625
                els.cpu.img.attr("src", data.stats.cpuBar);
1626
            }
1627

    
1628
            if (els.net.img.hasClass("series")) {
1629
                els.net.img.attr("src", data.stats.netTimeSeries);
1630
            } else {
1631
                els.net.img.attr("src", data.stats.netBar);
1632
            }
1633
        }
1634
    }
1635

    
1636
    // stats container is hidden
1637
    // do not update the stats
1638
    if (els.cont.length == 0 || !els.cont.is(":visible")) {
1639
        clear = true;
1640
    }
1641
    
1642
    // set timeout to call the stats update
1643
    set_stats_update_handler(vm_id, data.stats.refresh, clear);
1644
}
1645

    
1646

    
1647
// get stats elements based on current view
1648
function get_current_view_stats_elements(vm_id) {
1649
        // in icon view
1650
        if ( $.cookie('view') == 0 ) {
1651
            vm_el = $("#" + vm_id);
1652
            return {
1653
                'cont': vm_el.find('.vm-stats'),
1654
                'cpu': {
1655
                    'img': vm_el.find(' img.cpu'), 
1656
                    'busy': vm_el.find('.cpu-cont .stat-busy'),
1657
                    'error': vm_el.find('.cpu-cont .stat-error')
1658
                },
1659
                'net': { 
1660
                    'img': vm_el.find('img.net'),
1661
                    'busy': vm_el.find('.net-cont .stat-busy'),
1662
                    'error': vm_el.find('.net-cont .stat-error')
1663
                }
1664
            }
1665
        }
1666
        // in single view
1667
        else if ( $.cookie('view') == 2 ) {
1668
            vm_el = $("#" + vm_id);
1669
            return {
1670
                'cont': vm_el.find('.lower'),
1671
                'cpu': {
1672
                    'img': vm_el.find('div.cpu-graph img.stats'), 
1673
                    'busy': vm_el.find('div.cpu-graph img.stat-busy'),
1674
                    'error': vm_el.find('div.cpu-graph .stat-error')
1675
                },
1676
                'net': { 
1677
                    'img': vm_el.find('div.network-graph img.stats'),
1678
                    'busy': vm_el.find('div.network-graph img.stat-busy'),
1679
                    'error': vm_el.find('div.network-graph .stat-error')
1680
                }
1681
            }
1682
        }
1683
}
1684

    
1685
// create network
1686
function create_network(networkName){
1687
    // ajax post start call
1688
    var payload = {
1689
        "network": { "name": networkName }
1690
    };
1691

    
1692
    $.ajax({
1693
        url: API_URL + '/networks',
1694
        type: "POST",
1695
        contentType: "application/json",
1696
        dataType: "json",
1697
        data: JSON.stringify(payload),
1698
        timeout: TIMEOUT,
1699
        error: function(jqXHR, textStatus, errorThrown) {
1700
            try {
1701
                // close wizard and show error box
1702
                $("a#networkscreate").overlay().close();
1703
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1704
            } catch (err) {
1705
                ajax_error(-1, textStatus, 'Create network', "NETWORK ERROR");
1706
            }
1707
        },
1708
        success: function(data, textStatus, jqXHR) {
1709
            if ( jqXHR.status == '202') {
1710
                try {
1711
                    console.info('created network ' + networkName);
1712
                } catch(err) {}
1713
                /*
1714
                On success of this call nothing happens.
1715
                When the UI gets the first update containing the created server,
1716
                the creation wizard is closed and the new network is inserted
1717
                to the DOM. This is done in update_networks_view()
1718
                */
1719
            } else {
1720
                // close wizard and show error box
1721
                $("a#networkscreate").overlay().close();
1722
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1723
            }
1724
        }
1725
    });
1726
    return false;
1727
}
1728

    
1729
// rename network
1730
function rename_network(networkID, networkName){
1731
    if (!networkID.length){
1732
        //ajax_success('DEFAULT');
1733
        return false;
1734
    }
1735
    // prepare payload
1736
    var payload = {
1737
        "network": {"name": networkName}
1738
    };
1739
    // ajax call
1740
    $.ajax({
1741
        url: API_URL + '/networks/' + networkID,
1742
        type: "PUT",
1743
        contentType: "application/json",
1744
        dataType: "json",
1745
        data: JSON.stringify(payload),
1746
        timeout: TIMEOUT,
1747
        error: function(jqXHR, textStatus, errorThrown) {
1748
            try {
1749
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1750
            } catch (err) {
1751
                ajax_error(-1, textStatus, 'Rename network', "NETWORK ERROR");
1752
            }
1753
        },
1754
        success: function(data, textStatus, jqXHR) {
1755
            if ( jqXHR.status == '204') {
1756
                try {
1757
                    console.info('renamed network' + networkID);
1758
                } catch(err) {}
1759
            } else {
1760
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1761
            }
1762
        }
1763
    });
1764
    return false;
1765
}
1766

    
1767
function delete_network(networkIDs){
1768
    if (!networkIDs.length){
1769
        //ajax_success('DEFAULT');
1770
        return false;
1771
    }
1772
    // get a network
1773
    var networkID = networkIDs.pop();
1774
    // ajax post destroy call can have an empty request body
1775
    var payload = {};
1776
    // ajax call
1777
    $.ajax({
1778
        url: API_URL + '/networks/' + networkID,
1779
        type: "DELETE",
1780
        contentType: "application/json",
1781
        dataType: "json",
1782
        data: JSON.stringify(payload),
1783
        timeout: TIMEOUT,
1784
        error: function(jqXHR, textStatus, errorThrown) {
1785
            try {
1786
                display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1787
            } catch (err) {
1788
                display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1789
            }
1790
        },
1791
        success: function(data, textStatus, jqXHR) {
1792
            if ( jqXHR.status == '204') {
1793
                try {
1794
                    console.info('deleted network ' + networkID);
1795
                } catch(err) {}
1796
                // continue with the rest of the servers
1797
                delete_network(networkIDs);
1798
            } else {
1799
                try {
1800
                    display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1801
                } catch (err) {
1802
                    display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1803
                }
1804
            }
1805
        }
1806
    });
1807
    return false;
1808
}
1809

    
1810
function add_server_to_network(networkID, serverIDs, serverNames, serverStates) {
1811
    if (!serverIDs.length){
1812
        // close the overlay when all the calls are made
1813
        $("a#add-machines-overlay").overlay().close();
1814
        return false;
1815
    }
1816
    // get a server
1817
    var serverID = serverIDs.pop();
1818
    var serverName = serverNames.pop();
1819
    var serverState = serverStates.pop();
1820
    // prepare payload
1821
    var payload = {
1822
            "add": { "serverRef": serverID }
1823
        };
1824
    // prepare ajax call
1825
    $.ajax({
1826
        url: API_URL + '/networks/' + networkID + '/action',
1827
        type: "POST",
1828
        contentType: "application/json",
1829
        dataType: "json",
1830
        data: JSON.stringify(payload),
1831
        timeout: TIMEOUT,
1832
        error: function(jqXHR, textStatus, errorThrown) {
1833
            try {
1834
                // close wizard and show error box
1835
                $("a#add-machines-overlay").data('overlay').close();
1836
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1837
            } catch (err) {
1838
                ajax_error(-1, textStatus, 'Add server to network', "NETWORK ERROR");
1839
            }
1840
        },
1841
        success: function(data, textStatus, jqXHR) {
1842
            if ( jqXHR.status == '202') {
1843
                try {
1844
                    console.info('added server ' + serverID + ' to network ' + networkID);
1845
                } catch(err) {}
1846
                // toggle the reboot dialog
1847
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1848
                // continue with the rest of the servers
1849
                add_server_to_network(networkID, serverIDs, serverNames, serverStates);
1850
            } else {
1851
                // close wizard and show error box
1852
                $("a#add-machines-overlay").data('overlay').close();
1853
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1854
            }
1855
        }
1856
    });
1857
    return false;
1858
}
1859

    
1860
function remove_server_from_network(networkIDs, serverIDs, serverNames, serverStates) {
1861
    if (!networkIDs.length){
1862
        //ajax_success('DEFAULT');
1863
        return false;
1864
    }
1865
    // get a network and a server
1866
    var networkID = networkIDs.pop();
1867
    var serverID = serverIDs.pop();
1868
    var serverName = serverNames.pop();
1869
    var serverState = serverStates.pop();
1870
    // prepare payload
1871
    var payload = {
1872
            "remove": { "serverRef": serverID }
1873
        };
1874
    // prepare ajax call
1875
    $.ajax({
1876
        url: API_URL + '/networks/' + networkID + '/action',
1877
        type: "POST",
1878
        contentType: "application/json",
1879
        dataType: "json",
1880
        data: JSON.stringify(payload),
1881
        timeout: TIMEOUT,
1882
        error: function(jqXHR, textStatus, errorThrown) {
1883
            try {
1884
                ajax_error(jqXHR.status, undefined, 'Remove server from network', jqXHR.responseText);
1885
            } catch (err) {
1886
                ajax_error(-1, textStatus, 'Remove server from network', "NETWORK ERROR");
1887
            }
1888
        },
1889
        success: function(data, textStatus, jqXHR) {
1890
            if ( jqXHR.status == '202') {
1891
                try {
1892
                    console.info('deleted server ' + serverID + ' from network ' + networkID);
1893
                } catch(err) {}
1894
                // toggle the reboot dialog
1895
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1896
                // continue with the rest of the servers
1897
                remove_server_form_network(networkIDs, serverIDs, serverNames, serverStates);
1898
            } else {
1899
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1900
            }
1901
        }
1902
    });
1903
    return false;
1904
}
1905

    
1906
function set_firewall(networkID, serverID, profile) {
1907
    if (!networkID.length || !serverID.length || !profile.length){
1908
        return false;
1909
    }
1910
    // prepare payload
1911
    var payload = {
1912
            "firewallProfile": { "profile": profile }
1913
    };
1914

    
1915
    // prepare ajax call
1916
    $.ajax({
1917
        url: API_URL + '/servers/' + serverID + '/action',
1918
        type: "POST",
1919
        contentType: "application/json",
1920
        dataType: "json",
1921
        data: JSON.stringify(payload),
1922
        timeout: TIMEOUT,
1923
        error: function(jqXHR, textStatus, errorThrown) {
1924
            try {
1925
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1926
            } catch (err) {
1927
                ajax_error(-1, textStatus, 'Set firewall profile', "NETWORK ERROR");
1928
            }
1929
        },
1930
        success: function(data, textStatus, jqXHR) {
1931
            if ( jqXHR.status == '202') {
1932
                try {
1933
                    console.info('for server ' + serverID + ' set firewall profile to ' + profile);
1934
                } catch(err) {}
1935
                // toggle the reboot dialog
1936
                try {
1937

    
1938
                    var serverName = $('div#net-' + networkID + '-server-' + serverID + ' div.machine-name-div span.name').text();
1939
                    var serverState = $('div#net-' + networkID + '-server-' + serverID + ' img.logo').attr('src').split('-')[1];
1940
                    serverState = serverState.split('.')[0];
1941
                    display_reboot_dialog(networkID, serverID, serverName, serverState);
1942

    
1943
                    //remove progress gif and toggle the content
1944
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').html(VARIOUS["APPLY"]);
1945
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').attr("disabled", false);
1946
                    $('div#net-' + networkID + '-server-' + serverID + ' div.firewall-header').click();
1947

    
1948
                } catch (err) {
1949
                }
1950
                
1951
                // api call was made, set transition state to get reset 
1952
                // on the next machines update api call
1953
                var vm = get_machine(serverID)
1954
                vm.network_transition = "NETWORK_CHANGE";
1955
                show_machine_network_indicator(vm.id, 'pub');
1956
            } else {
1957
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1958
            }
1959
        }
1960
    });
1961
    return false;
1962
}
1963

    
1964
// show the welcome screen
1965
function showWelcome() {
1966
    $("#view-select").fadeOut("fast");
1967
    $("#emptymachineslist").fadeIn("fast");
1968
    $("#machinesview").hide();
1969
}
1970

    
1971
// hide the welcome screen
1972
function hideWelcome() {
1973
    $("#emptymachineslist").fadeOut("fast");
1974
    $("#view-select").fadeIn("fast");
1975
    $("div#view-select").show();
1976
    $("#machinesview").show();
1977
}
1978

    
1979
function log_server_status_change(server_entry, new_status) {
1980
    // firebug console logging
1981
    try {
1982
        if ($("#machinesview-single").length > 0) {
1983
            console.info(server_entry.find("div.machine-details div.name").text() +
1984
                        ' from ' + server_entry.find(".state-label").text() +
1985
                        ' to ' + STATUSES[new_status]);
1986
        } else {
1987
            console.info(server_entry.find("div.name span.name").text() +
1988
                        ' from ' + server_entry.find(".status").text().replace(TRANSITION_STATE_APPEND, "") +
1989
                        ' to ' + STATUSES[new_status]);
1990
        }
1991
    } catch(err) {}
1992
}
1993

    
1994
function get_flavor_params(flavorRef) {
1995
    var cpus, ram, disk;
1996
    if ( flavors.length > 0 ) {
1997
        var current_flavor = '';
1998
        for (i=0; i<flavors.length; i++) {
1999
            if (flavors[i]['id'] == flavorRef) {
2000
                current_flavor = flavors[i];
2001
            }
2002
        }
2003
        cpus = current_flavor['cpu'];
2004
        ram = current_flavor['ram'];
2005
        disk = current_flavor['disk'];
2006
    } else {
2007
        cpus = 'undefined';
2008
        ram = 'undefined';
2009
        disk = 'undefined';
2010
    }
2011
    return {'cpus': cpus, 'ram': ram, 'disk': disk};
2012
}
2013

    
2014
function get_image_params(imageRef) {
2015
    var image_name, image_size;
2016
    if ( images.length > 0 ) {
2017
        var current_image = '';
2018
        for (i=0; i<images.length; i++) {
2019
            if (images[i]['id'] == imageRef) {
2020
                current_image = images[i];
2021
            }
2022
        }
2023
        try {
2024
            image_name = current_image['name'];
2025
        } catch(err) { image_name = 'undefined'; }
2026
        try{
2027
            image_size = current_image['metadata']['values']['size'];
2028
        } catch(err) { image_size = 'undefined'; }
2029
    } else {
2030
        image_name = 'undefined';
2031
        image_size = 'undefined';
2032
    }
2033
    return {'name': image_name,'size': image_size};
2034
}
2035

    
2036
function get_public_ips(server) {
2037
    var ip4, ip6;
2038
    try {
2039
        if (server.addresses.values) {
2040
            $.each (server.addresses.values, function(i, value) {
2041
                if (value.id == 'public') {
2042
                    try {
2043
                        $.each (value.values, function(i, ip) {
2044
                            if (ip.version == '4') {
2045
                                ip4 = ip.addr;
2046
                            } else if (ip.version == '6') {
2047
                                ip6 = ip.addr;
2048
                            } else {
2049
                                ip4 = 'pending';
2050
                                ip6 = 'pending';
2051
                            }
2052
                        });
2053
                    } catch (err){
2054
                        try{console.info('Server ' + server.id + ' has invalid ips')}catch(err){};
2055
                        ip4 = 'pending';
2056
                        ip6 = 'pending';
2057
                    }
2058
                }
2059
            });
2060
        }
2061
    } catch (err) {
2062
        try{console.info('Server ' + server.id + ' has no network addresses')}catch(err){};
2063
        ip4 = 'pending';
2064
        ip6 = 'pending';
2065
    }
2066
    return {'ip4': ip4, 'ip6': ip6};
2067
}
2068

    
2069
function get_private_ips(server) {
2070

    
2071
}
2072

    
2073
function close_all_overlays() {
2074
        try {
2075
                $("a#networkscreate").overlay().close();
2076
        } catch(err) {}
2077
        try {
2078
                $('a#create').overlay().close();
2079
        } catch(err) {}
2080
        try {
2081
                $("a#add-machines-overlay").overlay().close();
2082
        } catch(err) {}
2083
        try {
2084
                $("a#metadata-scrollable").overlay().close();
2085
        } catch(err) {}
2086
        try {
2087
                $("a#msgbox").overlay().close();
2088
        } catch(err) {}
2089
        try {
2090
                $("a#feedbackbox").overlay().close();
2091
        } catch(err) {}
2092
}
2093

    
2094
// logout
2095
function user_session_logout() {
2096
    $.cookie("X-Auth-Token", null);
2097
    if (window.LOGOUT_REDIRECT !== undefined)
2098
    {
2099
        window.location = window.LOGOUT_REDIRECT;
2100
    } else {
2101
        window.location.reload();
2102
    }
2103
}
2104

    
2105
// action indicators
2106
function init_action_indicator_handlers(machines_view)
2107
{
2108
    // init once for each view
2109
    if (window.ACTION_ICON_HANDLERS == undefined)
2110
    {
2111
        window.ACTION_ICON_HANDLERS = {};
2112
    }
2113

    
2114
    if (machines_view in window.ACTION_ICON_HANDLERS)
2115
    {
2116
        return;
2117
    }
2118
    window.ACTION_ICON_HANDLERS[machines_view] = 1;
2119

    
2120
    if (machines_view == "list")
2121
    {
2122
        // totally different logic for list view
2123
        init_action_indicator_list_handlers();
2124
        return;
2125
    }
2126

    
2127
    function update_action_icon_indicators(force)
2128
    {
2129
        function show(el, action) {
2130
            $(".action-indicator", $(el)).attr("class", "action-indicator " + action);
2131
            $(".action-indicator", $(el)).show();
2132
        }
2133

    
2134
        function hide(el) {
2135
            $(".action-indicator", $(el)).hide();
2136
        }
2137

    
2138
        function get_pending_actions(el) {
2139
            return $(".confirm_single:visible", $(el));
2140
        }
2141

    
2142
        function other_indicators(el) {
2143
           return $("img.wave:visible, img.spinner:visible", $(el))
2144
        }
2145

    
2146
        $("div.machine:visible, div.single-container").each(function(index, el){
2147
            var el = $(el);
2148
            var pending = get_pending_actions(el);
2149
            var other = other_indicators(el);
2150
            var action = undefined;
2151
            var force_action = force;
2152
            var visible = $(el).css("display") == "block";
2153

    
2154
            if (force_action !==undefined && force_action.el !== el[0]) {
2155
                // force action for other vm
2156
                // skipping force action
2157
                force_action = undefined;
2158
            }
2159

    
2160
            if (force_action !==undefined && force_action.el === el[0]) {
2161
                action = force_action.action;
2162
            }
2163

    
2164
            if (other.length >= 1) {
2165
                return;
2166
            }
2167

    
2168
            if (pending.length >= 1 && force_action === undefined) {
2169
                action = $(pending.parent()).attr("class").replace("action-container","");
2170
            }
2171

    
2172
            if (action in {'console':''}) {
2173
                return;
2174
            }
2175

    
2176
            if (action !== undefined) {
2177
                show(el, action);
2178
            } else {
2179
                try {
2180
                    if (el.attr('id') == pending_actions[0][1])
2181
                    {
2182
                        return;
2183
                    }
2184
                } catch (err) {
2185
                }
2186
                hide(el);
2187
            }
2188

    
2189
        });
2190
    }
2191

    
2192
    // action indicators
2193
    $(".action-container").live('mouseover', function(evn) {
2194
        force_action = {'el': $(evn.currentTarget).parent().parent()[0], 'action':$(evn.currentTarget).attr("class").replace("action-container","")};
2195
        // single view case
2196
        if ($(force_action.el).attr("class") == "upper")
2197
        {
2198
            force_action.el = $(evn.currentTarget).parent().parent().parent()[0]
2199
        };
2200
        update_action_icon_indicators(force_action);
2201
    });
2202

    
2203
    $("img.spinner, img.wave").live('hide', function(){
2204
        update_action_icon_indicators();
2205
    });
2206
    // register events where icons should get updated
2207

    
2208
    // hide action indicator image on mouse out, spinner appear, wave appear
2209
    $(".action-container").live("mouseout", function(evn){
2210
        update_action_icon_indicators();
2211
    });
2212

    
2213
    $(".confirm_single").live("click", function(evn){
2214
        update_action_icon_indicators();
2215
    });
2216

    
2217
    $("img.spinner, img.wave").live('show', function(){
2218
        $("div.action-indicator").hide();
2219
    });
2220

    
2221
    $(".confirm_single button.no").live('click', function(evn){
2222
        $("div.action-indicator", $(evn.currentTarget).parent().parent()).hide();
2223
    });
2224

    
2225
    $(".confirm_multiple button.no").click(function(){
2226
        $("div.action-indicator").hide();
2227
    });
2228

    
2229
    $(".confirm_multiple button.yes").click(function(){
2230
        $("div.action-indicator").hide();
2231
    });
2232
}
2233

    
2234
function init_action_indicator_list_handlers()
2235
{
2236
    var skip_actions = { 'connect':'','details':'' };
2237

    
2238
    var has_pending_confirmation = function()
2239
    {
2240
        return $(".confirm_multiple:visible").length >= 1
2241
    }
2242

    
2243
    function update_action_indicator_icons(force_action, skip_pending)
2244
    {
2245
        // pending action based on the element class
2246
        var pending_action = $(".selected", $(".actions"))[0];
2247
        var selected = get_list_view_selected_machine_rows();
2248

    
2249
        // reset previous state
2250
        list_view_hide_action_indicators();
2251

    
2252
        if (pending_action == undefined && !force_action)
2253
        {
2254
            // no action selected
2255
            return;
2256
        }
2257

    
2258
        if (force_action != undefined)
2259
        {
2260
            // user forced action choice
2261
            var action_class = force_action;
2262
        } else {
2263
            // retrieve action name (reboot, stop, etc..)
2264
            var action_class = $(pending_action).attr("id").replace("action-","");
2265
        }
2266

    
2267
        selected.each(function(index, el) {
2268
            if (has_pending_confirmation() && skip_pending)
2269
            {
2270
                return;
2271
            }
2272
            var el = $(el);
2273
            var logo = $("img.list-logo", el);
2274
            $(".action-indicator", el).remove();
2275
            var cls = "action-indicator " + action_class;
2276
            // add icon div
2277
            logo.after('<div class="' + cls + '"></div>');
2278
            // hide os logo
2279
            $("img.list-logo", el).hide();
2280
        });
2281
    }
2282

    
2283
    // on mouseover we force the images to the hovered action
2284
    $(".actions a").live("mouseover", function(evn) {
2285
        var el = $(evn.currentTarget);
2286
        if (!el.hasClass("enabled"))
2287
        {
2288
            return;
2289
        }
2290
        var action_class = el.attr("id").replace("action-","");
2291
        if (action_class in skip_actions)
2292
        {
2293
            return;
2294
        }
2295
        update_action_indicator_icons(action_class, false);
2296
    });
2297

    
2298

    
2299
    // register events where icons should get updated
2300
    $(".actions a.enabled").live("click", function(evn) {
2301
        // clear previous selections
2302
        $("a.selected").removeClass("selected");
2303

    
2304
        var el = $(evn.currentTarget);
2305
        el.addClass("selected");
2306
        update_action_indicator_icons(undefined, false);
2307
    });
2308

    
2309
    $(".actions a").live("mouseout", function(evn) {
2310
        update_action_indicator_icons(undefined, false);
2311
    });
2312

    
2313
    $(".confirm_multiple button.no").click(function(){
2314
        list_view_hide_action_indicators();
2315
    });
2316

    
2317
    $(".confirm_multiple button.yes").click(function(){
2318
        list_view_hide_action_indicators();
2319
    });
2320

    
2321
    $("input[type=checkbox]").live('change', function(){
2322
        // pending_actions will become empty on every checkbox click/change
2323
        // line 154 machines_list.html
2324
        pending_actions = [];
2325
        if (pending_actions.length == 0)
2326
        {
2327
            $(".confirm_multiple").hide();
2328
            $("a.selected").each(function(index, el){$(el).removeClass("selected")});
2329
        }
2330
        update_action_indicator_icons(undefined, false);
2331
    });
2332

    
2333
}
2334

    
2335
function list_view_hide_action_indicators()
2336
{
2337
    $("tr td .action-indicator").remove();
2338
    $("tr td img.list-logo").show();
2339
}
2340

    
2341
function get_list_view_selected_machine_rows()
2342
{
2343
    var table = $("table.list-machines");
2344
    var rows = $("tr:has(input[type=checkbox]:checked)",table);
2345
    return rows;
2346
}
2347

    
2348
// machines images utils
2349
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) {
2350
    var views_map = {'single': '.single-image', 'icon': '.logo'};
2351
    var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'}
2352
    var sizes_map = {'single': 'large', 'icon': 'medium'}
2353

    
2354
    var size = sizes_map[machines_view];
2355
    var img_selector = views_map[machines_view];
2356
    var cls = states_map[state];
2357

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

    
2361
    var el = $(img_selector, machine);
2362
    var current_img = el.css("backgroundImage");
2363
    if (os == undefined){
2364
        new_img = current_img;
2365
    }
2366

    
2367
    // os changed
2368
    el.css("backgroundImage", new_img);
2369

    
2370
    // reset current state
2371
    if (skip_reset_states === undefined)
2372
    {
2373
        el.removeClass("single-image-state1");
2374
        el.removeClass("single-image-state2");
2375
        el.removeClass("single-image-state3");
2376
        el.removeClass("single-image-state4");
2377
    }
2378

    
2379
    if (remove_state !== undefined)
2380
    {
2381
        remove_state = "single-image-" + states_map[remove_state];
2382
        el.removeClass(remove_state);
2383
        return;
2384
    }
2385
    
2386
    // set proper state
2387
    el.addClass("single-image-" + cls);
2388
}
2389

    
2390

    
2391
// generic info box
2392
function show_feedback_form(msg, from_error) {
2393
    var box = $("#feedback-form");
2394
    box.addClass("notification-box");
2395

    
2396
    // initialize
2397
    box.find(".form-container").show();
2398
    box.find("textarea").val("");
2399
    box.find(".message").hide();
2400
    
2401
    var initial_msg = msg || undefined;
2402
    
2403
    var triggers = $("a#feedbackbox").overlay({
2404
        // some mask tweaks suitable for modal dialogs
2405
        mask: '#666',
2406
        top: '10px',
2407
        fixed: false,
2408
        closeOnClick: false,
2409
        oneInstance: false,
2410
        load: false
2411
    });
2412

    
2413
    
2414
    if (initial_msg && from_error) {
2415
        // feedback form from ajax_error window
2416
        box.find("textarea").val(initial_msg);
2417
        $("a#feedbackbox").overlay().onClose(function(){window.location.reload()});
2418
        box.find("textarea").height(200);
2419
        $("a#feedbackbox").overlay().onLoad(function(){box.find("textarea").focus().setCursorPosition(500);});
2420
        
2421
    }
2422

    
2423
    $("#feedback-form form").unbind("submit");
2424
    $("#feedback-form form").submit(function(event) {
2425
        event.preventDefault();
2426
            
2427
        // empty msg
2428
        if ($("textarea.feedback-text").val().replace(/^\s*|\s*$/,"") == "") {
2429
            alert($(".empty-error-msg", this).text());
2430
            return;
2431
        }
2432

    
2433
        $("textarea.data-text", this).val("").val(get_user_data_json());
2434

    
2435
        $.ajax({
2436
            url: FEEDBACK_URL,
2437
            data: $(this).serialize(),
2438
            type: "POST",
2439
            // show loading
2440
            beforeSend: function() {box.find(".form-container").hide(); box.find(".sending").fadeIn() },
2441
            // hide form
2442
            complete: function() { box.find(".form-container").hide(); box.find(".sending").hide() },
2443
            // on success display success message
2444
            success: function() { box.find(".success").fadeIn(); box.find(".sending").hide() },
2445
            // display error message
2446
            error: function() { box.find(".errormsg").fadeIn(); box.find(".sending").hide() }
2447
        })
2448
    });
2449
    
2450
    $("a#feedbackbox").data('overlay').load();
2451

    
2452
    // reset feedback_pending for ajax_errors
2453
    window.FEEDBACK_PENDING = false;
2454
    return false;
2455
}
2456

    
2457
function get_user_data(extra_data) {
2458
    try {
2459
        var last_req = $.extend({}, last_request);
2460

    
2461
        // reset xhr, might raise exceptions while converting to JSON
2462
        last_req.xhr = {};
2463
    } catch (err) {
2464
        var last_req = {}
2465
    }
2466

    
2467
    return $.extend({
2468
        'servers': $.extend({}, servers),
2469
        'client': {'browser': $.browser, 'screen': $.extend({}, screen), 'client': $.client},
2470
        'dates': {'now': new Date, 'lastUpdate': changes_since_date},
2471
        'last_request': last_req
2472
    }, extra_data);
2473
}
2474

    
2475
function get_user_data_json() {
2476
    try {
2477
        return JSON.stringify(get_user_data());
2478
    } catch (err) {
2479
        return JSON.stringify({'error': err});
2480
    }
2481
}
2482

    
2483
function msg_box(config) {
2484
    var config = $.extend({'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false}, config);
2485
    // prepare the error message
2486
    // bring up success notification
2487

    
2488
    var box = $("#notification-box");
2489
    box.addClass("notification-box");
2490
    box.addClass('success');
2491
    box.removeClass('error');
2492

    
2493
    var sel = function(s){return $(s, box)};
2494
    // reset texts
2495
    sel("h3 span.header-box").html("");
2496
    sel(".sub-text").html("");
2497
    sel(".password-container .password").html("");
2498
    sel("div.machine-now-building").html("");
2499
    
2500

    
2501
    // apply msg box contents
2502
    sel("h3 span.header-box").html(config.title);
2503
    sel("div.machine-now-building").html(config.content);
2504
    sel(".popup-header").removeClass("popup-header-error");
2505
    box.removeClass("popup-border-error");
2506
    sel(".popup-details").removeClass("popup-details-error");
2507
    sel(".popup-separator").removeClass("popup-separator-error");
2508
    
2509
    sel(".password-container").hide();
2510
    if (config.extra) {
2511
        sel(".password-container .password").html(config.extra);
2512
        sel(".password-container").show();
2513
    }
2514
    
2515
    var conf = {
2516
        // some mask tweaks suitable for modal dialogs
2517
        mask: '#666',
2518
        top: '10px',
2519
        closeOnClick: false,
2520
        oneInstance: false,
2521
        load: false,
2522
        fixed: config.fixed || false,
2523
        onClose: function () {
2524
            // With partial refresh working properly,
2525
            // it is no longer necessary to refresh the whole page
2526
            // choose_view();
2527
        }
2528
    }
2529
    
2530
    var triggers = $("a#msgbox").overlay(conf);
2531

    
2532
    try {
2533
        conf = $("a#msgbox").data('overlay').getConf();
2534
        conf.fixed = config.fixed || false;
2535
    } catch (err) {}
2536
    $("a#msgbox").data('overlay').load();
2537
    
2538
    var parse_data = config.parse_data || false;
2539
    var load_html = config.html || false;
2540
    var user_success = config.success || false;
2541
    config.ajax = config.ajax || {};
2542

    
2543
    // requested to show remote data in msg_box
2544
    if (config.ajax) {
2545
        $.ajax($.extend({ 
2546
            url:config.ajax, 
2547
            success: function(data){
2548
                // we want to get our data parsed before
2549
                // placing them in content
2550
                if (parse_data) {
2551
                    data = parse_data(data);
2552
                }
2553

    
2554
                // no json response
2555
                // load html body
2556
                if (load_html) {
2557
                    sel("div.machine-now-building").html(data);
2558
                } else {
2559

    
2560
                    if (data.title) {
2561
                        sel("h3 span.header-box").text(data.title);
2562
                    }
2563

    
2564
                    if (data.content) {
2565
                        sel("div.machine-now-building").html(data.content);
2566
                    }
2567
                    if (data.extra) {
2568
                        sel(".password-container .password").html(data.extra);
2569
                        sel(".password-container").show();
2570
                    }
2571
                    if (data.subinfo) {
2572
                        sel(".sub-text").html(data.subinfo);
2573
                    } else {
2574
                        sel(".sub-text").html("");
2575
                    }
2576
                }
2577

    
2578
                if (user_success) {
2579
                    user_success($("div.machine-now-building"));
2580
                }
2581
            },
2582
            error: function(xhr, status, err) {
2583
                ajax_error(-519, "UI Error", "Machine connect", err);
2584
            }
2585
        }, config.ajax_config));
2586
    }
2587
    return false;
2588
}
2589

    
2590

    
2591
function show_invitations() {
2592

    
2593
    handle_invitations = function(el) {
2594

    
2595
        // proper class to identify the overlay block
2596
        el.addClass("invitations");
2597

    
2598
        var cont = el;
2599
        var form = $(el).find("form");
2600

    
2601
        // remove garbage rows that stay in DOM between requests
2602
        $(".removable-field-row:hidden").remove();
2603

    
2604
        // avoid buggy behaviour, close all overlays if something went wrong
2605
        try {
2606
            // form is in content (form is not displayed if user has no invitations)
2607
            if ($("#invform #removable-name-container-1").length) {
2608
                $("#invform #removable-name-container-1").dynamicField();
2609
            }
2610
        } catch (err) {
2611
            close_all_overlays();
2612
        }
2613
        
2614
        // we copy/paste it on the title no need to show it twice
2615
        $(".invitations-left").hide();
2616

    
2617
        // reset title
2618
        $("#notification-box .header-box").html("");
2619
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
2620

    
2621
        // handle form submit
2622
        form.submit(function(evn){
2623
            evn.preventDefault();
2624

    
2625
            // do the post
2626
            $.post(form.attr("action"), form.serialize(), function(data) {
2627
                // replace data
2628
                $(cont).html(data); 
2629

    
2630
                // append all handlers again (new html data need to redo all changes)
2631
                handle_invitations(cont);
2632
            });
2633

    
2634
            return false;
2635
        });
2636
    }
2637
    
2638
    // first time clicked (show the msg box with /invitations content)
2639
    msg_box({
2640
        title:window.INVITATIONS_TITLE, 
2641
        content:'', 
2642
        fixed: false,
2643
        ajax:INVITATIONS_URL, 
2644
        html:true, 
2645
        success: function(el){ 
2646
            handle_invitations(el)
2647
        }
2648
    });
2649
}
2650

    
2651

    
2652
function get_short_v6(v6, parts_to_keep) {
2653
    var parts = v6.split(":");
2654
    var new_parts = parts.slice(parts.length - parts_to_keep);
2655
    return new_parts.join(":");
2656
}
2657

    
2658
function fix_v6_addresses() {
2659

    
2660
    // what to prepend
2661
    var match = "...";
2662
    // long ip min length
2663
    var limit = 20;
2664
    // parts to show after the transformation
2665
    // (from the end)
2666
    var parts_to_keep_from_end = 4;
2667

    
2668
    $(".ipv6-text").each(function(index, el){
2669
        var el = $(el);
2670
        var ip = $(el).text();
2671
            
2672
        // transformation not applyied
2673
        // FIXME: use $.data for the condition
2674
        if (ip.indexOf(match) == -1 && ip != "pending") {
2675
            
2676
            // only too long ips
2677
            if (ip.length > 20) {
2678
                $(el).data("ipstring", ip);
2679
                $(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
2680
                $(el).attr("title", ip);
2681
                $(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'});
2682
            }
2683
        } else {
2684
            if (ip.indexOf(match) == 0) {
2685
            } else {
2686
                // not a long ip anymore
2687
                $(el).data("ipstring", undefined);
2688
                $(el).css({'text-decoration':'none'});
2689

    
2690
                if ($(el).data('tooltip')) {
2691
                    $(el).data('tooltip').show = function () {};
2692
                }
2693
            }
2694
        }
2695
    });
2696
}
2697

    
2698
function fix_server_name(str, limit, append) {
2699
    limit = limit || 30;
2700
    append = append || "...";
2701

    
2702
    if (str.length > limit) {
2703
        str = str.substring(0,limit-append.length) + append;
2704
    }
2705
    return str;
2706
}
2707

    
2708
function show_machine_network_indicator(vm_id, network_id) {
2709
    var el = $("div#net-" + network_id + '-server-' + vm_id);
2710
    el.find(".network-progress-indicator").show();
2711
}
2712

    
2713

    
2714
function get_firewall_profile(vm_id) {
2715
    var vm = get_machine(vm_id);
2716

    
2717
    try {
2718
        return vm.addresses.values[0].firewallProfile;
2719
    } catch (err) {
2720
        return undefined;
2721
    }
2722
}
2723

    
2724

    
2725
function get_progress_details(id) {
2726
    var vm = get_machine(id);
2727
    var progress = vm.progress;
2728

    
2729
    // no details for active machines
2730
    if (!vm.status == "BUILD") {
2731
        return false;
2732
    }
2733
    
2734
    // check if images not loaded yet
2735
    try {
2736
        var image = get_image_params(vm.imageRef);
2737
        var size = image.size;
2738
    } catch (err) {
2739
        // images not loaded yet (can this really happen ??)
2740
        return;
2741
    }
2742
    
2743
    var to_copy = size;
2744
    var copied = (size * progress / 100).toFixed(2);
2745
    var status = "INIT"
2746

    
2747
    // apply state
2748
    if (progress > 0) { status = "IMAGE_COPY" }
2749
    if (progress >= 100) { status = "FINISH" }
2750
    
2751
    // user information
2752
    var msg = BUILDING_STATUSES[status];
2753

    
2754
    // image copy state display extended user information
2755
    if (status == "IMAGE_COPY") {
2756
        msg = msg.format(readablizeBytes(copied*(1024*1024)), readablizeBytes(to_copy*(1024*1024)), progress)
2757
    }
2758

    
2759
    var progress_data = {
2760
        'percent': vm.progress,
2761
        'build_status': status,
2762
        'copied': copied,
2763
        'to_copy': size,
2764
        'msg': msg
2765
    }
2766

    
2767
    return progress_data;
2768
}
2769

    
2770
// display user friendly bytes amount
2771
function readablizeBytes(bytes) {
2772
    var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
2773
    var e = Math.floor(Math.log(bytes)/Math.log(1024));
2774
    return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
2775
}
2776