Statistics
| Branch: | Tag: | Revision:

root / ui / static / synnefo.js @ 7991d0c5

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

    
242
function update_network_confirmations(){
243
    // hide all confirm boxes to begin with
244
    $('#networks-pane div.confirm_multiple').hide();
245

    
246
    for (var i=0;i<pending_actions.length;i++){
247
        // show single confirms depending on the action
248
        if (pending_actions[i][0] == delete_network) {
249
            $("#networks-pane div.network#net-"+pending_actions[i][1]).children('.confirm_single').show();
250
        } else if (pending_actions[i][0] == remove_server_from_network) {
251
            $("#networks-pane div.network #net-"+pending_actions[i][1]+"-server-"+pending_actions[i][2]).children('.confirm_single').show();
252
        } // else {}
253
    }
254

    
255
    // if more than one pending action show multiple confirm box
256
    if (pending_actions.length > 1){
257
        $('#networks-pane div.confirm_multiple span.actionLen').text(pending_actions.length);
258
        $('#networks-pane div.confirm_multiple').show();
259
    }
260
}
261

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

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

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

    
349
function choose_view() {
350
    if ($.cookie("view")=='1') {
351
        list_view();
352
    } else if ($.cookie("view")=='2'){
353
        single_view();
354
    } else {
355
        standard_view();
356
    }
357
}
358

    
359
// return value from metadata key "OS", if it exists
360
function os_icon(metadata) {
361
    if (!metadata) {
362
        return 'okeanos';
363
    }
364
    if (metadata.values.OS == undefined || metadata.values.OS == '') {
365
        return 'okeanos';
366
    } else {
367
        if (os_icons.indexOf(metadata.values.OS) == -1) {
368
            return 'okeanos';
369
        } else {
370
            return metadata.values.OS;
371
        }
372
    }
373
}
374

    
375
function os_icon_from_value(metadata) {
376
    if (!metadata) {
377
        return 'okeanos';
378
    }
379
if (metadata == undefined || metadata == '') {
380
        return 'okeanos';
381
    } else {
382
        if (os_icons.indexOf(metadata) == -1) {
383
            return 'okeanos';
384
        } else {
385
            return metadata;
386
        }
387
    }
388
}
389

    
390
// get and show a list of running and terminated machines
391
function update_vms(interval) {
392
    try{ console.info('updating machines'); } catch(err){}
393
    var uri= API_URL + '/servers/detail';
394

    
395
    if (changes_since != 0)
396
        uri+='?changes-since='+changes_since
397

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

    
430
            if (interval) {
431
                clearTimeout(deferred);    // clear old deferred calls
432
                deferred = setTimeout(function() {update_vms(interval);},interval,interval);
433
            }
434

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

    
456
function update_servers_data(servers_update, data) {
457
    $(window).trigger("vm:update", servers_update, data);
458

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

    
476
        return false;
477
    }
478

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

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

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

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

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

    
563
    if (changes_since != 0)
564
        uri+='?changes-since='+changes_since
565

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
993
// reboot action
994
function reboot(serverIDs){
995
    if (!serverIDs.length){
996
        //ajax_success('DEFAULT');
997
        return false;
998
    }
999
    // ajax post reboot call
1000
    var payload = {
1001
        "reboot": {"type" : "HARD"}
1002
    };
1003

    
1004
    var serverID = serverIDs.pop();
1005

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

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

    
1066
    var serverID = serverIDs.pop();
1067

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

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

    
1108
    var serverID = serverIDs.pop();
1109

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

    
1130
                        // update status on local storage object
1131
                        vm = get_machine(serverID);
1132
                        vm.status = "DESTROY";
1133

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

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

    
1157
    var serverID = serverIDs.pop();
1158

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

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

    
1197
    vd.writeln("[connection]");
1198
    vd.writeln("host=" + host);
1199
    vd.writeln("port=" + port);
1200
    vd.writeln("password=" + password);
1201

    
1202
    vd.close();
1203
}
1204

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

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

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

    
1238
    var serverID = serverIDs.pop();
1239

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

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

    
1285
function vm_has_address(vmId) {
1286
    var vm = get_machine(vmId);
1287

    
1288
    if (!vm) return false;
1289

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

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

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

    
1317
    try {
1318
        var os = os_icon(machine.metadata);
1319
    } catch (err) { var os = 'undefined'; }
1320

    
1321
    var username = "";
1322
    try {
1323
        username = machine.metadata.values[username_meta_key];
1324
    } catch (err) { username = undefined }
1325

    
1326
    var domain = "";
1327
    try {
1328
        domain = machine.metadata.values[domain_meta_key];
1329
    } catch (erro) { domain = undefined }
1330

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

    
1333
    if (username) {
1334
        params_url += "&username=" + username;
1335
    }
1336

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

    
1374

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

    
1379
    return false;
1380
}
1381

    
1382

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

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

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

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

    
1488
// add metadata key-value pair
1489
function update_metadata(serverID, meta_key, meta_value) {
1490
    var payload = {
1491
        "meta": {
1492
        }
1493
    };
1494
    payload["meta"][meta_key] = meta_value;
1495

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

    
1523
                var os = os_icon_from_value(meta_value);
1524
                var state = $("#metadata-wizard div#on-off").text()
1525
                var state_single = $(".state", machine_single).hasClass("terminated-state") ? "off" : "on";
1526
                set_machine_os_image(machine_icon, "icon", state, os);
1527
                set_machine_os_image(machine_single, "single", state_single, os);
1528
            }
1529
        }
1530
    });
1531
    return false;
1532
}
1533

    
1534
// get stats
1535
function get_server_stats(serverID) {
1536
    $.ajax({
1537
        url: API_URL + '/servers/' + serverID + '/stats',
1538
        cache: false,
1539
        type: "GET",
1540
        //async: false,
1541
        dataType: "json",
1542
        timeout: TIMEOUT,
1543
        error: function(jqXHR, textStatus, errorThrown) {
1544
                // report error as text inline
1545
                $('#' + serverID + ' img.busy').hide();
1546
                $('#' + serverID + ' div.stat-error').show();
1547
        },
1548
        success: function(data, textStatus, jqXHR) {
1549
            // in icon view
1550
            if ( $.cookie('view') == 0 ) {
1551
                $('#' + serverID + ' img.busy').removeClass('busy');
1552
                $('#' + serverID + ' img.cpu').attr("src", data.stats.cpuBar);
1553
                $('#' + serverID + ' img.net').attr("src", data.stats.netBar);
1554
            }
1555
            // in single view
1556
            else if ( $.cookie('view') == 2 ) {
1557
                $('#' + serverID + ' div.cpu-graph img.stats').attr("src", data.stats.cpuTimeSeries);
1558
                $('#' + serverID + ' div.network-graph img.stats').attr("src", data.stats.netTimeSeries);
1559
            }
1560
        }
1561
    });
1562
    return false;
1563
}
1564

    
1565
// create network
1566
function create_network(networkName){
1567
    // ajax post start call
1568
    var payload = {
1569
        "network": { "name": networkName }
1570
    };
1571

    
1572
    $.ajax({
1573
        url: API_URL + '/networks',
1574
        type: "POST",
1575
        contentType: "application/json",
1576
        dataType: "json",
1577
        data: JSON.stringify(payload),
1578
        timeout: TIMEOUT,
1579
        error: function(jqXHR, textStatus, errorThrown) {
1580
            try {
1581
                // close wizard and show error box
1582
                $("a#networkscreate").overlay().close();
1583
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1584
            } catch (err) {
1585
                if (!isXhrException(err)) {
1586
                    ajax_error(-514, "UI Error", "Create network", err);
1587
                } else {
1588
                    return false;
1589
                }
1590
            }
1591
        },
1592
        success: function(data, textStatus, jqXHR) {
1593
            if ( jqXHR.status == '202') {
1594
                try {
1595
                    console.info('created network ' + networkName);
1596
                } catch(err) {}
1597
                /*
1598
                On success of this call nothing happens.
1599
                When the UI gets the first update containing the created server,
1600
                the creation wizard is closed and the new network is inserted
1601
                to the DOM. This is done in update_networks_view()
1602
                */
1603
            } else {
1604
                // close wizard and show error box
1605
                $("a#networkscreate").overlay().close();
1606
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1607
            }
1608
        }
1609
    });
1610
    return false;
1611
}
1612

    
1613
// rename network
1614
function rename_network(networkID, networkName){
1615
    if (!networkID.length){
1616
        //ajax_success('DEFAULT');
1617
        return false;
1618
    }
1619
    // prepare payload
1620
    var payload = {
1621
        "network": {"name": networkName}
1622
    };
1623
    // ajax call
1624
    $.ajax({
1625
        url: API_URL + '/networks/' + networkID,
1626
        type: "PUT",
1627
        contentType: "application/json",
1628
        dataType: "json",
1629
        data: JSON.stringify(payload),
1630
        timeout: TIMEOUT,
1631
        error: function(jqXHR, textStatus, errorThrown) {
1632
            try {
1633
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1634
            } catch (err) {
1635
                if (!isXhrException(err)) {
1636
                    ajax_error(-515, "UI Error", 'Rename network', err);
1637
                } else {
1638
                    return false;
1639
                }
1640
            }
1641
        },
1642
        success: function(data, textStatus, jqXHR) {
1643
            if ( jqXHR.status == '204') {
1644
                try {
1645
                    console.info('renamed network' + networkID);
1646
                } catch(err) {}
1647
            } else {
1648
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1649
            }
1650
        }
1651
    });
1652
    return false;
1653
}
1654

    
1655
function delete_network(networkIDs){
1656
    if (!networkIDs.length){
1657
        //ajax_success('DEFAULT');
1658
        return false;
1659
    }
1660
    // get a network
1661
    var networkID = networkIDs.pop();
1662
    // ajax post destroy call can have an empty request body
1663
    var payload = {};
1664
    // ajax call
1665
    $.ajax({
1666
        url: API_URL + '/networks/' + networkID,
1667
        type: "DELETE",
1668
        contentType: "application/json",
1669
        dataType: "json",
1670
        data: JSON.stringify(payload),
1671
        timeout: TIMEOUT,
1672
        error: function(jqXHR, textStatus, errorThrown) {
1673
            try {
1674
                display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1675
            } catch (err) {
1676
                display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1677
            }
1678
        },
1679
        success: function(data, textStatus, jqXHR) {
1680
            if ( jqXHR.status == '204') {
1681
                try {
1682
                    console.info('deleted network ' + networkID);
1683
                } catch(err) {}
1684
                // continue with the rest of the servers
1685
                delete_network(networkIDs);
1686
            } else {
1687
                try {
1688
                    display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1689
                } catch (err) {
1690
                    display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1691
                }
1692
            }
1693
        }
1694
    });
1695
    return false;
1696
}
1697

    
1698
function add_server_to_network(networkID, serverIDs, serverNames, serverStates) {
1699
    if (!serverIDs.length){
1700
        // close the overlay when all the calls are made
1701
        $("a#add-machines-overlay").overlay().close();
1702
        return false;
1703
    }
1704
    // get a server
1705
    var serverID = serverIDs.pop();
1706
    var serverName = serverNames.pop();
1707
    var serverState = serverStates.pop();
1708
    // prepare payload
1709
    var payload = {
1710
            "add": { "serverRef": serverID }
1711
        };
1712
    // prepare ajax call
1713
    $.ajax({
1714
        url: API_URL + '/networks/' + networkID + '/action',
1715
        type: "POST",
1716
        contentType: "application/json",
1717
        dataType: "json",
1718
        data: JSON.stringify(payload),
1719
        timeout: TIMEOUT,
1720
        error: function(jqXHR, textStatus, errorThrown) {
1721
            try {
1722
                // close wizard and show error box
1723
                $("a#add-machines-overlay").data('overlay').close();
1724
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1725
            } catch (err) {
1726
                if (!isXhrException(err)) {
1727
                    ajax_error(-516, "UI Error", 'Add server to network', err);
1728
                } else {
1729
                    return false;
1730
                }
1731
            }
1732
        },
1733
        success: function(data, textStatus, jqXHR) {
1734
            if ( jqXHR.status == '202') {
1735
                try {
1736
                    console.info('added server ' + serverID + ' to network ' + networkID);
1737
                } catch(err) {}
1738
                // toggle the reboot dialog
1739
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1740
                // continue with the rest of the servers
1741
                add_server_to_network(networkID, serverIDs, serverNames, serverStates);
1742
            } else {
1743
                // close wizard and show error box
1744
                $("a#add-machines-overlay").data('overlay').close();
1745
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1746
            }
1747
        }
1748
    });
1749
    return false;
1750
}
1751

    
1752
function remove_server_from_network(networkIDs, serverIDs, serverNames, serverStates) {
1753
    if (!networkIDs.length){
1754
        //ajax_success('DEFAULT');
1755
        return false;
1756
    }
1757
    // get a network and a server
1758
    var networkID = networkIDs.pop();
1759
    var serverID = serverIDs.pop();
1760
    var serverName = serverNames.pop();
1761
    var serverState = serverStates.pop();
1762
    // prepare payload
1763
    var payload = {
1764
            "remove": { "serverRef": serverID }
1765
        };
1766
    // prepare ajax call
1767
    $.ajax({
1768
        url: API_URL + '/networks/' + networkID + '/action',
1769
        type: "POST",
1770
        contentType: "application/json",
1771
        dataType: "json",
1772
        data: JSON.stringify(payload),
1773
        timeout: TIMEOUT,
1774
        error: function(jqXHR, textStatus, errorThrown) {
1775
            try {
1776
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1777
            } catch (err) {
1778
                if (!isXhrException(err)) {
1779
                    ajax_error(-517, "UI Error", 'Remove server form network', err);
1780
                } else {
1781
                    return false;
1782
                }
1783
            }
1784
        },
1785
        success: function(data, textStatus, jqXHR) {
1786
            if ( jqXHR.status == '202') {
1787
                try {
1788
                    console.info('deleted server ' + serverID + ' from network ' + networkID);
1789
                } catch(err) {}
1790
                // toggle the reboot dialog
1791
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1792
                // continue with the rest of the servers
1793
                remove_server_form_network(networkIDs, serverIDs, serverNames, serverStates);
1794
            } else {
1795
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1796
            }
1797
        }
1798
    });
1799
    return false;
1800
}
1801

    
1802
function set_firewall(networkID, serverID, profile) {
1803
    if (!networkID.length || !serverID.length || !profile.length){
1804
        return false;
1805
    }
1806
    // prepare payload
1807
    var payload = {
1808
            "firewallProfile": { "profile": profile }
1809
    };
1810

    
1811
    // prepare ajax call
1812
    $.ajax({
1813
        url: API_URL + '/servers/' + serverID + '/action',
1814
        type: "POST",
1815
        contentType: "application/json",
1816
        dataType: "json",
1817
        data: JSON.stringify(payload),
1818
        timeout: TIMEOUT,
1819
        error: function(jqXHR, textStatus, errorThrown) {
1820
            try {
1821
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1822
            } catch (err) {
1823
                if (!isXhrException(err)) {
1824
                    ajax_error(-518, "UI Error", 'Set firewall profile', err);
1825
                } else {
1826
                    return false;
1827
                }
1828
            }
1829
        },
1830
        success: function(data, textStatus, jqXHR) {
1831
            if ( jqXHR.status == '202') {
1832
                try {
1833
                    console.info('for server ' + serverID + ' set firewall profile to ' + profile);
1834
                } catch(err) {}
1835
                // toggle the reboot dialog
1836
                try {
1837

    
1838
                    var serverName = $('div#net-' + networkID + '-server-' + serverID + ' div.machine-name-div span.name').text();
1839
                    var serverState = $('div#net-' + networkID + '-server-' + serverID + ' img.logo').attr('src').split('-')[1];
1840
                    serverState = serverState.split('.')[0];
1841
                    display_reboot_dialog(networkID, serverID, serverName, serverState);
1842

    
1843
                    //remove progress gif and toggle the content
1844
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').html(VARIOUS["APPLY"]);
1845
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').attr("disabled", false);
1846
                    $('div#net-' + networkID + '-server-' + serverID + ' div.firewall-header').click();
1847

    
1848
                } catch (err) {
1849
                }
1850
                
1851
                // api call was made, set transition state to get reset 
1852
                // on the next machines update api call
1853
                var vm = get_machine(serverID)
1854
                vm.network_transition = "NETWORK_CHANGE";
1855
                show_machine_network_indicator(vm.id, 'pub');
1856
            } else {
1857
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1858
            }
1859
        }
1860
    });
1861
    return false;
1862
}
1863

    
1864
// show the welcome screen
1865
function showWelcome() {
1866
    $("#view-select").fadeOut("fast");
1867
    $("#emptymachineslist").fadeIn("fast");
1868
    $("#machinesview").hide();
1869
}
1870

    
1871
// hide the welcome screen
1872
function hideWelcome() {
1873
    $("#emptymachineslist").fadeOut("fast");
1874
    $("#view-select").fadeIn("fast");
1875
    $("div#view-select").show();
1876
    $("#machinesview").show();
1877
}
1878

    
1879
function log_server_status_change(server_entry, new_status) {
1880
    // firebug console logging
1881
    try {
1882
        if ($("#machinesview-single").length > 0) {
1883
            console.info(server_entry.find("div.machine-details div.name").text() +
1884
                        ' from ' + server_entry.find(".state-label").text() +
1885
                        ' to ' + STATUSES[new_status]);
1886
        } else {
1887
            console.info(server_entry.find("div.name span.name").text() +
1888
                        ' from ' + server_entry.find(".status").text() +
1889
                        ' to ' + STATUSES[new_status]);
1890
        }
1891
    } catch(err) {}
1892
}
1893

    
1894
function get_flavor_params(flavorRef) {
1895
    var cpus, ram, disk;
1896
    if ( flavors.length > 0 ) {
1897
        var current_flavor = '';
1898
        for (i=0; i<flavors.length; i++) {
1899
            if (flavors[i]['id'] == flavorRef) {
1900
                current_flavor = flavors[i];
1901
            }
1902
        }
1903
        cpus = current_flavor['cpu'];
1904
        ram = current_flavor['ram'];
1905
        disk = current_flavor['disk'];
1906
    } else {
1907
        cpus = 'undefined';
1908
        ram = 'undefined';
1909
        disk = 'undefined';
1910
    }
1911
    return {'cpus': cpus, 'ram': ram, 'disk': disk};
1912
}
1913

    
1914
function get_image_params(imageRef) {
1915
    var image_name, image_size;
1916
    if ( images.length > 0 ) {
1917
        var current_image = '';
1918
        for (i=0; i<images.length; i++) {
1919
            if (images[i]['id'] == imageRef) {
1920
                current_image = images[i];
1921
            }
1922
        }
1923
        try {
1924
            image_name = current_image['name'];
1925
        } catch(err) { image_name = 'undefined'; }
1926
        try{
1927
            image_size = current_image['metadata']['values']['size'];
1928
        } catch(err) { image_size = 'undefined'; }
1929
    } else {
1930
        image_name = 'undefined';
1931
        image_size = 'undefined';
1932
    }
1933
    return {'name': image_name,'size': image_size};
1934
}
1935

    
1936
function get_public_ips(server) {
1937
    var ip4, ip6;
1938
    try {
1939
        if (server.addresses.values) {
1940
            $.each (server.addresses.values, function(i, value) {
1941
                if (value.id == 'public') {
1942
                    try {
1943
                        $.each (value.values, function(i, ip) {
1944
                            if (ip.version == '4') {
1945
                                ip4 = ip.addr;
1946
                            } else if (ip.version == '6') {
1947
                                ip6 = ip.addr;
1948
                            } else {
1949
                                ip4 = 'pending';
1950
                                ip6 = 'pending';
1951
                            }
1952
                        });
1953
                    } catch (err){
1954
                        try{console.info('Server ' + server.id + ' has invalid ips')}catch(err){};
1955
                        ip4 = 'pending';
1956
                        ip6 = 'pending';
1957
                    }
1958
                }
1959
            });
1960
        }
1961
    } catch (err) {
1962
        try{console.info('Server ' + server.id + ' has no network addresses')}catch(err){};
1963
        ip4 = 'pending';
1964
        ip6 = 'pending';
1965
    }
1966
    return {'ip4': ip4, 'ip6': ip6};
1967
}
1968

    
1969
function get_private_ips(server) {
1970

    
1971
}
1972

    
1973
function close_all_overlays() {
1974
        try {
1975
                $("a#networkscreate").overlay().close();
1976
        } catch(err) {}
1977
        try {
1978
                $('a#create').overlay().close();
1979
        } catch(err) {}
1980
        try {
1981
                $("a#add-machines-overlay").overlay().close();
1982
        } catch(err) {}
1983
        try {
1984
                $("a#metadata-scrollable").overlay().close();
1985
        } catch(err) {}
1986
        try {
1987
                $("a#msgbox").overlay().close();
1988
        } catch(err) {}
1989
        try {
1990
                $("a#feedbackbox").overlay().close();
1991
        } catch(err) {}
1992
}
1993

    
1994
// logout
1995
function user_session_logout() {
1996
    $.cookie("X-Auth-Token", null);
1997
    if (window.LOGOUT_REDIRECT !== undefined)
1998
    {
1999
        window.location = window.LOGOUT_REDIRECT;
2000
    } else {
2001
        window.location.reload();
2002
    }
2003
}
2004

    
2005
// action indicators
2006
function init_action_indicator_handlers(machines_view)
2007
{
2008
    // init once for each view
2009
    if (window.ACTION_ICON_HANDLERS == undefined)
2010
    {
2011
        window.ACTION_ICON_HANDLERS = {};
2012
    }
2013

    
2014
    if (machines_view in window.ACTION_ICON_HANDLERS)
2015
    {
2016
        return;
2017
    }
2018
    window.ACTION_ICON_HANDLERS[machines_view] = 1;
2019

    
2020
    if (machines_view == "list")
2021
    {
2022
        // totally different logic for list view
2023
        init_action_indicator_list_handlers();
2024
        return;
2025
    }
2026

    
2027
    function update_action_icon_indicators(force)
2028
    {
2029
        function show(el, action) {
2030
            $(".action-indicator", $(el)).attr("class", "action-indicator " + action);
2031
            $(".action-indicator", $(el)).show();
2032
        }
2033

    
2034
        function hide(el) {
2035
            $(".action-indicator", $(el)).hide();
2036
        }
2037

    
2038
        function get_pending_actions(el) {
2039
            return $(".confirm_single:visible", $(el));
2040
        }
2041

    
2042
        function other_indicators(el) {
2043
           return $("img.wave:visible, img.spinner:visible", $(el))
2044
        }
2045

    
2046
        $("div.machine:visible, div.single-container").each(function(index, el){
2047
            var el = $(el);
2048
            var pending = get_pending_actions(el);
2049
            var other = other_indicators(el);
2050
            var action = undefined;
2051
            var force_action = force;
2052
            var visible = $(el).css("display") == "block";
2053

    
2054
            if (force_action !==undefined && force_action.el !== el[0]) {
2055
                // force action for other vm
2056
                // skipping force action
2057
                force_action = undefined;
2058
            }
2059

    
2060
            if (force_action !==undefined && force_action.el === el[0]) {
2061
                action = force_action.action;
2062
            }
2063

    
2064
            if (other.length >= 1) {
2065
                return;
2066
            }
2067

    
2068
            if (pending.length >= 1 && force_action === undefined) {
2069
                action = $(pending.parent()).attr("class").replace("action-container","");
2070
            }
2071

    
2072
            if (action in {'console':''}) {
2073
                return;
2074
            }
2075

    
2076
            if (action !== undefined) {
2077
                show(el, action);
2078
            } else {
2079
                try {
2080
                    if (el.attr('id') == pending_actions[0][1])
2081
                    {
2082
                        return;
2083
                    }
2084
                } catch (err) {
2085
                }
2086
                hide(el);
2087
            }
2088

    
2089
        });
2090
    }
2091

    
2092
    // action indicators
2093
    $(".action-container").live('mouseover', function(evn) {
2094
        force_action = {'el': $(evn.currentTarget).parent().parent()[0], 'action':$(evn.currentTarget).attr("class").replace("action-container","")};
2095
        // single view case
2096
        if ($(force_action.el).attr("class") == "upper")
2097
        {
2098
            force_action.el = $(evn.currentTarget).parent().parent().parent()[0]
2099
        };
2100
        update_action_icon_indicators(force_action);
2101
    });
2102

    
2103
    $("img.spinner, img.wave").live('hide', function(){
2104
        update_action_icon_indicators();
2105
    });
2106
    // register events where icons should get updated
2107

    
2108
    // hide action indicator image on mouse out, spinner appear, wave appear
2109
    $(".action-container").live("mouseout", function(evn){
2110
        update_action_icon_indicators();
2111
    });
2112

    
2113
    $(".confirm_single").live("click", function(evn){
2114
        update_action_icon_indicators();
2115
    });
2116

    
2117
    $("img.spinner, img.wave").live('show', function(){
2118
        $("div.action-indicator").hide();
2119
    });
2120

    
2121
    $(".confirm_single button.no").live('click', function(evn){
2122
        $("div.action-indicator", $(evn.currentTarget).parent().parent()).hide();
2123
    });
2124

    
2125
    $(".confirm_multiple button.no").click(function(){
2126
        $("div.action-indicator").hide();
2127
    });
2128

    
2129
    $(".confirm_multiple button.yes").click(function(){
2130
        $("div.action-indicator").hide();
2131
    });
2132
}
2133

    
2134
function init_action_indicator_list_handlers()
2135
{
2136
    var skip_actions = { 'connect':'','details':'' };
2137

    
2138
    var has_pending_confirmation = function()
2139
    {
2140
        return $(".confirm_multiple:visible").length >= 1
2141
    }
2142

    
2143
    function update_action_indicator_icons(force_action, skip_pending)
2144
    {
2145
        // pending action based on the element class
2146
        var pending_action = $(".selected", $(".actions"))[0];
2147
        var selected = get_list_view_selected_machine_rows();
2148

    
2149
        // reset previous state
2150
        list_view_hide_action_indicators();
2151

    
2152
        if (pending_action == undefined && !force_action)
2153
        {
2154
            // no action selected
2155
            return;
2156
        }
2157

    
2158
        if (force_action != undefined)
2159
        {
2160
            // user forced action choice
2161
            var action_class = force_action;
2162
        } else {
2163
            // retrieve action name (reboot, stop, etc..)
2164
            var action_class = $(pending_action).attr("id").replace("action-","");
2165
        }
2166

    
2167
        selected.each(function(index, el) {
2168
            if (has_pending_confirmation() && skip_pending)
2169
            {
2170
                return;
2171
            }
2172
            var el = $(el);
2173
            var logo = $("img.list-logo", el);
2174
            $(".action-indicator", el).remove();
2175
            var cls = "action-indicator " + action_class;
2176
            // add icon div
2177
            logo.after('<div class="' + cls + '"></div>');
2178
            // hide os logo
2179
            $("img.list-logo", el).hide();
2180
        });
2181
    }
2182

    
2183
    // on mouseover we force the images to the hovered action
2184
    $(".actions a").live("mouseover", function(evn) {
2185
        var el = $(evn.currentTarget);
2186
        if (!el.hasClass("enabled"))
2187
        {
2188
            return;
2189
        }
2190
        var action_class = el.attr("id").replace("action-","");
2191
        if (action_class in skip_actions)
2192
        {
2193
            return;
2194
        }
2195
        update_action_indicator_icons(action_class, false);
2196
    });
2197

    
2198

    
2199
    // register events where icons should get updated
2200
    $(".actions a.enabled").live("click", function(evn) {
2201
        // clear previous selections
2202
        $("a.selected").removeClass("selected");
2203

    
2204
        var el = $(evn.currentTarget);
2205
        el.addClass("selected");
2206
        update_action_indicator_icons(undefined, false);
2207
    });
2208

    
2209
    $(".actions a").live("mouseout", function(evn) {
2210
        update_action_indicator_icons(undefined, false);
2211
    });
2212

    
2213
    $(".confirm_multiple button.no").click(function(){
2214
        list_view_hide_action_indicators();
2215
    });
2216

    
2217
    $(".confirm_multiple button.yes").click(function(){
2218
        list_view_hide_action_indicators();
2219
    });
2220

    
2221
    $("input[type=checkbox]").live('change', function(){
2222
        // pending_actions will become empty on every checkbox click/change
2223
        // line 154 machines_list.html
2224
        pending_actions = [];
2225
        if (pending_actions.length == 0)
2226
        {
2227
            $(".confirm_multiple").hide();
2228
            $("a.selected").each(function(index, el){$(el).removeClass("selected")});
2229
        }
2230
        update_action_indicator_icons(undefined, false);
2231
    });
2232

    
2233
}
2234

    
2235
function list_view_hide_action_indicators()
2236
{
2237
    $("tr td .action-indicator").remove();
2238
    $("tr td img.list-logo").show();
2239
}
2240

    
2241
function get_list_view_selected_machine_rows()
2242
{
2243
    var table = $("table.list-machines");
2244
    var rows = $("tr:has(input[type=checkbox]:checked)",table);
2245
    return rows;
2246
}
2247

    
2248
// machines images utils
2249
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) {
2250
    var views_map = {'single': '.single-image', 'icon': '.logo'};
2251
    var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'}
2252
    var sizes_map = {'single': 'large', 'icon': 'medium'}
2253

    
2254
    var size = sizes_map[machines_view];
2255
    var img_selector = views_map[machines_view];
2256
    var cls = states_map[state];
2257

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

    
2261
    var el = $(img_selector, machine);
2262
    var current_img = el.css("backgroundImage");
2263
    if (os == undefined){
2264
        new_img = current_img;
2265
    }
2266

    
2267
    // os changed
2268
    el.css("backgroundImage", new_img);
2269

    
2270
    // reset current state
2271
    if (skip_reset_states === undefined)
2272
    {
2273
        el.removeClass("single-image-state1");
2274
        el.removeClass("single-image-state2");
2275
        el.removeClass("single-image-state3");
2276
        el.removeClass("single-image-state4");
2277
    }
2278

    
2279
    if (remove_state !== undefined)
2280
    {
2281
        remove_state = "single-image-" + states_map[remove_state];
2282
        el.removeClass(remove_state);
2283
        return;
2284
    }
2285
    
2286
    // set proper state
2287
    el.addClass("single-image-" + cls);
2288
}
2289

    
2290

    
2291
// generic info box
2292
function show_feedback_form(msg, from_error) {
2293
    var box = $("#feedback-form");
2294
    box.addClass("notification-box");
2295

    
2296
    // initialize
2297
    box.find(".form-container").show();
2298
    box.find("textarea").val("");
2299
    box.find(".message").hide();
2300
    
2301
    var initial_msg = msg || undefined;
2302
    
2303
    var triggers = $("a#feedbackbox").overlay({
2304
        // some mask tweaks suitable for modal dialogs
2305
        mask: '#666',
2306
        top: '10px',
2307
        fixed: false,
2308
        closeOnClick: false,
2309
        oneInstance: false,
2310
        load: false
2311
    });
2312

    
2313
    
2314
    if (initial_msg && from_error) {
2315
        // feedback form from ajax_error window
2316
        box.find("textarea").val(initial_msg);
2317
        $("a#feedbackbox").overlay().onClose(function(){window.location.reload()});
2318
        box.find("textarea").height(200);
2319
        $("a#feedbackbox").overlay().onLoad(function(){box.find("textarea").focus().setCursorPosition(500);});
2320
        
2321
    }
2322

    
2323
    $("#feedback-form form").unbind("submit");
2324
    $("#feedback-form form").submit(function(event) {
2325
        event.preventDefault();
2326
            
2327
        // empty msg
2328
        if ($("textarea.feedback-text").val().replace(/^\s*|\s*$/,"") == "") {
2329
            alert($(".empty-error-msg", this).text());
2330
            return;
2331
        }
2332

    
2333
        $("textarea.data-text", this).val("").val(get_user_data_json());
2334

    
2335
        $.ajax({
2336
            url: FEEDBACK_URL,
2337
            data: $(this).serialize(),
2338
            type: "POST",
2339
            // show loading
2340
            beforeSend: function() {box.find(".form-container").hide(); box.find(".sending").fadeIn() },
2341
            // hide form
2342
            complete: function() { box.find(".form-container").hide(); box.find(".sending").hide() },
2343
            // on success display success message
2344
            success: function() { box.find(".success").fadeIn(); box.find(".sending").hide() },
2345
            // display error message
2346
            error: function() { box.find(".errormsg").fadeIn(); box.find(".sending").hide() }
2347
        })
2348
    });
2349
    
2350
    $("a#feedbackbox").data('overlay').load();
2351

    
2352
    // reset feedback_pending for ajax_errors
2353
    window.FEEDBACK_PENDING = false;
2354
    return false;
2355
}
2356

    
2357
function get_user_data(extra_data) {
2358
    try {
2359
        var last_req = $.extend({}, last_request);
2360

    
2361
        // reset xhr, might raise exceptions while converting to JSON
2362
        last_req.xhr = {};
2363
    } catch (err) {
2364
        var last_req = {}
2365
    }
2366

    
2367
    return $.extend({
2368
        'servers': $.extend({}, servers),
2369
        'client': {'browser': $.browser, 'screen': $.extend({}, screen), 'client': $.client},
2370
        'dates': {'now': new Date, 'lastUpdate': changes_since_date},
2371
        'last_request': last_req
2372
    }, extra_data);
2373
}
2374

    
2375
function get_user_data_json() {
2376
    try {
2377
        return JSON.stringify(get_user_data());
2378
    } catch (err) {
2379
        return JSON.stringify({'error': err});
2380
    }
2381
}
2382

    
2383
function msg_box(config) {
2384
    var config = $.extend({'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false}, config);
2385
    // prepare the error message
2386
    // bring up success notification
2387

    
2388
    var box = $("#notification-box");
2389
    box.addClass("notification-box");
2390
    box.addClass('success');
2391
    box.removeClass('error');
2392

    
2393
    var sel = function(s){return $(s, box)};
2394
    // reset texts
2395
    sel("h3 span.header-box").html("");
2396
    sel(".sub-text").html("");
2397
    sel(".password-container .password").html("");
2398
    sel("div.machine-now-building").html("");
2399
    
2400

    
2401
    // apply msg box contents
2402
    sel("h3 span.header-box").html(config.title);
2403
    sel("div.machine-now-building").html(config.content);
2404
    sel(".popup-header").removeClass("popup-header-error");
2405
    box.removeClass("popup-border-error");
2406
    sel(".popup-details").removeClass("popup-details-error");
2407
    sel(".popup-separator").removeClass("popup-separator-error");
2408
    
2409
    sel(".password-container").hide();
2410
    if (config.extra) {
2411
        sel(".password-container .password").html(config.extra);
2412
        sel(".password-container").show();
2413
    }
2414
    
2415
    var conf = {
2416
        // some mask tweaks suitable for modal dialogs
2417
        mask: '#666',
2418
        top: '10px',
2419
        closeOnClick: false,
2420
        oneInstance: false,
2421
        load: false,
2422
        fixed: config.fixed || false,
2423
        onClose: function () {
2424
            // With partial refresh working properly,
2425
            // it is no longer necessary to refresh the whole page
2426
            // choose_view();
2427
        }
2428
    }
2429
    
2430
    var triggers = $("a#msgbox").overlay(conf);
2431

    
2432
    try {
2433
        conf = $("a#msgbox").data('overlay').getConf();
2434
        conf.fixed = config.fixed || false;
2435
    } catch (err) {}
2436
    $("a#msgbox").data('overlay').load();
2437
    
2438
    var parse_data = config.parse_data || false;
2439
    var load_html = config.html || false;
2440
    var user_success = config.success || false;
2441
    config.ajax = config.ajax || {};
2442

    
2443
    // requested to show remote data in msg_box
2444
    if (config.ajax) {
2445
        $.ajax($.extend({ 
2446
            url:config.ajax, 
2447
            success: function(data){
2448
                // we want to get our data parsed before
2449
                // placing them in content
2450
                if (parse_data) {
2451
                    data = parse_data(data);
2452
                }
2453

    
2454
                // no json response
2455
                // load html body
2456
                if (load_html) {
2457
                    sel("div.machine-now-building").html(data);
2458
                } else {
2459

    
2460
                    if (data.title) {
2461
                        sel("h3 span.header-box").text(data.title);
2462
                    }
2463

    
2464
                    if (data.content) {
2465
                        sel("div.machine-now-building").html(data.content);
2466
                    }
2467
                    if (data.extra) {
2468
                        sel(".password-container .password").html(data.extra);
2469
                        sel(".password-container").show();
2470
                    }
2471
                    if (data.subinfo) {
2472
                        sel(".sub-text").html(data.subinfo);
2473
                    } else {
2474
                        sel(".sub-text").html("");
2475
                    }
2476
                }
2477

    
2478
                if (user_success) {
2479
                    user_success($("div.machine-now-building"));
2480
                }
2481
            },
2482
            error: function(xhr, status, err) {
2483
                ajax_error(-519, "UI Error", "Machine connect", err);
2484
            }
2485
        }, config.ajax_config));
2486
    }
2487
    return false;
2488
}
2489

    
2490

    
2491
function show_invitations() {
2492

    
2493
    handle_invitations = function(el) {
2494

    
2495
        // proper class to identify the overlay block
2496
        el.addClass("invitations");
2497

    
2498
        var cont = el;
2499
        var form = $(el).find("form");
2500

    
2501
        // remove garbage rows that stay in DOM between requests
2502
        $(".removable-field-row:hidden").remove();
2503

    
2504
        // avoid buggy behaviour, close all overlays if something went wrong
2505
        try {
2506
            // form is in content (form is not displayed if user has no invitations)
2507
            if ($("#invform #removable-name-container-1").length) {
2508
                $("#invform #removable-name-container-1").dynamicField();
2509
            }
2510
        } catch (err) {
2511
            close_all_overlays();
2512
        }
2513
        
2514
        // we copy/paste it on the title no need to show it twice
2515
        $(".invitations-left").hide();
2516

    
2517
        // reset title
2518
        $("#notification-box .header-box").html("");
2519
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
2520

    
2521
        // handle form submit
2522
        form.submit(function(evn){
2523
            evn.preventDefault();
2524

    
2525
            // do the post
2526
            $.post(form.attr("action"), form.serialize(), function(data) {
2527
                // replace data
2528
                $(cont).html(data); 
2529

    
2530
                // append all handlers again (new html data need to redo all changes)
2531
                handle_invitations(cont);
2532
            });
2533

    
2534
            return false;
2535
        });
2536
    }
2537
    
2538
    // first time clicked (show the msg box with /invitations content)
2539
    msg_box({
2540
        title:window.INVITATIONS_TITLE, 
2541
        content:'', 
2542
        fixed: false,
2543
        ajax:INVITATIONS_URL, 
2544
        html:true, 
2545
        success: function(el){ 
2546
            handle_invitations(el)
2547
        }
2548
    });
2549
}
2550

    
2551

    
2552
function get_short_v6(v6, parts_to_keep) {
2553
    var parts = v6.split(":");
2554
    var new_parts = parts.slice(parts.length - parts_to_keep);
2555
    return new_parts.join(":");
2556
}
2557

    
2558
function fix_v6_addresses() {
2559

    
2560
    // what to prepend
2561
    var match = "...";
2562
    // long ip min length
2563
    var limit = 20;
2564
    // parts to show after the transformation
2565
    // (from the end)
2566
    var parts_to_keep_from_end = 4;
2567

    
2568
    $(".ipv6-text").each(function(index, el){
2569
        var el = $(el);
2570
        var ip = $(el).text();
2571
            
2572
        // transformation not applyied
2573
        // FIXME: use $.data for the condition
2574
        if (ip.indexOf(match) == -1 && ip != "pending") {
2575
            
2576
            // only too long ips
2577
            if (ip.length > 20) {
2578
                $(el).data("ipstring", ip);
2579
                $(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
2580
                $(el).attr("title", ip);
2581
                $(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'});
2582
            }
2583
        } else {
2584
            if (ip.indexOf(match) == 0) {
2585
            } else {
2586
                // not a long ip anymore
2587
                $(el).data("ipstring", undefined);
2588
                $(el).css({'text-decoration':'none'});
2589

    
2590
                if ($(el).data('tooltip')) {
2591
                    $(el).data('tooltip').show = function () {};
2592
                }
2593
            }
2594
        }
2595
    });
2596
}
2597

    
2598
function fix_server_name(str, limit, append) {
2599
    limit = limit || 30;
2600
    append = append || "...";
2601

    
2602
    if (str.length > limit) {
2603
        str = str.substring(0,limit-append.length) + append;
2604
    }
2605
    return str;
2606
}
2607

    
2608
function show_machine_network_indicator(vm_id, network_id) {
2609
    var el = $("div#net-" + network_id + '-server-' + vm_id);
2610
    el.find(".network-progress-indicator").show();
2611
}
2612

    
2613

    
2614
function get_firewall_profile(vm_id) {
2615
    var vm = get_machine(vm_id);
2616

    
2617
    try {
2618
        return vm.addresses.values[0].firewallProfile;
2619
    } catch (err) {
2620
        return undefined;
2621
    }
2622
}