Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_main_view.js @ 45363ab3

History | View | Annotate | Download (41.8 kB)

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

    
35
;(function(root){
36

    
37
    // root
38
    var root = root;
39
    
40
    // setup namepsaces
41
    var snf = root.synnefo = root.synnefo || {};
42
    var models = snf.models = snf.models || {}
43
    var storage = snf.storage = snf.storage || {};
44
    var ui = snf.ui = snf.ui || {};
45

    
46
    var views = snf.views = snf.views || {}
47

    
48
    // shortcuts
49
    var bb = root.Backbone;
50
    var util = snf.util;
51
    
52
    // generic details overlay view.
53
    views.DetailsView = views.Overlay.extend({
54
        view_id: "details_view",
55
        
56
        content_selector: "#details-overlay",
57
        css_class: 'overlay-api-info overlay-info',
58
        overlay_id: "overlay-details",
59

    
60
        subtitle: "",
61
        title: "Details",
62
        
63
        show: function(title, msg, content) {
64
            this.title = title;
65
            this.msg = msg;
66
            this.content = content;
67
            views.DetailsView.__super__.show.apply(this);
68
        },
69

    
70
        beforeOpen: function() {
71
            this.set_title(this.title);
72
            if (!this.msg) { 
73
                this.$(".description.intro").hide() 
74
            } else {
75
                this.$(".description.intro").html(this.msg).show();
76
            }
77

    
78
            if (!this.content) { 
79
                this.$(".description.subinfo").hide() 
80
            } else {
81
                this.$(".description.subinfo").html(this.content).show(); 
82
            };
83
        }
84

    
85
    });
86

    
87
    views.SuspendedVMView = views.FeedbackView.extend({
88
        view_id: "suspended_info_view",
89
        
90
        css_class: 'overlay-api-info overlay-error non-critical',
91
        overlay_id: "overlay-api-info",
92

    
93
        subtitle: "",
94
        title: "VM Suspended",
95

    
96
        beforeOpen: function() {
97
            views.SuspendedVMView.__super__.beforeOpen.apply(this);
98
            $(this.$(".description p")[0]).html($("#suspended-vm-overlay .description").html())
99
        },
100

    
101
        show: function(vm, data, collect_data, extra_data, cb) {
102
            this.vm = vm;
103
            data = "Suspended VM Details";
104
            data += "\n====================";
105
            data += "\nID: " + vm.id;
106
            data += "\nName: " + vm.get('name');
107
            var public_ips = vm.get_public_ips();
108
            _.each(public_ips, function(ip) {
109
              data += "\nPublic IP{0}: {1}".format(ip.get('type'), ip.get('ip_address'));
110
            });
111
            data += "\n\n";
112
            views.SuspendedVMView.__super__.show.call(this, data, collect_data, extra_data, cb);
113
        }
114

    
115
    });
116

    
117
    views.ApiInfoView = views.Overlay.extend({
118
        view_id: "api_info_view",
119
        
120
        content_selector: "#api-info-overlay",
121
        css_class: 'overlay-api-info overlay-info',
122
        overlay_id: "overlay-api-info",
123

    
124
        subtitle: "",
125
        title: "API Access",
126

    
127
        beforeOpen: function() {
128
            var cont = this.$(".copy-content p");
129
            var token = snf.user.get_token();
130

    
131
            cont.html("");
132
            cont.text(token);
133
            
134
            this.cont = cont;
135
            this.token = token;
136
            try { delete this.clip; } catch (err) {};
137
        },
138

    
139
        onOpen: function() {
140
            views.ApiInfoView.__super__.onOpen(this, arguments);
141
            this.clip = new snf.util.ClipHelper(this.cont.parent(), this.token);
142
        },
143

    
144
        onClose: function() {
145
            var cont = this.$(".copy-content p");
146
            var token = snf.user.token;
147
            cont.html("");
148
        }
149
    });
150

    
151
    // TODO: implement me
152
    views.NoticeView = views.Overlay.extend({});
153

    
154
    views.MultipleActionsView = views.View.extend({
155
        view_id: "multiple_actions",
156

    
157
        _actions: {},
158
        el: '#multiple_actions_container',
159
        
160
        initialize: function() {
161
            this.actions = {};
162
            this.ns_config = {};
163

    
164
            views.MultipleActionsView.__super__.initialize.call(this);
165

    
166
            this.ns_tpl = this.$(".confirm_multiple_actions-template").clone()
167

    
168
            this.init_handlers();
169
            this.update_layout();
170
            
171
            // for heavy resize/scroll window events
172
            // do it `like a boss` 
173
            this.fix_position = _.throttle(this.fix_position, 100);
174
            this.update_layout = _.throttle(this.update_layout, 100);
175
            this.show_limit = 1;
176

    
177
            this.init_ns("vms", {
178
                msg_tpl:"Your actions will affect 1 machine",
179
                msg_tpl_plural:"Your actions will affect {0} machines",
180
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
181
                limit: 1,
182
                cancel_all: function() { snf.storage.vms.reset_pending_actions(); },
183
                do_all: function() { snf.storage.vms.do_all_pending_actions(); }
184
            });
185
            
186
            this.init_ns("nets", {
187
                msg_tpl:"Your actions will affect 1 private network",
188
                msg_tpl_plural:"Your actions will affect {0} private networks",
189
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
190
                limit: 1,
191
                cancel_all: function(actions, config) {
192
                  _.each(actions, function(action, id) {
193
                    action.model.actions.reset_pending();
194
                  });
195
                },
196
                do_all: function(actions, config) {
197
                  _.each(actions, function(action, id) {
198
                    var action_method = "do_{0}".format(action.actions[0]);
199
                    action.model[action_method].apply(action.model);
200
                  });
201
                }
202
            });
203

    
204
            this.init_ns("reboots", {
205
                msg_tpl:"1 machine needs to be rebooted for changes to apply.",
206
                msg_tpl_plural:"{0} machines needs to be rebooted for changes to apply.",
207
                actions_msg: {confirm: "Reboot all", cancel: "Cancel all"},
208
                limit: 0,
209
                cancel_all: function() { snf.storage.vms.reset_reboot_required(); },
210
                do_all: function() { snf.storage.vms.do_all_reboots(); }
211
            });
212

    
213
            this.init_ns("ips", {
214
                msg_tpl:"Your actions will affect 1 IP address.",
215
                msg_tpl_plural:"{0} actions will affect {0} IP addresses.",
216
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
217
                limit: 1,
218
                cancel_all: function(actions, config) {
219
                  _.each(actions, function(action, id) {
220
                    action.model.actions.reset_pending();
221
                  });
222
                },
223
                do_all: function(actions, config) {
224
                  _.each(actions, function(action, id) {
225
                    var action_method = "do_{0}".format(action.actions[0]);
226
                    action.model[action_method].apply(action.model);
227
                  });
228
                }
229
            });
230

    
231

    
232
            this.init_ns("keys", {
233
                msg_tpl:"Your actions will affect 1 public key.",
234
                msg_tpl_plural:"{0} actions will affect {0} public keys.",
235
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
236
                limit: 1,
237
                cancel_all: function(actions, config) {
238
                  _.each(actions, function(action, id) {
239
                    action.model.actions.reset_pending();
240
                  });
241
                },
242
                do_all: function(actions, config) {
243
                  _.each(actions, function(action, id) {
244
                    var action_method = "do_{0}".format(action.actions[0]);
245
                    action.model[action_method].apply(action.model);
246
                  });
247
                }
248

    
249
            });
250
        },
251
        
252
        init_ns: function(ns, params) {
253
            this.actions[ns] = {};
254
            var nsconf = this.ns_config[ns] = params || {};
255
            nsconf.cont = $(this.$("#conirm_multiple_cont_template").clone());
256
            nsconf.cont.attr("id", "confirm_multiple_cont_" + ns);
257
            $(this.el).find(".ns-confirms-cont").append(nsconf.cont).addClass(ns);
258
            $(this.el).find(".ns-confirms-cont").append(nsconf.cont).addClass("confirm-ns");
259
            nsconf.cont.find(".msg button.yes").text(
260
                nsconf.actions_msg.confirm).click(_.bind(this.do_all, this, ns));
261
            nsconf.cont.find(".msg button.no").text(
262
                nsconf.actions_msg.cancel).click(_.bind(this.cancel_all, this, ns));
263
        },
264

    
265
        do_all: function(ns) {
266
            this.ns_config[ns].do_all.apply(this, [this.actions[ns], this.ns_config[ns]]);
267
        },
268

    
269
        cancel_all: function(ns) {
270
            this.ns_config[ns].cancel_all.apply(this, [this.actions[ns], this.ns_config[ns]]);
271
        },
272
        
273
        register_actions_ns: function(store, ns) {
274
          store.bind("action:set-pending", function(action, actions, model) {
275
            this.handle_action_add(ns, model, [action]);
276
          }, this);
277
          store.bind("action:unset-pending", function(action, actions, model) {
278
            this.handle_action_remove(ns, model, action);
279
          }, this);
280
          store.bind("action:reset-pending", function(actions, model) {
281
            _.each(actions.actions, function(action) {
282
              this.handle_action_remove(ns, model, action);
283
            }, this);
284
          }, this);
285
        },
286

    
287
        init_handlers: function() {
288
            var self = this;
289

    
290
            $(window).resize(_.bind(function(){
291
                this.fix_position();
292
            }, this));
293

    
294
            $(window).scroll(_.bind(function(){
295
                this.fix_position();
296
            }, this));
297

    
298
            storage.vms.bind("change:pending_action", 
299
                             _.bind(this.handle_action_add, this, "vms"));
300
            storage.vms.bind("change:reboot_required", 
301
                             _.bind(this.handle_action_add, this, "reboots"));
302
            
303
            var ns_map = {
304
              'ips': storage.floating_ips,
305
              'keys': storage.keys,
306
              'nets': storage.networks
307
            }
308
            _.each(ns_map, function(store, ns) {
309
              this.register_actions_ns(store, ns);
310
            }, this);
311
        },
312
        
313
        handle_action_remove: function(type, model, action) {
314
          var actions = this.actions[type];
315
          var model_actions = actions[model.id] && actions[model.id].actions;
316
          if (!model_actions) { return }
317
          actions[model.id].actions = _.without(model_actions, action);
318
          if (actions[model.id].actions.length == 0) {
319
            delete actions[model.id];
320
          }
321
          this.update_layout();
322
        },
323

    
324
        handle_action_add: function(type, model, action) {
325
            var actions = this.actions[type];
326
            
327
            if (type == "keys") {
328
                actions[model.id] = {model: model, actions: action};
329
            }
330

    
331
            if (type == "ips") {
332
                actions[model.id] = {model: model, actions: action};
333
            }
334

    
335
            if (type == "nets") {
336
                actions[model.id] = {model: model, actions: action};
337
            }
338

    
339
            if (type == "vms") {
340
                _.each(actions, function(action) {
341
                    if (action.model.id == model.id) {
342
                        delete actions[action]
343
                    }
344
                });
345

    
346
                var actobject = {};
347
                actobject[action] = [[]];
348
                actions[model.id] = {model: model, actions: actobject};
349
                if (typeof action == "undefined") {
350
                    delete actions[model.id]
351
                }
352
            }
353

    
354
            if (type == "reboots") {
355
                _.each(actions, function(action) {
356
                    if (action.model.id == model.id) {
357
                        delete actions[action]
358
                    }
359
                });
360
                var actobject = {};
361
                actobject['reboot'] = [[]];
362
                actions[model.id] = {model: model, actions: actobject};
363
                if (!action) {
364
                    delete actions[model.id]
365
                }
366
            }
367
            
368
            this.update_layout();
369
        },
370

    
371
        update_actions_content: function(ns) {
372
            var conf = this.ns_config[ns];
373
            conf.cont.find(".details").empty();
374
            conf.cont.find(".msg p").text("");
375
            
376
            var count = 0;
377
            var actionscount = 0;
378
            _.each(this.actions[ns], function(actions, model_id) {
379
                count++;
380
                _.each(actions.actions, function(params, act_name){
381
                    if (_.isString(params)) {
382
                      actionscount++;
383
                      return
384
                    }
385
                    if (params && params.length) {
386
                        actionscount += params.length;
387
                    } else {
388
                        actionscount++;
389
                    }
390
                })
391
                this.total_confirm_actions++;
392
            });
393
            
394
            var limit = conf.limit;
395
            if (ui.main.current_view.view_id == "vm_list") {
396
                limit = 0;
397
            }
398

    
399
            if (actionscount > limit) {
400
                conf.cont.show();
401
                this.confirm_ns_open++;
402
            } else {
403
                conf.cont.hide();
404
            }
405
            
406
            var msg = count > 1 ? conf.msg_tpl_plural : conf.msg_tpl;
407
            conf.cont.find(".msg p").text(msg.format(count));
408

    
409
            return conf.cont;
410
        },
411

    
412
        fix_position: function() {
413
            $('.confirm_multiple').removeClass('fixed');
414
            if (($(this.el).offset().top +$(this.el).height())> ($(window).scrollTop() + $(window).height())) {
415
                $('.confirm_multiple').addClass('fixed');
416
            }
417
        },
418
        
419
        update_layout: function() {
420
            this.confirm_ns_open = 0;
421
            this.total_confirm_actions = 0;
422

    
423
            $(this.el).show();
424
            $(this.el).find("#conirm_multiple_cont_template").hide();
425
            $(this.el).find(".confirm-ns").show();
426
            
427
            _.each(this.ns_config, _.bind(function(params, key) {
428
                this.update_actions_content(key);
429
            }, this));
430

    
431
            if (this.confirm_ns_open > 0) {
432
                $(this.el).show();
433
                this.$(".confirm-all-cont").hide();
434
                this.$(".ns-confirms-cont").show();
435
            } else {
436
                $(this.el).hide();
437
                this.$(".confirm-all-cont").hide();
438
                this.$(".ns-confirms-cont").hide();
439
            }
440

    
441
            $(window).trigger("resize");
442
        }
443
    })
444
    
445
    // menu wrapper view
446
    views.SelectView = views.View.extend({
447
        
448
        initialize: function(view, router) {
449
            this.parent = view;
450
            this.router = router;
451
            this.pane_view_selector = $(".css-tabs");
452
            this.machine_view_selector = $("#view-select");
453
            this.el = $(".css-tabs");
454
            this.title = $(".tab-name");
455

    
456
            this.set_handlers();
457
            this.update_layout();
458

    
459
            views.SelectView.__super__.initialize.apply(this, arguments);
460
        },
461
        
462
        clear_active: function() {
463
            this.pane_view_selector.find("a").removeClass("active");
464
            this.machine_view_selector.find("a").removeClass("activelink");
465
        },
466
        
467
        // intercept menu links
468
        set_handlers: function() {
469
            var self = this;
470
            this.pane_view_selector.find("a").hover(function(){
471
                self.title.text($(this).data("hover-title"));
472
            }, function(){
473
                self.title.text(self.parent.get_title());
474
            });
475

    
476
            this.pane_view_selector.find("a#machines_view_link").click(_.bind(function(ev){
477
                ev.preventDefault();
478
                this.router.vms_index();
479
            }, this))
480
            this.pane_view_selector.find("a#networks_view_link").click(_.bind(function(ev){
481
                ev.preventDefault();
482
                this.router.networks_view();
483
            }, this))
484
            this.pane_view_selector.find("a#ips_view_link").click(_.bind(function(ev){
485
                ev.preventDefault();
486
                this.router.ips_view();
487
            }, this))
488
            this.pane_view_selector.find("a#public_keys_view_link").click(_.bind(function(ev){
489
                ev.preventDefault();
490
                this.router.public_keys_view();
491
            }, this))
492
            
493
            this.machine_view_selector.find("a#machines_view_icon_link").click(_.bind(function(ev){
494
                ev.preventDefault();
495
                var d = $.now();
496
                this.router.vms_icon_view();
497
            }, this))
498
            this.machine_view_selector.find("a#machines_view_list_link").click(_.bind(function(ev){
499
                ev.preventDefault();
500
                this.router.vms_list_view();
501
            }, this))
502
            this.machine_view_selector.find("a#machines_view_single_link").click(_.bind(function(ev){
503
                ev.preventDefault();
504
                this.router.vms_single_view();
505
            }, this))
506
        },
507

    
508
        update_layout: function() {
509
            this.clear_active();
510

    
511
            var pane_index = this.parent.pane_ids[this.parent.current_view_id];
512
            $(this.pane_view_selector.find("a")).removeClass("active");
513
            $(this.pane_view_selector.find("a").get(pane_index)).addClass("active");
514
            
515
            if (this.parent.current_view && this.parent.current_view.vms_view) {
516

    
517
                if (storage.vms.length > 0) {
518
                    this.machine_view_selector.show();
519
                    var machine_index = this.parent.views_ids[this.parent.current_view_id];
520
                    $(this.machine_view_selector.find("a").get(machine_index)).addClass("activelink");
521
                } else {
522
                    this.machine_view_selector.hide();
523
                }
524
            } else {
525
                this.machine_view_selector.hide();
526
            }
527

    
528
        }
529
    });
530

    
531
    views.MainView = views.View.extend({
532
        el: 'body',
533
        view_id: 'main',
534
        
535
        // FIXME: titles belong to SelectView
536
        views_titles: {
537
            'icon': 'machines', 'single': 'machines', 
538
            'list': 'machines', 'networks': 'networks',
539
            'ips': 'IP addresses',
540
            'public-keys': 'public keys'
541
        },
542

    
543
        // indexes registry
544
        views_indexes: {
545
          0: 'icon', 
546
          2: 'single', 
547
          1: 'list', 
548
          3: 'networks', 
549
          4: 'ips',
550
          5: 'public-keys'
551
        },
552

    
553
        views_pane_indexes: {
554
          0: 'single', 
555
          1: 'networks', 
556
          2: 'ips', 
557
          3: 'public-keys'
558
        },
559

    
560
        // views classes registry
561
        views_classes: {
562
            'icon': views.IconView, 
563
            'single': views.SingleView, 
564
            'list': views.ListView, 
565
            'networks': views.NetworksPaneView, 
566
            'ips': views.IpsPaneView,
567
            'public-keys': views.PublicKeysPaneView
568
        },
569

    
570
        // view ids
571
        views_ids: {'icon':0, 'single':2, 'list':1, 'networks':3, 'ips':4, 'public-keys': 5},
572

    
573
        // on which pane id each view exists
574
        // machine views (icon,single,list) are all on first pane
575
        pane_ids: {'icon':0, 'single':0, 'list':0, 'networks':1, 'ips':2, 'public-keys': 3},
576
    
577
        initialize: function(show_view) {
578
            if (!show_view) { show_view = 'icon' };
579
            
580
            this.router = snf.router;
581
            this.empty_hidden = true;
582
            // fallback to browser error reporting (true for debug)
583
            this.skip_errors = true
584

    
585
            // reset views
586
            this.views = {};
587

    
588
            this.el = $("#app");
589
            // reset main view status
590
            this._loaded = false;
591
            this.status = "Initializing...";
592

    
593
            // initialize handlers
594
            this.init_handlers();
595

    
596
            // identify initial view from user cookies
597
            // this view will be visible after loading of
598
            // main view
599
            this.initial_view = this.session_view();
600

    
601
            views.MainView.__super__.initialize.call(this);
602

    
603
            $(window).focus(_.bind(this.handle_window_focus, this, "focus"));
604
            $(window).blur(_.bind(this.handle_window_focus, this, "out"));
605

    
606
            this.focused = true;
607
        },
608

    
609
        handle_window_focus: function(focus) {
610
            if (!snf.config.delay_on_blur) { return };
611

    
612
            if (focus === "focus") {
613
                this.focused = true;
614
                this.set_interval_timeouts();
615
            } else {
616
                this.focused = false;
617
                this.set_interval_timeouts();
618
            }
619
        },
620

    
621
        set_interval_timeouts: function(time) {
622
            _.each(this._fetchers, _.bind(function(fetcher){
623
                if (!fetcher) { return };
624
                if (this.focused) {
625
                    fetcher.interval = fetcher.normal_interval;
626
                    fetcher.stop(false).start(true);
627
                } else {
628
                    fetcher.interval = fetcher.maximum_interval;
629
                    fetcher.stop(false).start(false);
630
                }
631

    
632
            }, this));
633
        },
634
        
635
        vms_handlers_registered: false,
636

    
637
        // register event handlers
638
        // 
639
        // vms storage events to identify if vms list 
640
        // is empty and display empty view if user viewing
641
        // a machine view
642
        //
643
        // api/ui error event handlers
644
        init_handlers: function() {
645
            // vm handlers
646
            storage.vms.bind("remove", _.bind(this.check_empty, this));
647
            storage.vms.bind("add", _.bind(this.check_empty, this));
648
            storage.vms.bind("change:status", _.bind(this.check_empty, this));
649
            storage.vms.bind("reset", _.bind(this.check_empty, this));
650
            storage.quotas.bind("change", _.bind(this.update_create_buttons_status, this));
651
            // additionally check quotas the first time they get fetched
652
            storage.quotas.bind("add", _.bind(this.update_create_buttons_status, this));
653
            
654
        },
655
        
656
        handle_api_error_state: function(state) {
657
            if (snf.api.error_state === snf.api.STATES.ERROR) {
658
                this.stop_intervals();
659
            } else {
660
                if (this.intervals_stopped) {
661
                    this.update_intervals();
662
                }
663
            }
664
        },
665
        
666
        handle_api_error: function(args) {
667
            if (arguments.length == 1) { arguments = _.toArray(arguments[0])};
668

    
669
            if (!_.last(arguments).display) {
670
                return;
671
            }
672

    
673
            this.error_state = true;
674
            
675
            var xhr = arguments[0];
676
            var args = util.parse_api_error.apply(util, arguments);
677
            
678
            // force logout if UNAUTHORIZED request arrives
679
            if (args.code == 401) { snf.auth_client.redirect_to_login(); return };
680

    
681
            var error_entry = [args.ns, args.code, args.message, 
682
                               args.api_message, args.type, 
683
                               args.details, args];
684
            this.error_view.show_error.apply(this.error_view, error_entry);
685
        },
686

    
687
        handle_ui_error: function(data) {
688
            var msg = data.msg, code = data.code, err_obj = data.error;
689
            error = msg + "<br /><br />" + snf.util.stacktrace().replace("at", "<br /><br />at");
690
            params = { title: "UI error", extra_details: data.extra };
691
            delete data.extra.allow_close;
692
            params.allow_close = data.extra.allow_close === undefined ? true : data.extra.allow_close;
693
            this.error_view.show_error("UI", -1, msg, undefined, 
694
                                       "JS Exception", error, params);
695
        },
696

    
697
        init_overlays: function() {
698
            this.create_vm_view = new views.CreateVMView();
699
            this.create_snapshot_view = new views.CreateSnapshotView();
700
            this.api_info_view = new views.ApiInfoView();
701
            this.details_view = new views.DetailsView();
702
            this.suspended_view = new views.SuspendedVMView();
703
            //this.notice_view = new views.NoticeView();
704
        },
705
        
706
        show_loading_view: function() {
707
            $("#container #content").hide();
708
            $("#loading-view").show();
709
        },
710

    
711
        hide_loading_view: function() {
712
            $("#container #content").show();
713
            $("#loading-view").hide();
714
            $(".css-panes").show();
715
        },
716
        
717
        items_to_load: 6,
718
        completed_items: 0,
719
        check_status: function(loaded) {
720
            this.completed_items++;
721
            // images, flavors loaded
722
            if (this.completed_items == this.items_to_load) {
723
                this.update_status("layout", 1);
724
                var self = this;
725
                window.setTimeout(function(){
726
                    self.after_load();
727
                }, 10)
728
            }
729
        },
730
            
731
        load_missing_images: function(cb) {
732
            synnefo.storage.vms.load_missing_images(cb);
733
        },
734

    
735
        load_nets_and_vms: function() {
736
            var self = this;
737
            this.update_status("vms", 0);
738
            storage.vms.fetch({refresh:true, update:false, success: function(){
739
                self.load_missing_images(function(){
740
                    self.update_status("vms", 1);
741
                    self.update_status("layout", 0);
742
                    self.check_status();
743
                });
744
            }});
745

    
746
            this.update_status("networks", 0);
747
            $.when([
748
                   storage.networks.fetch({refresh: true}), 
749
                   storage.floating_ips.fetch({refresh: true}),
750
                   storage.subnets.fetch({refresh: true}),
751
                   storage.ports.fetch({refresh: true})
752
            ]).done(function() {
753
              self.update_status("networks", 1);
754
              self.check_status();
755
            })
756
        },  
757
        
758
        _fetchers: {},
759
        init_interval: function(key, collection) {
760
            if (this._fetchers[key]) { return }
761
            var fetcher_params = [snf.config.update_interval, 
762
                                  snf.config.update_interval_increase || 500,
763
                                  snf.config.fast_interval || snf.config.update_interval/2, 
764
                                  snf.config.update_interval_increase_after_calls || 4,
765
                                  snf.config.update_interval_max || 20000,
766
                                  true, 
767
                                  {is_recurrent: true}];
768
            var fetcher = collection.get_fetcher.apply(collection, _.clone(fetcher_params));
769
            this._fetchers[key] = fetcher;
770
            collection.fetch();
771

    
772
        },
773

    
774
        init_intervals: function() {
775
            _.each({
776
              'networks': storage.networks,
777
              'vms': storage.vms,
778
              'quotas': storage.quotas,
779
              'ips': storage.floating_ips,
780
              'subnets': storage.subnets,
781
              'ports': storage.ports,
782
              'keys': storage.keys
783
            }, function(col, name) {
784
              this.init_interval(name, col)
785
            }, this);
786
        },
787

    
788
        stop_intervals: function() {
789
            _.each(this._fetchers, function(fetcher) {
790
                fetcher.stop();
791
            });
792
            this.intervals_stopped = true;
793
        },
794

    
795
        update_intervals: function() {
796
            _.each(this._fetchers, function(fetcher) {
797
                fetcher.stop();
798
                fetcher.start();
799
            })
800
            this.intervals_stopped = false;
801
        },
802

    
803
        after_load: function() {
804
            var self = this;
805
            this.init_intervals();
806
            this.update_intervals();
807
            this.update_status("layout", 0);
808
            
809
            // bypass update_hidden_views in initial view
810
            // rendering to force all views to get render
811
            // on their creation
812
            var uhv = snf.config.update_hidden_views;
813
            snf.config.update_hidden_views = true;
814
            this.initialize_views();
815
            snf.config.update_hidden_views = uhv;
816

    
817
            window.setTimeout(function() {
818
                self.load_initialize_overlays();
819
            }, 20);
820
        },
821

    
822
        load_initialize_overlays: function() {
823
            this.init_overlays();
824
            // display initial view
825
            this.loaded = true;
826
            
827
            // application start point
828

    
829
            this.check_empty();
830
            this.show_initial_view();
831
        },
832

    
833
        load: function() {
834
            if (synnefo.config.use_glance) {
835
                synnefo.glance.register();
836
            }
837
            this.error_view = new views.ErrorView();
838
            this.vm_resize_view = new views.VmResizeView();
839

    
840
            // api request error handling
841
            synnefo.api.bind("error", _.bind(this.handle_api_error, this));
842
            synnefo.api.bind("change:error_state", _.bind(this.handle_api_error_state, this));
843
            synnefo.ui.bind("error", _.bind(this.handle_ui_error, this));
844

    
845
            this.feedback_view = new views.FeedbackView();
846
            
847
            if (synnefo.config.use_glance) {
848
                this.custom_images_view = new views.CustomImagesOverlay();
849
            }
850

    
851
            var self = this;
852
            // initialize overlay views
853
            
854
            // display loading message
855
            this.show_loading_view();
856
            // sync load initial data
857
            this.update_status("images", 0);
858
            storage.images.fetch({refresh:true, update:false, success: function(){
859
                self.update_status("images", 1);
860
                self.check_status();
861
                self.load_nets_and_vms();
862
            }});
863
            this.update_status("flavors", 0);
864
            storage.flavors.fetch({refresh:true, update:false, success:function(){
865
                self.update_status("flavors", 1);
866
                self.check_status()
867
            }});
868

    
869
            this.update_status("resources", 0);
870
            storage.resources.fetch({refresh:true, update:false, success: function(){
871
                self.update_status("resources", 1);
872
                self.update_status("quotas", 0);
873
                self.check_status();
874
                storage.quotas.fetch({refresh:true, update:true, success: function() {
875
                  self.update_status("quotas", 1);
876
                  self.update_status("layout", 1);
877
                  self.check_status()
878
                }})
879
            }})
880
        },
881

    
882
        update_status: function(ns, state) {
883
            var el = $("#loading-view .header."+ns);
884
            if (state == 0) {
885
                el.removeClass("off").addClass("on");
886
            }
887
            if (state == 1) {
888
                el.removeClass("on").addClass("done");
889
            }
890
        },
891

    
892
        initialize_views: function() {
893
            this.select_view = new views.SelectView(this, this.router);
894
            this.empty_view = new views.EmptyView();
895
            this.metadata_view = new views.MetadataView();
896
            this.multiple_actions_view = new views.MultipleActionsView();
897
            
898
            this.add_view("icon");
899
            this.add_view("list");
900
            this.add_view("single");
901
            this.add_view("networks");
902
            this.add_view("ips");
903
            this.add_view("public-keys");
904

    
905
            this.init_menu();
906
        },
907

    
908
        init_menu: function() {
909
            $(".usermenu .feedback").click(_.bind(function(e){
910
                e.preventDefault();
911
                this.feedback_view.show();
912
            }, this));
913
        },
914
        
915
        // initial view based on user cookie
916
        show_initial_view: function() {
917
          this.set_vm_view_handlers();
918
          this.hide_loading_view();
919
          bb.history.start();
920
          this.trigger("ready");
921
        },
922

    
923
        show_vm_details: function(vm) {
924
            if (vm) {
925
              this.router.vm_details_view(vm.id);
926
            }
927
        },
928
        
929
        update_create_buttons_status: function() {
930
            var nets = storage.quotas.get('cyclades.network.private');
931
            var vms = storage.quotas.get('cyclades.vm');
932
            
933
            if (!nets || !vms) { return }
934

    
935
            if (!nets.can_consume()) {
936
                $("#networks-pane a.createbutton").addClass("disabled");
937
            } else {
938
                $("#networks-pane a.createbutton").removeClass("disabled");
939
            }
940

    
941
            if (!vms.can_consume()) {
942
                $("#createcontainer #create").addClass("disabled");
943
            } else {
944
                $("#createcontainer #create").removeClass("disabled");
945
            }
946
        },
947

    
948
        set_vm_view_handlers: function() {
949
            var self = this;
950
            $("#createcontainer #create").click(function(e){
951
                e.preventDefault();
952
                if ($(this).hasClass("disabled")) { return }
953
                synnefo.storage.images.fetch({
954
                  complete: function() {
955
                    self.router.vm_create_view();
956
                  }
957
                });
958
            });
959
        },
960

    
961
        check_empty: function() {
962
            if (!this.loaded) { return }
963
            if (storage.vms.length == 0) {
964
                this.show_view("machines");
965
                this.router.show_welcome();
966
                this.empty_hidden = false;
967
            } else {
968
                this.hide_empty();
969
            }
970
        },
971

    
972
        show_empty: function() {
973
            if (!this.empty_hidden) { return };
974
            $("#machines-pane-top").addClass("empty");
975

    
976
            this.$(".panes").hide();
977
            this.$("#machines-pane").show();
978

    
979
            this.hide_views([]);
980
            this.empty_hidden = false;
981
            this.empty_view.show();
982
            this.select_view.update_layout();
983
            this.empty_hidden = false;
984
        },
985

    
986
        hide_empty: function() {
987
            if (this.empty_hidden) { return };
988
            $("#machines-pane-top").removeClass("empty");
989

    
990
            this.empty_view.hide(true);
991
            this.router.vms_index();
992
            this.empty_hidden = true;
993
            this.select_view.update_layout();
994
        },
995
        
996
        get_title: function(view_id) {
997
            var view_id = view_id || this.current_view_id;
998
            return this.views_titles[view_id];
999
        },
1000

    
1001
        // return class object for the given view or false if
1002
        // the view is not registered
1003
        get_class_for_view: function (view_id) {
1004
            if (!this.views_classes[view_id]) {
1005
                return false;
1006
            }
1007
            return this.views_classes[view_id];
1008
        },
1009

    
1010
        view: function(view_id) {
1011
            return this.views[view_id];
1012
        },
1013

    
1014
        add_view: function(view_id) {
1015
            if (!this.views[view_id]) {
1016
                var cls = this.get_class_for_view(view_id);
1017
                if (this.skip_errors) {
1018
                    this.views[view_id] = new cls();
1019
                    $(this.views[view_id]).bind("resize", _.bind(function() {
1020
                        window.forcePositionFooter();
1021
                        this.multiple_actions_view.fix_position();
1022
                    }, this));
1023
                } else {
1024
                    // catch ui errors
1025
                    try {
1026
                        this.views[view_id] = new cls();
1027
                        $(this.views[view_id]).bind("resize", _.bind(function() {
1028
                            window.positionFooter();
1029
                            this.multiple_actions_view.fix_position();
1030
                        }, this));
1031
                    } catch (err) {snf.ui.trigger_error(-1, "Cannot add view", err)}
1032
                }
1033
            } else {
1034
            }
1035

    
1036
            if (this.views[view_id].vms_view) {
1037
                this.views[view_id].metadata_view = this.metadata_view;
1038
            }
1039
            return this.views[view_id];
1040
        },
1041
            
1042
        hide_views: function(skip) {
1043
            _.each(this.views, function(view) {
1044
                if (skip.indexOf(view) === -1) {
1045
                    view.hide();
1046
                }
1047
            }, this)
1048
        },
1049
        
1050
        get: function(view_id) {
1051
            return this.views[view_id];
1052
        },
1053
        
1054
        session_view: function() {
1055
            if (this.pane_view_from_session() > 0) {
1056
                return this.views_pane_indexes[this.pane_view_from_session()];
1057
            } else {
1058
                return this.views_indexes[this.machine_view_from_session()];
1059
            }
1060
        },
1061

    
1062
        pane_view_from_session: function() {
1063
            return $.cookie("pane_view") || 0;
1064
        },
1065

    
1066
        machine_view_from_session: function() {
1067
            return $.cookie("machine_view") || 0;
1068
        },
1069

    
1070
        update_session: function() {
1071
            $.cookie("pane_view", this.pane_ids[this.current_view_id]);
1072
            if (this.current_view.vms_view) {
1073
                $.cookie("machine_view", this.views_ids[this.current_view_id]);
1074
            }
1075
        },
1076

    
1077
        identify_view: function(view_id) {
1078
            // machines view_id is an alias to
1079
            // one of the 3 machines view
1080
            // identify which one (if no cookie set defaults to icon)
1081
            if (view_id == "machines") {
1082
                var index = this.machine_view_from_session();
1083
                view_id = this.views_indexes[index];
1084
            }
1085
            return view_id;
1086
        },
1087
        
1088
        // switch to current view pane
1089
        // if differs from the visible one
1090
        show_view_pane: function() {
1091
            if (this.current_view.pane != this.current_pane) {
1092
                $(this.current_view.pane).show();
1093
                $(this.current_pane).hide();
1094
                this.current_pane = this.current_view.pane;
1095
            }
1096
        },
1097
        
1098
        show_view: function(view_id) {
1099
            //var d = new Date;
1100
            var ret = this._show_view(view_id);
1101
            //console.log((new Date)-d)
1102
            return ret;
1103
        },
1104

    
1105
        _show_view: function(view_id) {
1106
                // same view, visible
1107
                // get out of here asap
1108
                if (this.current_view && 
1109
                    this.current_view.id == view_id && 
1110
                    this.current_view.visible()) {
1111
                    return;
1112
                }
1113
                
1114
                // choose proper view_id
1115
                view_id = this.identify_view(view_id);
1116

    
1117
                // add/create view and update current view
1118
                var view = this.add_view(view_id);
1119
                
1120
                // set current view
1121
                this.current_view = view;
1122
                this.current_view_id = view_id;
1123

    
1124
                // hide all other views
1125
                this.hide_views([this.current_view]);
1126
                
1127
                // FIXME: depricated
1128
                $(".large-spinner").remove();
1129

    
1130
                storage.vms.reset_pending_actions();
1131
                //storage.networks.reset_pending_actions();
1132
                storage.vms.stop_stats_update();
1133

    
1134
                // show current view
1135
                this.show_view_pane();
1136
                view.show(true);
1137
                
1138
                // update menus
1139
                if (this.select_view) {
1140
                    this.select_view.update_layout();
1141
                }
1142

    
1143
                var update_layout = this.current_view.__update_layout;
1144
                update_layout && update_layout.call(this.current_view);
1145

    
1146
                // update cookies
1147
                this.update_session();
1148
                
1149
                // machines view subnav
1150
                if (this.current_view.vms_view) {
1151
                    $("#machines-pane").show();
1152
                } else {
1153
                    $("#machines-pane").hide();
1154
                }
1155
                
1156
                // fix footer position
1157
                // TODO: move footer handlers in
1158
                // main view (from home.html)
1159
                if (window.positionFooter) {
1160
                    window.positionFooter();
1161
                }
1162

    
1163
                // trigger view change event
1164
                this.trigger("view:change", this.current_view.view_id);
1165
                this.select_view.title.text(this.get_title());
1166
                $(window).trigger("view:change");
1167
                return view;
1168
        },
1169

    
1170
        reset_vm_actions: function() {
1171
        
1172
        },
1173
        
1174
        // identify current view
1175
        // based on views element visibility
1176
        current_view_id: function() {
1177
            var found = false;
1178
            _.each(this.views, function(key, value) {
1179
                if (value.visible()) {
1180
                    found = value;
1181
                }
1182
            })
1183
            return found;
1184
        }
1185

    
1186
    });
1187

    
1188
    snf.ui.main = new views.MainView();
1189
    
1190
    snf.ui.init = function() {
1191
        if (snf.config.handle_window_exceptions) {
1192
            window.onerror = function(msg, file, line) {
1193
                snf.ui.trigger_error("CRITICAL", msg, {}, { file:file + ":" + line, allow_close: true });
1194
            };
1195
        }
1196
        snf.ui.main.load();
1197
    }
1198

    
1199
})(this);