diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d12e969 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.buildpath +.idea +.project +.settings +bin +composer.lock +composer.phar +vendor diff --git a/composer.json b/composer.json index aeda3f8..fe1afb4 100644 --- a/composer.json +++ b/composer.json @@ -1,41 +1,46 @@ { - "name": "xatta-trone/laravel-backup-ui", - "description": ":package_description", - "license": "MIT", - "authors": [ - { - "name": "Author Name", - "email": "author@email.com", - "homepage": "http://author.com" - } - ], - "homepage": "https://github.com/xatta-trone/laravel-backup-ui", - "keywords": ["Laravel", "LaravelBackupUi"], - "require": { - "illuminate/support": "~9" - }, - "require-dev": { - "phpunit/phpunit": "~9.0", - "orchestra/testbench": "~7" - }, - "autoload": { - "psr-4": { - "XattaTrone\\LaravelBackupUi\\": "src/" - } - }, - "autoload-dev": { - "psr-4": { - "XattaTrone\\LaravelBackupUi\\Tests\\": "tests" - } - }, - "extra": { - "laravel": { - "providers": [ - "XattaTrone\\LaravelBackupUi\\LaravelBackupUiServiceProvider" - ], - "aliases": { - "LaravelBackupUi": "XattaTrone\\LaravelBackupUi\\Facades\\LaravelBackupUi" - } - } + "name": "xatta-trone/laravel-backup-ui", + "description": "Manage your laravel-backups from one place", + "license": "MIT", + "authors": [ + { + "name": "Monzurul Islam", + "email": "monzurul.ce.buet@gmail.com", + "homepage": "https://github.com/Xatta-Trone" } + ], + "homepage": "https://github.com/xatta-trone/laravel-backup-ui", + "keywords": [ + "Laravel", + "Laravel-backup", + "Laravel-backup-ui" + ], + "require": { + "php": "^7.4|^8.0", + "illuminate/support": "^8.0" + }, + "require-dev": { + "orchestra/testbench": "^6.0", + "phpunit/phpunit": "^9.0" + }, + "autoload": { + "psr-4": { + "XattaTrone\\LaravelBackupUi\\": "src/" + } + }, + "autoload-dev": { + "psr-4": { + "XattaTrone\\LaravelBackupUi\\Tests\\": "tests" + } + }, + "extra": { + "laravel": { + "providers": [ + "XattaTrone\\LaravelBackupUi\\LaravelBackupUiServiceProvider" + ], + "aliases": { + "LaravelBackupUi": "XattaTrone\\LaravelBackupUi\\Facades\\LaravelBackupUi" + } + } + } } diff --git a/config/laravel-backup-ui.php b/config/laravel-backup-ui.php index 035fcd5..bfc497a 100644 --- a/config/laravel-backup-ui.php +++ b/config/laravel-backup-ui.php @@ -1,5 +1,48 @@ 'laravel-backups', + + /* + |-------------------------------------------------------------------------- + | Route Middleware + |-------------------------------------------------------------------------- + | + | The middleware listed here will be assigned to every route + |-------------------------------------------------------------------------- + | + */ + 'route_middleware' => [], + + + /* + |-------------------------------------------------------------------------- + | Theme + |-------------------------------------------------------------------------- + | + | The theme used in all available views. Default set to bootstrap-4. + | Available options are: bootstrap-4, bootstrap-5, tailwind and semantic-ui. + |-------------------------------------------------------------------------- + | + */ + + 'theme' => 'bootstrap-4', +]; diff --git a/license.md b/license.md index 2712710..59e5ec5 100644 --- a/license.md +++ b/license.md @@ -1,5 +1,21 @@ -# The license +The MIT License (MIT) -Copyright (c) Author Name +Copyright (c) Spatie bvba -...Add your license text here... \ No newline at end of file +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/readme.md b/readme.md index 7223d84..bff5d77 100644 --- a/readme.md +++ b/readme.md @@ -1,11 +1,22 @@ -# LaravelBackupUi +# Laravel Backup UI -[![Latest Version on Packagist][ico-version]][link-packagist] -[![Total Downloads][ico-downloads]][link-downloads] -[![Build Status][ico-travis]][link-travis] -[![StyleCI][ico-styleci]][link-styleci] +### Manage your laravel backups from one place -This is where your description should go. Take a look at [contributing.md](contributing.md) to see a to do list. +[![MIT Licensed](https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square)](LICENSE.md) +[![Total Downloads](https://img.shields.io/packagist/dt/xatta-trone/laravel-backup-ui.svg?style=flat-square)](https://packagist.org/packages/xatta-trone/laravel-backup-ui) + + +### Features + +- 📂 **View all the Laravel backups** in your application, +- 🎚 **Filter** by disk type, timestamp, pagination +- and more... + + +### Requirements + +- **PHP 8.0+** +- **Laravel 8+** ## Installation @@ -15,7 +26,32 @@ Via Composer composer require xatta-trone/laravel-backup-ui ``` -## Usage +### Usage + +Once the installation is complete, you will be able to access **Log Viewer** directly in your browser. + +By default, the application is available at: `{APP_URL}/laravel-backups`. + +(for example: `https://my-app.test/laravel-backups`) + +## Advanced usage +### Customize view +Publish `log.blade.php` into `/resources/views/vendor/laravel-log-viewer/` for view customization: + +```bash +php artisan vendor:publish \ + --provider="XattaTrone\LaravelBackupUi\LaravelBackupUiServiceProvider" \ + --tag=laravel-backup-ui.views +``` + +### Edit configuration +Publish `logviewer.php` configuration file into `/config/` for configuration customization: + +```bash +php artisan vendor:publish \ + --provider="XattaTrone\LaravelBackupUi\LaravelBackupUiServiceProvider" \ + --tag=laravel-backup-ui.config +``` ## Change log @@ -33,11 +69,11 @@ Please see [contributing.md](contributing.md) for details and a todolist. ## Security -If you discover any security related issues, please email author@email.com instead of using the issue tracker. +If you discover any security related issues, please email monzurul.ce.buet@gmail.com instead of using the issue tracker. ## Credits -- [Author Name][link-author] +- [Monzurul Islam][link-author] - [All Contributors][link-contributors] ## License @@ -52,6 +88,5 @@ MIT. Please see the [license file](license.md) for more information. [link-packagist]: https://packagist.org/packages/xatta-trone/laravel-backup-ui [link-downloads]: https://packagist.org/packages/xatta-trone/laravel-backup-ui [link-travis]: https://travis-ci.org/xatta-trone/laravel-backup-ui -[link-styleci]: https://styleci.io/repos/12345678 [link-author]: https://github.com/xatta-trone [link-contributors]: ../../contributors diff --git a/src/Http/Controllers/BackupController.php b/src/Http/Controllers/BackupController.php new file mode 100644 index 0000000..e15999e --- /dev/null +++ b/src/Http/Controllers/BackupController.php @@ -0,0 +1,68 @@ +dir = config('backup.backup.name'); + $this->disks = config('backup.backup.destination.disks', ['local']); + } + + public function index(LaravelBackupUiIndexRequest $request) + { + $disk = $request->input('disk') ?: $this->disks[0]; + + $results = LaravelBackupUiService::getBackups( + $disk, + $this->dir, + $request->input('page'), + $request->input('per_page'), + $request->input('sort'), + ); + + $paginate = new LengthAwarePaginator( + $results['items'], + $results['total_items'], + $request->input('per_page'), + LengthAwarePaginator::resolveCurrentPage(), + array('path' => LengthAwarePaginator::resolveCurrentPath()) + ); + return view( + 'xatta-trone::laravel-backups.' . config('laravel-backup-ui.theme', 'bootstrap-4'), + ['paginate' => $paginate, 'disks' => $this->disks] + ); + } + + public function download(LaravelBackupUiDownloadRequest $request) + { + try { + $subPath = $this->dir . '/' . $request->input('filename'); + return Storage::disk($request->input('disk'))->download($subPath); + } catch (Exception $e) { + return redirect()->back()->with('danger', 'Error downloading file: ' . $e->getMessage()); + } + } + + public function destroy(LaravelBackupUiDownloadRequest $request) + { + try { + $subPath = $this->dir . '/' . $request->input('filename'); + Storage::disk($request->input('disk'))->delete($subPath); + return redirect()->back()->with('success', 'File deleted successfully.'); + } catch (Exception $e) { + return redirect()->back()->with('danger', 'Error deleting file: ' . $e->getMessage()); + } + } +} diff --git a/src/Http/Controllers/Controller.php b/src/Http/Controllers/Controller.php new file mode 100644 index 0000000..a8461f5 --- /dev/null +++ b/src/Http/Controllers/Controller.php @@ -0,0 +1,13 @@ + ['required', 'string'], + 'filename' => ['required', 'string'], + ]; + } +} diff --git a/src/Http/Requests/LaravelBackupUiIndexRequest.php b/src/Http/Requests/LaravelBackupUiIndexRequest.php new file mode 100644 index 0000000..af55f43 --- /dev/null +++ b/src/Http/Requests/LaravelBackupUiIndexRequest.php @@ -0,0 +1,47 @@ + ['nullable', 'string'], + 'sort' => ['required', 'string', 'in:asc,desc'], + 'per_page' => ['required', 'integer'], + 'page' => ['required', 'integer'], + ]; + } + + /** + * Prepares the data for validation. + * + * @return void + */ + public function prepareForValidation() + { + $this->merge([ + 'sort' => $this->sort ? $this->sort : 'asc', + 'per_page' => $this->per_page ? $this->per_page : 10, + 'page' => $this->page ? $this->page : 1, + ]); + } +} diff --git a/src/LaravelBackupUiServiceProvider.php b/src/LaravelBackupUiServiceProvider.php index 9671d63..667a148 100644 --- a/src/LaravelBackupUiServiceProvider.php +++ b/src/LaravelBackupUiServiceProvider.php @@ -2,6 +2,7 @@ namespace XattaTrone\LaravelBackupUi; +use Illuminate\Support\Facades\Route; use Illuminate\Support\ServiceProvider; class LaravelBackupUiServiceProvider extends ServiceProvider @@ -14,9 +15,12 @@ class LaravelBackupUiServiceProvider extends ServiceProvider public function boot(): void { // $this->loadTranslationsFrom(__DIR__.'/../resources/lang', 'xatta-trone'); - // $this->loadViewsFrom(__DIR__.'/../resources/views', 'xatta-trone'); + $this->loadViewsFrom(__DIR__ . '/resources/views', 'xatta-trone'); // $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); - // $this->loadRoutesFrom(__DIR__.'/routes.php'); + // $this->loadRoutesFrom(__DIR__ . '/routes/web.php'); + $this->registerRoutes(); + + // Publishing is only necessary when using the CLI. if ($this->app->runningInConsole()) { @@ -62,9 +66,9 @@ protected function bootForConsole(): void ], 'laravel-backup-ui.config'); // Publishing the views. - /*$this->publishes([ - __DIR__.'/../resources/views' => base_path('resources/views/vendor/xatta-trone'), - ], 'laravel-backup-ui.views');*/ + $this->publishes([ + __DIR__ . '/resources/views/laravel-backups' => base_path('resources/views/vendor/xatta-trone/laravel-backups'), + ], 'laravel-backup-ui.views'); // Publishing assets. /*$this->publishes([ @@ -79,4 +83,30 @@ protected function bootForConsole(): void // Registering package commands. // $this->commands([]); } + + /** + * Register routes with configurations + * + * @return void + */ + protected function registerRoutes() + { + Route::group($this->routeConfiguration(), function () { + $this->loadRoutesFrom(__DIR__ . '/routes/web.php'); + }); + } + + /** + * Route configurations + * + * @return array + */ + protected function routeConfiguration() + { + return [ + 'prefix' => config('laravel-backup-ui.route_prefix', 'laravel-backups'), + 'middleware' => config('laravel-backup-ui.route_middleware', []), + ]; + } + } diff --git a/src/Services/LaravelBackupUiService.php b/src/Services/LaravelBackupUiService.php new file mode 100644 index 0000000..0234c53 --- /dev/null +++ b/src/Services/LaravelBackupUiService.php @@ -0,0 +1,64 @@ +files($dir); + $filesPrefix = config('backup.backup.destination.filename_prefix', ""); + $pattern = '/^' . preg_quote($filesPrefix, '/') . '\d{4}-\d{2}-\d{2}-\d{2}-\d{2}-\d{2}\.zip$/'; + $filteredFiles = []; + + foreach ($files as $file) { + $fileMeta = Storage::disk($disk)->getMetadata($file); + $fileName = array_key_exists('name', $fileMeta) ? $fileMeta['name'] : $fileMeta['path']; + $fileSize = array_key_exists('size', $fileMeta) ? $fileMeta['size'] : null; + $fileDate = array_key_exists('timestamp', $fileMeta) ? $fileMeta['timestamp'] : null; + + if (preg_match($pattern, basename($fileName))) { + $filteredFiles[] = [ + 'file_id' => $file, + 'name' => $fileName, + 'size' => $fileSize ? number_format($fileSize / (1024 * 1024), 2) . "MB" : null, + 'last_modified' => $fileDate, + 'disk' => $disk, + 'download_url' => URL::signedRoute('laravel-backups.download', ['filename' => $file, 'disk' => $disk]), + 'delete_url' => URL::signedRoute('laravel-backups.destroy', ['filename' => $file, 'disk' => $disk]), + ]; + } + } + + + // Sort files by date (ascending order) + $sortOrder = isset($_GET['sort']) && $_GET['sort'] === 'desc' ? -1 : 1; + usort($filteredFiles, function ($a, $b) use ($sortOrder, $disk) { + return $sortOrder * ($a['last_modified'] - $b['last_modified']); + }); + + // Pagination settings + $totalFiles = count($filteredFiles); + $totalPages = ceil($totalFiles / $limit); + + // Calculate the offset + $offset = ($page - 1) * $limit; + + // Get the files for the current page + $slicedFilesResult = array_slice($filteredFiles, $offset, $limit); + + return [ + 'items' => $slicedFilesResult, + 'total_items' => $totalFiles, + 'total_pages' => $totalPages, + 'current_page' => $page, + 'limit' => $limit, + 'sort' => $sort, + 'disk' => $disk, + ]; + } +} diff --git a/src/resources/views/laravel-backups/bootstrap-4.blade.php b/src/resources/views/laravel-backups/bootstrap-4.blade.php new file mode 100644 index 0000000..7b7d138 --- /dev/null +++ b/src/resources/views/laravel-backups/bootstrap-4.blade.php @@ -0,0 +1,191 @@ + + + + + + + + + + + Laravel Backups UI + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {{-- Alerts start --}} + @foreach (['success', 'danger', 'info'] as $alert) + @if (Session::has($alert)) + + @endif + @endforeach + {{-- Alerts end --}} +
+
+ + {{-- Show total backups --}} +

Total Backups Count: {{ $paginate->total() }}

+ {{-- Show total backups end --}} + + {{-- Table Start --}} +
+ + + + + + + + + + + + @forelse ($paginate->items() as $index=>$file) + + + + + + + + @empty + + + + @endforelse + +
#File NameSizeLast ModifiedAction
+ {{ $index + 1 + (request()->get('page', 1) - 1) * request()->get('per_page', 10) }} + {{ $file['name'] }}{{ $file['size'] }}{{ \Carbon\Carbon::parse($file['last_modified'])->format('Y-m-d H:i:s') }} + Download +
+ {{ csrf_field() }} + {{ method_field('DELETE') }} + +
+
No files found
+ {{ $paginate->withQueryString()->links('pagination::bootstrap-4') }} +
+ {{-- Table End --}} + + +
+ + + + + + + + + + + + diff --git a/src/resources/views/laravel-backups/bootstrap-5.blade.php b/src/resources/views/laravel-backups/bootstrap-5.blade.php new file mode 100644 index 0000000..fc4faa0 --- /dev/null +++ b/src/resources/views/laravel-backups/bootstrap-5.blade.php @@ -0,0 +1,191 @@ + + + + + + + + + + + Laravel Backups UI + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ {{-- Alerts start --}} + @foreach (['success', 'danger', 'info'] as $alert) + @if (Session::has($alert)) + + @endif + @endforeach + {{-- Alerts end --}} +
+
+ + {{-- Show total backups --}} +

Total Backups Count: {{ $paginate->total() }}

+ {{-- Show total backups end --}} + + {{-- Table Start --}} +
+ + + + + + + + + + + + @forelse ($paginate->items() as $index=>$file) + + + + + + + + @empty + + + + @endforelse + +
#File NameSizeLast ModifiedAction
+ {{ $index + 1 + (request()->get('page', 1) - 1) * request()->get('per_page', 10) }} + {{ $file['name'] }}{{ $file['size'] }}{{ \Carbon\Carbon::parse($file['last_modified'])->format('Y-m-d H:i:s') }} + Download +
+ {{ csrf_field() }} + {{ method_field('DELETE') }} + +
+
No files found
+ {{ $paginate->withQueryString()->links() }} +
+ {{-- Table End --}} + + +
+ + + + + + + + + + + diff --git a/src/resources/views/laravel-backups/semantic-ui.blade.php b/src/resources/views/laravel-backups/semantic-ui.blade.php new file mode 100644 index 0000000..09c2dc0 --- /dev/null +++ b/src/resources/views/laravel-backups/semantic-ui.blade.php @@ -0,0 +1,172 @@ + + + + + + + + + + + Laravel Backups UI + + + + + + +
+
+
+
+ + +
+
+ + +
+
+ + +
+
+
+ + + + {{-- Alerts start --}} + @foreach (['success', 'danger', 'info'] as $alert) + @if (Session::has($alert)) + + @endif + @endforeach + {{-- Alerts end --}} + + {{-- Show total backups --}} +

Total Backups Count: {{ $paginate->total() }}

+ {{-- Show total backups end --}} + + {{-- Table Start --}} + + + + + + + + + + + + @forelse ($paginate->items() as $index=>$file) + + + + + + + + @empty + + + + @endforelse + +
#File NameSizeLast ModifiedAction
+ {{ $index + 1 + (request()->get('page', 1) - 1) * request()->get('per_page', 10) }} + {{ $file['name'] }}{{ $file['size'] }}{{ \Carbon\Carbon::parse($file['last_modified'])->format('Y-m-d H:i:s') }} + Download +
+ {{ csrf_field() }} + {{ method_field('DELETE') }} + +
+
No files found
+ + {{ $paginate->withQueryString()->links('pagination::semantic-ui') }} + {{-- Table End --}} +
+ + + + + + + + + + diff --git a/src/resources/views/laravel-backups/tailwind.blade.php b/src/resources/views/laravel-backups/tailwind.blade.php new file mode 100644 index 0000000..19ee57c --- /dev/null +++ b/src/resources/views/laravel-backups/tailwind.blade.php @@ -0,0 +1,185 @@ + + + + + + + + + + + Laravel Backups UI + + + + + + +
+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+ {{-- Alerts start --}} + @foreach (['success', 'danger', 'info'] as $alert) + @if (Session::has($alert)) + + @endif + @endforeach + {{-- Alerts end --}} +
+ + {{-- Show total backups --}} +

Total Backups Count: {{ $paginate->total() }}

+ {{-- Show total backups end --}} + + {{-- Table Start --}} +
+ + + + + + + + + + + + @forelse ($paginate->items() as $index=>$file) + + + + + + + + @empty + + + + @endforelse + +
#File NameSizeLast ModifiedAction
+ {{ $index + 1 + (request()->get('page', 1) - 1) * request()->get('per_page', 10) }} + {{ $file['name'] }}{{ $file['size'] }} + {{ \Carbon\Carbon::parse($file['last_modified'])->format('Y-m-d H:i:s') }} + Download +
+ {{ csrf_field() }} + {{ method_field('DELETE') }} + +
+
No files found
+
+ {{ $paginate->withQueryString()->links('pagination::tailwind') }} +
+
+ {{-- Table End --}} +
+ + + + + + + + diff --git a/src/routes/web.php b/src/routes/web.php new file mode 100644 index 0000000..53e26ca --- /dev/null +++ b/src/routes/web.php @@ -0,0 +1,10 @@ +name('laravel-backups.index'); +Route::group(['middleware' => ['signed']], function () { + Route::get('/download', [BackupController::class, 'download'])->name('laravel-backups.download'); + Route::delete('/destroy', [BackupController::class, 'destroy'])->name('laravel-backups.destroy'); +});