Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / ui / static / snf / js / utils.js @ 198b546d

History | View | Annotate | Download (21.2 kB)

1
// Copyright 2014 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
    var root = root;
38
    var snf = root.synnefo = root.synnefo || {};
39
    
40
    snf.i18n = {};
41

    
42
    // Logging namespace
43
    var logging = snf.logging = snf.logging || {};
44

    
45
    // logger object
46
    var logger = logging.logger = function(ns, level){
47
        var levels = ["debug", "info", "error"];
48
        var con = window.console;
49
        
50
        this.level = level || synnefo.logging.level;
51
        this.ns = ns || "";
52

    
53
        this._log = function(lvl) {
54
            if (lvl >= this.level && con) {
55
                var args = Array.prototype.slice.call(arguments[1]);
56
                var level_name = levels[lvl];
57
                    
58
                if (this.ns) {
59
                    args = ["["+this.ns+"] "].concat(args);
60
                }
61

    
62
                log = con.log
63
                if (con[level_name])
64
                    log = con[level_name]
65

    
66
                try {
67
                    con && log.apply(con, Array.prototype.slice.call(args));
68
                } catch (err) {}
69
            }
70
        }
71

    
72
        this.debug = function() {
73
            var args = [0]; args.push.call(args, arguments);
74
            this._log.apply(this, args);
75
        }
76

    
77
        this.info = function() {
78
            var args = [1]; args.push.call(args, arguments);
79
            this._log.apply(this, args);
80
        }
81

    
82
        this.error = function() {
83
            var args = [2]; args.push.call(args, arguments);
84
            this._log.apply(this, args);
85
        }
86

    
87
    };
88
    
89
    synnefo.collect_user_data = function() {
90
        var data = {}
91
        
92
        try {
93
            data.client = {'browser': $.browser, 'screen': $.extend({}, screen), 'client': $.client}
94
        } catch (err) { data.client = err }
95
        try {
96
            data.calls = synnefo.api.requests;
97
        } catch (err) { data.calls = err }
98
        try {
99
            data.errors = synnefo.api.errors;
100
        } catch (err) { data.errors = err }
101
        try {
102
            data.data = {};
103
        } catch (err) { data.data = err }
104
        try {
105
            data.data.vms = synnefo.storage.vms.toJSON();
106
        } catch (err) { data.data.vms = err }
107
        try {
108
            data.data.networks = synnefo.storage.networks.toJSON();
109
        } catch (err) { data.data.networks = err }
110
        //try {
111
            //data.data.images = synnefo.storage.images.toJSON();
112
        //} catch (err) { data.data.images = err }
113
        //try {
114
            //data.data.flavors = synnefo.storage.flavors.toJSON();
115
        //} catch (err) { data.data.flavors = err }
116
        try {
117
            data.date = new Date;
118
        } catch (err) { data.date = err }
119

    
120
        return data;
121
    }
122

    
123
    // default logger level (debug)
124
    synnefo.logging.level = 0;
125

    
126
    // generic logger
127
    synnefo.log = new logger({'ns':'SNF'});
128

    
129
    // synnefo config options
130
    synnefo.config = synnefo.config || {};
131
    synnefo.config.api_url = "/api/v1.1";
132
    
133
    // Util namespace
134
    synnefo.util = synnefo.util || {};
135
    
136
    synnefo.util.FormatDigits = function(num, length) {
137
        var r = "" + num;
138
        while (r.length < length) {
139
            r = "0" + r;
140
        }
141
        return r;
142
    }
143

    
144
    synnefo.util.formatDate = function(d) {
145
        var dt = synnefo.util.FormatDigits(d.getDate()) + '/';
146
        dt += synnefo.util.FormatDigits(d.getMonth() + 1, 2);
147
        dt += '/' + d.getFullYear();
148
        dt += ' ' + synnefo.util.FormatDigits(d.getHours(), 2) + ':';
149
        dt += synnefo.util.FormatDigits(d.getMinutes(), 2) + ':';
150
        dt += synnefo.util.FormatDigits(d.getSeconds(), 2);
151
        return dt;
152
    },
153

    
154
    // Extensions and Utility functions
155
    synnefo.util.ISODateString = function(d){
156
        function pad(n){
157
            return n<10 ? '0'+n : n
158
        }
159
         return d.getUTCFullYear()+'-'
160
         + pad(d.getUTCMonth()+1)+'-'
161
         + pad(d.getUTCDate())+'T'
162
         + pad(d.getUTCHours())+':'
163
         + pad(d.getUTCMinutes())+':'
164
         + pad(d.getUTCSeconds())+'Z'
165
    }
166

    
167
    
168
    synnefo.util.parseHeaders = function(headers) {
169
        var res = {};
170
        _.each(headers.split("\n"), function(h) {
171
            var tuple = h.split(/:(.+)?/);
172
            if (!tuple.length > 1 || !(tuple[0] && tuple[1])) {
173
                return;
174
            }
175
            res[tuple[0]] = tuple[1]
176
        })
177

    
178
        return res;
179
    }
180

    
181
    synnefo.util.parseUri = function(sourceUri) {
182
        var uriPartNames = ["source","protocol","authority","domain","port","path","directoryPath","fileName","query","anchor"];
183
        var uriParts = new RegExp("^(?:([^:/?#.]+):)?(?://)?(([^:/?#]*)(?::(\\d*))?)?((/(?:[^?#](?![^?#/]*\\.[^?#/.]+(?:[\\?#]|$)))*/?)?([^?#/]*))?(?:\\?([^#]*))?(?:#(.*))?").exec(sourceUri);
184
        var uri = {};
185
        
186
        for(var i = 0; i < 10; i++){
187
            uri[uriPartNames[i]] = (uriParts[i] ? uriParts[i] : "");
188
        }
189
    
190
        // Always end directoryPath with a trailing backslash if a path was present in the source URI
191
        // Note that a trailing backslash is NOT automatically inserted within or appended to the "path" key
192
        if(uri.directoryPath.length > 0){
193
            uri.directoryPath = uri.directoryPath.replace(/\/?$/, "/");
194
        }
195
        
196
        return uri;
197
    }
198

    
199
    synnefo.util.equalHeights = function() {
200
        var max_height = 0;
201
        var selectors = _.toArray(arguments);
202
            
203
        _.each(selectors, function(s){
204
            console.log($(s).height());
205
        })
206
        // TODO: implement me
207
    }
208

    
209
    synnefo.util.ClipHelper = function(wrapper, text, settings) {
210
        settings = settings || {};
211
        this.el = $('<div class="clip-copy"></div>');
212
        wrapper.append(this.el);
213
        this.clip = $(this.el).zclip(_.extend({
214
            path: synnefo.config.js_url + "lib/ZeroClipboard.swf",
215
            copy: text
216
        }, settings));
217
    }
218

    
219
    synnefo.util.truncate = function(string, size, append, words) {
220
        if (string === undefined) { return "" };
221
        if (string.length <= size) {
222
            return string;
223
        }
224

    
225
        if (append === undefined) {
226
            append = "...";
227
        }
228
        
229
        if (!append) { append = "" };
230
        // TODO: implement word truncate
231
        if (words === undefined) {
232
            words = false;
233
        }
234
        
235
        len = size - append.length;
236
        return string.substring(0, len) + append;
237
    }
238

    
239
    synnefo.util.readablizeBytes = function(bytes, fix) {
240
        if (parseInt(bytes) == 0) { return '0 bytes' }
241
        if (fix === undefined) { fix = 2; }
242
        var s = ['bytes', 'kb', 'MB', 'GB', 'TB', 'PB'];
243
        var e = Math.floor(Math.log(bytes)/Math.log(1024));
244
        return (bytes/Math.pow(1024, Math.floor(e))).toFixed(fix)+" "+s[e];
245
    }
246
    
247

    
248
    synnefo.util.IP_REGEX = /(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\/([0-9]|[1-2][0-9]|3[0-2]?)$/
249

    
250
    synnefo.i18n.API_ERROR_MESSAGES = {
251
        'timeout': {
252
            'title': 'API error',
253
            'message': 'TIMEOUT', 
254
            'allow_report': false,
255
            'type': 'Network'
256
        },
257
        
258
        'error': {
259
            'title': 'API error',
260
            'message': null
261
        }, 
262

    
263
        'abort': {},
264
        'parserror': {},
265
        '413': {
266
            'title': "Account warning"
267
        }
268
    }
269
    
270
    synnefo.util.array_diff = function(arr1, arr2) {
271
        var removed = [];
272
        var added = [];
273

    
274
        _.each(arr1, function(v) {
275
            if (arr2.indexOf(v) == -1) {
276
                removed[removed.length] = v;
277
            }
278
        })
279

    
280

    
281
        _.each(arr2, function(v) {
282
            if (arr1.indexOf(v) == -1) {
283
                added[added.length] = v;
284
            }
285
        })
286

    
287
        return {del: removed, add: added};
288
    }
289

    
290
    synnefo.util.open_window = function(url, name, opts) {
291
        // default specs
292
        opts = _.extend({
293
            menubar: 'no',
294
            toolbar: 'no',
295
            status: 'no',
296
            height: screen.height,
297
            width: screen.width,
298
            fullscreen: 'yes',
299
            channelmode: 'yes',
300
            directories: 'no',
301
            left: 0,
302
            location: 'no',
303
            top: 0
304
        }, opts)
305
        
306
        var specs = _.map(opts, function(v,k) {return k + "=" + v}).join(",");
307
        window.open(url, name, specs);
308
    }
309
    
310
    synnefo.util.readFileContents = function(f, cb) {
311
        var reader = new FileReader();
312
        var start = 0;
313
        var stop = f.size - 1;
314

    
315
        reader.onloadend = function(e) {
316
            return cb(e.target.result);
317
        }
318
        
319
        var data = reader.readAsText(f);
320
    },
321
    
322
    synnefo.util.generateKey = function(passphrase, length) {
323
        var passphrase = passphrase || "";
324
        var length = length || 1024;
325
        var key = cryptico.generateRSAKey(passphrase, length);
326

    
327
        _.extend(key.prototype, {
328
            download: function() {
329
            }
330
        });
331

    
332
        return key;
333
    }
334
    
335
    synnefo.util.publicKeyTypesMap = {
336
        "ecdsa-sha2-nistp256": "ecdsa",
337
        "ssh-dss" : "dsa",
338
        "ssh-rsa": "rsa"
339
    }
340

    
341
    synnefo.util.validatePublicKey = function(key) {
342
        var b64 = _(key).trim().split("\n").join("").split("\r\n").join("");
343
        var type = "rsa";
344

    
345
        // in case key starts with something like ssh-rsa
346
        if (b64.split(" ").length > 1) {
347
            var parts = key.split(" ");
348
            
349
            // identify key type
350
            type_key = parts[0];
351
            if (parseInt(type_key) >= 768) {
352
                type = "rsa1";
353
                
354
                if (parts[1] == 65537) {
355
                    if (parts.length == 3) {
356
                        return [parts[0], parts[1], parts[2]].join(" ")
357
                    }
358
                }
359
                // invalid rsa1 key
360
                throw "Invalid rsa1 key";
361
            }
362
            
363
            b64 = parts[1];
364
            if (!synnefo.util.publicKeyTypesMap[type_key]) { throw "Invalid rsa key (cannot identify encryption)" }
365

    
366
            try {
367
                var data = $.base64.decode(b64);
368
                return [parts[0], parts[1]].join(" ");
369
            } catch (err) {
370
                throw "Invalid key content";
371
            }
372

    
373
            throw "Invalid key content";
374
        }
375
        
376
        // no type defined check rsa
377
        if (_(b64).startsWith("AAAAB3NzaC1yc2EA")) {
378
            try {
379
                var data = $.base64.decode(b64);
380
                return ["ssh-rsa", b64].join(" ");
381
            } catch (err) {
382
                throw "Invalid content for rsa key";
383
            }
384
        }
385

    
386
        if (_(b64).startsWith("AAAAE2Vj")) {
387
            try {
388
                var data = $.base64.decode(b64);
389
                return ["ecdsa-sha2-nistp256", b64].join(" ");
390
            } catch (err) {
391
                throw "Invalid content for ecdsa key";
392
            }
393
        }
394

    
395
        if (_(b64).startsWith("AAAAB3N")) {
396
            try {
397
                var data = $.base64.decode(b64);
398
                return ["ssh-dss", b64].join(" ");
399
            } catch (err) {
400
                throw "Invalid content for dss key (" + err + ")";
401
            }
402
        }
403

    
404
        throw "Invalid key content";
405
    }
406
    
407
    // detect flash `like a boss`
408
    // http://stackoverflow.com/questions/998245/how-can-i-detect-if-flash-is-installed-and-if-not-display-a-hidden-div-that-inf/3336320#3336320 
409
    synnefo.util.hasFlash = function() {
410
        var hasFlash = false;
411
        try {
412
            var fo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');
413
            if (fo) hasFlash = true;
414
        } catch(e) {
415
          if(navigator.mimeTypes ["application/x-shockwave-flash"] != undefined) hasFlash = true;
416
        }
417
        return hasFlash;
418
    }
419

    
420
    synnefo.util.promptSaveFile = function(selector, filename, data, options) {
421
        if (!synnefo.util.hasFlash()) { return };
422
        try {
423
            return $(selector).downloadify(_.extend({
424
                filename: function(){ return filename },
425
                data: function(){ return data },
426
                onComplete: function(){},
427
                onCancel: function(){},
428
                onError: function(){
429
                    console.log("ERROR", arguments);
430
                },
431
                swf: synnefo.config.media_url + 'js/lib/media/downloadify.swf',
432
                downloadImage: synnefo.config.images_url + 'download.png',
433
                transparent: true,
434
                append: false,
435
                height:20,
436
                width: 20,
437
                dataType: 'string'
438
          }, options));
439
        } catch (err) {
440
            return false;
441
        }
442
    }
443

    
444
    synnefo.util.canReadFile = function() {
445
        if ($.browser.msie) { return false };
446
        if (window.FileReader && window.File) {
447
            var f = File.prototype.__proto__;
448
            if (f.slice || f.webkitSlice || f.mozSlice) {
449
                return true
450
            }
451
        }
452
        return false;
453
    }
454

    
455
    synnefo.util.errorList = function() {
456
        
457
        this.initialize = function() {
458
            this.errors = {};
459
        }
460

    
461
        this.add = function(key, msg) {
462
            this.errors[key] = this.errors[key] || [];
463
            this.errors[key].push(msg);
464
        }
465

    
466
        this.get = function(key) {
467
            return this.errors[key];
468
        }
469

    
470
        this.empty = function() {
471
            return _.isEmpty(this.errors);
472
        }
473

    
474
        this.initialize();
475
    }
476

    
477
    synnefo.util.stacktrace = function() {
478
        try {
479
            var obj = {};
480
            if (window.Error && Error.captureStackTrace) {
481
                Error.captureStackTrace(obj, synnefo.util.stacktrace);
482
                return obj.stack;
483
            } else {
484
                return printStackTrace().join("<br /><br />");
485
            }
486
        } catch (err) {}
487
        return "";
488
    },
489
    
490
    synnefo.util.array_combinations = function(arr) {
491
        if (arr.length == 1) {
492
            return arr[0];
493
        } else {
494
            var result = [];
495

    
496
            // recur with the rest of array
497
            var allCasesOfRest = synnefo.util.array_combinations(arr.slice(1));  
498
            for (var i = 0; i < allCasesOfRest.length; i++) {
499
                for (var j = 0; j < arr[0].length; j++) {
500
                    result.push(arr[0][j] + "-" + allCasesOfRest[i]);
501
                }
502
            }
503
            return result;
504
        }
505
    }
506

    
507
    synnefo.util.parse_api_error = function() {
508
        if (arguments.length == 1) { arguments = arguments[0] };
509

    
510
        var xhr = arguments[0];
511
        var error_message = arguments[1];
512
        var error_thrown = arguments[2];
513
        var ajax_settings = _.last(arguments) || {};
514
        var call_settings = ajax_settings.error_params || {};
515
        var json_data = undefined;
516

    
517
        var critical = ajax_settings.critical === undefined ? true : ajax_settings.critical;
518

    
519
        if (xhr.responseText) {
520
            try {
521
                json_data = JSON.parse(xhr.responseText)
522
            } catch (err) {
523
                json_data = 'Raw error response contnent (could not parse as JSON):\n\n' + xhr.responseText;
524
            }
525
        }
526
        
527
        module = "API"
528

    
529
        try {
530
            path = synnefo.util.parseUri(ajax_settings.url).path.split("/");
531
            path.splice(0,3)
532
            module = path.join("/");
533
        } catch (err) {
534
            console.error("cannot identify api error module");
535
        }
536
        
537
        defaults = {
538
            'message': 'Api error',
539
            'type': 'API',
540
            'allow_report': true,
541
            'fatal_error': ajax_settings.critical || false,
542
            'non_critical': !critical
543
        }
544

    
545
        var code = -1;
546
        try {
547
            code = xhr.status || "undefined";
548
        } catch (err) {console.error(err);}
549
        var details = "";
550
        
551
        if ([413].indexOf(code) > -1) {
552
            defaults.non_critical = true;
553
            defaults.allow_report = false;
554
            defaults.allow_reload = false;
555
            error_message = "limit_error";
556
        }
557

    
558
        if (critical) {
559
            defaults.allow_report = true;
560
        }
561
        
562
        if (json_data) {
563
            if (_.isObject(json_data)) {
564
                $.each(json_data, function(key, obj) {
565
                    code = obj.code;
566
                    details = obj.details;
567
                    error_message = obj.message;
568
                })
569
            } else {
570
                details = json_data;
571
            }
572
        }
573

    
574
        extra = {'URL': ajax_settings.url};
575
        options = {};
576
        options = _.extend(options, {'details': details, 'message': error_message, 'ns': module, 'extra_details': extra});
577
        options = _.extend(options, call_settings);
578
        options = _.extend(options, synnefo.i18n.API_ERROR_MESSAGES[error_message] || {});
579
        options = _.extend(options, synnefo.i18n.API_ERROR_MESSAGES[code] || {});
580
        
581
        options.api_message = options.message;
582

    
583
        if (window.ERROR_OVERRIDES && window.ERROR_OVERRIDES[options.message]) {
584
            options.message = window.ERROR_OVERRIDES[options.message];
585
            options.api_message = '';
586
        }
587
        
588
        if (code && window.ERROR_OVERRIDES && window.ERROR_OVERRIDES[code]) {
589
            options.message = window.ERROR_OVERRIDES[code];
590
        }
591
        
592
        if (options.api_message == options.message) {
593
          options.api_message = '';
594
        }
595
        options = _.extend(defaults, options);
596
        options.code = code;
597

    
598
        return options;
599
    }
600

    
601

    
602
    // Backbone extensions
603
    //
604
    // super method
605
    Backbone.Model.prototype._super = Backbone.Collection.prototype._super = Backbone.View.prototype._super = function(funcName){
606
        return this.constructor.__super__[funcName].apply(this, _.rest(arguments));
607
    }
608

    
609
    // simple string format helper 
610
    // http://stackoverflow.com/questions/610406/javascript-equivalent-to-printf-string-format
611
    String.prototype.format = function() {
612
        var formatted = this;
613
        for (var i = 0; i < arguments.length; i++) {
614
            var regexp = new RegExp('\\{'+i+'\\}', 'gi');
615
            formatted = formatted.replace(regexp, arguments[i]);
616
        }
617
        return formatted;
618
    };
619

    
620

    
621
    $.fn.setCursorPosition = function(pos) {
622
        if ($(this).get(0).setSelectionRange) {
623
          $(this).get(0).setSelectionRange(pos, pos);
624
        } else if ($(this).get(0).createTextRange) {
625
          var range = $(this).get(0).createTextRange();
626
          range.collapse(true);
627
          range.moveEnd('character', pos);
628
          range.moveStart('character', pos);
629
          range.select();
630
        }
631
    }
632

    
633
    // trim prototype for IE
634
    if(typeof String.prototype.trim !== 'function') {
635
        String.prototype.trim = function() {
636
            return this.replace(/^\s+|\s+$/g, '');
637
        }
638
    }
639

    
640
    // http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area 
641
    $.fn.setCursorPosition = function(pos) {
642
        // not all browsers support setSelectionRange
643
        // put it in try/catch, fallback to no text selection
644
        try {
645
            if ($(this).get(0).setSelectionRange) {
646
              $(this).get(0).setSelectionRange(pos, pos);
647
            } else if ($(this).get(0).createTextRange) {
648
              var range = $(this).get(0).createTextRange();
649
              range.collapse(true);
650
              range.moveEnd('character', pos);
651
              range.moveStart('character', pos);
652
              range.select();
653
            }
654
        } catch (err) {
655
        }
656
    }
657

    
658
    // indexOf prototype for IE
659
    if (!Array.prototype.indexOf) {
660
      Array.prototype.indexOf = function(elt /*, from*/) {
661
        var len = this.length;
662
        var from = Number(arguments[1]) || 0;
663
        from = (from < 0)
664
             ? Math.ceil(from)
665
             : Math.floor(from);
666
        if (from < 0)
667
          from += len;
668

    
669
        for (; from < len; from++) {
670
          if (from in this &&
671
              this[from] === elt)
672
            return from;
673
        }
674
        return -1;
675
      };
676
    }
677

    
678
})(this);