Statistics
| Branch: | Tag: | Revision:

root / ui / static / snf / js / ui / web / ui_public_keys_view.js @ 47276ec2

History | View | Annotate | Download (19.8 kB)

1
;(function(root){
2

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

    
14
    var views = snf.views = snf.views || {}
15

    
16
    // shortcuts
17
    var bb = root.Backbone;
18
    
19
    views.CollectionView = views.View.extend({
20

    
21
        collection: undefined,
22
        
23
        id_tpl: 'model-item-{0}',
24
        list_tpl: '#model-item-tpl',
25
        
26
        force_reset_before_fetch: true,
27
        auto_append_actions: true,
28

    
29
        initialize: function(options) {
30
            views.CollectionView.__super__.initialize.apply(this, arguments);
31
            _.bindAll(this);
32

    
33
            this.loading = this.$(".loading-models");
34
            this.content = this.$(".content");
35
            this.list = this.$(".model-list .items-list");
36
            this.list_cont = this.$(".model-list");
37
            this.form = this.$(".model-form");
38
            this.empty_msg = this.$(".items-empty-msg");
39
            
40
            this.init_collection_handlers();
41
            this.init_handlers();
42
            this.close_form();
43
            this.content.hide();
44
            this.loading.show();
45
            this.update_models();
46
            this.items = [];
47
            this.submiting = false;
48
        },
49
        
50
        update_models: function() {
51
            this.collection.fetch({success:this.handle_reset});
52
        },
53
        
54
        init_handlers: function() {
55
            this.$(".add-new").click(_.bind(this.show_form, this, undefined));
56

    
57
            this.form.find(".form-action.cancel").click(this.close_form);
58
            this.form.find(".form-action.submit").click(this.submit_form);
59
            
60
            var self = this;
61
            this.form.find("form").submit(function(e){
62
                e.preventDefault();
63
                self.submit_form();
64
                return false;
65
            });
66

    
67
            this.$(".quick-add").click(_.bind(this.show_form, this, undefined));
68
        },
69

    
70
        init_collection_handlers: function() {
71
            this.collection.bind("reset", this.handle_reset);
72
        },
73

    
74
        reset_handlers: function() {
75
            this.collection.unbind("reset", this.handle_reset);
76
        },
77
            
78
        set_items: function(models) {
79
            this.items = _.map(models, function(m) { return m.id });
80
        },
81

    
82
        handle_reset: function(collection, models) {
83
            this.loading.hide();
84
            this.content.show();
85

    
86
            if (this.force_reset_before_update) {
87
                this.reset_list();
88
            }
89

    
90
            this.update_list(this.collection.models);
91
            this.update_removed(this.items, this.collection.models);
92

    
93
            this.set_items(this.collection.models);
94
        },
95

    
96
        show_form: function(model) {
97
            var create = (model === undefined ? true : false);
98
        
99
            if (create) {
100
                this.form.find(".new-title").show();
101
                this.form.find(".edit-title").hide();
102
            } else {
103
                this.form.find(".new-title").hide();
104
                this.form.find(".edit-title").show();
105
            }
106

    
107
            var model = model || new this.collection.model();
108
            this.list_cont.hide();
109

    
110
            this.reset_form(model);
111
            if (!snf.util.canReadFile()) {
112
                this.form.find(".fromfile-field").hide();
113
            }
114
            this.form.show();
115

    
116
            this.editing = true;
117
            this.editing_id = model.id;
118
            this.creating = create ? true : false;
119

    
120
            $(this.form.find("input").get(0)).focus();
121
            this.reset_form_errors();
122
        },
123

    
124
        reset_form: function(model) {
125
            if (!model) {
126
                this.form.find("input, textarea").val("");
127
                return;
128
            }
129

    
130
            this.update_form_from_model(model);
131
        },
132
        
133
        show_list_msg: function(type, msg) {
134
            this.list_messages = this.list_messages || this.$(".list-messages");
135
            var el = $('<div class="{0}">{1}</div>'.format(type, msg));
136
            this.list_messages.append(el);
137
            window.setTimeout(function(){
138
                el.fadeOut(300).delay(300).remove();
139
            }, this.message_timeout || 4000)
140
        },
141
        
142
        get_fields_map: function() {
143
            return {};
144
        },
145
        
146
        reset_form_errors: function() {
147
            this.form.find(".form-field").removeClass("error");
148
            this.form.find(".form-field .errors").empty();
149
        },
150

    
151
        show_form_errors: function(errors) {
152
            this.reset_form_errors();
153
            var fields_map = this.get_fields_map();
154
            this.form_messages = this.form_messages || this.$(".form-messages");
155
            
156
            _.each(errors.errors, _.bind(function(error, key){
157
                var field = this.form.find(fields_map[key]).closest(".form-field");
158
                field.addClass("error");
159
                _.each(error, function(error_msg) {
160
                    var error_el = $('<div class="error">{0}</div>'.format(error_msg));
161
                    field.find(".errors").append(error_el);
162
                });
163
            }, this));
164
            //var el = $('<div class="error">{1}</div>'.format(type, msg));
165
            //this.list_messages.append(el);
166
        },
167

    
168
        clean_form_errors: function() {
169
        },
170
        
171
        submit_form: function() {
172
            if (this.submiting) { return };
173
            var errlist = this.validate_data(this.get_form_data());
174
            if (errlist.empty()) {
175
                this.save_model(this.get_form_data());
176
            } else {
177
                this.show_form_errors(errlist);
178
            }
179
        },
180

    
181
        close_form: function() {
182
            this.editing = false;
183
            this.editing_id = undefined;
184
            this.creating = false;
185

    
186
            this.form.hide();
187
            this.list_cont.show();
188
            this.list_cont.find("h3").show();
189
        },
190

    
191
        create_model_element: function(model) {
192
            var el = this.$(this.list_tpl).clone();
193

    
194
            el.removeClass("hidden");
195
            el.addClass("model-item");
196
            el.attr("id", "item-content-" + this.id_tpl.format(model.id));
197

    
198
            if (this.auto_append_actions) {
199
                this.append_actions(el, model);
200
            }
201

    
202
            return el;
203
        },
204

    
205
        append_actions: function(el, model) {
206
            var actions = $('<div class="item-actions">' +
207
                            '<div class="item-action remove">remove</div>' + 
208
                            '<div class="item-action confirm-remove confirm">' +
209
                            '<span class="text">confirm</span>' + 
210
                            '<span class="cancel-remove cancel">cancel</span></div>' + 
211
                            '<div class="item-action edit">edit</div>' +
212
                            '</div>');
213
            el.append(actions);
214
        },
215

    
216
        bind_list_item_actions: function(el, model) {
217
            el.find(".item-actions .edit").click(_.bind(this.show_form, this, model));
218
            el.find(".item-actions .remove").click(_.bind(this.show_confirm_remove, this, el, model));
219
            el.find(".item-actions .confirm-remove .do-confirm").click(_.bind(this.delete_model, this, model)).hide();
220
            el.find(".item-actions .confirm-remove .cancel-remove").click(_.bind(this.cancel_confirm_remove, 
221
                                                                        this, el, model)).hide();
222
            
223
            // initialize download link
224
            snf.util.promptSaveFile(el.find(".item-actions .download"), model.get_filename(), model.get("content"))
225
        },
226

    
227
        show_confirm_remove: function(el, model) {
228
            var confirmed = confirm("Are you sure you want to delete this key ?");
229
            if (confirmed) {
230
                this.delete_model(model);
231
            }
232
            //el.closest(".model-item").addClass("pending-delete");
233
        },
234

    
235
        cancel_confirm_remove: function(el, model) {
236
            el.closest(".model-item").removeClass("pending-delete");
237
        },
238

    
239
        new_list_el: function(model) {
240
            var list_el = $("<li></li>");
241
            el = this.create_model_element(model);
242
            list_el.attr("id", this.id_tpl.format(model.id));
243
            list_el.addClass("model-item");
244
            this.update_list_item(el, model, true);
245
            list_el.append(el);
246
            this.bind_list_item_actions(list_el, model);
247
            return list_el;
248
        },
249

    
250
        item_el: function(id) {
251
            return this.$("#" + this.id_tpl.format(id));
252
        },
253

    
254
        item_exists: function(model) {
255
            return this.item_el(model.id).length > 0;
256
        },
257

    
258
        reset_list: function() {
259
            this.list.find("model-item").remove();
260
        },
261
        
262
        save_model: function(data) {
263
            this.form.find("form-action.submit").addClass("in-progress");
264
            this.submiting = true;
265

    
266
            var options = {
267
                success: _.bind(function(){
268
                    this.update_models();
269
                    this.close_form();
270
                    this.show_list_msg("success", "Public key created");
271
                }, this),
272

    
273
                error: _.bind(function(){
274
                    this.show_form_errors({'':'Public key submition failed'})
275
                }, this),
276

    
277
                complete: _.bind(function(){
278
                    this.submiting = false;
279
                    this.form.find("form-action.submit").addClass("in-progress");
280
                }, this)
281
            }
282

    
283
            if (this.editing_id && this.collection.get(this.editing_id)) {
284
                var model = this.collection.get(this.editing_id);
285
                model.save(data, options);
286
            } else {
287
                this.collection.create(data, options);
288
            }
289
        },
290

    
291
        delete_model: function(model) {
292
            this.item_el(model.id).addClass("in-progress").addClass("deleting");
293
            var self = this;
294
            model.destroy({success:this.update_models, error: function() {
295
                    self.show_list_msg("error", "Remove failed");
296
                    self.item_el(model.id).removeClass("in-progress").removeClass("deleting");
297
                }});
298
        },
299
        
300
        update_removed: function(ids, models) {
301
            var newids = _.map(models, function(m) { return m.id });
302
            _.each(_.difference(ids, newids), _.bind(function(id){
303
                this.item_el(id).remove();
304
            }, this));
305
        },
306

    
307
        update_list: function(models, reset) {
308
            var reset = reset || false;
309
            if (reset) { this.reset_list() };
310
            
311
            // handle removed items
312
            _.each(models, _.bind(function(model) {
313
                if (this.item_exists(model)) { 
314
                    this.update_list_item(this.item_el(model.id), model);
315
                    return;
316
                };
317
                this.list.append(this.new_list_el(model))
318
            }, this));
319

    
320
            this.check_empty();
321
        },
322

    
323
        check_empty: function() {
324
            if (this.collection.length == 0) {
325
                this.empty_msg.show();
326
                this.list.find(".header").hide();
327
                this.el.addClass("empty");
328
            } else {
329
                this.empty_msg.hide();
330
                this.list.find(".header").show();
331
                this.el.removeClass("empty");
332
            }
333
        },
334

    
335
        reset: function (){}
336

    
337
    });
338

    
339
    views.PublicKeysView = views.CollectionView.extend({
340
        collection: storage.keys,
341

    
342
        initialize: function(options) {
343
            views.PublicKeysView.__super__.initialize.apply(this, arguments);
344
            this.$(".private-cont").hide();
345
            _.bindAll(this);
346
        },
347

    
348
        append_actions: function(el, model) {
349
            var actions = $('<div class="item-actions">' +
350
                            '<div class="item-action remove">remove</div>' + 
351
                            '<div class="item-action confirm-remove">' + 
352
                            '<span class="text do-confirm">confirm</span>' + 
353
                            '<span class="cancel-remove cancel">X</span></div>' + 
354
                            '<div class="item-action edit">edit</div>' + 
355
                            '<div class="item-action show">show key</div>' + 
356
                            '</div>');
357
            el.append(actions);
358
        },
359

    
360
        init_handlers: function() {
361
            views.PublicKeysView.__super__.init_handlers.apply(this, arguments);
362
            
363
            this.$(".add-generate").click(_.bind(this.generate_new, this, undefined));
364

    
365
            // browser compat check
366
            if (snf.util.canReadFile()) {
367
                var self = this;
368
                this.form.find(".fromfile").get(0).addEventListener("change", function(e){
369
                    var f = undefined;
370
                    var files = e.target.files;
371
                    if (files.length == 0) { return };
372

    
373
                    f = files[0];
374
                    var data = snf.util.readFileContents(f, _.bind(function(data) {
375
                        this.form.find("textarea").val(data);   
376
                    }, self));
377
                });
378
            }
379

    
380
            var self = this;
381
            this.$(".private-cont .close-private").live("click", function(e) {
382
                self.$(".private-cont").hide();
383
                self.$(".private-cont textarea").val("");   
384
            });
385

    
386
            this.$(".item-action.show, .item-action.hide").live("click", function(e) {
387
                var open = $(this).parent().parent().parent().hasClass("expanded");
388
                if (open) {
389
                    $(this).text("show key");
390
                    $(this).addClass("show").removeClass("hide");
391
                } else {
392
                    $(this).text("hide key");
393
                    $(this).removeClass("show").addClass("hide");
394
                }
395
                $(this).parent().parent().parent().toggleClass("expanded");
396
            });
397
        },
398
        
399
        __generate_new: function(generate_text) {
400
            var key = storage.keys.generate_new();
401
            var self = this;
402

    
403
            storage.keys.add_crypto_key(key,
404
                _.bind(function(instance, data) {
405
                    self.update_models();
406
                    this.generating = false;
407
                    this.$(".add-generate").text(generate_text).removeClass(
408
                        "in-progress").addClass("download");
409
                    this.show_download_private(key, instance);
410
                },this),
411

    
412
                _.bind(function() {
413
                    self.show_list_msg("error", "Cannot generate public key, please try again later.");
414
                    
415
                    this.generating = false;
416
                    this.download_private = false;
417

    
418
                    this.$(".add-generate").text(generate_text).removeClass("in-progress").removeClass("download");
419
                }, this)
420
            );
421
        },
422

    
423
        generate_new: function() {
424
            if (this.generating) { return false };
425
            this.$(".private-cont").hide();
426
            this.generating = true;
427
            this.download_private = false;
428

    
429
            var generate_text = this.$(".add-generate").text();
430
            this.$(".add-generate").text("Generating...").addClass("in-progress").removeClass("download");
431
            
432
            window.setTimeout(_.bind(this.__generate_new, this, generate_text), 400);
433

    
434
        },
435
        
436
        show_download_private: function(key, data) {
437
            var download_cont = this.$(".private-cont");
438
            download_cont.find(".key-contents textarea").val("");
439
            download_cont.find(".private-msg, .down-button").hide();
440

    
441
            var pr = snf.util.promptSaveFile(download_cont.find(".down-button"), 
442
                                             "{0}_private.pem".format(data.get("name")), key.privatePEM());
443
            //pr = false;
444
            if (pr) {
445
                download_cont.find(".private-msg.download").show();
446
                download_cont.find(".down-button").show();
447
                download_cont.find(".key-contents textarea").val("").hide();
448
            } else {
449
                download_cont.find(".key-contents textarea").val(key.privatePEM()).show();
450
                download_cont.find(".private-msg.copy").show();
451
            }
452

    
453
            download_cont.show();
454
        },
455

    
456
        update_list_item: function(el, model) {
457
            el.find(".name").text(model.get("name"));
458
            el.find(".key-type").text(model.identify_type() || "unknown");
459
            el.find(".publicid .param-content textarea").val(model.get("content"));
460
            el.find(".publicid").attr("title", _(model.get("content")).truncate(1000, "..."));
461
            return el;
462
        },
463

    
464
        update_form_from_model: function(model) {
465
            this.form.find("input.input-name").val(model.get("name"));
466
            this.form.find("textarea.input-content").val(model.get("content"));
467
        },
468

    
469
        get_form_data: function() {
470
            return {
471
                'name': this.form.find("input.input-name").val(),
472
                'content': this.form.find("textarea.input-content").val()
473
            }
474
        },
475
        
476
        get_fields_map: function() {
477
            return {'name': "input.input-name", 'content': "textarea.input-content"};
478
        },
479
        
480
        validate_data: function(data) {
481
            var user_data = _.clone(data)
482
            var errors = new snf.util.errorList();
483

    
484
            if (!data.name || _.clean(data.name) == "") {
485
                errors.add("name", "Provide a valid public key name");
486
            }
487

    
488
            if (!data.content || _.clean(data.content) == "") {
489
                errors.add("content", "Provide valid public key content");
490
                return errors;
491
            }
492
            
493
            try {
494
                var content = snf.util.validatePublicKey(data.content);
495
                if (content) {
496
                    this.form.find("textarea.input-content").val(content);
497
                }
498
            } catch (err) {
499
                errors.add("content", "Invalid key content (" + err + ")");
500
            }
501

    
502
            return errors;
503
        },
504

    
505
        reset: function() {
506
            this.$(".private-cont").hide();
507
            this.$(".list-messages").empty();
508
            this.$(".form-messages").empty();
509
            this.$(".model-item").removeClass("expanded");
510
            this.close_form();
511
        }
512

    
513
    })
514

    
515
    views.PublicKeysOverlay = views.Overlay.extend({
516
        
517
        view_id: "public_keys_view",
518
        content_selector: "#user_public_keys",
519
        css_class: 'overlay-public-keys overlay-info',
520
        overlay_id: "user_public_keys_overlay",
521

    
522
        title: "Manage your ssh keys",
523
        subtitle: "SSH keys",
524

    
525
        initialize: function(options) {
526
            views.PublicKeysOverlay.__super__.initialize.apply(this, arguments);
527
            this.subview = new views.PublicKeysView({el:this.$(".public-keys-view")});
528
            
529
            var self = this;
530
            this.$(".previous-view-link").live('click', function(){
531
                self.hide();
532
            })
533
        },
534

    
535
        show: function(view) {
536
            this.from_view = view || undefined;
537
            
538
            if (this.from_view) {
539
                this.$(".previous-view-link").show();
540
            } else {
541
                this.$(".previous-view-link").hide();
542
            }
543

    
544
            this.subview.reset();
545
            views.PublicKeysOverlay.__super__.show.apply(this, arguments);
546
        },
547
        
548
        onClose: function() {
549
            if (this.from_view) {
550
                this.hiding = true;
551
                this.from_view.skip_reset_on_next_open = true;
552
                this.from_view.show();
553
                this.from_view = undefined;
554
            }
555
        },
556

    
557
        init_handlers: function() {
558
        }
559
        
560
    });
561
})(this);
562

    
563