Statistics
| Branch: | Tag: | Revision:

root / ui / static / synnefo.js @ e9e7fe51

History | View | Annotate | Download (83.7 kB)

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

    
40

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
434
        return false;
435
    }
436

    
437
    // merge object properties
438
    merge = function() {
439
        var initial = arguments[0];
440
        var status_changed = undefined;
441
        $.each(arguments, function(index, el) {
442
            $.each(el, function(key,v) {
443
                // new attribute added
444
                var previous_value = initial[key];
445
                var v = v;
446
                if (initial[key] == undefined) {
447
                    $(window).trigger("vm:attr:add", initial, key, v);
448
                } else {
449
                    // value changed
450
                    if (initial[key] != v) {
451
                        if (key == "status") {
452
                            // dont change if in destroy state
453
                            if (initial.status == "DESTROY") {
454
                                v = "DESTROY";
455
                            }
456
                            status_changed = {'old': previous_value, 'new': v}; 
457
                        }
458

    
459
                        $(window).trigger("vm:attr:change", {'initial': initial, 'attr': key, 'newvalue': v});
460
                    }
461
                }
462
                initial[key] = v;
463
            });
464
        });
465
        if (status_changed !== undefined) {
466
            $(window).trigger('vm:status:change', {'vm': initial, 'old': status_changed['old'], 'new': status_changed['new']});
467
        }
468
        return initial;
469
    }
470
    
471
    // server removed
472
    var remove = [];
473
    $.each(servers_update, function(index, server) {
474
        if (server.status == "DELETED") {
475
            remove.push(server.id);
476
        }
477
    });
478
    
479
    // check server, if exists merge it with new values else add it
480
    $.each(servers_update, function(index, server) {
481
        var exists = server_exists(server);
482
        var old_server = servers[exists[1]];
483

    
484
        // reset network transition
485
        try {
486
            if (old_server.network_transition) {
487
                if (old_server.network_transition == "NETWORK_CHANGE") {
488
                    // network profile changed, servers data updated, so act if the change was made
489
                    // this flag will trigger ui to remove any transiiton indicators
490
                    // and hopefully apply the new value to the profile options
491
                    old_server.network_transition = "CHANGED"
492
                } else {
493
                    // nothing happened
494
                    old_server.network_transition = undefined;
495
                };
496
            }
497
        } catch (err) { console.info(err) }
498

    
499
        if (exists !== false) {
500
            try {
501
                servers[exists[1]] = merge(servers[exists[1]], server);
502
            } catch (err) {
503
            }
504
        } else {
505
            servers.push(server);
506
            $(window).trigger("vm:add", server);
507
        }
508
        if (remove.indexOf(server.id) > -1) {
509
            var remove_exists = server_exists(server);
510
            servers.splice(remove_exists[1], 1);
511
            $(window).trigger("vm:remove", server);
512
        }
513
    });
514
}
515

    
516
// get a list of running and terminated machines, used in network view
517
function update_networks(interval) {
518
    try{ console.info('updating networks'); } catch(err){}
519
    var uri= API_URL + '/servers/detail';
520

    
521
    if (changes_since != 0)
522
        uri+='?changes-since='+changes_since
523

    
524
    update_request = $.ajax({
525
        cache: false,
526
        url: uri,
527
        type: "GET",
528
        timeout: TIMEOUT,
529
        dataType: "json",
530
        error: function(jqXHR, textStatus, errorThrown) {
531
            // don't forget to try again later
532
            if (interval) {
533
                clearTimeout(deferred);    // clear old deferred calls
534
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
535
            }
536
            // as for now, just show an error message
537
            try { console.info('update_networks errback:' + jqXHR.status ) } catch(err) {}
538
            try {
539
                ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
540
            } catch(err) {
541
                ajax_error(-5, "UI Error", 'Update networks', err);
542
            }
543
            return false;
544
            },
545
        success: function(data, textStatus, jqXHR) {
546
            // create changes_since string if necessary
547
            if (jqXHR.getResponseHeader('Date') != null){
548
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
549
                changes_since = ISODateString(changes_since_date);
550
            }
551

    
552
            if (interval) {
553
                clearTimeout(deferred);    // clear old deferred calls
554
                deferred = setTimeout(function() {update_networks(interval);},interval,interval);
555
            }
556

    
557
            if (jqXHR.status == 200 || jqXHR.status == 203) {
558
                try {
559
                    //servers = data.servers.values;
560
                    update_servers_data(data.servers.values, data);
561
                    update_network_names(data);
562
                } catch(err) { ajax_error(-5, "UI Error", 'Update networks', err);}
563
            } else if (jqXHR.status == 304) {
564
                update_network_names();
565
            }
566
            else {
567
                try { console.info('update_networks callback:' + jqXHR.status ) } catch(err) {}
568
                /*
569
                FIXME:  Here it should return the error, however Opera does not support 304.
570
                        Instead 304 it returns 0. To dealt with this we treat 0 as an
571
                        304, which should be corrected (Bug #317).
572
                */
573
                //ajax_error(jqXHR.status, undefined, 'Update networks', jqXHR.responseText);
574
                update_network_names();
575
            }
576
            return false;
577
        }
578
    });
579
    return false;
580
}
581

    
582
// get and show a list of public and private networks
583
function update_network_names(servers_data) {
584
    try{ console.info('updating network names'); } catch(err){}
585
    var uri= API_URL + '/networks/detail';
586

    
587
    if (networks_changes_since != 0)
588
        //FIXME: Comment out the following, until metadata do not 304 when changed
589
        uri+='?changes-since=' + networks_changes_since
590

    
591
    update_request = $.ajax({
592
        cache: false,
593
        url: uri,
594
        type: "GET",
595
        timeout: TIMEOUT,
596
        dataType: "json",
597
        error: function(jqXHR, textStatus, errorThrown) {
598
            // as for now, just show an error message
599
            try {
600
                console.info('update_network names errback:' + jqXHR.status )
601
            } catch(err) {}
602
            try {
603
                ajax_error(jqXHR.status, undefined, 'Update network names', jqXHR.responseText);
604
            } catch(err) {
605
                ajax_error(-5, "UI Error", 'Update network names', err);
606
            }
607
            return false;
608
            },
609
        success: function(data, textStatus, jqXHR) {
610
            // create changes_since string if necessary
611
            if (jqXHR.getResponseHeader('Date') != null){
612
                changes_since_date = new Date(jqXHR.getResponseHeader('Date'));
613
                networks_changes_since = ISODateString(changes_since_date);
614
            }
615

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

    
641
// get and show a list of available standard and custom images
642
function update_images() {
643
    $.ajax({
644
        url: API_URL + '/images/detail',
645
        type: "GET",
646
        //async: false,
647
        dataType: "json",
648
        timeout: TIMEOUT,
649
        error: function(jqXHR, textStatus, errorThrown) {
650
                    try {
651
                        ajax_error(jqXHR.status, undefined, 'Update Images', jqXHR.responseText);
652
                    } catch(err) {
653
                        ajax_error(-5, "UI error", 'Update Images', err);
654
                    }
655
                },
656
        success: function(data, textStatus, jqXHR) {
657
            try {
658
                images = data.images.values;
659
                jQuery.parseJSON(data);
660
                update_wizard_images();
661
            } catch(err){
662
                ajax_error("NO_IMAGES");
663
            }
664
        }
665
    });
666
    return false;
667
}
668

    
669
function update_wizard_images() {
670
    if ($("ul#standard-images li").toArray().length + $("ul#custom-images li").toArray().length == 0) {
671
        $.each(images, function(i,image){
672
            var img = $('#image-template').clone().attr("id","img-"+image.id).fadeIn("slow");
673
            img.find("label").attr('for',"img-radio-" + image.id);
674
            img.find(".image-title").text(image.name);
675
            if (image.metadata) {
676
                if (image.metadata.values.description != undefined) {
677
                    img.find(".description").text(image.metadata.values.description);
678
                }
679
                if (image.metadata.values.size != undefined) {
680
                    img.find("#size").text(image.metadata.values.size);
681
                }
682
            }
683
            img.find("input.radio").attr('id',"img-radio-" + image.id);
684
            if (i==0) img.find("input.radio").attr("checked","checked");
685
            var image_logo = os_icon(image.metadata);
686
            img.find("img.image-logo").attr('src','static/icons/os/'+image_logo+'.png');
687
            if (image.metadata) {
688
                if (image.metadata.values.serverId != undefined) {
689
                    img.appendTo("ul#custom-images");
690
                } else {
691
                    img.appendTo("ul#standard-images");
692
                }
693
            } else {
694
                img.appendTo("ul#standard-images");
695
            }
696
        });
697
    }
698
}
699

    
700
function update_wizard_flavors(){
701
    // sliders for selecting VM flavor
702
    $("#cpu:range").rangeinput({min:0,
703
                               value:0,
704
                               step:1,
705
                               progress: true,
706
                               max:cpus.length-1});
707

    
708
    $("#storage:range").rangeinput({min:0,
709
                               value:0,
710
                               step:1,
711
                               progress: true,
712
                               max:disks.length-1});
713

    
714
    $("#ram:range").rangeinput({min:0,
715
                               value:0,
716
                               step:1,
717
                               progress: true,
718
                               max:ram.length-1});
719
    $("#small").click();
720

    
721
    // update the indicators when sliding
722
    $("#cpu:range").data().rangeinput.onSlide(function(event,value){
723
        $("#cpu-indicator")[0].value = cpus[Number(value)];
724
        $("#cpu-indicator").addClass('selectedrange');
725
    });
726
    $("#cpu:range").data().rangeinput.change(function(event,value){
727
        $("#cpu-indicator")[0].value = cpus[Number(value)];
728
        $("#custom").click();
729
        $("#cpu-indicator").removeClass('selectedrange');
730
    });
731
    $("#ram:range").data().rangeinput.onSlide(function(event,value){
732
        $("#ram-indicator")[0].value = ram[Number(value)];
733
        $("#ram-indicator").addClass('selectedrange');
734
    });
735
    $("#ram:range").data().rangeinput.change(function(event,value){
736
        $("#ram-indicator")[0].value = ram[Number(value)];
737
        $("#custom").click();
738
        $("#ram-indicator").removeClass('selectedrange');
739
    });
740
    $("#storage:range").data().rangeinput.onSlide(function(event,value){
741
        $("#storage-indicator")[0].value = disks[Number(value)];
742
        $("#storage-indicator").addClass('selectedrange');
743
    });
744
    $("#storage:range").data().rangeinput.change(function(event,value){
745
        $("#storage-indicator")[0].value = disks[Number(value)];
746
        $("#custom").click();
747
        $("#storage-indicator").removeClass('selectedrange');
748
    });
749
}
750

    
751
Array.prototype.unique = function () {
752
    var r = new Array();
753
    o:for(var i = 0, n = this.length; i < n; i++)
754
    {
755
        for(var x = 0, y = r.length; x < y; x++)
756
        {
757
            if(r[x]==this[i])
758
            {
759
                continue o;
760
            }
761
        }
762
        r[r.length] = this[i];
763
    }
764
    return r;
765
}
766

    
767
// get and configure flavor selection
768
function update_flavors() {
769
    $.ajax({
770
        url: API_URL + '/flavors/detail',
771
        type: "GET",
772
        //async: false,
773
        dataType: "json",
774
        timeout: TIMEOUT,
775
        error: function(jqXHR, textStatus, errorThrown) {
776
            try {
777
                ajax_error(jqXHR.status, undefined, 'Update Flavors', jqXHR.responseText);
778
            } catch (err) {
779
                ajax_error(-5, "UI Error", "Update Flavors", err);
780
            }
781
            // start updating vm list
782
            update_vms(UPDATE_INTERVAL);
783
        },
784
        success: function(data, textStatus, jqXHR) {
785

    
786
            try {
787
                flavors = data.flavors.values;
788
                jQuery.parseJSON(data);
789
                $.each(flavors, function(i, flavor) {
790
                    cpus[i] = flavor['cpu'];
791
                    disks[i] = flavor['disk'];
792
                    ram[i] = flavor['ram'];
793
                });
794
                cpus = cpus.unique();
795
                disks = disks.unique();
796
                ram = ram.unique();
797
                update_wizard_flavors();
798
            } catch(err){
799
                ajax_error("NO_FLAVORS");
800
            }
801
            // start updating vm list
802
            update_vms(UPDATE_INTERVAL);
803
        }
804
    });
805
    return false;
806
}
807

    
808
// return flavorRef from cpu, disk, ram values
809
function identify_flavor(cpu, disk, ram){
810
    for (i=0;i<flavors.length;i++){
811
        if (flavors[i]['cpu'] == cpu && flavors[i]['disk']==disk && flavors[i]['ram']==ram) {
812
            return flavors[i]['id']
813
        }
814
    }
815
    return 0;
816
}
817

    
818
// return image entry from imageRef
819
function get_image(imageRef) {
820
    for (i=0;i<images.length;i++){
821
        if (images[i]['id'] == imageRef) {
822
            return images[i];
823
        }
824
    }
825
    return 0;
826
}
827

    
828
// return machine entry from serverID
829
function get_machine(serverID) {
830
    for (i=0;i<servers.length;i++){
831
        if (servers[i]['id'] == serverID) {
832
            return servers[i];
833
        }
834
    }
835
    return 0;
836
}
837

    
838
// update the actions in icon view, per server
839
function update_iconview_actions(serverID, server_status) {
840
    if ($.cookie("view")=='2') {
841
        // remove .disable from all actions to begin with
842
        $('#machinesview-single #' + serverID + ' div.single-action').show();
843
        // decide which actions should be disabled
844
        for (current_action in actions) {
845
            if (actions[current_action].indexOf(server_status) == -1 ) {
846
                $('#machinesview-single #' + serverID + ' div.action-' + current_action).hide();
847
            }
848
        }
849
    } else {
850
        // remove .disable from all actions to begin with
851
        $('#machinesview-icon.standard #' + serverID + ' div.actions').find('a').removeClass('disabled');
852
        // decide which actions should be disabled
853
        for (current_action in actions) {
854
            if (actions[current_action].indexOf(server_status) == -1 ) {
855
                $('#machinesview-icon.standard #' + serverID + ' a.action-' + current_action).addClass('disabled');
856
            }
857
        }
858
    }
859
}
860

    
861
// update the actions in list view
862
function update_listview_actions() {
863
    var states = [];
864
    var on = [];
865
    var checked = $("table.list-machines tbody input[type='checkbox']:checked");
866
    // disable all actions to begin with
867
    $('#machinesview .list div.actions').children().removeClass('enabled');
868

    
869
    // are there multiple machines selected?
870
    if (checked.length>1)
871
        states[0] = 'multiple';
872

    
873
    // check the states of selected machines
874
    checked.each(function(i,checkbox) {
875
        states[states.length] = checkbox.className;
876
        var ip = $("#" + checkbox.id.replace('input-','') + ".ip span.public").text();
877
        if (ip.replace('undefined','').length)
878
            states[states.length] = 'network';
879
    });
880

    
881
    // decide which actions should be enabled
882
    for (a in actions) {
883
        var enabled = false;
884
        for (var s =0; s<states.length; s++) {
885
            if (actions[a].indexOf(states[s]) != -1 ) {
886
                enabled = true;
887
            } else {
888
                enabled = false;
889
                break;
890
            }
891
        }
892
        if (enabled)
893
            on[on.length]=a;
894
    }
895
    // enable those actions
896
    for (action in on) {
897
        $("#action-" + on[action]).addClass('enabled');
898
    }
899
}
900

    
901
//create server action
902
function create_vm(machineName, imageRef, flavorRef){
903
    var image_logo = os_icon(get_image(imageRef).metadata);
904
    var uri = API_URL + '/servers';
905
    var payload = {
906
        "server": {
907
            "name": machineName,
908
            "imageRef": imageRef,
909
            "flavorRef" : flavorRef,
910
            "metadata" : {
911
                "OS" : image_logo
912
            }
913
        }
914
    };
915

    
916
    $.ajax({
917
    url: uri,
918
    type: "POST",
919
    contentType: "application/json",
920
    dataType: "json",
921
    data: JSON.stringify(payload),
922
    timeout: TIMEOUT,
923
    error: function(jqXHR, textStatus, errorThrown) {
924
                // close wizard and show error box
925
                $('#machines-pane a#create').data('overlay').close();
926
                    try {
927
                        ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
928
                    } catch(err) {
929
                        ajax_error(-5, "UI Error", 'Create VM', err);
930
                    }
931
           },
932
    success: function(data, textStatus, jqXHR) {
933
                if ( jqXHR.status == '202') {
934
                    ajax_success("CREATE_VM_SUCCESS", data.server.adminPass);
935
                } else {
936
                    // close wizard and show error box
937
                    $('#machines-pane a#create').data('overlay').close();
938
                    ajax_error(jqXHR.status, undefined, 'Create VM', jqXHR.responseText);
939
                }
940
            }
941
    });
942
}
943

    
944
// reboot action
945
function reboot(serverIDs){
946
    if (!serverIDs.length){
947
        //ajax_success('DEFAULT');
948
        return false;
949
    }
950
    // ajax post reboot call
951
    var payload = {
952
        "reboot": {"type" : "HARD"}
953
    };
954

    
955
    var serverID = serverIDs.pop();
956

    
957
    $.ajax({
958
        url: API_URL + '/servers/' + serverID + '/action',
959
        type: "POST",
960
        contentType: "application/json",
961
        dataType: "json",
962
        data: JSON.stringify(payload),
963
        timeout: TIMEOUT,
964
        error: function(jqXHR, textStatus, errorThrown) {
965
                    // in machine views
966
                    if ( $.cookie("pane") == 0) {
967
                        try {
968
                            display_failure(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
969
                        } catch (err) {
970
                            display_failure(0, serverID, 'Reboot', jqXHR.responseText);
971
                        }
972
                    }
973
                    // in network view
974
                    else {
975
                        try {
976
                            display_reboot_failure(jqXHR.status, serverID, jqXHR.responseText);
977
                        } catch (err) {
978
                            display_reboot_failure(0, serverID, jqXHR.responseText);
979
                        }
980
                    }
981
                },
982
        success: function(data, textStatus, jqXHR) {
983
                    if ( jqXHR.status == '202') {
984
                        try {
985
                            console.info('rebooted ' + serverID);
986
                        } catch(err) {}
987
                        // indicate that the action succeeded
988
                        // in machine views
989
                        if ( $.cookie("pane") == 0) {
990
                            display_success(serverID);
991
                        }
992
                        // in network view
993
                        else {
994
                            display_reboot_success(serverID);
995
                        }
996
                        // continue with the rest of the servers
997
                        reboot(serverIDs);
998
                    } else {
999
                        ajax_error(jqXHR.status, serverID, 'Reboot', jqXHR.responseText);
1000
                    }
1001
                }
1002
    });
1003
    return false;
1004
}
1005

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

    
1017
    var serverID = serverIDs.pop();
1018

    
1019
    $.ajax({
1020
        url: API_URL + '/servers/' + serverID + '/action',
1021
        type: "POST",
1022
        contentType: "application/json",
1023
        dataType: "json",
1024
        data: JSON.stringify(payload),
1025
        timeout: TIMEOUT,
1026
        error: function(jqXHR, textStatus, errorThrown) {
1027
                    try {
1028
                        display_failure(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1029
                    } catch(err) {
1030
                        display_failure(0, serverID, 'Shutdown', jqXHR.responseText);
1031
                    }
1032
                },
1033
        success: function(data, textStatus, jqXHR) {
1034
                    if ( jqXHR.status == '202') {
1035
                        try {
1036
                            console.info('suspended ' + serverID);
1037
                        } catch(err) {}
1038
                        // indicate that the action succeeded
1039
                        display_success(serverID);
1040
                        // continue with the rest of the servers
1041
                        shutdown(serverIDs);
1042
                    } else {
1043
                        ajax_error(jqXHR.status, serverID, 'Shutdown', jqXHR.responseText);
1044
                    }
1045
                }
1046
    });
1047
    return false;
1048
}
1049

    
1050
// destroy action
1051
function destroy(serverIDs) {
1052
    if (!serverIDs.length){
1053
        //ajax_success('DEFAULT');
1054
        return false;
1055
    }
1056
    // ajax post destroy call can have an empty request body
1057
    var payload = {};
1058

    
1059
    var serverID = serverIDs.pop();
1060

    
1061
    $.ajax({
1062
        url: API_URL + '/servers/' + serverID,
1063
        type: "DELETE",
1064
        contentType: "application/json",
1065
        dataType: "json",
1066
        data: JSON.stringify(payload),
1067
        timeout: TIMEOUT,
1068
        error: function(jqXHR, textStatus, errorThrown) {
1069
                    try {
1070
                        display_failure(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1071
                    } catch(err) {
1072
                        display_failure(0, serverID, 'Destroy', jqXHR.responseText);
1073
                    }
1074
                },
1075
        success: function(data, textStatus, jqXHR) {
1076
                    if ( jqXHR.status == '204') {
1077
                        try {
1078
                            console.info('destroyed ' + serverID);
1079
                        } catch (err) {}
1080

    
1081
                        // update status on local storage object
1082
                        vm = get_machine(serverID);
1083
                        vm.status = "DESTROY";
1084

    
1085
                        // indicate that the action succeeded
1086
                        display_success(serverID);
1087
                        // continue with the rest of the servers
1088
                        destroy(serverIDs);
1089
                    } else {
1090
                        ajax_error(jqXHR.status, serverID, 'Destroy', jqXHR.responseText);
1091
                    }
1092
                }
1093
    });
1094
    return false;
1095
}
1096

    
1097
// start action
1098
function start(serverIDs){
1099
    if (!serverIDs.length){
1100
        //ajax_success('DEFAULT');
1101
        return false;
1102
    }
1103
    // ajax post start call
1104
    var payload = {
1105
        "start": {}
1106
    };
1107

    
1108
    var serverID = serverIDs.pop();
1109

    
1110
    $.ajax({
1111
        url: API_URL + '/servers/' + serverID + '/action',
1112
        type: "POST",
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, 'Start', jqXHR.responseText);
1120
                    } catch(err) {
1121
                        display_failure(0, serverID, 'Start', jqXHR.responseText);
1122
                    }
1123
                },
1124
        success: function(data, textStatus, jqXHR) {
1125
                    if ( jqXHR.status == '202') {
1126
                        try {
1127
                            console.info('started ' + serverID);
1128
                        } catch(err) {}
1129
                        // indicate that the action succeeded
1130
                        display_success(serverID);
1131
                        // continue with the rest of the servers
1132
                        start(serverIDs);
1133
                    } else {
1134
                        ajax_error(jqXHR.status, serverID, 'Start', jqXHR.responseText);
1135
                    }
1136
                }
1137
    });
1138
    return false;
1139
}
1140

    
1141
// Show VNC console
1142
function vnc_attachment(host, port, password) {
1143
    // FIXME: Must be made into parameters, in settings.py
1144
    //vnc = open("", "displayWindow",
1145
    //    "status=yes,toolbar=yes,menubar=yes");
1146
    vd = document.open("application/x-vnc");
1147

    
1148
    vd.writeln("[connection]");
1149
    vd.writeln("host=" + host);
1150
    vd.writeln("port=" + port);
1151
    vd.writeln("password=" + password);
1152

    
1153
    vd.close();
1154
}
1155

    
1156
// Show VNC console
1157
function show_vnc_console(serverID, serverName, serverIP, host, port, password) {
1158
    var params_url = '?machine=' + serverName + '&host_ip=' + serverIP.v4 + '&host_ip_v6=' + serverIP.v6 + '&host=' + host + '&port=' + port + '&password=' + password;
1159
    var params_window = 'scrollbars=no,' +
1160
                        'menubar=no,' +
1161
                        'toolbar=no,' +
1162
                        'status=no,' +
1163
                        'top=0,' +
1164
                        'left=0,' +
1165
                        'height=' + screen.height + ',' +
1166
                        'width=' + screen.width + ',' +
1167
                        'fullscreen=yes';
1168
    
1169
    var url = 'machines/console' + params_url;
1170
    window.open(url, 'formresult' + serverID, params_window);
1171

    
1172
    // Restore os icon in list view
1173
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1174
    osIcon.attr('src',osIcon.attr('os'));
1175
    return false;
1176
}
1177

    
1178
// console action
1179
function open_console(serverIDs){
1180
    if (!serverIDs.length){
1181
        //ajax_success('DEFAULT');
1182
        return false;
1183
    }
1184
    // ajax post start call
1185
    var payload = {
1186
        "console": {"type": "vnc"}
1187
    };
1188

    
1189
    var serverID = serverIDs.pop();
1190

    
1191
    var machine = get_machine(serverID);
1192
    var serverName = machine.name;
1193
    try {
1194
        var serverIP = {};
1195
        serverIP.v4 = machine.addresses.values[0].values[0].addr;
1196
        serverIP.v6 = machine.addresses.values[0].values[1].addr;
1197
    } catch(err) { var serverIP = 'undefined'; }
1198

    
1199
    $.ajax({
1200
        url: API_URL + '/servers/' + serverID + '/action',
1201
        type: "POST",
1202
        contentType: "application/json",
1203
        dataType: "json",
1204
        data: JSON.stringify(payload),
1205
        timeout: TIMEOUT,
1206
        error: function(jqXHR, textStatus, errorThrown) {
1207
                    try {
1208
                        display_failure(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1209
                    } catch(err) {
1210
                        display_failure(0, serverID, 'Console', jqXHR.responseText);
1211
                    }
1212
                },
1213
        success: function(data, textStatus, jqXHR) {
1214
                    if ( jqXHR.status == '200') {
1215
                        try {
1216
                            console.info('got_console ' + serverID);
1217
                        } catch(err) {}
1218
                        // indicate that the action succeeded
1219
                        // show_vnc_console(serverID, serverName, serverIP,
1220
                        // data.console.host,data.console.port,data.console.password);
1221
                        show_vnc_console(serverID, serverName, serverIP,
1222
                                         data.console.host,data.console.port,data.console.password);
1223
                        display_success(serverID);
1224
                        // hide spinner
1225
                        $('#' + serverID + ' .spinner').hide();
1226
                        // continue with the rest of the servers
1227
                        open_console(serverIDs);
1228
                    } else {
1229
                        ajax_error(jqXHR.status, serverID, 'Console', jqXHR.responseText);
1230
                    }
1231
                }
1232
    });
1233
    return false;
1234
}
1235

    
1236
function vm_has_address(vmId) {
1237
    var vm = get_machine(vmId);
1238

    
1239
    if (!vm) return false;
1240

    
1241
    try {
1242
        var ip = vm.addresses.values[0].values[0].addr;
1243
    } catch (err) {
1244
        return false;
1245
    }
1246
    return ip;
1247
}
1248

    
1249
// connect to machine action
1250
function machine_connect(serverIDs){
1251
    if (!serverIDs.length){
1252
        //ajax_success('DEFAULT');
1253
        return false;
1254
    }
1255
    
1256
    // prefer metadata values for specific options (username, domain)
1257
    var username_meta_key = 'user';
1258
    var domain_meta_key = "domain";
1259

    
1260
    var serverID = serverIDs.pop();
1261
    var machine = get_machine(serverID);
1262
    var serverName = machine.name;
1263
    
1264
    try {
1265
        var serverIP = machine.addresses.values[0].values[0].addr;
1266
    } catch (err) { var serverIP = 'undefined'; }
1267

    
1268
    try {
1269
        var os = os_icon(machine.metadata);
1270
    } catch (err) { var os = 'undefined'; }
1271

    
1272
    var username = "";
1273
    try {
1274
        username = machine.metadata.values[username_meta_key];
1275
    } catch (err) { username = undefined }
1276

    
1277
    var domain = "";
1278
    try {
1279
        domain = machine.metadata.values[domain_meta_key];
1280
    } catch (erro) { domain = undefined }
1281

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

    
1284
    if (username) {
1285
        params_url += "&username=" + username;
1286
    }
1287

    
1288
    if (domain) {
1289
        params_url += "&domain=" + domain;
1290
    }
1291
    
1292
    //if ($.client.os == "Windows" && os == "windows") {
1293
        //// request rdp file
1294
        //window.open('machines/connect' + params_url + "&rdp=1");
1295
        //return;
1296
    //}
1297
    
1298
    // FIXME: I18n ???
1299
    var title = 'Connect to: ' + '<span class="machine-title"><img src="static/icons/machines/small/'+os+'-on.png" /> '+serverName+'</span>';
1300
    
1301
    // open msg box and fill it with json data retrieved from connect machine view
1302
    try {
1303
        // open msg box
1304
        msg_box({
1305
            title:title, 
1306
            fixed: true,
1307
            content:'loading...',
1308
            extra:'', 'ajax':'machines/connect' + params_url,
1309
            parse_data:function(data){
1310
                var box_content = "<a href='"+data.link.url+"'>"+data.link.title+"</a>";
1311
                if (!data.link.url) {
1312
                    box_content = "<span class='cmd'>"+data.link.title+"</span>";
1313
                }
1314
                data.title = false;
1315
                data.content = data.info;
1316
                data.extra = box_content;
1317
                return data;
1318
            }
1319
        });
1320
    } catch (error) {
1321
        // if msg box fails fallback redirecting the user to the connect url
1322
        window.open('machines/connect' + params_url);
1323
    }
1324

    
1325

    
1326
    // Restore os icon in list view
1327
    osIcon = $('#'+serverID).parent().parent().find('.list-logo');
1328
    osIcon.attr('src',osIcon.attr('os'));
1329

    
1330
    return false;
1331
}
1332

    
1333

    
1334
// rename server
1335
function rename(serverID, serverName){
1336
    if (!serverID.length){
1337
        //ajax_success('DEFAULT');
1338
        return false;
1339
    }
1340
    // ajax post rename call
1341
    var payload = {
1342
        "server": {"name": serverName}
1343
    };
1344

    
1345
    $.ajax({
1346
        url: API_URL + '/servers/' + serverID,
1347
        type: "PUT",
1348
        contentType: "application/json",
1349
        dataType: "json",
1350
        data: JSON.stringify(payload),
1351
        timeout: TIMEOUT,
1352
        error: function(jqXHR, textStatus, errorThrown) {
1353
                    try {
1354
                        display_failure(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1355
                    } catch(err) {
1356
                        display_failure(0, serverID, 'Rename', jqXHR.responseText);
1357
                    }
1358
                },
1359
        success: function(data, textStatus, jqXHR) {
1360
                    if ( jqXHR.status == '204' || jqXHR.status == '1223') {
1361
                        try {
1362
                            console.info('renamed ' + serverID);
1363
                        } catch(err) {}
1364
                        // indicate that the action succeeded
1365
                        display_success(serverID);
1366
                    } else {
1367
                        ajax_error(jqXHR.status, serverID, 'Rename', jqXHR.responseText);
1368
                    }
1369
                }
1370
    });
1371
    return false;
1372
}
1373

    
1374
// get server metadata
1375
function get_metadata(serverID, keys_only) {
1376
    $.ajax({
1377
        url: API_URL + '/servers/' + serverID + '/meta',
1378
        cache: false,
1379
        type: "GET",
1380
        //async: false,
1381
        dataType: "json",
1382
        timeout: TIMEOUT,
1383
        error: function(jqXHR, textStatus, errorThrown) {
1384
            try {
1385
                // close wizard and show error box
1386
                $("a#metadata-scrollable").data('overlay').close();
1387
                ajax_error(jqXHR.status, undefined, 'Get metadata', jqXHR.responseText);
1388
            } catch (err) {
1389
                ajax_error(-5, "UI Error", "Get metadata", err);
1390
            }
1391
        },
1392
        success: function(data, textStatus, jqXHR) {
1393
            // to list the new results in the edit dialog
1394
            if (keys_only) {
1395
                list_metadata_keys(serverID, data.metadata.values);
1396
            } else {
1397
                list_metadata(data);
1398
                list_metadata_keys(serverID, data.metadata.values);
1399
            }
1400
            //hide spinner
1401
            $('#metadata-wizard .large-spinner').hide();
1402
        }
1403
    });
1404
    return false;
1405
}
1406

    
1407
// delete metadata key-value pair
1408
function delete_metadata(serverID, meta_key) {
1409
    $.ajax({
1410
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1411
        type: "DELETE",
1412
        //async: false,
1413
        dataType: "json",
1414
        timeout: TIMEOUT,
1415
        error: function(jqXHR, textStatus, errorThrown) {
1416
            try {
1417
                // close wizard and show error box
1418
                $("a#metadata-scrollable").data('overlay').close();
1419
                ajax_error(jqXHR.status, undefined, 'Delete metadata', jqXHR.responseText);
1420
            } catch (err) {
1421
                ajax_error(-5, "UI Error", "Delete metadata", err);
1422
            }
1423
        },
1424
        success: function(data, textStatus, jqXHR) {
1425
                    // success: Do nothing, the UI is already updated
1426
        }
1427
    });
1428
    return false;
1429
}
1430

    
1431
// add metadata key-value pair
1432
function update_metadata(serverID, meta_key, meta_value) {
1433
    var payload = {
1434
        "meta": {
1435
        }
1436
    };
1437
    payload["meta"][meta_key] = meta_value;
1438

    
1439
    $.ajax({
1440
        url: API_URL + '/servers/' + serverID + '/meta/' + meta_key,
1441
        type: "PUT",
1442
        contentType: "application/json",
1443
        dataType: "json",
1444
        data: JSON.stringify(payload),
1445
        timeout: TIMEOUT,
1446
        error: function(jqXHR, textStatus, errorThrown) {
1447
            try {
1448
                // close wizard and show error box
1449
                $("a#metadata-scrollable").data('overlay').close();
1450
                ajax_error(jqXHR.status, undefined, 'add metadata', jqXHR.responseText);
1451
            } catch (err) {
1452
                ajax_error(-5, "UI Error", "add metadata", err);
1453
            }
1454
        },
1455
        success: function(data, textStatus, jqXHR) {
1456
            // success: Update icons if meta key is OS
1457
            if (meta_key == "OS") {
1458
                $("#metadata-wizard .machine-icon").attr("src","static/icons/machines/small/" + os_icon_from_value(meta_value) + '-' + $("#metadata-wizard div#on-off").text() + '.png');
1459
                var machine_icon = $("#machinesview-icon").find("div#" + serverID);
1460
                var machine_single = $("#machinesview-single").find("div#" + serverID);
1461

    
1462
                var os = os_icon_from_value(meta_value);
1463
                var state = $("#metadata-wizard div#on-off").text()
1464
                var state_single = $(".state", machine_single).hasClass("terminated-state") ? "off" : "on";
1465
                set_machine_os_image(machine_icon, "icon", state, os);
1466
                set_machine_os_image(machine_single, "single", state_single, os);
1467
            }
1468
        }
1469
    });
1470
    return false;
1471
}
1472

    
1473
// get stats
1474
function get_server_stats(serverID) {
1475
    $.ajax({
1476
        url: API_URL + '/servers/' + serverID + '/stats',
1477
        cache: false,
1478
        type: "GET",
1479
        //async: false,
1480
        dataType: "json",
1481
        timeout: TIMEOUT,
1482
        error: function(jqXHR, textStatus, errorThrown) {
1483
                // report error as text inline
1484
                $('#' + serverID + ' img.busy').hide();
1485
                $('#' + serverID + ' div.stat-error').show();
1486
        },
1487
        success: function(data, textStatus, jqXHR) {
1488
            // in icon view
1489
            if ( $.cookie('view') == 0 ) {
1490
                $('#' + serverID + ' img.busy').removeClass('busy');
1491
                $('#' + serverID + ' img.cpu').attr("src", data.stats.cpuBar);
1492
                $('#' + serverID + ' img.net').attr("src", data.stats.netBar);
1493
            }
1494
            // in single view
1495
            else if ( $.cookie('view') == 2 ) {
1496
                $('#' + serverID + ' div.cpu-graph img.stats').attr("src", data.stats.cpuTimeSeries);
1497
                $('#' + serverID + ' div.network-graph img.stats').attr("src", data.stats.netTimeSeries);
1498
            }
1499
        }
1500
    });
1501
    return false;
1502
}
1503

    
1504
// create network
1505
function create_network(networkName){
1506
    // ajax post start call
1507
    var payload = {
1508
        "network": { "name": networkName }
1509
    };
1510

    
1511
    $.ajax({
1512
        url: API_URL + '/networks',
1513
        type: "POST",
1514
        contentType: "application/json",
1515
        dataType: "json",
1516
        data: JSON.stringify(payload),
1517
        timeout: TIMEOUT,
1518
        error: function(jqXHR, textStatus, errorThrown) {
1519
            try {
1520
                // close wizard and show error box
1521
                $("a#networkscreate").overlay().close();
1522
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1523
            } catch (err) {
1524
                ajax_error(-5, "UI Error", "Create network", err);
1525
            }
1526
        },
1527
        success: function(data, textStatus, jqXHR) {
1528
            if ( jqXHR.status == '202') {
1529
                try {
1530
                    console.info('created network ' + networkName);
1531
                } catch(err) {}
1532
                /*
1533
                On success of this call nothing happens.
1534
                When the UI gets the first update containing the created server,
1535
                the creation wizard is closed and the new network is inserted
1536
                to the DOM. This is done in update_networks_view()
1537
                */
1538
            } else {
1539
                // close wizard and show error box
1540
                $("a#networkscreate").overlay().close();
1541
                ajax_error(jqXHR.status, undefined, 'Create network', jqXHR.responseText);
1542
            }
1543
        }
1544
    });
1545
    return false;
1546
}
1547

    
1548
// rename network
1549
function rename_network(networkID, networkName){
1550
    if (!networkID.length){
1551
        //ajax_success('DEFAULT');
1552
        return false;
1553
    }
1554
    // prepare payload
1555
    var payload = {
1556
        "network": {"name": networkName}
1557
    };
1558
    // ajax call
1559
    $.ajax({
1560
        url: API_URL + '/networks/' + networkID,
1561
        type: "PUT",
1562
        contentType: "application/json",
1563
        dataType: "json",
1564
        data: JSON.stringify(payload),
1565
        timeout: TIMEOUT,
1566
        error: function(jqXHR, textStatus, errorThrown) {
1567
            try {
1568
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1569
            } catch (err) {
1570
                ajax_error(-1, "UI Error", 'Rename network', err);
1571
            }
1572
        },
1573
        success: function(data, textStatus, jqXHR) {
1574
            if ( jqXHR.status == '204') {
1575
                try {
1576
                    console.info('renamed network' + networkID);
1577
                } catch(err) {}
1578
            } else {
1579
                ajax_error(jqXHR.status, undefined, 'Rename network', jqXHR.responseText);
1580
            }
1581
        }
1582
    });
1583
    return false;
1584
}
1585

    
1586
function delete_network(networkIDs){
1587
    if (!networkIDs.length){
1588
        //ajax_success('DEFAULT');
1589
        return false;
1590
    }
1591
    // get a network
1592
    var networkID = networkIDs.pop();
1593
    // ajax post destroy call can have an empty request body
1594
    var payload = {};
1595
    // ajax call
1596
    $.ajax({
1597
        url: API_URL + '/networks/' + networkID,
1598
        type: "DELETE",
1599
        contentType: "application/json",
1600
        dataType: "json",
1601
        data: JSON.stringify(payload),
1602
        timeout: TIMEOUT,
1603
        error: function(jqXHR, textStatus, errorThrown) {
1604
            try {
1605
                display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1606
            } catch (err) {
1607
                display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1608
            }
1609
        },
1610
        success: function(data, textStatus, jqXHR) {
1611
            if ( jqXHR.status == '204') {
1612
                try {
1613
                    console.info('deleted network ' + networkID);
1614
                } catch(err) {}
1615
                // continue with the rest of the servers
1616
                delete_network(networkIDs);
1617
            } else {
1618
                try {
1619
                    display_net_failure(jqXHR.status, networkID, 'Delete', jqXHR.responseText);
1620
                } catch (err) {
1621
                    display_net_failure(0, networkID, 'Delete', jqXHR.responseText);
1622
                }
1623
            }
1624
        }
1625
    });
1626
    return false;
1627
}
1628

    
1629
function add_server_to_network(networkID, serverIDs, serverNames, serverStates) {
1630
    if (!serverIDs.length){
1631
        // close the overlay when all the calls are made
1632
        $("a#add-machines-overlay").overlay().close();
1633
        return false;
1634
    }
1635
    // get a server
1636
    var serverID = serverIDs.pop();
1637
    var serverName = serverNames.pop();
1638
    var serverState = serverStates.pop();
1639
    // prepare payload
1640
    var payload = {
1641
            "add": { "serverRef": serverID }
1642
        };
1643
    // prepare ajax call
1644
    $.ajax({
1645
        url: API_URL + '/networks/' + networkID + '/action',
1646
        type: "POST",
1647
        contentType: "application/json",
1648
        dataType: "json",
1649
        data: JSON.stringify(payload),
1650
        timeout: TIMEOUT,
1651
        error: function(jqXHR, textStatus, errorThrown) {
1652
            try {
1653
                // close wizard and show error box
1654
                $("a#add-machines-overlay").data('overlay').close();
1655
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1656
            } catch (err) {
1657
                ajax_error(-5, "UI Error", 'Add server to network', err);
1658
            }
1659
        },
1660
        success: function(data, textStatus, jqXHR) {
1661
            if ( jqXHR.status == '202') {
1662
                try {
1663
                    console.info('added server ' + serverID + ' to network ' + networkID);
1664
                } catch(err) {}
1665
                // toggle the reboot dialog
1666
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1667
                // continue with the rest of the servers
1668
                add_server_to_network(networkID, serverIDs, serverNames, serverStates);
1669
            } else {
1670
                // close wizard and show error box
1671
                $("a#add-machines-overlay").data('overlay').close();
1672
                ajax_error(jqXHR.status, undefined, 'Add server to network', jqXHR.responseText);
1673
            }
1674
        }
1675
    });
1676
    return false;
1677
}
1678

    
1679
function remove_server_from_network(networkIDs, serverIDs, serverNames, serverStates) {
1680
    if (!networkIDs.length){
1681
        //ajax_success('DEFAULT');
1682
        return false;
1683
    }
1684
    // get a network and a server
1685
    var networkID = networkIDs.pop();
1686
    var serverID = serverIDs.pop();
1687
    var serverName = serverNames.pop();
1688
    var serverState = serverStates.pop();
1689
    // prepare payload
1690
    var payload = {
1691
            "remove": { "serverRef": serverID }
1692
        };
1693
    // prepare ajax call
1694
    $.ajax({
1695
        url: API_URL + '/networks/' + networkID + '/action',
1696
        type: "POST",
1697
        contentType: "application/json",
1698
        dataType: "json",
1699
        data: JSON.stringify(payload),
1700
        timeout: TIMEOUT,
1701
        error: function(jqXHR, textStatus, errorThrown) {
1702
            try {
1703
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1704
            } catch (err) {
1705
                ajax_error(-5, "UI Error", 'Remove server form network', err);
1706
            }
1707
        },
1708
        success: function(data, textStatus, jqXHR) {
1709
            if ( jqXHR.status == '202') {
1710
                try {
1711
                    console.info('deleted server ' + serverID + ' from network ' + networkID);
1712
                } catch(err) {}
1713
                // toggle the reboot dialog
1714
                display_reboot_dialog(networkID, serverID, serverName, serverState);
1715
                // continue with the rest of the servers
1716
                remove_server_form_network(networkIDs, serverIDs, serverNames, serverStates);
1717
            } else {
1718
                ajax_error(jqXHR.status, undefined, 'Remove server form network', jqXHR.responseText);
1719
            }
1720
        }
1721
    });
1722
    return false;
1723
}
1724

    
1725
function set_firewall(networkID, serverID, profile) {
1726
    if (!networkID.length || !serverID.length || !profile.length){
1727
        return false;
1728
    }
1729
    // prepare payload
1730
    var payload = {
1731
            "firewallProfile": { "profile": profile }
1732
    };
1733

    
1734
    // prepare ajax call
1735
    $.ajax({
1736
        url: API_URL + '/servers/' + serverID + '/action',
1737
        type: "POST",
1738
        contentType: "application/json",
1739
        dataType: "json",
1740
        data: JSON.stringify(payload),
1741
        timeout: TIMEOUT,
1742
        error: function(jqXHR, textStatus, errorThrown) {
1743
            try {
1744
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1745
            } catch (err) {
1746
                ajax_error(-5, "UI Error", 'Set firewall profile', err);
1747
            }
1748
        },
1749
        success: function(data, textStatus, jqXHR) {
1750
            if ( jqXHR.status == '202') {
1751
                try {
1752
                    console.info('for server ' + serverID + ' set firewall profile to ' + profile);
1753
                } catch(err) {}
1754
                // toggle the reboot dialog
1755
                try {
1756

    
1757
                    var serverName = $('div#net-' + networkID + '-server-' + serverID + ' div.machine-name-div span.name').text();
1758
                    var serverState = $('div#net-' + networkID + '-server-' + serverID + ' img.logo').attr('src').split('-')[1];
1759
                    serverState = serverState.split('.')[0];
1760
                    display_reboot_dialog(networkID, serverID, serverName, serverState);
1761

    
1762
                    //remove progress gif and toggle the content
1763
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').html(VARIOUS["APPLY"]);
1764
                    $('div#net-' + networkID + '-server-' + serverID + ' button.firewall-apply').attr("disabled", false);
1765
                    $('div#net-' + networkID + '-server-' + serverID + ' div.firewall-header').click();
1766

    
1767
                } catch (err) {
1768
                }
1769
                
1770
                // api call was made, set transition state to get reset 
1771
                // on the next machines update api call
1772
                var vm = get_machine(serverID)
1773
                vm.network_transition = "NETWORK_CHANGE";
1774
                show_machine_network_indicator(vm.id, 'pub');
1775
            } else {
1776
                ajax_error(jqXHR.status, undefined, 'Set firewall profile', jqXHR.responseText);
1777
            }
1778
        }
1779
    });
1780
    return false;
1781
}
1782

    
1783
// show the welcome screen
1784
function showWelcome() {
1785
    $("#view-select").fadeOut("fast");
1786
    $("#emptymachineslist").fadeIn("fast");
1787
    $("#machinesview").hide();
1788
}
1789

    
1790
// hide the welcome screen
1791
function hideWelcome() {
1792
    $("#emptymachineslist").fadeOut("fast");
1793
    $("#view-select").fadeIn("fast");
1794
    $("div#view-select").show();
1795
    $("#machinesview").show();
1796
}
1797

    
1798
function log_server_status_change(server_entry, new_status) {
1799
    // firebug console logging
1800
    try {
1801
        if ($("#machinesview-single").length > 0) {
1802
            console.info(server_entry.find("div.machine-details div.name").text() +
1803
                        ' from ' + server_entry.find(".state-label").text() +
1804
                        ' to ' + STATUSES[new_status]);
1805
        } else {
1806
            console.info(server_entry.find("div.name span.name").text() +
1807
                        ' from ' + server_entry.find(".status").text() +
1808
                        ' to ' + STATUSES[new_status]);
1809
        }
1810
    } catch(err) {}
1811
}
1812

    
1813
function get_flavor_params(flavorRef) {
1814
    var cpus, ram, disk;
1815
    if ( flavors.length > 0 ) {
1816
        var current_flavor = '';
1817
        for (i=0; i<flavors.length; i++) {
1818
            if (flavors[i]['id'] == flavorRef) {
1819
                current_flavor = flavors[i];
1820
            }
1821
        }
1822
        cpus = current_flavor['cpu'];
1823
        ram = current_flavor['ram'];
1824
        disk = current_flavor['disk'];
1825
    } else {
1826
        cpus = 'undefined';
1827
        ram = 'undefined';
1828
        disk = 'undefined';
1829
    }
1830
    return {'cpus': cpus, 'ram': ram, 'disk': disk};
1831
}
1832

    
1833
function get_image_params(imageRef) {
1834
    var image_name, image_size;
1835
    if ( images.length > 0 ) {
1836
        var current_image = '';
1837
        for (i=0; i<images.length; i++) {
1838
            if (images[i]['id'] == imageRef) {
1839
                current_image = images[i];
1840
            }
1841
        }
1842
        try {
1843
            image_name = current_image['name'];
1844
        } catch(err) { image_name = 'undefined'; }
1845
        try{
1846
            image_size = current_image['metadata']['values']['size'];
1847
        } catch(err) { image_size = 'undefined'; }
1848
    } else {
1849
        image_name = 'undefined';
1850
        image_size = 'undefined';
1851
    }
1852
    return {'name': image_name,'size': image_size};
1853
}
1854

    
1855
function get_public_ips(server) {
1856
    var ip4, ip6;
1857
    try {
1858
        if (server.addresses.values) {
1859
            $.each (server.addresses.values, function(i, value) {
1860
                if (value.id == 'public') {
1861
                    try {
1862
                        $.each (value.values, function(i, ip) {
1863
                            if (ip.version == '4') {
1864
                                ip4 = ip.addr;
1865
                            } else if (ip.version == '6') {
1866
                                ip6 = ip.addr;
1867
                            } else {
1868
                                ip4 = 'pending';
1869
                                ip6 = 'pending';
1870
                            }
1871
                        });
1872
                    } catch (err){
1873
                        try{console.info('Server ' + server.id + ' has invalid ips')}catch(err){};
1874
                        ip4 = 'pending';
1875
                        ip6 = 'pending';
1876
                    }
1877
                }
1878
            });
1879
        }
1880
    } catch (err) {
1881
        try{console.info('Server ' + server.id + ' has no network addresses')}catch(err){};
1882
        ip4 = 'pending';
1883
        ip6 = 'pending';
1884
    }
1885
    return {'ip4': ip4, 'ip6': ip6};
1886
}
1887

    
1888
function get_private_ips(server) {
1889

    
1890
}
1891

    
1892
function close_all_overlays() {
1893
        try {
1894
                $("a#networkscreate").overlay().close();
1895
        } catch(err) {}
1896
        try {
1897
                $('a#create').overlay().close();
1898
        } catch(err) {}
1899
        try {
1900
                $("a#add-machines-overlay").overlay().close();
1901
        } catch(err) {}
1902
        try {
1903
                $("a#metadata-scrollable").overlay().close();
1904
        } catch(err) {}
1905
        try {
1906
                $("a#msgbox").overlay().close();
1907
        } catch(err) {}
1908
        try {
1909
                $("a#feedbackbox").overlay().close();
1910
        } catch(err) {}
1911
}
1912

    
1913
// logout
1914
function user_session_logout() {
1915
    $.cookie("X-Auth-Token", null);
1916
    if (window.LOGOUT_REDIRECT !== undefined)
1917
    {
1918
        window.location = window.LOGOUT_REDIRECT;
1919
    } else {
1920
        window.location.reload();
1921
    }
1922
}
1923

    
1924
// action indicators
1925
function init_action_indicator_handlers(machines_view)
1926
{
1927
    // init once for each view
1928
    if (window.ACTION_ICON_HANDLERS == undefined)
1929
    {
1930
        window.ACTION_ICON_HANDLERS = {};
1931
    }
1932

    
1933
    if (machines_view in window.ACTION_ICON_HANDLERS)
1934
    {
1935
        return;
1936
    }
1937
    window.ACTION_ICON_HANDLERS[machines_view] = 1;
1938

    
1939
    if (machines_view == "list")
1940
    {
1941
        // totally different logic for list view
1942
        init_action_indicator_list_handlers();
1943
        return;
1944
    }
1945

    
1946
    function update_action_icon_indicators(force)
1947
    {
1948
        function show(el, action) {
1949
            $(".action-indicator", $(el)).attr("class", "action-indicator " + action);
1950
            $(".action-indicator", $(el)).show();
1951
        }
1952

    
1953
        function hide(el) {
1954
            $(".action-indicator", $(el)).hide();
1955
        }
1956

    
1957
        function get_pending_actions(el) {
1958
            return $(".confirm_single:visible", $(el));
1959
        }
1960

    
1961
        function other_indicators(el) {
1962
           return $("img.wave:visible, img.spinner:visible", $(el))
1963
        }
1964

    
1965
        $("div.machine:visible, div.single-container").each(function(index, el){
1966
            var el = $(el);
1967
            var pending = get_pending_actions(el);
1968
            var other = other_indicators(el);
1969
            var action = undefined;
1970
            var force_action = force;
1971
            var visible = $(el).css("display") == "block";
1972

    
1973
            if (force_action !==undefined && force_action.el !== el[0]) {
1974
                // force action for other vm
1975
                // skipping force action
1976
                force_action = undefined;
1977
            }
1978

    
1979
            if (force_action !==undefined && force_action.el === el[0]) {
1980
                action = force_action.action;
1981
            }
1982

    
1983
            if (other.length >= 1) {
1984
                return;
1985
            }
1986

    
1987
            if (pending.length >= 1 && force_action === undefined) {
1988
                action = $(pending.parent()).attr("class").replace("action-container","");
1989
            }
1990

    
1991
            if (action in {'console':''}) {
1992
                return;
1993
            }
1994

    
1995
            if (action !== undefined) {
1996
                show(el, action);
1997
            } else {
1998
                try {
1999
                    if (el.attr('id') == pending_actions[0][1])
2000
                    {
2001
                        return;
2002
                    }
2003
                } catch (err) {
2004
                }
2005
                hide(el);
2006
            }
2007

    
2008
        });
2009
    }
2010

    
2011
    // action indicators
2012
    $(".action-container").live('mouseover', function(evn) {
2013
        force_action = {'el': $(evn.currentTarget).parent().parent()[0], 'action':$(evn.currentTarget).attr("class").replace("action-container","")};
2014
        // single view case
2015
        if ($(force_action.el).attr("class") == "upper")
2016
        {
2017
            force_action.el = $(evn.currentTarget).parent().parent().parent()[0]
2018
        };
2019
        update_action_icon_indicators(force_action);
2020
    });
2021

    
2022
    $("img.spinner, img.wave").live('hide', function(){
2023
        update_action_icon_indicators();
2024
    });
2025
    // register events where icons should get updated
2026

    
2027
    // hide action indicator image on mouse out, spinner appear, wave appear
2028
    $(".action-container").live("mouseout", function(evn){
2029
        update_action_icon_indicators();
2030
    });
2031

    
2032
    $(".confirm_single").live("click", function(evn){
2033
        update_action_icon_indicators();
2034
    });
2035

    
2036
    $("img.spinner, img.wave").live('show', function(){
2037
        $("div.action-indicator").hide();
2038
    });
2039

    
2040
    $(".confirm_single button.no").live('click', function(evn){
2041
        $("div.action-indicator", $(evn.currentTarget).parent().parent()).hide();
2042
    });
2043

    
2044
    $(".confirm_multiple button.no").click(function(){
2045
        $("div.action-indicator").hide();
2046
    });
2047

    
2048
    $(".confirm_multiple button.yes").click(function(){
2049
        $("div.action-indicator").hide();
2050
    });
2051
}
2052

    
2053
function init_action_indicator_list_handlers()
2054
{
2055
    var skip_actions = { 'connect':'','details':'' };
2056

    
2057
    var has_pending_confirmation = function()
2058
    {
2059
        return $(".confirm_multiple:visible").length >= 1
2060
    }
2061

    
2062
    function update_action_indicator_icons(force_action, skip_pending)
2063
    {
2064
        // pending action based on the element class
2065
        var pending_action = $(".selected", $(".actions"))[0];
2066
        var selected = get_list_view_selected_machine_rows();
2067

    
2068
        // reset previous state
2069
        list_view_hide_action_indicators();
2070

    
2071
        if (pending_action == undefined && !force_action)
2072
        {
2073
            // no action selected
2074
            return;
2075
        }
2076

    
2077
        if (force_action != undefined)
2078
        {
2079
            // user forced action choice
2080
            var action_class = force_action;
2081
        } else {
2082
            // retrieve action name (reboot, stop, etc..)
2083
            var action_class = $(pending_action).attr("id").replace("action-","");
2084
        }
2085

    
2086
        selected.each(function(index, el) {
2087
            if (has_pending_confirmation() && skip_pending)
2088
            {
2089
                return;
2090
            }
2091
            var el = $(el);
2092
            var logo = $("img.list-logo", el);
2093
            $(".action-indicator", el).remove();
2094
            var cls = "action-indicator " + action_class;
2095
            // add icon div
2096
            logo.after('<div class="' + cls + '"></div>');
2097
            // hide os logo
2098
            $("img.list-logo", el).hide();
2099
        });
2100
    }
2101

    
2102
    // on mouseover we force the images to the hovered action
2103
    $(".actions a").live("mouseover", function(evn) {
2104
        var el = $(evn.currentTarget);
2105
        if (!el.hasClass("enabled"))
2106
        {
2107
            return;
2108
        }
2109
        var action_class = el.attr("id").replace("action-","");
2110
        if (action_class in skip_actions)
2111
        {
2112
            return;
2113
        }
2114
        update_action_indicator_icons(action_class, false);
2115
    });
2116

    
2117

    
2118
    // register events where icons should get updated
2119
    $(".actions a.enabled").live("click", function(evn) {
2120
        // clear previous selections
2121
        $("a.selected").removeClass("selected");
2122

    
2123
        var el = $(evn.currentTarget);
2124
        el.addClass("selected");
2125
        update_action_indicator_icons(undefined, false);
2126
    });
2127

    
2128
    $(".actions a").live("mouseout", function(evn) {
2129
        update_action_indicator_icons(undefined, false);
2130
    });
2131

    
2132
    $(".confirm_multiple button.no").click(function(){
2133
        list_view_hide_action_indicators();
2134
    });
2135

    
2136
    $(".confirm_multiple button.yes").click(function(){
2137
        list_view_hide_action_indicators();
2138
    });
2139

    
2140
    $("input[type=checkbox]").live('change', function(){
2141
        // pending_actions will become empty on every checkbox click/change
2142
        // line 154 machines_list.html
2143
        pending_actions = [];
2144
        if (pending_actions.length == 0)
2145
        {
2146
            $(".confirm_multiple").hide();
2147
            $("a.selected").each(function(index, el){$(el).removeClass("selected")});
2148
        }
2149
        update_action_indicator_icons(undefined, false);
2150
    });
2151

    
2152
}
2153

    
2154
function list_view_hide_action_indicators()
2155
{
2156
    $("tr td .action-indicator").remove();
2157
    $("tr td img.list-logo").show();
2158
}
2159

    
2160
function get_list_view_selected_machine_rows()
2161
{
2162
    var table = $("table.list-machines");
2163
    var rows = $("tr:has(input[type=checkbox]:checked)",table);
2164
    return rows;
2165
}
2166

    
2167
// machines images utils
2168
function set_machine_os_image(machine, machines_view, state, os, skip_reset_states, remove_state) {
2169
    var views_map = {'single': '.single-image', 'icon': '.logo'};
2170
    var states_map = {'on': 'state1', 'off': 'state3', 'hover': 'state4', 'click': 'state2'}
2171
    var sizes_map = {'single': 'large', 'icon': 'medium'}
2172

    
2173
    var size = sizes_map[machines_view];
2174
    var img_selector = views_map[machines_view];
2175
    var cls = states_map[state];
2176

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

    
2180
    var el = $(img_selector, machine);
2181
    var current_img = el.css("backgroundImage");
2182
    if (os == undefined){
2183
        new_img = current_img;
2184
    }
2185

    
2186
    // os changed
2187
    el.css("backgroundImage", new_img);
2188

    
2189
    // reset current state
2190
    if (skip_reset_states === undefined)
2191
    {
2192
        el.removeClass("single-image-state1");
2193
        el.removeClass("single-image-state2");
2194
        el.removeClass("single-image-state3");
2195
        el.removeClass("single-image-state4");
2196
    }
2197

    
2198
    if (remove_state !== undefined)
2199
    {
2200
        remove_state = "single-image-" + states_map[remove_state];
2201
        el.removeClass(remove_state);
2202
        return;
2203
    }
2204
    
2205
    // set proper state
2206
    el.addClass("single-image-" + cls);
2207
}
2208

    
2209

    
2210
// generic info box
2211
function show_feedback_form(msg, from_error) {
2212
    var box = $("#feedback-form");
2213
    box.addClass("notification-box");
2214

    
2215
    // initialize
2216
    box.find(".form-container").show();
2217
    box.find("textarea").val("");
2218
    box.find(".message").hide();
2219
    
2220
    var initial_msg = msg || undefined;
2221
    
2222
    var triggers = $("a#feedbackbox").overlay({
2223
        // some mask tweaks suitable for modal dialogs
2224
        mask: '#666',
2225
        top: '10px',
2226
        fixed: false,
2227
        closeOnClick: false,
2228
        oneInstance: false,
2229
        load: false
2230
    });
2231

    
2232
    
2233
    if (initial_msg && from_error) {
2234
        // feedback form from ajax_error window
2235
        box.find("textarea").val(initial_msg);
2236
        $("a#feedbackbox").overlay().onClose(function(){window.location.reload()});
2237
        box.find("textarea").height(200);
2238
        $("a#feedbackbox").overlay().onLoad(function(){box.find("textarea").focus().setCursorPosition(500);});
2239
        
2240
    }
2241

    
2242
    $("#feedback-form form").unbind("submit");
2243
    $("#feedback-form form").submit(function(event) {
2244
        event.preventDefault();
2245
            
2246
        // empty msg
2247
        if ($("textarea.feedback-text").val().replace(/^\s*|\s*$/,"") == "") {
2248
            alert($(".empty-error-msg", this).text());
2249
            return;
2250
        }
2251

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

    
2254
        $.ajax({
2255
            url: FEEDBACK_URL,
2256
            data: $(this).serialize(),
2257
            type: "POST",
2258
            // show loading
2259
            beforeSend: function() {box.find(".form-container").hide(); box.find(".sending").fadeIn() },
2260
            // hide form
2261
            complete: function() { box.find(".form-container").hide(); box.find(".sending").hide() },
2262
            // on success display success message
2263
            success: function() { box.find(".success").fadeIn(); box.find(".sending").hide() },
2264
            // display error message
2265
            error: function() { box.find(".errormsg").fadeIn(); box.find(".sending").hide() }
2266
        })
2267
    });
2268
    
2269
    $("a#feedbackbox").data('overlay').load();
2270

    
2271
    // reset feedback_pending for ajax_errors
2272
    window.FEEDBACK_PENDING = false;
2273
    return false;
2274
}
2275

    
2276
function get_user_data(extra_data) {
2277
    return $.extend({
2278
        'servers': $.extend({}, servers),
2279
        'client': {'browser': $.browser, 'screen': $.extend({}, screen), 'client': $.client},
2280
        'dates': {'now': new Date, 'lastUpdate': changes_since_date}
2281
    }, extra_data);
2282
}
2283

    
2284
function msg_box(config) {
2285
    var config = $.extend({'title':'Info message', 'content': 'this is an info message', 'ajax': false, 'extra':false}, config);
2286
    // prepare the error message
2287
    // bring up success notification
2288

    
2289
    var box = $("#notification-box");
2290
    box.addClass("notification-box");
2291
    box.addClass('success');
2292
    box.removeClass('error');
2293

    
2294
    var sel = function(s){return $(s, box)};
2295
    // reset texts
2296
    sel("h3 span.header-box").html("");
2297
    sel(".sub-text").html("");
2298
    sel(".password-container .password").html("");
2299
    sel("div.machine-now-building").html("");
2300
    
2301

    
2302
    // apply msg box contents
2303
    sel("h3 span.header-box").html(config.title);
2304
    sel("div.machine-now-building").html(config.content);
2305
    sel(".popup-header").removeClass("popup-header-error");
2306
    box.removeClass("popup-border-error");
2307
    sel(".popup-details").removeClass("popup-details-error");
2308
    sel(".popup-separator").removeClass("popup-separator-error");
2309
    
2310
    sel(".password-container").hide();
2311
    if (config.extra) {
2312
        sel(".password-container .password").html(config.extra);
2313
        sel(".password-container").show();
2314
    }
2315
    
2316
    var conf = {
2317
        // some mask tweaks suitable for modal dialogs
2318
        mask: '#666',
2319
        top: '10px',
2320
        closeOnClick: false,
2321
        oneInstance: false,
2322
        load: false,
2323
        fixed: config.fixed || false,
2324
        onClose: function () {
2325
            // With partial refresh working properly,
2326
            // it is no longer necessary to refresh the whole page
2327
            // choose_view();
2328
        }
2329
    }
2330
    
2331
    var triggers = $("a#msgbox").overlay(conf);
2332

    
2333
    try {
2334
        conf = $("a#msgbox").data('overlay').getConf();
2335
        conf.fixed = config.fixed || false;
2336
    } catch (err) {}
2337
    $("a#msgbox").data('overlay').load();
2338
    
2339
    var parse_data = config.parse_data || false;
2340
    var load_html = config.html || false;
2341
    var user_success = config.success || false;
2342
    config.ajax = config.ajax || {};
2343

    
2344
    // requested to show remote data in msg_box
2345
    if (config.ajax) {
2346
        $.ajax($.extend({ 
2347
            url:config.ajax, 
2348
            success: function(data){
2349
                // we want to get our data parsed before
2350
                // placing them in content
2351
                if (parse_data) {
2352
                    data = parse_data(data);
2353
                }
2354

    
2355
                // no json response
2356
                // load html body
2357
                if (load_html) {
2358
                    sel("div.machine-now-building").html(data);
2359
                } else {
2360

    
2361
                    if (data.title) {
2362
                        sel("h3 span.header-box").text(data.title);
2363
                    }
2364

    
2365
                    if (data.content) {
2366
                        sel("div.machine-now-building").html(data.content);
2367
                    }
2368
                    if (data.extra) {
2369
                        sel(".password-container .password").html(data.extra);
2370
                        sel(".password-container").show();
2371
                    }
2372
                    if (data.subinfo) {
2373
                        sel(".sub-text").html(data.subinfo);
2374
                    } else {
2375
                        sel(".sub-text").html("");
2376
                    }
2377
                }
2378

    
2379
                if (user_success) {
2380
                    user_success($("div.machine-now-building"));
2381
                }
2382
            },
2383
            error: function(xhr, status, err) {
2384
                ajax_error(-5, "UI Error", "Machine connect", err);
2385
            }
2386
        }, config.ajax_config));
2387
    }
2388
    return false;
2389
}
2390

    
2391

    
2392
function show_invitations() {
2393

    
2394
    handle_invitations = function(el) {
2395

    
2396
        // proper class to identify the overlay block
2397
        el.addClass("invitations");
2398

    
2399
        var cont = el;
2400
        var form = $(el).find("form");
2401

    
2402
        // remove garbage rows that stay in DOM between requests
2403
        $(".removable-field-row:hidden").remove();
2404

    
2405
        // avoid buggy behaviour, close all overlays if something went wrong
2406
        try {
2407
            // form is in content (form is not displayed if user has no invitations)
2408
            if ($("#invform #removable-name-container-1").length) {
2409
                $("#invform #removable-name-container-1").dynamicField();
2410
            }
2411
        } catch (err) {
2412
            close_all_overlays();
2413
        }
2414
        
2415
        // we copy/paste it on the title no need to show it twice
2416
        $(".invitations-left").hide();
2417

    
2418
        // reset title
2419
        $("#notification-box .header-box").html("");
2420
        $("#notification-box .header-box").html(window.INVITATIONS_TITLE + " " + $($(".invitations-left")[0]).text());
2421

    
2422
        // handle form submit
2423
        form.submit(function(evn){
2424
            evn.preventDefault();
2425

    
2426
            // do the post
2427
            $.post(form.attr("action"), form.serialize(), function(data) {
2428
                // replace data
2429
                $(cont).html(data); 
2430

    
2431
                // append all handlers again (new html data need to redo all changes)
2432
                handle_invitations(cont);
2433
            });
2434

    
2435
            return false;
2436
        });
2437
    }
2438
    
2439
    // first time clicked (show the msg box with /invitations content)
2440
    msg_box({
2441
        title:window.INVITATIONS_TITLE, 
2442
        content:'', 
2443
        fixed: false,
2444
        ajax:INVITATIONS_URL, 
2445
        html:true, 
2446
        success: function(el){ 
2447
            handle_invitations(el)
2448
        }
2449
    });
2450
}
2451

    
2452

    
2453
function get_short_v6(v6, parts_to_keep) {
2454
    var parts = v6.split(":");
2455
    var new_parts = parts.slice(parts.length - parts_to_keep);
2456
    return new_parts.join(":");
2457
}
2458

    
2459
function fix_v6_addresses() {
2460

    
2461
    // what to prepend
2462
    var match = "...";
2463
    // long ip min length
2464
    var limit = 20;
2465
    // parts to show after the transformation
2466
    // (from the end)
2467
    var parts_to_keep_from_end = 4;
2468

    
2469
    $(".ipv6-text").each(function(index, el){
2470
        var el = $(el);
2471
        var ip = $(el).text();
2472
            
2473
        // transformation not applyied
2474
        // FIXME: use $.data for the condition
2475
        if (ip.indexOf(match) == -1 && ip != "pending") {
2476
            
2477
            // only too long ips
2478
            if (ip.length > 20) {
2479
                $(el).data("ipstring", ip);
2480
                $(el).text(match + get_short_v6(ip, parts_to_keep_from_end));
2481
                $(el).attr("title", ip);
2482
                $(el).tooltip({'tipClass':'tooltip ipv6-tip', 'position': 'center center'});
2483
            }
2484
        } else {
2485
            if (ip.indexOf(match) == 0) {
2486
            } else {
2487
                // not a long ip anymore
2488
                $(el).data("ipstring", undefined);
2489
                $(el).css({'text-decoration':'none'});
2490

    
2491
                if ($(el).data('tooltip')) {
2492
                    $(el).data('tooltip').show = function () {};
2493
                }
2494
            }
2495
        }
2496
    });
2497
}
2498

    
2499
function fix_server_name(str, limit, append) {
2500
    limit = limit || 30;
2501
    append = append || "...";
2502

    
2503
    if (str.length > limit) {
2504
        str = str.substring(0,limit-append.length) + append;
2505
    }
2506
    return str;
2507
}
2508

    
2509
function show_machine_network_indicator(vm_id, network_id) {
2510
    var el = $("div#net-" + network_id + '-server-' + vm_id);
2511
    el.find(".network-progress-indicator").show();
2512
}
2513

    
2514

    
2515
function get_firewall_profile(vm_id) {
2516
    var vm = get_machine(vm_id);
2517

    
2518
    try {
2519
        return vm.addresses.values[0].firewallProfile;
2520
    } catch (err) {
2521
        return undefined;
2522
    }
2523
}