diff --git a/.gitignore b/.gitignore index 38cdbd7..894b792 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,7 @@ /vendor +/node_modules composer.phar composer.lock +package-lock.json .DS_Store .idea \ No newline at end of file diff --git a/README.md b/README.md index f9bb175..ee1d54c 100644 --- a/README.md +++ b/README.md @@ -155,6 +155,30 @@ Link to create new webhook should be [https://bitbucket.org/:USERNAME:/:REPO:/ad It looks like this ![BitBucket Webhook Create](https://i.imgur.com/5Zw4Obl.png), Title can be anything, URL should be same as in GitHub settings, you must check Active and you can check Skip certificate verification for now (until next versions) +## Front-end requirements and installation + +Front part of this package works as Vue.js SPA and it heavily depends on npm packages + +```` +npm install bootstrap-vue vue-resource vue-router vue-toasted vue-awesome lang.js lodash change-case datejs +``` +Then you have to export assets from this package, this will add some JavaScript and SASS files inside your resources/assets/vendor/laravel-deploy directory + +``` + +php artisan vendor:publish --provider=KgBot\\LaravelDeploy\\LaravelDeployServiceProvider --tag=assets +``` + +After this you have to alter webpack.mix.js and add this at the end of file +``` +/** + * Laravel deploy assets + */ +mix.js( 'resources/assets/vendor/laravel-deploy/js/laravel-deploy.js', 'public/assets/js' ) + .sass( 'resources/assets/vendor/laravel-deploy/sass/laravel-deploy.scss', 'public/assets/css' ); +``` +You can now go to `http://localhost/laravel-deploy/dashboard` or any other URL if you have changed route prefix, just add `/dashboard` at the end. + ## Proposals, comments, feedback Everything of this is highly welcome and appreciated diff --git a/composer.json b/composer.json index e030627..c58c0f8 100644 --- a/composer.json +++ b/composer.json @@ -3,6 +3,8 @@ "description": "Laravel package used to automatically deploy code from GIT version controls", "require": { "php": ">=7.1", + "ddtraceweb/monolog-parser": "^1.2", + "kg-bot/laravel-localization-to-vue": "1.*", "laravel/framework": "5.*" }, "keywords": [ diff --git a/package.json b/package.json new file mode 100755 index 0000000..52b8f4f --- /dev/null +++ b/package.json @@ -0,0 +1,37 @@ +{ + "private": true, + "devDependencies": { + "axios": "^0.17", + "bootstrap": "^4.0.0", + "cross-env": "^5.1", + "css-loader": "^0.28.10", + "jquery": "^3.2", + "laravel-localization-loader": "^1.0.5", + "laravel-mix": "^2.0", + "lodash": "^4.17.4", + "popper.js": "^1.12", + "style-loader": "^0.20.2", + "vue": "^2.5.7" + }, + "dependencies": { + "ajv": "^6.5.0", + "bootstrap-vue": "^2.0.0-rc.1", + "change-case": "^3.0.2", + "datejs": "^1.0.0-rc3", + "fs": "0.0.1-security", + "glob": "^7.1.2", + "lang.js": "^1.1.12", + "path": "^0.12.7", + "require-dir": "^1.0.0", + "require.all": "^2.0.4", + "vee-validate": "^2.0.5", + "vue-awesome": "^3.0.2", + "vue-multiselect": "^2.1.0", + "vue-resource": "^1.5.0", + "vue-router": "^3.0.1", + "vue-spinner": "^1.0.3", + "vue-toasted": "^1.1.24", + "vue2-dropzone": "^3.1.0", + "vuejs-datepicker": "^0.9.29" + } +} diff --git a/routes.php b/routes.php index c336b29..7af3d2b 100644 --- a/routes.php +++ b/routes.php @@ -17,3 +17,61 @@ Route::any( 'deploy', 'DeployController@request' )->name( 'laravel-deploy.deploy.request' ); } ); + +/** + * Front-end routes + */ +Route::group( [ + + 'prefix' => config( 'laravel-deploy.routes.prefix' ) . '/dashboard', + 'middleware' => array_merge( [ 'web', 'auth' ], config( 'laravel-deploy.front.routes.middleware' ) ), + 'namespace' => config( 'laravel-deploy.front.routes.namespace' ), +], function () { + + Route::get( '', 'DashboardController@index' )->name( 'laravel-deploy.dashboard' ); +} ); + +/** + * Ajax routes + */ +Route::group( [ + + 'prefix' => config( 'laravel-deploy.routes.prefix' ) . '/ajax', + 'middleware' => array_merge( [ 'web' ], config( 'laravel-deploy.front.routes.ajax.middleware' ) ), + 'namespace' => config( 'laravel-deploy.front.routes.ajax.namespace' ), +], function () { + + Route::post( '/clients/{client}/status', 'ClientsController@changeStatus' ) + ->name( 'laravel-deploy.ajax.clients.status' ); + + Route::post( '/clients/{client}/auto-deploy', 'ClientsController@changeAutoDeploy' ) + ->name( 'laravel-deploy.ajax.clients.auto_deploy' ); + + Route::resource( '/clients', 'ClientsController', [ + + 'only' => [ 'index', 'store', 'update', 'destroy' ], + 'names' => [ + + 'index' => 'laravel-deploy.ajax.clients.index', + 'store' => 'laravel-deploy.ajax.clients.store', + 'update' => 'laravel-deploy.ajax.clients.update', + 'destroy' => 'laravel-deploy.ajax.clients.destroy', + ], + ] ); + + /** + * Settings routes + */ + Route::group( [ 'prefix' => 'settings' ], function () { + + Route::get( 'last-log', 'SettingsController@lastLog' )->name( 'laravel-deploy.ajax.settings.last_log' ); + Route::get( 'logs', 'SettingsController@allLogs' )->name( 'laravel-deploy.ajax.settings.logs' ); + Route::get( 'index', 'SettingsController@index' )->name( 'laravel-deploy.ajax.settings.index' ); + + /** + * Deployments routes + */ + Route::post( 'deploy/now/{client}', 'SettingsController@deployNow' ) + ->name( 'laravel-deploy.ajax.settings.deployments.deploy_now' ); + } ); +} ); diff --git a/src/Console/Commands/NewClient.php b/src/Console/Commands/NewClient.php index 8536897..0f85c70 100644 --- a/src/Console/Commands/NewClient.php +++ b/src/Console/Commands/NewClient.php @@ -10,7 +10,7 @@ use Illuminate\Console\Command; -use KgBot\LaravelDeploy\Models\DeploySource; +use KgBot\LaravelDeploy\Models\Client; class NewClient extends Command { @@ -47,11 +47,11 @@ public function handle() { $name = $this->argument( 'name' ); - $token = bcrypt( $this->argument( 'token' ) ); + $token = $this->argument( 'token' ); $script_source = $this->argument( 'script_source' ); $source = $this->argument( 'source' ); - $client = DeploySource::create( compact( 'name', 'token', 'script_source', 'source' ) ); + $client = Client::create( compact( 'name', 'token', 'script_source', 'source' ) ); $this->info( 'New client created, token has been encrypted and it is: ' . $token ); } diff --git a/src/Events/LaravelDeployFailed.php b/src/Events/LaravelDeployFailed.php new file mode 100644 index 0000000..faa90e2 --- /dev/null +++ b/src/Events/LaravelDeployFailed.php @@ -0,0 +1,45 @@ +client = $client; + $this->message = $message; + } + + /** + * Get the channels the event should broadcast on. + * + * @return \Illuminate\Broadcasting\Channel|array + */ + public function broadcastOn() + { + return new PrivateChannel( config( 'laravel-deploy.events.channel', 'channel-name' ) ); + } +} \ No newline at end of file diff --git a/src/Events/LaravelDeployFinished.php b/src/Events/LaravelDeployFinished.php index f013813..afcc791 100644 --- a/src/Events/LaravelDeployFinished.php +++ b/src/Events/LaravelDeployFinished.php @@ -13,22 +13,24 @@ use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -use KgBot\LaravelDeploy\Models\DeploySource; +use KgBot\LaravelDeploy\Models\Client; class LaravelDeployFinished { use Dispatchable, InteractsWithSockets, SerializesModels; public $client; + public $message; /** * Create a new event instance. * * @return void */ - public function __construct( DeploySource $client ) + public function __construct( Client $client, $message ) { - $this->client = $client; + $this->client = $client; + $this->message = $message; } /** diff --git a/src/Events/LaravelDeployStarted.php b/src/Events/LaravelDeployStarted.php index a69c8fc..5198ae7 100644 --- a/src/Events/LaravelDeployStarted.php +++ b/src/Events/LaravelDeployStarted.php @@ -13,22 +13,24 @@ use Illuminate\Broadcasting\PrivateChannel; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; -use KgBot\LaravelDeploy\Models\DeploySource; +use KgBot\LaravelDeploy\Models\Client; class LaravelDeployStarted { use Dispatchable, InteractsWithSockets, SerializesModels; public $client; + public $command; /** * Create a new event instance. * * @return void */ - public function __construct( DeploySource $client ) + public function __construct( Client $client, string $command ) { - $this->client = $client; + $this->client = $client; + $this->command = $command; } /** diff --git a/src/Http/Controllers/DeployController.php b/src/Http/Controllers/DeployController.php index 10f32bf..3625b7c 100644 --- a/src/Http/Controllers/DeployController.php +++ b/src/Http/Controllers/DeployController.php @@ -11,32 +11,30 @@ use Illuminate\Http\Request; use KgBot\LaravelDeploy\Exceptions\UnableToReadScriptFile; use KgBot\LaravelDeploy\Jobs\DeployJob; -use KgBot\LaravelDeploy\Models\DeploySource; +use KgBot\LaravelDeploy\Models\Client; class DeployController extends BaseController { public function request( Request $request ) { - if ( config( 'laravel-deploy.run_deploy' ) ) { + $client = Client::where( [ - $client = DeploySource::where( [ + [ 'token', $request->get( '_token' ) ], + [ 'active', true ], + [ 'auto_deploy' => true ], + ] )->first(); - [ 'token', $request->get( '_token' ) ], - [ 'active', true ], - ] )->first(); + $filename = $client->script_source; + $script_file = base_path( $filename ); - $filename = $client->script_source; - $script_file = base_path( $filename ); + if ( !file_exists( $script_file ) ) { - if ( !file_exists( $script_file ) ) { + throw new UnableToReadScriptFile(); - throw new UnableToReadScriptFile(); - - } - - dispatch( new DeployJob( $client, $script_file ) )->onQueue( config( 'laravel-deploy.queue', 'default' ) ); } + dispatch( new DeployJob( $client, $script_file ) )->onQueue( config( 'laravel-deploy.queue', 'default' ) ); + return response()->json( 'success' ); } } \ No newline at end of file diff --git a/src/Http/Controllers/Front/Ajax/ClientsController.php b/src/Http/Controllers/Front/Ajax/ClientsController.php new file mode 100644 index 0000000..ee645a2 --- /dev/null +++ b/src/Http/Controllers/Front/Ajax/ClientsController.php @@ -0,0 +1,79 @@ +json( compact( 'clients' ) ); + } + + /** + * Store a newly created resource in storage. + * + * @param \Illuminate\Http\Request $request + * + * @return \Illuminate\Http\Response + */ + public function store( ClientRequest $request ) + { + $client = Client::create( $request->all() ); + + return response()->json( compact( 'client' ) ); + } + + /** + * Update the specified resource in storage. + * + * @param \Illuminate\Http\Request $request + * @param \App\KgBot\LaravelDeploy\Models\DeploySource $deploySource + * + * @return \Illuminate\Http\Response + */ + public function update( ClientRequest $request, Client $client ) + { + $client->update( $request->all() ); + + return response()->json( compact( 'client' ) ); + } + + /** + * Remove the specified resource from storage. + * + * @param \App\KgBot\LaravelDeploy\Models\DeploySource $deploySource + * + * @return \Illuminate\Http\Response + */ + public function destroy( Client $client ) + { + $client->delete(); + + return response()->json( 'success' ); + } + + public function changeStatus( Client $client ) + { + $client->changeStatus(); + + return response()->json( 'success' ); + } + + public function changeAutoDeploy( Client $client ) + { + $client->changeAutoDeploy(); + + return response()->json( 'success' ); + } +} diff --git a/src/Http/Controllers/Front/Ajax/SettingsController.php b/src/Http/Controllers/Front/Ajax/SettingsController.php new file mode 100644 index 0000000..46c7269 --- /dev/null +++ b/src/Http/Controllers/Front/Ajax/SettingsController.php @@ -0,0 +1,97 @@ +.*)\] (?P[\w-\s]+).(?P\w+): (?P[^\[\{]+) (?P[\[\{].*[\]\}]) (?P[\[\{].*[\]\}])/'; + $reader->getParser()->registerPattern( 'newPatternName', $pattern ); + $reader->setPattern( 'newPatternName' ); + + } else { + + return response()->json( 'Log file path does not exist, check your configuration and try again.', 404 ); + } + + if ( count( $reader ) ) { + + return response()->json( [ 'log' => $reader[ count( $reader ) - 2 ] ] ); + + } else { + + return response()->json( 'Log is empty, deploy something!!!', 404 ); + } + } + + /** + * Return collection of all logs + * + * @return \Illuminate\Http\JsonResponse + */ + public function allLogs() + { + $log_file_name = config( 'laravel-deploy.log_file_name', 'laravel-log' ); + $path = storage_path( 'logs/' . $log_file_name ); + + if ( file_exists( $path ) ) { + + $reader = new LogReader( $path ); + $pattern = + '/\[(?P.*)\] (?P[\w-\s]+).(?P\w+): (?P[^\[\{]+) (?P[\[\{].*[\]\}]) (?P[\[\{].*[\]\}])/'; + $reader->getParser()->registerPattern( 'newPatternName', $pattern ); + $reader->setPattern( 'newPatternName' ); + + } else { + + return response()->json( 'Log file path does not exist, check your configuration and try again.', 404 ); + } + + if ( count( $reader ) ) { + + return response()->json( [ 'logs' => collect( $reader ) ] ); + + } else { + + return response()->json( 'Log is empty, deploy something!!!', 404 ); + } + } + + public function deployNow( Client $client ) + { + + dispatch( new DeployJob( $client, base_path( $client->script_source ) ) ); + + return response()->json( 'success' ); + } + + public function index() + { + $clients = Client::Active()->get(); + $settings = [ + + 'quick_deploy' => config( 'laravel-deploy.run_deploy' ), + ]; + + return response()->json( compact( 'clients', 'settings' ) ); + } +} diff --git a/src/Http/Controllers/Front/DashboardController.php b/src/Http/Controllers/Front/DashboardController.php new file mode 100644 index 0000000..cdf71d8 --- /dev/null +++ b/src/Http/Controllers/Front/DashboardController.php @@ -0,0 +1,20 @@ + 'required|string', + 'source' => 'required|string', + 'script_source' => 'required|string', + 'token' => 'required|string', + 'auto_deploy' => 'required|boolean', + ]; + } +} diff --git a/src/Jobs/DeployJob.php b/src/Jobs/DeployJob.php index 8dd89b4..81b8dd1 100644 --- a/src/Jobs/DeployJob.php +++ b/src/Jobs/DeployJob.php @@ -7,9 +7,12 @@ use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; +use KgBot\LaravelDeploy\Events\LaravelDeployFailed; use KgBot\LaravelDeploy\Events\LaravelDeployFinished; use KgBot\LaravelDeploy\Events\LaravelDeployStarted; -use KgBot\LaravelDeploy\Models\DeploySource; +use KgBot\LaravelDeploy\Models\Client; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; use Symfony\Component\Process\Process; class DeployJob implements ShouldQueue @@ -22,12 +25,13 @@ class DeployJob implements ShouldQueue public $client; public $script_file; + /** * Create a new job instance. * * @return void */ - public function __construct( DeploySource $client, string $script_file ) + public function __construct( Client $client, string $script_file ) { $this->client = $client; $this->script_file = $script_file; @@ -40,22 +44,33 @@ public function __construct( DeploySource $client, string $script_file ) */ public function handle() { + $logger = new Logger( 'laravel-deploy-logger' ); + + $log_file_name = config( 'laravel-deploy.log_file_name', 'laravel-log' ); + $logger->pushHandler( new StreamHandler( storage_path( '/logs/' . $log_file_name ), Logger::DEBUG ) ); + $command = 'echo ' . config( 'laravel-deploy.user.password' ); $command .= ' | sudo -S -u ' . config( 'laravel-deploy.user.username' ); $command .= ' sh ' . $this->script_file; $process = new Process( $command ); - event( new LaravelDeployStarted( $this->client ) ); + event( new LaravelDeployStarted( $this->client, $command ) ); $process->run(); try { - \Log::info( $process->getOutput() ); + $logger->info( $process->getOutput(), [ + 'client' => json_encode( $this->client ), + 'script' => $this->script_file, + ] ); + event( new LaravelDeployFinished( $this->client, $process->getOutput() ) ); } catch ( \Exception $exception ) { - \Log::critical( 'Can\'t get deploy process output.' ); + $logger->critical( $process->getOutput(), [ + 'client' => json_encode( $this->client ), + 'script' => $this->script_file, + ] ); + event( new LaravelDeployFailed( $this->client, $exception->getMessage() ) ); } - - event( new LaravelDeployFinished( $this->client ) ); } } diff --git a/src/LaravelDeployServiceProvider.php b/src/LaravelDeployServiceProvider.php index f738d9f..d6bd9c9 100644 --- a/src/LaravelDeployServiceProvider.php +++ b/src/LaravelDeployServiceProvider.php @@ -9,6 +9,8 @@ namespace KgBot\LaravelDeploy; +use ExportLocalization; +use Illuminate\Support\Facades\View; use Illuminate\Support\ServiceProvider; use KgBot\LaravelDeploy\Console\Commands\NewClient; @@ -16,27 +18,73 @@ class LaravelDeployServiceProvider extends ServiceProvider { public function register() { - $this->mergeConfigFrom( - __DIR__ . '//config/laravel-deploy.php', 'laravel-deploy' - ); + } public function boot() { + View::composer( 'laravel-deploy::dashboard', function ( $view ) { + + return $view->with( [ + 'user' => auth()->user(), + 'messages' => ExportLocalization::export()->toFlat(), + ] ); + } ); + if ( $this->app->runningInConsole() ) { $this->commands( [ NewClient::class, ] ); } + /** + * Config + */ + $this->mergeConfigFrom( + __DIR__ . '//config/laravel-deploy.php', 'laravel-deploy' + ); + $this->publishes( [ __DIR__ . '/config/laravel-deploy.php' => config_path( 'laravel-deploy.php' ), ], 'config' ); + /** + * Migrations + */ $this->publishes( [ __DIR__ . '/database/migrations/' => database_path( 'migrations' ), ], 'migrations' ); + /** + * Routes + */ $this->loadRoutesFrom( __DIR__ . '/../routes.php' ); + + /** + * Assets + */ + $this->publishes( [ + + __DIR__ . '/resources/assets/' => resource_path( 'assets/vendor/laravel-deploy' ), + ], 'assets' ); + + /** + * Localization + */ + $this->loadTranslationsFrom( __DIR__ . '/resource/lang', 'laravel-deploy' ); + $this->publishes( [ + + __DIR__ . '/resources/lang' => resource_path( 'lang/vendor/laravel-deploy' ), + ], 'lang' ); + + /** + * Views + */ + $this->loadViewsFrom( __DIR__ . '/resources/views', 'laravel-deploy' ); + + $this->publishes( [ + + __DIR__ . '/resources/views' => resource_path( 'views/vendor/laravel-deploy' ), + ], 'views' ); } } \ No newline at end of file diff --git a/src/Models/Client.php b/src/Models/Client.php new file mode 100644 index 0000000..ee2f370 --- /dev/null +++ b/src/Models/Client.php @@ -0,0 +1,59 @@ + 'boolean', + 'auto_deploy' => 'boolean', + ]; + + public function setTokenAttribute( $value ) + { + $this->attributes[ 'token' ] = bcrypt( $value ); + } + + public function changeStatus() + { + $this->update( [ + + 'active' => !$this->active, + ] ); + } + + public function changeAutoDeploy() + { + $this->update( [ + + 'auto_deploy' => !$this->auto_deploy, + ] ); + } + + public function scopeActive() + { + return $this->where( 'active', true ); + } +} \ No newline at end of file diff --git a/src/Models/DeploySource.php b/src/Models/DeploySource.php deleted file mode 100644 index 5a8e765..0000000 --- a/src/Models/DeploySource.php +++ /dev/null @@ -1,24 +0,0 @@ - [ + 'routes' => [ /** * Route prefix, example of route http://localhost/laravel-deploy/deploy?_token=################# @@ -26,7 +26,7 @@ 'middleware' => ( env( 'LARAVEL_DEPLOY_MIDDLEWARE' ) ) ? explode( ',', env( 'LARAVEL_DEPLOY_MIDDLEWARE' ) ) : [], ], - 'events' => [ + 'events' => [ /** * This package emits some events before and after it run's deployment script @@ -39,21 +39,47 @@ /** * This packages is doing all of it's work in a Job and here you change queue on which it will execute jobs */ - 'queue' => env( 'LARAVEL_DEPLOY_QUEUE', 'default' ), - - /** - * With this directive you can enable/disable this package - */ - 'run_deploy' => env( 'LARAVEL_DEPLOY_RUN', true ), + 'queue' => env( 'LARAVEL_DEPLOY_QUEUE', 'default' ), /** * Detailed description is provided inside a README.md file * * Here you set your default server user and password which will be used to run deploy script */ - 'user' => [ + 'user' => [ 'username' => env( 'LARAVEL_DEPLOY_USERNAME', 'www-data' ), 'password' => env( 'LARAVEL_DEPLOY_PASSWORD', '' ), ], + + /** + * Name of the log file where deployment logs will be saved + */ + 'log_file_name' => env( 'LARAVEL_DEPLOY_LOG_FILE_NAME', 'laravel-deploy' ), + + /** + * Everything related to front-end part of package should go here + */ + 'front' => [ + + 'title' => 'Laravel Deploy - Dashboard', + + 'routes' => [ + + 'middleware' => ( env( 'LARAVEL_DEPLOY_FRONT_MIDDLEWARE' ) ) ? + explode( ',', env( 'LARAVEL_DEPLOY_FRONT_MIDDLEWARE' ) ) : + [], + + 'namespace' => 'KgBot\LaravelDeploy\Http\Controllers\Front', + + 'ajax' => [ + + 'middleware' => ( env( 'LARAVEL_DEPLOY_FRONT_AJAX_MIDDLEWARE' ) ) ? + explode( ',', env( 'LARAVEL_DEPLOY_FRONT_AJAX_MIDDLEWARE' ) ) : + [], + + 'namespace' => 'KgBot\LaravelDeploy\Http\Controllers\Front\Ajax', + ], + ], + ], ]; \ No newline at end of file diff --git a/src/database/migrations/2018_06_10_113144_rename_deploy_sources_table.php b/src/database/migrations/2018_06_10_113144_rename_deploy_sources_table.php new file mode 100644 index 0000000..0fca87f --- /dev/null +++ b/src/database/migrations/2018_06_10_113144_rename_deploy_sources_table.php @@ -0,0 +1,34 @@ +rename( 'clients' ); + } ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( 'clients', function ( Blueprint $table ) { + + $table->rename( 'deploy_sources' ); + } ); + } +} diff --git a/src/database/migrations/2018_06_10_231128_table_clients_rename_to_ldp_clients.php b/src/database/migrations/2018_06_10_231128_table_clients_rename_to_ldp_clients.php new file mode 100644 index 0000000..87e7de9 --- /dev/null +++ b/src/database/migrations/2018_06_10_231128_table_clients_rename_to_ldp_clients.php @@ -0,0 +1,34 @@ +rename( 'ldp_clients' ); + } ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( 'ldp_clients', function ( Blueprint $table ) { + + $table->rename( 'clients' ); + } ); + } +} diff --git a/src/database/migrations/2018_06_10_231443_table_ldp_clients_add_auto_deploy.php b/src/database/migrations/2018_06_10_231443_table_ldp_clients_add_auto_deploy.php new file mode 100644 index 0000000..105862a --- /dev/null +++ b/src/database/migrations/2018_06_10_231443_table_ldp_clients_add_auto_deploy.php @@ -0,0 +1,34 @@ +boolean( 'auto_deploy' )->default( true ); + } ); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table( 'ldp_clients', function ( Blueprint $table ) { + + $table->dropColumn( 'auto_deploy' ); + } ); + } +} diff --git a/src/resources/assets/js/Gate.js b/src/resources/assets/js/Gate.js new file mode 100755 index 0000000..7b9c68c --- /dev/null +++ b/src/resources/assets/js/Gate.js @@ -0,0 +1,41 @@ +/** + * Here you should import all policies that you create and want to use through your application + */ +import DashboardPolicy from './Policies/DashboardPolicy'; +import RouterPolicy from './Policies/RouterPolicy'; + + +export default class Gate { + constructor( user ) { + + this.user = user; + + this.policies = { + dashboard: DashboardPolicy, + router: RouterPolicy, + }; + } + + before() { + return this.user.role === undefined || this.user.role === 'admin'; + } + + can( action, type, model = null ) { + if ( this.before() ) { + return true; + } + + if ( this.policies[ type ][ action ] !== undefined ) { + + return this.policies[ type ][ action ]( this.user, model ); + } + + return true; + + + } + + cant( action, type, model = null ) { + return !this.can( action, type, model ); + } +} \ No newline at end of file diff --git a/src/resources/assets/js/Policies/DashboardPolicy.js b/src/resources/assets/js/Policies/DashboardPolicy.js new file mode 100755 index 0000000..7bfc238 --- /dev/null +++ b/src/resources/assets/js/Policies/DashboardPolicy.js @@ -0,0 +1,8 @@ +export default class DashboardPolicy { + + static access( user ) { + + return user.role === undefined || user.role === 'admin' || user.role === 'user'; + } + +} \ No newline at end of file diff --git a/src/resources/assets/js/Policies/RouterPolicy.js b/src/resources/assets/js/Policies/RouterPolicy.js new file mode 100755 index 0000000..fb13596 --- /dev/null +++ b/src/resources/assets/js/Policies/RouterPolicy.js @@ -0,0 +1,22 @@ +export default class RouterPolicy { + + static customers_table( user ) { + + return user.role === 'user'; + } + + static customer_groups( user ) { + + return user.role === 'user'; + } + + static customer_details( user, route ) { + + return user.role === 'user' || user.id === route.params.id; + } + + static inventory( user, route ) { + + return user.role === 'user'; + } +} \ No newline at end of file diff --git a/src/resources/assets/js/components/Clients/EditClient.vue b/src/resources/assets/js/components/Clients/EditClient.vue new file mode 100644 index 0000000..5a3b60c --- /dev/null +++ b/src/resources/assets/js/components/Clients/EditClient.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/Clients/NewClient.vue b/src/resources/assets/js/components/Clients/NewClient.vue new file mode 100644 index 0000000..c50e05c --- /dev/null +++ b/src/resources/assets/js/components/Clients/NewClient.vue @@ -0,0 +1,97 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/Clients/Table.vue b/src/resources/assets/js/components/Clients/Table.vue new file mode 100644 index 0000000..0780998 --- /dev/null +++ b/src/resources/assets/js/components/Clients/Table.vue @@ -0,0 +1,232 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/Navbar.vue b/src/resources/assets/js/components/Navbar.vue new file mode 100755 index 0000000..e094e2b --- /dev/null +++ b/src/resources/assets/js/components/Navbar.vue @@ -0,0 +1,77 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/NavigationComponent.vue b/src/resources/assets/js/components/NavigationComponent.vue new file mode 100755 index 0000000..0928263 --- /dev/null +++ b/src/resources/assets/js/components/NavigationComponent.vue @@ -0,0 +1,101 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/Settings/Components/Deployments.vue b/src/resources/assets/js/components/Settings/Components/Deployments.vue new file mode 100644 index 0000000..6f329c2 --- /dev/null +++ b/src/resources/assets/js/components/Settings/Components/Deployments.vue @@ -0,0 +1,248 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/Settings/Components/Navigation.vue b/src/resources/assets/js/components/Settings/Components/Navigation.vue new file mode 100644 index 0000000..3d919b5 --- /dev/null +++ b/src/resources/assets/js/components/Settings/Components/Navigation.vue @@ -0,0 +1,30 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/components/Settings/Settings.vue b/src/resources/assets/js/components/Settings/Settings.vue new file mode 100644 index 0000000..3f21539 --- /dev/null +++ b/src/resources/assets/js/components/Settings/Settings.vue @@ -0,0 +1,63 @@ + + + + + \ No newline at end of file diff --git a/src/resources/assets/js/laravel-deploy.js b/src/resources/assets/js/laravel-deploy.js new file mode 100644 index 0000000..5038d9f --- /dev/null +++ b/src/resources/assets/js/laravel-deploy.js @@ -0,0 +1,121 @@ +import Gate from './Gate'; +import Vue from 'vue'; +import BootstrapVue from 'bootstrap-vue'; +import VueResource from 'vue-resource'; +import VueRouter from 'vue-router'; +import Toasted from 'vue-toasted'; +import Icon from 'vue-awesome/components/Icon'; +import 'vue-awesome/icons'; +// Translations +import Lang from 'lang.js'; +// Navigation components +import NavigationComponent from './components/NavigationComponent.vue'; +import Navbar from './components/Navbar'; +// Clients +import ClientsTable from './components/Clients/Table'; +// Settings +import Settings from './components/Settings/Settings'; +import SettingsDeployments from './components/Settings/Components/Deployments'; + +const default_locale = window.default_locale; +const fallback_locale = window.fallback_locale; +const messages = window.messages; + +window._ = require( 'lodash' ); +window.changeCase = require( 'change-case' ); +require( 'datejs' ); + +const routes = [ + + { path: '/', redirect: 'clients' }, + { path: '/clients', component: ClientsTable, name: 'clients' }, + { + path: '/settings', component: Settings, name: 'settings', redirect: 'settings/deployments', children: + [ + { + path: 'deployments', + component: SettingsDeployments, + name: 'settings-deployments' + } + ] + }, +]; + +Vue.use( VueResource ); +Vue.use( BootstrapVue ); +Vue.use( VueRouter ); +Vue.component( 'icon', Icon ); +Vue.use( Toasted ); + +console.log( messages ); +console.log( default_locale ); +Vue.prototype.$eventHub = new Vue(); // Global event bus +Vue.prototype.$gate = new Gate( window.user ); +VueRouter.prototype.$gate = new Gate( window.user ); +Vue.prototype.trans = new Lang( { messages, locale: default_locale, fallback: fallback_locale } ); + +/** + * Next, we will create a fresh Vue application instance and attach it to + * the page. Then, you may begin adding components to this application + * or customize the JavaScript scaffolding to fit your unique needs. + */ + +let token = document.head.querySelector( 'meta[name="csrf-token"]' ).content; + +Vue.http.headers.common[ 'X-CSRF-TOKEN' ] = token; +Vue.http.headers.common[ 'Accept' ] = 'application/json'; + +const router = new VueRouter( { + routes // short for `routes: routes` +} ); + +Vue.http.interceptors.push( function ( request ) { + + let self = this; + + // return response callback + return function ( response ) { + + if ( response.status === 401 ) { + + self.$toasted.show( 'You have been logged out due to inactivity, will be now redirected to login.', { + + type: 'danger', + duration: 2000, + } ); + + window.setTimeout( function () { + + window.location.replace( '/' ); + }, 3000 ); + + } + + }; +} ); +router.beforeEach( ( to, from, next ) => { + + //console.log( to ); + let self = router; + + if ( to.path !== '/' && to.name !== null && self.$gate.can( window.changeCase.snakeCase( to.name ), 'router', to ) ) { + + next(); + + } else { + + return false; + } + +} ); + +const app = new Vue( { + el: '#app', + components: { + + 'navigation-component': NavigationComponent, + 'navbar': Navbar, + }, + router, + +} ); diff --git a/src/resources/assets/js/messages.js b/src/resources/assets/js/messages.js new file mode 100755 index 0000000..ed1fad0 --- /dev/null +++ b/src/resources/assets/js/messages.js @@ -0,0 +1,51 @@ +fs = require( 'fs' ); + +export default { + + /** + * English + */ + 'en.app': require( '../../lang/en/app.php' ), + 'en.auth': require( '../../lang/en/auth.php' ), + 'en.admin': require( '../../lang/en/admin.php' ), + 'en.search': require( '../../lang/en/search.php' ), + 'en.navigation': require( '../../lang/en/navigation.php' ), + 'en.buttons': require( '../../lang/en/buttons.php' ), + // Products + 'en.products': require( '../../lang/en/products.php' ), + 'en.product': require( '../../lang/en/product.php' ), + // Integrations + 'en.integrations': require( '../../lang/en/integrations.php' ), + // Customers + 'en.customers': require( '../../lang/en/customers.php' ), + // Inventory + 'en.inventory': require( '../../lang/en/inventory.php' ), + // Contracts + 'en.contracts': require( '../../lang/en/contracts.php' ), + // General tables + 'en.tables': require( '../../lang/en/tables.php' ), + + + /** + * Denmark + */ + 'da.app': require( '../../lang/da/app.php' ), + 'da.auth': require( '../../lang/da/auth.php' ), + 'da.admin': require( '../../lang/da/admin.php' ), + 'da.search': require( '../../lang/da/search.php' ), + 'da.navigation': require( '../../lang/da/navigation.php' ), + 'da.buttons': require( '../../lang/da/buttons.php' ), + // Products + 'da.products': require( '../../lang/da/products.php' ), + 'da.product': require( '../../lang/da/product.php' ), + // Integrations + 'da.integrations': require( '../../lang/da/integrations.php' ), + // Customers + 'da.customers': require( '../../lang/da/customers.php' ), + // Inventory + 'da.inventory': require( '../../lang/da/inventory.php' ), + // Contracts + 'da.contracts': require( '../../lang/da/contracts.php' ), + // General tables + 'da.tables': require( '../../lang/da/tables.php' ), +}; \ No newline at end of file diff --git a/src/resources/assets/sass/_variables.scss b/src/resources/assets/sass/_variables.scss new file mode 100755 index 0000000..3d72bd1 --- /dev/null +++ b/src/resources/assets/sass/_variables.scss @@ -0,0 +1,23 @@ +// Body +$body-bg : #f5f8fa; + +// Typography +$font-family-sans-serif : "Raleway", sans-serif; +$font-size-base : 0.9rem; +$line-height-base : 1.6; +$text-color : #636b6f; + +// Navbar +$navbar-default-bg : #ffffff; + +// Buttons +$btn-default-color : $text-color; + +// Panels +$panel-default-heading-bg : #ffffff; + +// bg variants +$bg-primary-black : #212121; + +// Text variants +$blue-on-primary-black : #03a9f4; diff --git a/src/resources/assets/sass/laravel-deploy.scss b/src/resources/assets/sass/laravel-deploy.scss new file mode 100755 index 0000000..fb5e9e6 --- /dev/null +++ b/src/resources/assets/sass/laravel-deploy.scss @@ -0,0 +1,17 @@ +// Fonts +@import url("https://fonts.googleapis.com/css?family=Raleway:300,400,600"); +// Variables +@import "variables"; +// Bootstrap +@import '~bootstrap/scss/bootstrap'; +@import '~bootstrap-vue/dist/bootstrap-vue'; + +.bg-black { + + background-color : $bg-primary-black; +} + +.text-blue-on-primary-black { + + color : $blue-on-primary-black; +} diff --git a/src/resources/lang/en/clients.php b/src/resources/lang/en/clients.php new file mode 100644 index 0000000..8a4dc28 --- /dev/null +++ b/src/resources/lang/en/clients.php @@ -0,0 +1,49 @@ + [ + 'title' => 'Add New Client', + + 'labels' => [ + + 'name' => 'Name:', + 'source' => 'Source:', + 'token' => 'Token:', + 'script' => 'Script name:', + 'auto_deploy' => 'Auto deploy', + ], + 'placeholders' => [ + + 'name' => 'eg. GitHub', + 'source' => 'eg. https://github.com', + 'script' => 'eg. github_deploy.sh', + 'token' => 'eg. test123', + ], + ], + 'edit' => [ + 'title' => 'Edit Client', + + 'labels' => [ + + 'name' => 'Name:', + 'source' => 'Source:', + 'token' => 'Token:', + 'script' => 'Script name:', + 'auto_deploy' => 'Auto deploy', + ], + 'placeholders' => [ + + 'name' => 'eg. GitHub', + 'source' => 'eg. https://github.com', + 'script' => 'eg. github_deploy.sh', + 'token' => 'eg. test123', + ], + ], +]; \ No newline at end of file diff --git a/src/resources/lang/en/navbar.php b/src/resources/lang/en/navbar.php new file mode 100644 index 0000000..25fd62c --- /dev/null +++ b/src/resources/lang/en/navbar.php @@ -0,0 +1,13 @@ + 'Clients', + 'settings' => 'Settings', +]; \ No newline at end of file diff --git a/src/resources/lang/en/settings.php b/src/resources/lang/en/settings.php new file mode 100644 index 0000000..6c58653 --- /dev/null +++ b/src/resources/lang/en/settings.php @@ -0,0 +1,50 @@ + [ + + 'http' => [ + + 'deploy_now' => [ + + 'success' => 'Deployment has been started, please check back soon for details.', + 'error' => 'We can\'t run deployment script right now.', + ], + 'change_quick_deploy' => [ + + 'success' => 'Your quick deploy settings have been changed.', + 'error' => 'We can\'t change quick deploy settings right now.', + ], + ], + 'deploy_now' => [ + + 'modal' => [ + + 'labels' => [ + + 'client' => 'Select Client', + ], + 'title' => 'Choose Client to Deploy As', + ], + ], + + 'quick_deploy' => [ + + 'modal' => [ + + 'labels' => [ + + 'client' => 'Select Client', + ], + 'title' => 'Choose Client to Set Quick Deploy', + ], + ], + ], +]; \ No newline at end of file diff --git a/src/resources/views/dashboard.blade.php b/src/resources/views/dashboard.blade.php new file mode 100644 index 0000000..0aca1ab --- /dev/null +++ b/src/resources/views/dashboard.blade.php @@ -0,0 +1,107 @@ + + + + + + + + + {{ config('laravel-deploy.front.title') }} + + + + + + + + + + + @stack('styles') + + + + + + + + + + + + + + + + + + + + +@stack('urls') + + + + + + +@stack('scripts') + + \ No newline at end of file