Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (37.6 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
            data += "\nPublic IP: " + vm.get_public_nic().get('ipv4');
108
            data += "\n\n";
109
            views.SuspendedVMView.__super__.show.call(this, data, collect_data, extra_data, cb);
110
        }
111

    
112
    });
113

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

    
121
        subtitle: "",
122
        title: "API Access",
123

    
124
        beforeOpen: function() {
125
            var cont = this.$(".copy-content p");
126
            var token = snf.user.token;
127

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

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

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

    
148
    // TODO: implement me
149
    views.NoticeView = views.Overlay.extend({});
150

    
151
    views.MultipleActionsView = views.View.extend({
152
        view_id: "multiple_actions",
153

    
154
        _actions: {},
155
        el: '#multiple_actions_container',
156
        
157
        initialize: function() {
158
            this.actions = {};
159
            this.ns_config = {};
160

    
161
            views.MultipleActionsView.__super__.initialize.call(this);
162

    
163
            this.ns_tpl = this.$(".confirm_multiple_actions-template").clone()
164

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

    
174
            this.init_ns("vms", {
175
                msg_tpl:"Your actions will affect 1 machine",
176
                msg_tpl_plural:"Your actions will affect {0} machines",
177
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
178
                limit: 1,
179
                cancel_all: function() { snf.storage.vms.reset_pending_actions(); },
180
                do_all: function() { snf.storage.vms.do_all_pending_actions(); }
181
            });
182
            
183
            this.init_ns("nets", {
184
                msg_tpl:"Your actions will affect 1 private network",
185
                msg_tpl_plural:"Your actions will affect {0} private networks",
186
                actions_msg: {confirm: "Confirm all", cancel: "Cancel all"},
187
                limit: 1,
188
                cancel_all: function() { snf.storage.networks.reset_pending_actions(); },
189
                do_all: function() { snf.storage.networks.do_all_pending_actions(); }
190
            });
191

    
192
            this.init_ns("reboots", {
193
                msg_tpl:"1 machine needs to be rebooted for changes to apply.",
194
                msg_tpl_plural:"{0} machines needs to be rebooted for changes to apply.",
195
                actions_msg: {confirm: "Reboot all", cancel: "Cancel all"},
196
                limit: 0,
197
                cancel_all: function() { snf.storage.vms.reset_reboot_required(); },
198
                do_all: function() { snf.storage.vms.do_all_reboots(); }
199
            });
200
        },
201
        
202
        init_ns: function(ns, params) {
203
            this.actions[ns] = {};
204
            var nsconf = this.ns_config[ns] = params || {};
205
            nsconf.cont = $(this.$("#conirm_multiple_cont_template").clone());
206
            nsconf.cont.attr("id", "confirm_multiple_cont_" + ns);
207
            $(this.el).find(".ns-confirms-cont").append(nsconf.cont).addClass(ns);
208
            $(this.el).find(".ns-confirms-cont").append(nsconf.cont).addClass("confirm-ns");
209
            nsconf.cont.find(".msg button.yes").text(
210
                nsconf.actions_msg.confirm).click(_.bind(this.do_all, this, ns));
211
            nsconf.cont.find(".msg button.no").text(
212
                nsconf.actions_msg.cancel).click(_.bind(this.cancel_all, this, ns));
213
        },
214

    
215
        do_all: function(ns) {
216
            this.ns_config[ns].do_all();
217
        },
218

    
219
        cancel_all: function(ns) {
220
            this.ns_config[ns].cancel_all();
221
        },
222

    
223
        init_handlers: function() {
224
            var self = this;
225

    
226
            $(window).resize(_.bind(function(){
227
                this.fix_position();
228
            }, this));
229

    
230
            $(window).scroll(_.bind(function(){
231
                this.fix_position();
232
            }, this));
233

    
234
            storage.vms.bind("change:pending_action", _.bind(this.handle_action_add, this, "vms"));
235
            storage.vms.bind("change:reboot_required", _.bind(this.handle_action_add, this, "reboots"));
236
            storage.networks.bind("change:actions", _.bind(this.handle_action_add, this, "nets"));
237
        },
238

    
239
        handle_action_add: function(type, model, action) {
240
            var actions = this.actions[type];
241
            
242
            // TODO: remove type specific addition code in its own namespace
243
            if (type == "nets") {
244
                if (!action || action.is_empty()) {
245
                    delete actions[model.id];
246
                } else {
247
                    actions[model.id] = {model: model, actions: action.actions};
248
                }
249
            }
250

    
251
            if (type == "vms") {
252
                _.each(actions, function(action) {
253
                    if (action.model.id == model.id) {
254
                        delete actions[action]
255
                    }
256
                });
257

    
258
                var actobject = {};
259
                actobject[action] = [[]];
260
                actions[model.id] = {model: model, actions: actobject};
261
                if (typeof action == "undefined") {
262
                    delete actions[model.id]
263
                }
264
            }
265

    
266
            if (type == "reboots") {
267
                _.each(actions, function(action) {
268
                    if (action.model.id == model.id) {
269
                        delete actions[action]
270
                    }
271
                });
272
                var actobject = {};
273
                actobject['reboot'] = [[]];
274
                actions[model.id] = {model: model, actions: actobject};
275
                if (!action) {
276
                    delete actions[model.id]
277
                }
278
            }
279
            
280
            this.update_layout();
281
        },
282

    
283
        update_actions_content: function(ns) {
284
            var conf = this.ns_config[ns];
285
            conf.cont.find(".details").empty();
286
            conf.cont.find(".msg p").text("");
287
            
288
            var count = 0;
289
            var actionscount = 0;
290
            _.each(this.actions[ns], function(actions, model_id) {
291
                count++;
292
                _.each(actions.actions, function(params, act_name){
293
                    if (params && params.length) {
294
                        actionscount += params.length;
295
                    } else {
296
                        actionscount++;
297
                    }
298
                })
299
                this.total_confirm_actions++;
300
            });
301
            
302
            var limit = conf.limit;
303
            if (ui.main.current_view.view_id == "vm_list") {
304
                limit = 0;
305
            }
306

    
307
            if (actionscount > limit) {
308
                conf.cont.show();
309
                this.confirm_ns_open++;
310
            } else {
311
                conf.cont.hide();
312
            }
313
            
314
            var msg = count > 1 ? conf.msg_tpl_plural : conf.msg_tpl;
315
            conf.cont.find(".msg p").text(msg.format(count));
316

    
317
            return conf.cont;
318
        },
319

    
320
        fix_position: function() {
321
            $('.confirm_multiple').removeClass('fixed');
322
            if (($(this.el).offset().top +$(this.el).height())> ($(window).scrollTop() + $(window).height())) {
323
                $('.confirm_multiple').addClass('fixed');
324
            }
325
        },
326
        
327
        update_layout: function() {
328
            this.confirm_ns_open = 0;
329
            this.total_confirm_actions = 0;
330

    
331
            $(this.el).show();
332
            $(this.el).find("#conirm_multiple_cont_template").hide();
333
            $(this.el).find(".confirm-ns").show();
334
            
335
            _.each(this.ns_config, _.bind(function(params, key) {
336
                this.update_actions_content(key);
337
            }, this));
338

    
339
            if (this.confirm_ns_open > 0) {
340
                $(this.el).show();
341
                this.$(".confirm-all-cont").hide();
342
                this.$(".ns-confirms-cont").show();
343
            } else {
344
                $(this.el).hide();
345
                this.$(".confirm-all-cont").hide();
346
                this.$(".ns-confirms-cont").hide();
347
            }
348

    
349
            $(window).trigger("resize");
350
        }
351
    })
352
    
353
    // menu wrapper view
354
    views.SelectView = views.View.extend({
355
        
356
        initialize: function(view, router) {
357
            this.parent = view;
358
            this.router = router;
359
            this.pane_view_selector = $(".css-tabs");
360
            this.machine_view_selector = $("#view-select");
361
            this.el = $(".css-tabs");
362
            this.title = $(".tab-name");
363

    
364
            this.set_handlers();
365
            this.update_layout();
366

    
367
            views.SelectView.__super__.initialize.apply(this, arguments);
368
        },
369
        
370
        clear_active: function() {
371
            this.pane_view_selector.find("a").removeClass("active");
372
            this.machine_view_selector.find("a").removeClass("activelink");
373
        },
374
        
375
        // intercept menu links
376
        set_handlers: function() {
377
            var self = this;
378
            this.pane_view_selector.find("a").hover(function(){
379
                // FIXME: title from href ? omg
380
                self.title.text($(this).attr("href"));
381
            }, function(){
382
                self.title.text(self.parent.get_title());
383
            });
384

    
385
            this.pane_view_selector.find("a#machines_view_link").click(_.bind(function(ev){
386
                ev.preventDefault();
387
                this.router.vms_index();
388
            }, this))
389
            this.pane_view_selector.find("a#networks_view_link").click(_.bind(function(ev){
390
                ev.preventDefault();
391
                this.router.networks_view();
392
            }, this))
393
            this.pane_view_selector.find("a#disks_view_link").click(_.bind(function(ev){
394
                ev.preventDefault();
395
                this.router.disks_view();
396
            }, this))
397
            
398
            this.machine_view_selector.find("a#machines_view_icon_link").click(_.bind(function(ev){
399
                ev.preventDefault();
400
                var d = $.now();
401
                this.router.vms_icon_view();
402
            }, this))
403
            this.machine_view_selector.find("a#machines_view_list_link").click(_.bind(function(ev){
404
                ev.preventDefault();
405
                this.router.vms_list_view();
406
            }, this))
407
            this.machine_view_selector.find("a#machines_view_single_link").click(_.bind(function(ev){
408
                ev.preventDefault();
409
                this.router.vms_single_view();
410
            }, this))
411
        },
412

    
413
        update_layout: function() {
414
            this.clear_active();
415

    
416
            var pane_index = this.parent.pane_ids[this.parent.current_view_id];
417
            $(this.pane_view_selector.find("a")).removeClass("active");
418
            $(this.pane_view_selector.find("a").get(pane_index)).addClass("active");
419
            
420
            if (this.parent.current_view && this.parent.current_view.vms_view) {
421

    
422
                if (storage.vms.length > 0) {
423
                    this.machine_view_selector.show();
424
                    var machine_index = this.parent.views_ids[this.parent.current_view_id];
425
                    $(this.machine_view_selector.find("a").get(machine_index)).addClass("activelink");
426
                } else {
427
                    this.machine_view_selector.hide();
428
                }
429
            } else {
430
                this.machine_view_selector.hide();
431
            }
432

    
433
        }
434
    });
435

    
436
    views.MainView = views.View.extend({
437
        el: 'body',
438
        view_id: 'main',
439
        
440
        // FIXME: titles belong to SelectView
441
        views_titles: {
442
            'icon': 'machines', 'single': 'machines', 
443
            'list': 'machines', 'networks': 'networks',
444
            'disks': 'disks'
445
        },
446

    
447
        // indexes registry
448
        views_indexes: {0: 'icon', 2:'single', 1: 'list', 3:'networks'},
449
        views_pane_indexes: {0:'single', 1:'networks', 2:'disks'},
450

    
451
        // views classes registry
452
        views_classes: {'icon': views.IconView, 'single': views.SingleView, 
453
            'list': views.ListView, 'networks': views.NetworksView, 'disks': views.DisksView},
454

    
455
        // view ids
456
        views_ids: {'icon':0, 'single':2, 'list':1, 'networks':3, 'disks':4},
457

    
458
        // on which pane id each view exists
459
        // machine views (icon,single,list) are all on first pane
460
        pane_ids: {'icon':0, 'single':0, 'list':0, 'networks':1, 'disks':2},
461
    
462
        initialize: function(show_view) {
463
            if (!show_view) { show_view = 'icon' };
464
            
465
            this.router = snf.router;
466
            this.empty_hidden = true;
467
            // fallback to browser error reporting (true for debug)
468
            this.skip_errors = true
469

    
470
            // reset views
471
            this.views = {};
472

    
473
            this.el = $("#app");
474
            // reset main view status
475
            this._loaded = false;
476
            this.status = "Initializing...";
477

    
478
            // initialize handlers
479
            this.init_handlers();
480

    
481
            // identify initial view from user cookies
482
            // this view will be visible after loading of
483
            // main view
484
            this.initial_view = this.session_view();
485

    
486
            views.MainView.__super__.initialize.call(this);
487

    
488
            $(window).focus(_.bind(this.handle_window_focus, this, "focus"));
489
            $(window).blur(_.bind(this.handle_window_focus, this, "out"));
490

    
491
            this.focused = true;
492
        },
493

    
494
        handle_window_focus: function(focus) {
495
            if (!snf.config.delay_on_blur) { return };
496

    
497
            if (focus === "focus") {
498
                this.focused = true;
499
                this.set_interval_timeouts();
500
            } else {
501
                this.focused = false;
502
                this.set_interval_timeouts();
503
            }
504
        },
505

    
506
        set_interval_timeouts: function(time) {
507
            _.each([this._networks, this._vms], _.bind(function(fetcher){
508
                if (!fetcher) { return };
509
                if (this.focused) {
510
                    fetcher.interval = fetcher.normal_interval;
511
                    fetcher.stop(false).start(true);
512
                } else {
513
                    fetcher.interval = fetcher.maximum_interval;
514
                    fetcher.stop(false).start(false);
515
                }
516

    
517
            }, this));
518
        },
519
        
520
        vms_handlers_registered: false,
521

    
522
        // register event handlers
523
        // 
524
        // vms storage events to identify if vms list 
525
        // is empty and display empty view if user viewing
526
        // a machine view
527
        //
528
        // api/ui error event handlers
529
        init_handlers: function() {
530
            // vm handlers
531
            storage.vms.bind("remove", _.bind(this.check_empty, this));
532
            storage.vms.bind("add", _.bind(this.check_empty, this));
533
            storage.vms.bind("change:status", _.bind(this.check_empty, this));
534
            storage.vms.bind("reset", _.bind(this.check_empty, this));
535
            
536
        },
537
        
538
        handle_api_error_state: function(state) {
539
            if (snf.api.error_state === snf.api.STATES.ERROR) {
540
                this.stop_intervals();
541
            } else {
542
                if (this.intervals_stopped) {
543
                    this.update_intervals();
544
                }
545
            }
546
        },
547
        
548
        handle_api_error: function(args) {
549
            if (arguments.length == 1) { arguments = _.toArray(arguments[0])};
550

    
551
            if (!_.last(arguments).display) {
552
                return;
553
            }
554

    
555
            this.error_state = true;
556
            
557
            var xhr = arguments[0];
558
            var args = util.parse_api_error.apply(util, arguments);
559
            
560
            // force logout if UNAUTHORIZED request arrives
561
            if (args.code == 401) { snf.auth_client.redirect_to_login(); return };
562

    
563
            var error_entry = [args.ns, args.code, args.message, args.type, args.details, args];
564
            this.error_view.show_error.apply(this.error_view, error_entry);
565
        },
566

    
567
        handle_ui_error: function(data) {
568
            var msg = data.msg, code = data.code, err_obj = data.error;
569
            error = msg + "<br /><br />" + snf.util.stacktrace().replace("at", "<br /><br />at");
570
            params = { title: "UI error", extra_details: data.extra };
571
            params.allow_close = data.extra.allow_close === undefined ? true : data.extra.allow_close;
572
            this.error_view.show_error("UI", -1, msg, "JS Exception", error, params);
573
        },
574

    
575
        init_overlays: function() {
576
            this.create_vm_view = new views.CreateVMView();
577
            this.api_info_view = new views.ApiInfoView();
578
            this.details_view = new views.DetailsView();
579
            this.suspended_view = new views.SuspendedVMView();
580
            //this.notice_view = new views.NoticeView();
581
        },
582
        
583
        show_loading_view: function() {
584
            $("#container #content").hide();
585
            $("#loading-view").show();
586
        },
587

    
588
        hide_loading_view: function() {
589
            $("#container #content").show();
590
            $("#loading-view").hide();
591
            $(".css-panes").show();
592
        },
593
        
594
        items_to_load: 4,
595
        completed_items: 0,
596
        check_status: function(loaded) {
597
            this.completed_items++;
598
            // images, flavors loaded
599
            if (this.completed_items == 2) {
600
                this.load_nets_and_vms();
601
            }
602

    
603
            if (this.completed_items == this.items_to_load) {
604
                this.update_status("Rendering layout...");
605
                var self = this;
606
                window.setTimeout(function(){
607
                    self.after_load();
608
                }, 10)
609
            }
610
        },
611

    
612
        load_nets_and_vms: function() {
613
            var self = this;
614
            this.update_status("Loading vms...");
615
            storage.vms.fetch({refresh:true, update:false, success: function(){
616
                self.update_status("VMS Loaded.");
617
                self.check_status();
618
            }});
619

    
620
            this.update_status("Loading networks...");
621
            storage.networks.fetch({refresh:true, update:false, success: function(){
622
                self.update_status("Networks loaded.");
623
                self.check_status();
624
            }});
625
        },  
626

    
627
        init_intervals: function() {
628
            var fetcher_params = [snf.config.update_interval, 
629
                                  snf.config.update_interval_increase || 500,
630
                                  snf.config.fast_interval || snf.config.update_interval/2, 
631
                                  snf.config.update_interval_increase_after_calls || 4,
632
                                  snf.config.update_interval_max || 20000,
633
                                  true, 
634
                                  {is_recurrent: true}]
635
            
636
            this._networks = storage.networks.get_fetcher.apply(storage.networks, _.clone(fetcher_params));
637
            this._vms = storage.vms.get_fetcher.apply(storage.vms, _.clone(fetcher_params));
638
        },
639

    
640
        stop_intervals: function() {
641
            if (this._networks) { this._networks.stop(); }
642
            if (this._vms) { this._vms.stop(); }
643
            this.intervals_stopped = true;
644
        },
645

    
646
        update_intervals: function() {
647
            if (this._networks) {
648
                this._networks.stop();
649
                this._networks.start();
650
            } else {
651
                this.init_intervals();
652
            }
653

    
654
            if (this._vms) {
655
                this._vms.stop();
656
                this._vms.start();
657
            } else {
658
                this.init_intervals();
659
            }
660

    
661
            this.intervals_stopped = false;
662
        },
663

    
664
        after_load: function() {
665
            var self = this;
666
            this.update_status("Setting vms update interval...");
667
            this.init_intervals();
668
            this.update_intervals();
669
            this.update_status("Showing initial view...");
670
            
671
            // bypass update_hidden_views in initial view
672
            // rendering to force all views to get render
673
            // on their creation
674
            var uhv = snf.config.update_hidden_views;
675
            snf.config.update_hidden_views = true;
676
            this.initialize_views();
677
            snf.config.update_hidden_views = uhv;
678

    
679
            window.setTimeout(function() {
680
                self.update_status("Initializing overlays...");
681
                self.load_initialize_overlays();
682
            }, 20);
683
        },
684

    
685
        load_initialize_overlays: function() {
686
            this.init_overlays();
687
            // display initial view
688
            this.loaded = true;
689
            
690
            // application start point
691

    
692
            this.check_empty();
693
            this.show_initial_view();
694
        },
695

    
696
        load: function() {
697
            if (synnefo.config.use_glance) {
698
                synnefo.glance.register();
699
            }
700
            this.error_view = new views.ErrorView();
701
            // api request error handling
702
            synnefo.api.bind("error", _.bind(this.handle_api_error, this));
703
            synnefo.api.bind("change:error_state", _.bind(this.handle_api_error_state, this));
704
            synnefo.ui.bind("error", _.bind(this.handle_ui_error, this));
705

    
706
            this.feedback_view = new views.FeedbackView();
707
            this.public_keys_view = new views.PublicKeysOverlay();
708
            
709
            if (synnefo.config.use_glance) {
710
                this.custom_images_view = new views.CustomImagesOverlay();
711
            }
712

    
713
            var self = this;
714
            // initialize overlay views
715
            
716
            // display loading message
717
            this.show_loading_view();
718
            // sync load initial data
719
            this.update_status("Loading images...");
720
            storage.images.fetch({refresh:true, update:false, success: function(){
721
                self.check_status()
722
            }});
723
            this.update_status("Loading flavors...");
724
            storage.flavors.fetch({refresh:true, update:false, success:function(){
725
                self.check_status()
726
            }});
727
        },
728

    
729
        update_status: function(msg) {
730
            //this.log.debug(msg)
731
            this.status = msg;
732
            $("#loading-view .info").removeClass("hidden")
733
            $("#loading-view .info").text(this.status);
734
        },
735

    
736
        initialize_views: function() {
737
            this.select_view = new views.SelectView(this, this.router);
738
            this.empty_view = new views.EmptyView();
739
            this.metadata_view = new views.MetadataView();
740
            this.multiple_actions_view = new views.MultipleActionsView();
741
            
742
            this.add_view("icon");
743
            this.add_view("list");
744
            this.add_view("single");
745
            this.add_view("networks");
746
            this.add_view("disks");
747

    
748
            this.init_menu();
749
        },
750

    
751
        init_menu: function() {
752
            $(".usermenu .feedback").click(_.bind(function(e){
753
                e.preventDefault();
754
                this.feedback_view.show();
755
            }, this));
756
            $(".usermenu .public_keys").click(_.bind(function(e){
757
                e.preventDefault();
758
                this.public_keys_view.show();
759
            }, this));
760

    
761
            if (snf.glance) {
762
                $(".usermenu .custom_images").click(_.bind(function(e){
763
                    e.preventDefault();
764
                    this.custom_images_view.show();
765
                }, this));
766
            } else {
767
                $(".usermenu .custom_images").hide();
768
            }
769
        },
770
        
771
        load_user_quotas: function() {
772
          var main_view = this;
773
          snf.api.sync('read', undefined, {
774
            url: synnefo.config.quota_url, 
775
            success: function(d) {
776
              snf.user.quotas = {};
777
              snf.user.quotas['vms'] = d.vms_quota;
778
              snf.user.quotas['networks'] = d.networks_quota;
779
              main_view.init_quotas_handlers(['vms','networks']);
780
            }
781
          });
782
        },
783
        
784
        check_quotas: function(type) {
785
          var storage = synnefo.storage[type];
786
          var consumed = storage.length;
787
          if (type == "networks") {
788
            consumed = storage.filter(function(net){
789
              return !net.is_public() && !net.is_deleted();
790
            }).length;
791
          }
792
          if (snf.user.quotas && consumed >= snf.user.quotas[type]) {
793
            storage.trigger("quota_reached");
794
          } else {
795
            storage.trigger("quota_free");
796
          }
797
        },
798

    
799
        init_quotas_handlers: function(types) {
800
          var self = this;
801
          _.each(types, function(type) {
802
            var storage = synnefo.storage[type];
803
            if (!storage) { return };
804
            var check_quotas = function() {
805
              self.check_quotas(type);
806
            }
807
            storage.bind("add", check_quotas);
808
            storage.bind("remove", check_quotas);
809
            check_quotas();
810
          })
811
        },
812

    
813
        // initial view based on user cookie
814
        show_initial_view: function() {
815
          this.set_vm_view_handlers();
816
          this.load_user_quotas();
817
          this.hide_loading_view();
818
          
819
          bb.history.start();
820

    
821
          this.trigger("ready");
822
        },
823

    
824
        show_vm_details: function(vm) {
825
            if (vm) {
826
              this.router.vm_details_view(vm.id);
827
            }
828
        },
829

    
830
        set_vm_view_handlers: function() {
831
            var self = this;
832
            $("#createcontainer #create").click(function(e){
833
                e.preventDefault();
834
                if ($(this).hasClass("disabled")) { return }
835
                self.router.vm_create_view();
836
            });
837

    
838
            synnefo.storage.vms.bind("quota_reached", function(){
839
              $("#createcontainer #create").addClass("disabled");
840
              $("#createcontainer #create").attr("title", "Machines limit reached");
841
            });
842

    
843
            synnefo.storage.vms.bind("quota_free", function(){
844
              $("#createcontainer #create").removeClass("disabled");
845
              $("#createcontainer #create").attr("title", "");
846
            });
847

    
848
            this.check_quotas('vms');
849
        },
850

    
851
        check_empty: function() {
852
            if (!this.loaded) { return }
853
            if (storage.vms.length == 0) {
854
                this.show_view("machines");
855
                this.router.show_welcome();
856
                this.empty_hidden = false;
857
            } else {
858
                this.hide_empty();
859
            }
860
        },
861

    
862
        show_empty: function() {
863
            if (!this.empty_hidden) { return };
864
            $("#machines-pane-top").addClass("empty");
865

    
866
            this.$(".panes").hide();
867
            this.$("#machines-pane").show();
868

    
869
            this.hide_views([]);
870
            this.empty_hidden = false;
871
            this.empty_view.show();
872
            this.select_view.update_layout();
873
            this.empty_hidden = false;
874
        },
875

    
876
        hide_empty: function() {
877
            if (this.empty_hidden) { return };
878
            $("#machines-pane-top").removeClass("empty");
879

    
880
            this.empty_view.hide(true);
881
            this.router.vms_index();
882
            this.empty_hidden = true;
883
            this.select_view.update_layout();
884
        },
885
        
886
        get_title: function(view_id) {
887
            var view_id = view_id || this.current_view_id;
888
            return this.views_titles[view_id];
889
        },
890

    
891
        // return class object for the given view or false if
892
        // the view is not registered
893
        get_class_for_view: function (view_id) {
894
            if (!this.views_classes[view_id]) {
895
                return false;
896
            }
897
            return this.views_classes[view_id];
898
        },
899

    
900
        view: function(view_id) {
901
            return this.views[view_id];
902
        },
903

    
904
        add_view: function(view_id) {
905
            if (!this.views[view_id]) {
906
                var cls = this.get_class_for_view(view_id);
907
                if (this.skip_errors) {
908
                    this.views[view_id] = new cls();
909
                    $(this.views[view_id]).bind("resize", _.bind(function() {
910
                        window.positionFooter();
911
                        this.multiple_actions_view.fix_position();
912
                    }, this));
913
                } else {
914
                    // catch ui errors
915
                    try {
916
                        this.views[view_id] = new cls();
917
                        $(this.views[view_id]).bind("resize", _.bind(function() {
918
                            window.positionFooter();
919
                            this.multiple_actions_view.fix_position();
920
                        }, this));
921
                    } catch (err) {snf.ui.trigger_error(-1, "Cannot add view", err)}
922
                }
923
            } else {
924
            }
925

    
926
            if (this.views[view_id].vms_view) {
927
                this.views[view_id].metadata_view = this.metadata_view;
928
            }
929
            return this.views[view_id];
930
        },
931
            
932
        hide_views: function(skip) {
933
            _.each(this.views, function(view) {
934
                if (skip.indexOf(view) === -1) {
935
                    $(view.el).hide();
936
                }
937
            }, this)
938
        },
939
        
940
        get: function(view_id) {
941
            return this.views[view_id];
942
        },
943
        
944
        session_view: function() {
945
            if (this.pane_view_from_session() > 0) {
946
                return this.views_pane_indexes[this.pane_view_from_session()];
947
            } else {
948
                return this.views_indexes[this.machine_view_from_session()];
949
            }
950
        },
951

    
952
        pane_view_from_session: function() {
953
            return $.cookie("pane_view") || 0;
954
        },
955

    
956
        machine_view_from_session: function() {
957
            return $.cookie("machine_view") || 0;
958
        },
959

    
960
        update_session: function() {
961
            $.cookie("pane_view", this.pane_ids[this.current_view_id]);
962
            if (this.current_view.vms_view) {
963
                $.cookie("machine_view", this.views_ids[this.current_view_id]);
964
            }
965
        },
966

    
967
        identify_view: function(view_id) {
968
            // machines view_id is an alias to
969
            // one of the 3 machines view
970
            // identify which one (if no cookie set defaults to icon)
971
            if (view_id == "machines") {
972
                var index = this.machine_view_from_session();
973
                view_id = this.views_indexes[index];
974
            }
975
            return view_id;
976
        },
977
        
978
        // switch to current view pane
979
        // if differs from the visible one
980
        show_view_pane: function() {
981
            if (this.current_view.pane != this.current_pane) {
982
                $(this.current_view.pane).show();
983
                $(this.current_pane).hide();
984
                this.current_pane = this.current_view.pane;
985
            }
986
        },
987
        
988
        show_view: function(view_id) {
989
            //var d = new Date;
990
            var ret = this._show_view(view_id);
991
            //console.log((new Date)-d)
992
            return ret;
993
        },
994

    
995
        _show_view: function(view_id) {
996
                // same view, visible
997
                // get out of here asap
998
                if (this.current_view && 
999
                    this.current_view.id == view_id && 
1000
                    this.current_view.visible()) {
1001
                    return;
1002
                }
1003
                
1004
                // choose proper view_id
1005
                view_id = this.identify_view(view_id);
1006

    
1007
                // add/create view and update current view
1008
                var view = this.add_view(view_id);
1009
                
1010
                // set current view
1011
                this.current_view = view;
1012
                this.current_view_id = view_id;
1013

    
1014
                // hide all other views
1015
                this.hide_views([this.current_view]);
1016
                
1017
                // FIXME: depricated
1018
                $(".large-spinner").remove();
1019

    
1020
                storage.vms.reset_pending_actions();
1021
                storage.networks.reset_pending_actions();
1022
                storage.vms.stop_stats_update();
1023

    
1024
                // show current view
1025
                this.show_view_pane();
1026
                view.show();
1027
                
1028
                // update menus
1029
                if (this.select_view) {
1030
                    this.select_view.update_layout();
1031
                }
1032
                this.current_view.__update_layout();
1033

    
1034
                // update cookies
1035
                this.update_session();
1036
                
1037
                // machines view subnav
1038
                if (this.current_view.vms_view) {
1039
                    $("#machines-pane").show();
1040
                } else {
1041
                    $("#machines-pane").hide();
1042
                }
1043
                
1044
                // fix footer position
1045
                // TODO: move footer handlers in
1046
                // main view (from home.html)
1047
                if (window.positionFooter) {
1048
                    window.positionFooter();
1049
                }
1050

    
1051
                // trigger view change event
1052
                this.trigger("view:change", this.current_view.view_id);
1053
                this.select_view.title.text(this.get_title());
1054
                $(window).trigger("view:change");
1055
                return view;
1056
        },
1057

    
1058
        reset_vm_actions: function() {
1059
        
1060
        },
1061
        
1062
        // identify current view
1063
        // based on views element visibility
1064
        current_view_id: function() {
1065
            var found = false;
1066
            _.each(this.views, function(key, value) {
1067
                if (value.visible()) {
1068
                    found = value;
1069
                }
1070
            })
1071
            return found;
1072
        }
1073

    
1074
    });
1075

    
1076
    snf.ui.main = new views.MainView();
1077
    
1078
    snf.ui.init = function() {
1079
        if (snf.config.handle_window_exceptions) {
1080
            window.onerror = function(msg, file, line) {
1081
                snf.ui.trigger_error("CRITICAL", msg, {}, { file:file + ":" + line, allow_close: false });
1082
            };
1083
        }
1084
        snf.ui.main.load();
1085
    }
1086

    
1087
})(this);