diff --git a/src/css/style.less b/src/css/style.less index 21b27fa8..1f362492 100644 --- a/src/css/style.less +++ b/src/css/style.less @@ -706,12 +706,15 @@ input[type='radio'].nice-radio { #view-groups { .panel-heading a { text-decoration: none; - &.group-delete { - float:right; - color:lighten(@label-danger-bg, 20%); - :hover { - color:@label-danger-bg; - } + } + .group-delete { + font-size: 24px; + line-height: 24px; + padding: 0; + float:right; + color:lighten(@label-danger-bg, 20%); + :hover { + color:@label-danger-bg; } } .panel-body { @@ -725,6 +728,10 @@ input[type='radio'].nice-radio { .dropdown-menu { max-height: 200px; overflow-y: auto; + + button { + background: none; + } } .label-removable { // The following match properties from regular btn's @@ -743,15 +750,17 @@ input[type='radio'].nice-radio { margin-right:7px; // Spacing between labels - > a { + > button { + line-height: 12px; margin-left:6px; + padding: 0; padding-left:6px; border-left: #ccc 1px solid; color:lighten(@label-info-bg,20); text-decoration: none; } - > a:hover { + > button:hover { color:@label-info-bg; } } @@ -925,3 +934,10 @@ input[type='radio'].nice-radio { float: none !important; } } + +.notransition { + -webkit-transition: none !important; + -moz-transition: none !important; + -o-transition: none !important; + transition: none !important; +} diff --git a/src/index.html b/src/index.html index 377f1727..c693f55a 100644 --- a/src/index.html +++ b/src/index.html @@ -81,8 +81,8 @@
diff --git a/src/js/yunohost/controllers/apps.js b/src/js/yunohost/controllers/apps.js index a9694ae0..17c91e10 100644 --- a/src/js/yunohost/controllers/apps.js +++ b/src/js/yunohost/controllers/apps.js @@ -10,7 +10,7 @@ // List installed apps app.get('#/apps', function (c) { - c.api('/apps?installed', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 + c.api('GET', '/apps?installed', {}, function(data) { var apps = data['apps']; c.arraySortById(apps); c.view('app/app_list', {apps: apps}); @@ -109,8 +109,8 @@ // 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 + c.api('GET', '/apps', {}, function (data) { + c.api('GET', '/apps?raw', {}, function (dataraw) { var apps = [] $.each(data['apps'], function(k, v) { app = dataraw[v['id']]; @@ -201,30 +201,59 @@ // 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 - c.api('/users/permissions', function(data_permissions) { + c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(data) { + c.api('GET', '/users/permissions', {}, function(data_permissions) { - // Permissions - data.permissions = data_permissions.permissions[c.params['app']+".main"]["allowed"]; + // Permissions + data.permissions = data_permissions.permissions[c.params['app']+".main"]["allowed"]; - // Multilingual description - data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ? - data.manifest.description[y18n.locale] : - data.manifest.description['en'] - ; + // 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); + // 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); + c.view('app/app_info', data, function() { + + // Button to set the app as default + $('button[data-action="set-as-default"]').on("click", function() { + var app = $(this).data("app"); + c.confirm( + y18n.t('applications'), + y18n.t('confirm_app_default'), + function() { c.api('PUT', '/apps/'+app+'/default', {}, function() { c.refresh() }); } + ); + }); + + // Button to uninstall the app + $('button[data-action="uninstall"]').on("click", function() { + var app = $(this).data("app"); + c.confirm( + y18n.t('applications'), + y18n.t('confirm_uninstall', [app]), + function() { + c.api('DELETE', '/apps/'+ app, {}, function() { + c.redirect_to('#/apps'); + }); + } + ); + }); }); }); + }); }); + // + // App actions + // + // Get app actions list app.get('#/apps/:app/actions', function (c) { - c.api('/apps/'+c.params['app']+'/actions', function(data) { + c.api('GET', '/apps/'+c.params['app']+'/actions', {}, function(data) { $.each(data.actions, function(_, action) { formatYunoHostStyleArguments(action.arguments, c.params); @@ -241,7 +270,7 @@ }); }); - // Perform application + // Perform app action app.put('#/apps/:app/actions/:action', function(c) { // taken from app install $.each(c.params, function(k, v) { @@ -260,14 +289,18 @@ '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); + c.api('PUT', '/apps/'+app_id+'/actions/'+action_id, params, function() { + c.redirect_to('#/apps/'+app_id+'/actions', {slide:false}); + }); }); + // + // App config panel + // + // Get app config panel app.get('#/apps/:app/config-panel', function (c) { - c.api('/apps/'+c.params['app']+'/config-panel', function(data) { + c.api('GET', '/apps/'+c.params['app']+'/config-panel', {}, function(data) { $.each(data.config_panel.panel, function(_, panel) { $.each(panel.sections, function(_, section) { formatYunoHostStyleArguments(section.options, c.params); @@ -293,16 +326,16 @@ '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); + c.api('POST', '/apps/'+app_id+'/config', params, function() { + c.redirect_to('#/apps/'+app_id+'/config-panel', {slide:false}); + }); }) // 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'); + c.redirect_to('#/apps/install'); }); // Helper function that formats YunoHost style arguments for generating a form @@ -473,7 +506,7 @@ // 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 + c.api('GET', '/apps?raw', {}, function(data) { var app_name = c.params["app"]; var app_infos = data[app_name]; if (app_infos['state'] === "validated") @@ -495,10 +528,6 @@ app_infos.manifest, c.params ); - }, - function(){ - $('div.loader').remove(); - c.redirect('#/apps/install'); } ); } @@ -539,14 +568,13 @@ delete params['args']; } - c.api('/apps', function() { // http://api.yunohost.org/#!/app/app_install_post_2 - c.redirect('#/apps'); - }, 'POST', params); + c.api('POST', '/apps', params, function() { + c.redirect_to('#/apps'); + }); } else { c.flash('warning', y18n.t('app_install_cancel')); - store.clear('slide'); - c.redirect('#/apps/install'); + c.refresh(); } }); @@ -586,77 +614,35 @@ }) .fail(function(xhr) { c.flash('fail', y18n.t('app_install_custom_no_manifest')); - store.clear('slide'); - c.redirect('#/apps/install'); + c.refresh(); }); - }, - 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']); - } - ); - }); - - // 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 = { + c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(app_data) { + data = { id: c.params['app'], label: app_data.settings.label, - }; - c.view('app/app_changelabel', data); + }; + 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); + c.api('PUT', '/apps/' + c.params['app'] + '/label', params, function(data) { + c.redirect_to('#/apps/'+ c.params['app']); + }); }); // 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 + c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(app_data) { + c.api('GET', '/domains', {}, function(domain_data) { // Display a list of available domains var domains = []; @@ -688,14 +674,9 @@ 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'); + c.api('PUT', '/apps/' + c.params['app'] + '/changeurl', params, function(data) { + c.redirect_to('#/apps/'+ c.params['app']); + }); } ); }); diff --git a/src/js/yunohost/controllers/backup.js b/src/js/yunohost/controllers/backup.js index a4f77b27..42a5d455 100644 --- a/src/js/yunohost/controllers/backup.js +++ b/src/js/yunohost/controllers/backup.js @@ -32,118 +32,9 @@ c.view('backup/backup', {'storages':storages}); }); - // Storage list - app.get('#/storages/create', function (c) { - c.view('backup/storage_create', {}); - }); - - // Create a storage - app.post('#/storages', function (c) { - store.clear('slide'); - c.redirect('#/storages'); - }); - - // Create a backup - app.get('#/backup/:storage/create', function (c) { - var data = []; - data['storage'] = { - id:c.params['storage'], - name:y18n.t('local_archives') - }; - c.api('/hooks/backup', function(hooks) { - data['hooks'] = c.groupHooks(hooks['hooks']); - data['apps'] = {}; - c.api('/apps?with_backup', function(apps_list) { - data['apps'] = apps_list.apps; - c.view('backup/backup_create', data, c.selectAllOrNone); - }); - }); - }); - - - app.post('#/backup/:storage', function (c) { - var params = c.ungroupHooks(c.params['system_parts'],c.params['apps']); - c.api('/backup', function() { - store.clear('slide'); - c.redirect('#/backup/'+ c.params['storage']); - }, 'POST', params); - }); - - // Restore a backup - app.post('#/backup/:storage/:archive/restore', function (c) { - c.confirm( - y18n.t('backup'), - y18n.t('confirm_restore', [c.params['archive']]), - $.proxy(function(c){ - var params = c.ungroupHooks(c.params['system_parts'],c.params['apps']); - params['force'] = ''; - c.api('/backup/restore/'+c.params['archive'], function(data) { - store.clear('slide'); - c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']); - }, 'POST', params); - }, this, c), - function(){ - store.clear('slide'); - c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']); - } - ); - }); - - // Delete a backup - app.get('#/backup/:storage/:archive/delete', function (c) { - c.confirm( - y18n.t('backup'), - y18n.t('confirm_delete', [c.params['archive']]), - function(){ - c.api('/backup/archives/'+c.params['archive'], function(data) { - c.redirect('#/backup/'+ c.params['storage']); - }, 'DELETE'); - }, - function(){ - store.clear('slide'); - c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']); - } - ); - }); - - // Download a backup - app.get('#/backup/:storage/:archive/download', function (c) { - c.api('/backup/'+c.params['archive']+'/download', function(data) { - c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']); - }, 'GET'); - }); - - // Copy a backup - app.get('#/backup/:storage/:archive/copy', function (c) { - store.clear('slide'); - c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']); - }); - - // Upload a backup - app.get('#/backup/:storage/:archive/upload', function (c) { - store.clear('slide'); - c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']); - }); - - // Get archive info - app.get('#/backup/:storage/:archive', function (c) { - c.api('/backup/archives/'+c.params['archive']+'?with_details', function(data) { - data.storage = { - id: c.params['storage'], - name: y18n.t('local_archives') - }; - data.other_storages = []; - data.name = c.params['archive']; - data.system_parts = c.groupHooks(Object.keys(data['system']),data['system']); - data.items = (data['system']!={} || data['apps']!=[]); - data.locale = y18n.locale - c.view('backup/backup_info', data, c.selectAllOrNone); - }); - }); - // Archive list app.get('#/backup/:storage', function (c) { - c.api('/backup/archives?with_info', function(data) { + c.api('GET', '/backup/archives?with_info', {}, function(data) { data.storage = { id: 'local', name: y18n.t('local_archives') @@ -159,4 +50,173 @@ }); }); + // View to create a backup + app.get('#/backup/:storage/create', function (c) { + var data = []; + data['storage'] = { + id:c.params['storage'], + name:y18n.t('local_archives') + }; + c.api('GET', '/hooks/backup', {}, function(hooks) { + data['hooks'] = groupHooks(hooks['hooks']); + data['apps'] = {}; + c.api('GET', '/apps?with_backup', {}, function(apps_list) { + data['apps'] = apps_list.apps; + c.view('backup/backup_create', data, function() { + + // Configure buttons "select all" and "select none" + + // 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); + }); + }); + }); + }); + }); + + // Actually creating the backup + app.post('#/backup/:storage', function (c) { + var params = ungroupHooks(c.params['system_parts'],c.params['apps']); + c.api('POST', '/backup', params, function() { + c.redirect_to('#/backup/'+ c.params['storage']); + }); + }); + + // Get archive info + app.get('#/backup/:storage/:archive', function (c) { + c.api('GET', '/backup/archives/'+c.params['archive']+'?with_details', {}, function(data) { + data.storage = { + id: c.params['storage'], + name: y18n.t('local_archives') + }; + data.name = c.params['archive']; + data.system_parts = groupHooks(Object.keys(data['system']),data['system']); + data.items = (data['system']!={} || data['apps']!=[]); + data.locale = y18n.locale; + c.view('backup/backup_info', data, function() { + + // Configure buttons "select all" and "select none" + + // 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); + }); + + // Delete button + $('button[data-action="delete"]').on('click', function() { + var storage = $(this).data('storage'); + var archive = $(this).data('archive'); + c.confirm( + y18n.t('backup'), + y18n.t('confirm_delete', [archive]), + function(){ + c.api('DELETE', '/backup/archives/'+archive, {}, function(data) { + c.redirect_to('#/backup/'+ storage); + }); + } + ); + }); + }); + }); + }); + + // Restore a backup + app.post('#/backup/:storage/:archive/restore', function (c) { + c.confirm( + y18n.t('backup'), + y18n.t('confirm_restore', [c.params['archive']]), + $.proxy(function(c){ + var params = ungroupHooks(c.params['system_parts'],c.params['apps']); + params['force'] = ''; + c.api('POST', '/backup/restore/'+c.params['archive'], params, function(data) { + c.redirect_to('#/backup/'+ c.params['storage']+'/'+c.params['archive']); + }); + }, this, c) + ); + }); + + function groupHooks(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; + }; + + function ungroupHooks(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; + }; + + })(); diff --git a/src/js/yunohost/controllers/domains.js b/src/js/yunohost/controllers/domains.js index d50da321..51850f90 100644 --- a/src/js/yunohost/controllers/domains.js +++ b/src/js/yunohost/controllers/domains.js @@ -10,8 +10,8 @@ // List existing domains app.get('#/domains', function (c) { - c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2 - c.api('/domains/main', function(data2) { + c.api('GET', '/domains', {}, function(data) { + c.api('PUT', '/domains/main', {}, function(data2) { var domains = []; $.each(data.domains, function(k, domain) { domains.push({ @@ -29,7 +29,7 @@ domains: domains, main_domain_form: main_domain_form }); - }, 'PUT'); + }); }); }); @@ -68,8 +68,7 @@ if (c.params['domain'] === '') { if (c.params['ddomain'] === '') { c.flash('fail', y18n.t('error_select_domain')); - store.clear('slide'); - c.redirect('#/domains/add'); + c.redirect_to('#/domains/add'); } params.domain = c.params['ddomain'] + c.params['ddomain-ext']; endurl = 'dyndns'; @@ -77,42 +76,56 @@ params.domain = c.params['domain']; } - c.api('/domains?'+endurl, function(data) { // http://api.yunohost.org/#!/domain/domain_add_post_1 - c.redirect('#/domains'); - }, 'POST', params); + c.api('POST', '/domains?'+endurl, params, function(data) { + c.redirect_to('#/domains'); + }); }); // Get existing domain info app.get('#/domains/:domain', function (c) { - c.api('/domains/main', function(dataMain) { - c.api('/apps?installed', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 - - // FIXME - This dirty trick (along with the previous API call - // for apps installed) should be removed once letsencrypt_ynh - // is not used by many people anymore. Probably around 07/2017 - // or end of 2017... - var enable_cert_management_ = true; - $.each(data['apps'], function(k, v) { - if (v.id == "letsencrypt") { - enable_cert_management_ = false; - } - }); - + c.api('PUT', '/domains/main', {}, function(dataMain) { + c.api('GET', '/apps?installed', {}, function(data) { var domain = { name: c.params['domain'], main: (c.params['domain'] == dataMain.current_main_domain) ? true : false, - url: "https://"+c.params['domain'], - enable_cert_management: enable_cert_management_ + url: "https://"+c.params['domain'] }; - c.view('domain/domain_info', domain); + c.view('domain/domain_info', domain, function() { + + // Configure "set default" button + $('button[data-action="set_default"]').on("click", function() { + var domain = $(this).data("domain"); + c.confirm( + y18n.t('domains'), + y18n.t('confirm_change_maindomain'), + function() { + c.api('PUT', '/domains/main', {new_main_domain: domain}, function() { c.refresh() }); + } + ) + }); + + // Configure delete button + $('button[data-action="delete"]').on("click", function() { + var domain = $(this).data("domain"); + c.confirm( + y18n.t('domains'), + y18n.t('confirm_delete', [domain]), + function(){ + c.api('DELETE', '/domains/'+ domain, {}, function(data) { + c.redirect_to('#/domains'); + }); + } + ); + }); + }); }); - }, 'PUT'); + }); }); // Domain DNS app.get('#/domains/:domain/dns', function (c) { - c.api('/domains/' + c.params['domain'] + '/dns', function(data) { + c.api('GET', '/domains/' + c.params['domain'] + '/dns', {}, function(data) { var domain = { name: c.params['domain'], dns: data @@ -123,7 +136,7 @@ // Domain certificate app.get('#/domains/:domain/cert-management', function (c) { - c.api('/domains/cert-status/' + c.params['domain'] + '?full', function(data) { + c.api('GET', '/domains/cert-status/' + c.params['domain'] + '?full', {}, function(data) { var s = data["certificates"][c.params['domain']]; var status_ = { @@ -199,132 +212,45 @@ status: status_, actions_enabled : actions_enabled }; - c.view('domain/domain_cert', data_); + + c.view('domain/domain_cert', data_, function() { + // Configure install / renew buttons behavior + $("button[data-action]").on("click", function () { + var action = $(this).data("action"), + domain = $(this).data("domain"), + confirm_key = "", + api_url = ""; + + switch (action) { + case 'install-LE': + confirm_key = 'confirm_cert_install_LE'; + api_url = '/domains/cert-install/' + domain; + break; + case 'regen-selfsigned': + confirm_key = 'confirm_cert_regen_selfsigned'; + api_url = '/domains/cert-install/' + domain + "?self_signed"; + break; + case 'renew-letsencrypt': + confirm_key = 'confirm_cert_manual_renew_LE'; + api_url = '/domains/cert-renew/' + domain + "?force"; + break; + case 'replace-with-selfsigned': + confirm_key = 'confirm_cert_revert_to_selfsigned'; + api_url = '/domains/cert-install/' + domain + "?self_signed&force"; + break; + default: + c.flash('fail', y18n.t('unknown_action', [action])); + return + } + + c.confirm( + y18n.t('certificate'), + y18n.t(confirm_key, [domain]), + function(){ c.api('POST', api_url, {}, function() { c.refresh() }); } + ); + }); + }); }); }); - // Install let's encrypt certificate on domain - app.get('#/domains/:domain/cert-install-LE', function (c) { - c.confirm( - y18n.t('certificate'), - y18n.t('confirm_cert_install_LE', [c.params['domain']]), - function(){ - c.api('/domains/cert-install/' + c.params['domain'], function(data) { - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - }, 'POST'); - }, - function(){ - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - } - ); - }); - - // Regenerate a self-signed certificate - app.get('#/domains/:domain/cert-regen-selfsigned', function (c) { - c.confirm( - y18n.t('certificate'), - y18n.t('confirm_cert_regen_selfsigned', [c.params['domain']]), - function(){ - c.api('/domains/cert-install/' + c.params['domain'] + "?self_signed", function(data) { - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - }, 'POST'); - }, - function(){ - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - } - ); - }); - - // Manually renew a Let's Encrypt certificate - app.get('#/domains/:domain/cert-renew-letsencrypt', function (c) { - c.confirm( - y18n.t('certificate'), - y18n.t('confirm_cert_manual_renew_LE', [c.params['domain']]), - function(){ - c.api('/domains/cert-renew/' + c.params['domain'] + "?force", function(data) { - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - }, 'POST'); - }, - function(){ - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - } - ); - }); - - // Replace valid cert with self-signed - app.get('#/domains/:domain/cert-replace-with-selfsigned', function (c) { - c.confirm( - y18n.t('certificate'), - y18n.t('confirm_cert_revert_to_selfsigned', [c.params['domain']]), - function(){ - c.api('/domains/cert-install/' + c.params['domain'] + "?self_signed&force", function(data) { - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - }, 'POST'); - }, - function(){ - store.clear('slide'); - c.redirect('#/domains/'+c.params['domain']+'/cert-management'); - } - ); - }); - - - // Remove existing domain - app.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 - app.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(){ - var params = { - new_main_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 - var refreshDomain = window.setTimeout(function(){ - store.clear('slide'); - c.redirect('#/domains'); - }, 15000); - }, - function(){ - store.clear('slide'); - c.redirect('#/domains'); - } - ); - } - }); - })(); diff --git a/src/js/yunohost/controllers/firewall.js b/src/js/yunohost/controllers/firewall.js index beeff865..013d2511 100644 --- a/src/js/yunohost/controllers/firewall.js +++ b/src/js/yunohost/controllers/firewall.js @@ -10,7 +10,7 @@ // Firewall status app.get('#/tools/firewall', function (c) { - c.api('/firewall?raw', function(data) { + c.api('GET', '/firewall?raw', {}, function(data) { var firewall = { ports: {}, upnp: false @@ -30,28 +30,49 @@ // Get UPnP status firewall.upnp = data.uPnP.enabled; - c.view('tools/tools_firewall', firewall); + c.view('tools/tools_firewall', firewall, function() { + + // Buttons in the 'ports' panel to open/close specific ports + $("button[data-port]").on("click", function() { + + var port = $(this).data("port"); + var action = $(this).data("action"); + var protocol = $(this).data("protocol"); + var connection = $(this).data("connection"); + c.confirm( + y18n.t('firewall'), + // confirm_firewall_open and confirm_firewall_close + y18n.t('confirm_firewall_' + action, [ port, y18n.t(protocol), y18n.t(connection)]), + function(){ c.togglePort(port, protocol, connection, action); } + ); + }); + + // Buttons to enable / disable UPnP + $("button[data-upnp]").on("click", function() { + var action = $(this).data("upnp"); + c.confirm( + y18n.t('firewall'), + // confirm_upnp_enable and confirm_upnp_disable + y18n.t('confirm_upnp_' + action), + function(){ c.api('GET', '/firewall/upnp', {action: action}, function() { c.refresh() }); } + ); + }); + }); }); }); - // Enable/Disable UPnP - app.get('#/tools/firewall/upnp/:action', function (c) { + // Update port status from form + app.post('#/tools/firewall/port', function (c) { c.confirm( y18n.t('firewall'), - // confirm_upnp_enable and confirm_upnp_disable - y18n.t('confirm_upnp_' + c.params['action'].toLowerCase()), + y18n.t('confirm_firewall_' + c.params['action'].toLowerCase(), [ c.params['port'], y18n.t(c.params['protocol']), y18n.t(c.params['connection']) ]), function(){ - var params = { - action : c.params['action'] - }; - c.api('/firewall/upnp', function(data) { - store.clear('slide'); - c.redirect('#/tools/firewall'); - }, 'GET', params); - }, - function(){ - store.clear('slide'); - c.redirect('#/tools/firewall'); + c.togglePort( + c.params['port'], + c.params['protocol'], + c.params['connection'], + c.params['action'] + ); } ); }); @@ -65,8 +86,7 @@ if (port != parseInt(port) || port < 0 || port > 65535) { c.flash('fail', y18n.t('unknown_argument', [port])); - store.clear('slide'); - c.redirect('#/tools/firewall'); + c.refresh(); } switch (connection) { @@ -98,75 +118,27 @@ break; default: c.flash('fail', y18n.t('unknown_action', [action])); - store.clear('slide'); - c.redirect('#/tools/firewall'); + c.refresh(); } - if (method !== null && protocol !== null && port !== null) { - // port: - // protocol: - // - UDP - // - TCP - // - Both - // --ipv4-only: - // --ipv6-only: - // --no-upnp: - var params = { - port : port, - protocol : protocol - }; - c.api('/firewall/port?'+endurl, function(data) { - store.clear('slide'); - c.redirect('#/tools/firewall'); - }, method, params); - } - else { - store.clear('slide'); - c.redirect('#/tools/firewall'); - } + // port: + // protocol: + // - UDP + // - TCP + // - Both + // --ipv4-only: + // --ipv6-only: + // --no-upnp: + var params = { + port : port, + protocol : protocol + }; + + c.api(method, '/firewall/port?'+endurl, params, function() { c.refresh() }); + + return; }); - // Update port status from direct link - // #/firewall/port/{{@key}}/tcp/ipv4/close - app.get('#/tools/firewall/port/:port/:protocol/:connection/:action', function (c) { - c.confirm( - y18n.t('firewall'), - // confirm_firewall_open and confirm_firewall_close - y18n.t( 'confirm_firewall_' + c.params['action'].toLowerCase(), [ 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('#/tools/firewall'); - } - ); - }); - // Update port status from form - app.post('#/tools/firewall/port', function (c) { - c.confirm( - y18n.t('firewall'), - y18n.t('confirm_firewall_' + c.params['action'].toLowerCase(), [ 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('#/tools/firewall'); - } - ); - }); - -})(); \ No newline at end of file +})(); diff --git a/src/js/yunohost/controllers/home.js b/src/js/yunohost/controllers/home.js index ac315c21..9038ed76 100644 --- a/src/js/yunohost/controllers/home.js +++ b/src/js/yunohost/controllers/home.js @@ -24,45 +24,36 @@ $('#masthead').show() .find('.logout-btn').hide(); store.set('path-1', '#/login'); - if ($('div.loader').length === 0) { - $('#main').append('
'); + + c.showLoader(); + + // We gonna retry 3 times to check if yunohost is installed + if (app.isInstalledTry === undefined) { + app.isInstalledTry = 3; } c.checkInstall(function(isInstalled) { + if (isInstalled) { - // Remove loader - $('div.loader').remove(); - // Pass domain to hide form field c.view('login', { 'domain': window.location.hostname }); - } else if (typeof isInstalled === 'undefined') { - if (app.isInstalledTry > 0) { - app.isInstalledTry--; - app.loaded = false; // Show pacman - setTimeout(function() { - c.redirect('#/'); - }, 5000); - } - else { - // Reset count - app.isInstalledTry = 3; + return; + } - // 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 - app.loaded = true; - $('div.loader').remove(); - } - } else { - $('div.loader').remove(); + if (typeof isInstalled !== 'undefined') { c.redirect('#/postinstall'); + return; + } + + // If the retry counter is still up, retry this function 5 sec + // later + if (app.isInstalledTry > 0) { + app.isInstalledTry--; + setTimeout(function() { + c.redirect('#/'); + }, 5000); + } + else { + c.flash('fail', y18n.t('api_not_responding')); } }); }); @@ -80,7 +71,7 @@ var params = { password: c.params['password'] }; - c.api('/login', function(data) { + c.api('POST', '/login', params, function(data) { store.set('connected', true); c.trigger('login'); $('#masthead .logout-btn').fadeIn(); @@ -90,19 +81,19 @@ } else { c.redirect('#/'); } - }, 'POST', params, false); + }, undefined, false); }); app.get('#/logout', function (c) { - c.api('/logout', function (data) { + c.api('GET', '/logout', {}, function (data) { store.clear('url'); store.clear('connected'); store.set('path', '#/'); c.trigger('logout'); c.flash('success', y18n.t('logged_out')); c.redirect('#/login'); - }, 'GET', {}, false); + }, undefined, false); }); })(); diff --git a/src/js/yunohost/controllers/postinstall.js b/src/js/yunohost/controllers/postinstall.js index 04f7d7e9..d8fc96a7 100644 --- a/src/js/yunohost/controllers/postinstall.js +++ b/src/js/yunohost/controllers/postinstall.js @@ -13,7 +13,7 @@ $('#masthead').hide(); c.checkInstall(function(isInstalled) { if (isInstalled || typeof isInstalled === 'undefined') { - c.redirect('#/login'); + c.redirect_to('#/login'); } else { c.view('postinstall/postinstall_1'); } @@ -41,7 +41,6 @@ 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(); @@ -51,7 +50,7 @@ } store.set('maindomain', domain); }); - }, false); // We disable enableSlide because that causes some issues with accordion when using the 'previous' button + }); }); }); @@ -59,49 +58,52 @@ app.get('#/postinstall/password', function(c) { $('#masthead').hide(); if (!store.get('maindomain')) { - store.clear('slide'); - c.redirect('#/postinstall/domain'); + c.redirect_to('#/postinstall/domain'); } else { - c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() }, - function() { }, - false); // We disable enableSlide because that causes some issues with accordion when using the 'previous' button + c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() }); } }); // Execute post-installation app.post('#/postinstall', function (c) { - if (c.params['password'] === '' || c.params['confirmation'] === '') { + + var password = c.params['password']; + var confirmation = c.params['confirmation']; + var domain = c.params['domain'].toLowerCase(); + + // Check password ain't empty + if (password === '' || confirmation === '') { c.flash('fail', y18n.t('password_empty')); + return; } - 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 { - var 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 { + // Check password matches confirmation + if (password !== confirmation) { c.flash('fail', y18n.t('passwords_dont_match')); + return; } + + // Check domain ain't empty... + if (domain === '') { + c.flash('fail', y18n.t('error_select_domain')); + c.redirect_to('#/postinstall/domain', {slide: false}); + return; + } + + // Ask confirmation to the user + c.confirm( + y18n.t('postinstall'), + y18n.t('confirm_postinstall', [c.params['domain']]), + // Start the actual postinstall process + function(){ + store.set('url', window.location.hostname +'/yunohost/api'); + store.set('user', 'admin'); + c.api('POST', '/postinstall', {domain: domain, password: password}, function() { + c.flash('success', y18n.t('installation_complete')); + c.redirect_to('#/login'); + }); + } + ); }); })(); diff --git a/src/js/yunohost/controllers/services.js b/src/js/yunohost/controllers/services.js index e3de3554..113c34fc 100644 --- a/src/js/yunohost/controllers/services.js +++ b/src/js/yunohost/controllers/services.js @@ -10,7 +10,7 @@ // All services status app.get('#/services', function (c) { - c.api('/services', function(data) { // ? + c.api('GET', '/services', {}, function(data) { var data2 = { services: [] }; @@ -45,7 +45,7 @@ // Status & actions for a service app.get('#/services/:service', function (c) { - c.api('/services/'+ c.params['service'], function(data) { // ? + c.api('GET', '/services/'+ c.params['service'], {}, function(data) { var data2 = { service: data }; @@ -64,9 +64,45 @@ { data2.service.active_at = 0; } - store.clear('slide'); - c.view('service/service_info', data2); - }, 'GET'); + c.view('service/service_info', data2, function() { + + // Configure behavior for enable/disable and start/stop buttons + $('button[data-action]').on('click', function() { + + var service = $(this).data('service'); + var action = $(this).data('action'); + + c.confirm("Service", y18n.t('confirm_service_' + action, [service]), function(){ + + var method = null, + endurl = service; + + switch (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', [action])); + c.refresh(); + return; + } + + c.api(method, '/services/'+ endurl, {}, function() { c.refresh(); }); + }); + }); + }); + }); }); // Service log @@ -74,63 +110,14 @@ var params = { number: 50 }; - c.api('/services/'+ c.params['service'] +'/log', function(data) { // ? + c.api('GET', '/services/'+ c.params['service'] +'/log', params, 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 - app.get('#/services/:service/:action', function (c) { - c.confirm( - "Service", - // confirm_service_start, confirm_service_stop, confirm_service_enable and confirm_service_disable - y18n.t('confirm_service_' + c.params['action'].toLowerCase(), [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']); - } - ); + }); }); })(); diff --git a/src/js/yunohost/controllers/tools.js b/src/js/yunohost/controllers/tools.js index 8c7fb36c..4e62c693 100644 --- a/src/js/yunohost/controllers/tools.js +++ b/src/js/yunohost/controllers/tools.js @@ -26,77 +26,69 @@ }); 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 { - c.api('/login', function(data) { - // Remove useless variable - delete params['old_password']; - 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); - }, 'POST', { 'password': params['old_password'] }, false); + c.refresh(); + return; } + if (params['new_password'] !== params['confirm_new_password']) { + c.flash('fail', y18n.t('passwords_dont_match')); + c.refresh(); + return; + } + + c.api('POST', '/login', { 'password': params['old_password'] }, function(data) { + // Remove useless variable + delete params['old_password']; + delete params['confirm_new_password']; + + // Update password and redirect to the home + c.api('PUT', '/adminpw', params, function(data) { + c.redirect_to('#/logout'); + }); + }, undefined, false); }); // System update & upgrade app.get('#/update', function (c) { - c.api('/update', function(data) { - c.view('update/update', data); - }, 'PUT'); - }); + c.api('PUT', '/update', {}, function(data) { + c.view('tools/tools_update', data, function() { + // Configure buttons behaviors + $("button[data-upgrade]").on("click", function() { - // Upgrade apps or packages - app.get('#/upgrade/:type', function (c) { - c.confirm( - y18n.t('tools'), - // confirm_update_apps and confirm_update_packages - y18n.t('confirm_update_' + c.params['type'].toLowerCase()), - function(){ - c.api('/upgrade?'+c.params["type"], - function(data) { - store.clear('slide'); - c.redirect('#/tools/logs'); - }, - 'PUT'); - }, - function(){ - store.clear('slide'); - c.redirect('#/update'); - } - ); - }); + var what = $(this).data("upgrade").toLowerCase(); - // Upgrade a specific apps - app.get('#/upgrade/apps/:app', function (c) { - c.confirm( - y18n.t('tools'), - y18n.t('confirm_update_specific_app', [c.params['app']]), - function(){ - c.api('/upgrade/apps?app='+c.params['app'].toLowerCase(), - function(data) { - store.clear('slide'); - c.redirect('#/tools/logs'); - }, - 'PUT'); - }, - function(){ - store.clear('slide'); - c.redirect('#/update'); - } - ); + // Upgrade all apps or the system + + if ((what == "system") || (what == "system")) + { + var confirm_message = y18n.t('confirm_update_' + what); + var api_url = '/upgrade?'+what; + } + + // Upgrade a specific apps + + else + { + var confirm_message = y18n.t('confirm_update_specific_app', [what]); + var api_url = '/upgrade/apps?app='+what; + } + + c.confirm( + y18n.t('tools'), + confirm_message, + function(){ + c.api('PUT', api_url, {}, function(data) { + c.redirect_to('#/tools/logs'); + }); + } + ); + }); + }); + }); }); // Display journals list app.get('#/tools/logs', function (c) { - c.api("/logs?limit=25&with_details", function(categories) { + c.api('GET', "/logs?limit=25&with_details", {}, function(categories) { data = []; category_icons = { 'operation': 'wrench', @@ -138,8 +130,8 @@ var params = "?path=" + c.params["splat"][0]; var number = (c.params["number"])?c.params["number"]:50; params += "&number=" + number; - - c.api("/logs/display" + params, function(log) { + + c.api('GET', "/logs/display" + params, {}, function(log) { if ('metadata' in log) { if (!'env' in log.metadata && 'args' in log.metadata) { log.metadata.env = log.metadata.args @@ -149,10 +141,19 @@ "log": log, "next_number": log.logs.length == number ? number * 10:false, "locale": y18n.locale + }, function() { + // Configure behavior for the button to share log on Yunohost (it calls display --share) + $('button[data-action="share"]').on("click", function() { + c.api('GET', '/logs/display?path='+$(this).data('log-id')+'&share', {}, + function(data) { + c.hideLoader(); + window.open(data.url, '_blank'); + }); + }); }); }); }); - + // Download SSL Certificate Authority app.get('#/tools/ca', function (c) { @@ -210,59 +211,44 @@ // Reboot or shutdown button app.get('#/tools/reboot', function (c) { - c.view('tools/tools_reboot'); - }); + c.view('tools/tools_reboot', {}, function() { + // Configure reboot/shutdown buttons behavior + $("button[data-action]").on("click", function() { + var action = $(this).data("action"); - // Reboot or shutdown actions - app.get('#/tools/reboot/:action', function (c) { - var action = c.params['action'].toLowerCase(); - if (action == 'reboot' || action == 'shutdown') { - c.confirm( - y18n.t('tools_' + action), - // confirm_reboot_action_reboot or confirm_reboot_action_shutdown - y18n.t('confirm_reboot_action_' + action), - function(){ - c.api('/'+action+'?force', function(data) { - // This code is not executed due to 502 response (reboot or shutdown) - c.redirect('#/logout'); - }, 'PUT', {}, false, function (xhr) { - c.flash('success', y18n.t('tools_' + action + '_done')) - // Disconnect from the webadmin - store.clear('url'); - store.clear('connected'); - store.set('path', '#/'); + c.confirm( + y18n.t('tools_' + action), + y18n.t('confirm_reboot_action_' + action), + function(){ + c.api('PUT', '/'+action+'?force', {}, function(data) { + // This code is not executed due to 502 response (reboot or shutdown) + c.redirect_to('#/logout'); + }, function (xhr) { + c.flash('success', y18n.t('tools_' + action + '_done')) + // Disconnect from the webadmin + store.clear('url'); + store.clear('connected'); + store.set('path', '#/'); - // Rename the page to allow refresh without ask for rebooting - window.location.href = window.location.href.split('#')[0] + '#/'; - // Display reboot or shutdown info - // We can't use template because now the webserver is off - if (action == 'reboot') { - $('#main').replaceWith('
' + y18n.t('tools_rebooting') + '
'); - } - else { - $('#main').replaceWith('
' + y18n.t('tools_shuttingdown') + '
'); - } + // Rename the page to allow refresh without ask for rebooting + window.location.href = window.location.href.split('#')[0] + '#/'; + // Display reboot or shutdown info + // We can't use template because now the webserver is off + if (action == 'reboot') { + $('#main').replaceWith('
' + y18n.t('tools_rebooting') + '
'); + } + else { + $('#main').replaceWith('
' + y18n.t('tools_shuttingdown') + '
'); + } - // Remove loader if any - $('div.loader').remove(); + c.hideLoader(); - // Force scrollTop on page load - $('html, body').scrollTop(0); - store.clear('slide'); - }); - - }, - function(){ - store.clear('slide'); - c.redirect('#/tools/reboot'); - } - ); - } - else { - c.flash('fail', y18n.t('unknown_action', [action])); - store.clear('slide'); - c.redirect('#/tools/reboot'); - } + // Force scrollTop on page load + $('html, body').scrollTop(0); + }, false); + }); + }); + }); }); // Diagnosis @@ -271,7 +257,7 @@ var private = (c.params.splat[0] == 'private'); var endurl = (private) ? '?private' : ''; - c.api('/diagnosis'+endurl, function(diagnosis) { + c.api('GET', '/diagnosis'+endurl, {}, function(diagnosis) { c.view('tools/tools_diagnosis', { 'diagnosis' : JSON.stringify(diagnosis, undefined, 4), 'raw' : diagnosis, @@ -280,10 +266,10 @@ }); }); - // Reboot or shutdown button + // Migrations app.get('#/tools/migrations', function (c) { - c.api('/migrations?pending', function(pending_migrations) { - c.api('/migrations?done', function(done_migrations) { + c.api('GET', '/migrations?pending', {}, function(pending_migrations) { + c.api('GET', '/migrations?done', {}, function(done_migrations) { pending_migrations = pending_migrations.migrations; done_migrations = done_migrations.migrations; @@ -302,71 +288,45 @@ c.view('tools/tools_migrations', { 'pending_migrations' : pending_migrations.reverse(), 'done_migrations' : done_migrations.reverse() + }, function() { + + // Configure button 'Run' + $('button[data-action="run"]').on("click", function() { + + var disclaimerAcks = $(".disclaimer-ack"); + for (var i = 0 ; i < disclaimerAcks.length ; i++) + { + if (! $(disclaimerAcks[i]).find("input:checked").val()) + { + // FIXME / TODO i18n + c.flash('fail', "Some of these migrations require you to acknowledge a disclaimer before running them."); + c.refresh(); + return; + } + }; + + c.api('POST', '/migrations/migrate?accept_disclaimer', {}, function() { c.refresh(); }); + }); + + // Configure buttons 'Skip' + $('button[data-action="skip"]').on("click", function() { + var migration_id = $(this).data("migration"); + c.confirm( + y18n.t('migrations'), + y18n.t('confirm_migrations_skip'), + function(){ + c.api('POST', '/migrations/migrate?skip&targets=' + migration_id, {}, function() { c.refresh() }); + } + ); + }); }); }); }); }); - app.get('#/tools/migrations/run', function (c) { - var disclaimerAcks = $(".disclaimer-ack"); - var withAcceptDisclaimerFlag = false; - for (var i = 0 ; i < disclaimerAcks.length ; i++) - { - console.log($(disclaimerAcks[i]).find("input:checked").val()); - if (! $(disclaimerAcks[i]).find("input:checked").val()) - { - // FIXME / TODO i18n - c.flash('fail', "Some of these migrations require you to acknowledge a disclaimer before running them."); - c.redirect('#/tools/migrations'); - return; - } - else - { - withAcceptDisclaimerFlag = true; - } - }; - - // Not sure if necessary, but this distinction is to avoid accidentally - // triggering a migration with a disclaimer if one goes to the - // /tools/migrations/run page "directly" somehow ... - if (withAcceptDisclaimerFlag) - { - c.api('/migrations/migrate?accept_disclaimer', - function (data) { - store.clear('slide'); - c.redirect('#/tools/migrations'); - }, 'POST') - } - else - { - c.api('/migrations/migrate', - function (data) { - store.clear('slide'); - c.redirect('#/tools/migrations'); - }, 'POST') - } - }); - - app.get('#/tools/migrations/skip/:migration_id', function (c) { - c.confirm( - y18n.t('migrations'), - y18n.t('confirm_migrations_skip'), - function(){ - c.api('/migrations/migrate?skip&targets=' + c.params['migration_id'], function(data) { - store.clear('slide'); - c.redirect('#/tools/migrations'); - }, 'POST'); - }, - function(){ - store.clear('slide'); - c.redirect('#/tools/migrations'); - } - ); - }); - // List available apps lists app.get('#/tools/appslists', function (c) { - c.api('/appslists', function(data) { + c.api('GET', '/appslists', {}, function(data) { list = []; $.each(data, function(listname, listinfo) { list.push({ @@ -379,7 +339,7 @@ c.view('tools/tools_appslists_list', { appslists: list }); - }, 'GET'); + }); }); // Add a new apps list @@ -389,15 +349,14 @@ 'url' : c.params['appslist_url'] } - c.api('/appslists', function(data) { - store.clear('slide'); - c.redirect('#/tools/appslists/' + list.name); - }, 'PUT', list); + c.api('PUT', '/appslists', list, function(data) { + c.redirect_to('#/tools/appslists/' + list.name); + }); }); // Show appslist info and operations app.get('#/tools/appslists/:appslist', function (c) { - c.api('/appslists', function(data) { + c.api('GET', '/appslists', {}, function(data) { if (typeof data[c.params['appslist']] !== 'undefined') { list = { 'name' : c.params['appslist'], @@ -409,25 +368,23 @@ } else { c.flash('warning', y18n.t('appslists_unknown_list', [c.params['appslist']])); - store.clear('slide'); - c.redirect('#/tools/appslists'); + c.redirect_to('#/tools/appslists', {slide: false}); } - }, 'GET'); + }); }); // Refresh available apps list app.get('#/tools/appslists/refresh', function (c) { - c.api('/appslists', function(data) { - // c.redirect(store.get('path')); - c.redirect('#/apps/install'); - }, 'PUT'); + c.api('PUT', '/appslists', {}, function(data) { + c.redirect_to('#/apps/install', {slide: false}); + }); }); // Refresh specific apps list app.get('#/tools/appslists/:appslist/refresh', function (c) { - c.api('/appslists', function(data) { - c.redirect('#/tools/appslists'); - }, 'PUT', {'name' : c.params['appslist']}); + c.api('PUT', '/appslists', {'name' : c.params['appslist']}, function(data) { + c.redirect_to('#/tools/appslists', {slide: false}); + }); }); // Remove apps list @@ -436,13 +393,9 @@ y18n.t('appslist'), y18n.t('appslists_confirm_remove', [c.params['app']]), function() { - c.api('/appslists', function() { - c.redirect('#/tools/appslists'); - }, 'DELETE', {'name' : c.params['appslist']}); - }, - function() { - store.clear('slide'); - c.redirect('#/tools/appslists/'+ c.params['appslist']); + c.api('DELETE', '/appslists', {'name' : c.params['appslist']}, function() { + c.redirect_to('#/tools/appslists'); + }); } ); }); diff --git a/src/js/yunohost/controllers/users.js b/src/js/yunohost/controllers/users.js index 655ff4c5..7da1bcb6 100644 --- a/src/js/yunohost/controllers/users.js +++ b/src/js/yunohost/controllers/users.js @@ -31,20 +31,20 @@ * @param.item Name of the user or the permission to add or remove * @param.group Name of the group affected * - * This function is built to be apply with params generated by the use of + * This function is built to be apply with params generated by the use of * HTML dataset attributes (e.g. link in the partial inline view "label" in group_list.ms) * * @return void **/ function updateGroup(model, params) { var type = params.type; - var operation = params.operation; + var action = params.action; var item = params.item; var groupname = params.group; var group = data.groups[groupname]; - var to = (operation == 'add')?group[type]:group[type + 'Inv']; - var from = (operation == 'add')?group[type+'Inv']:group[type]; - // Do nothing, if array of destination already contains the item + var to = (action == 'add')?group[type]:group[type + 'Inv']; + var from = (action == 'add')?group[type+'Inv']:group[type]; + // Do nothing, if array of destination already contains the item if (from.indexOf(item) === -1) return; // Hack to disable pacman loader if any @@ -52,22 +52,22 @@ $('#main').append(''); } $('div.loader').css('display', 'none'); - + // Update group var params = {}; var url; if (type == 'members') { url = '/users/groups/' + groupname; - params[operation] = [item]; + params[action] = [item]; } else { url = '/users/permissions/' + item; - params[operation] = [groupname]; + params[action] = [groupname]; } - c.api(url, function(data_update) { + c.api('PUT', url, params, function(data_update) { to.push(item); from.splice(from.indexOf(item), 1); updateView(data); - }, 'PUT', params); + }); } /** @@ -85,29 +85,41 @@ model.groups[group].members.sort(); model.groups[group].membersInv.sort(); } - - // Manual render, we don't use c.view to avoid scrollTop and other + + // Manual render, we don't use c.view to avoid scrollTop and other // uneeded behaviour var rendered = c.render('views/user/group_list.ms', model); rendered.swap(function () { // Add click event to get a nice "reactive" interface - jQuery(".group-update").on('click', function (e) { - updateGroup(model, jQuery(this)[0].dataset); + $("button[data-action='add'], button[data-action='remove']").on('click', function (e) { + updateGroup(model, $(this)[0].dataset); return false; }); - jQuery(".group-add-user").on('click', function (e) { - data.groups[$(this)[0].dataset.user].display = true; + $('button[data-action="add-user-specific-permission"]').on('click', function (e) { + data.groups[$(this).data("user")].display = true; updateView(data); return false; }); + $('button[data-action="delete-group"]').on('click', function (e) { + + var group = $(this).data("group"); + + c.confirm( + y18n.t('groups'), + $('
'+ y18n.t('confirm_delete', [group]) +'
'), + function() { + c.api('DELETE', '/users/groups/'+ group, {}, function(data) { c.refresh(); }); + } + ); + }); }); } app.get('#/groups', function (c) { - c.api('/users/groups?full&include_primary_groups', function(data_groups) { - c.api('/users', function(data_users) { - c.api('/users/permissions?short', function(data_permissions) { + c.api('GET', '/users/groups?full&include_primary_groups', {}, function(data_groups) { + c.api('GET', '/users', {}, function(data_users) { + c.api('GET', '/users/permissions?short', {}, function(data_permissions) { //var perms = data_permissions.permissions; var specific_perms = {}; var all_perms = data_permissions.permissions; @@ -129,7 +141,7 @@ // Declare all_users and visitors has special data_groups.groups['all_users'].special = true; data_groups.groups['visitors'].special = true; - + // Data given to the view with 2 functions to convert technical // permission id to display names data = { @@ -166,33 +178,9 @@ app.post('#/groups/create', function (c) { c.params['groupname'] = c.params['groupname'].replace(' ', '_').toLowerCase(); - c.api('/users/groups', function(data) { - c.redirect('#/groups'); - }, 'POST', c.params.toHash()); - }); - - app.get('#/groups/:group/delete', function (c) { - - var params = {}; - - // make confirm content - var confirmModalContent = $('
'+ y18n.t('confirm_delete', [c.params['group']]) +'
'); - - // display confirm modal - c.confirm( - y18n.t('groups'), - confirmModalContent, - function(){ - c.api('/users/groups/'+ c.params['group'], function(data) { - c.redirect('#/groups'); - }, 'DELETE', params); - }, - function(){ - //store.clear('slide'); - c.redirect('#/groups'); - } - ); - + c.api('POST', '/users/groups', c.params.toHash(), function(data) { + c.redirect_to('#/groups'); + }); }); /** @@ -202,14 +190,14 @@ // List existing users app.get('#/users', function (c) { - c.api('/users', function(data) { // http://api.yunohost.org/#!/user/user_list_get_3 + c.api('GET', '/users', {}, function(data) { c.view('user/user_list', data); }); }); // Create user form app.get('#/users/create', function (c) { - c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2 + c.api('GET', '/domains', {}, function(data) { // Password min length data.password_min_length = PASSWORD_MIN_LENGTH; @@ -230,7 +218,6 @@ if (c.params['password'] == c.params['confirmation']) { if (c.params['password'].length < PASSWORD_MIN_LENGTH) { c.flash('fail', y18n.t('password_too_short')); - store.clear('slide'); } else { // Force unit or disable quota @@ -242,28 +229,63 @@ // Compute email field 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()); + c.api('POST', '/users', c.params.toHash(), function(data) { + c.redirect_to('#/users'); + }); } } else { c.flash('fail', y18n.t('passwords_dont_match')); - store.clear('slide'); - //c.redirect('#/users/create'); } }); // Show user information app.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); + c.api('GET', '/users/'+ c.params['user'], {}, function(data) { + c.view('user/user_info', data, function() { + + // Configure delete button behavior + $('button[data-action="delete"]').on("click", function() { + var user = $(this).data("user"); + + var params = {}; + + // make confirm content + var purgeCheckbox = '
'; + var purgeAlertMessage = ''; + var confirmModalContent = $('
'+ y18n.t('confirm_delete', [user]) +'

'+ purgeCheckbox +'
'+ purgeAlertMessage +'
'); + + // display confirm modal + c.confirm( + y18n.t('users'), + confirmModalContent, + function(){ + c.api('DELETE', '/users/'+ user, params, function(data) { + c.redirect_to('#/users'); + }); + } + ); + + // toggle purge warning and parameter + confirmModalContent.find("input").click(function(){ + + if (confirmModalContent.find("input").is(':checked')) { + params.purge = ""; + confirmModalContent.find(".danger").show(); + } + else { + delete params.purge; + confirmModalContent.find(".danger").hide(); + }; + }); + }); + }); }); }); // Edit user form app.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 + c.api('GET', '/users/'+ c.params['user'], {}, function(data) { + c.api('GET', '/domains', {}, function(dataDomains) { // Password min length data.password_min_length = PASSWORD_MIN_LENGTH; @@ -314,7 +336,7 @@ // Update user information app.put('#/users/:user', function (c) { // Get full user object - c.api('/users/'+ c.params['user'], function(user) { + c.api('GET', '/users/'+ c.params['user'], {}, function(user) { // Force unit or disable quota if (c.params['mailbox_quota']) { c.params['mailbox_quota'] += "M"; @@ -353,79 +375,32 @@ if ($.isEmptyObject(params)) { c.flash('fail', y18n.t('error_modify_something')); - store.clear('slide'); - c.redirect('#/users/'+ c.params['user'] + '/edit'); + c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false}); } else { if (params['password']) { if (params['password'] == params['confirmation']) { if (params['password'].length < PASSWORD_MIN_LENGTH) { c.flash('fail', y18n.t('password_too_short')); - store.clear('slide'); - c.redirect('#/users/'+ c.params['user'] + '/edit'); + c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false}); } 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); + c.api('PUT', '/users/'+ c.params['user'], params, function(data) { + c.redirect_to('#/users/'+ c.params['user']); + }); } } else { c.flash('fail', y18n.t('passwords_dont_match')); - store.clear('slide'); - c.redirect('#/users/'+ c.params['user'] + '/edit'); + c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false}); } } 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); + c.api('PUT', '/users/'+ c.params['user'], params, function(data) { + c.redirect_to('#/users/'+ c.params['user']); + }); } } - }, 'GET'); - }); - - // Remove existing user - app.get('#/users/:user/delete', function (c) { - - var params = {}; - - // make confirm content - var purgeCheckbox = '
'; - var purgeAlertMessage = ''; - var confirmModalContent = $('
'+ y18n.t('confirm_delete', [c.params['user']]) +'

'+ purgeCheckbox +'
'+ purgeAlertMessage +'
'); - - // display confirm modal - c.confirm( - y18n.t('users'), - confirmModalContent, - function(){ - c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_delete_delete_4 - c.redirect('#/users'); - }, 'DELETE', params); - }, - function(){ - store.clear('slide'); - c.redirect('#/users/'+ c.params['user']); - } - ); - - // toggle purge warning and parameter - confirmModalContent.find("input").click(function(){ - - if (confirmModalContent.find("input").is(':checked')) { - params.purge = ""; - confirmModalContent.find(".warning").show(); - } - else { - delete params.purge; - confirmModalContent.find(".warning").hide(); - }; - }); - }); - - - })(); diff --git a/src/js/yunohost/events.js b/src/js/yunohost/events.js index 8b9abb4c..5cfb10c4 100644 --- a/src/js/yunohost/events.js +++ b/src/js/yunohost/events.js @@ -8,7 +8,7 @@ * */ app.bind('login', function(e, data) { - c.api('/users', function(data) { + c.api('GET', '/users', {}, function(data) { // Warn admin if no users are created. if (typeof data.users !== 'undefined' && data.users.length === 0) { c.flash('warning', y18n.t('warning_first_user')); @@ -69,13 +69,13 @@ c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed])); }); - c.api("/diagnosis", function(data) { + c.api("GET", "/diagnosis", {}, function(data) { versions = data.packages; $('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo])); if (data.security["CVE-2017-5754"].vulnerable) { c.flash('danger', y18n.t('meltdown')); } - $('div.loader').remove(); + c.hideLoader(); }); }); }); diff --git a/src/js/yunohost/filters.js b/src/js/yunohost/filters.js index 0b793580..c35a9450 100644 --- a/src/js/yunohost/filters.js +++ b/src/js/yunohost/filters.js @@ -12,7 +12,7 @@ function prefetchDomains(req) { // Preload domains list. req.params.domains = []; - req.api('/domains', function(data) { + req.api('GET', '/domains', {}, function(data) { req.params.domains = data.domains; }); } @@ -20,7 +20,7 @@ function prefetchUsers(req){ // Preload users lists. req.params.users = []; - req.api('/users', function(data) { + req.api('GET', '/users', {}, function(data) { req.params.users = data.users; }); } diff --git a/src/js/yunohost/helpers.js b/src/js/yunohost/helpers.js index 44ea77e6..e5df079b 100644 --- a/src/js/yunohost/helpers.js +++ b/src/js/yunohost/helpers.js @@ -3,20 +3,64 @@ var app = Sammy.apps['#main']; var store = app.store; + // The logic used to temporily disable transition is from + // https://stackoverflow.com/a/16575811 + function whichTransitionEvent(){ + var t; + var el = document.createElement('fakeelement'); + var transitions = { + 'transition':'transitionend', + 'OTransition':'oTransitionEnd', + 'MozTransition':'transitionend', + 'WebkitTransition':'webkitTransitionEnd' + } + + for(t in transitions){ + if( el.style[t] !== undefined ){ + return transitions[t]; + } + } + }; + var transitionEvent = whichTransitionEvent(); + + function resetSliders() + { + // Disable transition effects + $('#slider-container').addClass('notransition'); + // Delete the left/right temporary stuff only used during animation + $('#slideTo').css('display', 'none'); + $('#slideTo').html(""); + $('#slideBack').css('display', 'none'); + $('#slideBack').html(""); + // Set the margin-left back to 0 + $('#slider-container').css('margin-left', '0'); + // c.f. the stackoverflow thread + $('#slider-container')[0].offsetHeight; + // Remove the binding to this event handler for next times + // Re-enable transition effects + $('#slider-container').removeClass('notransition'); + } + /** * 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])); + // + // Pacman loader management + // + + showLoader: function() { + app.loaded = false; // Not sure if that's really useful ... this is from old code with no explanation what it really does ... + if ($('div.loader').length === 0) { + $('#main').append('
'); } - return str.join("&"); + }, + + hideLoader: function() { + app.loaded = true; // Not sure if that's really useful ... this is from old code with no explanation what it really does ... + $('div.loader').remove(); }, // Flash helper to diplay instant notifications @@ -85,7 +129,7 @@ }, // API call - api: function(uri, callback, method, data, websocket, callbackOnFailure) { + api: function(method, uri, data, callback, callbackOnFailure, websocket) { c = this; method = typeof method !== 'undefined' ? method : 'GET'; @@ -93,96 +137,69 @@ 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('
'); - } + + c.showLoader(); + 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); - } - + // Define default callback for failures 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); + if (xhr.status == 200) { + // Fail with 200, WTF + callback({}); } - // Regular errors + // 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 { - 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'); + var errorMessage = xhr.status+' '+xhr.statusText; + c.flash('fail', y18n.t('error_server_unexpected', [errorMessage])); + console.log(xhr); } + + c.hideLoader(); + + // Force scrollTop on page load + $('html, body').scrollTop(0); + store.clear('slide'); }; } @@ -237,78 +254,17 @@ }, - // Render view (cross-browser) - view: function (view, data, callback, enableSlide) { + + // Ask confirmation to the user through the modal window + confirm: function(title, content, confirmCallback, cancelCallback) { 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() {};
 
+            c.hideLoader();
+
             // Get modal element
             var box = $('#modal');
 
@@ -335,12 +291,10 @@
 
                     $('#modal footer button').unbind( "click" );
                     // Reset & Hide modal
-                    box
-                        .removeClass('no-title')
-                        .modal('hide');
+                    box.removeClass('no-title').modal('hide');
 
                     // Do corresponding callback
-                    if ($(this).data('action') == 'confirm') {
+                    if ($(this).data('modal-action') == 'confirm') {
                         confirmCallback();
                     }
                     else {
@@ -352,19 +306,165 @@
             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);
-          });
+
+        // Render view (cross-browser)
+        view: function (view, data, callback) {
+            c = this;
+
+            // Default
+            callback = typeof callback !== 'undefined' ? callback : function() {};
+
+            // Hide loader and modal
+            c.hideLoader();
+            $('#modal').modal('hide');
+
+            // Render content
+            var rendered = this.render('views/'+ view +'.ms', data);
+
+            // Update content helper
+            var leSwap = function() {
+                rendered.swap(function() {
+                    // Clicking on those kind of CSS elements will trigger a
+                    // slide effect i.e. the next view rendering will have
+                    // store.get('slide') set to 'back' or 'to'
+                    $('.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 (store.get('slide') == 'back') {
+
+                store.clear('slide');
+                // Disable transition while we tweak CSS
+                $('#slider-container').addClass('notransition');
+                // "Delete" the left part of the slider
+                $('#slideBack').css('display', 'none');
+
+                // Push the slider to the left
+                $('#slider-container').css('margin-left', '-100%');
+                // slideTo is the right part, and should contain the old view,
+                // so we copypasta what's in the "center" slider (#main)
+                $('#slideTo').show().html($('#main').html());
+                // leSwap will put the new view in the "center" slider (#main)
+                leSwap();
+
+                // So now things look like:
+                //                          |                 |
+                //                          |   the screen    |
+                //                          |                 |
+                //
+                //       .     #main        .    #slideTo     .
+                //       .  the new view    .  the old view   .
+                //       ^                          ^
+                //  margin-left: -100%             currently shown
+                //
+                //            =====>>>  sliiiiide  =====>>>
+
+                // Re-add transition effect
+                $('#slider-container').removeClass('notransition');
+
+                // add the transition event to detect the end of the transition effect
+                transitionEvent
+                    && $("#slider-container").off(transitionEvent)
+                    && $("#slider-container").on(transitionEvent, resetSliders);
+
+                // And actually play the transition effect that will move the container from left to right
+                $('#slider-container').css('margin-left', '0px');
+            }
+            // Slide to effect
+            else if (store.get('slide') == 'to') {
+
+                // Disable transition while we tweak CSS
+                $('#slider-container').addClass('notransition');
+                // "Delete" the right part of the slider
+                $('#slideTo').css('display', 'none');
+                // Push the slider to the right
+                $('#slider-container').css('margin-left', '0px');
+                // slideBack should contain the old view,
+                // so we copypasta what's in the "center" slider (#main)
+                $('#slideBack').show().html($('#main').html());
+                leSwap();
+
+                // So now things look like:
+                //
+                //                    |                 |
+                //                    |   the screen    |
+                //                    |                 |
+                //
+                //      .             .   #slideBack    .     #main      .
+                //      .             .  the old view   .  the new view  .
+                //      ^             ^        ^
+                //   margin-left: -100%      currently shown
+                //
+                //               <<<===== sliiiiide <<<=======
+
+
+                // Re-add transition effect
+                $('#slider-container').removeClass('notransition');
+
+                // add the transition event to detect the end of the transition effect
+                var transitionEvent = whichTransitionEvent();
+                transitionEvent
+                    && $("#slider-container").off(transitionEvent)
+                    && $("#slider-container").on(transitionEvent, resetSliders);
+
+                // And actually play the transition effect that will move the container from right to left
+                $('#slider-container').css('margin-left', '-100%');
+            }
+            // No slideing effect
+            else {
+                leSwap();
+            }
         },
 
+        redirect_to: function(destination, options) {
+            c = this;
+
+            options = options !== undefined ? options : {};
+
+            // If destination if the same as current url,
+            // we don't want to display the slide animation
+            // (or if the code explicitly state to disable slide animation)
+            if ((c.path.split("#")[1] == destination.split("#")[1]) || (options.slide == false))
+            {
+                store.clear('slide');
+            }
+
+            // This is a copy-pasta of some of the redirect/refresh code of
+            // sammy.js because for some reason calling the original
+            // redirect/refresh function in some context does not work >.>
+            // (e.g. if you're already on the page)
+            c.trigger('redirect', {to: destination});
+            c.app.last_location = c.path;
+            c.app.setLocation(destination);
+            c.app.trigger('location-changed');
+        },
+
+        refresh: function() {
+            c = this;
+            c.redirect_to(c.path, {slide: false});
+        },
+
+        //
+        // Array / object helpers
+        //
+
         arraySortById: function(arr) {
             arr.sort(function(a, b){
                 if (a.id > b.id) {
@@ -385,74 +485,20 @@
             });
         },
 
-        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;
+        // 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("&");
         },
 
-        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;
-        },
+        //
+        // Misc helpers used in views etc..
+        //
 
         // Paste 
         prePaste: function() {
@@ -461,8 +507,7 @@
                 // Get paste content element
                 var preElement = $($(this).data('paste-content'));
 
-                // Add pacman loader
-                $('#main').append('
'); + c.showLoader(); // Send to paste.yunohost.org $.ajax({ @@ -477,11 +522,11 @@ c.flash('fail', y18n.t('paste_error')); }) .always(function(){ - // Remove pacman - $('div.loader').remove(); + c.hideLoader(); }); }); } + }); })(); diff --git a/src/js/yunohost/main.js b/src/js/yunohost/main.js index 4d6ef4e4..5219715b 100644 --- a/src/js/yunohost/main.js +++ b/src/js/yunohost/main.js @@ -181,7 +181,7 @@ sam.store.set('url', window.location.hostname + '/yunohost/api'); if (sam.store.get('connected')) { - this.api('/diagnosis', function(diagnosis) { + this.api('GET', '/diagnosis', {}, function(diagnosis) { versions = diagnosis.packages; $('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo])); }); diff --git a/src/locales/en.json b/src/locales/en.json index f56ad459..ebb1118f 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -385,6 +385,6 @@ "appslists_last_update": "Last update", "appslists_unknown_list": "Unknown apps list: %s", "name": "Name", - "purge_user_data_checkbox": "Purge %s's data? (This will remove the content of it's home and mail directories.)", + "purge_user_data_checkbox": "Purge %s's data? (This will remove the content of its home and mail directories.)", "purge_user_data_warning": "Purging user's data is not reversible. Be sure you know what you're doing!" } diff --git a/src/views/app/app_info.ms b/src/views/app/app_info.ms index 8d46c7ca..7c972a0e 100644 --- a/src/views/app/app_info.ms +++ b/src/views/app/app_info.ms @@ -57,9 +57,9 @@

{{t 'app_info_default_desc' settings.domain}}

- +

@@ -79,9 +79,9 @@

{{t 'app_info_uninstall_desc'}}

- +
diff --git a/src/views/backup/backup.ms b/src/views/backup/backup.ms index 4da53a75..e4bd4b0c 100644 --- a/src/views/backup/backup.ms +++ b/src/views/backup/backup.ms @@ -3,12 +3,6 @@ {{t 'backup'}} -
- -
-
diff --git a/src/views/backup/backup_info.ms b/src/views/backup/backup_info.ms index ae42c67a..117558a7 100644 --- a/src/views/backup/backup_info.ms +++ b/src/views/backup/backup_info.ms @@ -83,41 +83,11 @@
-

{{t 'backup_archive_delete'}}

- +
- {{#if other_storages}} -
-
-

{{t 'backup_archive_copy'}}

-
-
- -
- -
-
-
-
- -
-
-
-
- {{/if}}
diff --git a/src/views/backup/storage_create.ms b/src/views/backup/storage_create.ms deleted file mode 100644 index bc07a003..00000000 --- a/src/views/backup/storage_create.ms +++ /dev/null @@ -1,52 +0,0 @@ - - - -
- -
-
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- -
-
diff --git a/src/views/domain/domain_cert.ms b/src/views/domain/domain_cert.ms index 663f17ea..3969dfa3 100644 --- a/src/views/domain/domain_cert.ms +++ b/src/views/domain/domain_cert.ms @@ -46,36 +46,36 @@

{{t 'domain_not_eligible_for_ACME'}}

{{/if}} - +
{{/if}} {{#if actions_enabled.manual_renew_letsencrpt}}

{{t 'manually_renew_letsencrypt_message'}}

- +

{{/if}} {{#if actions_enabled.regen_selfsigned}}

{{t 'regenerate_selfsigned_cert_message'}}

- +

{{/if}} {{#if actions_enabled.replace_with_selfsigned}}

{{t 'revert_to_selfsigned_cert_message'}}

- +
{{/if}} diff --git a/src/views/domain/domain_info.ms b/src/views/domain/domain_info.ms index 6eed7627..50c341d1 100644 --- a/src/views/domain/domain_info.ms +++ b/src/views/domain/domain_info.ms @@ -12,67 +12,46 @@ {{name}} -
- {{#if main}} -

- {{t 'domain_default_longdesc'}} -

- {{/if}} -

{{url}}

- -
- - -
-
-

- {{t 'operations'}} -

-
- {{#unless main}}

{{t 'domain_default_desc'}}

-
- - -
+ {{#if main}} +

+ {{t 'domain_default_longdesc'}} +

+ {{else}} + + {{/if}}
- {{/unless}}

{{t 'certificate_manage'}}

- {{#unless enable_cert_management}} -

- {{t 'certificate_old_letsencrypt_app_conflict'}} -

- {{/unless}} - - {{t 'ssl_certificate'}} + + {{t 'ssl_certificate'}}

{{t 'domain_delete_longdesc' name}}

- - {{t 'delete'}} - +
diff --git a/src/views/service/service_info.ms b/src/views/service/service_info.ms index 3b8c7e19..2e78941d 100644 --- a/src/views/service/service_info.ms +++ b/src/views/service/service_info.ms @@ -53,25 +53,25 @@
{{#is_loaded}} - + {{/is_loaded}} {{^is_loaded}} - + {{/is_loaded}} {{#is_running}} - + {{/is_running}} {{^is_running}} - + {{/is_running}} {{t 'log'}} diff --git a/src/views/tools/tools_firewall.ms b/src/views/tools/tools_firewall.ms index a22ef665..c03c03d3 100644 --- a/src/views/tools/tools_firewall.ms +++ b/src/views/tools/tools_firewall.ms @@ -29,20 +29,20 @@ {{#if this.ipv4}} - {{t 'close'}} + {{else}} - {{t 'open'}} + {{/if}} {{#if this.ipv6}} - {{t 'close'}} + {{else}} - {{t 'open'}} + {{/if}} @@ -75,20 +75,20 @@ {{#if this.ipv4}} - {{t 'close'}} + {{else}} - {{t 'open'}} + {{/if}} {{#if this.ipv6}} - {{t 'close'}} + {{else}} - {{t 'open'}} + {{/if}} @@ -168,10 +168,10 @@
{{#if upnp}}

{{t 'upnp_enabled'}}

- {{t 'disable'}} + {{else}}

{{t 'upnp_disabled'}}

- {{t 'enable'}} + {{/if}}
diff --git a/src/views/tools/tools_log.ms b/src/views/tools/tools_log.ms index f701b755..44891631 100644 --- a/src/views/tools/tools_log.ms +++ b/src/views/tools/tools_log.ms @@ -10,9 +10,9 @@
diff --git a/src/views/tools/tools_logs.ms b/src/views/tools/tools_logs.ms index b7f4d7b2..2b719534 100644 --- a/src/views/tools/tools_logs.ms +++ b/src/views/tools/tools_logs.ms @@ -22,7 +22,7 @@
diff --git a/src/views/tools/tools_migrations.ms b/src/views/tools/tools_migrations.ms index 8ffbcedf..3536aee7 100644 --- a/src/views/tools/tools_migrations.ms +++ b/src/views/tools/tools_migrations.ms @@ -11,7 +11,7 @@

{{t 'migrations_pending'}} {{#if pending_migrations}}
- {{t 'run'}} +
{{/if}}

@@ -24,7 +24,7 @@

{{ number }}. {{ description }}
- {{t 'skip'}} +

{{#if disclaimer }} diff --git a/src/views/tools/tools_reboot.ms b/src/views/tools/tools_reboot.ms index c66bc0a0..ef184eaf 100644 --- a/src/views/tools/tools_reboot.ms +++ b/src/views/tools/tools_reboot.ms @@ -15,14 +15,14 @@
diff --git a/src/views/tools/tools_rebooting.ms b/src/views/tools/tools_rebooting.ms deleted file mode 100644 index 1d4d95cc..00000000 --- a/src/views/tools/tools_rebooting.ms +++ /dev/null @@ -1 +0,0 @@ -
{{t 'tools_rebooting'}}
diff --git a/src/views/tools/tools_shuttingdown.ms b/src/views/tools/tools_shuttingdown.ms deleted file mode 100644 index e7294efd..00000000 --- a/src/views/tools/tools_shuttingdown.ms +++ /dev/null @@ -1 +0,0 @@ -
{{t 'tools_shuttingdown'}}
diff --git a/src/views/update/update.ms b/src/views/tools/tools_update.ms similarity index 80% rename from src/views/update/update.ms rename to src/views/tools/tools_update.ms index 62a44b50..0be7740a 100644 --- a/src/views/update/update.ms +++ b/src/views/tools/tools_update.ms @@ -20,7 +20,7 @@ {{/system}} {{else}}
@@ -37,14 +37,14 @@
{{#apps}}
- {{t 'system_upgrade_btn'}} +

{{label}} {{id}}

{{t 'from_to' current_version new_version}}

{{/apps}}
{{else}}
diff --git a/src/views/upgrade/upgrade.ms b/src/views/upgrade/upgrade.ms deleted file mode 100644 index cf8d087f..00000000 --- a/src/views/upgrade/upgrade.ms +++ /dev/null @@ -1,18 +0,0 @@ - - -
- -{{#if logs}} -
-{{#logs}}
-    {{.}}
-{{/logs}}
-
- -{{else}} - {{t 'no_log'}} -{{/if}} diff --git a/src/views/user/group_list.ms b/src/views/user/group_list.ms index 7b68e404..a6643f79 100644 --- a/src/views/user/group_list.ms +++ b/src/views/user/group_list.ms @@ -17,10 +17,10 @@ {{text}} - + {{/inline}} @@ -35,7 +35,7 @@
@@ -53,10 +53,10 @@ {{#if special}}{{t (concat 'group_' @key)}}{{else}}{{t 'group'}} "{{ucwords @key}}"{{/if}} {{#unless special}} - + {{/unless}}
@@ -122,7 +122,7 @@ {{#each groups}} {{#if primary}} {{#unless (or permissions display)}} -
  • {{@key}}
  • +
  • {{/unless}} {{/if}} {{/each}} diff --git a/src/views/user/user_info.ms b/src/views/user/user_info.ms index ccca3788..7262aa89 100644 --- a/src/views/user/user_info.ms +++ b/src/views/user/user_info.ms @@ -62,7 +62,7 @@ {{t 'user_username_edit' username}} - {{t 'delete'}} +