app = Sammy('#main', function (sam) { /** * Sammy Configuration * */ // Plugins sam.use('Handlebars', 'ms'); Handlebars.registerHelper('ucwords', function(str) { return (str + '').replace(/^([a-z\u00E0-\u00FC])|\s+([a-z\u00E0-\u00FC])/g, function ($1) { return $1.toUpperCase(); }); }); Handlebars.registerHelper('humanSize', function(bytes) { var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']; if (bytes == 0) return 'n/a'; var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024))); return Math.round(bytes / Math.pow(1024, i), 2) + ' ' + sizes[[i]]; }); Handlebars.registerHelper('humanTime', function(time) { return Math.round(time) + 's'; }); Handlebars.registerHelper('bitRate', function(bytes, time) { var sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb']; if (time == 0) return 'n/a'; var bps = bytes / time * 8; var i = parseInt(Math.floor(Math.log(bps) / Math.log(1024))); return Math.round(bps / Math.pow(1024, i), 2) + ' ' + sizes[[i]] + '/s'; }); Handlebars.registerHelper('t', function(y18n_key) { var result = y18n.t(y18n_key, Array.prototype.slice.call(arguments, 1)); return new Handlebars.SafeString(result); }); // Look for supported type of storage to use var storageType; if (Sammy.Store.isAvailable('session')) { storageType = 'session'; } else if (Sammy.Store.isAvailable('cookie')) { storageType = 'cookie'; } else { storageType = 'memory'; } // Initialize storage var store = new Sammy.Store({name: 'storage', type: storageType}); var loaded = false; var isInstalledTry = 3; /** * Helpers * */ sam.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); } if (level == 'fail') { alertClass = 'alert-danger'; } else { alertClass = 'alert-'+ level; } if ($('#flash .alert').last().hasClass(alertClass)) { if (level == 'log') { $('#flash .alert').last().append('

'+ message +'

'); } else { $('#flash .alert').last().append('

'+ message +'

'); } } else { if (level == 'log') { $('#flash').append('').show(); } else { $('#flash').append('').show(); } $('#flash .alert').last().fadeIn(); } document.body.scrollTop = document.documentElement.scrollTop = 0; $('#flash .alert-log button.btn-small').on('click', function() { $('#flash .alert-log p:hidden').fadeIn(); $('#flash .alert-log div').hide(); }); }, checkInstall: function(callback) { domain = window.location.hostname; $.ajax({ dataType: "json", url: 'https://'+ domain +'/yunohost/api/installed', timeout: 3000 }) .success(function(data) { callback(data.installed); }) .fail(function() { callback(undefined); }); }, // API call api: function(uri, callback, method, data, websocket) { c = this; call = function(uri, callback, method, data) { 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); } var args = data; if (uri === '/postinstall') { var installing = false; setInterval(function () { installing = true; }, 1500); } loaded = false; if ($('div.loader').length == 0) { setInterval(function () { if (!loaded && $('div.loader').length == 0) { $('#main').append('
'); } }, 500); } 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(function(xhr) { if (xhr.status == 200) { // Fail with 200, WTF callback({}); } 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'); } } else if (typeof xhr.responseJSON !== 'undefined') { c.flash('fail', xhr.responseJSON.error); } else if (typeof xhr.responseText !== 'undefined' && uri !== '/postinstall') { c.flash('fail', xhr.responseText); } else { if (uri === '/postinstall') { if (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); } else { c.flash('fail', y18n.t('error_occured')); } } else { c.flash('fail', y18n.t('error_server')); } } if (uri !== '/postinstall') { store.clear('slide'); c.redirect(store.get('path-1')); }; }); } websocket = typeof websocket !== 'undefined' ? websocket : true; if (websocket) { // Open a WebSocket connection to retrieve live messages from the moulinette ws = new WebSocket('wss://'+ store.get('url') +'/messages'); 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 = ws.onopen; ws.onclose = function() { store.clear('flash'); } ws.onopen = call(uri, callback, method, data); } else { call(uri, callback, method, data); } }, // Render view (cross-browser) view: function (view, data, callback) { callback = typeof callback !== 'undefined' ? callback : function() {}; rendered = this.render('views/'+ view +'.ms', data); enableSlide = true; // Change to false to disable animation loaded = true; $('div.loader').remove(); $('#modal').modal('hide'); if (enableSlide) { function leSwap() { rendered.swap(function() { $('.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'); } }); callback(); // Force scrollTop on page load $('html, body').scrollTop(0); }); } blockSize = $('#slider').width(); // Slide back effect if (store.get('slide') == 'back') { store.clear('slide'); $('#slideBack').css('display', 'none'); $('#slider-container').removeClass('move').css('margin-left', '-'+ blockSize +'px'); $('#slideTo').show().html($('#main').html()); leSwap(); $('#slider-container').addClass('move').css('margin-left', '0px'); // Slide to effect } else if (store.get('slide') == 'to') { store.clear('slide'); $('#slideTo').css('display', 'none'); $('#slider-container').removeClass('move').css('margin-left', '-'+ blockSize +'px'); $('#slider-container').removeClass('move').css('margin-left', '0px'); $('#slideBack').show().html($('#main').html()); leSwap(); $('#slider-container').addClass('move').css('margin-left', '-'+ blockSize +'px'); } else { leSwap(); } } else { rendered.swap(function(){ callback() // Force scrollTop on page load $('html, body').scrollTop(0); }); } }, confirm: function(title, content, confirmCallback, cancelCallback) { // Default callbacks confirmCallback = typeof confirmCallback !== 'undefined' ? confirmCallback : function() {}; cancelCallback = typeof cancelCallback !== 'undefined' ? cancelCallback : function() {}; // Get modal element 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); // Handle buttons $('footer button', box) .click(function(e){ e.preventDefault(); // 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'); }, 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 != "")); }); } }); /** * Filters * */ sam.before(/apps\/install\//, function (req){ // Preload domains list. req.params.domains = []; req.api('/domains', function(data) { req.params.domains = data.domains; }); }); sam.before(/apps\/install\//, function (req){ // Preload users lists. req.params.users = []; req.api('/users', function(data) { req.params.users = data.users; }); }); sam.before(/apps\/install\//, function (req){ // Preload apps list. req.params.apps = []; req.api('/apps', function(data) { // Only installed apps $.each(data['apps'], function(k, v) { if (v['installed']) req.params.apps.push(v); }); }); }); sam.before({except: {path: ['#/logout', '#/login', '#/postinstall', '#/postinstall/domain', '#/postinstall/password']}}, function (req) { // Store path for further redirections store.set('path-1', store.get('path')); store.set('path', req.path); // Redirect to login page if no credentials stored if (!store.get('connected')) { req.redirect('#/login'); return false; } // Clear flash display if (!store.get('flash')) { $('#flash').fadeOut(function() { $('#flash').html(''); }); } }); sam.after(function () { // Clear flash notifications store.clear('flash'); }); /** * Errors */ sam.notFound = function(){ // Redirect to home page on 404. window.location = '#/'; }; /** * Routes * * Note: var "c" is Sammy's route context * @doc http://sammyjs.org/docs/api/#Sammy.EventContext * */ // Home page sam.get('#/', function (c) { c.api('/users', function(data) { // Warn admin if no users are created. if (data.users.length == 0) { c.flash('warning', y18n.t('warning_first_user')); } // Get security feed and display new items var securityFeed = 'https://yunohost.org/security.rss' $.ajax({ url: securityFeed, // dataType: (jQuery.browser.msie) ? "text" : "xml", dataType: "xml" }) .done(function(xml){ // Get viewed security alerts from cookie $.cookie.json = true; var viewedItems = $.cookie('ynhSecurityViewedItems') || []; // Loop through items $('item', xml).each(function(k, v) { var item = { guid: $('guid', v)[0].innerHTML, title: $('title', v)[0].innerHTML, url: $('link', v)[0].innerHTML, desc: $('description', v)[0].textContent } if (viewedItems.indexOf(item.guid) === -1) { // Show security message to administrator // var warning = '

'+ item.title +'

'+ item.desc var warning = item.title + ' ('+y18n.t('read_more')+')'; c.flash('warning', warning); // Store viewed item viewedItems.push(item.guid); } }); // Saved viewed items to cookie $.cookie('ynhSecurityViewedItems', viewedItems, { expire: 7 }); }) .fail(function() { c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed])) }); c.view('home'); }); }); /** * Login * */ sam.get('#/login', function (c) { $('#masthead').show(); $('.logout-button').hide(); store.set('path-1', '#/login'); if ($('div.loader').length == 0) { setInterval(function () { if (!loaded && $('div.loader').length == 0) { $('#main').append('
'); } }, 500); } c.checkInstall(function(isInstalled) { if (isInstalled) { domain = window.location.hostname; $('div.loader').remove(); c.view('login', { 'domain': domain }); } else if (typeof isInstalled === 'undefined') { if (isInstalledTry > 0) { isInstalledTry--; loaded = false; // Show pacman setTimeout(function() { c.redirect('#/'); }, 5000); } else { // Reset count isInstalledTry = 3; // API is not responding after 3 try $( document ).ajaxError(function( event, request, settings ) { // Display error if status != 200. // .ajaxError fire even with status code 200 because json is sometimes not valid. if (request.status !== 200) c.flash('fail', y18n.t('api_not_responding', [request.status+' '+request.statusText] )); // Unbind directly $(document).off('ajaxError'); }); // Remove pacman loaded = true; $('div.loader').remove(); } } else { $('div.loader').remove(); c.redirect('#/postinstall'); } }); }); /** * Logout * */ sam.post('#/login', function (c) { store.set('url', c.params['domain'] +'/yunohost/api'); params = { 'password': c.params['password'] } c.api('/login', function(data) { store.set('connected', true); $('.logout-button').fadeIn(); c.flash('success', y18n.t('logged_in')); if (store.get('path')) { c.redirect(store.get('path')); } else { c.redirect('#/'); } }, 'POST', params, false); }); sam.get('#/logout', function (c) { c.api('/logout', function (data) { store.clear('url'); store.clear('connected'); store.set('path', '#/'); c.flash('success', y18n.t('logged_out')); c.redirect('#/login'); }, 'GET', {}, false); }); /** * Post installation * */ // Step 1 : introduction sam.get('#/postinstall', function(c) { $('#masthead').hide(); c.checkInstall(function(isInstalled) { if (isInstalled || typeof isInstalled === 'undefined') { c.redirect('#/login'); } else { c.view('postinstall/postinstall_1'); } }); }); // Step 2 : domain sam.get('#/postinstall/domain', function(c) { $('#masthead').hide(); $.get('https://dyndns.yunohost.org/domains', function() {}) .done(function(data){ c.params.ddomains = data.map(function(dom){return '.'+dom;}); }) .fail(function() { c.params.ddomains = ['.nohost.me', '.noho.st']; }) .always(function() { c.view('postinstall/postinstall_2', c.params, function() { $('#domain, #ddomain').keyup(function(event){ if(event.keyCode == 13){ $('a.savedomain').click(); } }); $('a.savedomain').on('click', function(e) { if ($('#domain').val() === '') { if ($('#ddomain').val() === '') { e.preventDefault(); store.clear('slide'); c.flash('fail', y18n.t('error_select_domain')); } else { domain = $('#ddomain').val() + $('select[name="ddomain-ext"]').val(); } } else { domain = $('#domain').val(); } store.set('maindomain', domain); }); }); }); }); // Step 3 : administration passowrd sam.get('#/postinstall/password', function(c) { $('#masthead').hide(); $('#flash .alert').remove(); if (!store.get('maindomain')) { store.clear('slide'); c.redirect('#/postinstall/domain'); } else { c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() }); } }); // Execute post-installation sam.post('#/postinstall', function (c) { if (c.params['password'] == '' || c.params['confirmation'] == '') { c.flash('fail', y18n.t('password_empty')); } else if (c.params['password'] == c.params['confirmation']) { if (c.params['domain'] === '') { c.flash('fail', y18n.t('error_select_domain')); store.clear('slide'); c.redirect('#/postinstall/domain'); } else { params = { 'domain': c.params['domain'].toLowerCase() } } c.confirm( y18n.t('postinstall'), y18n.t('confirm_postinstall', [c.params['domain']]), function(){ params['password'] = c.params['password'] store.set('url', window.location.hostname +'/yunohost/api'); store.set('user', 'admin'); c.api('/postinstall', function(data) { // http://api.yunohost.org/#!/tools/tools_postinstall_post_0 c.redirect('#/login'); }, 'POST', params); }, function(){ } ); } else { c.flash('fail', y18n.t('passwords_dont_match')); } }); /** * Users * */ // List existing users sam.get('#/users', function (c) { c.api('/users', function(data) { // http://api.yunohost.org/#!/user/user_list_get_3 c.view('user/user_list', data); }); }); // Create user form sam.get('#/users/create', function (c) { c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2 c.view('user/user_create', data); }); }); // Create user (POST) sam.post('#/users', function (c) { if (c.params['password'] == c.params['confirmation']) { if (c.params['password'].length < 4) { c.flash('fail', y18n.t('password_too_short')); store.clear('slide'); } else { c.params['mail'] = c.params['email'] + c.params['domain']; c.api('/users', function(data) { // http://api.yunohost.org/#!/user/user_create_post_2 c.redirect('#/users'); }, 'POST', c.params.toHash()); } } else { c.flash('fail', y18n.t('passwords_dont_match')); store.clear('slide'); //c.redirect('#/users/create'); } }); // Show user information sam.get('#/users/:user', function (c) { c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_info_get_0 c.view('user/user_info', data); }); }); // Edit user form sam.get('#/users/:user/edit', function (c) { c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_info_get_0 c.api('/domains', function(dataDomains) { // http://api.yunohost.org/#!/domain/domain_list_get_2 // User email use a fake splitted field email = data.mail.split('@'); data.email = { username : email[0], domain : email[1] } // Domains data.domains = [] $.each(dataDomains.domains, function(key, value) { data.domains.push({ domain: value, selected: (value == data.email.domain) ? true : false }) }); c.view('user/user_edit', data); }); }); }); // Update user information sam.put('#/users/:user', function (c) { // Get full user object c.api('/users/'+ c.params['user'], function(user) { // concat email/domain pseudo field if (c.params['mail'] !== c.params['email'] + c.params['domain']) { c.params['mail'] = c.params['email'] + c.params['domain']; } else { c.params['mail'] = ''; } // Clear temporary inputs c.params['email'] = c.params['domain'] = ''; // force array type for mail aliases and redirections if (typeof c.params['mailalias'] == 'string') {c.params['mailalias'] = [c.params['mailalias']]}; if (typeof c.params['mailforward'] == 'string') {c.params['mailforward'] = [c.params['mailforward']]}; // Check for added/removed aliases and redirections c.params['add_mailalias'] = c.arrayDiff(c.params['mailalias'], user['mail-aliases']); c.params['remove_mailalias'] = c.arrayDiff(user['mail-aliases'], c.params['mailalias']); c.params['add_mailforward'] = c.arrayDiff(c.params['mailforward'], user['mail-forward']); c.params['remove_mailforward'] = c.arrayDiff(user['mail-forward'], c.params['mailforward']); // Clear temporary inputs c.params['mailalias'] = c.params['mailforward'] = ''; // Remove empty inputs params = {} $.each(c.params.toHash(), function(key, value) { if (value.length > 0 && key !== 'user') { params[key] = value; } }); if ($.isEmptyObject(params)) { c.flash('fail', y18n.t('error_modify_something')); store.clear('slide'); c.redirect('#/users/'+ c.params['user'] + '/edit'); } else { if (params['password']) { if (params['password'] == params['confirmation']) { if (params['password'].length < 4) { c.flash('fail', y18n.t('password_too_short')); store.clear('slide'); c.redirect('#/users/'+ c.params['user'] + '/edit'); } else { params['change_password'] = params['password']; c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_update_put_1 c.redirect('#/users/'+ c.params['user']); }, 'PUT', params); } } else { c.flash('fail', y18n.t('passwords_dont_match')); store.clear('slide'); c.redirect('#/users/'+ c.params['user'] + '/edit'); } } else { c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_update_put_1 c.redirect('#/users/'+ c.params['user']); }, 'PUT', params); } } }, 'GET'); }); // Remove existing user sam.get('#/users/:user/delete', function (c) { c.confirm( y18n.t('users'), y18n.t('confirm_delete', [c.params['user']]), function(){ c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_delete_delete_4 c.redirect('#/users'); }, 'DELETE'); }, function(){ store.clear('slide'); c.redirect('#/users/'+ c.params['user']); } ); }); /** * Domains * */ // List existing domains sam.get('#/domains', function (c) { c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2 c.api('/domains/main', function(data2) { domains = []; $.each(data.domains, function(k, domain) { domains.push({ url: domain, main: (domain == data2.current_main_domain) ? true : false }); }); // Do not show main domain form if we have only 1 domain main_domain_form = (domains.length > 1) ? true: false; // Sort domains with main domain first domains.sort(function(a, b){ return -2*(a.main) + 1; }); c.view('domain/domain_list', {domains: domains, main_domain_form: main_domain_form}); }, 'PUT'); }); }); // Add domain form sam.get('#/domains/add', function (c) { $.get('https://dyndns.yunohost.org/domains', function() {}) .done(function(data){ c.params.ddomains = data.map(function(dom){return '.'+dom;}); }) .fail(function() { c.params.ddomains = ['.nohost.me', '.noho.st']; }) .always(function() { c.view('domain/domain_add', c.params); }); }); // Add domain (POST) sam.post('#/domains/add', function (c) { if (c.params['domain'] == '') { if (c.params['ddomain'] == '') { c.flash('fail', y18n.t('error_select_domain')); store.clear('slide'); c.redirect('#/domains/add'); } params = { 'domain': c.params['ddomain'].toLowerCase() + c.params['ddomain-ext'] } } else { params = { 'domain': c.params['domain'].toLowerCase() } } c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_add_post_1 c.redirect('#/domains'); }, 'POST', params); }); // Remove existing domain sam.get('#/domains/:domain/delete', function (c) { c.confirm( y18n.t('domains'), y18n.t('confirm_delete', [c.params['domain']]), function(){ c.api('/domains/'+ c.params['domain'], function(data) { // http://api.yunohost.org/#!/domain/domain_remove_delete_3 store.clear('slide'); c.redirect('#/domains'); }, 'DELETE'); }, function(){ store.clear('slide'); c.redirect('#/domains'); } ); }); // Set default domain sam.post('#/domains', function (c) { if (c.params['domain'] == '') { c.flash('fail', y18n.t('error_select_domain')); store.clear('slide'); c.redirect('#/domains'); } else { c.confirm( y18n.t('domains'), y18n.t('confirm_change_maindomain'), function(){ params = {'new_domain': c.params['domain']} c.api('/domains/main', function(data) { // http://api.yunohost.org/#!/tools/tools_maindomain_put_1 store.clear('slide'); c.redirect('#/domains'); }, 'PUT', params); // Wait 15s and refresh the page refreshDomain = window.setTimeout(function(){ store.clear('slide'); c.redirect('#/domains') }, 15000); }, function(){ store.clear('slide'); c.redirect('#/domains'); } ); } }); /** * Apps * */ // List installed apps sam.get('#/apps', function (c) { c.api('/apps', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 // Keep only installed apps data2 = { 'apps': [], 'installed': true } $.each(data['apps'], function(k, v) { if (v['installed']) data2['apps'].push(v); }); c.arraySortById(data2.apps); c.view('app/app_list', data2); }); }); // List available apps sam.get('#/apps/install', function (c) { c.api('/apps', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 c.api('/apps?raw', function(dataraw) { // http://api.yunohost.org/#!/app/app_list_get_8 // Keep only uninstalled apps data2 = { 'apps': [] } $.each(data['apps'], function(k, v) { if (dataraw[v['id']].manifest.multi_instance) v['installed'] = false; if (!v['installed'] && !v['id'].match(/__[0-9]{1,5}$/)) data2['apps'].push(v); }); c.arraySortById(data2.apps); c.view('app/app_list', data2); }); }); }); // Refresh available apps list sam.get('#/apps/refresh', function (c) { c.api('/appslists', function(data) { // http://api.yunohost.org/#!/app/app_fetchlist_put_5 // c.redirect(store.get('path')); c.redirect('#/apps/install'); }, 'PUT'); }); // Get app information sam.get('#/apps/:app', function (c) { c.api('/apps/'+c.params['app']+'?raw', function(data) { // http://api.yunohost.org/#!/app/app_info_get_9 // Presentation data.settings.allowed_users = (data.settings.allowed_users) ? data.settings.allowed_users.replace(',', ', ')+"." : y18n.t('everyone_has_access'); // Multilingual description data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ? data.manifest.description[y18n.locale] : data.manifest.description['en'] ; // Multi Instance settings data.manifest.multi_instance = (data.manifest.multi_instance == 'true') ? y18n.t('yes') : y18n.t('no'); // Installation date var d = new Date(data.settings.install_time * 1000); data.install_time = d.getDate() + '/' + (d.getMonth()+1) + '/' + d.getFullYear(); c.view('app/app_info', data); }); }); // Special case for custom app installation. sam.get('#/apps/install/custom', function (c) { // If we try to GET /apps/install/custom, it means that installation fail. // Need to redirect to apps/install to get rid of pacamn and see the log. c.redirect('#/apps/install'); }); // Helper function that build app installation form sam.helper('appInstallForm', function(appId, manifest, params) { data = { id: appId, manifest: manifest } if (typeof data.manifest.arguments.install !== 'undefined') { $.each(data.manifest.arguments.install, function(k, v) { // Default values data.manifest.arguments.install[k].type = (typeof v.type !== 'undefined') ? v.type : 'string'; data.manifest.arguments.install[k].inputType = 'text'; data.manifest.arguments.install[k].required = (typeof v.optional !== 'undefined' && v.optional == "true") ? '' : 'required'; // Input with choices becomes select list if (typeof data.manifest.arguments.install[k].choices !== 'undefined') { // Update choices values with key and checked data $.each(data.manifest.arguments.install[k].choices, function(ck, cv){ data.manifest.arguments.install[k].choices[ck] = { value: cv, label: cv, selected: (cv == data.manifest.arguments.install[k].default) ? true : false, }; }); } // Special case for domain input. // Display a list of available domains if (v.name == 'domain' || data.manifest.arguments.install[k].type == 'domain') { data.manifest.arguments.install[k].choices = []; $.each(params.domains, function(key, domain){ data.manifest.arguments.install[k].choices.push({ value: domain, label: domain, selected: false }); }); data.manifest.arguments.install[k].help = ""+y18n.t('manage_domains')+""; } // Special case for admin / user input. // Display a list of available users if (v.name == 'admin' || data.manifest.arguments.install[k].type == 'user') { data.manifest.arguments.install[k].choices = []; $.each(params.users, function(key, user){ data.manifest.arguments.install[k].choices.push({ value: user.username, label: user.fullname+' ('+user.mail+')', selected: false }); }); data.manifest.arguments.install[k].help = ""+y18n.t('manage_users')+""; } // 'app' type input display a list of available apps if (data.manifest.arguments.install[k].type == 'app') { data.manifest.arguments.install[k].choices = []; $.each(params.apps, function(key, app){ data.manifest.arguments.install[k].choices.push({ value: app.id, label: app.name, selected: false }); }); data.manifest.arguments.install[k].help = ""+y18n.t('manage_apps')+""; } // 'password' type input. if (v.name == 'password' || data.manifest.arguments.install[k].type == 'password') { // Change html input type data.manifest.arguments.install[k].inputType = 'password'; } // Multilingual label data.manifest.arguments.install[k].label = (typeof data.manifest.arguments.install[k].ask[y18n.locale] !== 'undefined') ? data.manifest.arguments.install[k].ask[y18n.locale] : data.manifest.arguments.install[k].ask['en'] ; }); } // Multilingual description data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ? data.manifest.description[y18n.locale] : data.manifest.description['en'] ; // Multi Instance settings boolean to text data.manifest.multi_instance = (data.manifest.multi_instance == 'true') ? y18n.t('yes') : y18n.t('no'); // View app install form c.view('app/app_install', data); return; }); // App installation form sam.get('#/apps/install/:app', function (c) { c.api('/apps?raw', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 c.appInstallForm( c.params['app'], data[c.params['app']].manifest, c.params ); }); }); // Install app (POST) sam.post('#/apps', function(c) { // Warn admin if app is going to be installed on domain root. if (c.params['path'] !== '/' || confirm(y18n.t('confirm_install_domain_root', [c.params['domain']]))) { params = { 'label': c.params['label'], 'app': c.params['app'] } delete c.params['label']; delete c.params['app']; params['args'] = c.serialize(c.params.toHash()); // Do not pass empty args. if (params['args'] == "") { delete params['args']; } c.api('/apps', function() { // http://api.yunohost.org/#!/app/app_install_post_2 c.redirect('#/apps'); }, 'POST', params); } else { c.flash('warning', y18n.t('app_install_cancel')); store.clear('slide'); c.redirect('#/apps/install'); } }); // Install custom app from github sam.post('#/apps/install/custom', function(c) { c.confirm( y18n.t('applications'), y18n.t('confirm_install_custom_app'), function(){ params = { 'label': c.params['label'], 'app': c.params['url'] } delete c.params['label']; delete c.params['url']; // Force trailing slash params.app = params.app.replace(/\/?$/, '/'); // Get manifest.json to get additional parameters jQuery.ajax({ url: params.app.replace('github.com', 'rawgit.com') + 'master/manifest.json', type: 'GET', crossdomain: true, dataType: 'json', }) .done(function(manifest) { manifest = manifest || {}; c.appInstallForm( params.app, manifest, c.params ); }) .fail(function(xhr) { c.flash('fail', y18n.t('app_install_custom_no_manifest')); store.clear('slide'); c.redirect('#/apps/install'); }); }, function(){ c.flash('warning', y18n.t('app_install_cancel')); store.clear('slide'); c.redirect('#/apps/install'); } ); }); // Remove installed app sam.get('#/apps/:app/uninstall', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_uninstall', [c.params['app']]), function() { c.api('/apps/'+ c.params['app'], function() { // http://api.yunohost.org/#!/app/app_remove_delete_4 c.redirect('#/apps'); }, 'DELETE'); }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app']); } ); }); // Manage app access sam.get('#/apps/:app/access', function (c) { c.api('/apps/'+c.params['app']+'?raw', function(data) { // http://api.yunohost.org/#!/app/app_info_get_9 c.api('/users', function(dataUsers) { // allowed_users as array if (typeof data.settings.allowed_users !== 'undefined') { if (data.settings.allowed_users.length === 0) { // Force empty array, means no user has access data.settings.allowed_users = []; } else { data.settings.allowed_users = data.settings.allowed_users.split(','); } } else { data.settings.allowed_users = []; // Force array // if 'allowed_users' is undefined, everyone has access // that means that undefined is different from empty array data.settings.allow_everyone = true; } // Available users data.users = []; $.each(dataUsers.users, function(key, user){ // Do not list allowed_users in select list if ( data.settings.allowed_users.indexOf(user.username) === -1 ) { data.users.push({ value: user.username, label: user.fullname+' ('+user.mail+')' }); } else { // Complete allowed_users data data.settings.allowed_users[data.settings.allowed_users.indexOf(user.username)] = { username: user.username, fullname: user.fullname, mail: user.mail, } } }) c.view('app/app_access', data); }); }); }); // Remove all access sam.get('#/apps/:app/access/remove', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_remove_all', [c.params['app']]), function() { params = {'apps': c.params['app'], 'users':[]} c.api('/access?'+c.serialize(params), function(data) { // http://api.yunohost.org/#!/app/app_removeaccess_delete_12 store.clear('slide'); c.redirect('#/apps/'+ c.params['app']+ '/access'); }, 'DELETE', params); }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app']+ '/access'); } ); }); // Remove access to a specific user sam.get('#/apps/:app/access/remove/:user', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_remove_user', [c.params['app'], c.params['user']]), function() { params = {'apps': c.params['app'], 'users': c.params['user']} c.api('/access?'+c.serialize(params), function(data) { // http://api.yunohost.org/#!/app/app_removeaccess_delete_12 store.clear('slide'); c.redirect('#/apps/'+ c.params['app']+ '/access'); }, 'DELETE', params); // passing 'params' here is useless because jQuery doesn't handle ajax datas for DELETE requests. Passing parameters through uri. }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app']+ '/access'); } ); }); // Grant all access sam.get('#/apps/:app/access/add', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_add', [c.params['app']]), function() { params = {'apps': c.params['app'], 'users': null} c.api('/access', function() { // http://api.yunohost.org/#!/app/app_addaccess_put_13 store.clear('slide'); c.redirect('#/apps/'+ c.params['app'] +'/access'); }, 'PUT', params); }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app']+ '/access'); } ); }); // Grant access for a specific user sam.post('#/apps/:app/access/add', function (c) { params = {'users': c.params['user'], 'apps': c.params['app']} c.api('/access', function() { // http://api.yunohost.org/#!/app/app_addaccess_put_13 store.clear('slide'); c.redirect('#/apps/'+ c.params['app'] +'/access'); }, 'PUT', params); }); // Clear access (reset) sam.get('#/apps/:app/access/clear', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_clear', [c.params['app']]), function() { params = {'apps': c.params['app']} c.api('/access', function() { // store.clear('slide'); c.redirect('#/apps/'+ c.params['app'] +'/access'); }, 'POST', params); }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app']+ '/access'); } ); }); // Make app default sam.get('#/apps/:app/default', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_app_default'), function() { c.api('/apps/'+ c.params['app'] +'/default', function() { // store.clear('slide'); c.redirect('#/apps/'+ c.params['app']); }, 'PUT'); }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app']); } ); }); /** * Services * */ // All services status sam.get('#/services', function (c) { c.api('/services', function(data) { // ? data2 = { 'services': [] } $.each(data, function(k, v) { v.name = k; v.is_loaded = (v.loaded=='enabled') ? true : false; v.is_running = (v.status=='running') ? true : false; data2.services.push(v); }); c.view('service/service_list', data2); }); }); // Status & actions for a service sam.get('#/services/:service', function (c) { c.api('/services/'+ c.params['service'], function(data) { // ? data2 = { 'service': data } data2.service.name = c.params['service']; data2.service.is_loaded = (data.loaded=='enabled') ? true : false; data2.service.is_running = (data.status=='running') ? true : false; store.clear('slide'); c.view('service/service_info', data2); }, 'GET'); }); // Service log sam.get('#/services/:service/log', function (c) { params = { 'number': 50 } c.api('/services/'+ c.params['service'] +'/log', function(data) { // ? data2 = { 'logs': [], 'name': c.params['service'] } $.each(data, function(k, v) { data2.logs.push({filename: k, filecontent: v.join('\n')}); }); c.view('service/service_log', data2); }, 'GET', params); }); // Enable/Disable & Start/Stop service sam.get('#/services/:service/:action', function (c) { c.confirm( "Service", y18n.t('confirm_service_action', [y18n.t(c.params['action']), c.params['service']]), function(){ var method = null, endurl = c.params['service']; switch (c.params['action']) { case 'start': method = 'PUT'; break; case 'stop': method = 'DELETE'; break; case 'enable': method = 'PUT'; endurl += '/enable'; break; case 'disable': method = 'DELETE'; endurl += '/enable'; break; default: c.flash('fail', y18n.t('unknown_action', [c.params['action']])); store.clear('slide'); c.redirect('#/services/'+ c.params['service']); } if (method && endurl) { c.api('/services/'+ endurl, function(data) { store.clear('slide'); c.redirect('#/services/'+ c.params['service']); }, method); } else { store.clear('slide'); c.redirect('#/services/'+ c.params['service']); } }, function(){ store.clear('slide'); c.redirect('#/services/'+ c.params['service']); } ); }); /** * Firewall * */ // Firewall status sam.get('#/firewall', function (c) { c.api('/firewall?raw', function(data) { var firewall = { ports : {}, upnp : false }; // Reorganize ports $.each(['ipv4', 'ipv6', 'uPnP'], function(i, protocol) { $.each(['TCP', 'UDP'], function(j, connection) { firewall.ports[connection] = firewall.ports[connection] || {};  $.each(data[protocol][connection], function(k, port) { firewall.ports[connection][port] = firewall.ports[connection][port] || {};  firewall.ports[connection][port][protocol] = true; }); }); }); // Get UPnP status firewall.upnp = data.uPnP.enabled; c.view('firewall/firewall', firewall); }); }); // Enable/Disable UPnP sam.get('#/firewall/upnp/:action', function (c) { c.confirm( y18n.t('firewall'), y18n.t('confirm_upnp_action', [y18n.t(c.params['action'])]), function(){ params = {'action' : c.params['action']} c.api('/firewall/upnp', function(data) { store.clear('slide'); c.redirect('#/firewall'); }, 'GET', params); }, function(){ store.clear('slide'); c.redirect('#/firewall'); } ); }); // Toggle port status helper (available in every controller) sam.helper('togglePort', function(port, protocol, connection, action) { var method = null , endurl = [] , c = this ; if (port != parseInt(port) || port < 0 || port > 65535) { c.flash('fail', y18n.t('unknown_argument', [port])); store.clear('slide'); c.redirect('#/firewall'); } switch (connection) { case 'ipv4': break; case 'ipv6': endurl = 'ipv6' break; } switch (protocol) { case 'udp': protocol = 'UDP'; break; case 'both': protocol = 'Both'; break; case 'tcp': default: protocol = []; } switch (action) { case "open": method = 'POST'; break; case "close": method = 'DELETE'; break; default: c.flash('fail', y18n.t('unknown_action', [action])); store.clear('slide'); c.redirect('#/firewall'); } if (method !== null && protocol !== null && port != null) { // port: // protocol: // - UDP // - TCP // - Both // --ipv6: // --no-upnp: var params = { 'port' : port, 'protocol' : protocol, } c.api('/firewall/port?'+endurl, function(data) { store.clear('slide'); c.redirect('#/firewall'); }, method, params); } else { store.clear('slide'); c.redirect('#/firewall'); } return; }); // Update port status from direct link // #/firewall/port/{{@key}}/tcp/ipv4/close sam.get('#/firewall/port/:port/:protocol/:connection/:action', function (c) { c.confirm( y18n.t('firewall'), y18n.t( 'confirm_firewall', [ y18n.t(c.params['action']), c.params['port'], y18n.t(c.params['protocol']), y18n.t(c.params['connection'])]), function(){ c.togglePort( c.params['port'], c.params['protocol'], c.params['connection'], c.params['action'] ); }, function(){ store.clear('slide'); c.redirect('#/firewall'); } ); }); // Update port status from form sam.post('#/firewall/port', function (c) { c.confirm( y18n.t('firewall'), y18n.t('confirm_firewall', [ y18n.t(c.params['action']), c.params['port'], y18n.t(c.params['protocol']), y18n.t(c.params['connection']) ]), function(){ c.togglePort( c.params['port'], c.params['protocol'], c.params['connection'], c.params['action'] ); }, function(){ store.clear('slide'); c.redirect('#/firewall'); } ); }); /** * Monitor * */ // Server monitoring sam.get('#/monitor', function (c) { monitorData = {} // Why this method ? c.api('/services/glances', function(data) { // ? monitorData.status = true; if (data.status == 'running') { c.api('/monitor/system', function(data) { monitorData.system = data; c.api('/monitor/disk', function(data) { monitorData.disk = data; c.api('/monitor/network', function(data) { monitorData.network = data; // Remove useless interface delete monitorData.network.usage.lo; c.view('monitor/monitor', monitorData); }); }); }); } else { monitorData.status = false; c.view('monitor/monitor', monitorData); } }, 'GET'); }); /** * Tools * */ // Tools list sam.get('#/tools', function (c) { c.view('tools/tools_list'); }); // Update administration password sam.get('#/tools/adminpw', function (c) { c.view('tools/tools_adminpw'); }); // Update administration password (PUT) sam.put('#/tools/adminpw', function (c) { params = {} $.each(c.params.toHash(), function(key, value) { if (value !== '') { params[key] = value; } }); if ($.isEmptyObject(params)) { c.flash('fail', y18n.t('error_modify_something')); store.clear('slide'); c.redirect('#/tools/adminpw'); } else if (params['new_password'] !== params['confirm_new_password']) { c.flash('fail', y18n.t('passwords_dont_match')); store.clear('slide'); c.redirect('#/tools/adminpw'); } else { // Remove useless variable delete params['confirm_new_password']; // Update password and redirect to the home c.api('/adminpw', function(data) { // http://api.yunohost.org/#!/tools/tools_adminpw_put_3 c.redirect('#/logout'); }, 'PUT', params); } }); // System update & upgrade sam.get('#/tools/update', function (c) { c.api('/update', function(data) { packagesLength = data.packages.length; for(var i = 0; i < packagesLength; i++) { data.packages[i].delayed = false; data.packages[i].changelog = data.packages[i].changelog.replace(/\n/g, '
'); // Check for special packages that need delayed upgrade. if (["moulinette", "moulinette-yunohost", "yunohost-admin", "yunohost-config-nginx", "ssowat", "python"].indexOf(data.packages[i].name) != -1) { c.flash('warning', y18n.t('system_delayed_upgrade_warning', [data.packages[i].name])); data.packages[i].delayed = true; } } c.view('tools/tools_update', data); }, 'PUT'); }); // Upgrade apps or packages sam.get('#/tools/upgrade/:type', function (c) { if (c.params['type'] !== 'apps' && c.params['type'] !== 'packages') { c.flash('fail', y18n.t('unknown_argument', [c.params['type']])); store.clear('slide'); c.redirect('#/tools/update'); } else { c.confirm( y18n.t('tools'), y18n.t('confirm_update_type', [y18n.t('system_'+c.params['type']).toLowerCase()]), function(){ endurl = ''; if (c.params['type'] == 'packages') {endurl = 'ignore_apps';} else if (c.params['type'] == 'apps') {endurl = 'ignore_packages';} c.api('/upgrade?'+endurl, function(data) { // 'log' is a reserved name, maybe in handlebars data.logs = data.log; c.view('tools/tools_upgrade', data); }, 'PUT'); }, function(){ store.clear('slide'); c.redirect('#/tools/update'); } ); } }); // Download SSL Certificate Authority sam.get('#/tools/ca', function (c) { c.view('tools/tools_ca'); }); // Security feed sam.get('#/tools/security-feed', function (c) { data = { items: [] }; // Get security feed and display items var securityUrl = 'https://forum.yunohost.org/c/security'; var securityFeed = 'https://yunohost.org/security.rss'; data.url = { web: securityUrl, rss: securityFeed } $.ajax({ url: securityFeed, // dataType: (jQuery.browser.msie) ? "text" : "xml", dataType: "xml" }) .done(function(xml){ // Loop through items $('item', xml).each(function(k, v) { var item = { guid: $('guid', v)[0].innerHTML, title: $('title', v)[0].innerHTML, url: $('link', v)[0].innerHTML, desc: $('description', v)[0].textContent, date: $('pubDate', v)[0].innerHTML.split(' +')[0], } data.items.push(item); }); c.view('tools/tools_security_feed', data); }) .fail(function() { c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed])) }); }); /** * Backup * */ // Backup view sam.get('#/backup', function (c) { c.view('backup/backup'); }); }); /** * Translations */ $.getJSON('locales/en.json', function(data){ y18n.translations['en'] = data; y18n.translateInlineHTML(); }); // User defined language if (window.navigator && window.navigator.language) { y18n.locale = window.navigator.language.substr(0, 2); if (y18n.locale !== 'en') { $.getJSON('locales/'+ y18n.locale +'.json', function(data){ y18n.translations[y18n.locale] = data; y18n.translateInlineHTML(); }); } } /** * Run the app * */ $(document).ready(function () { /** * Application */ app.run('#/'); // Fixes for sliding effect $('#slider-container').width(2*$('#slider').width() +'px'); $(window).resize(function() { $('#slideBack').css('display', 'none'); $('#slideTo').css('display', 'none'); $('#slider-container').width(2*$('#slider').width() +'px').removeClass('move').css('margin-left', '0px'); }); });