add database backup
This commit is contained in:
parent
bc3a90d9b1
commit
cf35982fb3
BIN
database_backups/20180217030001.sql.gz
Normal file
BIN
database_backups/20180217030001.sql.gz
Normal file
Binary file not shown.
BIN
database_backups/20180218030001.sql.gz
Normal file
BIN
database_backups/20180218030001.sql.gz
Normal file
Binary file not shown.
BIN
database_backups/20180219030001.sql.gz
Normal file
BIN
database_backups/20180219030001.sql.gz
Normal file
Binary file not shown.
BIN
database_backups/20180220030001.sql.gz
Normal file
BIN
database_backups/20180220030001.sql.gz
Normal file
Binary file not shown.
BIN
database_backups/20180221030001.sql.gz
Normal file
BIN
database_backups/20180221030001.sql.gz
Normal file
Binary file not shown.
BIN
database_backups/20180222030001.sql.gz
Normal file
BIN
database_backups/20180222030001.sql.gz
Normal file
Binary file not shown.
BIN
database_backups/20180223030001.sql.gz
Normal file
BIN
database_backups/20180223030001.sql.gz
Normal file
Binary file not shown.
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -2,5 +2,6 @@ import Ember from 'ember';
|
||||
|
||||
export default Ember.Component.extend({
|
||||
classNames: ['widget-header'],
|
||||
dropdownMenu: true
|
||||
dropdownMenu: true,
|
||||
'search-box': true
|
||||
});
|
||||
|
@ -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;
|
||||
|
18
web/app/routes/database-backup/list.js
Normal file
18
web/app/routes/database-backup/list.js
Normal 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');
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
@ -15,4 +15,7 @@
|
||||
{{/if}}
|
||||
{{reload-btn clear-search=(get this 'clear-search')}}
|
||||
</div>
|
||||
{{#if search-box}}
|
||||
{{search-box}}
|
||||
{{/if}}
|
||||
|
||||
|
@ -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 -->
|
||||
|
||||
|
@ -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}}
|
||||
{{!--
|
||||
|
@ -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}}
|
||||
|
41
web/app/templates/database-backup/list.hbs
Normal file
41
web/app/templates/database-backup/list.hbs
Normal 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}}
|
@ -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
24
web/package-lock.json
generated
@ -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"
|
||||
|
@ -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",
|
||||
|
11
web/tests/unit/routes/database-backup/list-test.js
Normal file
11
web/tests/unit/routes/database-backup/list-test.js
Normal 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);
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user