(function() { // Get application context var app = Sammy.apps['#main']; var store = app.store; /** * Apps * */ // List installed apps app.get('#/apps', function (c) { c.api('/apps?installed', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 var apps = data['apps']; c.arraySortById(apps); c.view('app/app_list', {apps: apps}); }); }); function levelToColor(level) { if (level >= 8) { return 'best'; } else if (level > 4) { return 'success'; } else if (level >= 1) { return 'warning'; } else if (isNaN(level)) { return 'default'; } else { return 'danger' } } function stateToColor(state) { if (state === "high-quality") { return 'best'; } else if (state === "working") { return 'success'; } else { return 'danger'; } } function maintainedStateToColor(state) { if ( state === "request_help" ) { return 'info'; } else if ( state === "request_adoption" ) { return 'warning'; } else if ( state === "orphaned" ) { return 'danger'; } else { return 'success'; } } function combineColors(stateColor, levelColor, installable) { if (stateColor === "danger" || levelColor === "danger") { return 'danger'; } else if (stateColor === "warning" || levelColor === "warning" || levelColor === "default") { return 'warning'; } else { return 'success'; } } function extractMaintainer(manifest) { if (manifest.maintainer === undefined) { if ((manifest.developer !== undefined) && (manifest.developer.name !== undefined)) { return manifest.developer.name; } else { return "?"; } } else if (Array.isArray(manifest.maintainer)) { maintainers = []; manifest.maintainer.forEach(function(maintainer) { if (maintainer.name !== undefined) { maintainers.push(maintainer.name); } }); return maintainers.join(', '); } else if (manifest.maintainer.name !== undefined) { return manifest.maintainer.name; } else { return "?"; } } // List available apps app.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 var apps = [] $.each(data['apps'], function(k, v) { app = dataraw[v['id']]; app.level = parseInt(app.level); if (app.high_quality && app.level > 7) { app.state = "high-quality"; } if ( app.maintained === false ) { app.maintained = "orphaned"; } else if ( app.maintained === true ) { app.maintained = "maintained"; } app.manifest.maintainer = extractMaintainer(app.manifest); var isWorking = (app.state === 'working' || app.state === "high-quality") && app.level > 0; // Keep only the first instance of each app and remove not working apps if (!v['id'].match(/__[0-9]{1,5}$/) && (app.state !== 'notworking')) { app.installable = (!v.installed || app.manifest.multi_instance) app.levelFormatted = isNaN(app.level) ? '?' : app.level; app.levelColor = levelToColor(app.level); app.stateColor = stateToColor(app.state); app.maintainedColor = maintainedStateToColor(app.maintained); app.installColor = combineColors(app.stateColor, app.levelColor); app.updateDate = app.lastUpdate * 1000 || 0; app.isSafe = (app.installColor !== 'danger'); app.isWorking = isWorking ? "isworking" : "notFullyWorking"; app.isHighQuality = (app.state === "high-quality") ? "isHighQuality" : ""; app.decentQuality = (app.level > 4)?"decentQuality":"badQuality"; jQuery.extend(app, v); apps.push(app); } }); // Sort app list c.arraySortById(apps); // setup filtering of apps once the view is loaded function setupFilterEvents () { // Uses plugin isotope to filter apps (we could had ordering to) var cardGrid = jQuery('.grid').isotope({ itemSelector: '.app-card', layoutMode: 'fitRows', transitionDuration: 200 }); filterByClassAndName = function () { var input = jQuery("#filter-app-cards").val().toLowerCase(); var inputMatch = (jQuery(this).find('.app-title').text().toLowerCase().indexOf(input) > -1); var filterClass = jQuery("#dropdownFilter").attr("data-filter"); var classMatch = (filterClass === '*') ? true : jQuery(this).hasClass(filterClass); return inputMatch && classMatch; }, // Default filter is 'decent quality apps' cardGrid.isotope({ filter: '.decentQuality' }); jQuery('.dropdownFilter').on('click', function() { // change dropdown label jQuery('#app-cards-list-filter-text').text(jQuery(this).find('.menu-item').text()); // change filter attribute jQuery('#dropdownFilter').attr("data-filter", jQuery(this).attr("data-filter")); // filter ! cardGrid.isotope({ filter: filterByClassAndName }); }); jQuery("#filter-app-cards").on("keyup", function() { cardGrid.isotope({ filter: filterByClassAndName }); }); }; // render c.view('app/app_list_install', {apps: apps}, setupFilterEvents); }); }); }); // List available apps lists app.get('#/apps/lists', function (c) { c.api('/appslists', function(data) { list = []; var has_community_list = false; $.each(data, function(listname, listinfo) { list.push({ 'name': listname, 'url': listinfo['url'], 'lastUpdate': listinfo['lastUpdate'] }); // Check for community list if (listname == 'community' || listinfo['url'] == 'https://app.yunohost.org/community.json') { has_community_list = true; } }); c.view('app/app_appslists_list', { appslists: list, has_community_list: has_community_list }); }, 'GET'); }); // Add a new apps list app.post('#/apps/lists', function (c) { list = { 'name' : c.params['appslist_name'], 'url' : c.params['appslist_url'] } c.api('/appslists', function(data) { store.clear('slide'); c.redirect('#/apps/lists/' + list.name); }, 'PUT', list); }); // Show appslist info and operations app.get('#/apps/lists/:appslist', function (c) { c.api('/appslists', function(data) { if (typeof data[c.params['appslist']] !== 'undefined') { list = { 'name' : c.params['appslist'], 'url': data[c.params['appslist']]['url'], 'lastUpdate': data[c.params['appslist']]['lastUpdate'], 'removable' : (c.params['appslist'] !== 'yunohost') ? true : false // Do not remove default apps list }; c.view('app/app_appslists_info', {appslist: list}); } else { c.flash('warning', y18n.t('appslists_unknown_list', [c.params['appslist']])); store.clear('slide'); c.redirect('#/apps/lists'); } }, 'GET'); }); // Refresh available apps list app.get('#/apps/lists/refresh', function (c) { c.api('/appslists', function(data) { // c.redirect(store.get('path')); c.redirect('#/apps/install'); }, 'PUT'); }); // Refresh specific apps list app.get('#/apps/lists/:appslist/refresh', function (c) { c.api('/appslists', function(data) { c.redirect('#/apps/lists'); }, 'PUT', {'name' : c.params['appslist']}); }); // Remove apps list app.get('#/apps/lists/:appslist/remove', function (c) { c.confirm( y18n.t('appslist'), y18n.t('appslists_confirm_remove', [c.params['app']]), function() { c.api('/appslists', function() { c.redirect('#/apps/lists'); }, 'DELETE', {'name' : c.params['appslist']}); }, function() { store.clear('slide'); c.redirect('#/apps/lists/'+ c.params['appslist']); } ); }); // Get app information app.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 ? y18n.t('yes') : y18n.t('no'); data.install_time = new Date(data.settings.install_time * 1000); c.view('app/app_info', data); }); }); // Get app debug page app.get('#/apps/:app/debug', function (c) { c.api('/apps/'+c.params['app']+'/debug', function(data) { c.view('app/app_debug', data); }); }); // Get app actions list app.get('#/apps/:app/actions', function (c) { c.api('/apps/'+c.params['app']+'/actions', function(data) { $.each(data.actions, function(_, action) { formatYunoHostStyleArguments(action.arguments, c.params); // Multilingual description action.description = (typeof action.description[y18n.locale] !== 'undefined') ? action.description[y18n.locale] : action.description['en'] ; }); c.view('app/app_actions', data); return; }); }); // Perform application app.put('#/apps/:app/actions/:action', function(c) { // taken from app install $.each(c.params, function(k, v) { if (typeof(v) === 'object' && Array.isArray(v)) { // And return only first value c.params[k] = v[0]; } }); var app_id = c.params['app']; delete c.params['app']; var action_id = c.params['action']; delete c.params['action']; var params = { 'args': c.serialize(c.params.toHash()) } c.api('/apps/'+app_id+'/actions/'+action_id, function() { // http://api.yunohost.org/#!/app/app_install_post_2 c.redirect('#/apps/'+app_id+'/actions'); }, 'PUT', params); }); // Get app config panel app.get('#/apps/:app/config-panel', function (c) { c.api('/apps/'+c.params['app']+'/config-panel', function(data) { c.view('app/app_config-panel', data); }); }); app.post('#/apps/:app/config', function(c) { // taken from app install $.each(c.params, function(k, v) { if (typeof(v) === 'object' && Array.isArray(v)) { // And return only first value c.params[k] = v[0]; } }); var app_id = c.params['app']; delete c.params['app']; var params = { 'args': c.serialize(c.params.toHash()) } c.api('/apps/'+app_id+'/config', function() { // http://api.yunohost.org/#!/app/app_install_post_2 c.redirect('#/apps/'+app_id+'/config-panel'); }, 'POST', params); }) // Special case for custom app installation. app.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 formats YunoHost style arguments for generating a form function formatYunoHostStyleArguments(args, params) { if (!args) { return; } // this is in place modification, I don't like it but it was done this way $.each(args, function(k, v) { // Default values args[k].type = (typeof v.type !== 'undefined') ? v.type : 'string'; args[k].inputType = 'text'; args[k].isPassword = false; args[k].isDisplayText = false; args[k].required = (typeof v.optional !== 'undefined' && v.optional == "true") ? '' : 'required'; args[k].attributes = ""; args[k].helpText = ""; args[k].helpLink = ""; // Multilingual label args[k].label = (typeof args[k].ask[y18n.locale] !== 'undefined') ? args[k].ask[y18n.locale] : args[k].ask['en'] ; // Multilingual help text if (typeof args[k].help !== 'undefined') { args[k].helpText = (typeof args[k].help[y18n.locale] !== 'undefined') ? args[k].help[y18n.locale] : args[k].help['en'] ; } // Input with choices becomes select list if (typeof args[k].choices !== 'undefined') { // Update choices values with key and checked data var choices = [] $.each(args[k].choices, function(ck, cv){ // Non key/value choices have numeric key, that we don't want. if (typeof ck == "number") { // Key is Value in this case. ck = cv; } choices.push({ value: ck, label: cv, selected: (ck == args[k].default) ? true : false, }); }); args[k].choices = choices; } // Special case for domain input. // Display a list of available domains if (v.name == 'domain' || args[k].type == 'domain') { args[k].choices = []; $.each(params.domains, function(key, domain){ args[k].choices.push({ value: domain, label: domain, selected: false }); }); // Custom help link args[k].helpLink += ""+y18n.t('manage_domains')+""; } // Special case for admin / user input. // Display a list of available users if (v.name == 'admin' || args[k].type == 'user') { args[k].choices = []; $.each(params.users, function(username, user){ args[k].choices.push({ value: username, label: user.fullname+' ('+user.mail+')', selected: false }); }); // Custom help link args[k].helpLink += ""+y18n.t('manage_users')+""; } // 'app' type input display a list of available apps if (args[k].type == 'app') { args[k].choices = []; $.each(params.apps, function(key, app){ args[k].choices.push({ value: app.id, label: app.name, selected: false }); }); // Custom help link args[k].helpLink += ""+y18n.t('manage_apps')+""; } // Boolean fields if (args[k].type == 'boolean') { args[k].inputType = 'checkbox'; // Checked or not ? if (typeof args[k].default !== 'undefined') { if (args[k].default == true) { args[k].attributes = 'checked="checked"'; } } // 'default' is used as value, so we need to force it for checkboxes. args[k].default = 1; // Checkbox should not be required to be unchecked args[k].required = ''; // Clone a hidden input with empty value // https://stackoverflow.com/questions/476426/submit-an-html-form-with-empty-checkboxes var inputClone = { name : args[k].name, inputType : 'hidden', default : 0 }; args.push(inputClone); } // 'password' type input. if (v.name == 'password' || args[k].type == 'password') { // Change html input type args[k].inputType = 'password'; args[k].isPassword = true; } if (args[k].type == "display_text") { args[k].isDisplayText = true; args[k].label = args[k].label.split("\n"); } }); } // Helper function that build app installation form app.helper('appInstallForm', function(appId, manifest, params) { var data = { id: appId, manifest: manifest, displayLicense: (manifest['license'] !== undefined && manifest['license'] !== 'free') }; formatYunoHostStyleArguments(data.manifest.arguments.install, params); // 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 ? y18n.t('yes') : y18n.t('no'); // View app install form c.view('app/app_install', data); return; }); // App installation form app.get('#/apps/install/:app', function (c) { c.api('/apps?raw', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 var app_name = c.params["app"]; var app_infos = data[app_name]; if (app_infos['state'] === "validated") { app_infos['state'] = "official"; } var state_color = stateToColor(app_infos['state']); var level_color = levelToColor(parseInt(app_infos['level'])); var is_safe_for_install_color = combineColors(state_color, level_color); if ((is_safe_for_install_color === "warning") || (is_safe_for_install_color === "danger")) { c.confirm( y18n.t("applications"), y18n.t("confirm_install_app_"+is_safe_for_install_color), function(){ c.appInstallForm( app_name, app_infos.manifest, c.params ); }, function(){ $('div.loader').remove(); c.redirect('#/apps/install'); } ); } else { c.appInstallForm( c.params['app'], data[c.params['app']].manifest, c.params ); } }); }); // Install app (POST) app.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']]))) { var params = { label: c.params['label'], app: c.params['app'] }; // Check for duplicate arg produced by empty checkbox. (See inputClone) delete c.params['label']; delete c.params['app']; $.each(c.params, function(k, v) { if (typeof(v) === 'object' && Array.isArray(v)) { // And return only first value c.params[k] = v[0]; } }); 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 app.post('#/apps/install/custom', function(c) { var params = { label: c.params['label'], app: c.params['url'] }; delete c.params['label']; delete c.params['url']; c.confirm( y18n.t('applications'), y18n.t('confirm_install_custom_app'), function(){ // Force trailing slash params.app = params.app.replace(/\/?$/, '/'); // Get manifest.json to get additional parameters jQuery.ajax({ url: params.app.replace('github.com', 'raw.githubusercontent.com') + 'master/manifest.json', type: 'GET', }) .done(function(manifest) { // raw.githubusercontent.com serve content as plain text manifest = jQuery.parseJSON(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 app.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 app.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(username, user){ // Do not list allowed_users in select list if ( data.settings.allowed_users.indexOf(username) === -1 ) { data.users.push({ value: username, label: user.fullname+' ('+user.mail+')' }); } else { // Complete allowed_users data data.settings.allowed_users[data.settings.allowed_users.indexOf(username)] = { username: username, fullname: user.fullname, mail: user.mail, }; } }); c.view('app/app_access', data); }); }); }); // Remove all access app.get('#/apps/:app/access/remove', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_remove_all', [c.params['app']]), function() { var 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 app.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() { var 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 app.get('#/apps/:app/access/add', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_add', [c.params['app']]), function() { var 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 app.post('#/apps/:app/access/add', function (c) { var 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) app.get('#/apps/:app/access/clear', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_access_clear', [c.params['app']]), function() { var 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 app.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']); } ); }); // Get app change label page app.get('#/apps/:app/changelabel', function (c) { c.api('/apps/'+c.params['app']+'?raw', function(app_data) { data = { id: c.params['app'], label: app_data.settings.label, }; c.view('app/app_changelabel', data); }); }); // Change app label app.post('#/apps/:app/changelabel', function (c) { params = {'new_label': c.params['label']}; c.api('/apps/' + c.params['app'] + '/label', function(data) { // Call changelabel API store.clear('slide'); c.redirect('#/apps/'+ c.params['app']); }, 'PUT', params); }); // Get app change URL page app.get('#/apps/:app/changeurl', function (c) { c.api('/apps/'+c.params['app']+'?raw', function(app_data) { c.api('/domains', function(domain_data) { // http://api.yunohost.org/#!/domain/domain_list_get_2 // Display a list of available domains var domains = []; $.each(domain_data.domains, function(k, domain) { domains.push({ value: domain, label: domain, // Select current domain selected: (domain == app_data.settings.domain ? true : false) }); }); data = { id: c.params['app'], label: app_data.manifest.name, domains: domains, // Pre-fill with current path path: app_data.settings.path }; c.view('app/app_changeurl', data); }); }); }); // Change app URL app.post('#/apps/:app/changeurl', function (c) { c.confirm( y18n.t('applications'), y18n.t('confirm_app_change_url', [c.params['app']]), function() { params = {'domain': c.params['domain'], 'path': c.params['path']}; c.api('/apps/' + c.params['app'] + '/changeurl', function(data) { // Call changeurl API store.clear('slide'); c.redirect('#/apps/'+ c.params['app']); }, 'PUT', params); }, function() { store.clear('slide'); c.redirect('#/apps/'+ c.params['app'] + '/changeurl'); } ); }); })();