Merge pull request #262 from YunoHost/refactoring-and-readability-improvements

Code refactoring, readability improvements, use buttons instead of links when it's not about changing page
This commit is contained in:
Alexandre Aubin 2019-11-05 23:30:12 +01:00 committed by GitHub
commit 17bb595eb9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
34 changed files with 1117 additions and 1338 deletions

View file

@ -706,12 +706,15 @@ input[type='radio'].nice-radio {
#view-groups { #view-groups {
.panel-heading a { .panel-heading a {
text-decoration: none; text-decoration: none;
&.group-delete { }
float:right; .group-delete {
color:lighten(@label-danger-bg, 20%); font-size: 24px;
:hover { line-height: 24px;
color:@label-danger-bg; padding: 0;
} float:right;
color:lighten(@label-danger-bg, 20%);
:hover {
color:@label-danger-bg;
} }
} }
.panel-body { .panel-body {
@ -725,6 +728,10 @@ input[type='radio'].nice-radio {
.dropdown-menu { .dropdown-menu {
max-height: 200px; max-height: 200px;
overflow-y: auto; overflow-y: auto;
button {
background: none;
}
} }
.label-removable { .label-removable {
// The following match properties from regular btn's // The following match properties from regular btn's
@ -743,15 +750,17 @@ input[type='radio'].nice-radio {
margin-right:7px; // Spacing between labels margin-right:7px; // Spacing between labels
> a { > button {
line-height: 12px;
margin-left:6px; margin-left:6px;
padding: 0;
padding-left:6px; padding-left:6px;
border-left: #ccc 1px solid; border-left: #ccc 1px solid;
color:lighten(@label-info-bg,20); color:lighten(@label-info-bg,20);
text-decoration: none; text-decoration: none;
} }
> a:hover { > button:hover {
color:@label-info-bg; color:@label-info-bg;
} }
} }
@ -925,3 +934,10 @@ input[type='radio'].nice-radio {
float: none !important; float: none !important;
} }
} }
.notransition {
-webkit-transition: none !important;
-moz-transition: none !important;
-o-transition: none !important;
transition: none !important;
}

View file

@ -81,8 +81,8 @@
</header> </header>
<div class="content"></div> <div class="content"></div>
<footer> <footer>
<button type="button" id="modal-cancel" data-action="cancel" data-y18n="cancel">Cancel</button> <button type="button" id="modal-cancel" data-modal-action="cancel" data-y18n="cancel">Cancel</button>
<button type="button" id="modal-confirm" data-action="confirm" data-y18n="ok">OK</button> <button type="button" id="modal-confirm" data-modal-action="confirm" data-y18n="ok">OK</button>
</footer> </footer>
</div></div> </div></div>
</div> </div>

View file

@ -10,7 +10,7 @@
// List installed apps // List installed apps
app.get('#/apps', function (c) { 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']; var apps = data['apps'];
c.arraySortById(apps); c.arraySortById(apps);
c.view('app/app_list', {apps: apps}); c.view('app/app_list', {apps: apps});
@ -109,8 +109,8 @@
// List available apps // List available apps
app.get('#/apps/install', function (c) { app.get('#/apps/install', function (c) {
c.api('/apps', function (data) { // http://api.yunohost.org/#!/app/app_list_get_8 c.api('GET', '/apps', {}, function (data) {
c.api('/apps?raw', function (dataraw) { // http://api.yunohost.org/#!/app/app_list_get_8 c.api('GET', '/apps?raw', {}, function (dataraw) {
var apps = [] var apps = []
$.each(data['apps'], function(k, v) { $.each(data['apps'], function(k, v) {
app = dataraw[v['id']]; app = dataraw[v['id']];
@ -201,30 +201,59 @@
// Get app information // Get app information
app.get('#/apps/:app', function (c) { 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('GET', '/apps/'+c.params['app']+'?raw', {}, function(data) {
c.api('/users/permissions', function(data_permissions) { c.api('GET', '/users/permissions', {}, function(data_permissions) {
// Permissions // Permissions
data.permissions = data_permissions.permissions[c.params['app']+".main"]["allowed"]; data.permissions = data_permissions.permissions[c.params['app']+".main"]["allowed"];
// Multilingual description // Multilingual description
data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ? data.description = (typeof data.manifest.description[y18n.locale] !== 'undefined') ?
data.manifest.description[y18n.locale] : data.manifest.description[y18n.locale] :
data.manifest.description['en'] data.manifest.description['en']
; ;
// Multi Instance settings // Multi Instance settings
data.manifest.multi_instance = data.manifest.multi_instance ? y18n.t('yes') : y18n.t('no'); data.manifest.multi_instance = data.manifest.multi_instance ? y18n.t('yes') : y18n.t('no');
data.install_time = new Date(data.settings.install_time * 1000); 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 // Get app actions list
app.get('#/apps/:app/actions', function (c) { 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) { $.each(data.actions, function(_, action) {
formatYunoHostStyleArguments(action.arguments, c.params); formatYunoHostStyleArguments(action.arguments, c.params);
@ -241,7 +270,7 @@
}); });
}); });
// Perform application // Perform app action
app.put('#/apps/:app/actions/:action', function(c) { app.put('#/apps/:app/actions/:action', function(c) {
// taken from app install // taken from app install
$.each(c.params, function(k, v) { $.each(c.params, function(k, v) {
@ -260,14 +289,18 @@
'args': c.serialize(c.params.toHash()) '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.api('PUT', '/apps/'+app_id+'/actions/'+action_id, params, function() {
c.redirect('#/apps/'+app_id+'/actions'); c.redirect_to('#/apps/'+app_id+'/actions', {slide:false});
}, 'PUT', params); });
}); });
//
// App config panel
//
// Get app config panel // Get app config panel
app.get('#/apps/:app/config-panel', function (c) { 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(data.config_panel.panel, function(_, panel) {
$.each(panel.sections, function(_, section) { $.each(panel.sections, function(_, section) {
formatYunoHostStyleArguments(section.options, c.params); formatYunoHostStyleArguments(section.options, c.params);
@ -293,16 +326,16 @@
'args': c.serialize(c.params.toHash()) 'args': c.serialize(c.params.toHash())
} }
c.api('/apps/'+app_id+'/config', function() { // http://api.yunohost.org/#!/app/app_install_post_2 c.api('POST', '/apps/'+app_id+'/config', params, function() {
c.redirect('#/apps/'+app_id+'/config-panel'); c.redirect_to('#/apps/'+app_id+'/config-panel', {slide:false});
}, 'POST', params); });
}) })
// Special case for custom app installation. // Special case for custom app installation.
app.get('#/apps/install/custom', function (c) { app.get('#/apps/install/custom', function (c) {
// If we try to GET /apps/install/custom, it means that installation fail. // 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. // 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 // Helper function that formats YunoHost style arguments for generating a form
@ -473,7 +506,7 @@
// App installation form // App installation form
app.get('#/apps/install/:app', function (c) { 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_name = c.params["app"];
var app_infos = data[app_name]; var app_infos = data[app_name];
if (app_infos['state'] === "validated") if (app_infos['state'] === "validated")
@ -495,10 +528,6 @@
app_infos.manifest, app_infos.manifest,
c.params c.params
); );
},
function(){
$('div.loader').remove();
c.redirect('#/apps/install');
} }
); );
} }
@ -539,14 +568,13 @@
delete params['args']; delete params['args'];
} }
c.api('/apps', function() { // http://api.yunohost.org/#!/app/app_install_post_2 c.api('POST', '/apps', params, function() {
c.redirect('#/apps'); c.redirect_to('#/apps');
}, 'POST', params); });
} }
else { else {
c.flash('warning', y18n.t('app_install_cancel')); c.flash('warning', y18n.t('app_install_cancel'));
store.clear('slide'); c.refresh();
c.redirect('#/apps/install');
} }
}); });
@ -586,77 +614,35 @@
}) })
.fail(function(xhr) { .fail(function(xhr) {
c.flash('fail', y18n.t('app_install_custom_no_manifest')); c.flash('fail', y18n.t('app_install_custom_no_manifest'));
store.clear('slide'); c.refresh();
c.redirect('#/apps/install');
}); });
},
function(){
c.flash('warning', y18n.t('app_install_cancel'));
store.clear('slide');
c.redirect('#/apps/install');
}
);
});
// Remove installed app
app.get('#/apps/:app/uninstall', function (c) {
c.confirm(
y18n.t('applications'),
y18n.t('confirm_uninstall', [c.params['app']]),
function() {
c.api('/apps/'+ c.params['app'], function() { // http://api.yunohost.org/#!/app/app_remove_delete_4
c.redirect('#/apps');
}, 'DELETE');
},
function() {
store.clear('slide');
c.redirect('#/apps/'+ c.params['app']);
}
);
});
// 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 // Get app change label page
app.get('#/apps/:app/changelabel', function (c) { app.get('#/apps/:app/changelabel', function (c) {
c.api('/apps/'+c.params['app']+'?raw', function(app_data) { c.api('GET', '/apps/'+c.params['app']+'?raw', {}, function(app_data) {
data = { data = {
id: c.params['app'], id: c.params['app'],
label: app_data.settings.label, label: app_data.settings.label,
}; };
c.view('app/app_changelabel', data); c.view('app/app_changelabel', data);
}); });
}); });
// Change app label // Change app label
app.post('#/apps/:app/changelabel', function (c) { app.post('#/apps/:app/changelabel', function (c) {
params = {'new_label': c.params['label']}; params = {'new_label': c.params['label']};
c.api('/apps/' + c.params['app'] + '/label', function(data) { // Call changelabel API c.api('PUT', '/apps/' + c.params['app'] + '/label', params, function(data) {
store.clear('slide'); c.redirect_to('#/apps/'+ c.params['app']);
c.redirect('#/apps/'+ c.params['app']); });
}, 'PUT', params);
}); });
// Get app change URL page // Get app change URL page
app.get('#/apps/:app/changeurl', function (c) { app.get('#/apps/:app/changeurl', function (c) {
c.api('/apps/'+c.params['app']+'?raw', function(app_data) { c.api('GET', '/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', '/domains', {}, function(domain_data) {
// Display a list of available domains // Display a list of available domains
var domains = []; var domains = [];
@ -688,14 +674,9 @@
y18n.t('confirm_app_change_url', [c.params['app']]), y18n.t('confirm_app_change_url', [c.params['app']]),
function() { function() {
params = {'domain': c.params['domain'], 'path': c.params['path']}; params = {'domain': c.params['domain'], 'path': c.params['path']};
c.api('/apps/' + c.params['app'] + '/changeurl', function(data) { // Call changeurl API c.api('PUT', '/apps/' + c.params['app'] + '/changeurl', params, function(data) {
store.clear('slide'); c.redirect_to('#/apps/'+ c.params['app']);
c.redirect('#/apps/'+ c.params['app']); });
}, 'PUT', params);
},
function() {
store.clear('slide');
c.redirect('#/apps/'+ c.params['app'] + '/changeurl');
} }
); );
}); });

View file

@ -32,118 +32,9 @@
c.view('backup/backup', {'storages':storages}); 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 // Archive list
app.get('#/backup/:storage', function (c) { 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 = { data.storage = {
id: 'local', id: 'local',
name: y18n.t('local_archives') 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;
};
})(); })();

View file

@ -10,8 +10,8 @@
// List existing domains // List existing domains
app.get('#/domains', function (c) { app.get('#/domains', function (c) {
c.api('/domains', function(data) { // http://api.yunohost.org/#!/domain/domain_list_get_2 c.api('GET', '/domains', {}, function(data) {
c.api('/domains/main', function(data2) { c.api('PUT', '/domains/main', {}, function(data2) {
var domains = []; var domains = [];
$.each(data.domains, function(k, domain) { $.each(data.domains, function(k, domain) {
domains.push({ domains.push({
@ -29,7 +29,7 @@
domains: domains, domains: domains,
main_domain_form: main_domain_form main_domain_form: main_domain_form
}); });
}, 'PUT'); });
}); });
}); });
@ -68,8 +68,7 @@
if (c.params['domain'] === '') { if (c.params['domain'] === '') {
if (c.params['ddomain'] === '') { if (c.params['ddomain'] === '') {
c.flash('fail', y18n.t('error_select_domain')); c.flash('fail', y18n.t('error_select_domain'));
store.clear('slide'); c.redirect_to('#/domains/add');
c.redirect('#/domains/add');
} }
params.domain = c.params['ddomain'] + c.params['ddomain-ext']; params.domain = c.params['ddomain'] + c.params['ddomain-ext'];
endurl = 'dyndns'; endurl = 'dyndns';
@ -77,42 +76,56 @@
params.domain = c.params['domain']; params.domain = c.params['domain'];
} }
c.api('/domains?'+endurl, function(data) { // http://api.yunohost.org/#!/domain/domain_add_post_1 c.api('POST', '/domains?'+endurl, params, function(data) {
c.redirect('#/domains'); c.redirect_to('#/domains');
}, 'POST', params); });
}); });
// Get existing domain info // Get existing domain info
app.get('#/domains/:domain', function (c) { app.get('#/domains/:domain', function (c) {
c.api('/domains/main', function(dataMain) { c.api('PUT', '/domains/main', {}, function(dataMain) {
c.api('/apps?installed', function(data) { // http://api.yunohost.org/#!/app/app_list_get_8 c.api('GET', '/apps?installed', {}, function(data) {
// 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;
}
});
var domain = { var domain = {
name: c.params['domain'], name: c.params['domain'],
main: (c.params['domain'] == dataMain.current_main_domain) ? true : false, main: (c.params['domain'] == dataMain.current_main_domain) ? true : false,
url: "https://"+c.params['domain'], url: "https://"+c.params['domain']
enable_cert_management: enable_cert_management_
}; };
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 // Domain DNS
app.get('#/domains/:domain/dns', function (c) { 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 = { var domain = {
name: c.params['domain'], name: c.params['domain'],
dns: data dns: data
@ -123,7 +136,7 @@
// Domain certificate // Domain certificate
app.get('#/domains/:domain/cert-management', function (c) { 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 s = data["certificates"][c.params['domain']];
var status_ = { var status_ = {
@ -199,132 +212,45 @@
status: status_, status: status_,
actions_enabled : actions_enabled 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');
}
);
}
});
})(); })();

View file

@ -10,7 +10,7 @@
// Firewall status // Firewall status
app.get('#/tools/firewall', function (c) { app.get('#/tools/firewall', function (c) {
c.api('/firewall?raw', function(data) { c.api('GET', '/firewall?raw', {}, function(data) {
var firewall = { var firewall = {
ports: {}, ports: {},
upnp: false upnp: false
@ -30,28 +30,49 @@
// Get UPnP status // Get UPnP status
firewall.upnp = data.uPnP.enabled; 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 // Update port status from form
app.get('#/tools/firewall/upnp/:action', function (c) { app.post('#/tools/firewall/port', function (c) {
c.confirm( c.confirm(
y18n.t('firewall'), y18n.t('firewall'),
// confirm_upnp_enable and confirm_upnp_disable y18n.t('confirm_firewall_' + c.params['action'].toLowerCase(), [ c.params['port'], y18n.t(c.params['protocol']), y18n.t(c.params['connection']) ]),
y18n.t('confirm_upnp_' + c.params['action'].toLowerCase()),
function(){ function(){
var params = { c.togglePort(
action : c.params['action'] c.params['port'],
}; c.params['protocol'],
c.api('/firewall/upnp', function(data) { c.params['connection'],
store.clear('slide'); c.params['action']
c.redirect('#/tools/firewall'); );
}, 'GET', params);
},
function(){
store.clear('slide');
c.redirect('#/tools/firewall');
} }
); );
}); });
@ -65,8 +86,7 @@
if (port != parseInt(port) || port < 0 || port > 65535) { if (port != parseInt(port) || port < 0 || port > 65535) {
c.flash('fail', y18n.t('unknown_argument', [port])); c.flash('fail', y18n.t('unknown_argument', [port]));
store.clear('slide'); c.refresh();
c.redirect('#/tools/firewall');
} }
switch (connection) { switch (connection) {
@ -98,75 +118,27 @@
break; break;
default: default:
c.flash('fail', y18n.t('unknown_action', [action])); c.flash('fail', y18n.t('unknown_action', [action]));
store.clear('slide'); c.refresh();
c.redirect('#/tools/firewall');
} }
if (method !== null && protocol !== null && port !== null) { // port:
// port: // protocol:
// protocol: // - UDP
// - UDP // - TCP
// - TCP // - Both
// - Both // --ipv4-only:
// --ipv4-only: // --ipv6-only:
// --ipv6-only: // --no-upnp:
// --no-upnp: var params = {
var params = { port : port,
port : port, protocol : protocol
protocol : protocol };
};
c.api('/firewall/port?'+endurl, function(data) { c.api(method, '/firewall/port?'+endurl, params, function() { c.refresh() });
store.clear('slide');
c.redirect('#/tools/firewall');
}, method, params);
}
else {
store.clear('slide');
c.redirect('#/tools/firewall');
}
return; 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');
}
);
});
})();

View file

@ -24,45 +24,36 @@
$('#masthead').show() $('#masthead').show()
.find('.logout-btn').hide(); .find('.logout-btn').hide();
store.set('path-1', '#/login'); store.set('path-1', '#/login');
if ($('div.loader').length === 0) {
$('#main').append('<div class="loader loader-content"></div>'); c.showLoader();
// We gonna retry 3 times to check if yunohost is installed
if (app.isInstalledTry === undefined) {
app.isInstalledTry = 3;
} }
c.checkInstall(function(isInstalled) { c.checkInstall(function(isInstalled) {
if (isInstalled) { if (isInstalled) {
// Remove loader
$('div.loader').remove();
// Pass domain to hide form field
c.view('login', { 'domain': window.location.hostname }); c.view('login', { 'domain': window.location.hostname });
} else if (typeof isInstalled === 'undefined') { return;
if (app.isInstalledTry > 0) { }
app.isInstalledTry--;
app.loaded = false; // Show pacman
setTimeout(function() {
c.redirect('#/');
}, 5000);
}
else {
// Reset count
app.isInstalledTry = 3;
// API is not responding after 3 try if (typeof isInstalled !== 'undefined') {
$( 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();
c.redirect('#/postinstall'); 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 = { var params = {
password: c.params['password'] password: c.params['password']
}; };
c.api('/login', function(data) { c.api('POST', '/login', params, function(data) {
store.set('connected', true); store.set('connected', true);
c.trigger('login'); c.trigger('login');
$('#masthead .logout-btn').fadeIn(); $('#masthead .logout-btn').fadeIn();
@ -90,19 +81,19 @@
} else { } else {
c.redirect('#/'); c.redirect('#/');
} }
}, 'POST', params, false); }, undefined, false);
}); });
app.get('#/logout', function (c) { app.get('#/logout', function (c) {
c.api('/logout', function (data) { c.api('GET', '/logout', {}, function (data) {
store.clear('url'); store.clear('url');
store.clear('connected'); store.clear('connected');
store.set('path', '#/'); store.set('path', '#/');
c.trigger('logout'); c.trigger('logout');
c.flash('success', y18n.t('logged_out')); c.flash('success', y18n.t('logged_out'));
c.redirect('#/login'); c.redirect('#/login');
}, 'GET', {}, false); }, undefined, false);
}); });
})(); })();

View file

@ -13,7 +13,7 @@
$('#masthead').hide(); $('#masthead').hide();
c.checkInstall(function(isInstalled) { c.checkInstall(function(isInstalled) {
if (isInstalled || typeof isInstalled === 'undefined') { if (isInstalled || typeof isInstalled === 'undefined') {
c.redirect('#/login'); c.redirect_to('#/login');
} else { } else {
c.view('postinstall/postinstall_1'); c.view('postinstall/postinstall_1');
} }
@ -41,7 +41,6 @@
if ($('#domain').val() === '') { if ($('#domain').val() === '') {
if ($('#ddomain').val() === '') { if ($('#ddomain').val() === '') {
e.preventDefault(); e.preventDefault();
store.clear('slide');
c.flash('fail', y18n.t('error_select_domain')); c.flash('fail', y18n.t('error_select_domain'));
} else { } else {
domain = $('#ddomain').val() + $('select[name="ddomain-ext"]').val(); domain = $('#ddomain').val() + $('select[name="ddomain-ext"]').val();
@ -51,7 +50,7 @@
} }
store.set('maindomain', domain); 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) { app.get('#/postinstall/password', function(c) {
$('#masthead').hide(); $('#masthead').hide();
if (!store.get('maindomain')) { if (!store.get('maindomain')) {
store.clear('slide'); c.redirect_to('#/postinstall/domain');
c.redirect('#/postinstall/domain');
} else { } else {
c.view('postinstall/postinstall_3', { 'domain': store.get('maindomain').toLowerCase() }, 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
} }
}); });
// Execute post-installation // Execute post-installation
app.post('#/postinstall', function (c) { 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')); 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( // Check password matches confirmation
y18n.t('postinstall'), if (password !== confirmation) {
y18n.t('confirm_postinstall', [c.params['domain']]),
function(){
params.password = c.params['password'];
store.set('url', window.location.hostname +'/yunohost/api');
store.set('user', 'admin');
c.api('/postinstall', function(data) { // http://api.yunohost.org/#!/tools/tools_postinstall_post_0
c.redirect('#/login');
}, 'POST', params);
},
function(){
}
);
} else {
c.flash('fail', y18n.t('passwords_dont_match')); 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');
});
}
);
}); });
})(); })();

View file

@ -10,7 +10,7 @@
// All services status // All services status
app.get('#/services', function (c) { app.get('#/services', function (c) {
c.api('/services', function(data) { // ? c.api('GET', '/services', {}, function(data) {
var data2 = { var data2 = {
services: [] services: []
}; };
@ -45,7 +45,7 @@
// Status & actions for a service // Status & actions for a service
app.get('#/services/:service', function (c) { 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 = { var data2 = {
service: data service: data
}; };
@ -64,9 +64,45 @@
{ {
data2.service.active_at = 0; data2.service.active_at = 0;
} }
store.clear('slide'); c.view('service/service_info', data2, function() {
c.view('service/service_info', data2);
}, 'GET'); // 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 // Service log
@ -74,63 +110,14 @@
var params = { var params = {
number: 50 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'] }; data2 = { 'logs': [], 'name': c.params['service'] };
$.each(data, function(k, v) { $.each(data, function(k, v) {
data2.logs.push({filename: k, filecontent: v.join('\n')}); data2.logs.push({filename: k, filecontent: v.join('\n')});
}); });
c.view('service/service_log', data2); 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']);
}
);
}); });
})(); })();

View file

@ -26,77 +26,69 @@
}); });
if ($.isEmptyObject(params)) { if ($.isEmptyObject(params)) {
c.flash('fail', y18n.t('error_modify_something')); c.flash('fail', y18n.t('error_modify_something'));
store.clear('slide'); c.refresh();
c.redirect('#/tools/adminpw'); return;
} 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);
} }
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 // System update & upgrade
app.get('#/update', function (c) { app.get('#/update', function (c) {
c.api('/update', function(data) { c.api('PUT', '/update', {}, function(data) {
c.view('update/update', data); c.view('tools/tools_update', data, function() {
}, 'PUT'); // Configure buttons behaviors
}); $("button[data-upgrade]").on("click", function() {
// Upgrade apps or packages var what = $(this).data("upgrade").toLowerCase();
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');
}
);
});
// Upgrade a specific apps // Upgrade all apps or the system
app.get('#/upgrade/apps/:app', function (c) {
c.confirm( if ((what == "system") || (what == "system"))
y18n.t('tools'), {
y18n.t('confirm_update_specific_app', [c.params['app']]), var confirm_message = y18n.t('confirm_update_' + what);
function(){ var api_url = '/upgrade?'+what;
c.api('/upgrade/apps?app='+c.params['app'].toLowerCase(), }
function(data) {
store.clear('slide'); // Upgrade a specific apps
c.redirect('#/tools/logs');
}, else
'PUT'); {
}, var confirm_message = y18n.t('confirm_update_specific_app', [what]);
function(){ var api_url = '/upgrade/apps?app='+what;
store.clear('slide'); }
c.redirect('#/update');
} c.confirm(
); y18n.t('tools'),
confirm_message,
function(){
c.api('PUT', api_url, {}, function(data) {
c.redirect_to('#/tools/logs');
});
}
);
});
});
});
}); });
// Display journals list // Display journals list
app.get('#/tools/logs', function (c) { 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 = []; data = [];
category_icons = { category_icons = {
'operation': 'wrench', 'operation': 'wrench',
@ -138,8 +130,8 @@
var params = "?path=" + c.params["splat"][0]; var params = "?path=" + c.params["splat"][0];
var number = (c.params["number"])?c.params["number"]:50; var number = (c.params["number"])?c.params["number"]:50;
params += "&number=" + number; params += "&number=" + number;
c.api("/logs/display" + params, function(log) { c.api('GET', "/logs/display" + params, {}, function(log) {
if ('metadata' in log) { if ('metadata' in log) {
if (!'env' in log.metadata && 'args' in log.metadata) { if (!'env' in log.metadata && 'args' in log.metadata) {
log.metadata.env = log.metadata.args log.metadata.env = log.metadata.args
@ -149,10 +141,19 @@
"log": log, "log": log,
"next_number": log.logs.length == number ? number * 10:false, "next_number": log.logs.length == number ? number * 10:false,
"locale": y18n.locale "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 // Download SSL Certificate Authority
app.get('#/tools/ca', function (c) { app.get('#/tools/ca', function (c) {
@ -210,59 +211,44 @@
// Reboot or shutdown button // Reboot or shutdown button
app.get('#/tools/reboot', function (c) { 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 c.confirm(
app.get('#/tools/reboot/:action', function (c) { y18n.t('tools_' + action),
var action = c.params['action'].toLowerCase(); y18n.t('confirm_reboot_action_' + action),
if (action == 'reboot' || action == 'shutdown') { function(){
c.confirm( c.api('PUT', '/'+action+'?force', {}, function(data) {
y18n.t('tools_' + action), // This code is not executed due to 502 response (reboot or shutdown)
// confirm_reboot_action_reboot or confirm_reboot_action_shutdown c.redirect_to('#/logout');
y18n.t('confirm_reboot_action_' + action), }, function (xhr) {
function(){ c.flash('success', y18n.t('tools_' + action + '_done'))
c.api('/'+action+'?force', function(data) { // Disconnect from the webadmin
// This code is not executed due to 502 response (reboot or shutdown) store.clear('url');
c.redirect('#/logout'); store.clear('connected');
}, 'PUT', {}, false, function (xhr) { store.set('path', '#/');
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 // Rename the page to allow refresh without ask for rebooting
window.location.href = window.location.href.split('#')[0] + '#/'; window.location.href = window.location.href.split('#')[0] + '#/';
// Display reboot or shutdown info // Display reboot or shutdown info
// We can't use template because now the webserver is off // We can't use template because now the webserver is off
if (action == 'reboot') { if (action == 'reboot') {
$('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-refresh"></i> ' + y18n.t('tools_rebooting') + '</div></div>'); $('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-refresh"></i> ' + y18n.t('tools_rebooting') + '</div></div>');
} }
else { else {
$('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-power-off"></i> ' + y18n.t('tools_shuttingdown') + '</div></div>'); $('#main').replaceWith('<div id="main"><div class="alert alert-warning"><i class="fa-power-off"></i> ' + y18n.t('tools_shuttingdown') + '</div></div>');
} }
// Remove loader if any c.hideLoader();
$('div.loader').remove();
// Force scrollTop on page load // Force scrollTop on page load
$('html, body').scrollTop(0); $('html, body').scrollTop(0);
store.clear('slide'); }, false);
}); });
});
}, });
function(){
store.clear('slide');
c.redirect('#/tools/reboot');
}
);
}
else {
c.flash('fail', y18n.t('unknown_action', [action]));
store.clear('slide');
c.redirect('#/tools/reboot');
}
}); });
// Diagnosis // Diagnosis
@ -271,7 +257,7 @@
var private = (c.params.splat[0] == 'private'); var private = (c.params.splat[0] == 'private');
var endurl = (private) ? '?private' : ''; var endurl = (private) ? '?private' : '';
c.api('/diagnosis'+endurl, function(diagnosis) { c.api('GET', '/diagnosis'+endurl, {}, function(diagnosis) {
c.view('tools/tools_diagnosis', { c.view('tools/tools_diagnosis', {
'diagnosis' : JSON.stringify(diagnosis, undefined, 4), 'diagnosis' : JSON.stringify(diagnosis, undefined, 4),
'raw' : diagnosis, 'raw' : diagnosis,
@ -280,10 +266,10 @@
}); });
}); });
// Reboot or shutdown button // Migrations
app.get('#/tools/migrations', function (c) { app.get('#/tools/migrations', function (c) {
c.api('/migrations?pending', function(pending_migrations) { c.api('GET', '/migrations?pending', {}, function(pending_migrations) {
c.api('/migrations?done', function(done_migrations) { c.api('GET', '/migrations?done', {}, function(done_migrations) {
pending_migrations = pending_migrations.migrations; pending_migrations = pending_migrations.migrations;
done_migrations = done_migrations.migrations; done_migrations = done_migrations.migrations;
@ -302,71 +288,45 @@
c.view('tools/tools_migrations', { c.view('tools/tools_migrations', {
'pending_migrations' : pending_migrations.reverse(), 'pending_migrations' : pending_migrations.reverse(),
'done_migrations' : done_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 // List available apps lists
app.get('#/tools/appslists', function (c) { app.get('#/tools/appslists', function (c) {
c.api('/appslists', function(data) { c.api('GET', '/appslists', {}, function(data) {
list = []; list = [];
$.each(data, function(listname, listinfo) { $.each(data, function(listname, listinfo) {
list.push({ list.push({
@ -379,7 +339,7 @@
c.view('tools/tools_appslists_list', { c.view('tools/tools_appslists_list', {
appslists: list appslists: list
}); });
}, 'GET'); });
}); });
// Add a new apps list // Add a new apps list
@ -389,15 +349,14 @@
'url' : c.params['appslist_url'] 'url' : c.params['appslist_url']
} }
c.api('/appslists', function(data) { c.api('PUT', '/appslists', list, function(data) {
store.clear('slide'); c.redirect_to('#/tools/appslists/' + list.name);
c.redirect('#/tools/appslists/' + list.name); });
}, 'PUT', list);
}); });
// Show appslist info and operations // Show appslist info and operations
app.get('#/tools/appslists/:appslist', function (c) { 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') { if (typeof data[c.params['appslist']] !== 'undefined') {
list = { list = {
'name' : c.params['appslist'], 'name' : c.params['appslist'],
@ -409,25 +368,23 @@
} }
else { else {
c.flash('warning', y18n.t('appslists_unknown_list', [c.params['appslist']])); c.flash('warning', y18n.t('appslists_unknown_list', [c.params['appslist']]));
store.clear('slide'); c.redirect_to('#/tools/appslists', {slide: false});
c.redirect('#/tools/appslists');
} }
}, 'GET'); });
}); });
// Refresh available apps list // Refresh available apps list
app.get('#/tools/appslists/refresh', function (c) { app.get('#/tools/appslists/refresh', function (c) {
c.api('/appslists', function(data) { c.api('PUT', '/appslists', {}, function(data) {
// c.redirect(store.get('path')); c.redirect_to('#/apps/install', {slide: false});
c.redirect('#/apps/install'); });
}, 'PUT');
}); });
// Refresh specific apps list // Refresh specific apps list
app.get('#/tools/appslists/:appslist/refresh', function (c) { app.get('#/tools/appslists/:appslist/refresh', function (c) {
c.api('/appslists', function(data) { c.api('PUT', '/appslists', {'name' : c.params['appslist']}, function(data) {
c.redirect('#/tools/appslists'); c.redirect_to('#/tools/appslists', {slide: false});
}, 'PUT', {'name' : c.params['appslist']}); });
}); });
// Remove apps list // Remove apps list
@ -436,13 +393,9 @@
y18n.t('appslist'), y18n.t('appslist'),
y18n.t('appslists_confirm_remove', [c.params['app']]), y18n.t('appslists_confirm_remove', [c.params['app']]),
function() { function() {
c.api('/appslists', function() { c.api('DELETE', '/appslists', {'name' : c.params['appslist']}, function() {
c.redirect('#/tools/appslists'); c.redirect_to('#/tools/appslists');
}, 'DELETE', {'name' : c.params['appslist']}); });
},
function() {
store.clear('slide');
c.redirect('#/tools/appslists/'+ c.params['appslist']);
} }
); );
}); });

View file

@ -31,20 +31,20 @@
* @param.item Name of the user or the permission to add or remove * @param.item Name of the user or the permission to add or remove
* @param.group Name of the group affected * @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) * HTML dataset attributes (e.g. link in the partial inline view "label" in group_list.ms)
* *
* @return void * @return void
**/ **/
function updateGroup(model, params) { function updateGroup(model, params) {
var type = params.type; var type = params.type;
var operation = params.operation; var action = params.action;
var item = params.item; var item = params.item;
var groupname = params.group; var groupname = params.group;
var group = data.groups[groupname]; var group = data.groups[groupname];
var to = (operation == 'add')?group[type]:group[type + 'Inv']; var to = (action == 'add')?group[type]:group[type + 'Inv'];
var from = (operation == 'add')?group[type+'Inv']:group[type]; var from = (action == 'add')?group[type+'Inv']:group[type];
// Do nothing, if array of destination already contains the item // Do nothing, if array of destination already contains the item
if (from.indexOf(item) === -1) return; if (from.indexOf(item) === -1) return;
// Hack to disable pacman loader if any // Hack to disable pacman loader if any
@ -52,22 +52,22 @@
$('#main').append('<div class="loader loader-content" style="display: none"></div>'); $('#main').append('<div class="loader loader-content" style="display: none"></div>');
} }
$('div.loader').css('display', 'none'); $('div.loader').css('display', 'none');
// Update group // Update group
var params = {}; var url; var params = {}; var url;
if (type == 'members') { if (type == 'members') {
url = '/users/groups/' + groupname; url = '/users/groups/' + groupname;
params[operation] = [item]; params[action] = [item];
} }
else { else {
url = '/users/permissions/' + item; 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); to.push(item);
from.splice(from.indexOf(item), 1); from.splice(from.indexOf(item), 1);
updateView(data); updateView(data);
}, 'PUT', params); });
} }
/** /**
@ -85,29 +85,41 @@
model.groups[group].members.sort(); model.groups[group].members.sort();
model.groups[group].membersInv.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 // uneeded behaviour
var rendered = c.render('views/user/group_list.ms', model); var rendered = c.render('views/user/group_list.ms', model);
rendered.swap(function () { rendered.swap(function () {
// Add click event to get a nice "reactive" interface // Add click event to get a nice "reactive" interface
jQuery(".group-update").on('click', function (e) { $("button[data-action='add'], button[data-action='remove']").on('click', function (e) {
updateGroup(model, jQuery(this)[0].dataset); updateGroup(model, $(this)[0].dataset);
return false; return false;
}); });
jQuery(".group-add-user").on('click', function (e) { $('button[data-action="add-user-specific-permission"]').on('click', function (e) {
data.groups[$(this)[0].dataset.user].display = true; data.groups[$(this).data("user")].display = true;
updateView(data); updateView(data);
return false; return false;
}); });
$('button[data-action="delete-group"]').on('click', function (e) {
var group = $(this).data("group");
c.confirm(
y18n.t('groups'),
$('<div>'+ y18n.t('confirm_delete', [group]) +'</div>'),
function() {
c.api('DELETE', '/users/groups/'+ group, {}, function(data) { c.refresh(); });
}
);
});
}); });
} }
app.get('#/groups', function (c) { app.get('#/groups', function (c) {
c.api('/users/groups?full&include_primary_groups', function(data_groups) { c.api('GET', '/users/groups?full&include_primary_groups', {}, function(data_groups) {
c.api('/users', function(data_users) { c.api('GET', '/users', {}, function(data_users) {
c.api('/users/permissions?short', function(data_permissions) { c.api('GET', '/users/permissions?short', {}, function(data_permissions) {
//var perms = data_permissions.permissions; //var perms = data_permissions.permissions;
var specific_perms = {}; var specific_perms = {};
var all_perms = data_permissions.permissions; var all_perms = data_permissions.permissions;
@ -129,7 +141,7 @@
// Declare all_users and visitors has special // Declare all_users and visitors has special
data_groups.groups['all_users'].special = true; data_groups.groups['all_users'].special = true;
data_groups.groups['visitors'].special = true; data_groups.groups['visitors'].special = true;
// Data given to the view with 2 functions to convert technical // Data given to the view with 2 functions to convert technical
// permission id to display names // permission id to display names
data = { data = {
@ -166,33 +178,9 @@
app.post('#/groups/create', function (c) { app.post('#/groups/create', function (c) {
c.params['groupname'] = c.params['groupname'].replace(' ', '_').toLowerCase(); c.params['groupname'] = c.params['groupname'].replace(' ', '_').toLowerCase();
c.api('/users/groups', function(data) { c.api('POST', '/users/groups', c.params.toHash(), function(data) {
c.redirect('#/groups'); c.redirect_to('#/groups');
}, 'POST', c.params.toHash()); });
});
app.get('#/groups/:group/delete', function (c) {
var params = {};
// make confirm content
var confirmModalContent = $('<div>'+ y18n.t('confirm_delete', [c.params['group']]) +'</div>');
// 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');
}
);
}); });
/** /**
@ -202,14 +190,14 @@
// List existing users // List existing users
app.get('#/users', function (c) { 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); c.view('user/user_list', data);
}); });
}); });
// Create user form // Create user form
app.get('#/users/create', function (c) { 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 // Password min length
data.password_min_length = 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'] == c.params['confirmation']) {
if (c.params['password'].length < PASSWORD_MIN_LENGTH) { if (c.params['password'].length < PASSWORD_MIN_LENGTH) {
c.flash('fail', y18n.t('password_too_short')); c.flash('fail', y18n.t('password_too_short'));
store.clear('slide');
} }
else { else {
// Force unit or disable quota // Force unit or disable quota
@ -242,28 +229,63 @@
// Compute email field // Compute email field
c.params['mail'] = c.params['email'] + c.params['domain']; c.params['mail'] = c.params['email'] + c.params['domain'];
c.api('/users', function(data) { // http://api.yunohost.org/#!/user/user_create_post_2 c.api('POST', '/users', c.params.toHash(), function(data) {
c.redirect('#/users'); c.redirect_to('#/users');
}, 'POST', c.params.toHash()); });
} }
} else { } else {
c.flash('fail', y18n.t('passwords_dont_match')); c.flash('fail', y18n.t('passwords_dont_match'));
store.clear('slide');
//c.redirect('#/users/create');
} }
}); });
// Show user information // Show user information
app.get('#/users/:user', function (c) { app.get('#/users/:user', function (c) {
c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_info_get_0 c.api('GET', '/users/'+ c.params['user'], {}, function(data) {
c.view('user/user_info', 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 = '<div><input type="checkbox" id="purge-user-data" name="purge-user-data"> <label for="purge-user-data">'+ y18n.t('purge_user_data_checkbox', [user]) +'</label></div>';
var purgeAlertMessage = '<div class="danger" style="display: none">⚠ '+ y18n.t('purge_user_data_warning') +'</div>';
var confirmModalContent = $('<div>'+ y18n.t('confirm_delete', [user]) +'<br><br>'+ purgeCheckbox +'<br>'+ purgeAlertMessage +'</div>');
// 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 // Edit user form
app.get('#/users/:user/edit', function (c) { 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('GET', '/users/'+ c.params['user'], {}, function(data) {
c.api('/domains', function(dataDomains) { // http://api.yunohost.org/#!/domain/domain_list_get_2 c.api('GET', '/domains', {}, function(dataDomains) {
// Password min length // Password min length
data.password_min_length = PASSWORD_MIN_LENGTH; data.password_min_length = PASSWORD_MIN_LENGTH;
@ -314,7 +336,7 @@
// Update user information // Update user information
app.put('#/users/:user', function (c) { app.put('#/users/:user', function (c) {
// Get full user object // 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 // Force unit or disable quota
if (c.params['mailbox_quota']) { if (c.params['mailbox_quota']) {
c.params['mailbox_quota'] += "M"; c.params['mailbox_quota'] += "M";
@ -353,79 +375,32 @@
if ($.isEmptyObject(params)) { if ($.isEmptyObject(params)) {
c.flash('fail', y18n.t('error_modify_something')); c.flash('fail', y18n.t('error_modify_something'));
store.clear('slide'); c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
c.redirect('#/users/'+ c.params['user'] + '/edit');
} else { } else {
if (params['password']) { if (params['password']) {
if (params['password'] == params['confirmation']) { if (params['password'] == params['confirmation']) {
if (params['password'].length < PASSWORD_MIN_LENGTH) { if (params['password'].length < PASSWORD_MIN_LENGTH) {
c.flash('fail', y18n.t('password_too_short')); c.flash('fail', y18n.t('password_too_short'));
store.clear('slide'); c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
c.redirect('#/users/'+ c.params['user'] + '/edit');
} }
else { else {
params['change_password'] = params['password']; params['change_password'] = params['password'];
c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_update_put_1 c.api('PUT', '/users/'+ c.params['user'], params, function(data) {
c.redirect('#/users/'+ c.params['user']); c.redirect_to('#/users/'+ c.params['user']);
}, 'PUT', params); });
} }
} else { } else {
c.flash('fail', y18n.t('passwords_dont_match')); c.flash('fail', y18n.t('passwords_dont_match'));
store.clear('slide'); c.redirect_to('#/users/'+ c.params['user'] + '/edit', {slide: false});
c.redirect('#/users/'+ c.params['user'] + '/edit');
} }
} }
else { else {
c.api('/users/'+ c.params['user'], function(data) { // http://api.yunohost.org/#!/user/user_update_put_1 c.api('PUT', '/users/'+ c.params['user'], params, function(data) {
c.redirect('#/users/'+ c.params['user']); c.redirect_to('#/users/'+ c.params['user']);
}, 'PUT', params); });
} }
} }
}, 'GET');
});
// Remove existing user
app.get('#/users/:user/delete', function (c) {
var params = {};
// make confirm content
var purgeCheckbox = '<div><input type="checkbox" id="purge-user-data" name="purge-user-data"> <label for="purge-user-data">'+ y18n.t('purge_user_data_checkbox', [c.params['user']]) +'</label></div>';
var purgeAlertMessage = '<div class="danger" style="display: none">⚠ '+ y18n.t('purge_user_data_warning') +'</div>';
var confirmModalContent = $('<div>'+ y18n.t('confirm_delete', [c.params['user']]) +'<br><br>'+ purgeCheckbox +'<br>'+ purgeAlertMessage +'</div>');
// 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();
};
}); });
}); });
})(); })();

View file

@ -8,7 +8,7 @@
* *
*/ */
app.bind('login', function(e, data) { app.bind('login', function(e, data) {
c.api('/users', function(data) { c.api('GET', '/users', {}, function(data) {
// Warn admin if no users are created. // Warn admin if no users are created.
if (typeof data.users !== 'undefined' && data.users.length === 0) { if (typeof data.users !== 'undefined' && data.users.length === 0) {
c.flash('warning', y18n.t('warning_first_user')); c.flash('warning', y18n.t('warning_first_user'));
@ -69,13 +69,13 @@
c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed])); c.flash('fail', y18n.t('error_retrieve_feed', [securityFeed]));
}); });
c.api("/diagnosis", function(data) { c.api("GET", "/diagnosis", {}, function(data) {
versions = data.packages; versions = data.packages;
$('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo])); $('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo]));
if (data.security["CVE-2017-5754"].vulnerable) { if (data.security["CVE-2017-5754"].vulnerable) {
c.flash('danger', y18n.t('meltdown')); c.flash('danger', y18n.t('meltdown'));
} }
$('div.loader').remove(); c.hideLoader();
}); });
}); });
}); });

View file

@ -12,7 +12,7 @@
function prefetchDomains(req) { function prefetchDomains(req) {
// Preload domains list. // Preload domains list.
req.params.domains = []; req.params.domains = [];
req.api('/domains', function(data) { req.api('GET', '/domains', {}, function(data) {
req.params.domains = data.domains; req.params.domains = data.domains;
}); });
} }
@ -20,7 +20,7 @@
function prefetchUsers(req){ function prefetchUsers(req){
// Preload users lists. // Preload users lists.
req.params.users = []; req.params.users = [];
req.api('/users', function(data) { req.api('GET', '/users', {}, function(data) {
req.params.users = data.users; req.params.users = data.users;
}); });
} }

View file

@ -3,20 +3,64 @@
var app = Sammy.apps['#main']; var app = Sammy.apps['#main'];
var store = app.store; 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 * Helpers
* *
*/ */
app.helpers({ app.helpers({
// Serialize an object //
serialize : function(obj) { // Pacman loader management
var str = []; //
for(var p in obj)
if (obj.hasOwnProperty(p)) { showLoader: function() {
str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p])); 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('<div class="loader loader-content"></div>');
} }
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 // Flash helper to diplay instant notifications
@ -85,7 +129,7 @@
}, },
// API call // API call
api: function(uri, callback, method, data, websocket, callbackOnFailure) { api: function(method, uri, data, callback, callbackOnFailure, websocket) {
c = this; c = this;
method = typeof method !== 'undefined' ? method : 'GET'; method = typeof method !== 'undefined' ? method : 'GET';
@ -93,96 +137,69 @@
if (window.navigator && window.navigator.language && (typeof data.locale === 'undefined')) { if (window.navigator && window.navigator.language && (typeof data.locale === 'undefined')) {
data.locale = y18n.locale || window.navigator.language.substr(0, 2); data.locale = y18n.locale || window.navigator.language.substr(0, 2);
} }
app.loaded = false;
if ($('div.loader').length === 0) { c.showLoader();
$('#main').append('<div class="loader loader-content"></div>');
}
call = function(uri, callback, method, data, callbackOnFailure) { call = function(uri, callback, method, data, callbackOnFailure) {
var args = data; // Define default callback for failures
// TODO: change this code
if (uri === '/postinstall') {
var post_installing = false;
setInterval(function () {
post_installing = true;
}, 1500);
}
if (typeof callbackOnFailure !== 'function') { if (typeof callbackOnFailure !== 'function') {
callbackOnFailure = function(xhr) { callbackOnFailure = function(xhr) {
// Postinstall is a custom case, we have to wait that if (xhr.status == 200) {
// operation is done before doing anything // Fail with 200, WTF
if ((uri === '/postinstall') && (post_installing)) { callback({});
interval = window.location.hostname === args.domain ? 20000 : 5000;
checkInstall = setInterval(function () {
c.checkInstall(function(isInstalled) {
if (isInstalled || typeof isInstalled === 'undefined') {
c.flash('success', y18n.t('installation_complete'));
clearInterval(checkInstall);
window.location.href = 'https://'+ window.location.hostname +'/yunohost/admin/';
}
});
}, interval);
} }
// Regular errors // 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 { else {
if (xhr.status == 200) { var errorMessage = xhr.status+' '+xhr.statusText;
// Fail with 200, WTF c.flash('fail', y18n.t('error_server_unexpected', [errorMessage]));
callback({}); console.log(xhr);
}
// 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');
} }
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; 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 <pre> 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 // Default callbacks
confirmCallback = typeof confirmCallback !== 'undefined' ? confirmCallback : function() {}; confirmCallback = typeof confirmCallback !== 'undefined' ? confirmCallback : function() {};
cancelCallback = typeof cancelCallback !== 'undefined' ? cancelCallback : function() {}; cancelCallback = typeof cancelCallback !== 'undefined' ? cancelCallback : function() {};
c.hideLoader();
// Get modal element // Get modal element
var box = $('#modal'); var box = $('#modal');
@ -335,12 +291,10 @@
$('#modal footer button').unbind( "click" ); $('#modal footer button').unbind( "click" );
// Reset & Hide modal // Reset & Hide modal
box box.removeClass('no-title').modal('hide');
.removeClass('no-title')
.modal('hide');
// Do corresponding callback // Do corresponding callback
if ($(this).data('action') == 'confirm') { if ($(this).data('modal-action') == 'confirm') {
confirmCallback(); confirmCallback();
} }
else { else {
@ -352,19 +306,165 @@
return box.modal('show'); return box.modal('show');
}, },
selectAllOrNone: function () {
// Remove active style from buttons // Render view (cross-browser)
$(".select_all-none input").click(function(){ $(this).toggleClass("active"); }); view: function (view, data, callback) {
// Select all checkbox in this panel c = this;
$(".select_all").click(function(){
$(this).parents(".panel").children(".list-group").find("input").prop("checked", true); // Default
}); callback = typeof callback !== 'undefined' ? callback : function() {};
// Deselect all checkbox in this panel
$(".select_none").click(function(){ // Hide loader and modal
$(this).parents(".panel").children(".list-group").find("input").prop("checked", false); 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 <pre> 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) { arraySortById: function(arr) {
arr.sort(function(a, b){ arr.sort(function(a, b){
if (a.id > b.id) { if (a.id > b.id) {
@ -385,74 +485,20 @@
}); });
}, },
groupHooks: function(hooks, raw_infos){ // Serialize an object
var data = {}; serialize : function(obj) {
var rules = [ var str = [];
{ for(var p in obj)
id:'configuration', if (obj.hasOwnProperty(p)) {
isIn:function (hook) { str.push(encodeURIComponent(p) + "=" + encodeURIComponent(obj[p]));
return hook.indexOf('conf_')==0 }
} return str.join("&");
}
];
$.each(hooks, function(i, hook) {
var group_id=hook;
var hook_size=(raw_infos && raw_infos[hook] && raw_infos[hook].size)?raw_infos[hook].size:0;
$.each(rules, function(i, rule) {
if (rule.isIn(hook)) {
group_id = 'adminjs_group_'+rule.id;
return false;
}
});
if(group_id in data) {
data[group_id] = {
name:y18n.t('hook_'+group_id),
value:data[group_id].value+','+hook,
description:data[group_id].description+', '+y18n.t('hook_'+hook),
size:data[group_id].size + hook_size
};
}
else {
data[group_id] = {
name:y18n.t('hook_'+group_id),
value:hook,
description:(group_id==hook)?y18n.t('hook_'+hook+'_desc'):y18n.t('hook_'+hook),
size:hook_size
};
}
});
return data;
}, },
ungroupHooks: function(system_parts,apps) {
var data = {};
data['apps'] = apps || [];
data['system'] = system_parts || [];
if (data['system'].constructor !== Array) { //
data['system'] = [data['system']]; // Misc helpers used in views etc..
} //
if (data['apps'].constructor !== Array) {
data['apps'] = [data['apps']];
}
// Some hook value contains multiple hooks separated by commas
var split_hooks = [];
$.each(data['system'], function(i, hook) {
split_hooks = split_hooks.concat(hook.split(','));
});
data['system'] = split_hooks;
if (data['system'].length == 0) {
delete data['system'];
}
if (data['apps'].length == 0) {
delete data['apps'];
}
return data;
},
// Paste <pre> // Paste <pre>
prePaste: function() { prePaste: function() {
@ -461,8 +507,7 @@
// Get paste content element // Get paste content element
var preElement = $($(this).data('paste-content')); var preElement = $($(this).data('paste-content'));
// Add pacman loader c.showLoader();
$('#main').append('<div class="loader loader-content"></div>');
// Send to paste.yunohost.org // Send to paste.yunohost.org
$.ajax({ $.ajax({
@ -477,11 +522,11 @@
c.flash('fail', y18n.t('paste_error')); c.flash('fail', y18n.t('paste_error'));
}) })
.always(function(){ .always(function(){
// Remove pacman c.hideLoader();
$('div.loader').remove();
}); });
}); });
} }
}); });
})(); })();

View file

@ -181,7 +181,7 @@
sam.store.set('url', window.location.hostname + '/yunohost/api'); sam.store.set('url', window.location.hostname + '/yunohost/api');
if (sam.store.get('connected')) { if (sam.store.get('connected')) {
this.api('/diagnosis', function(diagnosis) { this.api('GET', '/diagnosis', {}, function(diagnosis) {
versions = diagnosis.packages; versions = diagnosis.packages;
$('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo])); $('#yunohost-version').html(y18n.t('footer_version', [versions.yunohost.version, versions.yunohost.repo]));
}); });

View file

@ -385,6 +385,6 @@
"appslists_last_update": "Last update", "appslists_last_update": "Last update",
"appslists_unknown_list": "Unknown apps list: %s", "appslists_unknown_list": "Unknown apps list: %s",
"name": "Name", "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!" "purge_user_data_warning": "Purging user's data is not reversible. Be sure you know what you're doing!"
} }

View file

@ -57,9 +57,9 @@
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'app_info_default_desc' settings.domain}}</p> <p>{{t 'app_info_default_desc' settings.domain}}</p>
<a role="button" href="#/apps/{{settings.id}}/default" class="btn btn-success slide"> <button class="btn btn-success" data-action="set-as-default" data-app="{{settings.id}}">
<span class="fa-star"></span> {{t 'app_make_default'}} <span class="fa-star"></span> {{t 'app_make_default'}}
</a> </button>
</div> </div>
<hr> <hr>
<div class="container"> <div class="container">
@ -79,9 +79,9 @@
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'app_info_uninstall_desc'}}</p> <p>{{t 'app_info_uninstall_desc'}}</p>
<a href="#/apps/{{settings.id}}/uninstall" role="button" class="btn btn-danger slide back"> <button class="btn btn-danger slide back" data-action="uninstall" data-app="{{settings.id}}">
<span class="fa-trash-o"></span> {{t 'uninstall'}} <span class="fa-trash-o"></span> {{t 'uninstall'}}
</a> </button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -3,12 +3,6 @@
<a href="#/backup">{{t 'backup'}}</a> <a href="#/backup">{{t 'backup'}}</a>
</div> </div>
<div class="actions-group">
<!--<a role="button" href="#/storages/create" class="btn btn-success slide">
<span class="fa-plus"></span> {{t 'storages_new'}}
</a>-->
</div>
<div class="separator"></div> <div class="separator"></div>
<div class="list-group"> <div class="list-group">

View file

@ -83,41 +83,11 @@
</h2> </h2>
</div> </div>
<div class="panel-body"> <div class="panel-body">
<!--<div class="container">
<p>{{t 'backup_archive_download'}}</p>
<a role="button" class="btn btn-info slide" href="#/backup/{{storage.id}}/{{name}}/download">
<span class="fa-download"></span> {{t 'download'}}
</a>
</div>
<hr>-->
<div class="container"> <div class="container">
<p>{{t 'backup_archive_delete'}}</p> <p>{{t 'backup_archive_delete'}}</p>
<a href="#/backup/{{storage.id}}/{{name}}/delete" role="button" class="btn btn-danger slide"> <button class="btn btn-danger slide" data-action="delete" data-storage="{{storage.id}}" data-archive="{{name}}">
<span class="fa-trash-o"></span> {{t 'delete'}} <span class="fa-trash-o"></span> {{t 'delete'}}
</a> </button>
</div> </div>
{{#if other_storages}}
<hr>
<div class="container">
<p>{{t 'backup_archive_copy'}}</p>
<form action="#/backup/{{storage.id}}/{{name}}/copy" method="POST" class="form-horizontal">
<div class="form-group has-feedback">
<label for="label" class="col-sm-12">{{t 'url'}}</label>
<div class="col-sm-12">
<select id="storage" name="storage" class="form-control" required>
{{#each storages}}
<option value="{{id}}">{{name}} ({{uri}})</option>
{{/each}}
</select>
</div>
</div>
<div class="form-group">
<div class="text-center">
<input type="submit" role="button" class="btn btn-success slide" value="{{t 'copy'}}">
</div>
</div>
</form>
</div>
{{/if}}
</div> </div>
</div> </div>

View file

@ -1,52 +0,0 @@
<div class="btn-breadcrumb">
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/backup">{{t 'backup'}}</a>
<a href="#/backup/{{storage.id}}/create">{{t 'storage_create'}}</a>
</div>
<div class="separator"></div>
<form action="#/storages" method="POST" class="form-horizontal">
<div class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label for="type" class="col-sm-3 control-label">{{t 'backup_type'}}</label>
<div class="col-sm-9">
<select class="form-control" name="type">
<option>sftp</option>
<option>ftp</option>
<option>rsync</option>
</select>
</div>
</div>
<div class="form-group">
<label for="domain" class="col-sm-3 control-label">{{t 'domain'}}</label>
<div class="col-sm-9">
<input type="text" id="domain" name="domain" class="form-control" placeholder="monserver.fr" required>
</div>
</div>
<div class="form-group">
<label for="username" class="col-sm-3 control-label">{{t 'user_username'}}</label>
<div class="col-sm-9">
<input type="text" id="username" name="username" class="form-control" placeholder="johndoe" required>
</div>
</div>
<div class="form-group">
<label for="password" class="col-sm-3 control-label">{{t 'password'}}</label>
<div class="col-sm-9">
<input type="password" id="password" name="password" class="form-control" placeholder="•••••" required>
</div>
</div>
<div class="form-group">
<label for="path" class="col-sm-3 control-label">{{t 'path'}}</label>
<div class="col-sm-9">
<input type="text" id="path" name="path" class="form-control" placeholder="~/" required>
</div>
</div>
</div>
</div>
<div class="text-center">
<input type="submit" role="button" class="btn btn-success slide back" value="{{t 'save'}}">
</div>
</form>

View file

@ -46,36 +46,36 @@
<p><span class="fa-fw fa-meh-o"></span> <p><span class="fa-fw fa-meh-o"></span>
{{t 'domain_not_eligible_for_ACME'}}</p> {{t 'domain_not_eligible_for_ACME'}}</p>
{{/if}} {{/if}}
<a role="button" href="#/domains/{{name}}/cert-install-LE" class="btn btn-success {{#unless status.ACME_eligible}}disabled{{/unless}}"> <button class="btn btn-success {{#unless status.ACME_eligible}}disabled{{/unless}}" data-domain="{{name}}" data-action="install-LE" >
<span class="fa-star"></span> {{t 'install_letsencrypt_cert'}} <span class="fa-star"></span> {{t 'install_letsencrypt_cert'}}
</a> </button>
<hr> <hr>
</div> </div>
{{/if}} {{/if}}
{{#if actions_enabled.manual_renew_letsencrpt}} {{#if actions_enabled.manual_renew_letsencrpt}}
<div class="container"> <div class="container">
<p>{{t 'manually_renew_letsencrypt_message'}}</p> <p>{{t 'manually_renew_letsencrypt_message'}}</p>
<a role="button" href="#/domains/{{name}}/cert-renew-letsencrypt" class="btn btn-warning"> <button class="btn btn-warning" data-domain="{{name}}" data-action="renew-letsencrypt">
<span class="fa-refresh"></span> {{t 'manually_renew_letsencrypt'}} <span class="fa-refresh"></span> {{t 'manually_renew_letsencrypt'}}
</a> </button>
</div> </div>
<hr> <hr>
{{/if}} {{/if}}
{{#if actions_enabled.regen_selfsigned}} {{#if actions_enabled.regen_selfsigned}}
<div class="container"> <div class="container">
<p>{{t 'regenerate_selfsigned_cert_message'}}</p> <p>{{t 'regenerate_selfsigned_cert_message'}}</p>
<a href="#/domains/{{name}}/cert-regen-selfsigned" role="button" class="btn btn-warning"> <button class="btn btn-warning" data-domain="{{name}}" data-action="regen-selfsigned">
<span class="fa-refresh"></span> {{t 'regenerate_selfsigned_cert'}} <span class="fa-refresh"></span> {{t 'regenerate_selfsigned_cert'}}
</a> </button>
</div> </div>
<hr> <hr>
{{/if}} {{/if}}
{{#if actions_enabled.replace_with_selfsigned}} {{#if actions_enabled.replace_with_selfsigned}}
<div class="container"> <div class="container">
<p>{{t 'revert_to_selfsigned_cert_message'}}</p> <p>{{t 'revert_to_selfsigned_cert_message'}}</p>
<a href="#/domains/{{name}}/cert-replace-with-selfsigned" role="button" class="btn btn-danger"> <button class="btn btn-danger" data-domain="{{name}}" data-action="replace-with-selfsigned">
<span class="fa-exclamation-triangle"></span> {{t 'revert_to_selfsigned_cert'}} <span class="fa-exclamation-triangle"></span> {{t 'revert_to_selfsigned_cert'}}
</a> </button>
</div> </div>
{{/if}} {{/if}}
</div> </div>

View file

@ -12,67 +12,46 @@
<span class="fa-fw fa-globe"></span> {{name}} <span class="fa-fw fa-globe"></span> {{name}}
</h2> </h2>
</div> </div>
<div class="panel-body">
{{#if main}}
<p class="alert alert-success">
<span class="fa-star" title="{{t 'default'}}"></span> {{t 'domain_default_longdesc'}}
</p>
{{/if}}
<p><a href="{{url}}" target="_blank">{{url}}</a></p>
</div>
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">
<span class="fa-fw fa-wrench"></span> {{t 'operations'}}
</h2>
</div>
<div class="panel-body"> <div class="panel-body">
<div class="container"> <div class="container">
<p>{{t 'domain_visit_url' url}}</p> <p>{{t 'domain_visit_url' url}}</p>
<a role="button" href="{{url}}" class="btn btn-success" target="_blank"> <a role="button" href="{{url}}" class="btn btn-success" target="_blank">
{{t 'domain_visit'}} <span class="fa-fw fa-external-link"></span> <span class="fa-fw fa-external-link"></span> {{t 'domain_visit'}}
</a> </a>
</div> </div>
{{#unless main}}
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'domain_default_desc'}}</p> <p>{{t 'domain_default_desc'}}</p>
<form method="POST" action="#/domains"> {{#if main}}
<input type="hidden" name="domain" value="{{name}}" required class="form-control"> <p class="alert alert-info">
<button type="submit" class="btn btn-primary slide back" value="{{t 'set_default'}}"> <span class="fa-star" title="{{t 'default'}}"></span> {{t 'domain_default_longdesc'}}
{{t 'set_default'}} <span class="fa-fw fa-star"></span> </p>
</button> {{else}}
</form> <button class="btn btn-primary" data-action="set_default" data-domain="{{name}}">
<span class="fa-fw fa-star"></span> {{t 'set_default'}}
</button>
{{/if}}
</div> </div>
{{/unless}}
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'domain_dns_longdesc'}}</p> <p>{{t 'domain_dns_longdesc'}}</p>
<a role="button" href="#/domains/{{name}}/dns" class="btn btn-default slide"> <a role="button" href="#/domains/{{name}}/dns" class="btn btn-default slide">
{{t 'domain_dns_config'}} <span class="fa-fw fa-globe"></span> <span class="fa-fw fa-globe"></span> {{t 'domain_dns_config'}}
</a> </a>
</div> </div>
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'certificate_manage'}}</p> <p>{{t 'certificate_manage'}}</p>
{{#unless enable_cert_management}} <a href="#/domains/{{name}}/cert-management" role="button" class="btn btn-default slide">
<p><span class="fa-fw fa-exclamation-circle"></span> <span class="fa-fw fa-lock"></span> {{t 'ssl_certificate'}}
{{t 'certificate_old_letsencrypt_app_conflict'}}
</p>
{{/unless}}
<a href="#/domains/{{name}}/cert-management" role="button" class="btn btn-default slide {{#unless enable_cert_management}}disabled{{/unless}}">
{{t 'ssl_certificate'}} <span class="fa-fw fa-lock"></span>
</a> </a>
</div> </div>
<hr> <hr>
<div class="container"> <div class="container">
<p>{{t 'domain_delete_longdesc' name}}</p> <p>{{t 'domain_delete_longdesc' name}}</p>
<a href="#/domains/{{name}}/delete" role="button" class="btn btn-danger slide back"> <button class="btn btn-danger" data-action="delete" data-domain="{{name}}">
{{t 'delete'}} <span class="fa-fw fa-trash-o"></span> <span class="fa-fw fa-trash-o"></span> {{t 'delete'}}
</a> </button>
</div> </div>
</div> </div>
</div> </div>

View file

@ -53,25 +53,25 @@
<div class="pull-right"> <div class="pull-right">
{{#is_loaded}} {{#is_loaded}}
<a href="#/services/{{name}}/disable" role="button" class="btn btn-danger"> <button class="btn btn-danger" data-service="{{name}}" data-action="disable">
<span class="fa-square-o"></span> {{t 'disable'}} <span class="fa-square-o"></span> {{t 'disable'}}
</a> </button>
{{/is_loaded}} {{/is_loaded}}
{{^is_loaded}} {{^is_loaded}}
<a href="#/services/{{name}}/enable" role="button" class="btn btn-success"> <button class="btn btn-success" data-service="{{name}}" data-action="enable">
<span class="fa-check-square-o"></span> {{t 'enable'}} <span class="fa-check-square-o"></span> {{t 'enable'}}
</a> </button>
{{/is_loaded}} {{/is_loaded}}
{{#is_running}} {{#is_running}}
<a href="#/services/{{name}}/stop" role="button" class="btn btn-danger"> <button class="btn btn-danger" data-service="{{name}}" data-action="stop">
<span class="fa-stop"></span> {{t 'stop'}} <span class="fa-stop"></span> {{t 'stop'}}
</a> </button>
{{/is_running}} {{/is_running}}
{{^is_running}} {{^is_running}}
<a href="#/services/{{name}}/start" role="button" class="btn btn-success"> <button class="btn btn-success" data-service="{{name}}" data-action="start">
<span class="fa-play"></span> {{t 'start'}} <span class="fa-play"></span> {{t 'start'}}
</a> </button>
{{/is_running}} {{/is_running}}
<a href="#/services/{{name}}/log" role="button" class="btn btn-default slide"> <a href="#/services/{{name}}/log" role="button" class="btn btn-default slide">
<span class="fa-book"></span> {{t 'log'}} <span class="fa-book"></span> {{t 'log'}}

View file

@ -29,20 +29,20 @@
<td> <td>
{{#if this.ipv4}} {{#if this.ipv4}}
<span class="fa-check"></span> <span class="fa-check"></span>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/tcp/ipv4/close">{{t 'close'}}</a> <button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv4">{{t 'close'}}</button>
{{else}} {{else}}
<span></span> <span></span>
<span class="fa-times"></span> <span class="fa-times"></span>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/tcp/ipv4/open">{{t 'open'}}</a> <button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv4">{{t 'open'}}</button>
{{/if}} {{/if}}
</td> </td>
<td> <td>
{{#if this.ipv6}} {{#if this.ipv6}}
<span class="fa-check"></span> <span class="fa-check"></span>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/tcp/ipv6/close">{{t 'close'}}</a> <button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv6">{{t 'close'}}</button>
{{else}} {{else}}
<span class="fa-times"></span> <span class="fa-times"></span>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/tcp/ipv6/open">{{t 'open'}}</a> <button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="tcp" data-connection="ipv6">{{t 'open'}}</button>
{{/if}} {{/if}}
</td> </td>
<td> <td>
@ -75,20 +75,20 @@
<td> <td>
{{#if this.ipv4}} {{#if this.ipv4}}
<span class="fa-check"></span> <span class="fa-check"></span>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/udp/ipv4/close">{{t 'close'}}</a> <button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="udp" data-connection="ipv4">{{t 'close'}}</button>
{{else}} {{else}}
<span></span> <span></span>
<span class="fa-times"></span> <span class="fa-times"></span>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/udp/ipv4/open">{{t 'open'}}</a> <button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="udp" data-connection="ipv4">{{t 'open'}}</button>
{{/if}} {{/if}}
</td> </td>
<td> <td>
{{#if this.ipv6}} {{#if this.ipv6}}
<span class="fa-check"></span> <span class="fa-check"></span>
<a class="btn btn-xs btn-danger" href="#/tools/firewall/port/{{@key}}/udp/ipv6/close">{{t 'close'}}</a> <button class="btn btn-xs btn-danger" data-action="close" data-port="{{@key}}" data-protocol="udp" data-connection="ipv6">{{t 'close'}}</button>
{{else}} {{else}}
<span class="fa-times"></span> <span class="fa-times"></span>
<a class="btn btn-xs btn-success" href="#/tools/firewall/port/{{@key}}/udp/ipv6/open">{{t 'open'}}</a> <button class="btn btn-xs btn-success" data-action="open" data-port="{{@key}}" data-protocol="udp" data-connection="ipv6">{{t 'open'}}</button>
{{/if}} {{/if}}
</td> </td>
<td> <td>
@ -168,10 +168,10 @@
<div class="panel-body"> <div class="panel-body">
{{#if upnp}} {{#if upnp}}
<p class="text-success">{{t 'upnp_enabled'}}</p> <p class="text-success">{{t 'upnp_enabled'}}</p>
<a role="button" href="#/tools/firewall/upnp/disable" class="btn btn-danger">{{t 'disable'}}</a> <button class="btn btn-danger" data-upnp="disable">{{t 'disable'}}</button>
{{else}} {{else}}
<p class="text-danger">{{t 'upnp_disabled'}}</p> <p class="text-danger">{{t 'upnp_disabled'}}</p>
<a role="button" href="#/tools/firewall/upnp/enable" class="btn btn-success">{{t 'enable'}}</a> <button class="btn btn-success" data-upnp="enable">{{t 'enable'}}</button>
{{/if}} {{/if}}
</div> </div>
</div> </div>

View file

@ -10,9 +10,9 @@
</div> </div>
<div class="actions-group"> <div class="actions-group">
<a href="javascript:void(null);" onclick="c.api('/logs/display?path={{#if log.name}}{{ log.name }}{{else}}{{ log.log_path }}{{/if}}&share', function(data) { $('div.loader').remove(); window.open(data.url, '_blank'); });" class="btn btn-success"> <button class="btn btn-success" data-action="share" data-log-id="{{#if log.name}}{{ log.name }}{{else}}{{ log.log_path }}{{/if}}">
<span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}} <span class="fa-cloud-upload"></span> {{t 'logs_share_with_yunopaste'}}
</a> </button>
</div> </div>
<div class="separator"></div> <div class="separator"></div>

View file

@ -22,7 +22,7 @@
<div id="collapse-{{key}}" class="panel-collapse{{#if @first}}{{else}} collapse{{/if}}" role="tabpanel" aria-labelledby="heading-{{key}}"> <div id="collapse-{{key}}" class="panel-collapse{{#if @first}}{{else}} collapse{{/if}}" role="tabpanel" aria-labelledby="heading-{{key}}">
<div class="list-group"> <div class="list-group">
{{#value}} {{#value}}
<a href="#/tools/logs/{{ name }}" class="list-group-item" title='{{formatTime started_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}'><small style="margin-right:20px;" >{{formatRelative started_at}}</small> <a href="#/tools/logs/{{ name }}" class="list-group-item slide" title='{{formatTime started_at day="numeric" month="long" year="numeric" hour="numeric" minute="numeric"}}'><small style="margin-right:20px;" >{{formatRelative started_at}}</small>
<span class="fa-fw fa-{{success_icon}}"></span> {{ description }}</a> <span class="fa-fw fa-{{success_icon}}"></span> {{ description }}</a>
{{/value}} {{/value}}
</div> </div>

View file

@ -11,7 +11,7 @@
<h2 class="panel-title"><span class="fa-fw fa-cogs"></span> {{t 'migrations_pending'}} <h2 class="panel-title"><span class="fa-fw fa-cogs"></span> {{t 'migrations_pending'}}
{{#if pending_migrations}} {{#if pending_migrations}}
<div class="btn-toolbar pull-right"> <div class="btn-toolbar pull-right">
<a href="#/tools/migrations/run" class="btn btn-sm btn-success"><span class="fa-fw fa-play"></span> {{t 'run'}}</a> <button class="btn btn-sm btn-success" data-action="run"><span class="fa-fw fa-play"></span> {{t 'run'}}</button>
</div> </div>
{{/if}} {{/if}}
</h2> </h2>
@ -24,7 +24,7 @@
<h3 class="list-group-item-heading"> <h3 class="list-group-item-heading">
{{ number }}. {{ description }} {{ number }}. {{ description }}
<div class="btn-toolbar pull-right"> <div class="btn-toolbar pull-right">
<a href="#/tools/migrations/skip/{{ id }}" class="btn btn-xs btn-warning" style="color:white;"><span class="fa-fw fa-close"></span> {{t 'skip'}}</a> <button class="btn btn-xs btn-warning" style="color:white;" data-action="skip" data-migration="{{id}}"><span class="fa-fw fa-close"></span> {{t 'skip'}}</button>
</div> </div>
</h3> </h3>
{{#if disclaimer }} {{#if disclaimer }}

View file

@ -15,14 +15,14 @@
</div> </div>
<div class="panel-body"> <div class="panel-body">
<p> <p>
<a role="button" href="#/tools/reboot/reboot" class="btn btn-danger"> <button class="btn btn-danger" data-action="reboot">
<i class="fa-refresh"></i> {{t 'tools_reboot_btn'}} <i class="fa-refresh"></i> {{t 'tools_reboot_btn'}}
</a> </button>
</p> </p>
<p> <p>
<a role="button" href="#/tools/reboot/shutdown" class="btn btn-danger"> <button class="btn btn-danger" data-action="shutdown">
<i class="fa-power-off"></i> {{t 'tools_shutdown_btn'}} <i class="fa-power-off"></i> {{t 'tools_shutdown_btn'}}
</a> </button>
</p> </p>
</div> </div>
</div> </div>

View file

@ -1 +0,0 @@
<div class="alert alert-warning"><i class="fa-refresh"></i> {{t 'tools_rebooting'}}</div>

View file

@ -1 +0,0 @@
<div class="alert alert-warning"><i class="fa-power-off"></i> {{t 'tools_shuttingdown'}}</div>

View file

@ -20,7 +20,7 @@
{{/system}} {{/system}}
</div> </div>
<div class="panel-footer"> <div class="panel-footer">
<a href="#/upgrade/system" role="button" class="btn btn-success">{{t 'system_upgrade_all_packages_btn'}}</a> <button class="btn btn-success" data-upgrade="system">{{t 'system_upgrade_all_packages_btn'}}</button>
</div> </div>
{{else}} {{else}}
<div class="panel-body"> <div class="panel-body">
@ -37,14 +37,14 @@
<div class="list-group"> <div class="list-group">
{{#apps}} {{#apps}}
<div class="list-group-item clearfix"> <div class="list-group-item clearfix">
<a href="#/upgrade/apps/{{id}}" role="button" class="btn btn-success pull-right">{{t 'system_upgrade_btn'}}</a> <button class="btn btn-success pull-right" data-upgrade="{{id}}">{{t 'system_upgrade_btn'}}</button>
<h3 class="list-group-item-heading">{{label}} <small>{{id}}</small></h3> <h3 class="list-group-item-heading">{{label}} <small>{{id}}</small></h3>
<p class="list-group-item-text">{{t 'from_to' current_version new_version}}</p> <p class="list-group-item-text">{{t 'from_to' current_version new_version}}</p>
</div> </div>
{{/apps}} {{/apps}}
</div> </div>
<div class="panel-footer"> <div class="panel-footer">
<a role="button" href="#/upgrade/apps" class="btn btn-success">{{t 'system_upgrade_all_applications_btn'}}</a> <button class="btn btn-success" data-upgrade="apps">{{t 'system_upgrade_all_applications_btn'}}</button>
</div> </div>
{{else}} {{else}}
<div class="panel-body"> <div class="panel-body">

View file

@ -1,18 +0,0 @@
<div class="btn-breadcrumb">
<a href="#/" ><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/update">{{t 'system_update'}}</a>
<a href="#/upgrade">{{t 'system_upgrade'}}</a>
</div>
<div class="separator"></div>
{{#if logs}}
<pre id="upgrade-log" class="upgrade-log log">
{{#logs}}
{{.}}
{{/logs}}
</pre>
<button data-paste-content="#upgrade-log"><i class="fa-cloud-upload"></i> {{t 'upload'}}</button>
{{else}}
{{t 'no_log'}}
{{/if}}

View file

@ -17,10 +17,10 @@
<span class="label label-default label-removable"> <span class="label label-default label-removable">
<span class="fa-fw fa-{{icon}}"></span> <span class="fa-fw fa-{{icon}}"></span>
{{text}} {{text}}
<a role="button" data-type="{{type}}s" data-operation="remove" data-item="{{value}}" data-group="{{group}}" class="group-update"> <button data-type="{{type}}s" data-action="remove" data-item="{{value}}" data-group="{{group}}">
<span class="fa-close" style="margin-left:5px"></span> <span class="fa-close" style="margin-left:5px"></span>
<span class="sr-only">{{t 'delete'}}</span> <span class="sr-only">{{t 'delete'}}</span>
</a> </button>
</span> </span>
{{/inline}} {{/inline}}
@ -35,7 +35,7 @@
</button> </button>
<ul class="dropdown-menu"> <ul class="dropdown-menu">
{{#each inv}} {{#each inv}}
<li><a href="#" data-type="{{../type}}s" data-operation="add" data-item="{{.}}" data-group="{{../group}}" class="group-update">{{call ../display .}}</a></li> <li><button data-type="{{../type}}s" data-action="add" data-item="{{.}}" data-group="{{../group}}" >{{call ../display .}}</button></li>
{{/each}} {{/each}}
</ul> </ul>
</div> </div>
@ -53,10 +53,10 @@
<span class="fa-fw fa-group"></span> {{#if special}}{{t (concat 'group_' @key)}}{{else}}{{t 'group'}} "{{ucwords @key}}"{{/if}} <span class="fa-fw fa-group"></span> {{#if special}}{{t (concat 'group_' @key)}}{{else}}{{t 'group'}} "{{ucwords @key}}"{{/if}}
</a> </a>
{{#unless special}} {{#unless special}}
<a href="#/groups/{{@key}}/delete" role="button" class="group-delete"> <button class="group-delete" data-action="delete-group" data-group="{{@key}}">
<span class="fa-close"></span> <span class="fa-close"></span>
<span class="sr-only">{{t 'delete'}}</span> <span class="sr-only">{{t 'delete'}}</span>
</a> </button>
{{/unless}} {{/unless}}
</h2> </h2>
</div> </div>
@ -122,7 +122,7 @@
{{#each groups}} {{#each groups}}
{{#if primary}} {{#if primary}}
{{#unless (or permissions display)}} {{#unless (or permissions display)}}
<li><a href="#" data-user="{{@key}}" class="group-add-user">{{@key}}</a></li> <li><button data-action="add-user-specific-permission" data-user="{{@key}}">{{@key}}</button></li>
{{/unless}} {{/unless}}
{{/if}} {{/if}}
{{/each}} {{/each}}

View file

@ -62,7 +62,7 @@
</table> </table>
<span class="pull-right"> <span class="pull-right">
<a role="button" href="#/users/{{username}}/edit" class="btn btn-info slide"><span class="fa-pencil-square-o"/> {{t 'user_username_edit' username}}</a> <a role="button" href="#/users/{{username}}/edit" class="btn btn-info slide"><span class="fa-pencil-square-o"/> {{t 'user_username_edit' username}}</a>
<a role="button" href="#/users/{{username}}/delete" class="btn btn-danger slide back"><span class="fa-trash-o"/> {{t 'delete'}}</a> <button class="btn btn-danger" data-action="delete" data-user="{{username}}"><span class="fa-trash-o"/> {{t 'delete'}}</a>
</span> </span>
</div> </div>
</div> </div>