10 changed files with 751 additions and 78 deletions
-
30app/Helpers/helpers.php
-
84app/Http/Controllers/BackupsController.php
-
51app/Http/Requests/BackupUploadRequest.php
-
5composer.json
-
501composer.lock
-
3config/app.php
-
28resources/lang/id/backup.php
-
65resources/views/backups/forms.blade.php
-
54resources/views/backups/index.blade.php
-
8routes/web.php
@ -0,0 +1,84 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Controllers; |
||||
|
|
||||
|
use App\Http\Requests\BackupUploadRequest; |
||||
|
use BackupManager\Filesystems\Destination; |
||||
|
use BackupManager\Manager; |
||||
|
use Illuminate\Http\Request; |
||||
|
use League\Flysystem\FileExistsException; |
||||
|
use League\Flysystem\FileNotFoundException; |
||||
|
|
||||
|
class BackupsController extends Controller |
||||
|
{ |
||||
|
public function index(Request $request) |
||||
|
{ |
||||
|
if (!file_exists(storage_path('app/backup/db'))) { |
||||
|
$backups = []; |
||||
|
} else { |
||||
|
$backups = \File::allFiles(storage_path('app/backup/db')); |
||||
|
|
||||
|
// Sort files by modified time DESC
|
||||
|
usort($backups, function($a, $b) { |
||||
|
return -1 * strcmp($a->getMTime(), $b->getMTime()); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
return view('backups.index',compact('backups')); |
||||
|
} |
||||
|
|
||||
|
public function store(Request $request) |
||||
|
{ |
||||
|
$this->validate($request, [ |
||||
|
'file_name' => 'nullable|max:30|regex:/^[\w._-]+$/' |
||||
|
]); |
||||
|
|
||||
|
try { |
||||
|
$manager = app()->make(Manager::class); |
||||
|
$fileName = $request->get('file_name') ?: date('Y-m-d_Hi'); |
||||
|
|
||||
|
$manager->makeBackup()->run('mysql', [ |
||||
|
new Destination('local', 'backup/db/' . $fileName) |
||||
|
], 'gzip'); |
||||
|
|
||||
|
return redirect()->route('backups.index'); |
||||
|
} catch (FileExistsException $e) { |
||||
|
return redirect()->route('backups.index'); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public function destroy($fileName) |
||||
|
{ |
||||
|
if (file_exists(storage_path('app/backup/db/') . $fileName)) { |
||||
|
unlink(storage_path('app/backup/db/') . $fileName); |
||||
|
} |
||||
|
return redirect()->route('backups.index'); |
||||
|
} |
||||
|
|
||||
|
public function download($fileName) |
||||
|
{ |
||||
|
return response()->download(storage_path('app/backup/db/') . $fileName); |
||||
|
} |
||||
|
|
||||
|
public function restore($fileName) |
||||
|
{ |
||||
|
try { |
||||
|
$manager = app()->make(Manager::class); |
||||
|
$manager->makeRestore()->run('local', 'backup/db/' . $fileName, 'mysql', 'gzip'); |
||||
|
} catch (FileNotFoundException $e) {} |
||||
|
|
||||
|
return redirect()->route('backups.index'); |
||||
|
} |
||||
|
|
||||
|
public function upload(BackupUploadRequest $request) |
||||
|
{ |
||||
|
$file = $request->file('backup_file'); |
||||
|
|
||||
|
if (file_exists(storage_path('app/backup/db/') . $file->getClientOriginalName()) == false) { |
||||
|
$file->storeAs('backup/db', $file->getClientOriginalName()); |
||||
|
} |
||||
|
|
||||
|
return redirect()->route('backups.index'); |
||||
|
} |
||||
|
|
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
<?php |
||||
|
|
||||
|
namespace App\Http\Requests; |
||||
|
|
||||
|
use Illuminate\Foundation\Http\FormRequest; |
||||
|
|
||||
|
class BackupUploadRequest extends FormRequest |
||||
|
{ |
||||
|
/** |
||||
|
* Determine if the user is authorized to make this request. |
||||
|
* |
||||
|
* @return bool |
||||
|
*/ |
||||
|
public function authorize() |
||||
|
{ |
||||
|
return auth()->check(); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Get the validation rules that apply to the request. |
||||
|
* |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function rules() |
||||
|
{ |
||||
|
return [ |
||||
|
'backup_file' => 'required|sql_gz' |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
public function messages() |
||||
|
{ |
||||
|
return [ |
||||
|
'backup_file.sql_gz' => 'Invalid file type, must be <strong>.gz</strong> file', |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
protected function getValidatorInstance() |
||||
|
{ |
||||
|
$validator = parent::getValidatorInstance(); |
||||
|
|
||||
|
$validator->addImplicitExtension('sql_gz', function($attribute, $value, $parameters) { |
||||
|
if ($value) |
||||
|
return $value->getClientOriginalExtension() == 'gz'; |
||||
|
|
||||
|
return false; |
||||
|
}); |
||||
|
|
||||
|
return $validator; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,28 @@ |
|||||
|
<?php |
||||
|
|
||||
|
return [ |
||||
|
// Labels
|
||||
|
'index_title' => 'Database Backup Manager', |
||||
|
'list' => 'Backup File List', |
||||
|
|
||||
|
// Actions
|
||||
|
'create' => 'Create Backup File', |
||||
|
'file_name' => 'File Name', |
||||
|
'file_size' => 'File Size', |
||||
|
'created_at' => 'Created at', |
||||
|
'actions' => 'Actions', |
||||
|
'delete' => 'Delete', |
||||
|
'delete_title' => 'Delete this backup file', |
||||
|
'sure_to_delete_file' => 'Are you sure to delete this file <strong>":filename"</strong>?', |
||||
|
'cancel_delete' => 'Cancel Delete', |
||||
|
'confirm_delete' => 'YES, please delete this file!', |
||||
|
'empty' => 'No backup file available.', |
||||
|
'download' => 'Download', |
||||
|
'download_title' => 'Download this file', |
||||
|
'restore' => 'Restore', |
||||
|
'restore_title' => 'Restore database from file', |
||||
|
'sure_to_restore' => 'Are you sure to restore database with this backup file "<strong>:filename</strong>"? <br><br>Please make sure your <strong>current database has been backed up</strong>.', |
||||
|
'cancel_restore' => 'Cancel Restore', |
||||
|
'confirm_restore' => 'YES, Restore Database!', |
||||
|
'upload' => 'Upload Backup File', |
||||
|
]; |
||||
@ -0,0 +1,65 @@ |
|||||
|
@if (Request::get('action') == 'delete' && Request::has('file_name')) |
||||
|
<div class="panel panel-danger"> |
||||
|
<div class="panel-heading"> |
||||
|
<h3 class="panel-title">{{ trans('backup.delete') }}</h3> |
||||
|
</div> |
||||
|
<div class="panel-body"> |
||||
|
<p>{!! trans('backup.sure_to_delete_file', ['filename' => Request::get('file_name')]) !!}</p> |
||||
|
</div> |
||||
|
<div class="panel-footer"> |
||||
|
<a href="{{ route('backups.index') }}" class="btn btn-default">{{ trans('backup.cancel_delete') }}</a> |
||||
|
<form action="{{ route('backups.destroy', Request::get('file_name')) }}" method="post" class="pull-right"> |
||||
|
{{ method_field('delete') }} |
||||
|
{{ csrf_field() }} |
||||
|
<input type="hidden" name="file_name" value="{{ Request::get('file_name') }}"> |
||||
|
<input type="submit" class="btn btn-danger" value="{{ trans('backup.confirm_delete') }}"> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
@endif |
||||
|
@if (Request::get('action') == 'restore' && Request::has('file_name')) |
||||
|
<div class="panel panel-warning"> |
||||
|
<div class="panel-heading"><h3 class="panel-title">{{ trans('backup.restore') }}</h3></div> |
||||
|
<div class="panel-body"> |
||||
|
<p>{!! trans('backup.sure_to_restore', ['filename' => Request::get('file_name')]) !!}</p> |
||||
|
</div> |
||||
|
<div class="panel-footer"> |
||||
|
<a href="{{ route('backups.index') }}" class="btn btn-default">{{ trans('backup.cancel_restore') }}</a> |
||||
|
<form action="{{ route('backups.restore', Request::get('file_name')) }}" |
||||
|
method="post" |
||||
|
class="pull-right" |
||||
|
onsubmit="return confirm('Click OK to Restore.')"> |
||||
|
{{ csrf_field() }} |
||||
|
<input type="hidden" name="file_name" value="{{ Request::get('file_name') }}"> |
||||
|
<input type="submit" class="btn btn-warning" value="{{ trans('backup.confirm_restore') }}"> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
|
@endif |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-body"> |
||||
|
<form action="{{ route('backups.store') }}" method="post"> |
||||
|
{{ csrf_field() }} |
||||
|
<div class="form-group"> |
||||
|
<label for="file_name" class="control-label">{{ trans('backup.create') }}</label> |
||||
|
<input type="text" name="file_name" class="form-control" placeholder="{{ date('Y-m-d_Hi') }}"> |
||||
|
{!! $errors->first('file_name', '<div class="text-danger text-right">:message</div>') !!} |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<input type="submit" value="{{ trans('backup.create') }}" class="btn btn-success"> |
||||
|
</div> |
||||
|
</form> |
||||
|
<hr> |
||||
|
<form action="{{ route('backups.upload') }}" method="post" enctype="multipart/form-data"> |
||||
|
{{ csrf_field() }} |
||||
|
<div class="form-group"> |
||||
|
<label for="backup_file" class="control-label">{{ trans('backup.upload') }}</label> |
||||
|
<input type="file" name="backup_file" class="form-control"> |
||||
|
{!! $errors->first('backup_file', '<div class="text-danger text-right">:message</div>') !!} |
||||
|
</div> |
||||
|
<div class="form-group"> |
||||
|
<input type="submit" value="{{ trans('backup.upload') }}" class="btn btn-primary"> |
||||
|
</div> |
||||
|
</form> |
||||
|
</div> |
||||
|
</div> |
||||
@ -0,0 +1,54 @@ |
|||||
|
@extends('layouts.app') |
||||
|
|
||||
|
@section('title',trans('backup.index_title')) |
||||
|
|
||||
|
@section('content') |
||||
|
<h3 class="page-header">{{ trans('backup.index_title') }}</h3> |
||||
|
<div class="row"> |
||||
|
<div class="col-md-8"> |
||||
|
<div class="panel panel-default"> |
||||
|
<div class="panel-heading"><h3 class="panel-title">{{ trans('backup.list') }}</h3></div> |
||||
|
<table class="table table-condensed"> |
||||
|
<thead> |
||||
|
<th>#</th>
|
||||
|
<th>{{ trans('backup.file_name') }}</th> |
||||
|
<th>{{ trans('backup.file_size') }}</th> |
||||
|
<th>{{ trans('backup.created_at') }}</th> |
||||
|
<th class="text-center">{{ trans('backup.actions') }}</th> |
||||
|
</thead> |
||||
|
<tbody> |
||||
|
@forelse($backups as $key => $backup) |
||||
|
<tr> |
||||
|
<td>{{ $key + 1 }}</td> |
||||
|
<td>{{ $backup->getFilename() }}</td> |
||||
|
<td>{{ formatSizeUnits($backup->getSize()) }}</td> |
||||
|
<td>{{ date('Y-m-d H:i:s', $backup->getMTime()) }}</td> |
||||
|
<td class="text-center"> |
||||
|
<a href="{{ route('backups.index', ['action' => 'restore', 'file_name' => $backup->getFilename()]) }}" |
||||
|
id="restore_{{ str_replace('.gz', '', $backup->getFilename()) }}" |
||||
|
class="btn btn-warning btn-xs" |
||||
|
title="{{ trans('backup.download') }}">{{ trans('backup.restore') }}</a> |
||||
|
<a href="{{ route('backups.download', [$backup->getFilename()]) }}" |
||||
|
id="download_{{ str_replace('.gz', '', $backup->getFilename()) }}" |
||||
|
class="btn btn-info btn-xs" |
||||
|
title="{{ trans('backup.download') }}">{{ trans('backup.download') }}</a> |
||||
|
<a href="{{ route('backups.index', ['action' => 'delete', 'file_name' => $backup->getFilename()]) }}" |
||||
|
id="del_{{ str_replace('.gz', '', $backup->getFilename()) }}" |
||||
|
class="btn btn-danger btn-xs" |
||||
|
title="{{ trans('backup.delete') }}">{{ trans('backup.delete') }}</a> |
||||
|
</td> |
||||
|
</tr> |
||||
|
@empty |
||||
|
<tr> |
||||
|
<td colspan="3">{{ trans('backup.empty') }}</td> |
||||
|
</tr> |
||||
|
@endforelse |
||||
|
</tbody> |
||||
|
</table> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div class="col-md-4"> |
||||
|
@include('backups.forms') |
||||
|
</div> |
||||
|
</div> |
||||
|
@endsection |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue