Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (41.8 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.$(".form-field label")[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.create_snapshot_view = new views.CreateSnapshotView();
700
            this.api_info_view = new views.ApiInfoView();
701
            this.details_view = new views.DetailsView();
702
            this.suspended_view = new views.SuspendedVMView();
703
            //this.notice_view = new views.NoticeView();
704
        },
705
        
706
        show_loading_view: function() {
707
            $("#container #content").hide();
708
            $("#loading-view").show();
709
        },
710

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

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

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

    
773
        },
774

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

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

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

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

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

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

    
830
            this.check_empty();
831
            this.show_initial_view();
832
        },
833

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

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

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

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

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

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

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

    
906
            this.init_menu();
907
        },
908

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

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

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

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

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

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

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

    
973
            this.$(".panes").hide();
974
            this.$("#machines-pane").show();
975

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1127
                storage.vms.reset_pending_actions();
1128
                //storage.networks.reset_pending_actions();
1129
                storage.vms.stop_stats_update();
1130

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

    
1140
                var update_layout = this.current_view.__update_layout;
1141
                update_layout && update_layout.call(this.current_view);
1142

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

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

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

    
1183
    });
1184

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

    
1196
})(this);