Statistics
| Branch: | Tag: | Revision:

root / ui / static / synnefo.js @ 55e4b353

History | View | Annotate | Download (91.8 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
                if (!isXhrException(err)) {
68
                    ajax_error(-501, "UI Error", 'Generic error', err);
69
                } else {
70
                    return false;
71
                }
72
            }
73
        }
74

    
75
        // refresh after 10 seconds
76
        window.setTimeout("window.location.reload()", window.error_timeout);
77
    }
78
});
79

    
80
function isXhrException(err) {
81

    
82
    DOM_EXCEPTION_NAMES = [
83
        "NS_ERROR_NOT_AVAILABLE", // Firefox
84
        "INVALID_STATE_ERR" // Chrome
85
    ];
86

    
87
    try {
88
        if (DOM_EXCEPTION_NAMES.indexOf(err.name) != -1) {
89
            return true;
90
        } 
91
        
92
        // ie !!!!
93
        if (err.number == -2147467259) {
94
            return true;
95
        }
96

    
97
    } catch(err) {
98
        return false;
99
    }
100

    
101
    return false;
102
}
103

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

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

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

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

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

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

    
195
    for (; from < len; from++) {
196
      if (from in this &&
197
          this[from] === elt)
198
        return from;
199
    }
200
    return -1;
201
  };
202
}
203

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

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

    
221

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

    
253
function update_network_confirmations(){
254
    // hide all confirm boxes to begin with
255
    $('#networks-pane div.confirm_multiple').hide();
256

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

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

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

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

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

    
360
function choose_view() {
361
    if ($.cookie("view")=='1') {
362
        list_view();
363
    } else if ($.cookie("view")=='2'){
364
        single_view();
365
    } else {
366
        standard_view();
367
    }
368
}
369

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

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

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

    
406
    if (changes_since != 0)
407
        uri+='?changes-since='+changes_since
408

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

    
441
            if (interval) {
442
                clearTimeout(deferred);    // clear old deferred calls
443
                deferred = setTimeout(function() {update_vms(interval);},interval,interval);
444
            }
445

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

    
467
function update_servers_data(servers_update, data) {
468
    $(window).trigger("vm:update", servers_update, data);
469

    
470
    // first call
471
    if (!window.servers || window.servers.length == 0) {
472
        window.servers = servers_update;
473
        return;
474
    }
475
    
476
    // server exists helper
477
    server_exists = function(server) {
478
        var id = server.id;
479
        var found = false;
480
        var index = 0;
481
        $.each(servers, function(i, s) {
482
            if (s.id == id) { found = true, index = i };
483
        });
484
        if (found)
485
            return [found, index];
486

    
487
        return false;
488
    }
489

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

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

    
537
        // reset network transition
538
        try {
539
            if (old_server.network_transition) {
540
                if (old_server.network_transition == "NETWORK_CHANGE") {
541
                    // network profile changed, servers data updated, so act if the change was made
542
                    // this flag will trigger ui to remove any transiiton indicators
543
                    // and hopefully apply the new value to the profile options
544
                    old_server.network_transition = "CHANGED"
545
                } else {
546
                    // nothing happened
547
                    old_server.network_transition = undefined;
548
                };
549
            }
550
        } catch (err) { console.info(err) }
551

    
552
        if (exists !== false) {
553
            try {
554
                servers[exists[1]] = merge(servers[exists[1]], server);
555
            } catch (err) {
556
            }
557
        } else {
558
            servers.push(server);
559
            $(window).trigger("vm:add", server);
560
        }
561
        if (remove.indexOf(server.id) > -1) {
562
            var remove_exists = server_exists(server);
563
            servers.splice(remove_exists[1], 1);
564
            $(window).trigger("vm:remove", server);
565
        }
566
    });
567
}
568

    
569
// get a list of running and terminated machines, used in network view
570
function update_networks(interval) {
571
    try{ console.info('updating networks'); } catch(err){}
572
    var uri= API_URL + '/servers/detail';
573

    
574
    if (changes_since != 0)
575
        uri+='?changes-since='+changes_since
576

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

    
592
            try {
593
                ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
594
            } catch(err) {
595
                if (!isXhrException(err)) {
596
                    ajax_error(-504, "UI Error", 'Update networks', err);
597
                } else {
598
                    return false;
599
                }
600
            }
601
            return false;
602
            },
603
        success: function(data, textStatus, jqXHR) {
604
            // create changes_since string if necessary
605
            if (jqXHR.getResponseHeader('Date') != null){
606
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
607
                changes_since = ISODateString(changes_since_date);
608
            }
609

    
610
            if (interval) {
611
                clearTimeout(deferred);    // clear old deferred calls
612
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
613
            }
614

    
615
            if (jqXHR.status == 200 || jqXHR.status == 203) {
616
                try {
617
                    //servers = data.servers.values;
618
                    update_servers_data(data.servers.values, data);
619
                    update_network_names(data);
620
                } catch(err) { ajax_error(-505, "UI Error", 'Update networks', err);}
621
            } else if (jqXHR.status == 304) {
622
                update_network_names();
623
            }
624
            else {
625
                try { console.info('update_networks callback:' + jqXHR.status ) } catch(err) {}
626
                /*
627
                FIXME:  Here it should return the error, however Opera does not support 304.
628
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
629
                        304, which should be corrected (Bug #317).
630
                */
631
                //ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
632
                update_network_names();
633
            }
634
            return false;
635
        }
636
    });
637
    return false;
638
}
639

    
640
// get and show a list of public and private networks
641
function update_network_names(servers_data) {
642
    try{ console.info('updating network names'); } catch(err){}
643
    var uri= API_URL + '/networks/detail';
644

    
645
    if (networks_changes_since != 0)
646
        //FIXME: Comment out the following, until metadata do not 304 when changed
647
        uri+='?changes-since=' + networks_changes_since
648

    
649
    update_request = $.ajax({
650
        cache: false,
651
        url: uri,
652
        type: "GET",
653
        timeout: TIMEOUT,
654
        dataType: "json",
655
        error: function(jqXHR, textStatus, errorThrown) {
656
            // as for now, just show an error message
657
            try {
658
                console.info('update_network names errback:' + jqXHR.status )
659
            } catch(err) {}
660
            try {
661
                ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
662
            } catch(err) {
663
                ajax_error(-506, "UI Error", 'Update network names', err);
664
            }
665
            return false;
666
            },
667
        success: function(data, textStatus, jqXHR) {
668
            // create changes_since string if necessary
669
            if (jqXHR.getResponseHeader('Date') != null){
670
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
671
                networks_changes_since = ISODateString(changes_since_date);
672
            }
673

    
674
            if (jqXHR.status == 200 || jqXHR.status == 203) {
675
                try {
676
                    networks = data.networks.values;
677
                    update_networks_view(servers_data, data);
678
                } catch(err) {
679
                    ajax_error(-507, "UI Error", 'Update network names', err);
680
                }
681
            } else if (jqXHR.status == 304) {
682
                    update_networks_view(servers_data);
683
            } else if (jqXHR.status != 304){
684
                try { console.info('update_network_names callback:' + jqXHR.status ) } catch(err) {}
685
                /*
686
                FIXME:  Here it should return the error, however Opera does not support 304.
687
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
688
                        304, which should be corrected (Bug #317).
689
                */
690
                //ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
691
                update_networks_view(servers_data);
692
            }
693
            return false;
694
        }
695
    });
696
    return false;
697
}
698

    
699
// get and show a list of available standard and custom images
700
function update_images() {
701
    $.ajax({
702
        url: API_URL + '/images/detail',
703
        type: "GET",
704
        //async: false,
705
        dataType: "json",
706
        timeout: TIMEOUT,
707
        error: function(jqXHR, textStatus, errorThrown) {
708
                    try {
709
                        ajax_error(jqXHR.status, undefined, 'Update Images', jqXHR.responseText);
710
                    } catch(err) {
711
                        ajax_error(-508, "UI error", 'Update Images', err);
712
                    }
713
                },
714
        success: function(data, textStatus, jqXHR) {
715
            try {
716
                images = data.images.values;
717
                jQuery.parseJSON(data);
718
                update_wizard_images();
719
            } catch(err){
720
                ajax_error("NO_IMAGES");
721
            }
722
        }
723
    });
724
    return false;
725
}
726

    
727
function update_wizard_images() {
728
    if ($("ul#standard-images li").toArray().length + $("ul#custom-images li").toArray().length == 0) {
729
        $.each(images, function(i,image){
730
            var img = $('#image-template').clone().attr("id","img-"+image.id).fadeIn("slow");
731
            img.find("label").attr('for',"img-radio-" + image.id);
732
            img.find(".image-title").text(image.name);
733
            if (image.metadata) {
734
                if (image.metadata.values.description != undefined) {
735
                    img.find(".description").text(image.metadata.values.description);
736
                }
737
                if (image.metadata.values.size != undefined) {
738
                    img.find("#size").text(image.metadata.values.size);
739
                }
740
            }
741
            img.find("input.radio").attr('id',"img-radio-" + image.id);
742
            if (i==0) img.find("input.radio").attr("checked","checked");
743
            var image_logo = os_icon(image.metadata);
744
            img.find("img.image-logo").attr('src','static/icons/os/'+image_logo+'.png');
745
            if (image.metadata) {
746
                if (image.metadata.values.serverId != undefined) {
747
                    img.appendTo("ul#custom-images");
748
                } else {
749
                    img.appendTo("ul#standard-images");
750
                }
751
            } else {
752
                img.appendTo("ul#standard-images");
753
            }
754
        });
755
    }
756
}
757

    
758
function update_wizard_flavors(){
759
    // sliders for selecting VM flavor
760
    $("#cpu:range").rangeinput({min:0,
761
                               value:0,
762
                               step:1,
763
                               progress: true,
764
                               max:cpus.length-1});
765

    
766
    $("#storage:range").rangeinput({min:0,
767
                               value:0,
768
                               step:1,
769
                               progress: true,
770
                               max:disks.length-1});
771

    
772
    $("#ram:range").rangeinput({min:0,
773
                               value:0,
774
                               step:1,
775
                               progress: true,
776
                               max:ram.length-1});
777
    $("#small").click();
778

    
779
    // update the indicators when sliding
780
    $("#cpu:range").data().rangeinput.onSlide(function(event,value){
781
        $("#cpu-indicator")[0].value = cpus[Number(value)];
782
        $("#cpu-indicator").addClass('selectedrange');
783
    });
784
    $("#cpu:range").data().rangeinput.change(function(event,value){
785
        $("#cpu-indicator")[0].value = cpus[Number(value)];
786
        $("#custom").click();
787
        $("#cpu-indicator").removeClass('selectedrange');
788
    });
789
    $("#ram:range").data().rangeinput.onSlide(function(event,value){
790
        $("#ram-indicator")[0].value = ram[Number(value)];
791
        $("#ram-indicator").addClass('selectedrange');
792
    });
793
    $("#ram:range").data().rangeinput.change(function(event,value){
794
        $("#ram-indicator")[0].value = ram[Number(value)];
795
        $("#custom").click();
796
        $("#ram-indicator").removeClass('selectedrange');
797
    });
798
    $("#storage:range").data().rangeinput.onSlide(function(event,value){
799
        $("#storage-indicator")[0].value = disks[Number(value)];
800
        $("#storage-indicator").addClass('selectedrange');
801
    });
802
    $("#storage:range").data().rangeinput.change(function(event,value){
803
        $("#storage-indicator")[0].value = disks[Number(value)];
804
        $("#custom").click();
805
        $("#storage-indicator").removeClass('selectedrange');
806
    });
807
}
808

    
809
Array.prototype.unique = function () {
810
    var r = new Array();
811
    o:for(var i = 0, n = this.length; i < n; i++)
812
    {
813
        for(var x = 0, y = r.length; x < y; x++)
814
        {
815
            if(r[x]==this[i])
816
            {
817
                continue o;
818
            }
819
        }
820
        r[r.length] = this[i];
821
    }
822
    return r;
823
}
824

    
825
// get and configure flavor selection
826
function update_flavors() {
827
    $.ajax({
828
        url: API_URL + '/flavors/detail',
829
        type: "GET",
830
        //async: false,
831
        dataType: "json",
832
        timeout: TIMEOUT,
833
        error: function(jqXHR, textStatus, errorThrown) {
834
            try {
835
                ajax_error(jqXHR.status, undefined, 'Update Flavors', jqXHR.responseText);
836
            } catch (err) {
837
                ajax_error(-509, "UI Error", "Update Flavors", err);
838
            }
839
            // start updating vm list
840
            update_vms(UPDATE_INTERVAL);
841
        },
842
        success: function(data, textStatus, jqXHR) {
843

    
844
            try {
845
                flavors = data.flavors.values;
846
                jQuery.parseJSON(data);
847
                $.each(flavors, function(i, flavor) {
848
                    cpus[i] = flavor['cpu'];
849
                    disks[i] = flavor['disk'];
850
                    ram[i] = flavor['ram'];
851
                });
852
                cpus = cpus.unique();
853
                disks = disks.unique();
854
                ram = ram.unique();
855
                update_wizard_flavors();
856
            } catch(err){
857
                ajax_error("NO_FLAVORS");
858
            }
859
            // start updating vm list
860
            update_vms(UPDATE_INTERVAL);
861
        }
862
    });
863
    return false;
864
}
865

    
866
// return flavorRef from cpu, disk, ram values
867
function identify_flavor(cpu, disk, ram){
868
    for (i=0;i<flavors.length;i++){
869
        if (flavors[i]['cpu'] == cpu && flavors[i]['disk']==disk && flavors[i]['ram']==ram) {
870
            return flavors[i]['id']
871
        }
872
    }
873
    return 0;
874
}
875

    
876
// return image entry from imageRef
877
function get_image(imageRef) {
878
    for (i=0;i<images.length;i++){
879
        if (images[i]['id'] == imageRef) {
880
            return images[i];
881
        }
882
    }
883
    return 0;
884
}
885

    
886
// return machine entry from serverID
887
function get_machine(serverID) {
888
    for (i=0;i<servers.length;i++){
889
        if (servers[i]['id'] == serverID) {
890
            return servers[i];
891
        }
892
    }
893
    return 0;
894
}
895

    
896
// update the actions in icon view, per server
897
function update_iconview_actions(serverID, server_status) {
898
    if ($.cookie("view")=='2') {
899
        // remove .disable from all actions to begin with
900
        $('#machinesview-single #' + serverID + ' div.single-action').show();
901
        // decide which actions should be disabled
902
        for (current_action in actions) {
903
            if (actions[current_action].indexOf(server_status) == -1 ) {
904
                $('#machinesview-single #' + serverID + ' div.action-' + current_action).hide();
905
            }
906
        }
907
    } else {
908
        // remove .disable from all actions to begin with
909
        $('#machinesview-icon.standard #' + serverID + ' div.actions').find('a').removeClass('disabled');
910
        // decide which actions should be disabled
911
        for (current_action in actions) {
912
            if (actions[current_action].indexOf(server_status) == -1 ) {
913
                $('#machinesview-icon.standard #' + serverID + ' a.action-' + current_action).addClass('disabled');
914
            }
915
        }
916
    }
917
}
918

    
919
// update the actions in list view
920
function update_listview_actions() {
921
    var states = [];
922
    var on = [];
923
    var checked = $("table.list-machines tbody input[type='checkbox']:checked");
924
    // disable all actions to begin with
925
    $('#machinesview .list div.actions').children().removeClass('enabled');
926

    
927
    // are there multiple machines selected?
928
    if (checked.length>1)
929
        states[0] = 'multiple';
930

    
931
    // check the states of selected machines
932
    checked.each(function(i,checkbox) {
933
        states[states.length] = checkbox.className;
934
        var ip = $("#" + checkbox.id.replace('input-','') + ".ip span.public").text();
935
        if (ip.replace('undefined','').length)
936
            states[states.length] = 'network';
937
    });
938

    
939
    // decide which actions should be enabled
940
    for (a in actions) {
941
        var enabled = false;
942
        for (var s =0; s<states.length; s++) {
943
            if (actions[a].indexOf(states[s]) != -1 ) {
944
                enabled = true;
945
            } else {
946
                enabled = false;
947
                break;
948
            }
949
        }
950
        if (enabled)
951
            on[on.length]=a;
952
    }
953
    // enable those actions
954
    for (action in on) {
955
        $("#action-" + on[action]).addClass('enabled');
956
    }
957
}
958

    
959
//create server action
960
function create_vm(machineName, imageRef, flavorRef){
961
    var image_logo = os_icon(get_image(imageRef).metadata);
962
    var uri = API_URL + '/servers';
963
    var payload = {
964
        "server": {
965
            "name": machineName,
966
            "imageRef": imageRef,
967
            "flavorRef" : flavorRef,
968
            "metadata" : {
969
                "OS" : image_logo
970
            }
971
        }
972
    };
973

    
974
    $.ajax({
975
    url: uri,
976
    type: "POST",
977
    contentType: "application/json",
978
    dataType: "json",
979
    data: JSON.stringify(payload),
980
    timeout: TIMEOUT,
981
    error: function(jqXHR, textStatus, errorThrown) {
982
                // close wizard and show error box
983
                $('#machines-pane a#create').data('overlay').close();
984
                    try {
985
                        ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
986
                    } catch(err) {
987
                        if (!isXhrException(err)) {
988
                            ajax_error(-510, "UI Error", 'Create VM', err);
989
                        }
990
                    }
991
           },
992
    success: function(data, textStatus, jqXHR) {
993
                if ( jqXHR.status == '202') {
994
                    ajax_success("CREATE_VM_SUCCESS", data.server.adminPass);
995
                } else {
996
                    // close wizard and show error box
997
                    $('#machines-pane a#create').data('overlay').close();
998
                    ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
999
                }
1000
            }
1001
    });
1002
}
1003

    
1004
// reboot action
1005
function reboot(serverIDs){
1006
    if (!serverIDs.length){
1007
        //ajax_success('DEFAULT');
1008
        return false;
1009
    }
1010
    // ajax post reboot call
1011
    var payload = {
1012
        "reboot": {"type" : "HARD"}
1013
    };
1014

    
1015
    var serverID = serverIDs.pop();
1016

    
1017
    $.ajax({
1018
        url: API_URL + '/servers/' + serverID + '/action',
1019
        type: "POST",
1020
        contentType: "application/json",
1021
        dataType: "json",
1022
        data: JSON.stringify(payload),
1023
        timeout: TIMEOUT,
1024
        error: function(jqXHR, textStatus, errorThrown) {
1025
                    // in machine views
1026
                    if ( $.cookie("pane") == 0) {
1027
                        try {
1028
                            display_failure(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
1029
                        } catch (err) {
1030
                            display_failure(0, serverID, 'Reboot', jqXHR.responseText);
1031
                        }
1032
                    }
1033
                    // in network view
1034
                    else {
1035
                        try {
1036
                            display_reboot_failure(jqXHR.status, serverID, jqXHR.responseText);
1037
                        } catch (err) {
1038
                            display_reboot_failure(0, serverID, jqXHR.responseText);
1039
                        }
1040
                    }
1041
                },
1042
        success: function(data, textStatus, jqXHR) {
1043
                    if ( jqXHR.status == '202') {
1044
                        try {
1045
                            console.info('rebooted ' + serverID);
1046
                        } catch(err) {}
1047
                        // indicate that the action succeeded
1048
                        // in machine views
1049
                        if ( $.cookie("pane") == 0) {
1050
                            display_success(serverID);
1051
                        }
1052
                        // in network view
1053
                        else {
1054
                            display_reboot_success(serverID);
1055
                        }
1056
                        // continue with the rest of the servers
1057
                        reboot(serverIDs);
1058
                    } else {
1059
                        ajax_error(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
1060
                    }
1061
                }
1062
    });
1063
    return false;
1064
}
1065

    
1066
// shutdown action
1067
function shutdown(serverIDs) {
1068
    if (!serverIDs.length){
1069
        //ajax_success('DEFAULT');
1070
        return false;
1071
    }
1072
    // ajax post shutdown call
1073
    var payload = {
1074
        "shutdown": {}
1075
    };
1076

    
1077
    var serverID = serverIDs.pop();
1078

    
1079
    $.ajax({
1080
        url: API_URL + '/servers/' + serverID + '/action',
1081
        type: "POST",
1082
        contentType: "application/json",
1083
        dataType: "json",
1084
        data: JSON.stringify(payload),
1085
        timeout: TIMEOUT,
1086
        error: function(jqXHR, textStatus, errorThrown) {
1087
                    try {
1088
                        display_failure(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1089
                    } catch(err) {
1090
                        display_failure(0, serverID, 'Shutdown', jqXHR.responseText);
1091
                    }
1092
                },
1093
        success: function(data, textStatus, jqXHR) {
1094
                    if ( jqXHR.status == '202') {
1095
                        try {
1096
                            console.info('suspended ' + serverID);
1097
                        } catch(err) {}
1098
                        // indicate that the action succeeded
1099
                        display_success(serverID);
1100
                        // continue with the rest of the servers
1101
                        shutdown(serverIDs);
1102
                    } else {
1103
                        ajax_error(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1104
                    }
1105
                }
1106
    });
1107
    return false;
1108
}
1109

    
1110
// destroy action
1111
function destroy(serverIDs) {
1112
    if (!serverIDs.length){
1113
        //ajax_success('DEFAULT');
1114
        return false;
1115
    }
1116
    // ajax post destroy call can have an empty request body
1117
    var payload = {};
1118

    
1119
    var serverID = serverIDs.pop();
1120

    
1121
    $.ajax({
1122
        url: API_URL + '/servers/' + serverID,
1123
        type: "DELETE",
1124
        contentType: "application/json",
1125
        dataType: "json",
1126
        data: JSON.stringify(payload),
1127
        timeout: TIMEOUT,
1128
        error: function(jqXHR, textStatus, errorThrown) {
1129
                    try {
1130
                        display_failure(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1131
                    } catch(err) {
1132
                        display_failure(0, serverID, 'Destroy', jqXHR.responseText);
1133
                    }
1134
                },
1135
        success: function(data, textStatus, jqXHR) {
1136
                    if ( jqXHR.status == '204') {
1137
                        try {
1138
                            console.info('destroyed ' + serverID);
1139
                        } catch (err) {}
1140

    
1141
                        // update status on local storage object
1142
                        vm = get_machine(serverID);
1143
                        vm.status = "DESTROY";
1144

    
1145
                        // indicate that the action succeeded
1146
                        display_success(serverID);
1147
                        // continue with the rest of the servers
1148
                        destroy(serverIDs);
1149
                    } else {
1150
                        ajax_error(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1151
                    }
1152
                }
1153
    });
1154
    return false;
1155
}
1156

    
1157
// start action
1158
function start(serverIDs){
1159
    if (!serverIDs.length){
1160
        //ajax_success('DEFAULT');
1161
        return false;
1162
    }
1163
    // ajax post start call
1164
    var payload = {
1165
        "start": {}
1166
    };
1167

    
1168
    var serverID = serverIDs.pop();
1169

    
1170
    $.ajax({
1171
        url: API_URL + '/servers/' + serverID + '/action',
1172
        type: "POST",
1173
        contentType: "application/json",
1174
        dataType: "json",
1175
        data: JSON.stringify(payload),
1176
        timeout: TIMEOUT,
1177
        error: function(jqXHR, textStatus, errorThrown) {
1178
                    try {
1179
                        display_failure(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1180
                    } catch(err) {
1181
                        display_failure(0, serverID, 'Start', jqXHR.responseText);
1182
                    }
1183
                },
1184
        success: function(data, textStatus, jqXHR) {
1185
                    if ( jqXHR.status == '202') {
1186
                        try {
1187
                            console.info('started ' + serverID);
1188
                        } catch(err) {}
1189
                        // indicate that the action succeeded
1190
                        display_success(serverID);
1191
                        // continue with the rest of the servers
1192
                        start(serverIDs);
1193
                    } else {
1194
                        ajax_error(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1195
                    }
1196
                }
1197
    });
1198
    return false;
1199
}
1200

    
1201
// Show VNC console
1202
function vnc_attachment(host, port, password) {
1203
    // FIXME: Must be made into parameters, in settings.py
1204
    //vnc = open("", "displayWindow",
1205
    //    "status=yes,toolbar=yes,menubar=yes");
1206
    vd = document.open("application/x-vnc");
1207

    
1208
    vd.writeln("[connection]");
1209
    vd.writeln("host=" + host);
1210
    vd.writeln("port=" + port);
1211
    vd.writeln("password=" + password);
1212

    
1213
    vd.close();
1214
}
1215

    
1216
// Show VNC console
1217
function show_vnc_console(serverID, serverName, serverIP, host, port, password) {
1218
    var params_url = '?machine=' + serverName + '&host_ip=' + serverIP.v4 + '&host_ip_v6=' + serverIP.v6 + '&host=' + host + '&port=' + port + '&password=' + password;
1219
    var params_window = 'scrollbars=no,' +
1220
                        'menubar=no,' +
1221
                        'toolbar=no,' +
1222
                        'status=no,' +
1223
                        'top=0,' +
1224
                        'left=0,' +
1225
                        'height=' + screen.height + ',' +
1226
                        'width=' + screen.width + ',' +
1227
                        'fullscreen=yes';
1228
    
1229
    var url = 'machines/console' + params_url;
1230
    window.open(url, 'formresult' + serverID, params_window);
1231

    
1232
    // Restore os icon in list view
1233
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1234
    osIcon.attr('src',osIcon.attr('os'));
1235
    return false;
1236
}
1237

    
1238
// console action
1239
function open_console(serverIDs){
1240
    if (!serverIDs.length){
1241
        //ajax_success('DEFAULT');
1242
        return false;
1243
    }
1244
    // ajax post start call
1245
    var payload = {
1246
        "console": {"type": "vnc"}
1247
    };
1248

    
1249
    var serverID = serverIDs.pop();
1250

    
1251
    var machine = get_machine(serverID);
1252
    var serverName = machine.name;
1253
    try {
1254
        var serverIP = {};
1255
        serverIP.v4 = machine.addresses.values[0].values[0].addr;
1256
        serverIP.v6 = machine.addresses.values[0].values[1].addr;
1257
    } catch(err) { var serverIP = 'undefined'; }
1258

    
1259
    $.ajax({
1260
        url: API_URL + '/servers/' + serverID + '/action',
1261
        type: "POST",
1262
        contentType: "application/json",
1263
        dataType: "json",
1264
        data: JSON.stringify(payload),
1265
        timeout: TIMEOUT,
1266
        error: function(jqXHR, textStatus, errorThrown) {
1267
                    try {
1268
                        display_failure(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1269
                    } catch(err) {
1270
                        display_failure(0, serverID, 'Console', jqXHR.responseText);
1271
                    }
1272
                },
1273
        success: function(data, textStatus, jqXHR) {
1274
                    if ( jqXHR.status == '200') {
1275
                        try {
1276
                            console.info('got_console ' + serverID);
1277
                        } catch(err) {}
1278
                        // indicate that the action succeeded
1279
                        // show_vnc_console(serverID, serverName, serverIP,
1280
                        // data.console.host,data.console.port,data.console.password);
1281
                        show_vnc_console(serverID, serverName, serverIP,
1282
                                         data.console.host,data.console.port,data.console.password);
1283
                        display_success(serverID);
1284
                        // hide spinner
1285
                        $('#' + serverID + ' .spinner').hide();
1286
                        // continue with the rest of the servers
1287
                        open_console(serverIDs);
1288
                    } else {
1289
                        ajax_error(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1290
                    }
1291
                }
1292
    });
1293
    return false;
1294
}
1295

    
1296
function vm_has_address(vmId) {
1297
    var vm = get_machine(vmId);
1298

    
1299
    if (!vm) return false;
1300

    
1301
    try {
1302
        var ip = vm.addresses.values[0].values[0].addr;
1303
    } catch (err) {
1304
        return false;
1305
    }
1306
    return ip;
1307
}
1308

    
1309
// connect to machine action
1310
function machine_connect(serverIDs){
1311
    if (!serverIDs.length){
1312
        //ajax_success('DEFAULT');
1313
        return false;
1314
    }
1315
    
1316
    // prefer metadata values for specific options (username, domain)
1317
    var username_meta_key = 'user';
1318
    var domain_meta_key = "domain";
1319

    
1320
    var serverID = serverIDs.pop();
1321
    var machine = get_machine(serverID);
1322
    var serverName = machine.name;
1323
    
1324
    try {
1325
        var serverIP = machine.addresses.values[0].values[0].addr;
1326
    } catch (err) { var serverIP = 'undefined'; }
1327

    
1328
    try {
1329
        var os = os_icon(machine.metadata);
1330
    } catch (err) { var os = 'undefined'; }
1331

    
1332
    var username = "";
1333
    try {
1334
        username = machine.metadata.values[username_meta_key];
1335
    } catch (err) { username = undefined }
1336

    
1337
    var domain = "";
1338
    try {
1339
        domain = machine.metadata.values[domain_meta_key];
1340
    } catch (erro) { domain = undefined }
1341

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

    
1344
    if (username) {
1345
        params_url += "&username=" + username;
1346
    }
1347

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

    
1385

    
1386
    // Restore os icon in list view
1387
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1388
    osIcon.attr('src',osIcon.attr('os'));
1389

    
1390
    return false;
1391
}
1392

    
1393

    
1394
// rename server
1395
function rename(serverID, serverName){
1396
    if (!serverID.length){
1397
        //ajax_success('DEFAULT');
1398
        return false;
1399
    }
1400
    // ajax post rename call
1401
    var payload = {
1402
        "server": {"name": serverName}
1403
    };
1404

    
1405
    $.ajax({
1406
        url: API_URL + '/servers/' + serverID,
1407
        type: "PUT",
1408
        contentType: "application/json",
1409
        dataType: "json",
1410
        data: JSON.stringify(payload),
1411
        timeout: TIMEOUT,
1412
        error: function(jqXHR, textStatus, errorThrown) {
1413
                    try {
1414
                        display_failure(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1415
                    } catch(err) {
1416
                        display_failure(0, serverID, 'Rename', jqXHR.responseText);
1417
                    }
1418
                },
1419
        success: function(data, textStatus, jqXHR) {
1420
                    if ( jqXHR.status == '204' || jqXHR.status == '1223') {
1421
                        try {
1422
                            console.info('renamed ' + serverID);
1423
                        } catch(err) {}
1424
                        // indicate that the action succeeded
1425
                        display_success(serverID);
1426
                    } else {
1427
                        ajax_error(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1428
                    }
1429
                }
1430
    });
1431
    return false;
1432
}
1433

    
1434
// get server metadata
1435
function get_metadata(serverID, keys_only) {
1436
    $.ajax({
1437
        url: API_URL + '/servers/' + serverID + '/meta',
1438
        cache: false,
1439
        type: "GET",
1440
        //async: false,
1441
        dataType: "json",
1442
        timeout: TIMEOUT,
1443
        error: function(jqXHR, textStatus, errorThrown) {
1444
            try {
1445
                // close wizard and show error box
1446
                $("a#metadata-scrollable").data('overlay').close();
1447
                ajax_error(jqXHR.status, undefined, 'Get metadata', jqXHR.responseText);
1448
            } catch (err) {
1449
                if (!isXhrException(err)) {
1450
                    ajax_error(-511, "UI Error", "Get metadata", err);
1451
                } else {
1452
                    return false;
1453
                }
1454
            }
1455
        },
1456
        success: function(data, textStatus, jqXHR) {
1457
            // to list the new results in the edit dialog
1458
            if (keys_only) {
1459
                list_metadata_keys(serverID, data.metadata.values);
1460
            } else {
1461
                list_metadata(data);
1462
                list_metadata_keys(serverID, data.metadata.values);
1463
            }
1464
            //hide spinner
1465
            $('#metadata-wizard .large-spinner').hide();
1466
        }
1467
    });
1468
    return false;
1469
}
1470

    
1471
// delete metadata key-value pair
1472
function delete_metadata(serverID, meta_key) {
1473
    $.ajax({
1474
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1475
        type: "DELETE",
1476
        //async: false,
1477
        dataType: "json",
1478
        timeout: TIMEOUT,
1479
        error: function(jqXHR, textStatus, errorThrown) {
1480
            try {
1481
                // close wizard and show error box
1482
                $("a#metadata-scrollable").data('overlay').close();
1483
                ajax_error(jqXHR.status, undefined, 'Delete metadata', jqXHR.responseText);
1484
            } catch (err) {
1485
                if (!isXhrException(err)) {
1486
                    ajax_error(-512, "UI Error", "Delete metadata", err);
1487
                } else {
1488
                    return false;
1489
                }
1490
            }
1491
        },
1492
        success: function(data, textStatus, jqXHR) {
1493
                    // success: Do nothing, the UI is already updated
1494
        }
1495
    });
1496
    return false;
1497
}
1498

    
1499
// add metadata key-value pair
1500
function update_metadata(serverID, meta_key, meta_value) {
1501
    var payload = {
1502
        "meta": {
1503
        }
1504
    };
1505
    payload["meta"][meta_key] = meta_value;
1506

    
1507
    $.ajax({
1508
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1509
        type: "PUT",
1510
        contentType: "application/json",
1511
        dataType: "json",
1512
        data: JSON.stringify(payload),
1513
        timeout: TIMEOUT,
1514
        error: function(jqXHR, textStatus, errorThrown) {
1515
            try {
1516
                // close wizard and show error box
1517
                $("a#metadata-scrollable").data('overlay').close();
1518
                ajax_error(jqXHR.status, undefined, 'Add metadata', jqXHR.responseText);
1519
            } catch (err) {
1520
                if (!isXhrException(err)) {
1521
                    ajax_error(-513, "UI Error", "Add metadata", err);
1522
                } else {
1523
                    return false;
1524
                }
1525
            }
1526
        },
1527
        success: function(data, textStatus, jqXHR) {
1528
            // success: Update icons if meta key is OS
1529
            if (meta_key == "OS") {
1530
                $("#metadata-wizard .machine-icon").attr("src","static/icons/machines/small/" + os_icon_from_value(meta_value) + '-' + $("#metadata-wizard div#on-off").text() + '.png');
1531
                var machine_icon = $("#machinesview-icon").find("div#" + serverID);
1532
                var machine_single = $("#machinesview-single").find("div#" + serverID);
1533

    
1534
                var os = os_icon_from_value(meta_value);
1535
                var state = $("#metadata-wizard div#on-off").text()
1536
                var state_single = $(".state", machine_single).hasClass("terminated-state") ? "off" : "on";
1537

    
1538
                set_machine_os_image(machine_icon, "icon", state, os);
1539
                set_machine_os_image(machine_single, "single", state_single, os);
1540
            }
1541
        }
1542
    });
1543
    return false;
1544
}
1545

    
1546
// get stats
1547
function get_server_stats(serverID) {
1548
    
1549
    // do not update stats if machine in build state
1550
    var vm = get_machine(serverID);
1551
    if (vm.status == "BUILD" && vm.stats_timeout) {
1552
        els = get_current_view_stats_elements(vm.id);
1553
        els.cpu.img.hide();
1554
        els.net.img.hide();
1555

    
1556
        els.cpu.busy.show();
1557
        els.net.busy.show();
1558
        return;
1559
    }
1560

    
1561
    $.ajax({
1562
        url: API_URL + '/servers/' + serverID + '/stats',
1563
        cache: false,
1564
        type: "GET",
1565
        //async: false,
1566
        dataType: "json",
1567
        timeout: TIMEOUT,
1568
        error: function(jqXHR, textStatus, errorThrown) {
1569
            try {
1570
                ajax_error(jqXHR.status, undefined, 'Get server stats', jqXHR.responseText);
1571
            } catch(err) {
1572
                if (!isXhrException(err)) {
1573
                    ajax_error(-520, "UI Error", "Get server stats", err);
1574
                }
1575
            }
1576
        },
1577
        success: function(data, textStatus, jqXHR) {
1578
            update_machine_stats(serverID, data);
1579
        },
1580

    
1581
        // pass server id to ajax settings
1582
        serverID: serverID
1583
    });
1584
    return false;
1585
}
1586

    
1587
// set timeout function to update machine stats
1588
function set_stats_update_handler(vm_id, interval, clear) {
1589
    var vm = get_machine(vm_id);
1590

    
1591
    if (clear) {
1592
        window.clearInterval(vm.stats_timeout);
1593
        vm.stats_timeout = false;
1594
        return;
1595
    }
1596
    
1597
    if (!vm.stats_timeout) {
1598
        vm.stats_timeout = window.setInterval(function(){
1599
            get_server_stats(vm_id);
1600
        }, interval * 1000);
1601
    }
1602
}
1603

    
1604
// update machine stats
1605
// call set_stats_update_handler if machine stats are visible
1606
// to reupdate the stats (based on api interval)
1607
function update_machine_stats(vm_id, data) {
1608
    var els = get_current_view_stats_elements(vm_id);
1609
    var from_error = false;
1610
    var vm = get_machine(vm_id);    
1611
    var clear = false;
1612

    
1613
    // api error
1614
    if (!data) {
1615
        from_error = true;
1616
    }
1617

    
1618
    // hide helpers
1619
    function hide_imgs(els) {
1620
        els.cpu.img.hide();
1621
        els.net.img.hide();
1622
    }
1623

    
1624
    function hide_busy(els) {
1625
        els.cpu.busy.hide();
1626
        els.net.busy.hide();
1627
    }
1628

    
1629
    function hide_errors(els) {
1630
        els.cpu.error.hide();
1631
        els.net.error.hide();
1632
    }
1633

    
1634
    // apply logic
1635
    if (from_error) {
1636
        // api call returned error show error messages
1637
        clear = true;
1638
    } else {
1639
        // no need to show stats while machine in building state
1640
        if (vm.status == "BUILD") {
1641
            hide_imgs(els);
1642
            hide_errors(els);
1643
            els.cpu.busy.show();
1644
            els.net.busy.show();
1645
        } else {
1646
            hide_busy(els);
1647

    
1648
            // update stats, decide for series or bar image
1649
            // based on img class
1650
            if (els.cpu.img.hasClass("series")) {
1651
                els.cpu.img.attr("src", data.stats.cpuTimeSeries);
1652
            } else {
1653
                els.cpu.img.attr("src", data.stats.cpuBar);
1654
            }
1655

    
1656
            if (els.net.img.hasClass("series")) {
1657
                els.net.img.attr("src", data.stats.netTimeSeries);
1658
            } else {
1659
                els.net.img.attr("src", data.stats.netBar);
1660
            }
1661
        }
1662
    }
1663

    
1664
    // stats container is hidden
1665
    // do not update the stats
1666
    if (!els.cont.is(":visible")) {
1667
        clear = true;
1668
    }
1669
    
1670
    // set timeout to call the stats update
1671
    set_stats_update_handler(vm_id, data.stats.refresh, clear);
1672
}
1673

    
1674

    
1675
// get stats elements based on current view
1676
function get_current_view_stats_elements(vm_id) {
1677
        // in icon view
1678
        if ( $.cookie('view') == 0 ) {
1679
            vm_el = $("#" + vm_id);
1680
            return {
1681
                'cont': vm_el.find('.vm-stats'),
1682
                'cpu': {
1683
                    'img': vm_el.find(' img.cpu'), 
1684
                    'busy': vm_el.find('.cpu-cont .stat-busy'),
1685
                    'error': vm_el.find('.cpu-cont .stat-error')
1686
                },
1687
                'net': { 
1688
                    'img': vm_el.find('img.net'),
1689
                    'busy': vm_el.find('.net-cont .stat-busy'),
1690
                    'error': vm_el.find('.net-cont .stat-error')
1691
                }
1692
            }
1693
        }
1694
        // in single view
1695
        else if ( $.cookie('view') == 2 ) {
1696
            vm_el = $("#" + vm_id);
1697
            return {
1698
                'cont': vm_el.find('.lower'),
1699
                'cpu': {
1700
                    'img': vm_el.find('div.cpu-graph img.stats'), 
1701
                    'busy': vm_el.find('div.cpu-graph img.stat-busy'),
1702
                    'error': vm_el.find('div.cpu-graph .stat-error')
1703
                },
1704
                'net': { 
1705
                    'img': vm_el.find('div.network-graph img.stats'),
1706
                    'busy': vm_el.find('div.network-graph img.stat-busy'),
1707
                    'error': vm_el.find('div.network-graph .stat-error')
1708
                }
1709
            }
1710
        }
1711
}
1712

    
1713
// create network
1714
function create_network(networkName){
1715
    // ajax post start call
1716
    var payload = {
1717
        "network": { "name": networkName }
1718
    };
1719

    
1720
    $.ajax({
1721
        url: API_URL + '/networks',
1722
        type: "POST",
1723
        contentType: "application/json",
1724
        dataType: "json",
1725
        data: JSON.stringify(payload),
1726
        timeout: TIMEOUT,
1727
        error: function(jqXHR, textStatus, errorThrown) {
1728
            try {
1729
                // close wizard and show error box
1730
                $("a#networkscreate").overlay().close();
1731
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1732
            } catch (err) {
1733
                if (!isXhrException(err)) {
1734
                    ajax_error(-514, "UI Error", "Create network", err);
1735
                } else {
1736
                    return false;
1737
                }
1738
            }
1739
        },
1740
        success: function(data, textStatus, jqXHR) {
1741
            if ( jqXHR.status == '202') {
1742
                try {
1743
                    console.info('created network ' + networkName);
1744
                } catch(err) {}
1745
                /*
1746
                On success of this call nothing happens.
1747
                When the UI gets the first update containing the created server,
1748
                the creation wizard is closed and the new network is inserted
1749
                to the DOM. This is done in update_networks_view()
1750
                */
1751
            } else {
1752
                // close wizard and show error box
1753
                $("a#networkscreate").overlay().close();
1754
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1755
            }
1756
        }
1757
    });
1758
    return false;
1759
}
1760

    
1761
// rename network
1762
function rename_network(networkID, networkName){
1763
    if (!networkID.length){
1764
        //ajax_success('DEFAULT');
1765
        return false;
1766
    }
1767
    // prepare payload
1768
    var payload = {
1769
        "network": {"name": networkName}
1770
    };
1771
    // ajax call
1772
    $.ajax({
1773
        url: API_URL + '/networks/' + networkID,
1774
        type: "PUT",
1775
        contentType: "application/json",
1776
        dataType: "json",
1777
        data: JSON.stringify(payload),
1778
        timeout: TIMEOUT,
1779
        error: function(jqXHR, textStatus, errorThrown) {
1780
            try {
1781
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1782
            } catch (err) {
1783
                if (!isXhrException(err)) {
1784
                    ajax_error(-515, "UI Error", 'Rename network', err);
1785
                } else {
1786
                    return false;
1787
                }
1788
            }
1789
        },
1790
        success: function(data, textStatus, jqXHR) {
1791
            if ( jqXHR.status == '204') {
1792
                try {
1793
                    console.info('renamed network' + networkID);
1794
                } catch(err) {}
1795
            } else {
1796
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1797
            }
1798
        }
1799
    });
1800
    return false;
1801
}
1802

    
1803
function delete_network(networkIDs){
1804
    if (!networkIDs.length){
1805
        //ajax_success('DEFAULT');
1806
        return false;
1807
    }
1808
    // get a network
1809
    var networkID = networkIDs.pop();
1810
    // ajax post destroy call can have an empty request body
1811
    var payload = {};
1812
    // ajax call
1813
    $.ajax({
1814
        url: API_URL + '/networks/' + networkID,
1815
        type: "DELETE",
1816
        contentType: "application/json",
1817
        dataType: "json",
1818
        data: JSON.stringify(payload),
1819
        timeout: TIMEOUT,
1820
        error: function(jqXHR, textStatus, errorThrown) {
1821
            try {
1822
                display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1823
            } catch (err) {
1824
                display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1825
            }
1826
        },
1827
        success: function(data, textStatus, jqXHR) {
1828
            if ( jqXHR.status == '204') {
1829
                try {
1830
                    console.info('deleted network ' + networkID);
1831
                } catch(err) {}
1832
                // continue with the rest of the servers
1833
                delete_network(networkIDs);
1834
            } else {
1835
                try {
1836
                    display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1837
                } catch (err) {
1838
                    display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1839
                }
1840
            }
1841
        }
1842
    });
1843
    return false;
1844
}
1845

    
1846
function add_server_to_network(networkID, serverIDs, serverNames, serverStates) {
1847
    if (!serverIDs.length){
1848
        // close the overlay when all the calls are made
1849
        $("a#add-machines-overlay").overlay().close();
1850
        return false;
1851
    }
1852
    // get a server
1853
    var serverID = serverIDs.pop();
1854
    var serverName = serverNames.pop();
1855
    var serverState = serverStates.pop();
1856
    // prepare payload
1857
    var payload = {
1858
            "add": { "serverRef": serverID }
1859
        };
1860
    // prepare ajax call
1861
    $.ajax({
1862
        url: API_URL + '/networks/' + networkID + '/action',
1863
        type: "POST",
1864
        contentType: "application/json",
1865
        dataType: "json",
1866
        data: JSON.stringify(payload),
1867
        timeout: TIMEOUT,
1868
        error: function(jqXHR, textStatus, errorThrown) {
1869
            try {
1870
                // close wizard and show error box
1871
                $("a#add-machines-overlay").data('overlay').close();
1872
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1873
            } catch (err) {
1874
                if (!isXhrException(err)) {
1875
                    ajax_error(-516, "UI Error", 'Add server to network', err);
1876
                } else {
1877
                    return false;
1878
                }
1879
            }
1880
        },
1881
        success: function(data, textStatus, jqXHR) {
1882
            if ( jqXHR.status == '202') {
1883
                try {
1884
                    console.info('added server ' + serverID + ' to network ' + networkID);
1885
                } catch(err) {}
1886
                // toggle the reboot dialog
1887
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1888
                // continue with the rest of the servers
1889
                add_server_to_network(networkID, serverIDs, serverNames, serverStates);
1890
            } else {
1891
                // close wizard and show error box
1892
                $("a#add-machines-overlay").data('overlay').close();
1893
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1894
            }
1895
        }
1896
    });
1897
    return false;
1898
}
1899

    
1900
function remove_server_from_network(networkIDs, serverIDs, serverNames, serverStates) {
1901
    if (!networkIDs.length){
1902
        //ajax_success('DEFAULT');
1903
        return false;
1904
    }
1905
    // get a network and a server
1906
    var networkID = networkIDs.pop();
1907
    var serverID = serverIDs.pop();
1908
    var serverName = serverNames.pop();
1909
    var serverState = serverStates.pop();
1910
    // prepare payload
1911
    var payload = {
1912
            "remove": { "serverRef": serverID }
1913
        };
1914
    // prepare ajax call
1915
    $.ajax({
1916
        url: API_URL + '/networks/' + networkID + '/action',
1917
        type: "POST",
1918
        contentType: "application/json",
1919
        dataType: "json",
1920
        data: JSON.stringify(payload),
1921
        timeout: TIMEOUT,
1922
        error: function(jqXHR, textStatus, errorThrown) {
1923
            try {
1924
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1925
            } catch (err) {
1926
                if (!isXhrException(err)) {
1927
                    ajax_error(-517, "UI Error", 'Remove server form network', err);
1928
                } else {
1929
                    return false;
1930
                }
1931
            }
1932
        },
1933
        success: function(data, textStatus, jqXHR) {
1934
            if ( jqXHR.status == '202') {
1935
                try {
1936
                    console.info('deleted server ' + serverID + ' from network ' + networkID);
1937
                } catch(err) {}
1938
                // toggle the reboot dialog
1939
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1940
                // continue with the rest of the servers
1941
                remove_server_form_network(networkIDs, serverIDs, serverNames, serverStates);
1942
            } else {
1943
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1944
            }
1945
        }
1946
    });
1947
    return false;
1948
}
1949

    
1950
function set_firewall(networkID, serverID, profile) {
1951
    if (!networkID.length || !serverID.length || !profile.length){
1952
        return false;
1953
    }
1954
    // prepare payload
1955
    var payload = {
1956
            "firewallProfile": { "profile": profile }
1957
    };
1958

    
1959
    // prepare ajax call
1960
    $.ajax({
1961
        url: API_URL + '/servers/' + serverID + '/action',
1962
        type: "POST",
1963
        contentType: "application/json",
1964
        dataType: "json",
1965
        data: JSON.stringify(payload),
1966
        timeout: TIMEOUT,
1967
        error: function(jqXHR, textStatus, errorThrown) {
1968
            try {
1969
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1970
            } catch (err) {
1971
                if (!isXhrException(err)) {
1972
                    ajax_error(-518, "UI Error", 'Set firewall profile', err);
1973
                } else {
1974
                    return false;
1975
                }
1976
            }
1977
        },
1978
        success: function(data, textStatus, jqXHR) {
1979
            if ( jqXHR.status == '202') {
1980
                try {
1981
                    console.info('for server ' + serverID + ' set firewall profile to ' + profile);
1982
                } catch(err) {}
1983
                // toggle the reboot dialog
1984
                try {
1985

    
1986
                    var serverName = $('div#net-' + networkID + '-server-' + serverID + ' div.machine-name-div span.name').text();
1987
                    var serverState = $('div#net-' + networkID + '-server-' + serverID + ' img.logo').attr('src').split('-')[1];
1988
                    serverState = serverState.split('.')[0];
1989
                    display_reboot_dialog(networkID, serverID, serverName, serverState);
1990

    
1991
                    //remove progress gif and toggle the content
1992
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').html(VARIOUS["APPLY"]);
1993
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').attr("disabled", false);
1994
                    $('div#net-' + networkID + '-server-' + serverID + ' div.firewall-header').click();
1995

    
1996
                } catch (err) {
1997
                }
1998
                
1999
                // api call was made, set transition state to get reset 
2000
                // on the next machines update api call
2001
                var vm = get_machine(serverID)
2002
                vm.network_transition = "NETWORK_CHANGE";
2003
                show_machine_network_indicator(vm.id, 'pub');
2004
            } else {
2005
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
2006
            }
2007
        }
2008
    });
2009
    return false;
2010
}
2011

    
2012
// show the welcome screen
2013
function showWelcome() {
2014
    $("#view-select").fadeOut("fast");
2015
    $("#emptymachineslist").fadeIn("fast");
2016
    $("#machinesview").hide();
2017
}
2018

    
2019
// hide the welcome screen
2020
function hideWelcome() {
2021
    $("#emptymachineslist").fadeOut("fast");
2022
    $("#view-select").fadeIn("fast");
2023
    $("div#view-select").show();
2024
    $("#machinesview").show();
2025
}
2026

    
2027
function log_server_status_change(server_entry, new_status) {
2028
    // firebug console logging
2029
    try {
2030
        if ($("#machinesview-single").length > 0) {
2031
            console.info(server_entry.find("div.machine-details div.name").text() +
2032
                        ' from ' + server_entry.find(".state-label").text() +
2033
                        ' to ' + STATUSES[new_status]);
2034
        } else {
2035
            console.info(server_entry.find("div.name span.name").text() +
2036
                        ' from ' + server_entry.find(".status").text().replace(TRANSITION_STATE_APPEND, "") +
2037
                        ' to ' + STATUSES[new_status]);
2038
        }
2039
    } catch(err) {}
2040
}
2041

    
2042
function get_flavor_params(flavorRef) {
2043
    var cpus, ram, disk;
2044
    if ( flavors.length > 0 ) {
2045
        var current_flavor = '';
2046
        for (i=0; i<flavors.length; i++) {
2047
            if (flavors[i]['id'] == flavorRef) {
2048
                current_flavor = flavors[i];
2049
            }
2050
        }
2051
        cpus = current_flavor['cpu'];
2052
        ram = current_flavor['ram'];
2053
        disk = current_flavor['disk'];
2054
    } else {
2055
        cpus = 'undefined';
2056
        ram = 'undefined';
2057
        disk = 'undefined';
2058
    }
2059
    return {'cpus': cpus, 'ram': ram, 'disk': disk};
2060
}
2061

    
2062
function get_image_params(imageRef) {
2063
    var image_name, image_size;
2064
    if ( images.length > 0 ) {
2065
        var current_image = '';
2066
        for (i=0; i<images.length; i++) {
2067
            if (images[i]['id'] == imageRef) {
2068
                current_image = images[i];
2069
            }
2070
        }
2071
        try {
2072
            image_name = current_image['name'];
2073
        } catch(err) { image_name = 'undefined'; }
2074
        try{
2075
            image_size = current_image['metadata']['values']['size'];
2076
        } catch(err) { image_size = 'undefined'; }
2077
    } else {
2078
        image_name = 'undefined';
2079
        image_size = 'undefined';
2080
    }
2081
    return {'name': image_name,'size': image_size};
2082
}
2083

    
2084
function get_public_ips(server) {
2085
    var ip4, ip6;
2086
    try {
2087
        if (server.addresses.values) {
2088
            $.each (server.addresses.values, function(i, value) {
2089
                if (value.id == 'public') {
2090
                    try {
2091
                        $.each (value.values, function(i, ip) {
2092
                            if (ip.version == '4') {
2093
                                ip4 = ip.addr;
2094
                            } else if (ip.version == '6') {
2095
                                ip6 = ip.addr;
2096
                            } else {
2097
                                ip4 = 'pending';
2098
                                ip6 = 'pending';
2099
                            }
2100
                        });
2101
                    } catch (err){
2102
                        try{console.info('Server ' + server.id + ' has invalid ips')}catch(err){};
2103
                        ip4 = 'pending';
2104
                        ip6 = 'pending';
2105
                    }
2106
                }
2107
            });
2108
        }
2109
    } catch (err) {
2110
        try{console.info('Server ' + server.id + ' has no network addresses')}catch(err){};
2111
        ip4 = 'pending';
2112
        ip6 = 'pending';
2113
    }
2114
    return {'ip4': ip4, 'ip6': ip6};
2115
}
2116

    
2117
function get_private_ips(server) {
2118

    
2119
}
2120

    
2121
function close_all_overlays() {
2122
        try {
2123
                $("a#networkscreate").overlay().close();
2124
        } catch(err) {}
2125
        try {
2126
                $('a#create').overlay().close();
2127
        } catch(err) {}
2128
        try {
2129
                $("a#add-machines-overlay").overlay().close();
2130
        } catch(err) {}
2131
        try {
2132
                $("a#metadata-scrollable").overlay().close();
2133
        } catch(err) {}
2134
        try {
2135
                $("a#msgbox").overlay().close();
2136
        } catch(err) {}
2137
        try {
2138
                $("a#feedbackbox").overlay().close();
2139
        } catch(err) {}
2140
}
2141

    
2142
// logout
2143
function user_session_logout() {
2144
    $.cookie("X-Auth-Token", null);
2145
    if (window.LOGOUT_REDIRECT !== undefined)
2146
    {
2147
        window.location = window.LOGOUT_REDIRECT;
2148
    } else {
2149
        window.location.reload();
2150
    }
2151
}
2152

    
2153
// action indicators
2154
function init_action_indicator_handlers(machines_view)
2155
{
2156
    // init once for each view
2157
    if (window.ACTION_ICON_HANDLERS == undefined)
2158
    {
2159
        window.ACTION_ICON_HANDLERS = {};
2160
    }
2161

    
2162
    if (machines_view in window.ACTION_ICON_HANDLERS)
2163
    {
2164
        return;
2165
    }
2166
    window.ACTION_ICON_HANDLERS[machines_view] = 1;
2167

    
2168
    if (machines_view == "list")
2169
    {
2170
        // totally different logic for list view
2171
        init_action_indicator_list_handlers();
2172
        return;
2173
    }
2174

    
2175
    function update_action_icon_indicators(force)
2176
    {
2177
        function show(el, action) {
2178
            $(".action-indicator", $(el)).attr("class", "action-indicator " + action);
2179
            $(".action-indicator", $(el)).show();
2180
        }
2181

    
2182
        function hide(el) {
2183
            $(".action-indicator", $(el)).hide();
2184
        }
2185

    
2186
        function get_pending_actions(el) {
2187
            return $(".confirm_single:visible", $(el));
2188
        }
2189

    
2190
        function other_indicators(el) {
2191
           return $("img.wave:visible, img.spinner:visible", $(el))
2192
        }
2193

    
2194
        $("div.machine:visible, div.single-container").each(function(index, el){
2195
            var el = $(el);
2196
            var pending = get_pending_actions(el);
2197
            var other = other_indicators(el);
2198
            var action = undefined;
2199
            var force_action = force;
2200
            var visible = $(el).css("display") == "block";
2201

    
2202
            if (force_action !==undefined && force_action.el !== el[0]) {
2203
                // force action for other vm
2204
                // skipping force action
2205
                force_action = undefined;
2206
            }
2207

    
2208
            if (force_action !==undefined && force_action.el === el[0]) {
2209
                action = force_action.action;
2210
            }
2211

    
2212
            if (other.length >= 1) {
2213
                return;
2214
            }
2215

    
2216
            if (pending.length >= 1 && force_action === undefined) {
2217
                action = $(pending.parent()).attr("class").replace("action-container","");
2218
            }
2219

    
2220
            if (action in {'console':''}) {
2221
                return;
2222
            }
2223

    
2224
            if (action !== undefined) {
2225
                show(el, action);
2226
            } else {
2227
                try {
2228
                    if (el.attr('id') == pending_actions[0][1])
2229
                    {
2230
                        return;
2231
                    }
2232
                } catch (err) {
2233
                }
2234
                hide(el);
2235
            }
2236

    
2237
        });
2238
    }
2239

    
2240
    // action indicators
2241
    $(".action-container").live('mouseover', function(evn) {
2242
        force_action = {'el': $(evn.currentTarget).parent().parent()[0], 'action':$(evn.currentTarget).attr("class").replace("action-container","")};
2243
        // single view case
2244
        if ($(force_action.el).attr("class") == "upper")
2245
        {
2246
            force_action.el = $(evn.currentTarget).parent().parent().parent()[0]
2247
        };
2248
        update_action_icon_indicators(force_action);
2249
    });
2250

    
2251
    $("img.spinner, img.wave").live('hide', function(){
2252
        update_action_icon_indicators();
2253
    });
2254
    // register events where icons should get updated
2255

    
2256
    // hide action indicator image on mouse out, spinner appear, wave appear
2257
    $(".action-container").live("mouseout", function(evn){
2258
        update_action_icon_indicators();
2259
    });
2260

    
2261
    $(".confirm_single").live("click", function(evn){
2262
        update_action_icon_indicators();
2263
    });
2264

    
2265
    $("img.spinner, img.wave").live('show', function(){
2266
        $("div.action-indicator").hide();
2267
    });
2268

    
2269
    $(".confirm_single button.no").live('click', function(evn){
2270
        $("div.action-indicator", $(evn.currentTarget).parent().parent()).hide();
2271
    });
2272

    
2273
    $(".confirm_multiple button.no").click(function(){
2274
        $("div.action-indicator").hide();
2275
    });
2276

    
2277
    $(".confirm_multiple button.yes").click(function(){
2278
        $("div.action-indicator").hide();
2279
    });
2280
}
2281

    
2282
function init_action_indicator_list_handlers()
2283
{
2284
    var skip_actions = { 'connect':'','details':'' };
2285

    
2286
    var has_pending_confirmation = function()
2287
    {
2288
        return $(".confirm_multiple:visible").length >= 1
2289
    }
2290

    
2291
    function update_action_indicator_icons(force_action, skip_pending)
2292
    {
2293
        // pending action based on the element class
2294
        var pending_action = $(".selected", $(".actions"))[0];
2295
        var selected = get_list_view_selected_machine_rows();
2296

    
2297
        // reset previous state
2298
        list_view_hide_action_indicators();
2299

    
2300
        if (pending_action == undefined && !force_action)
2301
        {
2302
            // no action selected
2303
            return;
2304
        }
2305

    
2306
        if (force_action != undefined)
2307
        {
2308
            // user forced action choice
2309
            var action_class = force_action;
2310
        } else {
2311
            // retrieve action name (reboot, stop, etc..)
2312
            var action_class = $(pending_action).attr("id").replace("action-","");
2313
        }
2314

    
2315
        selected.each(function(index, el) {
2316
            if (has_pending_confirmation() && skip_pending)
2317
            {
2318
                return;
2319
            }
2320
            var el = $(el);
2321
            var logo = $("img.list-logo", el);
2322
            $(".action-indicator", el).remove();
2323
            var cls = "action-indicator " + action_class;
2324
            // add icon div
2325
            logo.after('<div class="' + cls + '"></div>');
2326
            // hide os logo
2327
            $("img.list-logo", el).hide();
2328
        });
2329
    }
2330

    
2331
    // on mouseover we force the images to the hovered action
2332
    $(".actions a").live("mouseover", function(evn) {
2333
        var el = $(evn.currentTarget);
2334
        if (!el.hasClass("enabled"))
2335
        {
2336
            return;
2337
        }
2338
        var action_class = el.attr("id").replace("action-","");
2339
        if (action_class in skip_actions)
2340
        {
2341
            return;
2342
        }
2343
        update_action_indicator_icons(action_class, false);
2344
    });
2345

    
2346

    
2347
    // register events where icons should get updated
2348
    $(".actions a.enabled").live("click", function(evn) {
2349
        // clear previous selections
2350
        $("a.selected").removeClass("selected");
2351

    
2352
        var el = $(evn.currentTarget);
2353
        el.addClass("selected");
2354
        update_action_indicator_icons(undefined, false);
2355
    });
2356

    
2357
    $(".actions a").live("mouseout", function(evn) {
2358
        update_action_indicator_icons(undefined, false);
2359
    });
2360

    
2361
    $(".confirm_multiple button.no").click(function(){
2362
        list_view_hide_action_indicators();
2363
    });
2364

    
2365
    $(".confirm_multiple button.yes").click(function(){
2366
        list_view_hide_action_indicators();
2367
    });
2368

    
2369
    $("input[type=checkbox]").live('change', function(){
2370
        // pending_actions will become empty on every checkbox click/change
2371
        // line 154 machines_list.html
2372
        pending_actions = [];
2373
        if (pending_actions.length == 0)
2374
        {
2375
            $(".confirm_multiple").hide();
2376
            $("a.selected").each(function(index, el){$(el).removeClass("selected")});
2377
        }
2378
        update_action_indicator_icons(undefined, false);
2379
    });
2380

    
2381
}
2382

    
2383
function list_view_hide_action_indicators()
2384
{
2385
    $("tr td .action-indicator").remove();
2386
    $("tr td img.list-logo").show();
2387
}
2388

    
2389
function get_list_view_selected_machine_rows()
2390
{
2391
    var table = $("table.list-machines");
2392
    var rows = $("tr:has(input[type=checkbox]:checked)",table);
2393
    return rows;
2394
}
2395

    
2396
// machines images utils
2397
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) {
2398
    var views_map = {'single': '.single-image', 'icon': '.logo'};
2399
    var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'}
2400
    var sizes_map = {'single': 'large', 'icon': 'medium'}
2401

    
2402
    var size = sizes_map[machines_view];
2403
    var img_selector = views_map[machines_view];
2404
    var cls = states_map[state];
2405

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

    
2409
    var el = $(img_selector, machine);
2410
    var current_img = el.css("backgroundImage");
2411
    if (os == undefined){
2412
        new_img = current_img;
2413
    }
2414

    
2415
    // os changed
2416
    el.css("backgroundImage", new_img);
2417

    
2418
    // reset current state
2419
    if (skip_reset_states === undefined)
2420
    {
2421
        el.removeClass("single-image-state1");
2422
        el.removeClass("single-image-state2");
2423
        el.removeClass("single-image-state3");
2424
        el.removeClass("single-image-state4");
2425
    }
2426

    
2427
    if (remove_state !== undefined)
2428
    {
2429
        remove_state = "single-image-" + states_map[remove_state];
2430
        el.removeClass(remove_state);
2431
        return;
2432
    }
2433
    
2434
    // set proper state
2435
    el.addClass("single-image-" + cls);
2436
}
2437

    
2438

    
2439
// generic info box
2440
function show_feedback_form(msg, from_error) {
2441
    var box = $("#feedback-form");
2442
    box.addClass("notification-box");
2443

    
2444
    // initialize
2445
    box.find(".form-container").show();
2446
    box.find("textarea").val("");
2447
    box.find(".message").hide();
2448
    
2449
    var initial_msg = msg || undefined;
2450
    
2451
    var triggers = $("a#feedbackbox").overlay({
2452
        // some mask tweaks suitable for modal dialogs
2453
        mask: '#666',
2454
        top: '10px',
2455
        fixed: false,
2456
        closeOnClick: false,
2457
        oneInstance: false,
2458
        load: false
2459
    });
2460

    
2461
    
2462
    if (initial_msg && from_error) {
2463
        // feedback form from ajax_error window
2464
        box.find("textarea").val(initial_msg);
2465
        $("a#feedbackbox").overlay().onClose(function(){window.location.reload()});
2466
        box.find("textarea").height(200);
2467
        $("a#feedbackbox").overlay().onLoad(function(){box.find("textarea").focus().setCursorPosition(500);});
2468
        
2469
    }
2470

    
2471
    $("#feedback-form form").unbind("submit");
2472
    $("#feedback-form form").submit(function(event) {
2473
        event.preventDefault();
2474
            
2475
        // empty msg
2476
        if ($("textarea.feedback-text").val().replace(/^\s*|\s*$/,"") == "") {
2477
            alert($(".empty-error-msg", this).text());
2478
            return;
2479
        }
2480

    
2481
        $("textarea.data-text", this).val("").val(get_user_data_json());
2482

    
2483
        $.ajax({
2484
            url: FEEDBACK_URL,
2485
            data: $(this).serialize(),
2486
            type: "POST",
2487
            // show loading
2488
            beforeSend: function() {box.find(".form-container").hide(); box.find(".sending").fadeIn() },
2489
            // hide form
2490
            complete: function() { box.find(".form-container").hide(); box.find(".sending").hide() },
2491
            // on success display success message
2492
            success: function() { box.find(".success").fadeIn(); box.find(".sending").hide() },
2493
            // display error message
2494
            error: function() { box.find(".errormsg").fadeIn(); box.find(".sending").hide() }
2495
        })
2496
    });
2497
    
2498
    $("a#feedbackbox").data('overlay').load();
2499

    
2500
    // reset feedback_pending for ajax_errors
2501
    window.FEEDBACK_PENDING = false;
2502
    return false;
2503
}
2504

    
2505
function get_user_data(extra_data) {
2506
    try {
2507
        var last_req = $.extend({}, last_request);
2508

    
2509
        // reset xhr, might raise exceptions while converting to JSON
2510
        last_req.xhr = {};
2511
    } catch (err) {
2512
        var last_req = {}
2513
    }
2514

    
2515
    return $.extend({
2516
        'servers': $.extend({}, servers),
2517
        'client': {'browser': $.browser, 'screen': $.extend({}, screen), 'client': $.client},
2518
        'dates': {'now': new Date, 'lastUpdate': changes_since_date},
2519
        'last_request': last_req
2520
    }, extra_data);
2521
}
2522

    
2523
function get_user_data_json() {
2524
    try {
2525
        return JSON.stringify(get_user_data());
2526
    } catch (err) {
2527
        return JSON.stringify({'error': err});
2528
    }
2529
}
2530

    
2531
function msg_box(config) {
2532
    var config = $.extend({'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false}, config);
2533
    // prepare the error message
2534
    // bring up success notification
2535

    
2536
    var box = $("#notification-box");
2537
    box.addClass("notification-box");
2538
    box.addClass('success');
2539
    box.removeClass('error');
2540

    
2541
    var sel = function(s){return $(s, box)};
2542
    // reset texts
2543
    sel("h3 span.header-box").html("");
2544
    sel(".sub-text").html("");
2545
    sel(".password-container .password").html("");
2546
    sel("div.machine-now-building").html("");
2547
    
2548

    
2549
    // apply msg box contents
2550
    sel("h3 span.header-box").html(config.title);
2551
    sel("div.machine-now-building").html(config.content);
2552
    sel(".popup-header").removeClass("popup-header-error");
2553
    box.removeClass("popup-border-error");
2554
    sel(".popup-details").removeClass("popup-details-error");
2555
    sel(".popup-separator").removeClass("popup-separator-error");
2556
    
2557
    sel(".password-container").hide();
2558
    if (config.extra) {
2559
        sel(".password-container .password").html(config.extra);
2560
        sel(".password-container").show();
2561
    }
2562
    
2563
    var conf = {
2564
        // some mask tweaks suitable for modal dialogs
2565
        mask: '#666',
2566
        top: '10px',
2567
        closeOnClick: false,
2568
        oneInstance: false,
2569
        load: false,
2570
        fixed: config.fixed || false,
2571
        onClose: function () {
2572
            // With partial refresh working properly,
2573
            // it is no longer necessary to refresh the whole page
2574
            // choose_view();
2575
        }
2576
    }
2577
    
2578
    var triggers = $("a#msgbox").overlay(conf);
2579

    
2580
    try {
2581
        conf = $("a#msgbox").data('overlay').getConf();
2582
        conf.fixed = config.fixed || false;
2583
    } catch (err) {}
2584
    $("a#msgbox").data('overlay').load();
2585
    
2586
    var parse_data = config.parse_data || false;
2587
    var load_html = config.html || false;
2588
    var user_success = config.success || false;
2589
    config.ajax = config.ajax || {};
2590

    
2591
    // requested to show remote data in msg_box
2592
    if (config.ajax) {
2593
        $.ajax($.extend({ 
2594
            url:config.ajax, 
2595
            success: function(data){
2596
                // we want to get our data parsed before
2597
                // placing them in content
2598
                if (parse_data) {
2599
                    data = parse_data(data);
2600
                }
2601

    
2602
                // no json response
2603
                // load html body
2604
                if (load_html) {
2605
                    sel("div.machine-now-building").html(data);
2606
                } else {
2607

    
2608
                    if (data.title) {
2609
                        sel("h3 span.header-box").text(data.title);
2610
                    }
2611

    
2612
                    if (data.content) {
2613
                        sel("div.machine-now-building").html(data.content);
2614
                    }
2615
                    if (data.extra) {
2616
                        sel(".password-container .password").html(data.extra);
2617
                        sel(".password-container").show();
2618
                    }
2619
                    if (data.subinfo) {
2620
                        sel(".sub-text").html(data.subinfo);
2621
                    } else {
2622
                        sel(".sub-text").html("");
2623
                    }
2624
                }
2625

    
2626
                if (user_success) {
2627
                    user_success($("div.machine-now-building"));
2628
                }
2629
            },
2630
            error: function(xhr, status, err) {
2631
                ajax_error(-519, "UI Error", "Machine connect", err);
2632
            }
2633
        }, config.ajax_config));
2634
    }
2635
    return false;
2636
}
2637

    
2638

    
2639
function show_invitations() {
2640

    
2641
    handle_invitations = function(el) {
2642

    
2643
        // proper class to identify the overlay block
2644
        el.addClass("invitations");
2645

    
2646
        var cont = el;
2647
        var form = $(el).find("form");
2648

    
2649
        // remove garbage rows that stay in DOM between requests
2650
        $(".removable-field-row:hidden").remove();
2651

    
2652
        // avoid buggy behaviour, close all overlays if something went wrong
2653
        try {
2654
            // form is in content (form is not displayed if user has no invitations)
2655
            if ($("#invform #removable-name-container-1").length) {
2656
                $("#invform #removable-name-container-1").dynamicField();
2657
            }
2658
        } catch (err) {
2659
            close_all_overlays();
2660
        }
2661
        
2662
        // we copy/paste it on the title no need to show it twice
2663
        $(".invitations-left").hide();
2664

    
2665
        // reset title
2666
        $("#notification-box .header-box").html("");
2667
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
2668

    
2669
        // handle form submit
2670
        form.submit(function(evn){
2671
            evn.preventDefault();
2672

    
2673
            // do the post
2674
            $.post(form.attr("action"), form.serialize(), function(data) {
2675
                // replace data
2676
                $(cont).html(data); 
2677

    
2678
                // append all handlers again (new html data need to redo all changes)
2679
                handle_invitations(cont);
2680
            });
2681

    
2682
            return false;
2683
        });
2684
    }
2685
    
2686
    // first time clicked (show the msg box with /invitations content)
2687
    msg_box({
2688
        title:window.INVITATIONS_TITLE, 
2689
        content:'', 
2690
        fixed: false,
2691
        ajax:INVITATIONS_URL, 
2692
        html:true, 
2693
        success: function(el){ 
2694
            handle_invitations(el)
2695
        }
2696
    });
2697
}
2698

    
2699

    
2700
function get_short_v6(v6, parts_to_keep) {
2701
    var parts = v6.split(":");
2702
    var new_parts = parts.slice(parts.length - parts_to_keep);
2703
    return new_parts.join(":");
2704
}
2705

    
2706
function fix_v6_addresses() {
2707

    
2708
    // what to prepend
2709
    var match = "...";
2710
    // long ip min length
2711
    var limit = 20;
2712
    // parts to show after the transformation
2713
    // (from the end)
2714
    var parts_to_keep_from_end = 4;
2715

    
2716
    $(".ipv6-text").each(function(index, el){
2717
        var el = $(el);
2718
        var ip = $(el).text();
2719
            
2720
        // transformation not applyied
2721
        // FIXME: use $.data for the condition
2722
        if (ip.indexOf(match) == -1 && ip != "pending") {
2723
            
2724
            // only too long ips
2725
            if (ip.length > 20) {
2726
                $(el).data("ipstring", ip);
2727
                $(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
2728
                $(el).attr("title", ip);
2729
                $(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'});
2730
            }
2731
        } else {
2732
            if (ip.indexOf(match) == 0) {
2733
            } else {
2734
                // not a long ip anymore
2735
                $(el).data("ipstring", undefined);
2736
                $(el).css({'text-decoration':'none'});
2737

    
2738
                if ($(el).data('tooltip')) {
2739
                    $(el).data('tooltip').show = function () {};
2740
                }
2741
            }
2742
        }
2743
    });
2744
}
2745

    
2746
function fix_server_name(str, limit, append) {
2747
    limit = limit || 30;
2748
    append = append || "...";
2749

    
2750
    if (str.length > limit) {
2751
        str = str.substring(0,limit-append.length) + append;
2752
    }
2753
    return str;
2754
}
2755

    
2756
function show_machine_network_indicator(vm_id, network_id) {
2757
    var el = $("div#net-" + network_id + '-server-' + vm_id);
2758
    el.find(".network-progress-indicator").show();
2759
}
2760

    
2761

    
2762
function get_firewall_profile(vm_id) {
2763
    var vm = get_machine(vm_id);
2764

    
2765
    try {
2766
        return vm.addresses.values[0].firewallProfile;
2767
    } catch (err) {
2768
        return undefined;
2769
    }
2770
}
2771

    
2772

    
2773
function get_progress_details(id) {
2774
    var vm = get_machine(id);
2775
    var progress = vm.progress;
2776

    
2777
    // no details for active machines
2778
    if (!vm.status == "BUILD") {
2779
        return false;
2780
    }
2781
    
2782
    // check if images not loaded yet
2783
    try {
2784
        var image = get_image_params(vm.imageRef);
2785
        var size = image.size;
2786
    } catch (err) {
2787
        // images not loaded yet (can this really happen ??)
2788
        return;
2789
    }
2790
    
2791
    var to_copy = size;
2792
    var copied = (size * progress / 100).toFixed(2);
2793
    var status = "INIT"
2794

    
2795
    // apply state
2796
    if (progress > 0) { status = "IMAGE_COPY" }
2797
    if (progress >= 100) { status = "FINISH" }
2798
    
2799
    // user information
2800
    var msg = BUILDING_STATUSES[status];
2801

    
2802
    // image copy state display extended user information
2803
    if (status == "IMAGE_COPY") {
2804
        msg = msg.format(readablizeBytes(copied*(1024*1024)), readablizeBytes(to_copy*(1024*1024)), progress)
2805
    }
2806

    
2807
    var progress_data = {
2808
        'percent': vm.progress,
2809
        'build_status': status,
2810
        'copied': copied,
2811
        'to_copy': size,
2812
        'msg': msg
2813
    }
2814

    
2815
    return progress_data;
2816
}
2817

    
2818
// display user friendly bytes amount
2819
function readablizeBytes(bytes) {
2820
    var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
2821
    var e = Math.floor(Math.log(bytes)/Math.log(1024));
2822
    return (bytes/Math.pow(1024, Math.floor(e))).toFixed(2)+" "+s[e];
2823
}
2824