Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (41.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
            var public_ips = vm.get_public_ips();
108
            _.each(public_ips, function(ip) {
109
              data += "\nPublic IP{0}: {1}".format(ip.get('type'), ip.get('ip_address'));
110
            });
111
            data += "\n\n";
112
            views.SuspendedVMView.__super__.show.call(this, data, collect_data, extra_data, cb);
113
        }
114

    
115
    });
116

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
231

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
409
            return conf.cont;
410
        },
411

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

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

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

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

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

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

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

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

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

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

    
528
        }
529
    });
530

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

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

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

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

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

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

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

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

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

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

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

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

    
606
            this.focused = true;
607
        },
608

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

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

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

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

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

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

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

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

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

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

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

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

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

    
771
        },
772

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
904
            this.init_menu();
905
        },
906

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

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

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

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

    
947
        set_vm_view_handlers: function() {
948
            var self = this;
949
            $("#createcontainer #create").click(function(e){
950
                e.preventDefault();
951
                if ($(this).hasClass("disabled")) { return }
952
                self.router.vm_create_view();
953
            });
954
        },
955

    
956
        check_empty: function() {
957
            if (!this.loaded) { return }
958
            if (storage.vms.length == 0) {
959
                this.show_view("machines");
960
                this.router.show_welcome();
961
                this.empty_hidden = false;
962
            } else {
963
                this.hide_empty();
964
            }
965
        },
966

    
967
        show_empty: function() {
968
            if (!this.empty_hidden) { return };
969
            $("#machines-pane-top").addClass("empty");
970

    
971
            this.$(".panes").hide();
972
            this.$("#machines-pane").show();
973

    
974
            this.hide_views([]);
975
            this.empty_hidden = false;
976
            this.empty_view.show();
977
            this.select_view.update_layout();
978
            this.empty_hidden = false;
979
        },
980

    
981
        hide_empty: function() {
982
            if (this.empty_hidden) { return };
983
            $("#machines-pane-top").removeClass("empty");
984

    
985
            this.empty_view.hide(true);
986
            this.router.vms_index();
987
            this.empty_hidden = true;
988
            this.select_view.update_layout();
989
        },
990
        
991
        get_title: function(view_id) {
992
            var view_id = view_id || this.current_view_id;
993
            return this.views_titles[view_id];
994
        },
995

    
996
        // return class object for the given view or false if
997
        // the view is not registered
998
        get_class_for_view: function (view_id) {
999
            if (!this.views_classes[view_id]) {
1000
                return false;
1001
            }
1002
            return this.views_classes[view_id];
1003
        },
1004

    
1005
        view: function(view_id) {
1006
            return this.views[view_id];
1007
        },
1008

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

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

    
1057
        pane_view_from_session: function() {
1058
            return $.cookie("pane_view") || 0;
1059
        },
1060

    
1061
        machine_view_from_session: function() {
1062
            return $.cookie("machine_view") || 0;
1063
        },
1064

    
1065
        update_session: function() {
1066
            $.cookie("pane_view", this.pane_ids[this.current_view_id]);
1067
            if (this.current_view.vms_view) {
1068
                $.cookie("machine_view", this.views_ids[this.current_view_id]);
1069
            }
1070
        },
1071

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

    
1100
        _show_view: function(view_id) {
1101
                // same view, visible
1102
                // get out of here asap
1103
                if (this.current_view && 
1104
                    this.current_view.id == view_id && 
1105
                    this.current_view.visible()) {
1106
                    return;
1107
                }
1108
                
1109
                // choose proper view_id
1110
                view_id = this.identify_view(view_id);
1111

    
1112
                // add/create view and update current view
1113
                var view = this.add_view(view_id);
1114
                
1115
                // set current view
1116
                this.current_view = view;
1117
                this.current_view_id = view_id;
1118

    
1119
                // hide all other views
1120
                this.hide_views([this.current_view]);
1121
                
1122
                // FIXME: depricated
1123
                $(".large-spinner").remove();
1124

    
1125
                storage.vms.reset_pending_actions();
1126
                //storage.networks.reset_pending_actions();
1127
                storage.vms.stop_stats_update();
1128

    
1129
                // show current view
1130
                this.show_view_pane();
1131
                view.show(true);
1132
                
1133
                // update menus
1134
                if (this.select_view) {
1135
                    this.select_view.update_layout();
1136
                }
1137

    
1138
                var update_layout = this.current_view.__update_layout;
1139
                update_layout && update_layout.call(this.current_view);
1140

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

    
1158
                // trigger view change event
1159
                this.trigger("view:change", this.current_view.view_id);
1160
                this.select_view.title.text(this.get_title());
1161
                $(window).trigger("view:change");
1162
                return view;
1163
        },
1164

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

    
1181
    });
1182

    
1183
    snf.ui.main = new views.MainView();
1184
    
1185
    snf.ui.init = function() {
1186
        if (snf.config.handle_window_exceptions) {
1187
            window.onerror = function(msg, file, line) {
1188
                snf.ui.trigger_error("CRITICAL", msg, {}, { file:file + ":" + line, allow_close: true });
1189
            };
1190
        }
1191
        snf.ui.main.load();
1192
    }
1193

    
1194
})(this);