Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_main_view.js @ 497c4795

History | View | Annotate | Download (24.8 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.show_limit = 1;
47
        },
48

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
264
        }
265
    });
266

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

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

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

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

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

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

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

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

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

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

    
320
        // register event handlers
321
        // 
322
        // vms storage events to identify if vms list 
323
        // is empty and display empty view if user viewing
324
        // a machine view
325
        //
326
        // api/ui error event handlers
327
        init_handlers: function() {
328
            // vm handlers
329
            storage.vms.bind("remove", _.bind(this.check_empty, this));
330
            storage.vms.bind("add", _.bind(this.check_empty, this));
331
            storage.vms.bind("change", _.bind(this.check_empty, this));
332
            storage.vms.bind("reset", _.bind(this.check_empty, this));
333
            
334
            // api calls handlers
335
            synnefo.api.bind("error", _.bind(this.handle_api_error, this));
336
            synnefo.api.bind("change:error_state", _.bind(this.handle_api_error_state, this));
337
            synnefo.ui.bind("error", _.bind(this.handle_ui_error, this));
338
        },
339
        
340
        handle_api_error_state: function(state) {
341
            if (snf.api.error_state) {
342
                this.stop_intervals();
343
            } else {
344
                if (this.intervals_stopped) {
345
                    this.update_intervals();
346
                }
347
            }
348
        },
349
        
350
        handle_api_error: function(xhr, type, message) {
351
            this.error_state = true;
352
            this.log.error("API ERRROR", arguments);
353
            
354
            var xhr = arguments[0];
355
            var args = util.parse_api_error(arguments);
356
            
357
            // force logout if UNAUTHORIZED request arrives
358
            if (args.code == 401) { snf.ui.logout(); return };
359

    
360
            var error_entry = [args.ns, args.code, args.message, args.type, args.details, args];
361
            this.error_view.show_error.apply(this.error_view, error_entry);
362
        },
363

    
364
        handle_ui_error: function(error) {
365
            error = error + "<br /><br />" + snf.util.stacktrace().replace("at", "<br /><br />at");
366
            this.error_view.show_error("Application", -1, "Something went wrong", "JS Exception", error);
367
        },
368

    
369
        init_overlays: function() {
370
            this.create_vm_view = new views.CreateVMView();
371
            //this.notice_view = new views.NoticeView();
372
        },
373
        
374
        show_loading_view: function() {
375
            $("#container #content").hide();
376
            $("#loading-view").show();
377
        },
378

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

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

    
412
        init_intervals: function() {
413
            this._networks = storage.networks.get_fetcher(snf.config.update_interval, snf.config.update_interval/2, 2, true, undefined);
414
            this._vms = storage.vms.get_fetcher(snf.config.update_interval, snf.config.update_interval/2, 2, true, undefined);
415
        },
416

    
417
        stop_intervals: function() {
418
            if (this._networks) { this._networks.stop(); }
419
            if (this._vms) { this._vms.stop(); }
420
            this.intervals_stopped = true;
421
        },
422

    
423
        update_intervals: function() {
424
            if (this._networks) {
425
                this._networks.stop();
426
                this._networks.start();
427
            } else {
428
                this.init_intervals();
429
            }
430

    
431
            if (this._vms) {
432
                this._vms.stop();
433
                this._vms.start();
434
            } else {
435
                this.init_intervals();
436
            }
437

    
438
            this.intervals_stopped = false;
439
        },
440

    
441
        after_load: function() {
442
            this.update_status("Setting vms update interval...");
443
            this.init_intervals();
444
            this.update_intervals();
445
            this.update_status("Loaded");
446
            // FIXME: refactor needed
447
            // initialize views
448
            this.initialize_views()
449
            this.update_status("Initializing overlays...");
450
            this.init_overlays();
451
            // display initial view
452
            this.loaded = true;
453
            this.show_initial_view();
454
            this.check_empty();
455
        },
456

    
457
        load: function() {
458
            this.error_view = new views.ErrorView();
459
            var self = this;
460
            // initialize overlay views
461
            
462
            // display loading message
463
            this.show_loading_view();
464
            // sync load initial data
465
            this.update_status("Loading images...");
466
            storage.images.fetch({refresh:true, update:false, success: function(){
467
                self.check_status()
468
            }});
469
            this.update_status("Loading flavors...");
470
            storage.flavors.fetch({refresh:true, update:false, success:function(){
471
                self.check_status()
472
            }});
473
        },
474

    
475
        update_status: function(msg) {
476
            this.log.debug(msg)
477
            this.status = msg;
478
            $("#loading-view .info").removeClass("hidden")
479
            $("#loading-view .info").text(this.status);
480
        },
481

    
482
        initialize_views: function() {
483
            this.empty_view = new views.EmptyView();
484
            this.select_view = new views.SelectView(this);
485
            this.metadata_view = new views.MetadataView();
486
            this.multiple_actions_view = new views.MultipleActionsView();
487
            this.feedback_view = new views.FeedbackView();
488
            
489
            this.add_view("icon");
490
            this.add_view("list");
491
            this.add_view("single");
492
            this.add_view("networks");
493

    
494
            this.init_menu();
495
        },
496

    
497
        init_menu: function() {
498
            $(".usermenu .feedback").click(_.bind(function(){
499
                this.feedback_view.show();
500
            }, this));
501
        },
502
        
503
        // initial view based on user cookie
504
        show_initial_view: function() {
505
          this.set_vm_view_handlers();
506
          this.hide_loading_view();
507
          this.show_view(this.initial_view);
508
          this.trigger("initial");
509
        },
510

    
511
        show_vm_details: function(vm) {
512
            snf.ui.main.show_view("single")
513
            snf.ui.main.current_view.show_vm(vm);
514
        },
515

    
516
        set_vm_view_handlers: function() {
517
            $("#createcontainer #create").click(_.bind(function(){
518
                this.create_vm_view.show();
519
            }, this))
520
        },
521

    
522
        check_empty: function() {
523
            if (!this.loaded) { return }
524
            if (storage.vms.length == 0) {
525
                this.show_empty();
526
            } else {
527
                this.hide_empty();
528
            }
529
            this.select_view.update_layout();
530
        },
531

    
532
        show_empty: function() {
533
            $("#machines-pane-top").addClass("empty");
534

    
535
            this.$(".panes").hide();
536
            this.$("#machines-pane").show();
537

    
538
            this.hide_views([]);
539
            this.empty_view.show();
540
        },
541

    
542
        hide_empty: function() {
543
            $("#machines-pane-top").removeClass("empty");
544

    
545
            this.empty_view = new views.EmptyView();
546
            this.empty_view.hide();
547
            if (this.current_view && !this.current_view.visible()) { 
548
                this.current_view.show(); 
549
            }
550
        },
551
        
552
        get_title: function(view_id) {
553
            var view_id = view_id || this.current_view_id;
554
            return this.views_titles[view_id];
555
        },
556

    
557
        // return class object for the given view or false if
558
        // the view is not registered
559
        get_class_for_view: function (view_id) {
560
            if (!this.views_classes[view_id]) {
561
                return false;
562
            }
563
            return this.views_classes[view_id];
564
        },
565

    
566
        view: function(view_id) {
567
            return this.views[view_id];
568
        },
569

    
570
        add_view: function(view_id) {
571
            if (!this.views[view_id]) {
572
                var cls = this.get_class_for_view(view_id);
573
                if (this.skip_errors) {
574
                    this.views[view_id] = new cls();
575
                    $(this.views[view_id]).bind("resize", _.bind(function() {
576
                        window.positionFooter();
577
                        this.multiple_actions_view.fix_position();
578
                    }, this));
579
                } else {
580
                    // catch ui errors
581
                    try {
582
                        this.views[view_id] = new cls();
583
                        $(this.views[view_id]).bind("resize", _.bind(function() {
584
                            window.positionFooter();
585
                            this.multiple_actions_view.fix_position();
586
                        }, this));
587
                    } catch (err) {snf.ui.trigger("error", err)}
588
                }
589
            } else {
590
            }
591

    
592
            if (this.views[view_id].vms_view) {
593
                this.views[view_id].metadata_view = this.metadata_view;
594
            }
595
            return this.views[view_id];
596
        },
597
            
598
        hide_views: function(skip) {
599
            _.each(this.views, function(view) {
600
                if (skip.indexOf(view) === -1) {
601
                    $(view.el).hide();
602
                }
603
            }, this)
604
        },
605
        
606
        get: function(view_id) {
607
            return this.views[view_id];
608
        },
609
        
610
        session_view: function() {
611
            if (this.pane_view_from_session() > 0) {
612
                return this.views_pane_indexes[this.pane_view_from_session()];
613
            } else {
614
                return this.views_indexes[this.machine_view_from_session()];
615
            }
616
        },
617

    
618
        pane_view_from_session: function() {
619
            return $.cookie("pane_view") || 0;
620
        },
621

    
622
        machine_view_from_session: function() {
623
            return $.cookie("machine_view") || 0;
624
        },
625

    
626
        update_session: function() {
627
            $.cookie("pane_view", this.pane_ids[this.current_view_id]);
628
            if (this.current_view.vms_view) {
629
                $.cookie("machine_view", this.views_ids[this.current_view_id]);
630
            }
631
        },
632

    
633
        identify_view: function(view_id) {
634
            // machines view_id is an alias to
635
            // one of the 3 machines view
636
            // identify which one (if no cookie set defaults to icon)
637
            if (view_id == "machines") {
638
                var index = this.machine_view_from_session();
639
                view_id = this.views_indexes[index];
640
            }
641
            return view_id;
642
        },
643
        
644
        // switch to current view pane
645
        // if differs from the visible one
646
        show_view_pane: function() {
647
            if (this.current_view.pane != this.current_pane) {
648
                $(this.current_view.pane).show();
649
                $(this.current_pane).hide();
650
                this.current_pane = this.current_view.pane;
651
            }
652
        },
653

    
654
        show_view: function(view_id) {
655
            // same view, visible
656
            // get out of here asap
657
            if (this.current_view && 
658
                this.current_view.id == view_id && 
659
                this.current_view.visible()) {
660
                return;
661
            }
662
            
663
            // choose proper view_id
664
            view_id = this.identify_view(view_id);
665

    
666
            // add/create view and update current view
667
            var view = this.add_view(view_id);
668
            
669
            // set current view
670
            this.current_view = view;
671
            this.current_view_id = view_id;
672

    
673
            // hide all other views
674
            this.hide_views([this.current_view]);
675
            
676
            // FIXME: depricated
677
            $(".large-spinner").remove();
678

    
679
            storage.vms.reset_pending_actions();
680
            storage.vms.reset_stats_update();
681

    
682
            // show current view
683
            this.show_view_pane();
684
            view.show();
685
            
686
            // update menus
687
            if (this.select_view) {
688
                this.select_view.update_layout();
689
            }
690
            this.current_view.__update_layout();
691

    
692
            // update cookies
693
            this.update_session();
694
            
695
            // machines view subnav
696
            if (this.current_view.vms_view) {
697
                $("#machines-pane").show();
698
            } else {
699
                $("#machines-pane").hide();
700
            }
701
            
702
            // fix footer position
703
            // TODO: move footer handlers in
704
            // main view (from home.html)
705
            if (window.positionFooter) {
706
                window.positionFooter();
707
            }
708
            
709
            // trigger view change event
710
            this.trigger("view:change", this.current_view.view_id);
711
            $(window).trigger("view:change");
712
            return view;
713
        },
714

    
715
        reset_vm_actions: function() {
716
        
717
        },
718
        
719
        // identify current view
720
        // based on views element visibility
721
        current_view_id: function() {
722
            var found = false;
723
            _.each(this.views, function(key, value) {
724
                if (value.visible()) {
725
                    found = value;
726
                }
727
            })
728
            return found;
729
        }
730

    
731
    });
732

    
733
    snf.ui.main = new views.MainView();
734
    
735
    snf.ui.logout = function() {
736
        $.cookie("X-Auth-Token", null);
737
        if (window.LOGOUT_REDIRECT !== undefined)
738
        {
739
            window.location = window.LOGOUT_REDIRECT;
740
        } else {
741
            window.location.reload();
742
        }
743
    }
744

    
745
    snf.ui.init = function() {
746
        snf.ui.main.load();
747
    }
748

    
749
})(this);