Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_main_view.js @ 9ce969a7

History | View | Annotate | Download (25.3 kB)

1
;(function(root){
2

    
3
    // root
4
    var root = root;
5
    
6
    // setup namepsaces
7
    var snf = root.synnefo = root.synnefo || {};
8
    var models = snf.models = snf.models || {}
9
    var storage = snf.storage = snf.storage || {};
10
    var ui = snf.ui = snf.ui || {};
11

    
12
    var views = snf.views = snf.views || {}
13

    
14
    // shortcuts
15
    var bb = root.Backbone;
16
    var util = snf.util;
17
    
18
    // TODO: implement me
19
    views.NoticeView = views.Overlay.extend({});
20

    
21
    views.MultipleActionsView = views.View.extend({
22
        view_id: "multiple_actions",
23

    
24
        _actions: {},
25
        el: '#multiple_actions_container',
26
        
27
        initialize: function() {
28
            this.actions = {};
29
            views.MultipleActionsView.__super__.initialize.call(this);
30
            
31
            // view elements
32
            this.confirm_actions = this.$(".confirm_multiple_actions");
33
            this.confirm_actions_yes = this.$(".confirm_multiple_actions button.yes");
34
            this.confirm_actions_no = this.$(".confirm_multiple_actions button.no");
35
            this.confirm_reboot = this.$(".confirm_reboot_required");
36
            this.confirm_reboot_yes = this.$(".confirm_reboot_required button.yes");
37
            this.confirm_reboot_no = this.$(".confirm_reboot_required button.no");
38
            this.confirm_reboot_list = this.confirm_reboot.find(".reboot-machines-list");
39

    
40
            this.init_handlers();
41
            this.update_layout();
42
            
43
            // for heavy resize/scroll window events
44
            // do it `like a boss` 
45
            this.fix_position = _.throttle(this.fix_position, 100);
46
            this.update_layout = _.throttle(this.update_layout, 100);
47
            this.show_limit = 1;
48
        },
49

    
50
        init_handlers: function() {
51
            var self = this;
52

    
53
            $(window).resize(_.bind(function(){
54
                this.fix_position();
55
            }, this));
56

    
57
            $(window).scroll(_.bind(function(){
58
                this.fix_position();
59
            }, this));
60
            
61
            // confirm/cancel button handlers
62
            var self = this;
63
            this.confirm_actions_yes.click(function(){ self.do_all(); })
64
            this.confirm_actions_no.click(function(){
65
                self.reset_actions();
66
            });
67

    
68
            this.confirm_reboot_yes.click(function(){ self.do_reboot_all(); })
69
            this.confirm_reboot_no.click(function(){
70
                self.reset_reboots();
71
            });
72

    
73
            storage.vms.bind("change:pending_action", _.bind(this.handle_vm_change, this));
74
            storage.vms.bind("change:reboot_required", _.bind(this.handle_vm_change, this));
75
        },
76
    
77
        handle_vm_change: function(vm) {
78
            if (vm.has_pending_action()) {
79
                var action = vm.get("pending_action");
80
                this.add_action(vm, action);
81
            } else {
82
                this.remove_action(vm);
83
            }
84
            this.update_layout();
85
        },
86

    
87
        add_action: function(vm, action) {
88
            this._actions[vm.id] = {'vm': vm, 'action': action};
89
        },
90

    
91
        remove_action: function(vm) {
92
            delete this._actions[vm.id];
93
        },
94

    
95
        reset: function() {
96
            this._actions = {};
97
            this.update_layout();
98
        },
99
        
100
        reboot_vm: function(vm) {
101
            vm.call("reboot");
102
        },
103

    
104
        do_reboot_all: function() {
105
            _.each(storage.vms.get_reboot_required(), function(vm){
106
                this.reboot_vm(vm)
107
            }, this)  
108
        },
109

    
110
        do_all: function() {
111
            _.each(this._actions, function(action){
112
                action.vm.call(action.action);
113
            }, this)  
114
            this.reset_actions();
115
        },
116

    
117
        reset_reboots: function () {
118
            _.each(storage.vms.get_reboot_required(), function(vm) {vm.set({'reboot_required': false})}, this);
119
            this.update_layout();
120
        },
121

    
122
        reset_actions: function() {
123
            _.each(this._actions, _.bind(function(action){
124
                try {
125
                    action.vm.clear_pending_action();
126
                    this.remove_action(action.vm);
127
                } catch(err) {
128
                    console.error("vm " + action.vm.id + " failed to reset", err);
129
                }
130
            }, this))  
131
        },
132
        
133
        fix_position: function() {
134
            $('.confirm_multiple').removeClass('fixed');
135
            if (($(this.el).offset().top +$(this.el).height())> ($(window).scrollTop() + $(window).height())) {
136
                $('.confirm_multiple').addClass('fixed');
137
            }
138
        },
139
        
140
        check_notify_limit: function() {
141
            this.show_limit = 1;
142
            if (ui.main.current_view && ['networks', 'vm_list'].indexOf(ui.main.current_view.view_id) > -1) {
143
                this.show_limit = 0;
144
            }
145
        },
146
        
147
        update_reboot_required_list: function(vms) {
148
            this.confirm_reboot_list.empty();
149
        },
150

    
151
        update_reboot_required: function() {
152
            var vms = storage.vms.get_reboot_required();
153
            if (vms.length) {
154
                this.confirm_reboot.find(".actionLen").text(vms.length);
155
                this.update_reboot_required_list();
156
                this.confirm_reboot.show();
157
                $(this.el).show();
158
            } else {
159
                if (!this.actions_visible) {
160
                   $(this.el).hide();
161
                }
162
                this.confirm_reboot.hide();
163
            }
164
        },
165

    
166
        update_layout: function() {
167
            this.check_notify_limit();
168
            this.actions_visible = false;
169

    
170
            if (_.size(this._actions) > this.show_limit) {
171
                this.actions_visible = true;
172
                $(this.el).show();
173
                this.confirm_actions.show();
174
            } else {
175
                $(this.el).hide();
176
                this.confirm_actions.hide();
177
            }
178

    
179
            this.update_reboot_required();
180
            this.confirm_actions.find(".actionLen").text(_.size(this._actions));
181
            $(window).trigger("resize");
182
        }
183
    })
184
    
185
    // menu wrapper view
186
    views.SelectView = views.View.extend({
187
        
188
        initialize: function(view) {
189
            this.parent = view;
190

    
191
            this.pane_view_selector = $(".css-tabs");
192
            this.machine_view_selector = $("#view-select");
193
            this.el = $(".css-tabs");
194
            this.title = $(".tab-name");
195

    
196
            this.set_handlers();
197
            this.update_layout();
198

    
199
            views.SelectView.__super__.initialize.apply(this, arguments);
200
        },
201
        
202
        clear_active: function() {
203
            this.pane_view_selector.find("a").removeClass("active");
204
            this.machine_view_selector.find("a").removeClass("activelink");
205
        },
206
        
207
        // intercept menu links
208
        set_handlers: function() {
209
            var self = this;
210
            this.pane_view_selector.find("a").hover(function(){
211
                // FIXME: title from href ? omg
212
                self.title.text($(this).attr("href"));
213
            }, function(){
214
                self.title.text(self.parent.get_title());
215
            });
216

    
217
            this.pane_view_selector.find("a#machines_view_link").click(_.bind(function(ev){
218
                ev.preventDefault();
219
                this.parent.show_view("machines");
220
            }, this))
221
            this.pane_view_selector.find("a#networks_view_link").click(_.bind(function(ev){
222
                ev.preventDefault();
223
                this.parent.show_view("networks");
224
            }, this))
225
            this.pane_view_selector.find("a#disks_view_link").click(_.bind(function(ev){
226
                ev.preventDefault();
227
                this.parent.show_view("disks");
228
            }, this))
229
            
230
            this.machine_view_selector.find("a#machines_view_icon_link").click(_.bind(function(ev){
231
                ev.preventDefault();
232
                var d = $.now();
233
                this.parent.show_view("icon");
234
            }, this))
235
            this.machine_view_selector.find("a#machines_view_list_link").click(_.bind(function(ev){
236
                ev.preventDefault();
237
                this.parent.show_view("list");
238
            }, this))
239
            this.machine_view_selector.find("a#machines_view_single_link").click(_.bind(function(ev){
240
                ev.preventDefault();
241
                this.parent.show_view("single");
242
            }, this))
243
        },
244

    
245
        update_layout: function() {
246
            this.clear_active();
247

    
248
            var pane_index = this.parent.pane_ids[this.parent.current_view_id];
249
            $(this.pane_view_selector.find("a")).removeClass("active");
250
            $(this.pane_view_selector.find("a").get(pane_index)).addClass("active");
251
            
252
            if (this.parent.current_view && this.parent.current_view.vms_view) {
253

    
254
                if (storage.vms.length > 0) {
255
                    this.machine_view_selector.show();
256
                    var machine_index = this.parent.views_ids[this.parent.current_view_id];
257
                    $(this.machine_view_selector.find("a").get(machine_index)).addClass("activelink");
258
                } else {
259
                    this.machine_view_selector.hide();
260
                }
261
            } else {
262
                this.machine_view_selector.hide();
263
            }
264

    
265
        }
266
    });
267

    
268
    views.MainView = views.View.extend({
269
        el: 'body',
270
        view_id: 'main',
271
        
272
        // FIXME: titles belong to SelectView
273
        views_titles: {
274
            'icon': 'machines', 'single': 'machines', 
275
            'list': 'machines', 'networks': 'networks',
276
            'disks': 'disks'
277
        },
278

    
279
        // indexes registry
280
        views_indexes: {0: 'icon', 2:'single', 1: 'list', 3:'networks'},
281
        views_pane_indexes: {0:'single', 1:'networks', 2:'disks'},
282

    
283
        // views classes registry
284
        views_classes: {'icon': views.IconView, 'single': views.SingleView, 
285
            'list': views.ListView, 'networks': views.NetworksView},
286

    
287
        // view ids
288
        views_ids: {'icon':0, 'single':2, 'list':1, 'networks':3},
289

    
290
        // on which pane id each view exists
291
        // machine views (icon,single,list) are all on first pane
292
        pane_ids: {'icon':0, 'single':0, 'list':0, 'networks':1, 'disks':2},
293
    
294
        initialize: function(show_view) {
295
            if (!show_view) { show_view = 'icon' };
296
            
297
            // fallback to browser error reporting (true for debug)
298
            this.skip_errors = true
299

    
300
            // reset views
301
            this.views = {};
302

    
303
            this.el = $("#app");
304
            // reset main view status
305
            this._loaded = false;
306
            this.status = "Initializing...";
307

    
308
            // initialize handlers
309
            this.init_handlers();
310

    
311
            // identify initial view from user cookies
312
            // this view will be visible after loading of
313
            // main view
314
            this.initial_view = this.session_view();
315

    
316
            views.MainView.__super__.initialize.call(this);
317
        },
318
        
319
        vms_handlers_registered: false,
320

    
321
        // register event handlers
322
        // 
323
        // vms storage events to identify if vms list 
324
        // is empty and display empty view if user viewing
325
        // a machine view
326
        //
327
        // api/ui error event handlers
328
        init_handlers: function() {
329
            // vm handlers
330
            storage.vms.bind("remove", _.bind(this.check_empty, this));
331
            storage.vms.bind("add", _.bind(this.check_empty, this));
332
            storage.vms.bind("change", _.bind(this.check_empty, this));
333
            storage.vms.bind("reset", _.bind(this.check_empty, this));
334
            
335
            // api calls handlers
336
            synnefo.api.bind("error", _.bind(this.handle_api_error, this));
337
            synnefo.api.bind("change:error_state", _.bind(this.handle_api_error_state, this));
338
            synnefo.ui.bind("error", _.bind(this.handle_ui_error, this));
339
        },
340
        
341
        handle_api_error_state: function(state) {
342
            if (snf.api.error_state === snf.api.STATES.ERROR) {
343
                this.stop_intervals();
344
            } else {
345
                if (this.intervals_stopped) {
346
                    this.update_intervals();
347
                }
348
            }
349
        },
350
        
351
        handle_api_error: function(args) {
352
            if (arguments.length == 1) { arguments = _.toArray(arguments[0])};
353

    
354
            if (!_.last(arguments).display) {
355
                return;
356
            }
357

    
358
            this.error_state = true;
359
            
360
            var xhr = arguments[0];
361
            var args = util.parse_api_error.apply(util, arguments);
362
            
363
            // force logout if UNAUTHORIZED request arrives
364
            if (args.code == 401) { snf.ui.logout(); return };
365

    
366
            var error_entry = [args.ns, args.code, args.message, args.type, args.details, args];
367
            this.error_view.show_error.apply(this.error_view, error_entry);
368
        },
369

    
370
        handle_ui_error: function(error) {
371
            error = error + "<br /><br />" + snf.util.stacktrace().replace("at", "<br /><br />at");
372
            this.error_view.show_error("Application", -1, "Something went wrong", "JS Exception", error);
373
        },
374

    
375
        init_overlays: function() {
376
            this.create_vm_view = new views.CreateVMView();
377
            //this.notice_view = new views.NoticeView();
378
        },
379
        
380
        show_loading_view: function() {
381
            $("#container #content").hide();
382
            $("#loading-view").show();
383
        },
384

    
385
        hide_loading_view: function() {
386
            $("#container #content").show();
387
            $("#loading-view").hide();
388
            $(".css-panes").show();
389
        },
390
        
391
        items_to_load: 4,
392
        completed_items: 0,
393
        check_status: function(loaded) {
394
            this.completed_items++;
395
            // images, flavors loaded
396
            if (this.completed_items == 2) {
397
                this.load_nets_and_vms();
398
            }
399
            if (this.completed_items == this.items_to_load) {
400
                this.after_load();
401
            }
402
        },
403

    
404
        load_nets_and_vms: function() {
405
            var self = this;
406
            this.update_status("Loading vms...");
407
            storage.vms.fetch({refresh:true, update:false, success: function(){
408
                self.update_status("VMS Loaded.");
409
                self.check_status();
410
            }});
411

    
412
            this.update_status("Loading networks...");
413
            storage.networks.fetch({refresh:true, update:false, success: function(){
414
                self.update_status("Networks loaded.");
415
                self.check_status();
416
            }});
417
        },  
418

    
419
        init_intervals: function() {
420
            this._networks = storage.networks.get_fetcher(snf.config.update_interval, 
421
                                                          snf.config.update_interval - 100, 
422
                                                          1, true, undefined);
423
            this._vms = storage.vms.get_fetcher(snf.config.update_interval, 
424
                                                snf.config.update_interval - 100, 
425
                                                1, true, undefined);
426
        },
427

    
428
        stop_intervals: function() {
429
            if (this._networks) { this._networks.stop(); }
430
            if (this._vms) { this._vms.stop(); }
431
            this.intervals_stopped = true;
432
        },
433

    
434
        update_intervals: function() {
435
            if (this._networks) {
436
                this._networks.stop();
437
                this._networks.start();
438
            } else {
439
                this.init_intervals();
440
            }
441

    
442
            if (this._vms) {
443
                this._vms.stop();
444
                this._vms.start();
445
            } else {
446
                this.init_intervals();
447
            }
448

    
449
            this.intervals_stopped = false;
450
        },
451

    
452
        after_load: function() {
453
            this.update_status("Setting vms update interval...");
454
            this.init_intervals();
455
            this.update_intervals();
456
            this.update_status("Loaded");
457
            // FIXME: refactor needed
458
            // initialize views
459
            this.initialize_views()
460
            this.update_status("Initializing overlays...");
461
            this.init_overlays();
462
            // display initial view
463
            this.loaded = true;
464
            this.show_initial_view();
465
            this.check_empty();
466
        },
467

    
468
        load: function() {
469
            this.error_view = new views.ErrorView();
470
            var self = this;
471
            // initialize overlay views
472
            
473
            // display loading message
474
            this.show_loading_view();
475
            // sync load initial data
476
            this.update_status("Loading images...");
477
            storage.images.fetch({refresh:true, update:false, success: function(){
478
                self.check_status()
479
            }});
480
            this.update_status("Loading flavors...");
481
            storage.flavors.fetch({refresh:true, update:false, success:function(){
482
                self.check_status()
483
            }});
484
        },
485

    
486
        update_status: function(msg) {
487
            this.log.debug(msg)
488
            this.status = msg;
489
            $("#loading-view .info").removeClass("hidden")
490
            $("#loading-view .info").text(this.status);
491
        },
492

    
493
        initialize_views: function() {
494
            this.empty_view = new views.EmptyView();
495
            this.select_view = new views.SelectView(this);
496
            this.metadata_view = new views.MetadataView();
497
            this.multiple_actions_view = new views.MultipleActionsView();
498
            this.feedback_view = new views.FeedbackView();
499
            
500
            this.add_view("icon");
501
            this.add_view("list");
502
            this.add_view("single");
503
            this.add_view("networks");
504

    
505
            this.init_menu();
506
        },
507

    
508
        init_menu: function() {
509
            $(".usermenu .feedback").click(_.bind(function(){
510
                this.feedback_view.show();
511
            }, this));
512
        },
513
        
514
        // initial view based on user cookie
515
        show_initial_view: function() {
516
          this.set_vm_view_handlers();
517
          this.hide_loading_view();
518
          this.show_view(this.initial_view);
519
          this.trigger("initial");
520
        },
521

    
522
        show_vm_details: function(vm) {
523
            snf.ui.main.show_view("single")
524
            snf.ui.main.current_view.show_vm(vm);
525
        },
526

    
527
        set_vm_view_handlers: function() {
528
            $("#createcontainer #create").click(_.bind(function(){
529
                this.create_vm_view.show();
530
            }, this))
531
        },
532

    
533
        check_empty: function() {
534
            if (!this.loaded) { return }
535
            if (storage.vms.length == 0) {
536
                this.show_view("machines");
537
                this.show_empty();
538
            } else {
539
                this.hide_empty();
540
            }
541
            this.select_view.update_layout();
542
        },
543

    
544
        show_empty: function() {
545
            $("#machines-pane-top").addClass("empty");
546

    
547
            this.$(".panes").hide();
548
            this.$("#machines-pane").show();
549

    
550
            this.hide_views([]);
551
            this.empty_view.show();
552
        },
553

    
554
        hide_empty: function() {
555
            $("#machines-pane-top").removeClass("empty");
556

    
557
            this.empty_view = new views.EmptyView();
558
            this.empty_view.hide();
559
            if (this.current_view && !this.current_view.visible()) { 
560
                this.current_view.show(); 
561
            }
562
        },
563
        
564
        get_title: function(view_id) {
565
            var view_id = view_id || this.current_view_id;
566
            return this.views_titles[view_id];
567
        },
568

    
569
        // return class object for the given view or false if
570
        // the view is not registered
571
        get_class_for_view: function (view_id) {
572
            if (!this.views_classes[view_id]) {
573
                return false;
574
            }
575
            return this.views_classes[view_id];
576
        },
577

    
578
        view: function(view_id) {
579
            return this.views[view_id];
580
        },
581

    
582
        add_view: function(view_id) {
583
            if (!this.views[view_id]) {
584
                var cls = this.get_class_for_view(view_id);
585
                if (this.skip_errors) {
586
                    this.views[view_id] = new cls();
587
                    $(this.views[view_id]).bind("resize", _.bind(function() {
588
                        window.positionFooter();
589
                        this.multiple_actions_view.fix_position();
590
                    }, this));
591
                } else {
592
                    // catch ui errors
593
                    try {
594
                        this.views[view_id] = new cls();
595
                        $(this.views[view_id]).bind("resize", _.bind(function() {
596
                            window.positionFooter();
597
                            this.multiple_actions_view.fix_position();
598
                        }, this));
599
                    } catch (err) {snf.ui.trigger("error", err)}
600
                }
601
            } else {
602
            }
603

    
604
            if (this.views[view_id].vms_view) {
605
                this.views[view_id].metadata_view = this.metadata_view;
606
            }
607
            return this.views[view_id];
608
        },
609
            
610
        hide_views: function(skip) {
611
            _.each(this.views, function(view) {
612
                if (skip.indexOf(view) === -1) {
613
                    $(view.el).hide();
614
                }
615
            }, this)
616
        },
617
        
618
        get: function(view_id) {
619
            return this.views[view_id];
620
        },
621
        
622
        session_view: function() {
623
            if (this.pane_view_from_session() > 0) {
624
                return this.views_pane_indexes[this.pane_view_from_session()];
625
            } else {
626
                return this.views_indexes[this.machine_view_from_session()];
627
            }
628
        },
629

    
630
        pane_view_from_session: function() {
631
            return $.cookie("pane_view") || 0;
632
        },
633

    
634
        machine_view_from_session: function() {
635
            return $.cookie("machine_view") || 0;
636
        },
637

    
638
        update_session: function() {
639
            $.cookie("pane_view", this.pane_ids[this.current_view_id]);
640
            if (this.current_view.vms_view) {
641
                $.cookie("machine_view", this.views_ids[this.current_view_id]);
642
            }
643
        },
644

    
645
        identify_view: function(view_id) {
646
            // machines view_id is an alias to
647
            // one of the 3 machines view
648
            // identify which one (if no cookie set defaults to icon)
649
            if (view_id == "machines") {
650
                var index = this.machine_view_from_session();
651
                view_id = this.views_indexes[index];
652
            }
653
            return view_id;
654
        },
655
        
656
        // switch to current view pane
657
        // if differs from the visible one
658
        show_view_pane: function() {
659
            if (this.current_view.pane != this.current_pane) {
660
                $(this.current_view.pane).show();
661
                $(this.current_pane).hide();
662
                this.current_pane = this.current_view.pane;
663
            }
664
        },
665

    
666
        show_view: function(view_id) {
667
            // same view, visible
668
            // get out of here asap
669
            if (this.current_view && 
670
                this.current_view.id == view_id && 
671
                this.current_view.visible()) {
672
                return;
673
            }
674
            
675
            // choose proper view_id
676
            view_id = this.identify_view(view_id);
677

    
678
            // add/create view and update current view
679
            var view = this.add_view(view_id);
680
            
681
            // set current view
682
            this.current_view = view;
683
            this.current_view_id = view_id;
684

    
685
            // hide all other views
686
            this.hide_views([this.current_view]);
687
            
688
            // FIXME: depricated
689
            $(".large-spinner").remove();
690

    
691
            storage.vms.reset_pending_actions();
692
            storage.vms.stop_stats_update();
693

    
694
            // show current view
695
            this.show_view_pane();
696
            view.show();
697
            
698
            // update menus
699
            if (this.select_view) {
700
                this.select_view.update_layout();
701
            }
702
            this.current_view.__update_layout();
703

    
704
            // update cookies
705
            this.update_session();
706
            
707
            // machines view subnav
708
            if (this.current_view.vms_view) {
709
                $("#machines-pane").show();
710
            } else {
711
                $("#machines-pane").hide();
712
            }
713
            
714
            // fix footer position
715
            // TODO: move footer handlers in
716
            // main view (from home.html)
717
            if (window.positionFooter) {
718
                window.positionFooter();
719
            }
720
            
721
            // trigger view change event
722
            this.trigger("view:change", this.current_view.view_id);
723
            $(window).trigger("view:change");
724
            return view;
725
        },
726

    
727
        reset_vm_actions: function() {
728
        
729
        },
730
        
731
        // identify current view
732
        // based on views element visibility
733
        current_view_id: function() {
734
            var found = false;
735
            _.each(this.views, function(key, value) {
736
                if (value.visible()) {
737
                    found = value;
738
                }
739
            })
740
            return found;
741
        }
742

    
743
    });
744

    
745
    snf.ui.main = new views.MainView();
746
    
747
    snf.ui.logout = function() {
748
        $.cookie("X-Auth-Token", null);
749
        if (window.LOGOUT_REDIRECT !== undefined)
750
        {
751
            window.location = window.LOGOUT_REDIRECT;
752
        } else {
753
            window.location.reload();
754
        }
755
    }
756

    
757
    snf.ui.init = function() {
758
        snf.ui.main.load();
759
    }
760

    
761
})(this);