[enh] Allow backup/restore

This commit is contained in:
zamentur 2015-10-01 23:56:11 +02:00
parent 26e9f1166d
commit bec1798f8d
9 changed files with 8878 additions and 22 deletions

File diff suppressed because one or more lines are too long

View file

@ -1819,11 +1819,248 @@ var app = Sammy('#main', function (sam) {
* Backup
*
*/
// Backup view
var config_hooks = [
'system_ldap',
'system_ssowat',
'system_cron',
'system_ssh',
'system_xmpp',
'system_mysql',
'system_yunohost',
'system_nginx'
];
// Storage list
sam.get('#/backup', function (c) {
c.view('backup/backup');
var storages = [];
var item = {
id: 'local',
name: y18n.t('local_archives'),
uri: '/home/yunohost.backup/'
};
storages.push(item);
c.view('backup/backup', {'storages':storages});
});
// Storage list
sam.get('#/storages/create', function (c) {
c.view('backup/storage_create', {});
});
// Create a storage
sam.post('#/storages', function (c) {
store.clear('slide');
c.redirect('#/storages');
});
// Create a backup
sam.get('#/backup/:storage/create', function (c) {
var data=[];
data['storage']={
id:c.params['storage'],
name:y18n.t('local_archives')
};
data['hooks']=[
{
id:'system_home',
name:y18n.t('home_data')
},
{
id:'system_mail',
name:y18n.t('mail')
},
{
id:'adminjs_group_hooks_config',
name:y18n.t('configuration'),
hooks:config_hooks
}
];
//TODO: (ljf) add personal hools in data['items']
data['apps']=[];
c.api('/apps?raw', function(dataraw) { // http://api.yunohost.org/#!/app/app_list_get_8
$.each(dataraw, function(app_id, app) {
if (app['installed']) {
data['apps'].push({
id: app_id,
app_id: app_id,
name: app['manifest']['name']
});
}
});
c.view('backup/backup_create', data);
});
});
sam.post('#/backup/:storage', function (c) {
var params = {
'hooks': c.params['hooks'],
'apps': c.params['apps']
};
if (params['hooks']==undefined)
params['hooks']=[];
else if (params['hooks'].constructor !== Array)
params['hooks']=[params['hooks']];
if (params['apps']==undefined)
params['apps']=[];
else if (params['apps'].constructor !== Array)
params['apps']=[params['apps']];
var config_pos=params['hooks'].indexOf('adminjs_group_hooks_config');
if (config_pos>-1)
{
params['hooks'].splice(config_pos, 1);
params['hooks']=params['hooks'].concat(config_hooks);
}
if (params['hooks'].length==0)
params['ignore_hooks']='';
if (params['apps'].length==0)
params['ignore_apps']='';
c.api('/backup', function() {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']);
}, 'POST', params);
});
// Restore a backup
sam.post('#/backup/:storage/:archive/restore', function (c) {
c.confirm(
y18n.t('backup'),
y18n.t('confirm_restore', [c.params['archive']]),
$.proxy(function(c){
var params={};
params['apps']=c.params['apps'];
params['hooks']=c.params['hooks'];
if (params['hooks']==undefined)
params['hooks']=[];
else if (params['hooks'].constructor !== Array)
params['hooks']=[params['hooks']];
if (params['apps']==undefined)
params['apps']=[];
else if (params['apps'].constructor !== Array)
params['apps']=[params['apps']];
if (params['hooks'].length==0)
params['ignore_hooks']='';
if (params['apps'].length==0)
params['ignore_apps']='';
var config_pos=params['hooks'].indexOf('adminjs_group_hooks_config');
if (config_pos>-1)
{
params['hooks'].splice(config_pos, 1);
params['hooks']=params['hooks'].concat(config_hooks);
}
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
sam.get('#/backup/:storage/:archive/delete', function (c) {
c.confirm(
y18n.t('backup'),
y18n.t('confirm_delete', [c.params['archive']]),
function(){
c.api('/backup/'+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
sam.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
sam.get('#/backup/:storage/:archive/copy', function (c) {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
});
// Upload a backup
sam.get('#/backup/:storage/:archive/upload', function (c) {
store.clear('slide');
c.redirect('#/backup/'+ c.params['storage']+'/'+c.params['archive']);
});
// Get archive info
sam.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['hooks2']={};
$.each(data['hooks'], function(hook, paths) {
if (hook.substring(0, 7)=='system_')
{
var service=hook.slice(7);
if (service=='home')
data['hooks2']['system_home']={
name:y18n.t('home_data')
};
else if (service=='mail')
data['hooks2']['system_mail']={
name:y18n.t('mail')
};
else
data['hooks2']['adminjs_group_hooks_config']={
name:y18n.t('configuration')
};
}
else
data['hooks2'][hook]={
name:hook
};
});
data['hooks']=data['hooks2'];
data['items']=(data['hooks']!={} || data['apps']!=[]);
c.view('backup/backup_info', data);
});
});
// Archive list
sam.get('#/backup/:storage', function (c) {
c.api('/backup/archives?with_info', function(data) {
data['storage']={
id:'local',
name:y18n.t('local_archives')
};
data['archives2']=[];
$.each(data['archives'], function(name, info) {
info['name']=name;
data['archives2'].unshift(info)
});
data['archives']=data['archives2'];
c.view('backup/backup_list', data);
});
});
});

View file

@ -243,5 +243,32 @@
"user_new_mail": "newmail@mydomain.org",
"user_new_forward": "newforward@myforeigndomain.org",
"user_mailbox_quota": "Mailbox quota",
"mailbox_quota_description": "Mailbox quota must be a size with b/k/M/G/T suffix or 0 to disable the quota."
"mailbox_quota_description": "Mailbox quota must be a size with b/k/M/G/T suffix or 0 to disable the quota.",
"local_archives": "Local archives",
"storages_new": "New remote storage",
"storages_no": "No storages.",
"backup_new": "New backup",
"backups_no": "No backup",
"upload_archive": "Archive upload",
"upload": "Upload",
"archive_empty": "Empty archive",
"backup_archive_delete": "Delete this archive",
"backup_archive_copy": "Copy this archive on another storage",
"copy": "Copy",
"backup_create": "Create a backup",
"restore": "Restore",
"download": "Download",
"home_data": "User data",
"mail": "Mail",
"configuration": "Configuration",
"backup_optional_encryption": "Optional encryption",
"backup_optional_password": "Optional password",
"backup_encryption_warning": "Don't forget this password, you will need it if you want restore the archive",
"backup_action": "Backup",
"backup_archive_download": "Download this archive",
"storage_create": "Add a remote storage",
"confirm_restore": "Are you sure you want to restore %s ?",
"backup_type": "Type",
"path": "Path"
}

View file

@ -224,5 +224,32 @@
"user_emailaliases": "Adresses supplémentaires",
"user_emailforward": "Adresses de transfert",
"user_new_mail": "nouvelle_adresse@domaine.org",
"user_new_forward": "nouveau_transfert@domainedistant.org"
"user_new_forward": "nouveau_transfert@domainedistant.org",
"local_archives": "Archives locales",
"storages_new": "Nouveau stockage distant",
"storages_no": "Aucun stockage.",
"backup_new": "Nouvelle sauvegarde",
"backups_no": "Aucune sauvegarde.",
"upload_archive": "Uploader une sauvegarde",
"upload": "Uploader",
"archive_empty": "La sauvegarde est vide.",
"backup_archive_delete": "Supprimer cette sauvegarde",
"backup_archive_copy": "Copier cette sauvegarde sur un autre stockage",
"copy": "Copier",
"backup_create": "Créer une sauvegarde",
"restore": "Restorer",
"download": "Télécharger",
"home_data": "Données utilisateurs",
"mail": "Mail",
"configuration": "Configuration",
"backup_optional_encryption": "Chiffrement optionnel",
"backup_optional_password": "Mot de passe optionnel",
"backup_encryption_warning": "N'oubliez pas ce mot de passe, vous en aurez besoin pour restaurer cette sauvegarde",
"backup_action": "Sauvegarder",
"backup_archive_download": "Télécharger cette sauvegarde",
"storage_create": "Ajouter un stockage distant",
"confirm_restore": "Êtes-vous sur de vouloir restaurer %s ?",
"backup_type": "Type",
"path": "Répertoire"
}

View file

@ -1,12 +1,30 @@
<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="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/backup" class="btn btn-default">{{t 'backup'}}</a>
</div>
<div class="actions-group">
<a href="#/storages/create" class="btn btn-success slide">
<span class="fa-plus"></span> {{t 'storages_new'}}
</a>
</div>
<div class="separator"></div>
<div class="list-group">
{{#each storages}}
<a href="#/backup/{{id}}" class="list-group-item slide clearfix">
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{name}} <small>{{id}}</small></h2>
<p class="list-group-item-text">{{uri}}</p>
</a>
{{else}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
<strong>{{t 'backup_warning_title'}}</strong>
<br>{{t 'backup_warning_desc'}}
{{t 'storages_no'}}
</div>
{{/each}}
</div>

View file

@ -0,0 +1,49 @@
<div class="btn-breadcrumb">
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/backup" class="btn btn-default">{{t 'backup'}}</a>
<a href="#/backup/{{storage.id}}" class="btn btn-default">{{storage.name}}</a>
<a href="#/backup/{{storage.id}}/create" class="btn btn-default">{{t 'backup_create'}}</a>
</div>
<div class="separator"></div>
<form action="#/backup/{{storage.id}}" method="POST" class="form-horizontal">
<div class="list-group">
{{#each hooks}}
<label for="{{id}}" class="list-group-item">
<span class="pull-right"><input type="checkbox" id="{{id}}" name="hooks" value="{{id}}" checked></span>
<h2 class="list-group-item-heading">{{name}}</h2>
</label>
{{/each}}
{{#each apps}}
<label for="{{id}}" class="list-group-item">
<span class="pull-right"><input type="checkbox" id="{{id}}" name="apps" value="{{id}}" checked></span>
<h2 class="list-group-item-heading">{{name}} <small>{{id}}</small></h2>
</label>
{{/each}}
</div>
<div class="separator"></div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-lock"></span> {{t 'backup_optional_encryption'}}</h2>
</div>
<div class="panel-body">
<div class="form-group has-feedback">
<label for="label" class="col-sm-12">{{t 'password'}}</label>
<div class="col-sm-12">
<input type="password" id="password" name="password" class="form-control" placeholder="{{t 'backup_optional_password'}}">
<p class="text-warning">
{{t 'backup_encryption_warning'}}
</p>
</div>
</label>
</div>
</div>
</div>
<div class="separator"></div>
<span class="pull-right">
<input type="submit" class="btn btn-success slide back" value="{{t 'backup_action'}}" />
</span>
</form>

View file

@ -0,0 +1,86 @@
<div class="btn-breadcrumb">
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/backup" class="btn btn-default">{{t 'backup'}}</a>
<a href="#/backup/{{storage.id}}" class="btn btn-default">{{storage.name}}</a>
<a href="#/backup/{{storage.id}}/{{name}}" class="btn btn-default">{{name}}</a>
</div>
<div class="separator"></div>
<form action="#/backup/{{storage.id}}/{{name}}/restore" method="POST" class="form-horizontal">
<div class="list-group">
{{#each hooks}}
<label for="{{@key}}" class="list-group-item">
<span class="pull-right"><input type="checkbox" id="{{@key}}" name="hooks" value="{{@key}}" checked></span>
<h2 class="list-group-item-heading">{{name}}</h2>
</label>
{{/each}}
{{#each apps}}
<label for="{{id}}" class="list-group-item">
<span class="pull-right"><input type="checkbox" id="{{id}}" name="apps" value="{{id}}" checked></span>
<h2 class="list-group-item-heading">{{name}} <small>{{id}}</small></h2>
</label>
{{/each}}
{{^items}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'archive_empty'}}
</div>
{{/items}}
</div>
{{#if items}}
<span class="pull-right">
<input type="submit" class="btn btn-danger slide back" value="{{t 'restore'}}" />
</span>
{{/if}}
</form>
<div class="separator"></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="container">
<p>{{t 'backup_archive_download'}}</p>
<a class="btn btn-info slide" href="#/backup/{{storage.id}}/{{name}}/download">
<span class="fa-download"></span> {{t 'download'}}
</a>
</div>
<hr>
<div class="container">
<p>{{t 'backup_archive_delete'}}</p>
<a href="#/backup/{{storage.id}}/{{name}}/delete" class="btn btn-danger slide">
<span class="fa-trash"></span> {{t 'delete'}}
</a>
</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" class="btn btn-success slide" value="{{t 'copy'}}">
</div>
</div>
</form>
</div>
{{/if}}
</div>
</div>

View file

@ -0,0 +1,53 @@
<div class="btn-breadcrumb">
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/backup" class="btn btn-default">{{t 'backup'}}</a>
<a href="#/backup/{{storage.id}}" class="btn btn-default">{{storage.name}}</a>
</div>
<div class="actions-group">
<a href="#/backup/{{storage.id}}/create" class="btn btn-success slide">
<span class="fa-plus"></span> {{t 'backup_new'}}
</a>
</div>
<div class="separator"></div>
<div class="list-group">
{{#each archives}}
<a href="#/backup/{{../storage.id}}/{{name}}" class="list-group-item slide clearfix">
<span class="pull-right fa-chevron-right"></span>
<h2 class="list-group-item-heading">{{created_at}} <small>{{name}} ({{humanSize size}})</small></h2>
<p class="list-group-item-text">{{path}}</p>
</a>
{{/each}}
{{^archives}}
<div class="alert alert-warning">
<span class="fa-exclamation-triangle"></span>
{{t 'backups_no'}}
</div>
{{/archives}}
</div>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title"><span class="fa-fw fa-download"></span> {{t 'upload_archive'}}</h2>
</div>
<div class="panel-body">
<form action="#/backup/{{storage.id}}/upload" 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">
<input type="file" id="url" name="url" value="" required pattern="^.*\.tar\.gz$">
</div>
</div>
<div class="form-group">
<div class="text-center">
<input type="submit" class="btn btn-success slide" value="{{t 'upload'}}">
</div>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,52 @@
<div class="btn-breadcrumb">
<a href="#/"><i class="fa-home"></i><span class="sr-only">{{t 'home'}}</span></a>
<a href="#/backup" class="btn btn-default">{{t 'backup'}}</a>
<a href="#/backup/{{storage.id}}/create" class="btn btn-default">{{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" class="btn btn-success slide back" value="{{t 'save'}}">
</div>
</form>