add database backup

This commit is contained in:
Shaun Chyxion 2018-02-23 18:50:44 +08:00
parent bc3a90d9b1
commit cf35982fb3
24 changed files with 257 additions and 20 deletions

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,45 @@
package com.pudonghot.ambition.crm.controller;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotNull;
import com.pudonghot.ambition.crm.model.User;
import org.springframework.stereotype.Controller;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import com.pudonghot.ambition.crm.service.DatabaseBackupService;
/**
* @author Shaun Chyxion <br>
* chyxion@163.com <br>
* Mar 10, 2017 23:15:01
*/
@Validated
@Controller
@RequiresRoles(User.ROLE_ADMIN)
@RequestMapping("/database-backup")
public class DatabaseBackupController {
@Autowired
private DatabaseBackupService databaseBackupService;
/**
* list backups
* @return database backups
*/
@RequestMapping("/list")
public List<Date> list() {
return databaseBackupService.list();
}
@RequestMapping(value = "/restore", method = RequestMethod.POST)
public void restore(
@NotNull
@RequestParam("date")
final Long date) {
databaseBackupService.restore(new Date(date));
}
}

View File

@ -0,0 +1,29 @@
package com.pudonghot.ambition.crm.service;
import java.util.Date;
import java.util.List;
import javax.validation.constraints.NotNull;
import org.springframework.validation.annotation.Validated;
/**
* @version 0.0.1
* @since 0.0.1
* @author Shaun Chyxion <br>
* chyxion@163.com <br>
* May 4, 2016 1:18:42 PM
*/
@Validated
public interface DatabaseBackupService {
/**
* list database backup dates
* @return database backup dates
*/
List<Date> list();
/**
* restore to date
* @param date date
*/
void restore(@NotNull Date date);
}

View File

@ -0,0 +1,74 @@
package com.pudonghot.ambition.crm.service.support;
import java.io.File;
import java.util.Date;
import java.util.List;
import java.io.IOException;
import java.util.Comparator;
import java.text.ParseException;
import lombok.extern.slf4j.Slf4j;
import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Service;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.springframework.beans.factory.annotation.Value;
import com.pudonghot.ambition.crm.service.DatabaseBackupService;
/**
* @version 0.0.1
* @since 0.0.1
* @author Shaun Chyxion <br>
* chyxion@163.com <br>
* Sep 22, 2015 10:45:41 AM
*/
@Slf4j
@Service
public class DatabaseBackupServiceSupport implements DatabaseBackupService {
@Value("${database.backup-dir}")
private String backupDir;
@Value("${database.restore-shell}")
private String restoreShell;
private static final String FILE_NAME_DATE_FORMAT = "yyyyMMddHHmmss";
/**
* {@inheritDoc}
*/
@Override
public List<Date> list() {
return FileUtils.listFiles(new File(backupDir), new String[] { "gz" }, false)
.stream().map(this::fileNameToDate)
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
}
/**
* {@inheritDoc}
*/
@Override
public void restore(final Date date) {
log.info("Restore database backup [{}].", date);
try {
Runtime.getRuntime().exec(restoreShell + " " +
DateFormatUtils.format(date, FILE_NAME_DATE_FORMAT)).waitFor();
}
catch (final IOException | InterruptedException e) {
throw new IllegalStateException(
"Restore database backup [" + date + "] error caused", e);
}
}
private Date fileNameToDate(final File file) {
log.info("Parse file [{}] to date.", file);
final String name = file.getName();
try {
return DateUtils.parseDate(name.substring(0, name.length() - 7),
FILE_NAME_DATE_FORMAT);
}
catch (ParseException e) {
throw new IllegalStateException(
"Parse file name [" + name + "] to date error caused", e);
}
}
}

View File

@ -8,6 +8,8 @@ datasource.port=63306
datasource.database-name=ambition_crm_test
datasource.username=root
datasource.password=696@2^~)oZ@^#*Q
database.backup-dir=/Users/chyxion/Workspaces/ambition-crm/database_backups
database.restore-shell=/data/program/mysql-backup/bin/mysql_restore.sh
# Shiro
shiro.session.validation.scheduler.enabled=true

View File

@ -8,6 +8,8 @@ datasource.port=43306
datasource.database-name=ambition_crm
datasource.username=root
datasource.password=696@2^~)oZ@^#*Q
database.backup-dir=/data/program/mysql-backup/backup/ambition_crm
database.backup-shell=/data/program/mysql-backup/bin/mysql_restore.sh
# Shiro
shiro.session.validation.scheduler.enabled=true

View File

@ -2,5 +2,6 @@ import Ember from 'ember';
export default Ember.Component.extend({
classNames: ['widget-header'],
dropdownMenu: true
dropdownMenu: true,
'search-box': true
});

View File

@ -64,6 +64,10 @@ Router.map(function() {
this.route('create');
this.route('edit', {path: '/:id/edit'});
});
this.route('database-backup', function() {
this.route('list');
});
});
export default Router;

View File

@ -0,0 +1,18 @@
import BaseRoute from '../base';
export default BaseRoute.extend({
breadcrumbs: [{text: 'Database Backup'}],
model() {
return this.get('store').ajaxGet('database-backup/list');
},
actions: {
restore(date) {
const me = this;
me.get('dialog').confirm('Are you sure to restore database backup?', () => {
me.get('store').ajaxPost('database-backup/restore', {date}).then(() => {
me.get('message').alert('Database backup restored successfully');
});
});
}
}
});

View File

@ -15,4 +15,7 @@
{{/if}}
{{reload-btn clear-search=(get this 'clear-search')}}
</div>
{{#if search-box}}
{{search-box}}
{{/if}}

View File

@ -78,6 +78,12 @@
<span class="menu-text"> Import Records </span>
{{/link-to}}
</li>
<li>
{{#link-to 'database-backup.list'}}
<i class="menu-icon fa fa-archive blue"></i>
<span class="menu-text"> Database Backup </span>
{{/link-to}}
</li>
{{/if}}
</ul><!-- /.nav-list -->

View File

@ -59,7 +59,7 @@
<td>
<div class="hidden-sm hidden-xs btn-group">
{{status-toggle-button model=it}}
{{#link-to 'customer-application.edit' it.id class='btn btn-xs btn-info' title='Edit'}}
{{#link-to 'customer-application.edit' it.id class='btn btn-xs btn-info' data-rel='tooltip' title='Edit'}}
<i class="ace-icon fa fa-pencil bigger-120"></i>
{{/link-to}}
{{!--

View File

@ -48,16 +48,16 @@
<td>
<div class="hidden-sm hidden-xs btn-group">
{{status-toggle-button model=it}}
{{#link-to 'customer-status.edit' it.id class='btn btn-xs btn-info' title='Edit'}}
{{#link-to 'customer-status.edit' it.id class='btn btn-xs btn-info' data-rel="tooltip" title='Edit'}}
<i class="ace-icon fa fa-pencil bigger-120"></i>
{{/link-to}}
{{#if (not-eq model.data.firstObject.id it.id)}}
<button class="btn btn-xs btn-purple" title="Move Up" {{action (route-action 'moveUp' it)}}>
<button class="btn btn-xs btn-purple" data-rel="tooltip" title="Move Up" {{action (route-action 'moveUp' it)}}>
<i class="ace-icon fa fa-arrow-up bigger-120"></i>
</button>
{{/if}}
{{#if (not-eq model.data.lastObject.id it.id)}}
<button class="btn btn-xs" title="Move Down" {{action (route-action 'moveDown' it)}}>
<button class="btn btn-xs" data-rel="tooltip" title="Move Down" {{action (route-action 'moveDown' it)}}>
<i class="ace-icon fa fa-arrow-down bigger-120"></i>
</button>
{{/if}}

View File

@ -0,0 +1,41 @@
{{#main-content}}
<div class="widget-box transparent">
{{grid-header search-box=false}}
<div class="widget-body">
<!-- #section:custom/scrollbar -->
<div class="widget-main no-padding table-responsive no-border">
<table class="table table-striped table-bordered table-hover dataTable" style="border: 1px solid #ddd;">
<thead class="thin-border-bottom">
<tr>
<th>
<i class="ace-icon fa fa-clock-o bigger-110"></i>
Backup Date
</th>
<th>
<i class="ace-icon fa fa-cogs bigger-110"></i>
Settings
</th>
</tr>
</thead>
<tbody>
{{#each model as |it|}}
<tr>
<td>
{{date-cell value=it}}
</td>
<td>
<div class="btn-group">
<button data-rel="tooltip" class="btn btn-xs btn-info" title="Restore" {{action (route-action 'restore' it)}}>
<i class="ace-icon fa fa-reply-all bigger-120"></i>
</button>
</div>
</td>
</tr>
{{/each}}
</tbody>
</table>
</div>
</div>
</div>
{{/main-content}}

View File

@ -18,7 +18,8 @@
"jquery-colorbox": "^1.6.4",
"fuelux": "^3.15.4",
"jquery.hotkeys": "*",
"bootstrap-wysiwyg": "*"
"bootstrap-wysiwyg": "*",
"editor.md": "^1.5.0"
},
"resolutions": {
"ember": "release"

24
web/package-lock.json generated
View File

@ -3909,7 +3909,7 @@
"exists-sync": "0.0.3",
"lodash.defaults": "3.1.2",
"moment": "2.18.1",
"moment-timezone": "0.5.13"
"moment-timezone": "0.5.14"
},
"dependencies": {
"babel-core": {
@ -3979,9 +3979,9 @@
"dev": true
},
"broccoli-babel-transpiler": {
"version": "5.7.2",
"resolved": "http://registry.npm.taobao.org/broccoli-babel-transpiler/download/broccoli-babel-transpiler-5.7.2.tgz",
"integrity": "sha1-dWwwVEd1FE6YQzO3EV9CyRa6COA=",
"version": "5.7.4",
"resolved": "http://registry.npm.taobao.org/broccoli-babel-transpiler/download/broccoli-babel-transpiler-5.7.4.tgz",
"integrity": "sha1-KwYRzp5dmLjY0rSa4SGa8vUnZ+M=",
"dev": true,
"requires": {
"babel-core": "5.8.38",
@ -4027,7 +4027,7 @@
"integrity": "sha1-XOT0awjtb20h6Hhhn7aJcZ1ujhM=",
"dev": true,
"requires": {
"broccoli-babel-transpiler": "5.7.2",
"broccoli-babel-transpiler": "5.7.4",
"broccoli-funnel": "1.2.0",
"clone": "2.1.1",
"ember-cli-version-checker": "1.3.1",
@ -4706,9 +4706,9 @@
"dev": true
},
"broccoli-babel-transpiler": {
"version": "5.7.2",
"resolved": "http://registry.npm.taobao.org/broccoli-babel-transpiler/download/broccoli-babel-transpiler-5.7.2.tgz",
"integrity": "sha1-dWwwVEd1FE6YQzO3EV9CyRa6COA=",
"version": "5.7.4",
"resolved": "http://registry.npm.taobao.org/broccoli-babel-transpiler/download/broccoli-babel-transpiler-5.7.4.tgz",
"integrity": "sha1-KwYRzp5dmLjY0rSa4SGa8vUnZ+M=",
"dev": true,
"requires": {
"babel-core": "5.8.38",
@ -4754,7 +4754,7 @@
"integrity": "sha1-XOT0awjtb20h6Hhhn7aJcZ1ujhM=",
"dev": true,
"requires": {
"broccoli-babel-transpiler": "5.7.2",
"broccoli-babel-transpiler": "5.7.4",
"broccoli-funnel": "1.2.0",
"clone": "2.1.1",
"ember-cli-version-checker": "1.3.1",
@ -10025,9 +10025,9 @@
"dev": true
},
"moment-timezone": {
"version": "0.5.13",
"resolved": "http://registry.npm.taobao.org/moment-timezone/download/moment-timezone-0.5.13.tgz",
"integrity": "sha1-mc5cfYJyYusPH3AgRBd/YHRde5A=",
"version": "0.5.14",
"resolved": "http://registry.npm.taobao.org/moment-timezone/download/moment-timezone-0.5.14.tgz",
"integrity": "sha1-TrOP+VOLgBCLpGekWPPtQmjM/LE=",
"dev": true,
"requires": {
"moment": "2.18.1"

View File

@ -30,7 +30,7 @@
"ember-cli-inject-live-reload": "^1.4.1",
"ember-cli-jshint": "^1.0.0",
"ember-cli-less": "^1.5.5",
"ember-cli-moment-shim": "1.2.0",
"ember-cli-moment-shim": "^1.2.0",
"ember-cli-qunit": "^4.0.0",
"ember-cli-release": "0.2.8",
"ember-cli-shims": "^1.1.0",
@ -38,7 +38,7 @@
"ember-cli-uglify": "^1.2.0",
"ember-export-application-global": "^2.0.0",
"ember-load-initializers": "^1.0.0",
"ember-moment": "6.1.0",
"ember-moment": "^6.1.0",
"ember-radio-button": "1.1.1",
"ember-resolver": "^4.0.0",
"ember-route-action-helper": "2.0.3",

View File

@ -0,0 +1,11 @@
import { moduleFor, test } from 'ember-qunit';
moduleFor('route:database-backup/list', 'Unit | Route | database backup/list', {
// Specify the other units that are required for this test.
// needs: ['controller:foo']
});
test('it exists', function(assert) {
let route = this.subject();
assert.ok(route);
});