(function() { // Get application context var app = Sammy.apps['#main']; var store = app.store; /** * Helpers * */ app.helpers({ // Serialize an object serialize : function(obj) { var str = []; for(var p in obj) if (obj.hasOwnProperty(p)) { str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); } return str.join("&"); }, // Flash helper to diplay instant notifications flash: function (level, message) { if (!store.get('flash')) { store.set('flash', true); } // Helper CSS class on main wrapper $('#slider').addClass('with-flashMessage'); // If the line is a bash command if (level === 'info' && message.charAt(0) === '+') { level = 'log'; } message = message.split("\n").join("
"); // If the message starts with a progress bar progressbar = message.match(/^\[#*\+*\.*\] > /); if (progressbar) { progressbar = progressbar[0]; // Remove the progress bar from the mesage message = message.replace(progressbar,""); // Compute percent done = (progressbar.match(/#/g)||[]).length; ongoing = (progressbar.match(/\+/g)||[]).length; remaining = (progressbar.match(/\./g)||[]).length; total = done + ongoing + remaining; done = done * 100 / total; ongoing = ongoing * 100 / total; // Actually build the message with the progress bar message = '

' + message + '

'; } else { message = '

'+message+'

'; } // Add message $('#flashMessage .messages') .prepend('
'+message+'
'); // Scroll to top to view new messages $('#flashMessage').scrollTop(0); }, checkInstall: function(callback) { // Get base url from store or guess from current url var baseUrl = (store.get('url') !== null) ? store.get('url') : window.location.hostname + '/yunohost/api'; // Call API endpoint $.ajax({ dataType: "json", url: 'https://'+ baseUrl +'/installed', timeout: 3000 }) .success(function(data) { callback(data.installed); }) .fail(function() { callback(undefined); }); }, // API call api: function(uri, callback, method, data, websocket, callbackOnFailure) { c = this; method = typeof method !== 'undefined' ? method : 'GET'; data = typeof data !== 'undefined' ? data : {}; if (window.navigator && window.navigator.language && (typeof data.locale === 'undefined')) { data.locale = y18n.locale || window.navigator.language.substr(0, 2); } app.loaded = false; if ($('div.loader').length === 0) { $('#main').append('
'); } call = function(uri, callback, method, data, callbackOnFailure) { var args = data; // TODO: change this code if (uri === '/postinstall') { var post_installing = false; setInterval(function () { post_installing = true; }, 1500); } if (typeof callbackOnFailure !== 'function') { callbackOnFailure = function(xhr) { // Postinstall is a custom case, we have to wait that // operation is done before doing anything if ((uri === '/postinstall') && (post_installing)) { interval = window.location.hostname === args.domain ? 20000 : 5000; checkInstall = setInterval(function () { c.checkInstall(function(isInstalled) { if (isInstalled || typeof isInstalled === 'undefined') { c.flash('success', y18n.t('installation_complete')); clearInterval(checkInstall); window.location.href = 'https://'+ window.location.hostname +'/yunohost/admin/'; } }); }, interval); } // Regular errors else { if (xhr.status == 200) { // Fail with 200, WTF callback({}); } // Unauthorized or wrong password else if (xhr.status == 401) { if (uri === '/login') { c.flash('fail', y18n.t('wrong_password')); } else { c.flash('fail', y18n.t('unauthorized')); c.redirect('#/login'); } } // 500 else if (xhr.status == 500) { try { error_log = JSON.parse(xhr.responseText); error_log.route = error_log.route.join(' ') + '\n'; error_log.arguments = JSON.stringify(error_log.arguments); } catch (e) { error_log = {}; error_log.route = "Failed to parse route"; error_log.arguments = "Failed to parse arguments"; error_log.traceback = xhr.responseText; } c.flash('fail', y18n.t('internal_exception', [error_log.route, error_log.arguments, error_log.traceback])); } // 502 Bad gateway means API is down else if (xhr.status == 502) { c.flash('fail', y18n.t('api_not_responding')); } // More verbose error messages first else if (typeof xhr.responseText !== 'undefined') { c.flash('fail', xhr.responseText); } // 0 mean "the connexion has been closed" apparently else if (xhr.status == 0) { var errorMessage = xhr.status+' '+xhr.statusText; c.flash('fail', y18n.t('error_connection_interrupted', [errorMessage])); console.log(xhr); } // Return HTTP error code at least else { var errorMessage = xhr.status+' '+xhr.statusText; c.flash('fail', y18n.t('error_server_unexpected', [errorMessage])); console.log(xhr); } // Remove loader if any $('div.loader').remove(); // Force scrollTop on page load $('html, body').scrollTop(0); store.clear('slide'); } }; } jQuery.ajax({ url: 'https://' + store.get('url') + uri, type: method, crossdomain: true, data: data, traditional: true, dataType: 'json' }) .always(function(xhr, ts, error) { }) .done(function(data) { data = data || {}; callback(data); }) .fail(callbackOnFailure); }; websocket = typeof websocket !== 'undefined' ? websocket : true; if (websocket) { // Open a WebSocket connection to retrieve live messages from the moulinette var ws = new WebSocket('wss://'+ store.get('url') +'/messages'); // Flag to avoid to call twice the API // We need to set that in ws object as we need to use it in ws.onopen // and several ws object could be running at the same time... ws.api_called = false; ws.onmessage = function(evt) { // console.log(evt.data); $.each($.parseJSON(evt.data), function(k, v) { c.flash(k, v); }); }; // If not connected, WebSocket connection will raise an error, but we do not want to interrupt API request ws.onerror = function () { ws.onopen(); }; ws.onclose = function() { }; ws.onopen = function () { if (!ws.api_called) { ws.api_called = true; call(uri, callback, method, data, callbackOnFailure); } }; } else { call(uri, callback, method, data, callbackOnFailure); } }, // Render view (cross-browser) view: function (view, data, callback, enableSlide) { c = this; // Default callback = typeof callback !== 'undefined' ? callback : function() {}; enableSlide = (typeof enableSlide !== 'undefined') ? enableSlide : true; // Change to false to disable animation app.loaded = true; // Hide loader and modal $('div.loader').remove(); $('#modal').modal('hide'); // Render content var rendered = this.render('views/'+ view +'.ms', data); // Update content helper var leSwap = function() { rendered.swap(function() { // Slide direction if (enableSlide) { $('.slide, .btn-breadcrumb a:not(:last-child)').on('click', function() { $(this).addClass('active'); if ($(this).hasClass('back') || $(this).parent('.btn-breadcrumb').length) { store.set('slide', 'back'); } else { store.set('slide', 'to'); } }); } // Paste
 helper
                    c.prePaste();

                    // Run callback
                    callback();

                    // Force scrollTop on page load
                    $('html, body').scrollTop(0);
                });
            };

            // Slide back effect
            if (enableSlide && store.get('slide') == 'back') {
                store.clear('slide');
                $('#slideBack').css('display', 'none');
                $('#slider-container').css('margin-left', '-100%');
                $('#slideTo').show().html($('#main').html());
                leSwap();
                $('#slider-container').css('margin-left', '0px');
            }
            // Slide to effect
            else if (enableSlide && store.get('slide') == 'to') {
                store.clear('slide');
                $('#slideTo').css('display', 'none');
                $('#slider-container').css('margin-left', '0px');
                $('#slideBack').show().html($('#main').html());
                leSwap();
                $('#slider-container').css('margin-left', '-100%');
            }
            // No slideing effect
            else {
                leSwap();
            }
        },

        confirm: function(title, content, confirmCallback, cancelCallback) {
            // Default callbacks
            confirmCallback = typeof confirmCallback !== 'undefined' ? confirmCallback : function() {};
            cancelCallback = typeof cancelCallback !== 'undefined' ? cancelCallback : function() {};

            // Get modal element
            var box = $('#modal');

            // Modal title
            if (typeof title === 'string' && title.length) {
                $('.title', box).html(title);
            }
            else {
                box.addClass('no-title');
            }

            // Modal content
            $('.content', box).html(content);

            // Clear any remaining click event that could still be there (e.g.
            // clicking outside the modal window doesn't equal to clicking
            // cancel...
            $('footer button', box).unbind( "click" );

            // Handle buttons
            $('footer button', box)
                .click(function(e){
                    e.preventDefault();

                    $('#modal footer button').unbind( "click" );
                    // Reset & Hide modal
                    box
                        .removeClass('no-title')
                        .modal('hide');

                    // Do corresponding callback
                    if ($(this).data('action') == 'confirm') {
                        confirmCallback();
                    }
                    else {
                        cancelCallback();
                    }
                });

            // Show modal
            return box.modal('show');
        },

        selectAllOrNone: function () {
          // Remove active style from buttons
          $(".select_all-none input").click(function(){ $(this).toggleClass("active"); });
          // Select all checkbox in this panel
          $(".select_all").click(function(){
            $(this).parents(".panel").children(".list-group").find("input").prop("checked", true);
          });
          // Deselect all checkbox in this panel
          $(".select_none").click(function(){
            $(this).parents(".panel").children(".list-group").find("input").prop("checked", false);
          });
        },

        arraySortById: function(arr) {
            arr.sort(function(a, b){
                if (a.id > b.id) {
                    return 1;
                }
                else if (a.id < b.id) {
                    return -1;
                }
                return 0;
            });
        },

        arrayDiff: function(arr1, arr2) {
            arr1 = arr1 || [];
            arr2 = arr2 || [];
            return arr1.filter(function (a) {
                return ((arr2.indexOf(a) == -1) && (a !== ""));
            });
        },

        groupHooks: function(hooks, raw_infos){
            var data = {};
            var rules = [
                {
                    id:'configuration',
                    isIn:function (hook) {
                        return hook.indexOf('conf_')==0
                    }
                }
            ];

            $.each(hooks, function(i, hook) {
                var group_id=hook;
                var hook_size=(raw_infos && raw_infos[hook] && raw_infos[hook].size)?raw_infos[hook].size:0;
                $.each(rules, function(i, rule) {
                    if (rule.isIn(hook)) {
                        group_id = 'adminjs_group_'+rule.id;
                        return false;
                    }
                });

                if(group_id in data) {
                    data[group_id] = {
                        name:y18n.t('hook_'+group_id),
                        value:data[group_id].value+','+hook,
                        description:data[group_id].description+', '+y18n.t('hook_'+hook),
                        size:data[group_id].size + hook_size
                    };
                }
                else {
                    data[group_id] = {
                        name:y18n.t('hook_'+group_id),
                        value:hook,
                        description:(group_id==hook)?y18n.t('hook_'+hook+'_desc'):y18n.t('hook_'+hook),
                        size:hook_size
                    };
                }
            });
            return data;
        },

        ungroupHooks: function(system_parts,apps) {
            var data = {};
            data['apps'] = apps || [];
            data['system'] = system_parts || [];

            if (data['system'].constructor !== Array) {
                data['system'] = [data['system']];
            }
            if (data['apps'].constructor !== Array) {
                data['apps'] = [data['apps']];
            }

            // Some hook value contains multiple hooks separated by commas
            var split_hooks = [];
            $.each(data['system'], function(i, hook) {
                split_hooks = split_hooks.concat(hook.split(','));
            });
            data['system'] = split_hooks;

            if (data['system'].length == 0) {
                delete data['system'];
	    }
            if (data['apps'].length == 0) {
                delete data['apps'];
	    }
            return data;
        },

        // Paste 
        prePaste: function() {
            var pasteButtons = $('button[data-paste-content],a[data-paste-content]');
            pasteButtons.on('click', function(){
                // Get paste content element
                var preElement = $($(this).data('paste-content'));

                // Add pacman loader
                $('#main').append('
'); // Send to paste.yunohost.org $.ajax({ type: "POST", url: 'https://paste.yunohost.org/documents', data: preElement.text(), }) .success(function(data, textStatus, jqXHR) { window.open('https://paste.yunohost.org/' + data.key, '_blank'); }) .fail(function() { c.flash('fail', y18n.t('paste_error')); }) .always(function(){ // Remove pacman $('div.loader').remove(); }); }); }, force_redirect: function(to) { c = this; // This is a copy-pasta of some of the redirect/refresh code of // sammy.js because for some reason calling the origina // redirect/refresh function in some context does not work >.> // (e.g. if you're already on the page) c.trigger('redirect', {to: to}); c.app.last_location = c.path; c.app.setLocation(to); c.app.trigger('location-changed'); } }); })();