Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / ui / web / ui_main_view.js @ 1e882dd7

History | View | Annotate | Download (34.5 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.ApiInfoView = views.Overlay.extend({
88
        view_id: "api_info_view",
89
        
90
        content_selector: "#api-info-overlay",
91
        css_class: 'overlay-api-info overlay-info',
92
        overlay_id: "overlay-api-info",
93

    
94
        subtitle: "",
95
        title: "API Access",
96

    
97
        beforeOpen: function() {
98
            var cont = this.$(".copy-content p");
99
            var token = snf.user.token;
100

    
101
            cont.html("");
102
            cont.text(token);
103
            
104
            this.cont = cont;
105
            this.token = token;
106
            try { delete this.clip; } catch (err) {};
107
        },
108

    
109
        onOpen: function() {
110
            views.ApiInfoView.__super__.onOpen(this, arguments);
111
            this.clip = new snf.util.ClipHelper(this.cont.parent(), this.token);
112
        },
113

    
114
        onClose: function() {
115
            var cont = this.$(".copy-content p");
116
            var token = snf.user.token;
117
            cont.html("");
118
        }
119
    });
120

    
121
    // TODO: implement me
122
    views.NoticeView = views.Overlay.extend({});
123

    
124
    views.MultipleActionsView = views.View.extend({
125
        view_id: "multiple_actions",
126

    
127
        _actions: {},
128
        el: '#multiple_actions_container',
129
        
130
        initialize: function() {
131
            this.actions = {};
132
            this.ns_config = {};
133

    
134
            views.MultipleActionsView.__super__.initialize.call(this);
135

    
136
            this.ns_tpl = this.$(".confirm_multiple_actions-template").clone()
137

    
138
            this.init_handlers();
139
            this.update_layout();
140
            
141
            // for heavy resize/scroll window events
142
            // do it `like a boss` 
143
            this.fix_position = _.throttle(this.fix_position, 100);
144
            this.update_layout = _.throttle(this.update_layout, 100);
145
            this.show_limit = 1;
146

    
147
            this.init_ns("vms", {
148
                msg_tpl:"Your actions will affect 1 machine",
149
                msg_tpl_plural:"Your actions will affect {0} machines",
150
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
151
                limit: 1,
152
                cancel_all: function() { snf.storage.vms.reset_pending_actions(); },
153
                do_all: function() { snf.storage.vms.do_all_pending_actions(); }
154
            });
155
            
156
            this.init_ns("nets", {
157
                msg_tpl:"Your actions will affect 1 private network",
158
                msg_tpl_plural:"Your actions will affect {0} private networks",
159
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
160
                limit: 1,
161
                cancel_all: function() { snf.storage.networks.reset_pending_actions(); },
162
                do_all: function() { snf.storage.networks.do_all_pending_actions(); }
163
            });
164

    
165
            this.init_ns("reboots", {
166
                msg_tpl:"1 machine needs to be rebooted for changes to apply.",
167
                msg_tpl_plural:"{0} machines needs to be rebooted for changes to apply.",
168
                actions_msg: {confirm: "Reboot all", cancel: "Cancel all"},
169
                limit: 0,
170
                cancel_all: function() { snf.storage.vms.reset_reboot_required(); },
171
                do_all: function() { snf.storage.vms.do_all_reboots(); }
172
            });
173
        },
174
        
175
        init_ns: function(ns, params) {
176
            this.actions[ns] = {};
177
            var nsconf = this.ns_config[ns] = params || {};
178
            nsconf.cont = $(this.$("#conirm_multiple_cont_template").clone());
179
            nsconf.cont.attr("id", "confirm_multiple_cont_" + ns);
180
            $(this.el).find(".ns-confirms-cont").append(nsconf.cont).addClass(ns);
181
            $(this.el).find(".ns-confirms-cont").append(nsconf.cont).addClass("confirm-ns");
182
            nsconf.cont.find(".msg button.yes").text(
183
                nsconf.actions_msg.confirm).click(_.bind(this.do_all, this, ns));
184
            nsconf.cont.find(".msg button.no").text(
185
                nsconf.actions_msg.cancel).click(_.bind(this.cancel_all, this, ns));
186
        },
187

    
188
        do_all: function(ns) {
189
            this.ns_config[ns].do_all();
190
        },
191

    
192
        cancel_all: function(ns) {
193
            this.ns_config[ns].cancel_all();
194
        },
195

    
196
        init_handlers: function() {
197
            var self = this;
198

    
199
            $(window).resize(_.bind(function(){
200
                this.fix_position();
201
            }, this));
202

    
203
            $(window).scroll(_.bind(function(){
204
                this.fix_position();
205
            }, this));
206

    
207
            storage.vms.bind("change:pending_action", _.bind(this.handle_action_add, this, "vms"));
208
            storage.vms.bind("change:reboot_required", _.bind(this.handle_action_add, this, "reboots"));
209
            storage.networks.bind("change:actions", _.bind(this.handle_action_add, this, "nets"));
210
        },
211

    
212
        handle_action_add: function(type, model, action) {
213
            var actions = this.actions[type];
214
            
215
            // TODO: remove type specific addition code in its own namespace
216
            if (type == "nets") {
217
                if (!action || action.is_empty()) {
218
                    delete actions[model.id];
219
                } else {
220
                    actions[model.id] = {model: model, actions: action.actions};
221
                }
222
            }
223

    
224
            if (type == "vms") {
225
                _.each(actions, function(action) {
226
                    if (action.model.id == model.id) {
227
                        delete actions[action]
228
                    }
229
                });
230

    
231
                var actobject = {};
232
                actobject[action] = [[]];
233
                actions[model.id] = {model: model, actions: actobject};
234
                if (typeof action == "undefined") {
235
                    delete actions[model.id]
236
                }
237
            }
238

    
239
            if (type == "reboots") {
240
                _.each(actions, function(action) {
241
                    if (action.model.id == model.id) {
242
                        delete actions[action]
243
                    }
244
                });
245
                var actobject = {};
246
                actobject['reboot'] = [[]];
247
                actions[model.id] = {model: model, actions: actobject};
248
                if (!action) {
249
                    delete actions[model.id]
250
                }
251
            }
252
            
253
            this.update_layout();
254
        },
255

    
256
        update_actions_content: function(ns) {
257
            var conf = this.ns_config[ns];
258
            conf.cont.find(".details").empty();
259
            conf.cont.find(".msg p").text("");
260
            
261
            var count = 0;
262
            var actionscount = 0;
263
            _.each(this.actions[ns], function(actions, model_id) {
264
                count++;
265
                _.each(actions.actions, function(params, act_name){
266
                    if (params && params.length) {
267
                        actionscount += params.length;
268
                    } else {
269
                        actionscount++;
270
                    }
271
                })
272
                this.total_confirm_actions++;
273
            });
274
            
275
            var limit = conf.limit;
276
            if (ui.main.current_view.view_id == "vm_list") {
277
                limit = 0;
278
            }
279

    
280
            if (actionscount > limit) {
281
                conf.cont.show();
282
                this.confirm_ns_open++;
283
            } else {
284
                conf.cont.hide();
285
            }
286
            
287
            var msg = count > 1 ? conf.msg_tpl_plural : conf.msg_tpl;
288
            conf.cont.find(".msg p").text(msg.format(count));
289

    
290
            return conf.cont;
291
        },
292

    
293
        fix_position: function() {
294
            $('.confirm_multiple').removeClass('fixed');
295
            if (($(this.el).offset().top +$(this.el).height())> ($(window).scrollTop() + $(window).height())) {
296
                $('.confirm_multiple').addClass('fixed');
297
            }
298
        },
299
        
300
        update_layout: function() {
301
            this.confirm_ns_open = 0;
302
            this.total_confirm_actions = 0;
303

    
304
            $(this.el).show();
305
            $(this.el).find("#conirm_multiple_cont_template").hide();
306
            $(this.el).find(".confirm-ns").show();
307
            
308
            _.each(this.ns_config, _.bind(function(params, key) {
309
                this.update_actions_content(key);
310
            }, this));
311

    
312
            if (this.confirm_ns_open > 0) {
313
                $(this.el).show();
314
                this.$(".confirm-all-cont").hide();
315
                this.$(".ns-confirms-cont").show();
316
            } else {
317
                $(this.el).hide();
318
                this.$(".confirm-all-cont").hide();
319
                this.$(".ns-confirms-cont").hide();
320
            }
321

    
322
            $(window).trigger("resize");
323
        }
324
    })
325
    
326
    // menu wrapper view
327
    views.SelectView = views.View.extend({
328
        
329
        initialize: function(view, router) {
330
            this.parent = view;
331
            this.router = router;
332
            this.pane_view_selector = $(".css-tabs");
333
            this.machine_view_selector = $("#view-select");
334
            this.el = $(".css-tabs");
335
            this.title = $(".tab-name");
336

    
337
            this.set_handlers();
338
            this.update_layout();
339

    
340
            views.SelectView.__super__.initialize.apply(this, arguments);
341
        },
342
        
343
        clear_active: function() {
344
            this.pane_view_selector.find("a").removeClass("active");
345
            this.machine_view_selector.find("a").removeClass("activelink");
346
        },
347
        
348
        // intercept menu links
349
        set_handlers: function() {
350
            var self = this;
351
            this.pane_view_selector.find("a").hover(function(){
352
                // FIXME: title from href ? omg
353
                self.title.text($(this).attr("href"));
354
            }, function(){
355
                self.title.text(self.parent.get_title());
356
            });
357

    
358
            this.pane_view_selector.find("a#machines_view_link").click(_.bind(function(ev){
359
                ev.preventDefault();
360
                this.router.vms_index();
361
            }, this))
362
            this.pane_view_selector.find("a#networks_view_link").click(_.bind(function(ev){
363
                ev.preventDefault();
364
                this.router.networks_view();
365
            }, this))
366
            this.pane_view_selector.find("a#disks_view_link").click(_.bind(function(ev){
367
                ev.preventDefault();
368
                this.router.disks_view();
369
            }, this))
370
            
371
            this.machine_view_selector.find("a#machines_view_icon_link").click(_.bind(function(ev){
372
                ev.preventDefault();
373
                var d = $.now();
374
                this.router.vms_icon_view();
375
            }, this))
376
            this.machine_view_selector.find("a#machines_view_list_link").click(_.bind(function(ev){
377
                ev.preventDefault();
378
                this.router.vms_list_view();
379
            }, this))
380
            this.machine_view_selector.find("a#machines_view_single_link").click(_.bind(function(ev){
381
                ev.preventDefault();
382
                this.router.vms_single_view();
383
            }, this))
384
        },
385

    
386
        update_layout: function() {
387
            this.clear_active();
388

    
389
            var pane_index = this.parent.pane_ids[this.parent.current_view_id];
390
            $(this.pane_view_selector.find("a")).removeClass("active");
391
            $(this.pane_view_selector.find("a").get(pane_index)).addClass("active");
392
            
393
            if (this.parent.current_view && this.parent.current_view.vms_view) {
394

    
395
                if (storage.vms.length > 0) {
396
                    this.machine_view_selector.show();
397
                    var machine_index = this.parent.views_ids[this.parent.current_view_id];
398
                    $(this.machine_view_selector.find("a").get(machine_index)).addClass("activelink");
399
                } else {
400
                    this.machine_view_selector.hide();
401
                }
402
            } else {
403
                this.machine_view_selector.hide();
404
            }
405

    
406
        }
407
    });
408

    
409
    views.MainView = views.View.extend({
410
        el: 'body',
411
        view_id: 'main',
412
        
413
        // FIXME: titles belong to SelectView
414
        views_titles: {
415
            'icon': 'machines', 'single': 'machines', 
416
            'list': 'machines', 'networks': 'networks',
417
            'disks': 'disks'
418
        },
419

    
420
        // indexes registry
421
        views_indexes: {0: 'icon', 2:'single', 1: 'list', 3:'networks'},
422
        views_pane_indexes: {0:'single', 1:'networks', 2:'disks'},
423

    
424
        // views classes registry
425
        views_classes: {'icon': views.IconView, 'single': views.SingleView, 
426
            'list': views.ListView, 'networks': views.NetworksView},
427

    
428
        // view ids
429
        views_ids: {'icon':0, 'single':2, 'list':1, 'networks':3},
430

    
431
        // on which pane id each view exists
432
        // machine views (icon,single,list) are all on first pane
433
        pane_ids: {'icon':0, 'single':0, 'list':0, 'networks':1, 'disks':2},
434
    
435
        initialize: function(show_view) {
436
            if (!show_view) { show_view = 'icon' };
437
            
438
            this.router = snf.router;
439
            this.empty_hidden = true;
440
            // fallback to browser error reporting (true for debug)
441
            this.skip_errors = true
442

    
443
            // reset views
444
            this.views = {};
445

    
446
            this.el = $("#app");
447
            // reset main view status
448
            this._loaded = false;
449
            this.status = "Initializing...";
450

    
451
            // initialize handlers
452
            this.init_handlers();
453

    
454
            // identify initial view from user cookies
455
            // this view will be visible after loading of
456
            // main view
457
            this.initial_view = this.session_view();
458

    
459
            views.MainView.__super__.initialize.call(this);
460

    
461
            $(window).focus(_.bind(this.handle_window_focus, this, "focus"));
462
            $(window).blur(_.bind(this.handle_window_focus, this, "out"));
463

    
464
            this.focused = true;
465
        },
466

    
467
        handle_window_focus: function(focus) {
468
            if (!snf.config.delay_on_blur) { return };
469

    
470
            if (focus === "focus") {
471
                this.focused = true;
472
                this.set_interval_timeouts();
473
            } else {
474
                this.focused = false;
475
                this.set_interval_timeouts();
476
            }
477
        },
478

    
479
        set_interval_timeouts: function(time) {
480
            _.each([this._networks, this._vms], _.bind(function(fetcher){
481
                if (!fetcher) { return };
482
                if (this.focused) {
483
                    fetcher.interval = fetcher.normal_interval;
484
                    fetcher.stop(false).start(true);
485
                } else {
486
                    fetcher.interval = fetcher.maximum_interval;
487
                    fetcher.stop(false).start(false);
488
                }
489

    
490
            }, this));
491
        },
492
        
493
        vms_handlers_registered: false,
494

    
495
        // register event handlers
496
        // 
497
        // vms storage events to identify if vms list 
498
        // is empty and display empty view if user viewing
499
        // a machine view
500
        //
501
        // api/ui error event handlers
502
        init_handlers: function() {
503
            // vm handlers
504
            storage.vms.bind("remove", _.bind(this.check_empty, this));
505
            storage.vms.bind("add", _.bind(this.check_empty, this));
506
            storage.vms.bind("change:status", _.bind(this.check_empty, this));
507
            storage.vms.bind("reset", _.bind(this.check_empty, this));
508
            
509
        },
510
        
511
        handle_api_error_state: function(state) {
512
            if (snf.api.error_state === snf.api.STATES.ERROR) {
513
                this.stop_intervals();
514
            } else {
515
                if (this.intervals_stopped) {
516
                    this.update_intervals();
517
                }
518
            }
519
        },
520
        
521
        handle_api_error: function(args) {
522
            if (arguments.length == 1) { arguments = _.toArray(arguments[0])};
523

    
524
            if (!_.last(arguments).display) {
525
                return;
526
            }
527

    
528
            this.error_state = true;
529
            
530
            var xhr = arguments[0];
531
            var args = util.parse_api_error.apply(util, arguments);
532
            
533
            // force logout if UNAUTHORIZED request arrives
534
            if (args.code == 401) { snf.auth_client.redirect_to_login(); return };
535

    
536
            var error_entry = [args.ns, args.code, args.message, args.type, args.details, args];
537
            this.error_view.show_error.apply(this.error_view, error_entry);
538
        },
539

    
540
        handle_ui_error: function(data) {
541
            var msg = data.msg, code = data.code, err_obj = data.error;
542
            error = msg + "<br /><br />" + snf.util.stacktrace().replace("at", "<br /><br />at");
543
            params = { title: "UI error", extra_details: data.extra };
544
            params.allow_close = data.extra.allow_close === undefined ? true : data.extra.allow_close;
545
            this.error_view.show_error("UI", -1, msg, "JS Exception", error, params);
546
        },
547

    
548
        init_overlays: function() {
549
            this.create_vm_view = new views.CreateVMView();
550
            this.api_info_view = new views.ApiInfoView();
551
            this.details_view = new views.DetailsView();
552
            //this.notice_view = new views.NoticeView();
553
        },
554
        
555
        show_loading_view: function() {
556
            $("#container #content").hide();
557
            $("#loading-view").show();
558
        },
559

    
560
        hide_loading_view: function() {
561
            $("#container #content").show();
562
            $("#loading-view").hide();
563
            $(".css-panes").show();
564
        },
565
        
566
        items_to_load: 4,
567
        completed_items: 0,
568
        check_status: function(loaded) {
569
            this.completed_items++;
570
            // images, flavors loaded
571
            if (this.completed_items == 2) {
572
                this.load_nets_and_vms();
573
            }
574

    
575
            if (this.completed_items == this.items_to_load) {
576
                this.update_status("Rendering layout...");
577
                var self = this;
578
                window.setTimeout(function(){
579
                    self.after_load();
580
                }, 10)
581
            }
582
        },
583

    
584
        load_nets_and_vms: function() {
585
            var self = this;
586
            this.update_status("Loading vms...");
587
            storage.vms.fetch({refresh:true, update:false, success: function(){
588
                self.update_status("VMS Loaded.");
589
                self.check_status();
590
            }});
591

    
592
            this.update_status("Loading networks...");
593
            storage.networks.fetch({refresh:true, update:false, success: function(){
594
                self.update_status("Networks loaded.");
595
                self.check_status();
596
            }});
597
        },  
598

    
599
        init_intervals: function() {
600
            var fetcher_params = [snf.config.update_interval, 
601
                                  snf.config.update_interval_increase || 500,
602
                                  snf.config.fast_interval || snf.config.update_interval/2, 
603
                                  snf.config.update_interval_increase_after_calls || 4,
604
                                  snf.config.update_interval_max || 20000,
605
                                  true, 
606
                                  {is_recurrent: true}]
607
            
608
            this._networks = storage.networks.get_fetcher.apply(storage.networks, _.clone(fetcher_params));
609
            this._vms = storage.vms.get_fetcher.apply(storage.vms, _.clone(fetcher_params));
610
        },
611

    
612
        stop_intervals: function() {
613
            if (this._networks) { this._networks.stop(); }
614
            if (this._vms) { this._vms.stop(); }
615
            this.intervals_stopped = true;
616
        },
617

    
618
        update_intervals: function() {
619
            if (this._networks) {
620
                this._networks.stop();
621
                this._networks.start();
622
            } else {
623
                this.init_intervals();
624
            }
625

    
626
            if (this._vms) {
627
                this._vms.stop();
628
                this._vms.start();
629
            } else {
630
                this.init_intervals();
631
            }
632

    
633
            this.intervals_stopped = false;
634
        },
635

    
636
        after_load: function() {
637
            var self = this;
638
            this.update_status("Setting vms update interval...");
639
            this.init_intervals();
640
            this.update_intervals();
641
            this.update_status("Showing initial view...");
642
            
643
            // bypass update_hidden_views in initial view
644
            // rendering to force all views to get render
645
            // on their creation
646
            var uhv = snf.config.update_hidden_views;
647
            snf.config.update_hidden_views = true;
648
            this.initialize_views();
649
            snf.config.update_hidden_views = uhv;
650

    
651
            window.setTimeout(function() {
652
                self.update_status("Initializing overlays...");
653
                self.load_initialize_overlays();
654
            }, 20);
655
        },
656

    
657
        load_initialize_overlays: function() {
658
            this.init_overlays();
659
            // display initial view
660
            this.loaded = true;
661
            
662
            // application start point
663

    
664
            this.check_empty();
665
            this.show_initial_view();
666
        },
667

    
668
        load: function() {
669
            if (synnefo.config.use_glance) {
670
                synnefo.glance.register();
671
            }
672
            this.error_view = new views.ErrorView();
673
            // api request error handling
674
            synnefo.api.bind("error", _.bind(this.handle_api_error, this));
675
            synnefo.api.bind("change:error_state", _.bind(this.handle_api_error_state, this));
676
            synnefo.ui.bind("error", _.bind(this.handle_ui_error, this));
677

    
678
            this.feedback_view = new views.FeedbackView();
679
            this.public_keys_view = new views.PublicKeysOverlay();
680
            
681
            if (synnefo.config.use_glance) {
682
                this.custom_images_view = new views.CustomImagesOverlay();
683
            }
684

    
685
            var self = this;
686
            // initialize overlay views
687
            
688
            // display loading message
689
            this.show_loading_view();
690
            // sync load initial data
691
            this.update_status("Loading images...");
692
            storage.images.fetch({refresh:true, update:false, success: function(){
693
                self.check_status()
694
            }});
695
            this.update_status("Loading flavors...");
696
            storage.flavors.fetch({refresh:true, update:false, success:function(){
697
                self.check_status()
698
            }});
699
        },
700

    
701
        update_status: function(msg) {
702
            //this.log.debug(msg)
703
            this.status = msg;
704
            $("#loading-view .info").removeClass("hidden")
705
            $("#loading-view .info").text(this.status);
706
        },
707

    
708
        initialize_views: function() {
709
            this.select_view = new views.SelectView(this, this.router);
710
            this.empty_view = new views.EmptyView();
711
            this.metadata_view = new views.MetadataView();
712
            this.multiple_actions_view = new views.MultipleActionsView();
713
            
714
            this.add_view("icon");
715
            this.add_view("list");
716
            this.add_view("single");
717
            this.add_view("networks");
718

    
719
            this.init_menu();
720
        },
721

    
722
        init_menu: function() {
723
            $(".usermenu .feedback").click(_.bind(function(e){
724
                e.preventDefault();
725
                this.feedback_view.show();
726
            }, this));
727
            $(".usermenu .public_keys").click(_.bind(function(e){
728
                e.preventDefault();
729
                this.public_keys_view.show();
730
            }, this));
731

    
732
            if (snf.glance) {
733
                $(".usermenu .custom_images").click(_.bind(function(e){
734
                    e.preventDefault();
735
                    this.custom_images_view.show();
736
                }, this));
737
            } else {
738
                $(".usermenu .custom_images").hide();
739
            }
740
        },
741
        
742
        // initial view based on user cookie
743
        show_initial_view: function() {
744
          this.set_vm_view_handlers();
745
          this.hide_loading_view();
746
          
747
          bb.history.start();
748

    
749
          this.trigger("ready");
750
        },
751

    
752
        show_vm_details: function(vm) {
753
            this.router.vm_details_view(vm.id);
754
        },
755

    
756
        set_vm_view_handlers: function() {
757
            var self = this;
758
            $("#createcontainer #create").click(function(e){
759
                e.preventDefault();
760
                self.router.vm_create_view();
761
            })
762
        },
763

    
764
        check_empty: function() {
765
            if (!this.loaded) { return }
766
            if (storage.vms.length == 0) {
767
                this.show_view("machines");
768
                this.router.show_welcome();
769
                this.empty_hidden = false;
770
            } else {
771
                this.hide_empty();
772
            }
773
        },
774

    
775
        show_empty: function() {
776
            if (!this.empty_hidden) { return };
777
            $("#machines-pane-top").addClass("empty");
778

    
779
            this.$(".panes").hide();
780
            this.$("#machines-pane").show();
781

    
782
            this.hide_views([]);
783
            this.empty_hidden = false;
784
            this.empty_view.show();
785
            this.select_view.update_layout();
786
            this.empty_hidden = false;
787
        },
788

    
789
        hide_empty: function() {
790
            if (this.empty_hidden) { return };
791
            $("#machines-pane-top").removeClass("empty");
792

    
793
            this.empty_view.hide(true);
794
            this.router.vms_index();
795
            this.empty_hidden = true;
796
            this.select_view.update_layout();
797
        },
798
        
799
        get_title: function(view_id) {
800
            var view_id = view_id || this.current_view_id;
801
            return this.views_titles[view_id];
802
        },
803

    
804
        // return class object for the given view or false if
805
        // the view is not registered
806
        get_class_for_view: function (view_id) {
807
            if (!this.views_classes[view_id]) {
808
                return false;
809
            }
810
            return this.views_classes[view_id];
811
        },
812

    
813
        view: function(view_id) {
814
            return this.views[view_id];
815
        },
816

    
817
        add_view: function(view_id) {
818
            if (!this.views[view_id]) {
819
                var cls = this.get_class_for_view(view_id);
820
                if (this.skip_errors) {
821
                    this.views[view_id] = new cls();
822
                    $(this.views[view_id]).bind("resize", _.bind(function() {
823
                        window.positionFooter();
824
                        this.multiple_actions_view.fix_position();
825
                    }, this));
826
                } else {
827
                    // catch ui errors
828
                    try {
829
                        this.views[view_id] = new cls();
830
                        $(this.views[view_id]).bind("resize", _.bind(function() {
831
                            window.positionFooter();
832
                            this.multiple_actions_view.fix_position();
833
                        }, this));
834
                    } catch (err) {snf.ui.trigger_error(-1, "Cannot add view", err)}
835
                }
836
            } else {
837
            }
838

    
839
            if (this.views[view_id].vms_view) {
840
                this.views[view_id].metadata_view = this.metadata_view;
841
            }
842
            return this.views[view_id];
843
        },
844
            
845
        hide_views: function(skip) {
846
            _.each(this.views, function(view) {
847
                if (skip.indexOf(view) === -1) {
848
                    $(view.el).hide();
849
                }
850
            }, this)
851
        },
852
        
853
        get: function(view_id) {
854
            return this.views[view_id];
855
        },
856
        
857
        session_view: function() {
858
            if (this.pane_view_from_session() > 0) {
859
                return this.views_pane_indexes[this.pane_view_from_session()];
860
            } else {
861
                return this.views_indexes[this.machine_view_from_session()];
862
            }
863
        },
864

    
865
        pane_view_from_session: function() {
866
            return $.cookie("pane_view") || 0;
867
        },
868

    
869
        machine_view_from_session: function() {
870
            return $.cookie("machine_view") || 0;
871
        },
872

    
873
        update_session: function() {
874
            $.cookie("pane_view", this.pane_ids[this.current_view_id]);
875
            if (this.current_view.vms_view) {
876
                $.cookie("machine_view", this.views_ids[this.current_view_id]);
877
            }
878
        },
879

    
880
        identify_view: function(view_id) {
881
            // machines view_id is an alias to
882
            // one of the 3 machines view
883
            // identify which one (if no cookie set defaults to icon)
884
            if (view_id == "machines") {
885
                var index = this.machine_view_from_session();
886
                view_id = this.views_indexes[index];
887
            }
888
            return view_id;
889
        },
890
        
891
        // switch to current view pane
892
        // if differs from the visible one
893
        show_view_pane: function() {
894
            if (this.current_view.pane != this.current_pane) {
895
                $(this.current_view.pane).show();
896
                $(this.current_pane).hide();
897
                this.current_pane = this.current_view.pane;
898
            }
899
        },
900
        
901
        show_view: function(view_id) {
902
            //var d = new Date;
903
            var ret = this._show_view(view_id);
904
            //console.log((new Date)-d)
905
            return ret;
906
        },
907

    
908
        _show_view: function(view_id) {
909
                // same view, visible
910
                // get out of here asap
911
                if (this.current_view && 
912
                    this.current_view.id == view_id && 
913
                    this.current_view.visible()) {
914
                    return;
915
                }
916
                
917
                // choose proper view_id
918
                view_id = this.identify_view(view_id);
919

    
920
                // add/create view and update current view
921
                var view = this.add_view(view_id);
922
                
923
                // set current view
924
                this.current_view = view;
925
                this.current_view_id = view_id;
926

    
927
                // hide all other views
928
                this.hide_views([this.current_view]);
929
                
930
                // FIXME: depricated
931
                $(".large-spinner").remove();
932

    
933
                storage.vms.reset_pending_actions();
934
                storage.networks.reset_pending_actions();
935
                storage.vms.stop_stats_update();
936

    
937
                // show current view
938
                this.show_view_pane();
939
                view.show();
940
                
941
                // update menus
942
                if (this.select_view) {
943
                    this.select_view.update_layout();
944
                }
945
                this.current_view.__update_layout();
946

    
947
                // update cookies
948
                this.update_session();
949
                
950
                // machines view subnav
951
                if (this.current_view.vms_view) {
952
                    $("#machines-pane").show();
953
                } else {
954
                    $("#machines-pane").hide();
955
                }
956
                
957
                // fix footer position
958
                // TODO: move footer handlers in
959
                // main view (from home.html)
960
                if (window.positionFooter) {
961
                    window.positionFooter();
962
                }
963

    
964
                // trigger view change event
965
                this.trigger("view:change", this.current_view.view_id);
966
                this.select_view.title.text(this.get_title());
967
                $(window).trigger("view:change");
968
                return view;
969
        },
970

    
971
        reset_vm_actions: function() {
972
        
973
        },
974
        
975
        // identify current view
976
        // based on views element visibility
977
        current_view_id: function() {
978
            var found = false;
979
            _.each(this.views, function(key, value) {
980
                if (value.visible()) {
981
                    found = value;
982
                }
983
            })
984
            return found;
985
        }
986

    
987
    });
988

    
989
    snf.ui.main = new views.MainView();
990
    
991
    snf.ui.init = function() {
992
        if (snf.config.handle_window_exceptions) {
993
            window.onerror = function(msg, file, line) {
994
                snf.ui.trigger_error("CRITICAL", msg, {}, { file:file + ":" + line, allow_close: false });
995
            };
996
        }
997
        snf.ui.main.load();
998
    }
999

    
1000
})(this);