diff --git a/.env.example b/.env.example index bf21e04..5678913 100755 --- a/.env.example +++ b/.env.example @@ -2,28 +2,9 @@ APP_NAME=Vito APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=http://vito.test +APP_URL= -LOG_CHANNEL=stack -LOG_LEVEL=debug - -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_DATABASE=vito -DB_USERNAME=root -DB_PASSWORD= - -BROADCAST_DRIVER=null -CACHE_DRIVER=file FILESYSTEM_DRIVER=local -QUEUE_CONNECTION=default -SESSION_DRIVER=database -SESSION_LIFETIME=120 - -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 MAIL_MAILER=smtp MAIL_HOST= @@ -33,6 +14,3 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" - -SSH_PUBLIC_KEY_NAME=ssh-public.key -SSH_PRIVATE_KEY_NAME=ssh-private.pem diff --git a/.env.prod b/.env.prod index 8b39156..574eedb 100755 --- a/.env.prod +++ b/.env.prod @@ -4,26 +4,7 @@ APP_KEY= APP_DEBUG=false APP_URL= -LOG_CHANNEL=stack -LOG_LEVEL=debug - -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_DATABASE= -DB_USERNAME= -DB_PASSWORD= - -BROADCAST_DRIVER=null -CACHE_DRIVER=file FILESYSTEM_DRIVER=local -QUEUE_CONNECTION=default -SESSION_DRIVER=database -SESSION_LIFETIME=120 - -REDIS_HOST=127.0.0.1 -REDIS_PASSWORD=null -REDIS_PORT=6379 MAIL_MAILER=smtp MAIL_HOST= @@ -33,6 +14,3 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" - -SSH_PUBLIC_KEY_NAME=ssh-public.key -SSH_PRIVATE_KEY_NAME=ssh-private.pem diff --git a/.env.sail b/.env.sail index 9511873..db44bba 100644 --- a/.env.sail +++ b/.env.sail @@ -2,28 +2,10 @@ APP_NAME=Vito APP_ENV=local APP_KEY= APP_DEBUG=true -APP_URL=http://vito.test +APP_URL= +APP_PORT=8000 -LOG_CHANNEL=stack -LOG_LEVEL=debug - -DB_CONNECTION=mysql -DB_HOST=mysql -DB_PORT=3306 -DB_DATABASE=vito -DB_USERNAME=sail -DB_PASSWORD=password - -BROADCAST_DRIVER=null -CACHE_DRIVER=redis FILESYSTEM_DRIVER=local -QUEUE_CONNECTION=default -SESSION_DRIVER=database -SESSION_LIFETIME=120 - -REDIS_HOST=redis -REDIS_PASSWORD=null -REDIS_PORT=6379 MAIL_MAILER=smtp MAIL_HOST= @@ -33,13 +15,3 @@ MAIL_PASSWORD=null MAIL_ENCRYPTION=null MAIL_FROM_ADDRESS=null MAIL_FROM_NAME="${APP_NAME}" - -SSH_PUBLIC_KEY_NAME=ssh-public.key -SSH_PRIVATE_KEY_NAME=ssh-private.pem - -APP_SERVICE=vito - -FORWARD_REDIS_PORT=2060 -FORWARD_DB_PORT=2070 -APP_PORT=2080 -HMR_PORT=2090 diff --git a/.env.testing b/.env.testing deleted file mode 100755 index caee8cd..0000000 --- a/.env.testing +++ /dev/null @@ -1,27 +0,0 @@ -APP_NAME=Vito -APP_ENV=local -APP_KEY=base64:d9kZW60V4lFEw2SPn6UiJ0cfi04v80EWP0GZ6kzoxNg= -APP_DEBUG=true -APP_URL=http://localhost:2080 - -LOG_CHANNEL=stack -LOG_LEVEL=debug - -DB_CONNECTION=mysql -DB_HOST=127.0.0.1 -DB_PORT=3306 -DB_DATABASE=vito_test -DB_USERNAME=root -DB_PASSWORD= - -BROADCAST_DRIVER=null -CACHE_DRIVER=array -FILESYSTEM_DRIVER=local -QUEUE_CONNECTION=database -SESSION_DRIVER=array -SESSION_LIFETIME=120 - -MAIL_MAILER=array - -SSH_PUBLIC_KEY_NAME=ssh-public.key -SSH_PRIVATE_KEY_NAME=ssh-private.pem diff --git a/.github/workflows/code-style.yml b/.github/workflows/code-style.yml index 7a77dbb..0fcfebb 100644 --- a/.github/workflows/code-style.yml +++ b/.github/workflows/code-style.yml @@ -2,9 +2,9 @@ name: code-style on: push: + branches: + - 1.x pull_request: - schedule: - - cron: '0 0 * * *' jobs: code-style: @@ -13,7 +13,8 @@ jobs: strategy: fail-fast: true matrix: - php: [ 8.1 ] + php: [8.2] + node-version: ["20.x"] steps: - uses: actions/checkout@v2 @@ -31,9 +32,21 @@ jobs: key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} restore-keys: | ${{ runner.os }}-php- + - name: Install dependencies if: steps.composer-cache.outputs.cache-hit != 'true' run: composer install --prefer-dist --no-progress --no-suggest - name: Run pint run: ./vendor/bin/pint --test + + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: "20.x" + + - name: Install NPM Dependencies + run: npm install + + - name: Run lint + run: npm run lint diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 42b2f12..4d68c1a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,30 +2,18 @@ name: tests on: push: + branches: + - 1.x pull_request: - schedule: - - cron: '0 0 * * *' jobs: tests: runs-on: ubuntu-22.04 - services: - mysql: - image: mysql - env: - MYSQL_DATABASE: test_db - MYSQL_USER: user - MYSQL_PASSWORD: password - MYSQL_ROOT_PASSWORD: rootpassword - ports: - - 3306:3306 - options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 - strategy: fail-fast: true matrix: - php: [ 8.1 ] + php: [8.2] steps: - uses: actions/checkout@v2 @@ -47,10 +35,8 @@ jobs: if: steps.composer-cache.outputs.cache-hit != 'true' run: composer install --prefer-dist --no-progress --no-suggest + - name: Create sqlite database + run: touch storage/database-test.sqlite + - name: Run test suite run: php artisan test - env: - DB_HOST: 127.0.0.1 - DB_DATABASE: test_db - DB_USERNAME: user - DB_PASSWORD: password diff --git a/.gitignore b/.gitignore index 8fade24..a935506 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ /public/storage /storage/*.key /storage/*.pem +/storage/test-key +/storage/test-key.pub /vendor .env .env.backup diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..3ff0972 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,16 @@ +{ + "plugins": ["prettier-plugin-blade", "prettier-plugin-tailwindcss"], + "overrides": [ + { + "files": ["*.blade.php"], + "options": { + "parser": "blade", + "printWidth": 120, + "htmlWhitespaceSensitivity": "ignore", + "tabWidth": 4, + "quoteProps": "consistent", + "trailingComma": "none" + } + } + ] +} diff --git a/README.md b/README.md index 2e8495a..970203c 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,12 @@ ## About Vito Vito is a self-hosted web application that helps you manage your servers and deploy your PHP applications into production servers without a hassle. +## Quick Start + +```sh +bash <(curl -Ls https://raw.githubusercontent.com/vitodeploy/vito/1.x/scripts/install.sh) +``` + ## Features - Provisions and Manages the server @@ -38,15 +44,13 @@ ## Useful Links ## Credits - Laravel -- Tailwindcss -- Livewire -- Alpinejs -- Vite -- Laravel Enum by BenSampo -- Log Viewer by Arunas Skirius - PHPSecLib -- Laravel Blade Icons -- Guzzlehttp -- Owenvoke for `owenvoke/blade-fontawesome` -- Axios +- PHPUnit +- Tailwindcss +- Alpinejs +- HTMX +- Vite - Toastr by CodeSeven +- Prettier +- Postcss +- Flowbite diff --git a/SECURITY.md b/SECURITY.md index 6a39a52..3ed54c5 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -2,9 +2,10 @@ # Security Policy ## Supported Versions -| Version | Supported | -| ------- | ------------------ | -| 0.x | :white_check_mark: | +| Version | Supported | +| ------- | ----------| +| 0.x | ❌ | +| 1.x | ✅ | ## Reporting a Vulnerability diff --git a/app/Actions/CronJob/CreateCronJob.php b/app/Actions/CronJob/CreateCronJob.php index ff5262a..972f6b9 100755 --- a/app/Actions/CronJob/CreateCronJob.php +++ b/app/Actions/CronJob/CreateCronJob.php @@ -19,11 +19,14 @@ public function create(Server $server, array $input): void 'server_id' => $server->id, 'user' => $input['user'], 'command' => $input['command'], - 'frequency' => $input['frequency'], + 'frequency' => $input['frequency'] == 'custom' ? $input['custom'] : $input['frequency'], 'status' => CronjobStatus::CREATING, ]); $cronJob->save(); - $cronJob->addToServer(); + + $server->cron()->update($cronJob->user, CronJob::crontab($server, $cronJob->user)); + $cronJob->status = CronjobStatus::READY; + $cronJob->save(); } /** @@ -41,8 +44,17 @@ private function validate(array $input): void ], 'frequency' => [ 'required', - new CronRule(), + new CronRule(acceptCustom: true), ], - ])->validateWithBag('createCronJob'); + ])->validate(); + + if ($input['frequency'] == 'custom') { + Validator::make($input, [ + 'custom' => [ + 'required', + new CronRule(), + ], + ])->validate(); + } } } diff --git a/app/Actions/CronJob/DeleteCronJob.php b/app/Actions/CronJob/DeleteCronJob.php new file mode 100755 index 0000000..9eeacf3 --- /dev/null +++ b/app/Actions/CronJob/DeleteCronJob.php @@ -0,0 +1,16 @@ +user; + $cronJob->delete(); + $server->cron()->update($cronJob->user, CronJob::crontab($server, $user)); + } +} diff --git a/app/Actions/Database/CreateBackup.php b/app/Actions/Database/CreateBackup.php index 3acbd01..f3057fc 100644 --- a/app/Actions/Database/CreateBackup.php +++ b/app/Actions/Database/CreateBackup.php @@ -24,15 +24,15 @@ public function create($type, Server $server, array $input): Backup $backup = new Backup([ 'type' => $type, 'server_id' => $server->id, - 'database_id' => $input['database'] ?? null, - 'storage_id' => $input['storage'], - 'interval' => $input['interval'] == 'custom' ? $input['custom'] : $input['interval'], - 'keep_backups' => $input['keep'], + 'database_id' => $input['backup_database'] ?? null, + 'storage_id' => $input['backup_storage'], + 'interval' => $input['backup_interval'] == 'custom' ? $input['backup_custom'] : $input['backup_interval'], + 'keep_backups' => $input['backup_keep'], 'status' => BackupStatus::RUNNING, ]); $backup->save(); - $backup->run(); + app(RunBackup::class)->run($backup); return $backup; } @@ -43,16 +43,16 @@ public function create($type, Server $server, array $input): Backup private function validate($type, Server $server, array $input): void { $rules = [ - 'storage' => [ + 'backup_storage' => [ 'required', Rule::exists('storage_providers', 'id'), ], - 'keep' => [ + 'backup_keep' => [ 'required', 'numeric', 'min:1', ], - 'interval' => [ + 'backup_interval' => [ 'required', Rule::in([ '0 * * * *', @@ -63,13 +63,13 @@ private function validate($type, Server $server, array $input): void ]), ], ]; - if ($input['interval'] == 'custom') { - $rules['custom'] = [ + if ($input['backup_interval'] == 'custom') { + $rules['backup_custom'] = [ 'required', ]; } if ($type === 'database') { - $rules['database'] = [ + $rules['backup_database'] = [ 'required', Rule::exists('databases', 'id') ->where('server_id', $server->id) diff --git a/app/Actions/Database/CreateDatabase.php b/app/Actions/Database/CreateDatabase.php index 7136035..f825d06 100755 --- a/app/Actions/Database/CreateDatabase.php +++ b/app/Actions/Database/CreateDatabase.php @@ -2,6 +2,7 @@ namespace App\Actions\Database; +use App\Enums\DatabaseStatus; use App\Models\Database; use App\Models\Server; use Illuminate\Support\Facades\Validator; @@ -21,8 +22,9 @@ public function create(Server $server, array $input): Database 'server_id' => $server->id, 'name' => $input['name'], ]); + $server->database()->handler()->create($database->name); + $database->status = DatabaseStatus::READY; $database->save(); - $database->createOnServer(); return $database; } diff --git a/app/Actions/Database/CreateDatabaseUser.php b/app/Actions/Database/CreateDatabaseUser.php index 0521c45..2c39587 100755 --- a/app/Actions/Database/CreateDatabaseUser.php +++ b/app/Actions/Database/CreateDatabaseUser.php @@ -2,6 +2,7 @@ namespace App\Actions\Database; +use App\Enums\DatabaseUserStatus; use App\Models\DatabaseUser; use App\Models\Server; use Illuminate\Support\Facades\Validator; @@ -24,8 +25,17 @@ public function create(Server $server, array $input, array $links = []): Databas 'host' => isset($input['remote']) && $input['remote'] ? $input['host'] : 'localhost', 'databases' => $links, ]); + $server->database()->handler()->createUser( + $databaseUser->username, + $databaseUser->password, + $databaseUser->host + ); + $databaseUser->status = DatabaseUserStatus::READY; $databaseUser->save(); - $databaseUser->createOnServer(); + + if (count($links) > 0) { + app(LinkUser::class)->link($databaseUser, ['databases' => $links]); + } return $databaseUser; } diff --git a/app/Actions/Database/DeleteDatabase.php b/app/Actions/Database/DeleteDatabase.php new file mode 100755 index 0000000..c8d49f8 --- /dev/null +++ b/app/Actions/Database/DeleteDatabase.php @@ -0,0 +1,15 @@ +database()->handler()->delete($database->name); + $database->delete(); + } +} diff --git a/app/Actions/Database/DeleteDatabaseUser.php b/app/Actions/Database/DeleteDatabaseUser.php new file mode 100755 index 0000000..8b70433 --- /dev/null +++ b/app/Actions/Database/DeleteDatabaseUser.php @@ -0,0 +1,15 @@ +database()->handler()->deleteUser($databaseUser->username, $databaseUser->host); + $databaseUser->delete(); + } +} diff --git a/app/Actions/Database/LinkUser.php b/app/Actions/Database/LinkUser.php index c6a9bcf..43026e8 100755 --- a/app/Actions/Database/LinkUser.php +++ b/app/Actions/Database/LinkUser.php @@ -4,6 +4,9 @@ use App\Models\Database; use App\Models\DatabaseUser; +use App\Models\Server; +use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; class LinkUser @@ -11,20 +14,49 @@ class LinkUser /** * @throws ValidationException */ - public function link(DatabaseUser $databaseUser, array $databases): void + public function link(DatabaseUser $databaseUser, array $input): void { - $dbs = Database::query() - ->where('server_id', $databaseUser->server_id) - ->whereIn('name', $databases) - ->count(); - if (count($databases) !== $dbs) { - throw ValidationException::withMessages(['databases' => __('Databases not found!')]) - ->errorBag('linkUser'); + if (! isset($input['databases']) || ! is_array($input['databases'])) { + $input['databases'] = []; } - $databaseUser->databases = $databases; - $databaseUser->unlinkUser(); - $databaseUser->linkUser(); + $this->validate($databaseUser->server, $input); + + $dbs = Database::query() + ->where('server_id', $databaseUser->server_id) + ->whereIn('name', $input['databases']) + ->count(); + if (count($input['databases']) !== $dbs) { + throw ValidationException::withMessages(['databases' => __('Databases not found!')]); + } + + $databaseUser->databases = $input['databases']; + + // Unlink the user from all databases + $databaseUser->server->database()->handler()->unlink( + $databaseUser->username, + $databaseUser->host + ); + + // Link the user to the selected databases + $databaseUser->server->database()->handler()->link( + $databaseUser->username, + $databaseUser->host, + $databaseUser->databases + ); + $databaseUser->save(); } + + private function validate(Server $server, array $input): void + { + $rules = [ + 'databases.*' => [ + 'required', + Rule::exists('databases', 'name')->where('server_id', $server->id), + ], + ]; + + Validator::make($input, $rules)->validate(); + } } diff --git a/app/Actions/Database/RestoreBackup.php b/app/Actions/Database/RestoreBackup.php new file mode 100644 index 0000000..2c9bea6 --- /dev/null +++ b/app/Actions/Database/RestoreBackup.php @@ -0,0 +1,39 @@ +validate($input); + + /** @var Database $database */ + $database = Database::query()->findOrFail($input['database']); + $backupFile->status = BackupFileStatus::RESTORING; + $backupFile->restored_to = $database->name; + $backupFile->save(); + + dispatch(function () use ($backupFile, $database) { + $database->server->database()->handler()->restoreBackup($backupFile, $database->name); + $backupFile->status = BackupFileStatus::RESTORED; + $backupFile->restored_at = now(); + $backupFile->save(); + })->catch(function () use ($backupFile) { + $backupFile->status = BackupFileStatus::RESTORE_FAILED; + $backupFile->save(); + })->onConnection('ssh'); + } + + private function validate(array $input): void + { + Validator::make($input, [ + 'database' => 'required|exists:databases,id', + ])->validate(); + } +} diff --git a/app/Actions/Database/RunBackup.php b/app/Actions/Database/RunBackup.php new file mode 100644 index 0000000..7ea949d --- /dev/null +++ b/app/Actions/Database/RunBackup.php @@ -0,0 +1,32 @@ + $backup->id, + 'name' => Str::of($backup->database->name)->slug().'-'.now()->format('YmdHis'), + 'status' => BackupFileStatus::CREATING, + ]); + $file->save(); + + dispatch(function () use ($file) { + $file->backup->server->database()->handler()->runBackup($file); + $file->status = BackupFileStatus::CREATED; + $file->save(); + })->catch(function () use ($file) { + $file->status = BackupFileStatus::FAILED; + $file->save(); + })->onConnection('ssh'); + + return $file; + } +} diff --git a/app/Actions/FirewallRule/CreateRule.php b/app/Actions/FirewallRule/CreateRule.php index 135f352..b5a4bab 100755 --- a/app/Actions/FirewallRule/CreateRule.php +++ b/app/Actions/FirewallRule/CreateRule.php @@ -21,10 +21,20 @@ public function create(Server $server, array $input): FirewallRule 'port' => $input['port'], 'source' => $input['source'], 'mask' => $input['mask'] ?? null, - 'status' => FirewallRuleStatus::CREATING, ]); + + $server->firewall() + ->handler() + ->addRule( + $rule->type, + $rule->getRealProtocol(), + $rule->port, + $rule->source, + $rule->mask + ); + + $rule->status = FirewallRuleStatus::READY; $rule->save(); - $rule->addToServer(); return $rule; } @@ -56,6 +66,6 @@ private function validate(Server $server, array $input): void 'mask' => [ 'numeric', ], - ])->validateWithBag('createRule'); + ])->validate(); } } diff --git a/app/Actions/FirewallRule/DeleteRule.php b/app/Actions/FirewallRule/DeleteRule.php new file mode 100755 index 0000000..e4ce83e --- /dev/null +++ b/app/Actions/FirewallRule/DeleteRule.php @@ -0,0 +1,28 @@ +status = FirewallRuleStatus::DELETING; + $rule->save(); + + $server->firewall() + ->handler() + ->removeRule( + $rule->type, + $rule->getRealProtocol(), + $rule->port, + $rule->source, + $rule->mask + ); + + $rule->delete(); + } +} diff --git a/app/Actions/PHP/ChangeDefaultCli.php b/app/Actions/PHP/ChangeDefaultCli.php new file mode 100644 index 0000000..ff60476 --- /dev/null +++ b/app/Actions/PHP/ChangeDefaultCli.php @@ -0,0 +1,29 @@ +validate($server, $input); + $service = $server->php($input['version']); + $service->handler()->setDefaultCli(); + $server->defaultService('php')->update(['is_default' => 0]); + $service->update(['is_default' => 1]); + $service->update(['status' => ServiceStatus::READY]); + } + + public function validate(Server $server, array $input): void + { + if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) { + throw ValidationException::withMessages( + ['version' => __('This version is not installed')] + ); + } + } +} diff --git a/app/Actions/PHP/GetPHPIni.php b/app/Actions/PHP/GetPHPIni.php new file mode 100644 index 0000000..ef717d6 --- /dev/null +++ b/app/Actions/PHP/GetPHPIni.php @@ -0,0 +1,33 @@ +validate($server, $input); + + $php = $server->php($input['version']); + + try { + return $php->handler()->getPHPIni(); + } catch (\Throwable $e) { + throw ValidationException::withMessages( + ['ini' => $e->getMessage()] + ); + } + } + + public function validate(Server $server, array $input): void + { + if (! isset($input['version']) || ! in_array($input['version'], $server->installedPHPVersions())) { + throw ValidationException::withMessages( + ['version' => __('This version is not installed')] + ); + } + } +} diff --git a/app/Actions/PHP/InstallNewPHP.php b/app/Actions/PHP/InstallNewPHP.php index fe75a05..b104975 100755 --- a/app/Actions/PHP/InstallNewPHP.php +++ b/app/Actions/PHP/InstallNewPHP.php @@ -28,7 +28,14 @@ public function install(Server $server, array $input): void 'is_default' => false, ]); $php->save(); - $php->install(); + + dispatch(function () use ($php) { + $php->handler()->install(); + $php->status = ServiceStatus::READY; + $php->save(); + })->catch(function () use ($php) { + $php->delete(); + })->onConnection('ssh'); } /** @@ -41,12 +48,12 @@ private function validate(Server $server, array $input): void 'required', Rule::in(config('core.php_versions')), ], - ])->validateWithBag('installPHP'); + ])->validate(); if (in_array($input['version'], $server->installedPHPVersions())) { throw ValidationException::withMessages( ['version' => __('This version is already installed')] - )->errorBag('installPHP'); + ); } } } diff --git a/app/Actions/PHP/InstallPHPExtension.php b/app/Actions/PHP/InstallPHPExtension.php index f310fa3..eba6a41 100755 --- a/app/Actions/PHP/InstallPHPExtension.php +++ b/app/Actions/PHP/InstallPHPExtension.php @@ -2,25 +2,35 @@ namespace App\Actions\PHP; +use App\Models\Server; use App\Models\Service; use Illuminate\Support\Facades\Validator; +use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; class InstallPHPExtension { - /** - * @throws ValidationException - */ - public function handle(Service $service, array $input): Service + public function install(Server $server, array $input): Service { + $this->validate($server, $input); + + /** @var Service $service */ + $service = $server->php($input['version']); $typeData = $service->type_data; $typeData['extensions'] = $typeData['extensions'] ?? []; + $typeData['extensions'][] = $input['extension']; $service->type_data = $typeData; $service->save(); - $this->validate($service, $input); - - $service->handler()->installExtension($input['name']); + dispatch(function () use ($service, $input) { + $service->handler()->installExtension($input['extension']); + })->catch(function () use ($service, $input) { + $service->refresh(); + $typeData = $service->type_data; + $typeData['extensions'] = array_values(array_diff($typeData['extensions'], [$input['extension']])); + $service->type_data = $typeData; + $service->save(); + })->onConnection('ssh'); return $service; } @@ -28,18 +38,25 @@ public function handle(Service $service, array $input): Service /** * @throws ValidationException */ - private function validate(Service $service, array $input): void + private function validate(Server $server, array $input): void { Validator::make($input, [ - 'name' => [ + 'extension' => [ 'required', 'in:'.implode(',', config('core.php_extensions')), ], - ])->validateWithBag('installPHPExtension'); + 'version' => [ + 'required', + Rule::in($server->installedPHPVersions()), + ], + ])->validate(); - if (in_array($input['name'], $service->type_data['extensions'])) { + /** @var Service $service */ + $service = $server->php($input['version']); + + if (in_array($input['extension'], $service->type_data['extensions'])) { throw ValidationException::withMessages( - ['name' => __('This extension already installed')] + ['extension' => __('This extension already installed')] )->errorBag('installPHPExtension'); } } diff --git a/app/Actions/PHP/UninstallPHP.php b/app/Actions/PHP/UninstallPHP.php index 2596545..9c7e8d5 100755 --- a/app/Actions/PHP/UninstallPHP.php +++ b/app/Actions/PHP/UninstallPHP.php @@ -2,36 +2,48 @@ namespace App\Actions\PHP; +use App\Enums\ServiceStatus; use App\Models\Server; use App\Models\Service; +use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; class UninstallPHP { - public function uninstall(Server $server, string $version): void + public function uninstall(Server $server, array $input): void { - $this->validate($server, $version); + $this->validate($server, $input); /** @var Service $php */ - $php = $server->services()->where('type', 'php')->where('version', $version)->first(); + $php = $server->php($input['version']); + $php->status = ServiceStatus::UNINSTALLING; + $php->save(); - $php->uninstall(); + dispatch(function () use ($php) { + $php->handler()->uninstall(); + $php->delete(); + })->catch(function () use ($php) { + $php->status = ServiceStatus::FAILED; + $php->save(); + })->onConnection('ssh'); } /** * @throws ValidationException */ - private function validate(Server $server, string $version): void + private function validate(Server $server, array $input): void { - $php = $server->services()->where('type', 'php')->where('version', $version)->first(); + Validator::make($input, [ + 'version' => 'required|string', + ])->validate(); - if (! $php) { + if (! in_array($input['version'], $server->installedPHPVersions())) { throw ValidationException::withMessages( - ['version' => __('This version has not been installed yet!')] + ['version' => __('This version is not installed')] ); } - $hasSite = $server->sites()->where('php_version', $version)->first(); + $hasSite = $server->sites()->where('php_version', $input['version'])->first(); if ($hasSite) { throw ValidationException::withMessages( ['version' => __('Cannot uninstall this version because some sites are using it!')] diff --git a/app/Actions/PHP/UpdatePHPIni.php b/app/Actions/PHP/UpdatePHPIni.php index fd9392b..2f253d9 100755 --- a/app/Actions/PHP/UpdatePHPIni.php +++ b/app/Actions/PHP/UpdatePHPIni.php @@ -2,8 +2,9 @@ namespace App\Actions\PHP; -use App\Models\Service; +use App\Models\Server; use Illuminate\Support\Facades\Storage; +use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Validation\ValidationException; use Throwable; @@ -13,14 +14,18 @@ class UpdatePHPIni /** * @throws ValidationException */ - public function update(Service $service, string $ini): void + public function update(Server $server, array $input): void { + $this->validate($server, $input); + + $service = $server->php($input['version']); + $tmpName = Str::random(10).strtotime('now'); try { /** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */ $storageDisk = Storage::disk('local'); - $storageDisk->put($tmpName, $ini); + $storageDisk->put($tmpName, $input['ini']); $service->server->ssh('root')->upload( $storageDisk->path($tmpName), "/etc/php/$service->version/cli/php.ini" @@ -42,4 +47,21 @@ private function deleteTempFile(string $name): void Storage::disk('local')->delete($name); } } + + public function validate(Server $server, array $input): void + { + Validator::make($input, [ + 'ini' => [ + 'required', + 'string', + ], + 'version' => 'required|string', + ])->validate(); + + if (! in_array($input['version'], $server->installedPHPVersions())) { + throw ValidationException::withMessages( + ['version' => __('This version is not installed')] + ); + } + } } diff --git a/app/Actions/Projects/DeleteProject.php b/app/Actions/Projects/DeleteProject.php index 05b4507..aa4e53d 100644 --- a/app/Actions/Projects/DeleteProject.php +++ b/app/Actions/Projects/DeleteProject.php @@ -8,11 +8,8 @@ class DeleteProject { - public function delete(User $user, int $projectId): void + public function delete(User $user, Project $project): void { - /** @var Project $project */ - $project = $user->projects()->findOrFail($projectId); - if ($user->projects()->count() === 1) { throw ValidationException::withMessages([ 'project' => __('Cannot delete the last project.'), diff --git a/app/Actions/Projects/UpdateProject.php b/app/Actions/Projects/UpdateProject.php index 0d5ecdd..132cf25 100644 --- a/app/Actions/Projects/UpdateProject.php +++ b/app/Actions/Projects/UpdateProject.php @@ -26,7 +26,7 @@ private function validate(Project $project, array $input): void 'required', 'string', 'max:255', - Rule::unique('projects')->ignore($project->id), + Rule::unique('projects')->where('user_id', $project->user_id)->ignore($project->id), ], ])->validate(); } diff --git a/app/Actions/Queue/CreateQueue.php b/app/Actions/Queue/CreateQueue.php index 42b15ce..29809ba 100644 --- a/app/Actions/Queue/CreateQueue.php +++ b/app/Actions/Queue/CreateQueue.php @@ -29,7 +29,23 @@ public function create(mixed $queueable, array $input): void 'status' => QueueStatus::CREATING, ]); $queue->save(); - $queue->deploy(); + + dispatch(function () use ($queue) { + $queue->server->processManager()->handler()->create( + $queue->id, + $queue->command, + $queue->user, + $queue->auto_start, + $queue->auto_restart, + $queue->numprocs, + $queue->getLogFile(), + $queue->site_id + ); + $queue->status = QueueStatus::RUNNING; + $queue->save(); + })->catch(function () use ($queue) { + $queue->delete(); + })->onConnection('ssh'); } /** @@ -60,6 +76,6 @@ protected function validate(array $input): void ], ]; - Validator::make($input, $rules)->validateWithBag('createQueue'); + Validator::make($input, $rules)->validate(); } } diff --git a/app/Actions/Queue/DeleteQueue.php b/app/Actions/Queue/DeleteQueue.php new file mode 100644 index 0000000..03af4af --- /dev/null +++ b/app/Actions/Queue/DeleteQueue.php @@ -0,0 +1,14 @@ +server->processManager()->handler()->delete($queue->id, $queue->site_id); + $queue->delete(); + } +} diff --git a/app/Actions/Queue/GetQueueLogs.php b/app/Actions/Queue/GetQueueLogs.php new file mode 100644 index 0000000..bbf7ca3 --- /dev/null +++ b/app/Actions/Queue/GetQueueLogs.php @@ -0,0 +1,13 @@ +server->processManager()->handler()->getLogs($queue->getLogFile()); + } +} diff --git a/app/Actions/Queue/ManageQueue.php b/app/Actions/Queue/ManageQueue.php new file mode 100644 index 0000000..4a89fb3 --- /dev/null +++ b/app/Actions/Queue/ManageQueue.php @@ -0,0 +1,42 @@ +status = QueueStatus::STARTING; + $queue->save(); + dispatch(function () use ($queue) { + $queue->server->processManager()->handler()->start($queue->id, $queue->site_id); + $queue->status = QueueStatus::RUNNING; + $queue->save(); + })->onConnection('ssh'); + } + + public function stop(Queue $queue): void + { + $queue->status = QueueStatus::STOPPING; + $queue->save(); + dispatch(function () use ($queue) { + $queue->server->processManager()->handler()->stop($queue->id, $queue->site_id); + $queue->status = QueueStatus::STOPPED; + $queue->save(); + })->onConnection('ssh'); + } + + public function restart(Queue $queue): void + { + $queue->status = QueueStatus::RESTARTING; + $queue->save(); + dispatch(function () use ($queue) { + $queue->server->processManager()->handler()->restart($queue->id, $queue->site_id); + $queue->status = QueueStatus::RUNNING; + $queue->save(); + })->onConnection('ssh'); + } +} diff --git a/app/Actions/SSL/CreateSSL.php b/app/Actions/SSL/CreateSSL.php index 18076da..3835659 100644 --- a/app/Actions/SSL/CreateSSL.php +++ b/app/Actions/SSL/CreateSSL.php @@ -2,8 +2,10 @@ namespace App\Actions\SSL; +use App\Enums\SslStatus; use App\Enums\SslType; use App\Models\Site; +use App\Models\Ssl; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; use Illuminate\Validation\ValidationException; @@ -17,13 +19,24 @@ public function create(Site $site, array $input): void { $this->validate($input); - if ($input['type'] == SslType::LETSENCRYPT) { - $site->createFreeSsl(); - } + $ssl = new Ssl([ + 'site_id' => $site->id, + 'type' => $input['type'], + 'certificate' => $input['certificate'] ?? null, + 'pk' => $input['private'] ?? null, + 'expires_at' => $input['type'] === SslType::LETSENCRYPT ? now()->addMonths(3) : $input['expires_at'], + 'status' => SslStatus::CREATING, + ]); + $ssl->save(); - if ($input['type'] == SslType::CUSTOM) { - $site->createCustomSsl($input['certificate'], $input['private']); - } + dispatch(function () use ($site, $ssl) { + $site->server->webserver()->handler()->setupSSL($ssl); + $ssl->status = SslStatus::CREATED; + $ssl->save(); + $site->type()->edit(); + })->catch(function () use ($ssl) { + $ssl->delete(); + }); } /** @@ -34,14 +47,15 @@ protected function validate(array $input): void $rules = [ 'type' => [ 'required', - Rule::in(SslType::getValues()), + Rule::in(config('core.ssl_types')), ], ]; if (isset($input['type']) && $input['type'] == SslType::CUSTOM) { $rules['certificate'] = 'required'; $rules['private'] = 'required'; + $rules['expires_at'] = 'required|date_format:Y-m-d|after_or_equal:'.now(); } - Validator::make($input, $rules)->validateWithBag('createSSL'); + Validator::make($input, $rules)->validate(); } } diff --git a/app/Actions/SSL/DeleteSSL.php b/app/Actions/SSL/DeleteSSL.php new file mode 100644 index 0000000..82e06e8 --- /dev/null +++ b/app/Actions/SSL/DeleteSSL.php @@ -0,0 +1,14 @@ +site->server->webserver()->handler()->removeSSL($ssl); + $ssl->delete(); + } +} diff --git a/app/Actions/Script/CreateScript.php b/app/Actions/Script/CreateScript.php deleted file mode 100644 index a469a6c..0000000 --- a/app/Actions/Script/CreateScript.php +++ /dev/null @@ -1,41 +0,0 @@ -validateInputs($input); - - $script = new Script([ - 'user_id' => $creator->id, - 'name' => $input['name'], - 'content' => $input['content'], - ]); - $script->save(); - - return $script; - } - - /** - * @throws ValidationException - */ - private function validateInputs(array $input): void - { - $rules = [ - 'name' => 'required', - 'content' => 'required', - ]; - - Validator::make($input, $rules)->validateWithBag('createScript'); - } -} diff --git a/app/Actions/Script/GetScripts.php b/app/Actions/Script/GetScripts.php deleted file mode 100755 index 5aeac76..0000000 --- a/app/Actions/Script/GetScripts.php +++ /dev/null @@ -1,17 +0,0 @@ -scripts() - ->orderBy('id', 'desc') - ->paginate(6) - ->onEachSide(1); - } -} diff --git a/app/Actions/Script/UpdateScript.php b/app/Actions/Script/UpdateScript.php deleted file mode 100644 index 267ca35..0000000 --- a/app/Actions/Script/UpdateScript.php +++ /dev/null @@ -1,37 +0,0 @@ -validateInputs($input); - - $script->name = $input['name']; - $script->content = $input['content']; - $script->save(); - - return $script; - } - - /** - * @throws ValidationException - */ - private function validateInputs(array $input): void - { - $rules = [ - 'name' => 'required', - 'content' => 'required', - ]; - - Validator::make($input, $rules)->validateWithBag('updateScript'); - } -} diff --git a/app/Actions/Server/CheckConnection.php b/app/Actions/Server/CheckConnection.php new file mode 100644 index 0000000..444d5c1 --- /dev/null +++ b/app/Actions/Server/CheckConnection.php @@ -0,0 +1,30 @@ +status; + try { + $server->ssh()->connect(); + $server->refresh(); + if ($status == 'disconnected') { + $server->status = 'ready'; + $server->save(); + } + } catch (Throwable) { + $server->status = 'disconnected'; + $server->save(); + Notifier::send($server, new ServerDisconnected($server)); + } + + return $server; + } +} diff --git a/app/Actions/Server/CreateServer.php b/app/Actions/Server/CreateServer.php index c1aabdb..1dbf6c8 100755 --- a/app/Actions/Server/CreateServer.php +++ b/app/Actions/Server/CreateServer.php @@ -3,13 +3,19 @@ namespace App\Actions\Server; use App\Enums\FirewallRuleStatus; +use App\Enums\ServerProvider; +use App\Enums\ServerStatus; use App\Exceptions\ServerProviderError; -use App\Jobs\Installation\ContinueInstallation; +use App\Facades\Notifier; use App\Models\Server; use App\Models\User; +use App\Notifications\ServerInstallationFailed; +use App\Notifications\ServerInstallationSucceed; use App\ValidationRules\RestrictedIPAddressesRule; use Exception; +use Illuminate\Support\Facades\Bus; use Illuminate\Support\Facades\DB; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Validator; use Illuminate\Support\Str; use Illuminate\Validation\Rule; @@ -30,7 +36,7 @@ public function create(User $creator, array $input): Server 'user_id' => $creator->id, 'name' => $input['name'], 'ssh_user' => config('core.server_providers_default_user')[$input['provider']][$input['os']], - 'ip' => $input['ip'], + 'ip' => $input['ip'] ?? '', 'port' => $input['port'] ?? 22, 'os' => $input['os'], 'type' => $input['type'], @@ -71,14 +77,8 @@ public function create(User $creator, array $input): Server $server->type()->createServices($input); // install server - if ($server->provider == 'custom') { - $server->install(); - } else { - $server->progress_step = __('Installation will begin in 3 minutes!'); - $server->save(); - dispatch(new ContinueInstallation($server)) - ->delay(now()->addMinutes(2)); - } + $this->install($server); + DB::commit(); return $server; @@ -88,12 +88,44 @@ public function create(User $creator, array $input): Server if ($e instanceof ServerProviderError) { throw ValidationException::withMessages([ 'provider' => __('Provider Error: ').$e->getMessage(), - ])->errorBag('createServer'); + ]); } throw $e; } } + private function install(Server $server): void + { + $bus = Bus::chain([ + function () use ($server) { + if (! $server->provider()->isRunning()) { + sleep(2); + } + $server->type()->install(); + $server->update([ + 'status' => ServerStatus::READY, + ]); + Notifier::send($server, new ServerInstallationSucceed($server)); + }, + ])->catch(function (Throwable $e) use ($server) { + $server->update([ + 'status' => ServerStatus::INSTALLATION_FAILED, + ]); + Notifier::send($server, new ServerInstallationFailed($server)); + Log::error('server-installation-error', [ + 'error' => (string) $e, + ]); + }); + + if ($server->provider != ServerProvider::CUSTOM) { + $server->progress_step = 'waiting-for-provider'; + $server->save(); + $bus->delay(now()->addMinutes(3)); + } + + $bus->onConnection('ssh')->dispatch(); + } + /** * @throws ValidationException */ @@ -136,7 +168,7 @@ private function validateInputs(array $input): void */ private function validateType(Server $server, array $input): void { - Validator::make($input, $server->type()->createValidationRules($input)) + Validator::make($input, $server->type()->createRules($input)) ->validate(); } @@ -145,7 +177,7 @@ private function validateType(Server $server, array $input): void */ private function validateProvider(Server $server, array $input): void { - Validator::make($input, $server->provider()->createValidationRules($input)) + Validator::make($input, $server->provider()->createRules($input)) ->validate(); } diff --git a/app/Actions/Server/EditServer.php b/app/Actions/Server/EditServer.php index 85187fe..8e929de 100755 --- a/app/Actions/Server/EditServer.php +++ b/app/Actions/Server/EditServer.php @@ -35,7 +35,7 @@ public function edit(Server $server, array $input): Server $server->save(); if ($checkConnection) { - $server->checkConnection(); + return $server->checkConnection(); } return $server; diff --git a/app/Actions/Server/GetServers.php b/app/Actions/Server/GetServers.php deleted file mode 100755 index d7d33a3..0000000 --- a/app/Actions/Server/GetServers.php +++ /dev/null @@ -1,14 +0,0 @@ -latest()->get(); - } -} diff --git a/app/Actions/Server/RebootServer.php b/app/Actions/Server/RebootServer.php new file mode 100644 index 0000000..c026485 --- /dev/null +++ b/app/Actions/Server/RebootServer.php @@ -0,0 +1,23 @@ +os()->reboot(); + $server->status = ServerStatus::DISCONNECTED; + $server->save(); + } catch (Throwable) { + $server = $server->checkConnection(); + } + + return $server; + } +} diff --git a/app/Actions/ServerProvider/CreateServerProvider.php b/app/Actions/ServerProvider/CreateServerProvider.php index 750bed7..bdcb453 100644 --- a/app/Actions/ServerProvider/CreateServerProvider.php +++ b/app/Actions/ServerProvider/CreateServerProvider.php @@ -2,9 +2,9 @@ namespace App\Actions\ServerProvider; -use App\Contracts\ServerProvider as ServerProviderContract; use App\Models\ServerProvider; use App\Models\User; +use App\ServerProviders\ServerProvider as ServerProviderContract; use Exception; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\Rule; @@ -28,7 +28,7 @@ public function create(User $user, array $input): ServerProvider } catch (Exception) { throw ValidationException::withMessages([ 'provider' => [ - __("Couldn't connect to provider. Please check your credentials and try again later."), + sprintf("Couldn't connect to %s. Please check your credentials.", $input['provider']), ], ]); } diff --git a/app/Actions/ServerProvider/DeleteServerProvider.php b/app/Actions/ServerProvider/DeleteServerProvider.php new file mode 100644 index 0000000..873ddb0 --- /dev/null +++ b/app/Actions/ServerProvider/DeleteServerProvider.php @@ -0,0 +1,21 @@ +servers()->exists()) { + throw new Exception('This server provider is being used by a server.'); + } + + $serverProvider->delete(); + } +} diff --git a/app/Actions/Service/InstallPHPMyAdmin.php b/app/Actions/Service/InstallPHPMyAdmin.php deleted file mode 100644 index c71aa28..0000000 --- a/app/Actions/Service/InstallPHPMyAdmin.php +++ /dev/null @@ -1,60 +0,0 @@ -validate($input); - - $phpMyAdmin = $server->defaultService('phpmyadmin'); - if ($phpMyAdmin) { - throw ValidationException::withMessages([ - 'allowed_ip' => __('Already installed'), - ]); - } - $phpMyAdmin = new Service([ - 'server_id' => $server->id, - 'type' => 'phpmyadmin', - 'type_data' => [ - 'allowed_ip' => $input['allowed_ip'], - 'port' => $input['port'], - 'php' => $server->defaultService('php')->version, - ], - 'name' => 'phpmyadmin', - 'version' => '5.1.2', - 'status' => ServiceStatus::INSTALLING, - 'is_default' => 1, - ]); - $phpMyAdmin->save(); - $phpMyAdmin->install(); - - return $phpMyAdmin; - } - - /** - * @throws ValidationException - */ - private function validate(array $input): void - { - Validator::make($input, [ - 'allowed_ip' => 'required', - 'port' => [ - 'required', - 'numeric', - 'min:1', - 'max:65535', - ], - ])->validate(); - } -} diff --git a/app/Actions/Service/Manage.php b/app/Actions/Service/Manage.php new file mode 100644 index 0000000..18010a3 --- /dev/null +++ b/app/Actions/Service/Manage.php @@ -0,0 +1,84 @@ +status = ServiceStatus::STARTING; + $service->save(); + dispatch(function () use ($service) { + $status = $service->server->systemd()->start($service->unit); + if (str($status)->contains('Active: active')) { + $service->status = ServiceStatus::READY; + } else { + $service->status = ServiceStatus::FAILED; + } + $service->save(); + })->onConnection('ssh'); + } + + public function stop(Service $service): void + { + $service->status = ServiceStatus::STOPPING; + $service->save(); + dispatch(function () use ($service) { + $status = $service->server->systemd()->stop($service->unit); + if (str($status)->contains('Active: inactive')) { + $service->status = ServiceStatus::STOPPED; + } else { + $service->status = ServiceStatus::FAILED; + } + $service->save(); + })->onConnection('ssh'); + } + + public function restart(Service $service): void + { + $service->status = ServiceStatus::RESTARTING; + $service->save(); + dispatch(function () use ($service) { + $status = $service->server->systemd()->restart($service->unit); + if (str($status)->contains('Active: active')) { + $service->status = ServiceStatus::READY; + } else { + $service->status = ServiceStatus::FAILED; + } + $service->save(); + })->onConnection('ssh'); + } + + public function enable(Service $service): void + { + $service->status = ServiceStatus::ENABLING; + $service->save(); + dispatch(function () use ($service) { + $status = $service->server->systemd()->enable($service->unit); + if (str($status)->contains('Active: active')) { + $service->status = ServiceStatus::READY; + } else { + $service->status = ServiceStatus::FAILED; + } + $service->save(); + })->onConnection('ssh'); + } + + public function disable(Service $service): void + { + $service->status = ServiceStatus::DISABLING; + $service->save(); + dispatch(function () use ($service) { + $status = $service->server->systemd()->disable($service->unit); + if (str($status)->contains('Active: inactive')) { + $service->status = ServiceStatus::DISABLED; + } else { + $service->status = ServiceStatus::FAILED; + } + $service->save(); + })->onConnection('ssh'); + } +} diff --git a/app/Actions/Site/ChangePHPVersion.php b/app/Actions/Site/ChangePHPVersion.php deleted file mode 100755 index 94bc56a..0000000 --- a/app/Actions/Site/ChangePHPVersion.php +++ /dev/null @@ -1,30 +0,0 @@ -validate($site, $input); - - $site->changePHPVersion($input['php_version']); - } - - /** - * @throws ValidationException - */ - protected function validate(Site $site, array $input): void - { - Validator::make($input, [ - 'php_version' => 'required|in:'.implode(',', $site->server->installedPHPVersions()), - ])->validateWithBag('changePHPVersion'); - } -} diff --git a/app/Actions/Site/CreateRedirect.php b/app/Actions/Site/CreateRedirect.php deleted file mode 100644 index b685275..0000000 --- a/app/Actions/Site/CreateRedirect.php +++ /dev/null @@ -1,51 +0,0 @@ -validate($input); - - $redirect = new Redirect([ - 'site_id' => $site->id, - 'mode' => $input['mode'], - 'from' => $input['from'], - 'to' => $input['to'], - ]); - $redirect->save(); - $redirect->addToServer(); - } - - /** - * @throws ValidationException - */ - private function validate(array $input): void - { - $rules = [ - 'mode' => [ - 'required', - 'in:301,302', - ], - 'from' => [ - 'required', - ], - 'to' => [ - 'required', - 'url', - ], - ]; - - Validator::make($input, $rules)->validateWithBag('createRedirect'); - } -} diff --git a/app/Actions/Site/CreateSite.php b/app/Actions/Site/CreateSite.php index 21bfbd8..d1aca35 100755 --- a/app/Actions/Site/CreateSite.php +++ b/app/Actions/Site/CreateSite.php @@ -4,8 +4,11 @@ use App\Enums\SiteStatus; use App\Exceptions\SourceControlIsNotConnected; +use App\Facades\Notifier; use App\Models\Server; use App\Models\Site; +use App\Notifications\SiteInstallationFailed; +use App\Notifications\SiteInstallationSucceed; use App\ValidationRules\DomainRule; use Exception; use Illuminate\Support\Facades\DB; @@ -30,11 +33,11 @@ public function create(Server $server, array $input): Site 'type' => $input['type'], 'domain' => $input['domain'], 'aliases' => isset($input['alias']) ? [$input['alias']] : [], - 'path' => '/home/'.$server->ssh_user.'/'.$input['domain'], + 'path' => '/home/'.$server->getSshUser().'/'.$input['domain'], 'status' => SiteStatus::INSTALLING, ]); - // fields based on type + // fields based on the type $site->fill($site->type()->createFields($input)); // check has access to repository @@ -63,8 +66,19 @@ public function create(Server $server, array $input): Site 'content' => '', ]); - // install server - $site->install(); + // install site + dispatch(function () use ($site) { + $site->type()->install(); + $site->update([ + 'status' => SiteStatus::READY, + 'progress' => 100, + ]); + Notifier::send($site, new SiteInstallationSucceed($site)); + })->catch(function () use ($site) { + $site->status = SiteStatus::INSTALLATION_FAILED; + $site->save(); + Notifier::send($site, new SiteInstallationFailed($site)); + })->onConnection('ssh'); DB::commit(); @@ -105,7 +119,7 @@ private function validateInputs(Server $server, array $input): void */ private function validateType(Site $site, array $input): void { - $rules = $site->type()->createValidationRules($input); + $rules = $site->type()->createRules($input); Validator::make($input, $rules)->validate(); } diff --git a/app/Actions/Site/DeleteSite.php b/app/Actions/Site/DeleteSite.php new file mode 100644 index 0000000..fa23947 --- /dev/null +++ b/app/Actions/Site/DeleteSite.php @@ -0,0 +1,14 @@ +server->webserver()->handler()->deleteSite($site); + $site->delete(); + } +} diff --git a/app/Actions/Site/Deploy.php b/app/Actions/Site/Deploy.php new file mode 100644 index 0000000..fccf265 --- /dev/null +++ b/app/Actions/Site/Deploy.php @@ -0,0 +1,51 @@ +sourceControl()) { + $site->sourceControl()->getRepo($site->repository); + } + + if (! $site->deploymentScript?->content) { + throw new DeploymentScriptIsEmptyException(); + } + + $deployment = new Deployment([ + 'site_id' => $site->id, + 'deployment_script_id' => $site->deploymentScript->id, + 'status' => DeploymentStatus::DEPLOYING, + ]); + $lastCommit = $site->sourceControl()->provider()->getLastCommit($site->repository, $site->branch); + if ($lastCommit) { + $deployment->commit_id = $lastCommit['commit_id']; + $deployment->commit_data = $lastCommit['commit_data']; + } + $deployment->save(); + + dispatch(function () use ($site, $deployment) { + $log = $site->server->os()->runScript($site->path, $site->deploymentScript->content, $site->id); + $deployment->status = DeploymentStatus::FINISHED; + $deployment->log_id = $log->id; + $deployment->save(); + })->catch(function () use ($deployment) { + $deployment->status = DeploymentStatus::FAILED; + $deployment->save(); + })->onConnection('ssh'); + + return $deployment; + } +} diff --git a/app/Actions/Site/EditSite.php b/app/Actions/Site/EditSite.php deleted file mode 100755 index 02d678d..0000000 --- a/app/Actions/Site/EditSite.php +++ /dev/null @@ -1,41 +0,0 @@ -validateType($site, $input); - - // set type data - $site->type_data = $site->type()->data($input); - - // save - $site->port = $input['port'] ?? null; - $site->save(); - - // edit - $site->type()->edit(); - - return $site; - } - - /** - * @throws ValidationException - */ - private function validateType(Site $site, array $input): void - { - $rules = $site->type()->editValidationRules($input); - - Validator::make($input, $rules)->validateWithBag('editSite'); - } -} diff --git a/app/Actions/Site/GetSites.php b/app/Actions/Site/GetSites.php deleted file mode 100755 index 6a69214..0000000 --- a/app/Actions/Site/GetSites.php +++ /dev/null @@ -1,14 +0,0 @@ -sites()->orderBy('id', 'desc')->get(); - } -} diff --git a/app/Actions/Site/UpdateBranch.php b/app/Actions/Site/UpdateBranch.php index 7cc5b0d..9996cd3 100755 --- a/app/Actions/Site/UpdateBranch.php +++ b/app/Actions/Site/UpdateBranch.php @@ -3,6 +3,7 @@ namespace App\Actions\Site; use App\Models\Site; +use App\SSH\Git\Git; use Illuminate\Support\Facades\Validator; use Illuminate\Validation\ValidationException; @@ -14,8 +15,9 @@ class UpdateBranch public function update(Site $site, array $input): void { $this->validate($input); - - $site->updateBranch($input['branch']); + $site->branch = $input['branch']; + app(Git::class)->checkout($site); + $site->save(); } /** @@ -25,6 +27,6 @@ protected function validate(array $input): void { Validator::make($input, [ 'branch' => 'required', - ])->validateWithBag('updateBranch'); + ]); } } diff --git a/app/Actions/Site/UpdateDeploymentScript.php b/app/Actions/Site/UpdateDeploymentScript.php index eaf207b..4410353 100755 --- a/app/Actions/Site/UpdateDeploymentScript.php +++ b/app/Actions/Site/UpdateDeploymentScript.php @@ -27,6 +27,6 @@ protected function validate(array $input): void { Validator::make($input, [ 'script' => 'required', - ])->validateWithBag('updateDeploymentScript'); + ]); } } diff --git a/app/Actions/Site/UpdateEnv.php b/app/Actions/Site/UpdateEnv.php index 9df9566..ed8c078 100644 --- a/app/Actions/Site/UpdateEnv.php +++ b/app/Actions/Site/UpdateEnv.php @@ -8,11 +8,9 @@ class UpdateEnv { public function update(Site $site, array $input): void { - $typeData = $site->type_data; - $typeData['env'] = $input['env']; - $site->type_data = $typeData; - $site->save(); - - $site->deployEnv(); + $site->server->os()->editFile( + $site->path.'/.env', + $input['env'] + ); } } diff --git a/app/Actions/Site/UpdateSourceControl.php b/app/Actions/Site/UpdateSourceControl.php deleted file mode 100755 index cd06da3..0000000 --- a/app/Actions/Site/UpdateSourceControl.php +++ /dev/null @@ -1,35 +0,0 @@ -validate($input); - - $site->source_control_id = $input['source_control']; - $site->save(); - } - - /** - * @throws ValidationException - */ - protected function validate(array $input): void - { - Validator::make($input, [ - 'source_control' => [ - 'required', - Rule::exists('source_controls', 'id'), - ], - ])->validate(); - } -} diff --git a/app/Actions/SourceControl/ConnectSourceControl.php b/app/Actions/SourceControl/ConnectSourceControl.php index 4c2fae4..da902db 100644 --- a/app/Actions/SourceControl/ConnectSourceControl.php +++ b/app/Actions/SourceControl/ConnectSourceControl.php @@ -13,13 +13,17 @@ class ConnectSourceControl public function connect(array $input): void { $this->validate($input); + $sourceControl = new SourceControl([ 'provider' => $input['provider'], 'profile' => $input['name'], - 'access_token' => $input['token'], 'url' => Arr::has($input, 'url') ? $input['url'] : null, ]); + $this->validateProvider($sourceControl, $input); + + $sourceControl->provider_data = $sourceControl->provider()->createData($input); + if (! $sourceControl->provider()->connect()) { throw ValidationException::withMessages([ 'token' => __('Cannot connect to :provider or invalid token!', ['provider' => $sourceControl->provider] @@ -38,20 +42,20 @@ private function validate(array $input): void $rules = [ 'provider' => [ 'required', - Rule::in(\App\Enums\SourceControl::getValues()), + Rule::in(config('core.source_control_providers')), ], 'name' => [ 'required', ], - 'token' => [ - 'required', - ], - 'url' => [ - 'nullable', - 'url:http,https', - 'ends_with:/', - ], ]; Validator::make($input, $rules)->validate(); } + + /** + * @throws ValidationException + */ + private function validateProvider(SourceControl $sourceControl, array $input): void + { + Validator::make($input, $sourceControl->provider()->createRules($input))->validate(); + } } diff --git a/app/Actions/SourceControl/DeleteSourceControl.php b/app/Actions/SourceControl/DeleteSourceControl.php new file mode 100644 index 0000000..e084056 --- /dev/null +++ b/app/Actions/SourceControl/DeleteSourceControl.php @@ -0,0 +1,17 @@ +sites()->exists()) { + throw new \Exception('This source control is being used by a site.'); + } + + $sourceControl->delete(); + } +} diff --git a/app/Actions/SshKey/DeleteKeyFromServer.php b/app/Actions/SshKey/DeleteKeyFromServer.php new file mode 100644 index 0000000..2d28173 --- /dev/null +++ b/app/Actions/SshKey/DeleteKeyFromServer.php @@ -0,0 +1,19 @@ +servers()->updateExistingPivot($server->id, [ + 'status' => SshKeyStatus::DELETING, + ]); + $server->os()->deleteSSHKey($sshKey->public_key); + $server->sshKeys()->detach($sshKey); + } +} diff --git a/app/Actions/SshKey/DeployKeyToServer.php b/app/Actions/SshKey/DeployKeyToServer.php new file mode 100644 index 0000000..4327e4c --- /dev/null +++ b/app/Actions/SshKey/DeployKeyToServer.php @@ -0,0 +1,38 @@ +validate($user, $input); + + /** @var SshKey $sshKey */ + $sshKey = SshKey::findOrFail($input['key_id']); + $server->sshKeys()->attach($sshKey, [ + 'status' => SshKeyStatus::ADDING, + ]); + $server->os()->deploySSHKey($sshKey->public_key); + $sshKey->servers()->updateExistingPivot($server->id, [ + 'status' => SshKeyStatus::ADDED, + ]); + } + + private function validate(User $user, array $input): void + { + Validator::make($input, [ + 'key_id' => [ + 'required', + Rule::exists('ssh_keys', 'id')->where('user_id', $user->id), + ], + ])->validate(); + } +} diff --git a/app/Actions/StorageProvider/CreateStorageProvider.php b/app/Actions/StorageProvider/CreateStorageProvider.php index 6c8520b..0f8ba73 100644 --- a/app/Actions/StorageProvider/CreateStorageProvider.php +++ b/app/Actions/StorageProvider/CreateStorageProvider.php @@ -27,11 +27,18 @@ public function create(User $user, array $input): void $storageProvider->credentials = $storageProvider->provider()->credentialData($input); - if (! $storageProvider->provider()->connect()) { + try { + if (! $storageProvider->provider()->connect()) { + throw ValidationException::withMessages([ + 'provider' => __("Couldn't connect to the provider"), + ]); + } + } catch (\Throwable $e) { throw ValidationException::withMessages([ - 'provider' => __("Couldn't connect to the provider"), + 'provider' => $e->getMessage(), ]); } + $storageProvider->save(); } diff --git a/app/Actions/StorageProvider/DeleteStorageProvider.php b/app/Actions/StorageProvider/DeleteStorageProvider.php new file mode 100644 index 0000000..d8cd5cf --- /dev/null +++ b/app/Actions/StorageProvider/DeleteStorageProvider.php @@ -0,0 +1,21 @@ +backups()->exists()) { + throw new Exception('This storage provider is being used by a backup.'); + } + + $storageProvider->delete(); + } +} diff --git a/app/Console/Commands/MigrateFromMysqlToSqlite.php b/app/Console/Commands/MigrateFromMysqlToSqlite.php new file mode 100644 index 0000000..a6f3395 --- /dev/null +++ b/app/Console/Commands/MigrateFromMysqlToSqlite.php @@ -0,0 +1,74 @@ +info('Migrating from Mysql to SQLite...'); + + File::exists(storage_path('database.sqlite')) + ? File::delete(storage_path('database.sqlite')) + : null; + + File::put(storage_path('database.sqlite'), ''); + + config(['database.default' => 'sqlite']); + + $this->call('migrate', ['--force' => true]); + + $this->migrateModel(\App\Models\Backup::class); + $this->migrateModel(\App\Models\BackupFile::class); + $this->migrateModel(\App\Models\CronJob::class); + $this->migrateModel(\App\Models\Database::class); + $this->migrateModel(\App\Models\DatabaseUser::class); + $this->migrateModel(\App\Models\Deployment::class); + $this->migrateModel(\App\Models\DeploymentScript::class); + $this->migrateModel(\App\Models\FirewallRule::class); + $this->migrateModel(\App\Models\GitHook::class); + $this->migrateModel(\App\Models\NotificationChannel::class); + $this->migrateModel(\App\Models\Project::class); + $this->migrateModel(\App\Models\Queue::class); + $this->migrateModel(\App\Models\Server::class); + $this->migrateModel(\App\Models\ServerLog::class); + $this->migrateModel(\App\Models\ServerProvider::class); + $this->migrateModel(\App\Models\Service::class); + $this->migrateModel(\App\Models\Site::class); + $this->migrateModel(\App\Models\SourceControl::class); + $this->migrateModel(\App\Models\SshKey::class); + $this->migrateModel(\App\Models\Ssl::class); + $this->migrateModel(\App\Models\StorageProvider::class); + $this->migrateModel(\App\Models\User::class); + + $env = File::get(base_path('.env')); + $env = str_replace('DB_CONNECTION=mysql', 'DB_CONNECTION=sqlite', $env); + $env = str_replace('DB_DATABASE=vito', '', $env); + File::put(base_path('.env'), $env); + + $this->info('Migrated from Mysql to SQLite'); + } + + private function migrateModel(string $model): void + { + $this->info("Migrating model: {$model}"); + + config(['database.default' => 'mysql']); + + $rows = $model::where('id', '>', 0)->get(); + + foreach ($rows as $row) { + DB::connection('sqlite')->table($row->getTable())->insert($row->getAttributes()); + } + + $this->info("Migrated model: {$model}"); + } +} diff --git a/app/Console/Commands/RunBackup.php b/app/Console/Commands/RunBackup.php deleted file mode 100644 index 5d8df57..0000000 --- a/app/Console/Commands/RunBackup.php +++ /dev/null @@ -1,49 +0,0 @@ -where('interval', $this->argument('interval')) - ->where('status', 'running') - ->chunk(100, function ($backups) { - /** @var Backup $backup */ - foreach ($backups as $backup) { - $backup->run(); - } - }); - } -} diff --git a/app/Console/Commands/RunBackupCommand.php b/app/Console/Commands/RunBackupCommand.php new file mode 100644 index 0000000..c6d3dfe --- /dev/null +++ b/app/Console/Commands/RunBackupCommand.php @@ -0,0 +1,33 @@ +where('interval', $this->argument('interval')) + ->where('status', BackupStatus::RUNNING) + ->chunk(100, function ($backups) use (&$total) { + /** @var Backup $backup */ + foreach ($backups as $backup) { + app(RunBackup::class)->run($backup); + $total++; + } + }); + + $this->info("{$total} backups started"); + } +} diff --git a/app/Contracts/SSHCommand.php b/app/Contracts/SSHCommand.php deleted file mode 100755 index f6c36a6..0000000 --- a/app/Contracts/SSHCommand.php +++ /dev/null @@ -1,10 +0,0 @@ -header('HX-Redirect', $redirect); + + return $this; + } + + public function back(): self + { + return $this->redirect(back()->getTargetUrl()); + } + + public function refresh(): self + { + $this->header('HX-Refresh', true); + + return $this; + } + + public function location(string $location): self + { + $this->header('HX-Location', $location); + + return $this; + } +} diff --git a/app/Helpers/Notifier.php b/app/Helpers/Notifier.php index 6373b44..95f3df7 100644 --- a/app/Helpers/Notifier.php +++ b/app/Helpers/Notifier.php @@ -2,8 +2,8 @@ namespace App\Helpers; -use App\Contracts\Notification; use App\Models\NotificationChannel; +use App\Notifications\NotificationInterface; class Notifier { @@ -12,7 +12,7 @@ class Notifier * For example, If it was a server then we will send the channels specified by that server * For now, we will send all channels. */ - public function send(object $notifiable, Notification $notification): void + public function send(object $notifiable, NotificationInterface $notification): void { NotificationChannel::notifyAll($notification); } diff --git a/app/Helpers/SSH.php b/app/Helpers/SSH.php index 07c0892..d2d91b4 100755 --- a/app/Helpers/SSH.php +++ b/app/Helpers/SSH.php @@ -2,8 +2,9 @@ namespace App\Helpers; -use App\Contracts\SSHCommand; use App\Exceptions\SSHAuthenticationError; +use App\Exceptions\SSHCommandError; +use App\Exceptions\SSHConnectionError; use App\Models\Server; use App\Models\ServerLog; use Exception; @@ -21,7 +22,7 @@ class SSH public ?ServerLog $log; - protected SSH2|SFTP|null $connection; + protected SSH2|SFTP|null $connection = null; protected ?string $user; @@ -37,8 +38,8 @@ public function init(Server $server, ?string $asUser = null): self $this->log = null; $this->asUser = null; $this->server = $server->refresh(); - $this->user = $server->ssh_user; - if ($asUser && $asUser != $server->ssh_user) { + $this->user = $server->getSshUser(); + if ($asUser && $asUser != $server->getSshUser()) { $this->user = $asUser; $this->asUser = $asUser; } @@ -49,7 +50,7 @@ public function init(Server $server, ?string $asUser = null): self return $this; } - public function setLog(string $logType, $siteId = null): void + public function setLog(string $logType, $siteId = null): self { $this->log = $this->server->logs()->create([ 'site_id' => $siteId, @@ -57,6 +58,8 @@ public function setLog(string $logType, $siteId = null): void 'type' => $logType, 'disk' => config('core.logs_disk'), ]); + + return $this; } /** @@ -64,6 +67,7 @@ public function setLog(string $logType, $siteId = null): void */ public function connect(bool $sftp = false): void { + // If the IP is an IPv6 address, we need to wrap it in square brackets $ip = $this->server->ip; if (str($ip)->contains(':')) { $ip = '['.$ip.']'; @@ -84,14 +88,15 @@ public function connect(bool $sftp = false): void Log::error('Error connecting', [ 'msg' => $e->getMessage(), ]); - throw $e; + throw new SSHConnectionError($e->getMessage()); } } /** - * @throws Throwable + * @throws SSHCommandError + * @throws SSHConnectionError */ - public function exec(string|array|SSHCommand $commands, string $log = '', ?int $siteId = null): string + public function exec(string|array $commands, string $log = '', ?int $siteId = null): string { if ($log) { $this->setLog($log, $siteId); @@ -99,20 +104,28 @@ public function exec(string|array|SSHCommand $commands, string $log = '', ?int $ $this->log = null; } - if (! $this->connection) { - $this->connect(); + try { + if (! $this->connection) { + $this->connect(); + } + } catch (Throwable $e) { + throw new SSHConnectionError($e->getMessage()); } if (! is_array($commands)) { $commands = [$commands]; } - $result = ''; - foreach ($commands as $command) { - $result .= $this->executeCommand($command); - } + try { + $result = ''; + foreach ($commands as $command) { + $result .= $this->executeCommand($command); + } - return $result; + return $result; + } catch (Throwable $e) { + throw new SSHCommandError($e->getMessage()); + } } /** @@ -131,19 +144,15 @@ public function upload(string $local, string $remote): void /** * @throws Exception */ - protected function executeCommand(string|SSHCommand $command): string + protected function executeCommand(string $command): string { - if ($command instanceof SSHCommand) { - $commandContent = $command->content(); - } else { - $commandContent = $command; - } - if ($this->asUser) { - $commandContent = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($commandContent).'"'; + $command = 'sudo su - '.$this->asUser.' -c '.'"'.addslashes($command).'"'; } - $output = $this->connection->exec($commandContent); + $this->connection->setTimeout(0); + + $output = $this->connection->exec($command); $this->log?->write($output); @@ -164,4 +173,12 @@ public function disconnect(): void $this->connection = null; } } + + /** + * @throws Exception + */ + public function __destruct() + { + $this->disconnect(); + } } diff --git a/app/Helpers/Toast.php b/app/Helpers/Toast.php index 18d8ba4..c15fa70 100644 --- a/app/Helpers/Toast.php +++ b/app/Helpers/Toast.php @@ -2,14 +2,8 @@ namespace App\Helpers; -use Livewire\Component; - class Toast { - public function __construct(public Component $component) - { - } - public function success(string $message): void { $this->toast('success', $message); @@ -32,6 +26,7 @@ public function info(string $message): void private function toast(string $type, string $message): void { - $this->component->dispatch('toast', type: $type, message: $message); + session()->flash('toast.type', $type); + session()->flash('toast.message', $message); } } diff --git a/app/Http/Controllers/ApplicationController.php b/app/Http/Controllers/ApplicationController.php new file mode 100644 index 0000000..bb6ac01 --- /dev/null +++ b/app/Http/Controllers/ApplicationController.php @@ -0,0 +1,108 @@ +run($site); + + Toast::success('Deployment started!'); + } catch (SourceControlIsNotConnected $e) { + Toast::error($e->getMessage()); + + return htmx()->redirect(route('source-controls')); + } catch (DeploymentScriptIsEmptyException) { + Toast::error('Deployment script is empty!'); + } + + return htmx()->back(); + } + + public function showDeploymentLog(Server $server, Site $site, Deployment $deployment): RedirectResponse + { + return back()->with('content', $deployment->log?->getContent()); + } + + public function updateDeploymentScript(Server $server, Site $site, Request $request): RedirectResponse + { + app(UpdateDeploymentScript::class)->update($site, $request->input()); + + Toast::success('Deployment script updated!'); + + return back(); + } + + public function updateBranch(Server $server, Site $site, Request $request): RedirectResponse + { + app(UpdateBranch::class)->update($site, $request->input()); + + Toast::success('Branch updated!'); + + return back(); + } + + public function getEnv(Server $server, Site $site): RedirectResponse + { + return back()->with('env', $site->getEnv()); + } + + public function updateEnv(Server $server, Site $site, Request $request): RedirectResponse + { + app(UpdateEnv::class)->update($site, $request->input()); + + Toast::success('Env updated!'); + + return back(); + } + + public function enableAutoDeployment(Server $server, Site $site): HtmxResponse + { + if (! $site->isAutoDeployment()) { + try { + $site->enableAutoDeployment(); + + $site->refresh(); + + Toast::success('Auto deployment has been enabled.'); + } catch (SourceControlIsNotConnected) { + Toast::error('Source control is not connected. Check site\'s settings.'); + } + } + + return htmx()->back(); + } + + public function disableAutoDeployment(Server $server, Site $site): HtmxResponse + { + if ($site->isAutoDeployment()) { + try { + $site->disableAutoDeployment(); + + $site->refresh(); + + Toast::success('Auto deployment has been disabled.'); + } catch (SourceControlIsNotConnected) { + Toast::error('Source control is not connected. Check site\'s settings.'); + } + } + + return htmx()->back(); + } +} diff --git a/app/Http/Controllers/CronjobController.php b/app/Http/Controllers/CronjobController.php index f9b4b57..2c55c3c 100644 --- a/app/Http/Controllers/CronjobController.php +++ b/app/Http/Controllers/CronjobController.php @@ -2,14 +2,41 @@ namespace App\Http\Controllers; +use App\Actions\CronJob\CreateCronJob; +use App\Actions\CronJob\DeleteCronJob; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; +use App\Models\CronJob; use App\Models\Server; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class CronjobController extends Controller { - public function index(Server $server) + public function index(Server $server): View { return view('cronjobs.index', [ 'server' => $server, + 'cronjobs' => $server->cronJobs, ]); } + + public function store(Server $server, Request $request): HtmxResponse + { + app(CreateCronJob::class)->create($server, $request->input()); + + Toast::success('Cronjob created successfully.'); + + return htmx()->back(); + } + + public function destroy(Server $server, CronJob $cronJob): RedirectResponse + { + app(DeleteCronJob::class)->delete($server, $cronJob); + + Toast::success('Cronjob deleted successfully.'); + + return back(); + } } diff --git a/app/Http/Controllers/DaemonController.php b/app/Http/Controllers/DaemonController.php deleted file mode 100644 index 933d439..0000000 --- a/app/Http/Controllers/DaemonController.php +++ /dev/null @@ -1,15 +0,0 @@ - $server, - ]); - } -} diff --git a/app/Http/Controllers/DatabaseBackupController.php b/app/Http/Controllers/DatabaseBackupController.php new file mode 100644 index 0000000..7ab6367 --- /dev/null +++ b/app/Http/Controllers/DatabaseBackupController.php @@ -0,0 +1,73 @@ + $server, + 'databases' => $server->databases, + 'backup' => $backup, + 'files' => $backup->files()->orderByDesc('id')->simplePaginate(10), + ]); + } + + public function run(Server $server, Backup $backup): RedirectResponse + { + app(RunBackup::class)->run($backup); + + Toast::success('Backup is running.'); + + return back(); + } + + public function store(Server $server, Request $request): HtmxResponse + { + app(CreateBackup::class)->create('database', $server, $request->input()); + + Toast::success('Backup created successfully.'); + + return htmx()->back(); + } + + public function destroy(Server $server, Backup $backup): RedirectResponse + { + $backup->delete(); + + Toast::success('Backup deleted successfully.'); + + return back(); + } + + public function restore(Server $server, Backup $backup, BackupFile $backupFile, Request $request): HtmxResponse + { + app(RestoreBackup::class)->restore($backupFile, $request->input()); + + Toast::success('Backup restored successfully.'); + + return htmx()->back(); + } + + public function destroyFile(Server $server, Backup $backup, BackupFile $backupFile): RedirectResponse + { + $backupFile->delete(); + + Toast::success('Backup file deleted successfully.'); + + return back(); + } +} diff --git a/app/Http/Controllers/DatabaseController.php b/app/Http/Controllers/DatabaseController.php index e00818f..dafd84f 100644 --- a/app/Http/Controllers/DatabaseController.php +++ b/app/Http/Controllers/DatabaseController.php @@ -2,9 +2,16 @@ namespace App\Http\Controllers; -use App\Models\Backup; +use App\Actions\Database\CreateDatabase; +use App\Actions\Database\CreateDatabaseUser; +use App\Actions\Database\DeleteDatabase; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; +use App\Models\Database; use App\Models\Server; use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class DatabaseController extends Controller { @@ -12,14 +19,31 @@ public function index(Server $server): View { return view('databases.index', [ 'server' => $server, + 'databases' => $server->databases, + 'databaseUsers' => $server->databaseUsers, + 'backups' => $server->backups, ]); } - public function backups(Server $server, Backup $backup): View + public function store(Server $server, Request $request): HtmxResponse { - return view('databases.backups', [ - 'server' => $server, - 'backup' => $backup, - ]); + $database = app(CreateDatabase::class)->create($server, $request->input()); + + if ($request->input('user')) { + app(CreateDatabaseUser::class)->create($server, $request->input(), [$database->name]); + } + + Toast::success('Database created successfully.'); + + return htmx()->back(); + } + + public function destroy(Server $server, Database $database): RedirectResponse + { + app(DeleteDatabase::class)->delete($server, $database); + + Toast::success('Database deleted successfully.'); + + return back(); } } diff --git a/app/Http/Controllers/DatabaseUserController.php b/app/Http/Controllers/DatabaseUserController.php new file mode 100644 index 0000000..945a908 --- /dev/null +++ b/app/Http/Controllers/DatabaseUserController.php @@ -0,0 +1,54 @@ +create($server, $request->input()); + + if ($request->input('user')) { + app(CreateDatabaseUser::class)->create($server, $request->input(), [$database->name]); + } + + Toast::success('User created successfully.'); + + return htmx()->back(); + } + + public function destroy(Server $server, DatabaseUser $databaseUser): RedirectResponse + { + app(DeleteDatabaseUser::class)->delete($server, $databaseUser); + + Toast::success('User deleted successfully.'); + + return back(); + } + + public function password(Server $server, DatabaseUser $databaseUser): RedirectResponse + { + return back()->with([ + 'password' => $databaseUser->password, + ]); + } + + public function link(Server $server, DatabaseUser $databaseUser, Request $request): HtmxResponse + { + app(LinkUser::class)->link($databaseUser, $request->input()); + + Toast::success('Database linked successfully.'); + + return htmx()->back(); + } +} diff --git a/app/Http/Controllers/FirewallController.php b/app/Http/Controllers/FirewallController.php index 4afe79f..218bb85 100644 --- a/app/Http/Controllers/FirewallController.php +++ b/app/Http/Controllers/FirewallController.php @@ -2,14 +2,41 @@ namespace App\Http\Controllers; +use App\Actions\FirewallRule\CreateRule; +use App\Actions\FirewallRule\DeleteRule; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; +use App\Models\FirewallRule; use App\Models\Server; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class FirewallController extends Controller { - public function index(Server $server) + public function index(Server $server): View { return view('firewall.index', [ 'server' => $server, + 'rules' => $server->firewallRules, ]); } + + public function store(Server $server, Request $request): HtmxResponse + { + app(CreateRule::class)->create($server, $request->input()); + + Toast::success('Firewall rule created!'); + + return htmx()->back(); + } + + public function destroy(Server $server, FirewallRule $firewallRule): RedirectResponse + { + app(DeleteRule::class)->delete($server, $firewallRule); + + Toast::success('Firewall rule deleted!'); + + return back(); + } } diff --git a/app/Http/Controllers/GitHookController.php b/app/Http/Controllers/GitHookController.php index 493da27..816a636 100644 --- a/app/Http/Controllers/GitHookController.php +++ b/app/Http/Controllers/GitHookController.php @@ -2,9 +2,11 @@ namespace App\Http\Controllers; +use App\Actions\Site\Deploy; use App\Exceptions\SourceControlIsNotConnected; use App\Facades\Notifier; use App\Models\GitHook; +use App\Models\ServerLog; use App\Notifications\SourceControlDisconnected; use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; @@ -26,10 +28,16 @@ public function __invoke(Request $request) foreach ($gitHook->actions as $action) { if ($action == 'deploy') { try { - $gitHook->site->deploy(); + app(Deploy::class)->run($gitHook->site); } catch (SourceControlIsNotConnected) { Notifier::send($gitHook->sourceControl, new SourceControlDisconnected($gitHook->sourceControl)); } catch (Throwable $e) { + ServerLog::log( + $gitHook->site->server, + 'deploy-failed', + $e->getMessage(), + $gitHook->site + ); Log::error('git-hook-exception', (array) $e); } } diff --git a/app/Http/Controllers/PHPController.php b/app/Http/Controllers/PHPController.php index b9850e3..aa016b8 100644 --- a/app/Http/Controllers/PHPController.php +++ b/app/Http/Controllers/PHPController.php @@ -2,14 +2,84 @@ namespace App\Http\Controllers; +use App\Actions\PHP\ChangeDefaultCli; +use App\Actions\PHP\GetPHPIni; +use App\Actions\PHP\InstallNewPHP; +use App\Actions\PHP\InstallPHPExtension; +use App\Actions\PHP\UninstallPHP; +use App\Actions\PHP\UpdatePHPIni; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; use App\Models\Server; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Illuminate\Validation\ValidationException; class PHPController extends Controller { - public function index(Server $server) + public function index(Server $server): View { return view('php.index', [ 'server' => $server, + 'phps' => $server->services()->where('type', 'php')->get(), + 'defaultPHP' => $server->defaultService('php'), ]); } + + public function install(Server $server, Request $request): HtmxResponse + { + try { + app(InstallNewPHP::class)->install($server, $request->input()); + + Toast::success('PHP is being installed!'); + } catch (ValidationException $e) { + Toast::error($e->getMessage()); + } + + return htmx()->back(); + } + + public function installExtension(Server $server, Request $request): HtmxResponse + { + app(InstallPHPExtension::class)->install($server, $request->input()); + + Toast::success('PHP extension is being installed! Check the logs'); + + return htmx()->back(); + } + + public function defaultCli(Server $server, Request $request): HtmxResponse + { + app(ChangeDefaultCli::class)->change($server, $request->input()); + + Toast::success('Default PHP CLI is being changed!'); + + return htmx()->back(); + } + + public function getIni(Server $server, Request $request): RedirectResponse + { + $ini = app(GetPHPIni::class)->getIni($server, $request->input()); + + return back()->with('ini', $ini); + } + + public function updateIni(Server $server, Request $request): RedirectResponse + { + app(UpdatePHPIni::class)->update($server, $request->input()); + + Toast::success('PHP ini updated!'); + + return back(); + } + + public function uninstall(Server $server, Request $request): RedirectResponse + { + app(UninstallPHP::class)->uninstall($server, $request->input()); + + Toast::success('PHP is being uninstalled!'); + + return back(); + } } diff --git a/app/Http/Controllers/ProjectController.php b/app/Http/Controllers/ProjectController.php deleted file mode 100644 index ed47101..0000000 --- a/app/Http/Controllers/ProjectController.php +++ /dev/null @@ -1,29 +0,0 @@ -user(); - - /** @var Project $project */ - $project = $user->projects()->findOrFail($projectId); - - $user->current_project_id = $project->id; - $user->save(); - - return redirect()->route('servers'); - } -} diff --git a/app/Http/Controllers/QueueController.php b/app/Http/Controllers/QueueController.php new file mode 100644 index 0000000..3009aee --- /dev/null +++ b/app/Http/Controllers/QueueController.php @@ -0,0 +1,60 @@ + $server, + 'site' => $site, + 'queues' => $site->queues, + ]); + } + + public function store(Server $server, Site $site, Request $request): HtmxResponse + { + app(CreateQueue::class)->create($site, $request->input()); + + Toast::success('Queue is being created.'); + + return htmx()->back(); + } + + public function action(Server $server, Site $site, Queue $queue, string $action): HtmxResponse + { + app(ManageQueue::class)->{$action}($queue); + + Toast::success('Queue is about to '.$action); + + return htmx()->back(); + } + + public function destroy(Server $server, Site $site, Queue $queue): RedirectResponse + { + app(DeleteQueue::class)->delete($queue); + + Toast::success('Queue is being deleted.'); + + return back(); + } + + public function logs(Server $server, Site $site, Queue $queue): RedirectResponse + { + return back()->with('content', app(GetQueueLogs::class)->getLogs($queue)); + } +} diff --git a/app/Http/Controllers/SSHKeyController.php b/app/Http/Controllers/SSHKeyController.php index a463945..4d7f0f5 100644 --- a/app/Http/Controllers/SSHKeyController.php +++ b/app/Http/Controllers/SSHKeyController.php @@ -2,14 +2,59 @@ namespace App\Http\Controllers; +use App\Actions\SshKey\CreateSshKey; +use App\Actions\SshKey\DeleteKeyFromServer; +use App\Actions\SshKey\DeployKeyToServer; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; use App\Models\Server; +use App\Models\SshKey; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class SSHKeyController extends Controller { - public function index(Server $server) + public function index(Server $server): View { return view('server-ssh-keys.index', [ 'server' => $server, + 'keys' => $server->sshKeys, ]); } + + public function store(Server $server, Request $request): HtmxResponse + { + /** @var \App\Models\SshKey $key */ + $key = app(CreateSshKey::class)->create( + $request->user(), + $request->input() + ); + + $request->merge(['key_id' => $key->id]); + + return $this->deploy($server, $request); + } + + public function destroy(Server $server, SshKey $sshKey): RedirectResponse + { + app(DeleteKeyFromServer::class)->delete($server, $sshKey); + + Toast::success('SSH Key has been deleted.'); + + return back(); + } + + public function deploy(Server $server, Request $request): HtmxResponse + { + app(DeployKeyToServer::class)->deploy( + $request->user(), + $server, + $request->input() + ); + + Toast::success('SSH Key has been deployed to the server.'); + + return htmx()->back(); + } } diff --git a/app/Http/Controllers/SSLController.php b/app/Http/Controllers/SSLController.php new file mode 100644 index 0000000..18d7d52 --- /dev/null +++ b/app/Http/Controllers/SSLController.php @@ -0,0 +1,44 @@ + $server, + 'site' => $site, + 'ssls' => $site->ssls, + ]); + } + + public function store(Server $server, Site $site, Request $request): HtmxResponse + { + app(CreateSSL::class)->create($site, $request->input()); + + Toast::success('SSL certificate is being created.'); + + return htmx()->back(); + } + + public function destroy(Server $server, Site $site, Ssl $ssl): RedirectResponse + { + app(DeleteSSL::class)->delete($ssl); + + Toast::success('SSL certificate has been deleted.'); + + return back(); + } +} diff --git a/app/Http/Controllers/SearchController.php b/app/Http/Controllers/SearchController.php new file mode 100644 index 0000000..712d8e7 --- /dev/null +++ b/app/Http/Controllers/SearchController.php @@ -0,0 +1,58 @@ +validate($request, [ + 'q' => 'required', + ]); + + $servers = Server::query() + ->where(function ($query) use ($request) { + $query->where('name', 'like', '%'.$request->input('q').'%') + ->orWhere('ip', 'like', '%'.$request->input('q').'%'); + }) + ->get(); + + $sites = Site::query() + ->where('domain', 'like', '%'.$request->input('q').'%') + ->get(); + + $result = []; + + /** @var Server $server */ + foreach ($servers as $server) { + $result[] = [ + 'type' => 'server', + 'url' => route('servers.show', ['server' => $server]), + 'text' => $server->name, + 'project' => $server->project->name, + ]; + } + + /** @var Site $site */ + foreach ($sites as $site) { + $result[] = [ + 'type' => 'site', + 'url' => route('servers.sites.show', ['server' => $site->server, 'site' => $site]), + 'text' => $site->domain, + 'project' => $site->server->project->name, + ]; + } + + return response()->json([ + 'results' => $result, + ]); + } +} diff --git a/app/Http/Controllers/ServerController.php b/app/Http/Controllers/ServerController.php index a16ffbf..8892642 100644 --- a/app/Http/Controllers/ServerController.php +++ b/app/Http/Controllers/ServerController.php @@ -2,27 +2,68 @@ namespace App\Http\Controllers; +use App\Actions\Server\CreateServer; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; use App\Models\Server; +use App\Models\ServerProvider; +use App\Models\User; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; +use Throwable; class ServerController extends Controller { - public function index() + public function index(): View { - return view('servers.index'); + /** @var User $user */ + $user = auth()->user(); + $servers = $user->currentProject->servers()->orderByDesc('created_at')->get(); + + return view('servers.index', compact('servers')); } - public function create() + public function create(Request $request): View { - return view('servers.create'); + $provider = $request->query('provider', old('provider', \App\Enums\ServerProvider::CUSTOM)); + $serverProviders = ServerProvider::query()->where('provider', $provider)->get(); + + return view('servers.create', [ + 'serverProviders' => $serverProviders, + 'provider' => $provider, + ]); } - public function show(Server $server) + /** + * @throws Throwable + */ + public function store(Request $request): HtmxResponse { - return view('servers.show', compact('server')); + $server = app(CreateServer::class)->create( + $request->user(), + $request->input() + ); + + Toast::success('Server created successfully.'); + + return htmx()->redirect(route('servers.show', ['server' => $server])); } - public function logs(Server $server) + public function show(Server $server): View { - return view('servers.logs', compact('server')); + return view('servers.show', [ + 'server' => $server, + 'logs' => $server->logs()->latest()->limit(10)->get(), + ]); + } + + public function delete(Server $server): RedirectResponse + { + $server->delete(); + + Toast::success('Server deleted successfully.'); + + return redirect()->route('servers'); } } diff --git a/app/Http/Controllers/ServerLogController.php b/app/Http/Controllers/ServerLogController.php new file mode 100644 index 0000000..7639c80 --- /dev/null +++ b/app/Http/Controllers/ServerLogController.php @@ -0,0 +1,29 @@ + $server, + ]); + } + + public function show(Server $server, ServerLog $serverLog): RedirectResponse + { + if ($server->id != $serverLog->server_id) { + abort(404); + } + + return back()->with([ + 'content' => $serverLog->getContent(), + ]); + } +} diff --git a/app/Http/Controllers/ServerSettingController.php b/app/Http/Controllers/ServerSettingController.php index 9c1da24..15f689e 100644 --- a/app/Http/Controllers/ServerSettingController.php +++ b/app/Http/Controllers/ServerSettingController.php @@ -2,12 +2,58 @@ namespace App\Http\Controllers; +use App\Actions\Server\EditServer; +use App\Actions\Server\RebootServer; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; use App\Models\Server; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class ServerSettingController extends Controller { - public function index(Server $server) + public function index(Server $server): View { return view('server-settings.index', compact('server')); } + + public function checkConnection(Server $server): RedirectResponse|HtmxResponse + { + $oldStatus = $server->status; + + $server = $server->checkConnection(); + + if ($server->status == 'disconnected') { + Toast::error('Server is disconnected.'); + } + + if ($server->status == 'ready') { + Toast::success('Server is ready.'); + } + + if ($oldStatus != $server->status) { + return htmx()->redirect(back()->getTargetUrl()); + } + + return back(); + } + + public function reboot(Server $server): HtmxResponse + { + app(RebootServer::class)->reboot($server); + + Toast::info('Server is rebooting.'); + + return htmx()->redirect(back()->getTargetUrl()); + } + + public function edit(Request $request, Server $server): RedirectResponse + { + app(EditServer::class)->edit($server, $request->input()); + + Toast::success('Server updated.'); + + return back(); + } } diff --git a/app/Http/Controllers/ServiceController.php b/app/Http/Controllers/ServiceController.php index 5f00f49..6a5e7ce 100644 --- a/app/Http/Controllers/ServiceController.php +++ b/app/Http/Controllers/ServiceController.php @@ -2,14 +2,64 @@ namespace App\Http\Controllers; +use App\Facades\Toast; use App\Models\Server; +use App\Models\Service; +use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; class ServiceController extends Controller { - public function index(Server $server) + public function index(Server $server): View { return view('services.index', [ 'server' => $server, + 'services' => $server->services, ]); } + + public function start(Server $server, Service $service): RedirectResponse + { + $service->start(); + + Toast::success('Service is being started!'); + + return back(); + } + + public function stop(Server $server, Service $service): RedirectResponse + { + $service->stop(); + + Toast::success('Service is being stopped!'); + + return back(); + } + + public function restart(Server $server, Service $service): RedirectResponse + { + $service->restart(); + + Toast::success('Service is being restarted!'); + + return back(); + } + + public function enable(Server $server, Service $service): RedirectResponse + { + $service->enable(); + + Toast::success('Service is being enabled!'); + + return back(); + } + + public function disable(Server $server, Service $service): RedirectResponse + { + $service->disable(); + + Toast::success('Service is being disabled!'); + + return back(); + } } diff --git a/app/Http/Controllers/Settings/NotificationChannelController.php b/app/Http/Controllers/Settings/NotificationChannelController.php new file mode 100644 index 0000000..88e1223 --- /dev/null +++ b/app/Http/Controllers/Settings/NotificationChannelController.php @@ -0,0 +1,45 @@ + NotificationChannel::query()->latest()->get(), + ]); + } + + public function add(Request $request): HtmxResponse + { + app(AddChannel::class)->add( + $request->user(), + $request->input() + ); + + Toast::success('Channel added successfully'); + + return htmx()->redirect(route('notification-channels')); + } + + public function delete(int $id): RedirectResponse + { + $channel = NotificationChannel::query()->findOrFail($id); + + $channel->delete(); + + Toast::success('Channel deleted successfully'); + + return redirect()->route('notification-channels'); + } +} diff --git a/app/Http/Controllers/Settings/ProfileController.php b/app/Http/Controllers/Settings/ProfileController.php new file mode 100644 index 0000000..4476c6a --- /dev/null +++ b/app/Http/Controllers/Settings/ProfileController.php @@ -0,0 +1,43 @@ +update( + $request->user(), + $request->input() + ); + + Toast::success('Profile information updated.'); + + return back(); + } + + public function password(Request $request): RedirectResponse + { + app(UpdateUserPassword::class)->update( + $request->user(), + $request->input() + ); + + Toast::success('Password updated.'); + + return back(); + } +} diff --git a/app/Http/Controllers/Settings/ProjectController.php b/app/Http/Controllers/Settings/ProjectController.php new file mode 100644 index 0000000..3d1f5c2 --- /dev/null +++ b/app/Http/Controllers/Settings/ProjectController.php @@ -0,0 +1,82 @@ + auth()->user()->projects, + ]); + } + + public function create(Request $request): HtmxResponse + { + app(CreateProject::class)->create($request->user(), $request->input()); + + Toast::success('Project created.'); + + return htmx()->redirect(route('projects')); + } + + public function update(Request $request, Project $project): HtmxResponse + { + /** @var Project $project */ + $project = $request->user()->projects()->findOrFail($project->id); + + app(UpdateProject::class)->update($project, $request->input()); + + Toast::success('Project updated.'); + + return htmx()->redirect(route('projects')); + } + + public function delete(Project $project): RedirectResponse + { + /** @var User $user */ + $user = auth()->user(); + + /** @var Project $project */ + $project = $user->projects()->findOrFail($project->id); + + try { + app(DeleteProject::class)->delete($user, $project); + } catch (ValidationException $e) { + Toast::error($e->getMessage()); + + return back(); + } + + Toast::success('Project deleted.'); + + return back(); + } + + public function switch($projectId): RedirectResponse + { + /** @var User $user */ + $user = auth()->user(); + + /** @var Project $project */ + $project = $user->projects()->findOrFail($projectId); + + $user->current_project_id = $project->id; + $user->save(); + + return redirect()->route('servers'); + } +} diff --git a/app/Http/Controllers/Settings/SSHKeyController.php b/app/Http/Controllers/Settings/SSHKeyController.php new file mode 100644 index 0000000..5fc1495 --- /dev/null +++ b/app/Http/Controllers/Settings/SSHKeyController.php @@ -0,0 +1,45 @@ + SshKey::query()->latest()->get(), + ]); + } + + public function add(Request $request): HtmxResponse + { + app(CreateSshKey::class)->create( + $request->user(), + $request->input() + ); + + Toast::success('SSH Key added'); + + return htmx()->redirect(route('ssh-keys')); + } + + public function delete(int $id): RedirectResponse + { + $key = SshKey::query()->findOrFail($id); + + $key->delete(); + + Toast::success('SSH Key deleted'); + + return redirect()->route('ssh-keys'); + } +} diff --git a/app/Http/Controllers/Settings/ServerProviderController.php b/app/Http/Controllers/Settings/ServerProviderController.php new file mode 100644 index 0000000..bae2092 --- /dev/null +++ b/app/Http/Controllers/Settings/ServerProviderController.php @@ -0,0 +1,50 @@ + auth()->user()->serverProviders, + ]); + } + + public function connect(Request $request): HtmxResponse + { + app(CreateServerProvider::class)->create( + $request->user(), + $request->input() + ); + + Toast::success('Server provider connected.'); + + return htmx()->redirect(route('server-providers')); + } + + public function delete(ServerProvider $serverProvider): RedirectResponse + { + try { + app(DeleteServerProvider::class)->delete($serverProvider); + } catch (\Exception $e) { + Toast::error($e->getMessage()); + + return back(); + } + + Toast::success('Server provider deleted.'); + + return back(); + } +} diff --git a/app/Http/Controllers/Settings/SourceControlController.php b/app/Http/Controllers/Settings/SourceControlController.php new file mode 100644 index 0000000..f3bb097 --- /dev/null +++ b/app/Http/Controllers/Settings/SourceControlController.php @@ -0,0 +1,49 @@ + SourceControl::query()->orderByDesc('id')->get(), + ]); + } + + public function connect(Request $request): HtmxResponse + { + app(ConnectSourceControl::class)->connect( + $request->input(), + ); + + Toast::success('Source control connected.'); + + return htmx()->redirect(route('source-controls')); + } + + public function delete(SourceControl $sourceControl): RedirectResponse + { + try { + app(DeleteSourceControl::class)->delete($sourceControl); + } catch (\Exception $e) { + Toast::error($e->getMessage()); + + return back(); + } + + Toast::success('Source control deleted.'); + + return redirect()->route('source-controls'); + } +} diff --git a/app/Http/Controllers/Settings/StorageProviderController.php b/app/Http/Controllers/Settings/StorageProviderController.php new file mode 100644 index 0000000..939c56b --- /dev/null +++ b/app/Http/Controllers/Settings/StorageProviderController.php @@ -0,0 +1,50 @@ + auth()->user()->storageProviders, + ]); + } + + public function connect(Request $request): HtmxResponse + { + app(CreateStorageProvider::class)->create( + $request->user(), + $request->input() + ); + + Toast::success('Storage provider connected.'); + + return htmx()->redirect(route('storage-providers')); + } + + public function delete(StorageProvider $storageProvider): RedirectResponse + { + try { + app(DeleteStorageProvider::class)->delete($storageProvider); + } catch (\Exception $e) { + Toast::error($e->getMessage()); + + return back(); + } + + Toast::success('Storage provider deleted.'); + + return back(); + } +} diff --git a/app/Http/Controllers/SiteController.php b/app/Http/Controllers/SiteController.php index d100795..4b57cb7 100644 --- a/app/Http/Controllers/SiteController.php +++ b/app/Http/Controllers/SiteController.php @@ -2,9 +2,17 @@ namespace App\Http\Controllers; +use App\Actions\Site\CreateSite; +use App\Actions\Site\DeleteSite; +use App\Enums\SiteType; +use App\Facades\Toast; +use App\Helpers\HtmxResponse; use App\Models\Server; use App\Models\Site; +use App\Models\SourceControl; use Illuminate\Contracts\View\View; +use Illuminate\Http\RedirectResponse; +use Illuminate\Http\Request; class SiteController extends Controller { @@ -12,13 +20,25 @@ public function index(Server $server): View { return view('sites.index', [ 'server' => $server, + 'sites' => $server->sites()->orderByDesc('id')->get(), ]); } + public function store(Server $server, Request $request): HtmxResponse + { + $site = app(CreateSite::class)->create($server, $request->input()); + + Toast::success('Site created'); + + return htmx()->redirect(route('servers.sites.show', [$server, $site])); + } + public function create(Server $server): View { return view('sites.create', [ 'server' => $server, + 'type' => old('type', request()->query('type', SiteType::LARAVEL)), + 'sourceControls' => SourceControl::all(), ]); } @@ -30,43 +50,12 @@ public function show(Server $server, Site $site): View ]); } - public function application(Server $server, Site $site): View + public function destroy(Server $server, Site $site): RedirectResponse { - return view('sites.application', [ - 'server' => $server, - 'site' => $site, - ]); - } + app(DeleteSite::class)->delete($site); - public function ssl(Server $server, Site $site): View - { - return view('sites.ssl', [ - 'server' => $server, - 'site' => $site, - ]); - } + Toast::success('Site is being deleted'); - public function queues(Server $server, Site $site): View - { - return view('sites.queues', [ - 'server' => $server, - 'site' => $site, - ]); - } - - public function settings(Server $server, Site $site): View - { - return view('sites.settings', [ - 'server' => $server, - 'site' => $site, - ]); - } - - public function logs(Server $server, Site $site): View - { - return view('sites.logs', [ - 'server' => $server, - 'site' => $site, - ]); + return redirect()->route('servers.sites', $server); } } diff --git a/app/Http/Controllers/SiteLogController.php b/app/Http/Controllers/SiteLogController.php new file mode 100644 index 0000000..349f894 --- /dev/null +++ b/app/Http/Controllers/SiteLogController.php @@ -0,0 +1,18 @@ + $server, + 'site' => $site, + ]); + } +} diff --git a/app/Http/Controllers/SiteSettingController.php b/app/Http/Controllers/SiteSettingController.php new file mode 100644 index 0000000..b2b940f --- /dev/null +++ b/app/Http/Controllers/SiteSettingController.php @@ -0,0 +1,66 @@ + $server, + 'site' => $site, + ]); + } + + public function getVhost(Server $server, Site $site): RedirectResponse + { + return back()->with('vhost', $server->webserver()->handler()->getVHost($site)); + } + + public function updateVhost(Server $server, Site $site, Request $request): RedirectResponse + { + $this->validate($request, [ + 'vhost' => 'required|string', + ]); + + try { + $server->webserver()->handler()->updateVHost($site, false, $request->input('vhost')); + + Toast::success('VHost updated successfully!'); + } catch (Throwable $e) { + Toast::error($e->getMessage()); + } + + return back(); + } + + public function updatePHPVersion(Server $server, Site $site, Request $request): HtmxResponse + { + $this->validate($request, [ + 'version' => [ + 'required', + Rule::exists('services', 'version')->where('type', 'php'), + ], + ]); + + try { + $site->changePHPVersion($request->input('version')); + + Toast::success('PHP version updated successfully!'); + } catch (Throwable $e) { + Toast::error($e->getMessage()); + } + + return htmx()->back(); + } +} diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php index ec01584..f0394da 100644 --- a/app/Http/Kernel.php +++ b/app/Http/Kernel.php @@ -2,6 +2,8 @@ namespace App\Http; +use App\Http\Middleware\HandleSSHErrors; +use App\Http\Middleware\SelectCurrentProject; use App\Http\Middleware\ServerIsReadyMiddleware; use Illuminate\Foundation\Http\Kernel as HttpKernel; @@ -40,7 +42,6 @@ class Kernel extends HttpKernel ], 'api' => [ - // \Laravel\Sanctum\Http\Middleware\EnsureFrontendRequestsAreStateful::class, \Illuminate\Routing\Middleware\ThrottleRequests::class.':api', \Illuminate\Routing\Middleware\SubstituteBindings::class, ], @@ -65,5 +66,7 @@ class Kernel extends HttpKernel 'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class, 'verified' => \Illuminate\Auth\Middleware\EnsureEmailIsVerified::class, 'server-is-ready' => ServerIsReadyMiddleware::class, + 'handle-ssh-errors' => HandleSSHErrors::class, + 'select-current-project' => SelectCurrentProject::class, ]; } diff --git a/app/Http/Livewire/Application/AutoDeployment.php b/app/Http/Livewire/Application/AutoDeployment.php deleted file mode 100644 index fe17dae..0000000 --- a/app/Http/Livewire/Application/AutoDeployment.php +++ /dev/null @@ -1,60 +0,0 @@ -site->auto_deployment) { - try { - $this->site->enableAutoDeployment(); - - $this->site->refresh(); - - $this->toast()->success(__('Auto deployment has been enabled.')); - } catch (SourceControlIsNotConnected) { - $this->toast()->error(__('Source control is not connected. Check site\'s settings.')); - } - } - } - - /** - * @throws Throwable - */ - public function disable(): void - { - if ($this->site->auto_deployment) { - try { - $this->site->disableAutoDeployment(); - - $this->site->refresh(); - - $this->toast()->success(__('Auto deployment has been disabled.')); - } catch (SourceControlIsNotConnected) { - $this->toast()->error(__('Source control is not connected. Check site\'s settings.')); - } - } - } - - public function render(): View - { - return view('livewire.application.auto-deployment'); - } -} diff --git a/app/Http/Livewire/Application/ChangeBranch.php b/app/Http/Livewire/Application/ChangeBranch.php deleted file mode 100644 index f528e0a..0000000 --- a/app/Http/Livewire/Application/ChangeBranch.php +++ /dev/null @@ -1,35 +0,0 @@ -branch = $this->site->branch; - } - - public function change(): void - { - app(UpdateBranch::class)->update($this->site, $this->all()); - - session()->flash('status', 'updating-branch'); - } - - public function render(): View - { - return view('livewire.application.change-branch'); - } -} diff --git a/app/Http/Livewire/Application/Deploy.php b/app/Http/Livewire/Application/Deploy.php deleted file mode 100644 index a84b377..0000000 --- a/app/Http/Livewire/Application/Deploy.php +++ /dev/null @@ -1,40 +0,0 @@ -site->deploy(); - - $this->toast()->success(__('Deployment started!')); - - $this->dispatch('$refresh')->to(DeploymentsList::class); - - $this->dispatch('$refresh')->to(DeploymentScript::class); - } catch (SourceControlIsNotConnected $e) { - session()->flash('toast.type', 'error'); - session()->flash('toast.message', $e->getMessage()); - $this->redirect(route('source-controls')); - } - } - - public function render(): View - { - return view('livewire.application.deploy'); - } -} diff --git a/app/Http/Livewire/Application/DeploymentScript.php b/app/Http/Livewire/Application/DeploymentScript.php deleted file mode 100644 index 0299909..0000000 --- a/app/Http/Livewire/Application/DeploymentScript.php +++ /dev/null @@ -1,38 +0,0 @@ -script = $this->site->deploymentScript->content; - } - - public function save(): void - { - app(UpdateDeploymentScript::class)->update($this->site, $this->all()); - - session()->flash('status', 'script-updated'); - - $this->dispatch('$refresh')->to(Deploy::class); - $this->dispatch('$refresh')->to(AutoDeployment::class); - } - - public function render(): View - { - return view('livewire.application.deployment-script'); - } -} diff --git a/app/Http/Livewire/Application/DeploymentsList.php b/app/Http/Livewire/Application/DeploymentsList.php deleted file mode 100644 index 7f030c2..0000000 --- a/app/Http/Livewire/Application/DeploymentsList.php +++ /dev/null @@ -1,34 +0,0 @@ -site->deployments()->findOrFail($id); - $this->logContent = $deployment->log->content; - - $this->dispatch('open-modal', 'show-log'); - } - - public function render(): View - { - return view('livewire.application.deployments-list', [ - 'deployments' => $this->site->deployments()->latest()->simplePaginate(10), - ]); - } -} diff --git a/app/Http/Livewire/Application/Env.php b/app/Http/Livewire/Application/Env.php deleted file mode 100644 index abfcfc0..0000000 --- a/app/Http/Livewire/Application/Env.php +++ /dev/null @@ -1,37 +0,0 @@ -env = $this->site->getEnv(); - } - - public function save(): void - { - app(UpdateEnv::class)->update($this->site, $this->all()); - - session()->flash('status', 'updating-env'); - - $this->dispatch('$refresh')->to(Deploy::class); - } - - public function render(): View - { - return view('livewire.application.env'); - } -} diff --git a/app/Http/Livewire/Application/LaravelApp.php b/app/Http/Livewire/Application/LaravelApp.php deleted file mode 100644 index 11d44b1..0000000 --- a/app/Http/Livewire/Application/LaravelApp.php +++ /dev/null @@ -1,20 +0,0 @@ -dispatch('broadcast', $event); - } - - return view('livewire.broadcast'); - } -} diff --git a/app/Http/Livewire/Cronjobs/CreateCronjob.php b/app/Http/Livewire/Cronjobs/CreateCronjob.php deleted file mode 100644 index 90147f8..0000000 --- a/app/Http/Livewire/Cronjobs/CreateCronjob.php +++ /dev/null @@ -1,34 +0,0 @@ -create($this->server, $this->all()); - - $this->dispatch('$refresh')->to(CronjobsList::class); - - $this->dispatch('created'); - } - - public function render(): View - { - return view('livewire.cronjobs.create-cronjob'); - } -} diff --git a/app/Http/Livewire/Cronjobs/CronjobsList.php b/app/Http/Livewire/Cronjobs/CronjobsList.php deleted file mode 100644 index 184c7f5..0000000 --- a/app/Http/Livewire/Cronjobs/CronjobsList.php +++ /dev/null @@ -1,35 +0,0 @@ -server->cronJobs()->where('id', $this->deleteId)->firstOrFail(); - - $cronjob->removeFromServer(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.cronjobs.cronjobs-list', [ - 'cronjobs' => $this->server->cronJobs, - ]); - } -} diff --git a/app/Http/Livewire/Databases/DatabaseBackupFiles.php b/app/Http/Livewire/Databases/DatabaseBackupFiles.php deleted file mode 100644 index 1d2d2a9..0000000 --- a/app/Http/Livewire/Databases/DatabaseBackupFiles.php +++ /dev/null @@ -1,67 +0,0 @@ -backup->run(); - - $this->refreshComponent([]); - } - - public function restore(): void - { - /** @var BackupFile $file */ - $file = BackupFile::query()->findOrFail($this->restoreId); - - /** @var Database $database */ - $database = Database::query()->findOrFail($this->restoreDatabaseId); - - $file->restore($database); - - $this->refreshComponent([]); - - $this->dispatch('restored'); - } - - public function delete(): void - { - /** @var BackupFile $file */ - $file = BackupFile::query()->findOrFail($this->deleteId); - - $file->delete(); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.databases.database-backup-files', [ - 'files' => $this->backup->files()->orderByDesc('id')->simplePaginate(10), - ]); - } -} diff --git a/app/Http/Livewire/Databases/DatabaseBackups.php b/app/Http/Livewire/Databases/DatabaseBackups.php deleted file mode 100644 index 4c0d62a..0000000 --- a/app/Http/Livewire/Databases/DatabaseBackups.php +++ /dev/null @@ -1,81 +0,0 @@ -create('database', $this->server, $this->all()); - - $this->refreshComponent([]); - - $this->dispatch('backup-created'); - } - - public function files(int $id): void - { - $backup = Backup::query()->findOrFail($id); - $this->backup = $backup; - $this->files = $backup->files()->orderByDesc('id')->simplePaginate(1); - $this->dispatch('show-files'); - } - - public function backup(): void - { - $this->backup?->run(); - - $this->files = $this->backup?->files()->orderByDesc('id')->simplePaginate(); - - $this->dispatch('backup-running'); - } - - public function delete(): void - { - /** @var Backup $backup */ - $backup = Backup::query()->findOrFail($this->deleteId); - - $backup->delete(); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.databases.database-backups', [ - 'backups' => $this->server->backups, - 'databases' => $this->server->databases, - 'files' => $this->files, - ]); - } -} diff --git a/app/Http/Livewire/Databases/DatabaseList.php b/app/Http/Livewire/Databases/DatabaseList.php deleted file mode 100644 index 881aa37..0000000 --- a/app/Http/Livewire/Databases/DatabaseList.php +++ /dev/null @@ -1,66 +0,0 @@ -create($this->server, $this->all()); - - if ($this->all()['user']) { - app(CreateDatabaseUser::class)->create($this->server, $this->all(), [$database->name]); - } - - $this->refreshComponent([]); - - $this->dispatch('database-created'); - } - - public function delete(): void - { - /** @var Database $database */ - $database = Database::query()->findOrFail($this->deleteId); - - $database->deleteFromServer(); - - $this->refreshComponent([]); - - $this->dispatch('$refresh')->to(DatabaseUserList::class); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.databases.database-list', [ - 'databases' => $this->server->databases, - ]); - } -} diff --git a/app/Http/Livewire/Databases/DatabaseUserList.php b/app/Http/Livewire/Databases/DatabaseUserList.php deleted file mode 100644 index cf64ac9..0000000 --- a/app/Http/Livewire/Databases/DatabaseUserList.php +++ /dev/null @@ -1,98 +0,0 @@ -create($this->server, $this->all()); - - $this->refreshComponent([]); - - $this->dispatch('database-user-created'); - } - - public function delete(): void - { - /** @var DatabaseUser $databaseUser */ - $databaseUser = DatabaseUser::query()->findOrFail($this->deleteId); - - $databaseUser->deleteFromServer(); - - $this->refreshComponent([]); - - $this->dispatch('$refresh')->to(DatabaseList::class); - - $this->dispatch('confirmed'); - } - - public function viewPassword(int $id): void - { - /** @var DatabaseUser $databaseUser */ - $databaseUser = DatabaseUser::query()->findOrFail($id); - - $this->viewPassword = $databaseUser->password; - - $this->dispatch('open-modal', 'database-user-password'); - } - - public function showLink(int $id): void - { - /** @var DatabaseUser $databaseUser */ - $databaseUser = DatabaseUser::query()->findOrFail($id); - - $this->linkId = $id; - $this->link = $databaseUser->databases ?? []; - - $this->dispatch('open-modal', 'link-database-user'); - } - - public function link(): void - { - /** @var DatabaseUser $databaseUser */ - $databaseUser = DatabaseUser::query()->findOrFail($this->linkId); - - app(LinkUser::class)->link($databaseUser, $this->link); - - $this->refreshComponent([]); - - $this->dispatch('linked'); - } - - public function render(): View - { - return view('livewire.databases.database-user-list', [ - 'databases' => $this->server->databases, - 'databaseUsers' => $this->server->databaseUsers, - ]); - } -} diff --git a/app/Http/Livewire/Firewall/CreateFirewallRule.php b/app/Http/Livewire/Firewall/CreateFirewallRule.php deleted file mode 100644 index 2777076..0000000 --- a/app/Http/Livewire/Firewall/CreateFirewallRule.php +++ /dev/null @@ -1,40 +0,0 @@ -create($this->server, $this->all()); - - $this->dispatch('$refresh')->to(FirewallRulesList::class); - - $this->dispatch('created'); - } - - public function render(): View - { - return view('livewire.firewall.create-firewall-rule'); - } -} diff --git a/app/Http/Livewire/Firewall/FirewallRulesList.php b/app/Http/Livewire/Firewall/FirewallRulesList.php deleted file mode 100644 index 92925aa..0000000 --- a/app/Http/Livewire/Firewall/FirewallRulesList.php +++ /dev/null @@ -1,37 +0,0 @@ -findOrFail($this->deleteId); - - $rule->removeFromServer(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.firewall.firewall-rules-list', [ - 'rules' => $this->server->firewallRules, - ]); - } -} diff --git a/app/Http/Livewire/NotificationChannels/AddChannel.php b/app/Http/Livewire/NotificationChannels/AddChannel.php deleted file mode 100644 index f68b68a..0000000 --- a/app/Http/Livewire/NotificationChannels/AddChannel.php +++ /dev/null @@ -1,38 +0,0 @@ -add( - auth()->user(), - $this->all() - ); - - $this->dispatch('$refresh')->to(ChannelsList::class); - - $this->dispatch('added'); - } - - public function render(): View - { - return view('livewire.notification-channels.add-channel'); - } -} diff --git a/app/Http/Livewire/NotificationChannels/ChannelsList.php b/app/Http/Livewire/NotificationChannels/ChannelsList.php deleted file mode 100644 index b8aab5f..0000000 --- a/app/Http/Livewire/NotificationChannels/ChannelsList.php +++ /dev/null @@ -1,37 +0,0 @@ -findOrFail($this->deleteId); - - $channel->delete(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.notification-channels.channels-list', [ - 'channels' => NotificationChannel::query()->latest()->get(), - ]); - } -} diff --git a/app/Http/Livewire/Php/DefaultCli.php b/app/Http/Livewire/Php/DefaultCli.php deleted file mode 100644 index 1a998d5..0000000 --- a/app/Http/Livewire/Php/DefaultCli.php +++ /dev/null @@ -1,30 +0,0 @@ -server->php($version)->handler()->setDefaultCli(); - - $this->refreshComponent([]); - } - - public function render(): View - { - return view('livewire.php.default-cli', [ - 'defaultPHP' => $this->server->defaultService('php'), - 'phps' => $this->server->services()->where('type', 'php')->get(), // - ]); - } -} diff --git a/app/Http/Livewire/Php/InstalledVersions.php b/app/Http/Livewire/Php/InstalledVersions.php deleted file mode 100644 index 3b7f71c..0000000 --- a/app/Http/Livewire/Php/InstalledVersions.php +++ /dev/null @@ -1,117 +0,0 @@ -install($this->server, [ - 'version' => $version, - ]); - - $this->refreshComponent([]); - } - - public function restart(int $id): void - { - /** @var Service $service */ - $service = Service::query()->findOrFail($id); - $service->restart(); - - $this->refreshComponent([]); - } - - public function uninstall(): void - { - /** @var Service $service */ - $service = Service::query()->findOrFail($this->uninstallId); - $service->uninstall(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function loadIni(int $id): void - { - $this->iniId = $id; - $this->ini = 'Loading php.ini'; - - /** @var Service $service */ - $service = Service::query()->findOrFail($this->iniId); - - try { - $this->ini = $service->server->ssh()->exec(new GetPHPIniCommand($service->version)); - } catch (Throwable) { - // - } - } - - public function saveIni(): void - { - /** @var Service $service */ - $service = Service::query()->findOrFail($this->iniId); - - app(UpdatePHPIni::class)->update($service, $this->all()['ini']); - - $this->refreshComponent([]); - - session()->flash('status', 'ini-updated'); - } - - /** - * @throws Exception - */ - public function installExtension(): void - { - /** @var Service $service */ - $service = Service::query()->findOrFail($this->extensionId); - - app(InstallPHPExtension::class)->handle($service, [ - 'name' => $this->extension, - ]); - - session()->flash('status', 'started-installation'); - } - - public function render(): View - { - if ($this->extensionId) { - /** @var Service $php */ - $php = Service::query()->findOrFail($this->extensionId); - $installedExtensions = $php->type_data['extensions'] ?? []; - } - - return view('livewire.php.installed-versions', [ - 'phps' => $this->server->services()->where('type', 'php')->get(), - 'installedExtensions' => $installedExtensions ?? [], - ]); - } -} diff --git a/app/Http/Livewire/Profile/TwoFactorAuthentication.php b/app/Http/Livewire/Profile/TwoFactorAuthentication.php deleted file mode 100644 index 02c5d84..0000000 --- a/app/Http/Livewire/Profile/TwoFactorAuthentication.php +++ /dev/null @@ -1,14 +0,0 @@ -update(auth()->user(), $this->all()); - - $this->current_password = ''; - $this->password = ''; - $this->password_confirmation = ''; - - session()->flash('status', 'password-updated'); - } - - public function render(): View - { - return view('livewire.profile.update-password'); - } -} diff --git a/app/Http/Livewire/Profile/UpdateProfileInformation.php b/app/Http/Livewire/Profile/UpdateProfileInformation.php deleted file mode 100644 index 58dc440..0000000 --- a/app/Http/Livewire/Profile/UpdateProfileInformation.php +++ /dev/null @@ -1,54 +0,0 @@ -name = auth()->user()->name; - $this->email = auth()->user()->email; - $this->timezone = auth()->user()->timezone; - } - - /** - * @throws Exception - */ - public function submit(): void - { - app(UpdateUserProfileInformation::class)->update(auth()->user(), $this->all()); - - session()->flash('status', 'profile-updated'); - - $this->dispatch('$refresh')->to(UserDropdown::class); - } - - public function sendVerificationEmail(): void - { - /** @var User $user */ - $user = auth()->user(); - if (! $user->hasVerifiedEmail()) { - $user->sendEmailVerificationNotification(); - - session()->flash('status', 'verification-link-sent'); - } - } - - public function render(): View - { - return view('livewire.profile.update-profile-information'); - } -} diff --git a/app/Http/Livewire/Projects/CreateProject.php b/app/Http/Livewire/Projects/CreateProject.php deleted file mode 100644 index 8966fc5..0000000 --- a/app/Http/Livewire/Projects/CreateProject.php +++ /dev/null @@ -1,37 +0,0 @@ -create(auth()->user(), $this->inputs); - - $this->dispatch('$refresh')->to(ProjectsList::class); - - $this->dispatch('created'); - } - - public function render(): View - { - if (request()->query('create')) { - $this->open = true; - } - - return view('livewire.projects.create-project'); - } -} diff --git a/app/Http/Livewire/Projects/EditProject.php b/app/Http/Livewire/Projects/EditProject.php deleted file mode 100644 index 5532462..0000000 --- a/app/Http/Livewire/Projects/EditProject.php +++ /dev/null @@ -1,37 +0,0 @@ -update($this->project, $this->inputs); - - $this->redirect(route('projects')); - } - - public function mount(): void - { - $this->inputs = [ - 'name' => $this->project->name, - ]; - } - - public function render(): View - { - return view('livewire.projects.edit-project'); - } -} diff --git a/app/Http/Livewire/Projects/ProjectsList.php b/app/Http/Livewire/Projects/ProjectsList.php deleted file mode 100644 index 68e233e..0000000 --- a/app/Http/Livewire/Projects/ProjectsList.php +++ /dev/null @@ -1,42 +0,0 @@ -delete(auth()->user(), $this->deleteId); - - $this->redirect(route('projects')); - - return; - } catch (ValidationException $e) { - $this->toast()->error($e->getMessage()); - } - } - - public function render(): View - { - return view('livewire.projects.projects-list', [ - 'projects' => auth()->user()->projects()->orderByDesc('id')->get(), - ]); - } -} diff --git a/app/Http/Livewire/Queues/CreateQueue.php b/app/Http/Livewire/Queues/CreateQueue.php deleted file mode 100644 index e6eeb30..0000000 --- a/app/Http/Livewire/Queues/CreateQueue.php +++ /dev/null @@ -1,36 +0,0 @@ -create($this->site, $this->all()); - - $this->dispatch('$refresh')->to(QueuesList::class); - - $this->dispatch('created'); - } - - public function render(): View - { - return view('livewire.queues.create-queue'); - } -} diff --git a/app/Http/Livewire/Queues/QueuesList.php b/app/Http/Livewire/Queues/QueuesList.php deleted file mode 100644 index 983961e..0000000 --- a/app/Http/Livewire/Queues/QueuesList.php +++ /dev/null @@ -1,57 +0,0 @@ -site->queues()->findOrFail($this->deleteId); - - $queue->remove(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function start(Queue $queue): void - { - $queue->start(); - - $this->refreshComponent([]); - } - - public function restart(Queue $queue): void - { - $queue->restart(); - - $this->refreshComponent([]); - } - - public function stop(Queue $queue): void - { - $queue->stop(); - - $this->refreshComponent([]); - } - - public function render(): View - { - return view('livewire.queues.queues-list', [ - 'queues' => $this->site->queues, - ]); - } -} diff --git a/app/Http/Livewire/ServerLogs/LogsList.php b/app/Http/Livewire/ServerLogs/LogsList.php deleted file mode 100644 index 8e21e31..0000000 --- a/app/Http/Livewire/ServerLogs/LogsList.php +++ /dev/null @@ -1,58 +0,0 @@ -server->logs()->findOrFail($id); - $this->logContent = $log->content; - - $this->dispatch('open-modal', 'show-log'); - } - - public function render(): View - { - if ($this->site) { - return $this->renderSite(); - } - - if ($this->count) { - $logs = $this->server->logs()->latest()->take(10)->get(); - } else { - $logs = $this->server->logs()->latest()->simplePaginate(10); - } - - return view('livewire.server-logs.logs-list', compact('logs')); - } - - private function renderSite(): View - { - if ($this->count) { - $logs = $this->site->logs()->latest()->take(10)->get(); - } else { - $logs = $this->site->logs()->latest()->simplePaginate(10); - } - - return view('livewire.server-logs.logs-list', compact('logs')); - } -} diff --git a/app/Http/Livewire/ServerProviders/ConnectProvider.php b/app/Http/Livewire/ServerProviders/ConnectProvider.php deleted file mode 100644 index 7f315f4..0000000 --- a/app/Http/Livewire/ServerProviders/ConnectProvider.php +++ /dev/null @@ -1,40 +0,0 @@ -create(auth()->user(), $this->all()); - - $this->dispatch('$refresh')->to(ProvidersList::class); - - $this->dispatch('connected'); - } - - public function render(): View - { - if (request()->query('provider')) { - $this->provider = request()->query('provider'); - } - - return view('livewire.server-providers.connect-provider', [ - 'open' => ! is_null(request()->query('provider')), - ]); - } -} diff --git a/app/Http/Livewire/ServerProviders/ProvidersList.php b/app/Http/Livewire/ServerProviders/ProvidersList.php deleted file mode 100644 index 5fb7ded..0000000 --- a/app/Http/Livewire/ServerProviders/ProvidersList.php +++ /dev/null @@ -1,37 +0,0 @@ -findOrFail($this->deleteId); - - $provider->delete(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.server-providers.providers-list', [ - 'providers' => ServerProvider::query()->latest()->get(), - ]); - } -} diff --git a/app/Http/Livewire/ServerSettings/CheckConnection.php b/app/Http/Livewire/ServerSettings/CheckConnection.php deleted file mode 100644 index d95c66f..0000000 --- a/app/Http/Livewire/ServerSettings/CheckConnection.php +++ /dev/null @@ -1,24 +0,0 @@ -server->checkConnection(); - - session()->flash('status', 'checking-connection'); - } - - public function render(): View - { - return view('livewire.server-settings.check-connection'); - } -} diff --git a/app/Http/Livewire/ServerSettings/EditServer.php b/app/Http/Livewire/ServerSettings/EditServer.php deleted file mode 100644 index 75721be..0000000 --- a/app/Http/Livewire/ServerSettings/EditServer.php +++ /dev/null @@ -1,40 +0,0 @@ -name = $this->server->name; - $this->ip = $this->server->ip; - $this->port = $this->server->port; - } - - public function update(): void - { - app(\App\Actions\Server\EditServer::class)->edit($this->server, $this->all()); - - session()->flash('status', 'server-updated'); - } - - public function render(): View - { - return view('livewire.server-settings.edit-server'); - } -} diff --git a/app/Http/Livewire/ServerSettings/RebootServer.php b/app/Http/Livewire/ServerSettings/RebootServer.php deleted file mode 100644 index cd908fd..0000000 --- a/app/Http/Livewire/ServerSettings/RebootServer.php +++ /dev/null @@ -1,24 +0,0 @@ -server->reboot(); - - session()->flash('status', 'rebooting-server'); - } - - public function render(): View - { - return view('livewire.server-settings.reboot-server'); - } -} diff --git a/app/Http/Livewire/ServerSettings/ServerDetails.php b/app/Http/Livewire/ServerSettings/ServerDetails.php deleted file mode 100644 index 5991e09..0000000 --- a/app/Http/Livewire/ServerSettings/ServerDetails.php +++ /dev/null @@ -1,20 +0,0 @@ -findOrFail($this->all()['key_id']); - - $key->deployTo($this->server); - - $this->dispatch('$refresh')->to(ServerKeysList::class); - - $this->dispatch('added'); - } - - public function render(): View - { - return view('livewire.server-ssh-keys.add-existing-key', [ - 'keys' => SshKey::all(), - ]); - } -} diff --git a/app/Http/Livewire/ServerSshKeys/AddNewKey.php b/app/Http/Livewire/ServerSshKeys/AddNewKey.php deleted file mode 100644 index 3817249..0000000 --- a/app/Http/Livewire/ServerSshKeys/AddNewKey.php +++ /dev/null @@ -1,36 +0,0 @@ -create( - auth()->user(), - $this->all() - ); - - $key->deployTo($this->server); - - $this->dispatch('$refresh')->to(ServerKeysList::class); - - $this->dispatch('added'); - } - - public function render(): View - { - return view('livewire.server-ssh-keys.add-new-key'); - } -} diff --git a/app/Http/Livewire/ServerSshKeys/ServerKeysList.php b/app/Http/Livewire/ServerSshKeys/ServerKeysList.php deleted file mode 100644 index 09f1b18..0000000 --- a/app/Http/Livewire/ServerSshKeys/ServerKeysList.php +++ /dev/null @@ -1,40 +0,0 @@ -findOrFail($this->deleteId); - - $key->deleteFrom($this->server); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.server-ssh-keys.server-keys-list', [ - 'keys' => $this->server->sshKeys, - ]); - } -} diff --git a/app/Http/Livewire/Servers/CreateServer.php b/app/Http/Livewire/Servers/CreateServer.php deleted file mode 100644 index 326d191..0000000 --- a/app/Http/Livewire/Servers/CreateServer.php +++ /dev/null @@ -1,64 +0,0 @@ -create( - auth()->user(), - $this->all() - ); - - $this->redirect(route('servers.show', ['server' => $server])); - } - - public function render(): View - { - $serverProviders = ServerProvider::query()->where('provider', $this->provider)->get(); - - return view( - 'livewire.servers.create-server', - compact([ - 'serverProviders', - ]) - ); - } -} diff --git a/app/Http/Livewire/Servers/DeleteServer.php b/app/Http/Livewire/Servers/DeleteServer.php deleted file mode 100644 index 7c63d23..0000000 --- a/app/Http/Livewire/Servers/DeleteServer.php +++ /dev/null @@ -1,29 +0,0 @@ -server = $server; - } - - public function delete(): void - { - $this->server->delete(); - - $this->redirect(route('servers')); - } - - public function render(): View - { - return view('livewire.servers.delete-server'); - } -} diff --git a/app/Http/Livewire/Servers/ServerStatus.php b/app/Http/Livewire/Servers/ServerStatus.php deleted file mode 100644 index 695cb21..0000000 --- a/app/Http/Livewire/Servers/ServerStatus.php +++ /dev/null @@ -1,20 +0,0 @@ -user(); - $servers = $user->currentProject->servers()->orderByDesc('created_at')->get(); - - return view('livewire.servers.servers-list', [ - 'servers' => $servers, - ]); - } -} diff --git a/app/Http/Livewire/Servers/ShowServer.php b/app/Http/Livewire/Servers/ShowServer.php deleted file mode 100644 index 0948cf7..0000000 --- a/app/Http/Livewire/Servers/ShowServer.php +++ /dev/null @@ -1,31 +0,0 @@ -redirect(route('servers.show', ['server' => $this->server])); - - return; - } - - $this->dispatch('refreshComponent'); - } - - public function render(): View - { - return view('livewire.servers.show-server'); - } -} diff --git a/app/Http/Livewire/Services/InstallPHPMyAdmin.php b/app/Http/Livewire/Services/InstallPHPMyAdmin.php deleted file mode 100644 index 92eabfe..0000000 --- a/app/Http/Livewire/Services/InstallPHPMyAdmin.php +++ /dev/null @@ -1,31 +0,0 @@ -install($this->server, $this->all()); - - $this->dispatch('started'); - - $this->dispatch('$refresh')->to(ServicesList::class); - } - - public function render(): View - { - return view('livewire.services.install-phpmyadmin'); - } -} diff --git a/app/Http/Livewire/Services/ServicesList.php b/app/Http/Livewire/Services/ServicesList.php deleted file mode 100644 index 4cc98f3..0000000 --- a/app/Http/Livewire/Services/ServicesList.php +++ /dev/null @@ -1,63 +0,0 @@ -server->services()->where('id', $id)->firstOrFail(); - - $service->stop(); - - $this->refreshComponent([]); - } - - public function start(int $id): void - { - /** @var Service $service */ - $service = $this->server->services()->where('id', $id)->firstOrFail(); - - $service->start(); - - $this->refreshComponent([]); - } - - public function restart(int $id): void - { - /** @var Service $service */ - $service = $this->server->services()->where('id', $id)->firstOrFail(); - - $service->restart(); - - $this->refreshComponent([]); - } - - public function uninstall(int $id): void - { - /** @var Service $service */ - $service = $this->server->services()->where('id', $id)->firstOrFail(); - - $service->uninstall(); - - $this->refreshComponent([]); - } - - public function render(): View - { - return view('livewire.services.services-list', [ - 'services' => $this->server->services, - ]); - } -} diff --git a/app/Http/Livewire/Sites/ChangePhpVersion.php b/app/Http/Livewire/Sites/ChangePhpVersion.php deleted file mode 100644 index 07ef34e..0000000 --- a/app/Http/Livewire/Sites/ChangePhpVersion.php +++ /dev/null @@ -1,41 +0,0 @@ -version = $site->php_version; - } - - public function change(): void - { - $this->site->changePHPVersion($this->version); - - session()->flash('status', 'changing-php-version'); - } - - public function refreshComponent(array $data): void - { - if (isset($data['type'])) { - session()->flash('status', $data['type']); - } - } - - public function render(): View - { - return view('livewire.sites.change-php-version'); - } -} diff --git a/app/Http/Livewire/Sites/CreateSite.php b/app/Http/Livewire/Sites/CreateSite.php deleted file mode 100644 index b93c5ed..0000000 --- a/app/Http/Livewire/Sites/CreateSite.php +++ /dev/null @@ -1,47 +0,0 @@ - '', - 'web_directory' => 'public', - 'source_control' => '', - 'php_version' => '', - ]; - - /** - * @throws SourceControlIsNotConnected - */ - public function create(): void - { - $site = app(\App\Actions\Site\CreateSite::class)->create( - $this->server, - $this->inputs - ); - - $this->redirect(route('servers.sites.show', [ - 'server' => $site->server, - 'site' => $site, - ])); - } - - public function render(): View - { - return view('livewire.sites.create-site', [ - 'sourceControls' => SourceControl::all(), - ]); - } -} diff --git a/app/Http/Livewire/Sites/DeleteSite.php b/app/Http/Livewire/Sites/DeleteSite.php deleted file mode 100644 index 4ad1056..0000000 --- a/app/Http/Livewire/Sites/DeleteSite.php +++ /dev/null @@ -1,27 +0,0 @@ -site->remove(); - - $this->redirect(route('servers.sites', ['server' => $this->site->server])); - } - - public function render(): View - { - return view('livewire.sites.delete-site'); - } -} diff --git a/app/Http/Livewire/Sites/ShowSite.php b/app/Http/Livewire/Sites/ShowSite.php deleted file mode 100644 index f3a2724..0000000 --- a/app/Http/Livewire/Sites/ShowSite.php +++ /dev/null @@ -1,20 +0,0 @@ -redirect( - route('servers.sites.show', [ - 'server' => $this->server, - 'site' => $data['data']['site']['id'], - ]) - ); - - return; - } - - $this->dispatch('refreshComponent'); - } - - public function render(): View - { - return view('livewire.sites.sites-list', [ - 'sites' => $this->server->sites()->latest()->get(), - ]); - } -} diff --git a/app/Http/Livewire/Sites/UpdateSourceControlProvider.php b/app/Http/Livewire/Sites/UpdateSourceControlProvider.php deleted file mode 100644 index ed72e47..0000000 --- a/app/Http/Livewire/Sites/UpdateSourceControlProvider.php +++ /dev/null @@ -1,33 +0,0 @@ -update($this->site, $this->all()); - - $this->resetErrorBag(); - - session()->flash('status', 'source-control-updated'); - } - - public function render(): View - { - if (! $this->source_control) { - $this->source_control = $this->site->source_control_id; - } - - return view('livewire.sites.update-source-control-provider'); - } -} diff --git a/app/Http/Livewire/Sites/UpdateVHost.php b/app/Http/Livewire/Sites/UpdateVHost.php deleted file mode 100644 index 26ec759..0000000 --- a/app/Http/Livewire/Sites/UpdateVHost.php +++ /dev/null @@ -1,41 +0,0 @@ -vHost = $this->site->server->webserver()->handler()->getVHost($this->site); - } - - public function update(): void - { - try { - $this->site->server->webserver()->handler()->updateVHost($this->site, false, $this->vHost); - - $this->toast()->success('VHost updated successfully!'); - } catch (Throwable $e) { - $this->toast()->error($e->getMessage()); - } - } - - public function render(): View - { - return view('livewire.sites.update-v-host'); - } -} diff --git a/app/Http/Livewire/SourceControls/Connect.php b/app/Http/Livewire/SourceControls/Connect.php deleted file mode 100644 index 9cadffa..0000000 --- a/app/Http/Livewire/SourceControls/Connect.php +++ /dev/null @@ -1,38 +0,0 @@ -connect($this->all()); - - $this->dispatch('$refresh')->to(SourceControlsList::class); - - $this->dispatch('connected'); - } - - public function render(): View - { - if (request()->query('provider')) { - $this->provider = request()->query('provider'); - } - - return view('livewire.source-controls.connect', [ - 'open' => ! is_null(request()->query('provider')), - ]); - } -} diff --git a/app/Http/Livewire/SourceControls/SourceControlsList.php b/app/Http/Livewire/SourceControls/SourceControlsList.php deleted file mode 100644 index 64ce831..0000000 --- a/app/Http/Livewire/SourceControls/SourceControlsList.php +++ /dev/null @@ -1,37 +0,0 @@ -findOrFail($this->deleteId); - - $provider->delete(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.source-controls.source-controls-list', [ - 'sourceControls' => SourceControl::query()->latest()->get(), - ]); - } -} diff --git a/app/Http/Livewire/SshKeys/AddKey.php b/app/Http/Livewire/SshKeys/AddKey.php deleted file mode 100644 index d6099c6..0000000 --- a/app/Http/Livewire/SshKeys/AddKey.php +++ /dev/null @@ -1,31 +0,0 @@ -create( - auth()->user(), - $this->all() - ); - - $this->dispatch('$refresh')->to(KeysList::class); - - $this->dispatch('added'); - } - - public function render(): View - { - return view('livewire.ssh-keys.add-key'); - } -} diff --git a/app/Http/Livewire/SshKeys/KeysList.php b/app/Http/Livewire/SshKeys/KeysList.php deleted file mode 100644 index 9b5a401..0000000 --- a/app/Http/Livewire/SshKeys/KeysList.php +++ /dev/null @@ -1,37 +0,0 @@ -findOrFail($this->deleteId); - - $key->delete(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.ssh-keys.keys-list', [ - 'keys' => SshKey::query()->latest()->get(), - ]); - } -} diff --git a/app/Http/Livewire/Ssl/CreateSsl.php b/app/Http/Livewire/Ssl/CreateSsl.php deleted file mode 100644 index 2277e4b..0000000 --- a/app/Http/Livewire/Ssl/CreateSsl.php +++ /dev/null @@ -1,35 +0,0 @@ -create($this->site, $this->all()); - - $this->dispatch('$refresh')->to(SslsList::class); - - $this->dispatch('created'); - } - - public function render(): View - { - return view('livewire.ssl.create-ssl'); - } -} diff --git a/app/Http/Livewire/Ssl/SslsList.php b/app/Http/Livewire/Ssl/SslsList.php deleted file mode 100644 index f7fb5a5..0000000 --- a/app/Http/Livewire/Ssl/SslsList.php +++ /dev/null @@ -1,46 +0,0 @@ -site->ssls()->where('id', $this->deleteId)->firstOrFail(); - - $ssl->remove(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function refreshComponent(array $data): void - { - if (isset($data['type']) && $data['type'] == 'deploy-ssl-failed') { - $this->toast()->error(__('SSL creation failed!')); - } - - $this->dispatch('refreshComponent'); - } - - public function render(): View - { - return view('livewire.ssl.ssls-list', [ - 'ssls' => $this->site->ssls, - ]); - } -} diff --git a/app/Http/Livewire/StorageProviders/ConnectProvider.php b/app/Http/Livewire/StorageProviders/ConnectProvider.php deleted file mode 100644 index 4499896..0000000 --- a/app/Http/Livewire/StorageProviders/ConnectProvider.php +++ /dev/null @@ -1,44 +0,0 @@ -create(auth()->user(), $this->all()); - - $this->dispatch('$refresh')->to(ProvidersList::class); - - $this->dispatch('connected'); - } - - public function render(): View - { - return view('livewire.storage-providers.connect-provider'); - } -} diff --git a/app/Http/Livewire/StorageProviders/ProvidersList.php b/app/Http/Livewire/StorageProviders/ProvidersList.php deleted file mode 100644 index 865a05f..0000000 --- a/app/Http/Livewire/StorageProviders/ProvidersList.php +++ /dev/null @@ -1,37 +0,0 @@ -findOrFail($this->deleteId); - - $provider->delete(); - - $this->refreshComponent([]); - - $this->dispatch('confirmed'); - } - - public function render(): View - { - return view('livewire.storage-providers.providers-list', [ - 'providers' => StorageProvider::query()->latest()->get(), - ]); - } -} diff --git a/app/Http/Livewire/UserDropdown.php b/app/Http/Livewire/UserDropdown.php deleted file mode 100644 index 69db7c3..0000000 --- a/app/Http/Livewire/UserDropdown.php +++ /dev/null @@ -1,18 +0,0 @@ -exception) { + if ($res->exception instanceof SSHConnectionError || $res->exception instanceof SSHCommandError) { + Toast::error($res->exception->getMessage()); + + if ($request->hasHeader('HX-Request')) { + return htmx()->back(); + } + + return back(); + } + } + + return $res; + } +} diff --git a/app/Http/Middleware/SelectCurrentProject.php b/app/Http/Middleware/SelectCurrentProject.php new file mode 100644 index 0000000..9ec41ea --- /dev/null +++ b/app/Http/Middleware/SelectCurrentProject.php @@ -0,0 +1,32 @@ +route('server'); + + /** @var User $user */ + $user = $request->user(); + + if ($server->project_id != $user->current_project_id) { + $user->current_project_id = $server->project_id; + $user->save(); + } + + return $next($request); + } +} diff --git a/app/Http/Middleware/ServerIsReadyMiddleware.php b/app/Http/Middleware/ServerIsReadyMiddleware.php index c9e6220..8b27926 100644 --- a/app/Http/Middleware/ServerIsReadyMiddleware.php +++ b/app/Http/Middleware/ServerIsReadyMiddleware.php @@ -2,6 +2,8 @@ namespace App\Http\Middleware; +use App\Enums\ServerStatus; +use App\Facades\Toast; use App\Models\Server; use Closure; use Illuminate\Http\Request; @@ -13,7 +15,9 @@ public function handle(Request $request, Closure $next) /** @var Server $server */ $server = $request->route('server'); - if (! $server->isReady()) { + if ($server->status !== ServerStatus::READY) { + Toast::error('Server is not ready yet'); + return redirect()->route('servers.show', ['server' => $server]); } diff --git a/app/Http/Requests/Auth/LoginRequest.php b/app/Http/Requests/Auth/LoginRequest.php deleted file mode 100644 index 7a19bc0..0000000 --- a/app/Http/Requests/Auth/LoginRequest.php +++ /dev/null @@ -1,85 +0,0 @@ - - */ - public function rules(): array - { - return [ - 'email' => ['required', 'string', 'email'], - 'password' => ['required', 'string'], - ]; - } - - /** - * Attempt to authenticate the request's credentials. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function authenticate(): void - { - $this->ensureIsNotRateLimited(); - - if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) { - RateLimiter::hit($this->throttleKey()); - - throw ValidationException::withMessages([ - 'email' => trans('auth.failed'), - ]); - } - - RateLimiter::clear($this->throttleKey()); - } - - /** - * Ensure the login request is not rate limited. - * - * @throws \Illuminate\Validation\ValidationException - */ - public function ensureIsNotRateLimited(): void - { - if (! RateLimiter::tooManyAttempts($this->throttleKey(), 5)) { - return; - } - - event(new Lockout($this)); - - $seconds = RateLimiter::availableIn($this->throttleKey()); - - throw ValidationException::withMessages([ - 'email' => trans('auth.throttle', [ - 'seconds' => $seconds, - 'minutes' => ceil($seconds / 60), - ]), - ]); - } - - /** - * Get the rate limiting throttle key for the request. - */ - public function throttleKey(): string - { - return Str::transliterate(Str::lower($this->input('email')).'|'.$this->ip()); - } -} diff --git a/app/Http/Requests/ProfileUpdateRequest.php b/app/Http/Requests/ProfileUpdateRequest.php deleted file mode 100644 index 327ce6f..0000000 --- a/app/Http/Requests/ProfileUpdateRequest.php +++ /dev/null @@ -1,23 +0,0 @@ - - */ - public function rules(): array - { - return [ - 'name' => ['string', 'max:255'], - 'email' => ['email', 'max:255', Rule::unique(User::class)->ignore($this->user()->id)], - ]; - } -} diff --git a/app/Jobs/Backup/RestoreDatabase.php b/app/Jobs/Backup/RestoreDatabase.php deleted file mode 100644 index 081e16a..0000000 --- a/app/Jobs/Backup/RestoreDatabase.php +++ /dev/null @@ -1,48 +0,0 @@ -backupFile = $backupFile; - $this->database = $database; - } - - public function handle(): void - { - $this->database->server->database()->handler()->restoreBackup($this->backupFile, $this->database->name); - - $this->backupFile->status = BackupFileStatus::RESTORED; - $this->backupFile->restored_at = now(); - $this->backupFile->save(); - - event( - new Broadcast('backup-restore-finished', [ - 'file' => $this->backupFile, - ]) - ); - } - - public function failed(): void - { - $this->backupFile->status = BackupFileStatus::RESTORE_FAILED; - $this->backupFile->save(); - event( - new Broadcast('backup-restore-failed', [ - 'file' => $this->backupFile, - ]) - ); - } -} diff --git a/app/Jobs/Backup/RunBackup.php b/app/Jobs/Backup/RunBackup.php deleted file mode 100644 index d08b755..0000000 --- a/app/Jobs/Backup/RunBackup.php +++ /dev/null @@ -1,45 +0,0 @@ -backupFile = $backupFile; - } - - public function handle(): void - { - if ($this->backupFile->backup->type === 'database') { - $this->backupFile->backup->server->database()->handler()->runBackup($this->backupFile); - } - - $this->backupFile->status = BackupFileStatus::CREATED; - $this->backupFile->save(); - - event( - new Broadcast('run-backup-finished', [ - 'file' => $this->backupFile, - ]) - ); - } - - public function failed(): void - { - $this->backupFile->status = BackupFileStatus::FAILED; - $this->backupFile->save(); - event( - new Broadcast('run-backup-failed', [ - 'file' => $this->backupFile, - ]) - ); - } -} diff --git a/app/Jobs/CronJob/AddToServer.php b/app/Jobs/CronJob/AddToServer.php deleted file mode 100644 index d1e28c4..0000000 --- a/app/Jobs/CronJob/AddToServer.php +++ /dev/null @@ -1,48 +0,0 @@ -cronJob = $cronJob; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->cronJob->server->ssh()->exec( - new UpdateCronJobsCommand($this->cronJob->user, $this->cronJob->crontab), - 'update-crontab' - ); - $this->cronJob->status = CronjobStatus::READY; - $this->cronJob->save(); - event( - new Broadcast('add-cronjob-finished', [ - 'cronJob' => $this->cronJob, - ]) - ); - } - - public function failed(): void - { - $this->cronJob->delete(); - event( - new Broadcast('add-cronjob-failed', [ - 'cronJob' => $this->cronJob, - ]) - ); - } -} diff --git a/app/Jobs/CronJob/RemoveFromServer.php b/app/Jobs/CronJob/RemoveFromServer.php deleted file mode 100644 index dce0992..0000000 --- a/app/Jobs/CronJob/RemoveFromServer.php +++ /dev/null @@ -1,46 +0,0 @@ -cronJob = $cronJob; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->cronJob->server->ssh()->exec( - new UpdateCronJobsCommand($this->cronJob->user, $this->cronJob->crontab), - 'update-crontab' - ); - $this->cronJob->delete(); - event( - new Broadcast('remove-cronjob-finished', [ - 'id' => $this->cronJob->id, - ]) - ); - } - - public function failed(): void - { - $this->cronJob->save(); - event( - new Broadcast('remove-cronjob-failed', [ - 'cronJob' => $this->cronJob, - ]) - ); - } -} diff --git a/app/Jobs/Database/CreateOnServer.php b/app/Jobs/Database/CreateOnServer.php deleted file mode 100644 index eee40a2..0000000 --- a/app/Jobs/Database/CreateOnServer.php +++ /dev/null @@ -1,35 +0,0 @@ -database = $database; - } - - public function handle(): void - { - $this->database->server->database()->handler()->create($this->database->name); - $this->database->status = DatabaseStatus::READY; - $this->database->save(); - event(new Broadcast('create-database-finished', [ - 'id' => $this->database->id, - ])); - } - - public function failed(): void - { - event(new Broadcast('create-database-failed', [ - 'id' => $this->database->id, - ])); - } -} diff --git a/app/Jobs/Database/DeleteFromServer.php b/app/Jobs/Database/DeleteFromServer.php deleted file mode 100644 index 57e432e..0000000 --- a/app/Jobs/Database/DeleteFromServer.php +++ /dev/null @@ -1,37 +0,0 @@ -database = $database; - } - - public function handle(): void - { - $this->database->server->database()->handler()->delete($this->database->name); - event( - new Broadcast('delete-database-finished', [ - 'id' => $this->database->id, - ]) - ); - $this->database->delete(); - } - - public function failed(): void - { - event( - new Broadcast('delete-database-failed', [ - 'id' => $this->database->id, - ]) - ); - } -} diff --git a/app/Jobs/DatabaseUser/CreateOnServer.php b/app/Jobs/DatabaseUser/CreateOnServer.php deleted file mode 100644 index a76839f..0000000 --- a/app/Jobs/DatabaseUser/CreateOnServer.php +++ /dev/null @@ -1,48 +0,0 @@ -databaseUser = $databaseUser; - } - - public function handle(): void - { - $this->databaseUser->server->database()->handler()->createUser( - $this->databaseUser->username, - $this->databaseUser->password, - $this->databaseUser->host - ); - $this->databaseUser->status = DatabaseUserStatus::READY; - $this->databaseUser->save(); - - if (count($this->databaseUser->databases) > 0) { - (new LinkUser($this->databaseUser))->handle(); - } - - event( - new Broadcast('create-database-user-finished', [ - 'id' => $this->databaseUser->id, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('create-database-user-failed', [ - 'id' => $this->databaseUser->id, - ]) - ); - } -} diff --git a/app/Jobs/DatabaseUser/DeleteFromServer.php b/app/Jobs/DatabaseUser/DeleteFromServer.php deleted file mode 100644 index 65e58cb..0000000 --- a/app/Jobs/DatabaseUser/DeleteFromServer.php +++ /dev/null @@ -1,40 +0,0 @@ -databaseUser = $databaseUser; - } - - public function handle(): void - { - $this->databaseUser->server->database()->handler()->deleteUser( - $this->databaseUser->username, - $this->databaseUser->host - ); - event( - new Broadcast('delete-database-user-finished', [ - 'id' => $this->databaseUser->id, - ]) - ); - $this->databaseUser->delete(); - } - - public function failed(): void - { - event( - new Broadcast('delete-database-user-failed', [ - 'id' => $this->databaseUser->id, - ]) - ); - } -} diff --git a/app/Jobs/DatabaseUser/LinkUser.php b/app/Jobs/DatabaseUser/LinkUser.php deleted file mode 100644 index dfd6add..0000000 --- a/app/Jobs/DatabaseUser/LinkUser.php +++ /dev/null @@ -1,40 +0,0 @@ -databaseUser = $databaseUser; - } - - public function handle(): void - { - $this->databaseUser->server->database()->handler()->link( - $this->databaseUser->username, - $this->databaseUser->host, - $this->databaseUser->databases - ); - event( - new Broadcast('link-database-user-finished', [ - 'id' => $this->databaseUser->id, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('link-database-user-failed', [ - 'id' => $this->databaseUser->id, - ]) - ); - } -} diff --git a/app/Jobs/DatabaseUser/UnlinkUser.php b/app/Jobs/DatabaseUser/UnlinkUser.php deleted file mode 100644 index 8ca8ffb..0000000 --- a/app/Jobs/DatabaseUser/UnlinkUser.php +++ /dev/null @@ -1,39 +0,0 @@ -databaseUser = $databaseUser; - } - - public function handle(): void - { - $this->databaseUser->server->database()->handler()->unlink( - $this->databaseUser->username, - $this->databaseUser->host, - ); - event( - new Broadcast('unlink-database-user-finished', [ - 'id' => $this->databaseUser->id, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('unlink-database-user-failed', [ - 'id' => $this->databaseUser->id, - ]) - ); - } -} diff --git a/app/Jobs/Firewall/AddToServer.php b/app/Jobs/Firewall/AddToServer.php deleted file mode 100644 index 912a93b..0000000 --- a/app/Jobs/Firewall/AddToServer.php +++ /dev/null @@ -1,48 +0,0 @@ -firewallRule = $firewallRule; - } - - public function handle(): void - { - $this->firewallRule->server->firewall() - ->handler() - ->addRule( - $this->firewallRule->type, - $this->firewallRule->real_protocol, - $this->firewallRule->port, - $this->firewallRule->source, - $this->firewallRule->mask - ); - $this->firewallRule->status = FirewallRuleStatus::READY; - $this->firewallRule->save(); - event( - new Broadcast('create-firewall-rule-finished', [ - 'firewallRule' => $this->firewallRule, - ]) - ); - } - - public function failed(): void - { - $this->firewallRule->delete(); - event( - new Broadcast('create-firewall-rule-failed', [ - 'firewallRule' => $this->firewallRule, - ]) - ); - } -} diff --git a/app/Jobs/Firewall/RemoveFromServer.php b/app/Jobs/Firewall/RemoveFromServer.php deleted file mode 100644 index fc3644f..0000000 --- a/app/Jobs/Firewall/RemoveFromServer.php +++ /dev/null @@ -1,48 +0,0 @@ -firewallRule = $firewallRule; - } - - public function handle(): void - { - $this->firewallRule->server->firewall() - ->handler() - ->removeRule( - $this->firewallRule->type, - $this->firewallRule->real_protocol, - $this->firewallRule->port, - $this->firewallRule->source, - $this->firewallRule->mask - ); - $this->firewallRule->delete(); - event( - new Broadcast('delete-firewall-rule-finished', [ - 'id' => $this->firewallRule->id, - ]) - ); - } - - public function failed(): void - { - $this->firewallRule->status = FirewallRuleStatus::READY; - $this->firewallRule->save(); - event( - new Broadcast('delete-firewall-rule-failed', [ - 'firewallRule' => $this->firewallRule, - ]) - ); - } -} diff --git a/app/Jobs/Installation/ContinueInstallation.php b/app/Jobs/Installation/ContinueInstallation.php deleted file mode 100644 index 6cca01b..0000000 --- a/app/Jobs/Installation/ContinueInstallation.php +++ /dev/null @@ -1,43 +0,0 @@ -server = $server; - $this->attempts = $attempts; - } - - public function handle(): void - { - if ($this->server->provider()->isRunning()) { - $this->server->install(); - - return; - } - - if ($this->attempts >= 2) { - $this->server->update([ - 'status' => 'installation_failed', - ]); - event( - new Broadcast('install-server-failed', [ - 'server' => $this->server, - ]) - ); - - return; - } - - dispatch(new self($this->server, $this->attempts++))->delay(now()->addMinute()); - } -} diff --git a/app/Jobs/Installation/Initialize.php b/app/Jobs/Installation/Initialize.php deleted file mode 100755 index 0d3f7e6..0000000 --- a/app/Jobs/Installation/Initialize.php +++ /dev/null @@ -1,72 +0,0 @@ -server = $server->refresh(); - $this->asUser = $asUser; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->authentication(); - $this->publicKey(); - // $this->setHostname(); - } - - /** - * @throws Throwable - */ - protected function authentication(): void - { - $this->server - ->ssh($this->asUser ?? $this->server->ssh_user) - ->exec( - new CreateUserCommand( - $this->server->authentication['user'], - $this->server->authentication['pass'], - $this->server->sshKey()['public_key'] - ), - 'create-user' - ); - - $this->server->ssh_user = config('core.ssh_user'); - $this->server->save(); - } - - /** - * @throws Throwable - */ - protected function publicKey(): void - { - $publicKey = $this->server->ssh()->exec(new GetPublicKeyCommand()); - $this->server->update([ - 'public_key' => $publicKey, - ]); - } - - /** - * @throws Throwable - */ - protected function setHostname(): void - { - $this->server - ->ssh() - ->exec('sudo hostnamectl set-hostname '.$this->server->hostname); - } -} diff --git a/app/Jobs/Installation/InstallCertbot.php b/app/Jobs/Installation/InstallCertbot.php deleted file mode 100755 index c9dc3ea..0000000 --- a/app/Jobs/Installation/InstallCertbot.php +++ /dev/null @@ -1,25 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec(new InstallCertbotCommand(), 'install-certbot'); - } -} diff --git a/app/Jobs/Installation/InstallComposer.php b/app/Jobs/Installation/InstallComposer.php deleted file mode 100755 index f864559..0000000 --- a/app/Jobs/Installation/InstallComposer.php +++ /dev/null @@ -1,25 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec(new InstallComposerCommand(), 'install-composer'); - } -} diff --git a/app/Jobs/Installation/InstallMariadb.php b/app/Jobs/Installation/InstallMariadb.php deleted file mode 100755 index 1ad4955..0000000 --- a/app/Jobs/Installation/InstallMariadb.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallMariadbCommand(), 'install-mariadb'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'mariadb-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallMysql.php b/app/Jobs/Installation/InstallMysql.php deleted file mode 100755 index 4627de0..0000000 --- a/app/Jobs/Installation/InstallMysql.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallMysqlCommand($this->service->version), 'install-mysql'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'mysql-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallNginx.php b/app/Jobs/Installation/InstallNginx.php deleted file mode 100755 index 142feef..0000000 --- a/app/Jobs/Installation/InstallNginx.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallNginxCommand(), 'install-nginx'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'nginx-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallNodejs.php b/app/Jobs/Installation/InstallNodejs.php deleted file mode 100755 index c30edde..0000000 --- a/app/Jobs/Installation/InstallNodejs.php +++ /dev/null @@ -1,25 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec(new InstallNodejsCommand(), 'install-nodejs'); - } -} diff --git a/app/Jobs/Installation/InstallPHP.php b/app/Jobs/Installation/InstallPHP.php deleted file mode 100755 index ed9dcff..0000000 --- a/app/Jobs/Installation/InstallPHP.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallPHPCommand($this->service->version), 'install-php'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'php-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallPHPMyAdmin.php b/app/Jobs/Installation/InstallPHPMyAdmin.php deleted file mode 100644 index b9fb301..0000000 --- a/app/Jobs/Installation/InstallPHPMyAdmin.php +++ /dev/null @@ -1,111 +0,0 @@ -service = $service; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->setUpFirewall(); - $this->downloadSource(); - $this->setUpVHost(); - $this->restartPHP(); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } - - /** - * @throws Throwable - */ - private function setUpFirewall(): void - { - $this->firewallRule = FirewallRule::query() - ->where('server_id', $this->service->server_id) - ->where('port', $this->service->type_data['port']) - ->first(); - if ($this->firewallRule) { - $this->firewallRule->source = $this->service->type_data['allowed_ip']; - $this->firewallRule->save(); - } else { - $this->firewallRule = app(CreateRule::class)->create( - $this->service->server, - [ - 'type' => 'allow', - 'protocol' => 'tcp', - 'port' => $this->service->type_data['port'], - 'source' => $this->service->type_data['allowed_ip'], - 'mask' => '0', - ] - ); - } - } - - /** - * @throws Throwable - */ - private function downloadSource(): void - { - $this->service->server->ssh()->exec( - new DownloadPHPMyAdminCommand(), - 'download-phpmyadmin' - ); - } - - /** - * @throws Throwable - */ - private function setUpVHost(): void - { - $vhost = File::get(resource_path('commands/webserver/nginx/phpmyadmin-vhost.conf')); - $vhost = Str::replace('__php_version__', $this->service->server->defaultService('php')->version, $vhost); - $vhost = Str::replace('__port__', $this->service->type_data['port'], $vhost); - $this->service->server->ssh()->exec( - new CreateNginxPHPMyAdminVHostCommand($vhost), - 'create-phpmyadmin-vhost' - ); - } - - private function restartPHP(): void - { - $this->service->server->service( - 'php', - $this->service->type_data['php'] - )?->restart(); - } - - /** - * @throws Throwable - */ - public function failed(Throwable $throwable): Throwable - { - $this->firewallRule?->removeFromServer(); - $this->service->update([ - 'status' => ServiceStatus::INSTALLATION_FAILED, - ]); - throw $throwable; - } -} diff --git a/app/Jobs/Installation/InstallRedis.php b/app/Jobs/Installation/InstallRedis.php deleted file mode 100755 index e70333d..0000000 --- a/app/Jobs/Installation/InstallRedis.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallRedisCommand(), 'install-redis'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'redis-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallRequirements.php b/app/Jobs/Installation/InstallRequirements.php deleted file mode 100755 index 7e0a88f..0000000 --- a/app/Jobs/Installation/InstallRequirements.php +++ /dev/null @@ -1,28 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec(new InstallRequirementsCommand( - $this->server->creator->email, - $this->server->creator->name, - ), 'install-requirements'); - } -} diff --git a/app/Jobs/Installation/InstallSupervisor.php b/app/Jobs/Installation/InstallSupervisor.php deleted file mode 100755 index 28df41c..0000000 --- a/app/Jobs/Installation/InstallSupervisor.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallSupervisorCommand(), 'install-supervisor'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'supervisor-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallUfw.php b/app/Jobs/Installation/InstallUfw.php deleted file mode 100755 index 67e5dbd..0000000 --- a/app/Jobs/Installation/InstallUfw.php +++ /dev/null @@ -1,35 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new InstallUfwCommand(), 'install-ufw'); - $status = $ssh->exec(new ServiceStatusCommand($this->service->unit), 'ufw-status'); - $this->service->validateInstall($status); - $this->service->update([ - 'status' => ServiceStatus::READY, - ]); - } -} diff --git a/app/Jobs/Installation/InstallationJob.php b/app/Jobs/Installation/InstallationJob.php deleted file mode 100755 index f9d0f07..0000000 --- a/app/Jobs/Installation/InstallationJob.php +++ /dev/null @@ -1,16 +0,0 @@ -service = $service; - } - - /** - * @throws InstallationFailed - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->service->server->ssh(); - $ssh->exec(new UninstallPHPCommand($this->service->version), 'uninstall-php'); - } -} diff --git a/app/Jobs/Installation/UninstallPHPMyAdmin.php b/app/Jobs/Installation/UninstallPHPMyAdmin.php deleted file mode 100644 index 5f11aae..0000000 --- a/app/Jobs/Installation/UninstallPHPMyAdmin.php +++ /dev/null @@ -1,63 +0,0 @@ -service = $service; - } - - /** - * @throws Exception - * @throws Throwable - */ - public function handle(): void - { - $this->removeFirewallRule(); - $this->deleteVHost(); - $this->restartPHP(); - } - - /** - * @throws Exception - */ - private function removeFirewallRule(): void - { - /** @var ?FirewallRule $rule */ - $rule = FirewallRule::query() - ->where('server_id', $this->service->server_id) - ->where('port', $this->service->type_data['port']) - ->first(); - $rule?->removeFromServer(); - } - - /** - * @throws Throwable - */ - private function deleteVHost(): void - { - $this->service->server->ssh()->exec( - new DeleteNginxPHPMyAdminVHostCommand('/home/vito/phpmyadmin'), - 'delete-phpmyadmin-vhost' - ); - } - - private function restartPHP(): void - { - $this->service->server->service( - 'php', - $this->service->type_data['php'] - )?->restart(); - } -} diff --git a/app/Jobs/Installation/Upgrade.php b/app/Jobs/Installation/Upgrade.php deleted file mode 100755 index 0c977aa..0000000 --- a/app/Jobs/Installation/Upgrade.php +++ /dev/null @@ -1,25 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec(new UpgradeCommand(), 'upgrade'); - } -} diff --git a/app/Jobs/Job.php b/app/Jobs/Job.php deleted file mode 100755 index be45a6a..0000000 --- a/app/Jobs/Job.php +++ /dev/null @@ -1,14 +0,0 @@ -service = $service; - $this->name = $name; - } - - /** - * @throws ProcessFailed - * @throws Throwable - */ - public function handle(): void - { - $result = $this->service->server->ssh()->exec( - new InstallPHPExtensionCommand($this->service->version, $this->name), - 'install-php-extension' - ); - $result = Str::substr($result, strpos($result, '[PHP Modules]')); - if (! Str::contains($result, $this->name)) { - throw new ProcessFailed('Extension failed'); - } - $typeData = $this->service->type_data; - $typeData['extensions'][] = $this->name; - $this->service->type_data = $typeData; - $this->service->save(); - event( - new Broadcast('install-php-extension-finished', [ - 'service' => $this->service, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('install-php-extension-failed', [ - 'service' => $this->service, - ]) - ); - } -} diff --git a/app/Jobs/PHP/SetDefaultCli.php b/app/Jobs/PHP/SetDefaultCli.php deleted file mode 100644 index 7cc9eba..0000000 --- a/app/Jobs/PHP/SetDefaultCli.php +++ /dev/null @@ -1,46 +0,0 @@ -service = $service; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->service->server->ssh()->exec( - new ChangeDefaultPHPCommand($this->service->version), - 'change-default-php' - ); - $this->service->server->defaultService('php')->update(['is_default' => 0]); - $this->service->update(['is_default' => 1]); - $this->service->update(['status' => ServiceStatus::READY]); - event( - new Broadcast('set-default-cli-finished', [ - 'defaultPHP' => $this->service->server->defaultService('php'), - ]) - ); - } - - public function failed(): void - { - event(new Broadcast('set-default-cli-failed', [ - 'defaultPHP' => $this->service->server->defaultService('php'), - ])); - } -} diff --git a/app/Jobs/Queue/Deploy.php b/app/Jobs/Queue/Deploy.php deleted file mode 100644 index 93cc46f..0000000 --- a/app/Jobs/Queue/Deploy.php +++ /dev/null @@ -1,49 +0,0 @@ -worker = $worker; - } - - public function handle(): void - { - $this->worker->server->processManager()->handler()->create( - $this->worker->id, - $this->worker->command, - $this->worker->user, - $this->worker->auto_start, - $this->worker->auto_restart, - $this->worker->numprocs, - $this->worker->log_file, - $this->worker->site_id - ); - $this->worker->status = QueueStatus::RUNNING; - $this->worker->save(); - event( - new Broadcast('deploy-queue-finished', [ - 'queue' => $this->worker, - ]) - ); - } - - public function failed(): void - { - $this->worker->delete(); - event( - new Broadcast('deploy-queue-failed', [ - 'queue' => $this->worker, - ]) - ); - } -} diff --git a/app/Jobs/Queue/GetLogs.php b/app/Jobs/Queue/GetLogs.php deleted file mode 100644 index 6fe00cc..0000000 --- a/app/Jobs/Queue/GetLogs.php +++ /dev/null @@ -1,37 +0,0 @@ -worker = $worker; - } - - public function handle(): void - { - $logs = $this->worker->server->processManager()->handler()->getLogs($this->worker->log_file); - event( - new Broadcast('get-logs-finished', [ - 'id' => $this->worker->id, - 'logs' => $logs, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('get-logs-failed', [ - 'message' => __('Failed to download the logs!'), - ]) - ); - } -} diff --git a/app/Jobs/Queue/Manage.php b/app/Jobs/Queue/Manage.php deleted file mode 100644 index 8fa1b7c..0000000 --- a/app/Jobs/Queue/Manage.php +++ /dev/null @@ -1,68 +0,0 @@ -worker = $worker; - $this->action = $action; - $this->successStatus = $successStatus; - $this->failStatus = $failStatus; - $this->failMessage = $failMessage; - } - - public function handle(): void - { - switch ($this->action) { - case 'start': - $this->worker->server->processManager()->handler()->start($this->worker->id, $this->worker->site_id); - break; - case 'stop': - $this->worker->server->processManager()->handler()->stop($this->worker->id, $this->worker->site_id); - break; - case 'restart': - $this->worker->server->processManager()->handler()->restart($this->worker->id, $this->worker->site_id); - break; - } - $this->worker->status = $this->successStatus; - $this->worker->save(); - event( - new Broadcast('manage-queue-finished', [ - 'queue' => $this->worker, - ]) - ); - } - - public function failed(): void - { - $this->worker->status = $this->failStatus; - $this->worker->save(); - event( - new Broadcast('manage-queue-failed', [ - 'message' => $this->failMessage, - 'queue' => $this->worker, - ]) - ); - } -} diff --git a/app/Jobs/Queue/Remove.php b/app/Jobs/Queue/Remove.php deleted file mode 100644 index 60291c2..0000000 --- a/app/Jobs/Queue/Remove.php +++ /dev/null @@ -1,41 +0,0 @@ -worker = $worker; - } - - public function handle(): void - { - $this->worker->server->processManager()->handler()->delete($this->worker->id, $this->worker->site_id); - $this->worker->delete(); - event( - new Broadcast('remove-queue-finished', [ - 'id' => $this->worker->id, - ]) - ); - } - - public function failed(): void - { - $this->worker->status = QueueStatus::FAILED; - $this->worker->save(); - event( - new Broadcast('remove-queue-failed', [ - 'message' => __('Failed to delete worker!'), - 'id' => $this->worker->id, - ]) - ); - } -} diff --git a/app/Jobs/Redirect/AddToServer.php b/app/Jobs/Redirect/AddToServer.php deleted file mode 100644 index b8915f6..0000000 --- a/app/Jobs/Redirect/AddToServer.php +++ /dev/null @@ -1,42 +0,0 @@ -redirect = $redirect; - } - - public function handle(): void - { - /** @var array $redirects */ - $redirects = Redirect::query()->where('site_id', $this->redirect->site_id)->get(); - $this->redirect->site->server->webserver()->handler()->updateRedirects($this->redirect->site, $redirects); - $this->redirect->status = 'ready'; - $this->redirect->save(); - event( - new Broadcast('create-redirect-finished', [ - 'redirect' => $this->redirect, - ]) - ); - } - - public function failed(): void - { - $this->redirect->status = 'failed'; - $this->redirect->delete(); - event( - new Broadcast('create-redirect-failed', [ - 'redirect' => $this->redirect, - ]) - ); - } -} diff --git a/app/Jobs/Redirect/DeleteFromServer.php b/app/Jobs/Redirect/DeleteFromServer.php deleted file mode 100644 index 38afd00..0000000 --- a/app/Jobs/Redirect/DeleteFromServer.php +++ /dev/null @@ -1,44 +0,0 @@ -redirect = $redirect; - } - - public function handle(): void - { - /** @var array $redirects */ - $redirects = Redirect::query() - ->where('site_id', $this->redirect->site_id) - ->where('id', '!=', $this->redirect->id) - ->get(); - $this->redirect->site->server->webserver()->handler()->updateRedirects($this->redirect->site, $redirects); - $this->redirect->delete(); - event( - new Broadcast('delete-redirect-finished', [ - 'id' => $this->redirect->id, - ]) - ); - } - - public function failed(): void - { - $this->redirect->status = 'failed'; - $this->redirect->save(); - event( - new Broadcast('delete-redirect-failed', [ - 'redirect' => $this->redirect, - ]) - ); - } -} diff --git a/app/Jobs/Script/ExecuteOn.php b/app/Jobs/Script/ExecuteOn.php deleted file mode 100644 index 58998dd..0000000 --- a/app/Jobs/Script/ExecuteOn.php +++ /dev/null @@ -1,61 +0,0 @@ -script = $script; - $this->server = $server; - $this->user = $user; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->scriptExecution = $this->script->executions()->create([ - 'server_id' => $this->server->id, - 'user' => $this->user, - ]); - $this->server->ssh($this->scriptExecution->user)->exec( - $this->script->content, - 'execute-script' - ); - $this->scriptExecution->finished_at = now(); - $this->scriptExecution->save(); - event( - new Broadcast('execute-script-finished', [ - 'execution' => $this->scriptExecution, - ]) - ); - } - - public function failed(): void - { - $this->scriptExecution->finished_at = now(); - $this->scriptExecution->save(); - event( - new Broadcast('execute-script-failed', [ - 'execution' => $this->scriptExecution, - ]) - ); - } -} diff --git a/app/Jobs/Server/CheckConnection.php b/app/Jobs/Server/CheckConnection.php deleted file mode 100644 index 4165a2d..0000000 --- a/app/Jobs/Server/CheckConnection.php +++ /dev/null @@ -1,51 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $status = $this->server->status; - $this->server->ssh()->connect(); - $this->server->refresh(); - if ($status == 'disconnected') { - $this->server->status = 'ready'; - $this->server->save(); - } - event( - new Broadcast('server-status-finished', [ - 'server' => $this->server, - ]) - ); - } - - public function failed(): void - { - $this->server->status = 'disconnected'; - $this->server->save(); - Notifier::send($this->server, new ServerDisconnected($this->server)); - event( - new Broadcast('server-status-failed', [ - 'server' => $this->server, - ]) - ); - } -} diff --git a/app/Jobs/Server/RebootServer.php b/app/Jobs/Server/RebootServer.php deleted file mode 100644 index 5e55cf9..0000000 --- a/app/Jobs/Server/RebootServer.php +++ /dev/null @@ -1,45 +0,0 @@ -server = $server; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec(new RebootCommand(), 'reboot'); - event( - new Broadcast('reboot-server-finished', [ - 'message' => __('The server is being rebooted. It can take several minutes to boot up'), - 'id' => $this->server->id, - ]) - ); - } - - public function failed(): void - { - $this->server->status = 'ready'; - $this->server->save(); - event( - new Broadcast('reboot-server-failed', [ - 'message' => __('Failed to reboot the server'), - 'server' => $this->server, - ]) - ); - } -} diff --git a/app/Jobs/Service/Install.php b/app/Jobs/Service/Install.php deleted file mode 100644 index 78896a9..0000000 --- a/app/Jobs/Service/Install.php +++ /dev/null @@ -1,24 +0,0 @@ -service = $service; - } - - public function handle() - { - } - - public function failed(\Throwable $throwable) - { - } -} diff --git a/app/Jobs/Service/Manage.php b/app/Jobs/Service/Manage.php deleted file mode 100644 index c6a7a43..0000000 --- a/app/Jobs/Service/Manage.php +++ /dev/null @@ -1,75 +0,0 @@ -service = $service; - $this->action = $action; - $this->successStatus = $successStatus; - $this->failStatus = $failStatus; - $this->failMessage = $failMessage; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $command = match ($this->action) { - 'start' => new StartServiceCommand($this->service->unit), - 'stop' => new StopServiceCommand($this->service->unit), - 'restart' => new RestartServiceCommand($this->service->unit), - default => throw new Exception('Invalid action'), - }; - $this->service->server->ssh()->exec( - $command, - $this->action.'-'.$this->service->name - ); - $this->service->status = $this->successStatus; - $this->service->save(); - event( - new Broadcast('update-service-finished', [ - 'service' => $this->service, - ]) - ); - } - - public function failed(): void - { - $this->service->status = $this->failStatus; - $this->service->save(); - event( - new Broadcast('update-service-failed', [ - 'message' => $this->service->name.' '.$this->failMessage, - 'service' => $this->service, - ]) - ); - } -} diff --git a/app/Jobs/Site/ChangePHPVersion.php b/app/Jobs/Site/ChangePHPVersion.php deleted file mode 100644 index e2b9c87..0000000 --- a/app/Jobs/Site/ChangePHPVersion.php +++ /dev/null @@ -1,43 +0,0 @@ -site = $site; - $this->version = $version; - } - - public function handle(): void - { - $this->site->php_version = $this->version; - $this->site->server->webserver()->handler()->changePHPVersion($this->site, $this->version); - $this->site->save(); - event( - new Broadcast('change-site-php-finished', [ - 'id' => $this->site->id, - 'php_version' => $this->site->php_version, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('change-site-php-failed', [ - 'message' => __('Failed to change PHP!'), - 'id' => $this->site->id, - ]) - ); - } -} diff --git a/app/Jobs/Site/CloneRepository.php b/app/Jobs/Site/CloneRepository.php deleted file mode 100755 index 4898ddf..0000000 --- a/app/Jobs/Site/CloneRepository.php +++ /dev/null @@ -1,35 +0,0 @@ -site = $site; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->site->server->ssh()->exec( - new CloneRepositoryCommand( - $this->site->full_repository_url, - $this->site->path, - $this->site->branch, - $this->site->ssh_key_name - ), - 'clone-repository', - $this->site->id - ); - } -} diff --git a/app/Jobs/Site/ComposerInstall.php b/app/Jobs/Site/ComposerInstall.php deleted file mode 100755 index e71deef..0000000 --- a/app/Jobs/Site/ComposerInstall.php +++ /dev/null @@ -1,34 +0,0 @@ -site = $site; - } - - /** - * @throws ComposerInstallFailed - * @throws Throwable - */ - public function handle(): void - { - $this->site->server->ssh()->exec( - new ComposerInstallCommand( - $this->site->path - ), - 'composer-install', - $this->site->id - ); - } -} diff --git a/app/Jobs/Site/CreateVHost.php b/app/Jobs/Site/CreateVHost.php deleted file mode 100755 index 95a93e2..0000000 --- a/app/Jobs/Site/CreateVHost.php +++ /dev/null @@ -1,21 +0,0 @@ -site = $site; - } - - public function handle(): void - { - $this->site->server->webserver()->handler()->createVHost($this->site); - } -} diff --git a/app/Jobs/Site/DeleteSite.php b/app/Jobs/Site/DeleteSite.php deleted file mode 100755 index 1242144..0000000 --- a/app/Jobs/Site/DeleteSite.php +++ /dev/null @@ -1,40 +0,0 @@ -site = $site; - } - - public function handle(): void - { - $this->site->server->webserver()->handler()->deleteSite($this->site); - $this->site->delete(); - event( - new Broadcast('delete-site-finished', [ - 'site' => $this->site, - ]) - ); - } - - public function failed(): void - { - $this->site->status = SiteStatus::READY; - $this->site->save(); - event( - new Broadcast('delete-site-failed', [ - 'site' => $this->site, - ]) - ); - } -} diff --git a/app/Jobs/Site/Deploy.php b/app/Jobs/Site/Deploy.php deleted file mode 100644 index 9b0cf43..0000000 --- a/app/Jobs/Site/Deploy.php +++ /dev/null @@ -1,64 +0,0 @@ -script = $deployment->deploymentScript->content; - $this->deployment = $deployment; - $this->path = $path; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $ssh = $this->deployment->site->server->ssh(); - try { - $ssh->exec( - new RunScriptCommand($this->path, $this->script), - 'deploy', - $this->deployment->site_id - ); - $this->deployment->status = DeploymentStatus::FINISHED; - $this->deployment->log_id = $ssh->log->id; - $this->deployment->save(); - event( - new Broadcast('deploy-site-finished', [ - 'deployment' => $this->deployment, - ]) - ); - } catch (Throwable) { - $this->deployment->log_id = $ssh->log->id; - $this->deployment->save(); - $this->failed(); - } - } - - public function failed(): void - { - $this->deployment->status = DeploymentStatus::FAILED; - $this->deployment->save(); - event( - new Broadcast('deploy-site-failed', [ - 'deployment' => $this->deployment, - ]) - ); - } -} diff --git a/app/Jobs/Site/DeployEnv.php b/app/Jobs/Site/DeployEnv.php deleted file mode 100644 index 1e396a3..0000000 --- a/app/Jobs/Site/DeployEnv.php +++ /dev/null @@ -1,46 +0,0 @@ -site = $site; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->site->server->ssh()->exec( - new EditFileCommand( - $this->site->path.'/.env', - $this->site->env - ) - ); - event( - new Broadcast('deploy-site-env-finished', [ - 'site' => $this->site, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('deploy-site-env-failed', [ - 'site' => $this->site, - ]) - ); - } -} diff --git a/app/Jobs/Site/DeployKey.php b/app/Jobs/Site/DeployKey.php deleted file mode 100755 index d99ac7a..0000000 --- a/app/Jobs/Site/DeployKey.php +++ /dev/null @@ -1,42 +0,0 @@ -site = $site; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->site->server->ssh()->exec( - new GenerateSshKeyCommand($this->site->ssh_key_name), - 'generate-ssh-key', - $this->site->id - ); - $this->site->ssh_key = $this->site->server->ssh()->exec( - new ReadSshKeyCommand($this->site->ssh_key_name), - 'read-public-key', - $this->site->id - ); - $this->site->save(); - $this->site->sourceControl()->provider()->deployKey( - $this->site->domain.'-key-'.$this->site->id, - $this->site->repository, - $this->site->ssh_key - ); - } -} diff --git a/app/Jobs/Site/InstallWordpress.php b/app/Jobs/Site/InstallWordpress.php deleted file mode 100755 index 760d5c1..0000000 --- a/app/Jobs/Site/InstallWordpress.php +++ /dev/null @@ -1,109 +0,0 @@ -site = $site; - } - - /** - * @throws ValidationException - * @throws FailedToInstallWordpress - * @throws Throwable - */ - public function handle(): void - { - $this->setupDatabase(); - - $result = $this->site->server->ssh()->exec( - new InstallWordpressCommand( - $this->site->path, - $this->site->domain, - $this->database->name, - $this->databaseUser->username, - $this->databaseUser->password, - 'localhost', - 'wp_', - $this->site->type_data['username'], - $this->site->type_data['password'], - $this->site->type_data['email'], - $this->site->type_data['title'], - ), - 'install-wordpress', - $this->site->id - ); - - if (! Str::contains($result, 'Success')) { - throw new FailedToInstallWordpress($result); - } - } - - /** - * @throws ValidationException - */ - private function setupDatabase() - { - // create database - $this->database = $this->site->server->databases()->where('name', $this->site->type_data['database'])->first(); - if (! $this->database) { - $this->database = new Database([ - 'server_id' => $this->site->server_id, - 'name' => $this->site->type_data['database'], - ]); - $this->database->server->database()->handler()->create($this->database->name); - $this->database->is_created = true; - $this->database->save(); - } - - // create database user - $this->databaseUser = $this->site->server->databaseUsers()->where('username', $this->site->type_data['database_user'])->first(); - if (! $this->databaseUser) { - $this->databaseUser = new DatabaseUser([ - 'server_id' => $this->site->server_id, - 'username' => $this->site->type_data['database_user'], - 'password' => Str::random(10), - 'host' => 'localhost', - ]); - $this->databaseUser->save(); - $this->databaseUser->server->database()->handler()->createUser($this->databaseUser->username, $this->databaseUser->password, $this->databaseUser->host); - $this->databaseUser->is_created = true; - $this->databaseUser->save(); - } - - // link database user - $linkedDatabases = $this->databaseUser->databases ?? []; - if (! in_array($this->database->name, $linkedDatabases)) { - $linkedDatabases[] = $this->database->name; - $this->databaseUser->databases = $linkedDatabases; - $this->databaseUser->server->database()->handler()->unlink( - $this->databaseUser->username, - $this->databaseUser->host, - ); - $this->databaseUser->server->database()->handler()->link( - $this->databaseUser->username, - $this->databaseUser->host, - $this->databaseUser->databases - ); - $this->databaseUser->save(); - } - } -} diff --git a/app/Jobs/Site/UpdateBranch.php b/app/Jobs/Site/UpdateBranch.php deleted file mode 100644 index f2606e6..0000000 --- a/app/Jobs/Site/UpdateBranch.php +++ /dev/null @@ -1,53 +0,0 @@ -site = $site; - $this->branch = $branch; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->site->server->ssh()->exec( - new UpdateBranchCommand( - $this->site->path, - $this->branch - ), - 'update-branch', - $this->site->id - ); - $this->site->branch = $this->branch; - $this->site->save(); - event( - new Broadcast('update-branch-finished', [ - 'site' => $this->site, - ]) - ); - } - - public function failed(): void - { - event( - new Broadcast('update-branch-failed', [ - 'site' => $this->site, - ]) - ); - } -} diff --git a/app/Jobs/Site/UpdateVHost.php b/app/Jobs/Site/UpdateVHost.php deleted file mode 100755 index 6dd616e..0000000 --- a/app/Jobs/Site/UpdateVHost.php +++ /dev/null @@ -1,21 +0,0 @@ -site = $site; - } - - public function handle(): void - { - $this->site->server->webserver()->handler()->updateVHost($this->site); - } -} diff --git a/app/Jobs/SshKey/DeleteSshKeyFromServer.php b/app/Jobs/SshKey/DeleteSshKeyFromServer.php deleted file mode 100644 index 26fbeb4..0000000 --- a/app/Jobs/SshKey/DeleteSshKeyFromServer.php +++ /dev/null @@ -1,50 +0,0 @@ -server = $server; - $this->sshKey = $sshKey; - } - - /** - * @throws Throwable - */ - public function handle() - { - $this->server->ssh()->exec( - new DeleteSshKeyCommand($this->sshKey->public_key), - 'delete-ssh-key' - ); - $this->server->sshKeys()->detach($this->sshKey); - event( - new Broadcast('delete-ssh-key-finished', [ - 'sshKey' => $this->sshKey, - ]) - ); - } - - public function failed(): void - { - $this->server->sshKeys()->attach($this->sshKey); - event( - new Broadcast('delete-ssh-key-failed', [ - 'sshKey' => $this->sshKey, - ]) - ); - } -} diff --git a/app/Jobs/SshKey/DeploySshKeyToServer.php b/app/Jobs/SshKey/DeploySshKeyToServer.php deleted file mode 100644 index b5d5b67..0000000 --- a/app/Jobs/SshKey/DeploySshKeyToServer.php +++ /dev/null @@ -1,53 +0,0 @@ -server = $server; - $this->sshKey = $sshKey; - } - - /** - * @throws Throwable - */ - public function handle(): void - { - $this->server->ssh()->exec( - new DeploySshKeyCommand($this->sshKey->public_key), - 'deploy-ssh-key' - ); - $this->sshKey->servers()->updateExistingPivot($this->server->id, [ - 'status' => SshKeyStatus::ADDED, - ]); - event( - new Broadcast('deploy-ssh-key-finished', [ - 'sshKey' => $this->sshKey, - ]) - ); - } - - public function failed(): void - { - $this->server->sshKeys()->detach($this->sshKey); - event( - new Broadcast('deploy-ssh-key-failed', [ - 'sshKey' => $this->sshKey, - ]) - ); - } -} diff --git a/app/Jobs/Ssl/Deploy.php b/app/Jobs/Ssl/Deploy.php deleted file mode 100644 index cead66d..0000000 --- a/app/Jobs/Ssl/Deploy.php +++ /dev/null @@ -1,48 +0,0 @@ -ssl = $ssl; - } - - public function handle(): void - { - $this->ssl->site->server->webserver()->handler()->setupSSL($this->ssl); - $this->ssl->status = SslStatus::CREATED; - $this->ssl->save(); - event( - new Broadcast('deploy-ssl-finished', [ - 'ssl' => $this->ssl, - ]) - ); - if ($this->ssl->site->type == SiteType::WORDPRESS) { - $typeData = $this->ssl->site->type_data; - $typeData['url'] = $this->ssl->site->url; - $this->ssl->site->type_data = $typeData; - $this->ssl->site->save(); - $this->ssl->site->type()->edit(); - } - } - - public function failed(): void - { - event( - new Broadcast('deploy-ssl-failed', [ - 'ssl' => $this->ssl, - ]) - ); - $this->ssl->delete(); - } -} diff --git a/app/Jobs/Ssl/Remove.php b/app/Jobs/Ssl/Remove.php deleted file mode 100644 index 6134155..0000000 --- a/app/Jobs/Ssl/Remove.php +++ /dev/null @@ -1,39 +0,0 @@ -ssl = $ssl; - } - - public function handle(): void - { - $this->ssl->site->server->webserver()->handler()->removeSSL($this->ssl); - $this->ssl->delete(); - event( - new Broadcast('remove-ssl-finished', [ - 'ssl' => $this->ssl, - ]) - ); - } - - public function failed(): void - { - $this->ssl->status = 'failed'; - $this->ssl->save(); - event( - new Broadcast('remove-ssl-failed', [ - 'ssl' => $this->ssl, - ]) - ); - } -} diff --git a/app/Jobs/StorageProvider/DeleteFile.php b/app/Jobs/StorageProvider/DeleteFile.php deleted file mode 100644 index ae715c8..0000000 --- a/app/Jobs/StorageProvider/DeleteFile.php +++ /dev/null @@ -1,24 +0,0 @@ -storageProvider = $storageProvider; - $this->paths = $paths; - } - - public function handle(): void - { - $this->storageProvider->provider()->delete($this->paths); - } -} diff --git a/app/Listeners/BroadcastListener.php b/app/Listeners/BroadcastListener.php deleted file mode 100644 index 8623ebe..0000000 --- a/app/Listeners/BroadcastListener.php +++ /dev/null @@ -1,21 +0,0 @@ - $event->type, - 'data' => $event->data, - ], now()->addMinutes(5)); - } -} diff --git a/app/Models/Backup.php b/app/Models/Backup.php index 2a8c382..21a2c81 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -2,12 +2,9 @@ namespace App\Models; -use App\Enums\BackupFileStatus; -use App\Jobs\Backup\RunBackup; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; -use Illuminate\Support\Str; /** * @property string $type @@ -71,16 +68,4 @@ public function files(): HasMany { return $this->hasMany(BackupFile::class, 'backup_id'); } - - public function run(): void - { - $file = new BackupFile([ - 'backup_id' => $this->id, - 'name' => Str::of($this->database->name)->slug().'-'.now()->format('YmdHis'), - 'status' => BackupFileStatus::CREATING, - ]); - $file->save(); - - dispatch(new RunBackup($file))->onConnection('ssh'); - } } diff --git a/app/Models/BackupFile.php b/app/Models/BackupFile.php index f3f8bfe..ea6ba66 100644 --- a/app/Models/BackupFile.php +++ b/app/Models/BackupFile.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Enums\BackupFileStatus; -use App\Jobs\Backup\RestoreDatabase; -use App\Jobs\StorageProvider\DeleteFile; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -17,8 +14,6 @@ * @property string $restored_to * @property Carbon $restored_at * @property Backup $backup - * @property string $path - * @property string $storage_path */ class BackupFile extends AbstractModel { @@ -56,11 +51,12 @@ protected static function booted(): void } }); - static::deleted(function (BackupFile $backupFile) { - dispatch(new DeleteFile( - $backupFile->backup->storage, - [$backupFile->storage_path] - )); + static::deleting(function (BackupFile $backupFile) { + $provider = $backupFile->backup->storage->provider(); + $path = $backupFile->storagePath(); + dispatch(function () use ($provider, $path) { + $provider->delete([$path]); + }); }); } @@ -69,21 +65,13 @@ public function backup(): BelongsTo return $this->belongsTo(Backup::class); } - public function getPathAttribute(): string + public function path(): string { - return '/home/'.$this->backup->server->ssh_user.'/'.$this->name.'.zip'; + return '/home/'.$this->backup->server->getSshUser().'/'.$this->name.'.zip'; } - public function getStoragePathAttribute(): string + public function storagePath(): string { - return '/'.$this->name.'.zip'; - } - - public function restore(Database $database): void - { - $this->status = BackupFileStatus::RESTORING; - $this->restored_to = $database->name; - $this->save(); - dispatch(new RestoreDatabase($this, $database))->onConnection('ssh'); + return '/'.$this->backup->database->name.'/'.$this->name.'.zip'; } } diff --git a/app/Models/CronJob.php b/app/Models/CronJob.php index cba2ee4..2618a87 100755 --- a/app/Models/CronJob.php +++ b/app/Models/CronJob.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Enums\CronjobStatus; -use App\Jobs\CronJob\AddToServer; -use App\Jobs\CronJob\RemoveFromServer; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -13,7 +10,6 @@ * @property string $command * @property string $user * @property string $frequency - * @property string $frequency_label * @property bool $hidden * @property string $status * @property string $crontab @@ -37,19 +33,15 @@ class CronJob extends AbstractModel 'hidden' => 'boolean', ]; - protected $appends = [ - 'frequency_label', - ]; - public function server(): BelongsTo { return $this->belongsTo(Server::class); } - public function getCrontabAttribute(): string + public static function crontab(Server $server, string $user): string { $data = ''; - $cronJobs = $this->server->cronJobs()->where('user', $this->user)->get(); + $cronJobs = $server->cronJobs()->where('user', $user)->get(); foreach ($cronJobs as $key => $cronJob) { $data .= $cronJob->frequency.' '.$cronJob->command; if ($key != count($cronJobs) - 1) { @@ -60,19 +52,7 @@ public function getCrontabAttribute(): string return $data; } - public function addToServer(): void - { - dispatch(new AddToServer($this))->onConnection('ssh'); - } - - public function removeFromServer(): void - { - $this->status = CronjobStatus::DELETING; - $this->save(); - dispatch(new RemoveFromServer($this))->onConnection('ssh'); - } - - public function getFrequencyLabelAttribute(): string + public function frequencyLabel(): string { $labels = [ '* * * * *' => 'Every minute', diff --git a/app/Models/Database.php b/app/Models/Database.php index 304f7d9..5a109c6 100755 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Enums\DatabaseStatus; -use App\Jobs\Database\CreateOnServer; -use App\Jobs\Database\DeleteFromServer; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -54,24 +51,6 @@ public function server(): BelongsTo return $this->belongsTo(Server::class); } - /** - * create database on server - */ - public function createOnServer(string $queue = 'ssh'): void - { - dispatch(new CreateOnServer($this))->onConnection($queue); - } - - /** - * delete database from server - */ - public function deleteFromServer(string $queue = 'ssh'): void - { - $this->status = DatabaseStatus::DELETING; - $this->save(); - dispatch(new DeleteFromServer($this))->onConnection($queue); - } - public function backups(): HasMany { return $this->hasMany(Backup::class)->where('type', 'database'); diff --git a/app/Models/DatabaseUser.php b/app/Models/DatabaseUser.php index 492a512..9b75b98 100755 --- a/app/Models/DatabaseUser.php +++ b/app/Models/DatabaseUser.php @@ -2,12 +2,6 @@ namespace App\Models; -use App\Enums\DatabaseStatus; -use App\Jobs\DatabaseUser\CreateOnServer; -use App\Jobs\DatabaseUser\DeleteFromServer; -use App\Jobs\DatabaseUser\LinkUser; -use App\Jobs\DatabaseUser\UnlinkUser; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -19,7 +13,6 @@ * @property string $host * @property string $status * @property Server $server - * @property string $full_user */ class DatabaseUser extends AbstractModel { @@ -48,49 +41,4 @@ public function server(): BelongsTo { return $this->belongsTo(Server::class); } - - public function scopeHasDatabase(Builder $query, string $databaseName): Builder - { - return $query->where('databases', 'like', "%\"$databaseName\"%"); - } - - public function createOnServer(string $queue = 'ssh'): void - { - dispatch(new CreateOnServer($this))->onConnection($queue); - } - - public function deleteFromServer(string $queue = 'ssh'): void - { - $this->status = DatabaseStatus::DELETING; - $this->save(); - - dispatch(new DeleteFromServer($this))->onConnection($queue); - } - - public function linkNewDatabase(string $name): void - { - $linkedDatabases = $this->databases ?? []; - if (! in_array($name, $linkedDatabases)) { - $linkedDatabases[] = $name; - $this->databases = $linkedDatabases; - $this->unlinkUser(); - $this->linkUser(); - $this->save(); - } - } - - public function linkUser(string $queue = 'ssh'): void - { - dispatch(new LinkUser($this))->onConnection($queue); - } - - public function unlinkUser(string $queue = 'ssh'): void - { - dispatch(new UnlinkUser($this))->onConnection($queue); - } - - public function getFullUserAttribute(): string - { - return $this->username.'@'.$this->host; - } } diff --git a/app/Models/Deployment.php b/app/Models/Deployment.php index 4626af7..4ded05f 100755 --- a/app/Models/Deployment.php +++ b/app/Models/Deployment.php @@ -37,10 +37,6 @@ class Deployment extends AbstractModel 'commit_data' => 'json', ]; - protected $appends = [ - 'commit_id_short', - ]; - public function site(): BelongsTo { return $this->belongsTo(Site::class); @@ -55,13 +51,4 @@ public function log(): BelongsTo { return $this->belongsTo(ServerLog::class, 'log_id'); } - - public function getCommitIdShortAttribute(): string - { - if ($this->commit_id) { - return substr($this->commit_id, 0, 7); - } - - return ''; - } } diff --git a/app/Models/FirewallRule.php b/app/Models/FirewallRule.php index ef5a60e..aec1fe8 100755 --- a/app/Models/FirewallRule.php +++ b/app/Models/FirewallRule.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Enums\FirewallRuleStatus; -use App\Jobs\Firewall\AddToServer; -use App\Jobs\Firewall\RemoveFromServer; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -12,7 +9,6 @@ * @property int $server_id * @property string $type * @property string $protocol - * @property string $real_protocol * @property int $port * @property string $source * @property ?string $mask @@ -40,28 +36,12 @@ class FirewallRule extends AbstractModel 'port' => 'integer', ]; - protected $appends = [ - 'real_protocol', - ]; - public function server(): BelongsTo { return $this->belongsTo(Server::class); } - public function addToServer(): void - { - dispatch(new AddToServer($this))->onConnection('ssh'); - } - - public function removeFromServer(): void - { - $this->status = FirewallRuleStatus::DELETING; - $this->save(); - dispatch(new RemoveFromServer($this))->onConnection('ssh'); - } - - public function getRealProtocolAttribute(): string + public function getRealProtocol(): string { return $this->protocol === 'udp' ? 'udp' : 'tcp'; } diff --git a/app/Models/GitHook.php b/app/Models/GitHook.php index 34b3294..9c1cd58 100755 --- a/app/Models/GitHook.php +++ b/app/Models/GitHook.php @@ -2,12 +2,8 @@ namespace App\Models; -use Exception; -use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Support\Facades\DB; -use Throwable; /** * @property int $site_id @@ -52,11 +48,6 @@ public function sourceControl(): BelongsTo return $this->belongsTo(SourceControl::class); } - public function scopeHasEvent(Builder $query, string $event): Builder - { - return $query->where('events', 'like', "%\"{$event}\"%"); - } - public function deployHook(): void { $this->update( @@ -64,19 +55,9 @@ public function deployHook(): void ); } - /** - * @throws Throwable - */ public function destroyHook(): void { - DB::beginTransaction(); - try { - $this->sourceControl->provider()->destroyHook($this->site->repository, $this->hook_id); - $this->delete(); - DB::commit(); - } catch (Exception $e) { - DB::rollBack(); - throw $e; - } + $this->sourceControl->provider()->destroyHook($this->site->repository, $this->hook_id); + $this->delete(); } } diff --git a/app/Models/NotificationChannel.php b/app/Models/NotificationChannel.php index cd8522c..803cc07 100644 --- a/app/Models/NotificationChannel.php +++ b/app/Models/NotificationChannel.php @@ -2,7 +2,7 @@ namespace App\Models; -use App\Contracts\Notification; +use App\Notifications\NotificationInterface; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Notifications\Notifiable; @@ -33,14 +33,14 @@ class NotificationChannel extends AbstractModel 'is_default' => 'boolean', ]; - public function provider(): \App\Contracts\NotificationChannel + public function provider(): \App\NotificationChannels\NotificationChannel { $class = config('core.notification_channels_providers_class')[$this->provider]; return new $class($this); } - public static function notifyAll(Notification $notification): void + public static function notifyAll(NotificationInterface $notification): void { $channels = self::all(); foreach ($channels as $channel) { diff --git a/app/Models/Queue.php b/app/Models/Queue.php index ccecb4d..56aeb56 100644 --- a/app/Models/Queue.php +++ b/app/Models/Queue.php @@ -2,11 +2,6 @@ namespace App\Models; -use App\Enums\QueueStatus; -use App\Jobs\Queue\Deploy; -use App\Jobs\Queue\GetLogs; -use App\Jobs\Queue\Manage; -use App\Jobs\Queue\Remove; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -21,8 +16,6 @@ * @property int $redirect_stderr * @property string $stdout_logfile * @property string $status - * @property string $log_directory - * @property string $log_file * @property Server $server * @property Site $site */ @@ -73,71 +66,13 @@ public function site(): BelongsTo return $this->belongsTo(Site::class); } - public function getLogDirectoryAttribute(): string + public function getLogDirectory(): string { return '/home/'.$this->user.'/.logs/workers'; } - public function getLogFileAttribute(): string + public function getLogFile(): string { - return $this->log_directory.'/'.$this->id.'.log'; - } - - public function deploy(): void - { - dispatch(new Deploy($this))->onConnection('ssh'); - } - - private function action(string $type, string $status, string $successStatus, string $failStatus, string $failMessage): void - { - $this->status = $status; - $this->save(); - dispatch(new Manage($this, $type, $successStatus, $failStatus, $failMessage)) - ->onConnection('ssh'); - } - - public function start(): void - { - $this->action( - 'start', - QueueStatus::STARTING, - QueueStatus::RUNNING, - QueueStatus::FAILED, - __('Failed to start') - ); - } - - public function stop(): void - { - $this->action( - 'stop', - QueueStatus::STOPPING, - QueueStatus::STOPPED, - QueueStatus::FAILED, - __('Failed to stop') - ); - } - - public function restart(): void - { - $this->action( - 'restart', - QueueStatus::RESTARTING, - QueueStatus::RUNNING, - QueueStatus::FAILED, - __('Failed to restart') - ); - } - - public function remove(): void - { - $this->status = QueueStatus::DELETING; - $this->save(); - dispatch(new Remove($this))->onConnection('ssh'); - } - - public function getLogs(): void - { - dispatch(new GetLogs($this))->onConnection('ssh'); + return $this->getLogDirectory().'/'.$this->id.'.log'; } } diff --git a/app/Models/Redirect.php b/app/Models/Redirect.php deleted file mode 100644 index 214fc16..0000000 --- a/app/Models/Redirect.php +++ /dev/null @@ -1,50 +0,0 @@ - 'integer', - 'mode' => 'integer', - ]; - - public function site(): BelongsTo - { - return $this->belongsTo(Site::class); - } - - public function addToServer(): void - { - dispatch(new AddToServer($this))->onConnection('ssh'); - } - - public function deleteFromServer(): void - { - $this->status = 'deleting'; - $this->save(); - dispatch(new DeleteFromServer($this))->onConnection('ssh'); - } -} diff --git a/app/Models/Script.php b/app/Models/Script.php deleted file mode 100644 index 2abed49..0000000 --- a/app/Models/Script.php +++ /dev/null @@ -1,44 +0,0 @@ - 'integer', - ]; - - public function creator(): BelongsTo - { - return $this->belongsTo(User::class); - } - - public function executions(): HasMany - { - return $this->hasMany(ScriptExecution::class, 'script_id'); - } - - public function executeOn(Server $server, string $user): void - { - dispatch(new ExecuteOn($this, $server, $user))->onConnection('ssh'); - } -} diff --git a/app/Models/ScriptExecution.php b/app/Models/ScriptExecution.php deleted file mode 100644 index f0aa0cd..0000000 --- a/app/Models/ScriptExecution.php +++ /dev/null @@ -1,42 +0,0 @@ - 'integer', - 'server_id' => 'integer', - ]; - - public function script(): BelongsTo - { - return $this->belongsTo(Script::class); - } - - public function server(): BelongsTo - { - return $this->belongsTo(Server::class); - } -} diff --git a/app/Models/Server.php b/app/Models/Server.php index 8092c49..7e63f01 100755 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,16 +2,13 @@ namespace App\Models; -use App\Contracts\ServerType; -use App\Enums\ServerStatus; +use App\Actions\Server\CheckConnection; use App\Enums\ServiceStatus; -use App\Facades\Notifier; use App\Facades\SSH; -use App\Jobs\Installation\Upgrade; -use App\Jobs\Server\CheckConnection; -use App\Jobs\Server\RebootServer; -use App\Notifications\ServerInstallationStarted; -use App\Support\Testing\SSHFake; +use App\ServerTypes\ServerType; +use App\SSH\Cron\Cron; +use App\SSH\OS\OS; +use App\SSH\Systemd\Systemd; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -54,7 +51,6 @@ * @property FirewallRule[] $firewallRules * @property CronJob[] $cronJobs * @property Queue[] $queues - * @property ScriptExecution[] $scriptExecutions * @property Backup[] $backups * @property Queue[] $daemons * @property SshKey[] $sshKeys @@ -124,7 +120,6 @@ public static function boot(): void $server->cronJobs()->delete(); $server->queues()->delete(); $server->daemons()->delete(); - $server->scriptExecutions()->delete(); $server->sshKeys()->detach(); if (File::exists($server->sshKey()['public_key_path'])) { File::delete($server->sshKey()['public_key_path']); @@ -190,11 +185,6 @@ public function queues(): HasMany return $this->hasMany(Queue::class); } - public function scriptExecutions(): HasMany - { - return $this->hasMany(ScriptExecution::class); - } - public function backups(): HasMany { return $this->hasMany(Backup::class); @@ -205,6 +195,22 @@ public function daemons(): HasMany return $this->queues()->whereNull('site_id'); } + public function sshKeys(): BelongsToMany + { + return $this->belongsToMany(SshKey::class, 'server_ssh_keys') + ->withPivot('status') + ->withTimestamps(); + } + + public function getSshUser(): string + { + if ($this->ssh_user) { + return $this->ssh_user; + } + + return config('core.ssh_user'); + } + public function service($type, $version = null): ?Service { /* @var Service $service */ @@ -230,6 +236,7 @@ public function defaultService($type): ?Service // If no default service found, get the first service with status ready or stopped if (! $service) { + /** @var Service $service */ $service = $this->services() ->where('type', $type) ->whereIn('status', [ServiceStatus::READY, ServiceStatus::STOPPED]) @@ -243,24 +250,7 @@ public function defaultService($type): ?Service return $service; } - public function getServiceByUnit($unit): ?Service - { - /* @var Service $service */ - $service = $this->services() - ->where('unit', $unit) - ->where('is_default', 1) - ->first(); - - return $service; - } - - public function install(): void - { - $this->type()->install(); - Notifier::send($this, new ServerInstallationStarted($this)); - } - - public function ssh(?string $user = null): \App\Helpers\SSH|SSHFake + public function ssh(?string $user = null): mixed { return SSH::init($this, $user); } @@ -283,7 +273,7 @@ public function type(): ServerType return new $typeClass($this); } - public function provider(): \App\Contracts\ServerProvider + public function provider(): \App\ServerProviders\ServerProvider { $providerClass = config('core.server_providers_class')[$this->provider]; @@ -335,32 +325,8 @@ public function php(?string $version = null): ?Service return $this->service('php', $version); } - public function sshKeys(): BelongsToMany - { - return $this->belongsToMany(SshKey::class, 'server_ssh_keys') - ->withPivot('status') - ->withTimestamps(); - } - - public function getSshUserAttribute(string $value): string - { - if ($value) { - return $value; - } - - return config('core.ssh_user'); - } - public function sshKey(): array { - if (app()->environment() == 'testing') { - return [ - 'public_key' => 'public', - 'public_key_path' => '/path', - 'private_key_path' => '/path', - ]; - } - /** @var FilesystemAdapter $storageDisk */ $storageDisk = Storage::disk(config('core.key_pairs_disk')); @@ -371,46 +337,28 @@ public function sshKey(): array ]; } - public function getServiceUnits(): array + public function checkConnection(): self { - $units = []; - $services = $this->services; - foreach ($services as $service) { - if ($service->unit) { - $units[] = $service->unit; - } - } - - return $units; + return app(CheckConnection::class)->check($this); } - public function checkConnection(): void - { - dispatch(new CheckConnection($this))->onConnection('ssh'); - } - - public function installUpdates(): void - { - $this->available_updates = 0; - $this->security_updates = 0; - $this->save(); - dispatch(new Upgrade($this))->onConnection('ssh'); - } - - public function reboot(): void - { - $this->status = 'disconnected'; - $this->save(); - dispatch(new RebootServer($this))->onConnection('ssh'); - } - - public function getHostnameAttribute(): string + public function hostname(): string { return Str::of($this->name)->slug(); } - public function isReady(): bool + public function os(): OS { - return $this->status == ServerStatus::READY; + return new OS($this); + } + + public function systemd(): Systemd + { + return new Systemd($this); + } + + public function cron(): Cron + { + return new Cron($this); } } diff --git a/app/Models/ServerLog.php b/app/Models/ServerLog.php index d52c39a..2efee2c 100755 --- a/app/Models/ServerLog.php +++ b/app/Models/ServerLog.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Storage; use Illuminate\Support\Str; @@ -15,7 +16,6 @@ * @property string $disk * @property Server $server * @property ?Site $site - * @property string $content */ class ServerLog extends AbstractModel { @@ -39,8 +39,12 @@ public static function boot(): void parent::boot(); static::deleting(function (ServerLog $log) { - if (Storage::disk($log->disk)->exists($log->name)) { - Storage::disk($log->disk)->delete($log->name); + try { + if (Storage::disk($log->disk)->exists($log->name)) { + Storage::disk($log->disk)->delete($log->name); + } + } catch (\Exception $e) { + Log::error($e->getMessage(), ['exception' => $e]); } }); } @@ -72,7 +76,7 @@ public function write($buf): void } } - public function getContentAttribute(): ?string + public function getContent(): ?string { if (Storage::disk($this->disk)->exists($this->name)) { return Storage::disk($this->disk)->get($this->name); @@ -80,4 +84,17 @@ public function getContentAttribute(): ?string return ''; } + + public static function log(Server $server, string $type, string $content, ?Site $site = null): void + { + $log = new static([ + 'server_id' => $server->id, + 'site_id' => $site?->id, + 'name' => $server->id.'-'.strtotime('now').'-'.$type.'.log', + 'type' => $type, + 'disk' => config('core.logs_disk'), + ]); + $log->save(); + $log->write($content); + } } diff --git a/app/Models/ServerProvider.php b/app/Models/ServerProvider.php index 366663c..130c39d 100644 --- a/app/Models/ServerProvider.php +++ b/app/Models/ServerProvider.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @property int $user_id @@ -40,4 +41,9 @@ public function getCredentials(): array { return $this->credentials; } + + public function servers(): HasMany + { + return $this->hasMany(Server::class, 'provider_id'); + } } diff --git a/app/Models/Service.php b/app/Models/Service.php index e747ad2..6c30d88 100755 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -2,18 +2,16 @@ namespace App\Models; -use App\Contracts\Database; -use App\Contracts\Firewall; -use App\Contracts\ProcessManager; -use App\Contracts\Webserver; -use App\Enums\ServiceStatus; -use App\Events\Broadcast; -use App\Exceptions\InstallationFailed; -use App\Jobs\Service\Manage; -use App\ServiceHandlers\PHP; +use App\Actions\Service\Manage; +use App\Exceptions\ServiceInstallationFailed; +use App\SSH\Services\Database\Database as DatabaseHandler; +use App\SSH\Services\Firewall\Firewall as FirewallHandler; +use App\SSH\Services\PHP\PHP as PHPHandler; +use App\SSH\Services\ProcessManager\ProcessManager as ProcessManagerHandler; +use App\SSH\Services\Redis\Redis as RedisHandler; +use App\SSH\Services\Webserver\Webserver as WebserverHandler; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; -use Illuminate\Support\Facades\Bus; use Illuminate\Support\Str; /** @@ -50,168 +48,59 @@ class Service extends AbstractModel 'is_default' => 'boolean', ]; + public static function boot(): void + { + parent::boot(); + + static::creating(function (Service $service) { + $service->unit = config('core.service_units')[$service->name][$service->server->os][$service->version]; + }); + } + public function server(): BelongsTo { return $this->belongsTo(Server::class); } - public function handler(): Database|Firewall|Webserver|PHP|ProcessManager - { + public function handler( + ): PHPHandler|WebserverHandler|DatabaseHandler|FirewallHandler|ProcessManagerHandler|RedisHandler { $handler = config('core.service_handlers')[$this->name]; return new $handler($this); } - public function installer(): mixed - { - $installer = config('core.service_installers')[$this->name]; - - return new $installer($this); - } - - public function uninstaller(): mixed - { - $uninstaller = config('core.service_uninstallers')[$this->name]; - - return new $uninstaller($this); - } - - public function getUnitAttribute($value): ?string - { - if ($value) { - return $value; - } - if (isset(config('core.service_units')[$this->name])) { - $value = config('core.service_units')[$this->name][$this->server->os][$this->version]; - if ($value) { - $this->fill(['unit' => $value]); - $this->save(); - } - } - - return $value; - } - - public function install(): void - { - Bus::chain([ - $this->installer(), - function () { - event( - new Broadcast('install-service-finished', [ - 'service' => $this, - ]) - ); - }, - ])->catch(function () { - event( - new Broadcast('install-service-failed', [ - 'service' => $this, - ]) - ); - })->onConnection('ssh-long')->dispatch(); - } - /** - * @throws InstallationFailed + * @throws ServiceInstallationFailed */ public function validateInstall($result): void { - if (Str::contains($result, 'Active: active')) { - event( - new Broadcast('install-service-finished', [ - 'service' => $this, - ]) - ); - } else { - event( - new Broadcast('install-service-failed', [ - 'service' => $this, - ]) - ); - throw new InstallationFailed(); + if (! Str::contains($result, 'Active: active')) { + throw new ServiceInstallationFailed(); } } - public function uninstall(): void - { - $this->status = ServiceStatus::UNINSTALLING; - $this->save(); - Bus::chain([ - $this->uninstaller(), - function () { - event( - new Broadcast('uninstall-service-finished', [ - 'service' => $this, - ]) - ); - $this->delete(); - }, - ])->catch(function () { - $this->status = ServiceStatus::FAILED; - $this->save(); - event( - new Broadcast('uninstall-service-failed', [ - 'service' => $this, - ]) - ); - })->onConnection('ssh')->dispatch(); - } - public function start(): void { - $this->action( - 'start', - ServiceStatus::STARTING, - ServiceStatus::READY, - ServiceStatus::STOPPED, - __('Failed to start') - ); + app(Manage::class)->start($this); } public function stop(): void { - $this->action( - 'stop', - ServiceStatus::STOPPING, - ServiceStatus::STOPPED, - ServiceStatus::FAILED, - __('Failed to stop') - ); + app(Manage::class)->stop($this); } public function restart(): void { - $this->action( - 'restart', - ServiceStatus::RESTARTING, - ServiceStatus::READY, - ServiceStatus::FAILED, - __('Failed to restart') - ); + app(Manage::class)->restart($this); } - public function action( - string $type, - string $status, - string $successStatus, - string $failStatus, - string $failMessage - ): void { - $this->status = $status; - $this->save(); - dispatch(new Manage($this, $type, $successStatus, $failStatus, $failMessage)) - ->onConnection('ssh'); - } - - public function installedVersions(): array + public function enable(): void { - $versions = []; - $services = $this->server->services()->where('type', $this->type)->get(['version']); - foreach ($services as $service) { - $versions[] = $service->version; - } + app(Manage::class)->enable($this); + } - return $versions; + public function disable(): void + { + app(Manage::class)->disable($this); } } diff --git a/app/Models/Site.php b/app/Models/Site.php index f2c59fb..66bd533 100755 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -2,30 +2,14 @@ namespace App\Models; -use App\Contracts\SiteType; -use App\Enums\DeploymentStatus; -use App\Enums\SiteStatus; -use App\Enums\SslStatus; -use App\Events\Broadcast; use App\Exceptions\SourceControlIsNotConnected; -use App\Facades\Notifier; -use App\Jobs\Site\ChangePHPVersion; -use App\Jobs\Site\Deploy; -use App\Jobs\Site\DeployEnv; -use App\Jobs\Site\UpdateBranch; -use App\Notifications\SiteInstallationFailed; -use App\Notifications\SiteInstallationSucceed; -use App\SSHCommands\Website\GetEnvCommand; -use Exception; +use App\SiteTypes\SiteType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasOne; -use Illuminate\Support\Facades\DB; -use Illuminate\Support\Facades\Log; use Illuminate\Support\Str; -use Throwable; /** * @property int $server_id @@ -34,7 +18,6 @@ * @property string $domain * @property array $aliases * @property string $web_directory - * @property string $web_directory_path * @property string $path * @property string $php_version * @property string $source_control @@ -45,21 +28,14 @@ * @property string $status * @property int $port * @property int $progress - * @property bool $auto_deployment - * @property string $url * @property Server $server * @property ServerLog[] $logs * @property Deployment[] $deployments * @property ?GitHook $gitHook * @property DeploymentScript $deploymentScript - * @property Redirect[] $redirects * @property Queue[] $queues * @property Ssl[] $ssls * @property ?Ssl $activeSsl - * @property string $full_repository_url - * @property string $aliases_string - * @property string $deployment_script_text - * @property string $env * @property string $ssh_key_name */ class Site extends AbstractModel @@ -90,22 +66,15 @@ class Site extends AbstractModel 'type_data' => 'json', 'port' => 'integer', 'progress' => 'integer', - 'auto_deployment' => 'boolean', 'aliases' => 'array', 'source_control_id' => 'integer', ]; - protected $appends = [ - 'url', - 'auto_deployment', - ]; - public static function boot(): void { parent::boot(); static::deleting(function (Site $site) { - $site->redirects()->delete(); $site->queues()->delete(); $site->ssls()->delete(); $site->deployments()->delete(); @@ -145,11 +114,6 @@ public function deploymentScript(): HasOne return $this->hasOne(DeploymentScript::class); } - public function redirects(): HasMany - { - return $this->hasMany(Redirect::class); - } - public function queues(): HasMany { return $this->hasMany(Queue::class); @@ -189,12 +153,12 @@ public function sourceControl(): SourceControl|HasOne|null|Model /** * @throws SourceControlIsNotConnected */ - public function getFullRepositoryUrlAttribute() + public function getFullRepositoryUrl() { - return $this->sourceControl()->provider()->fullRepoUrl($this->repository, $this->ssh_key_name); + return $this->sourceControl()->provider()->fullRepoUrl($this->repository, $this->getSshKeyName()); } - public function getAliasesStringAttribute(): string + public function getAliasesString(): string { if (count($this->aliases) > 0) { return implode(' ', $this->aliases); @@ -210,19 +174,6 @@ public function type(): SiteType return new $typeClass($this); } - public function install(): void - { - $this->type()->install(); - } - - public function remove(): void - { - $this->update([ - 'status' => SiteStatus::DELETING, - ]); - $this->type()->delete(); - } - public function php(): ?Service { if ($this->php_version) { @@ -234,63 +185,9 @@ public function php(): ?Service public function changePHPVersion($version): void { - dispatch(new ChangePHPVersion($this, $version))->onConnection('ssh'); - } - - public function getDeploymentScriptTextAttribute(): string - { - /* @var DeploymentScript $script */ - $script = $this->deploymentScript()->firstOrCreate([ - 'site_id' => $this->id, - ], [ - 'site_id' => $this->id, - 'name' => 'default', - ]); - - return $script->content; - } - - /** - * @throws SourceControlIsNotConnected - */ - public function deploy(): Deployment - { - if ($this->sourceControl()) { - $this->sourceControl()->getRepo($this->repository); - } - - $deployment = new Deployment([ - 'site_id' => $this->id, - 'deployment_script_id' => $this->deploymentScript->id, - 'status' => DeploymentStatus::DEPLOYING, - ]); - $lastCommit = $this->sourceControl()->provider()->getLastCommit($this->repository, $this->branch); - if ($lastCommit) { - $deployment->commit_id = $lastCommit['commit_id']; - $deployment->commit_data = $lastCommit['commit_data']; - } - $deployment->save(); - - dispatch(new Deploy($deployment, $this->path))->onConnection('ssh'); - - return $deployment; - } - - public function getEnvAttribute(): string - { - $typeData = $this->type_data; - if (! isset($typeData['env'])) { - $typeData['env'] = ''; - $this->type_data = $typeData; - $this->save(); - } - - return $typeData['env']; - } - - public function deployEnv(): void - { - dispatch(new DeployEnv($this))->onConnection('ssh'); + $this->server->webserver()->handler()->changePHPVersion($this, $version); + $this->php_version = $version; + $this->save(); } public function activeSsl(): HasOne @@ -300,33 +197,7 @@ public function activeSsl(): HasOne ->orderByDesc('id'); } - public function createFreeSsl(): void - { - $ssl = new Ssl([ - 'site_id' => $this->id, - 'type' => 'letsencrypt', - 'expires_at' => now()->addMonths(3), - 'status' => SslStatus::CREATING, - ]); - $ssl->save(); - $ssl->deploy(); - } - - public function createCustomSsl(string $certificate, string $pk): void - { - $ssl = new Ssl([ - 'site_id' => $this->id, - 'type' => 'custom', - 'certificate' => $certificate, - 'pk' => $pk, - 'expires_at' => '', - 'status' => SslStatus::CREATING, - ]); - $ssl->save(); - $ssl->deploy(); - } - - public function getUrlAttribute(): string + public function getUrl(): string { if ($this->activeSsl) { return 'https://'.$this->domain; @@ -335,7 +206,7 @@ public function getUrlAttribute(): string return 'http://'.$this->domain; } - public function getWebDirectoryPathAttribute(): string + public function getWebDirectoryPath(): string { if ($this->web_directory) { return $this->path.'/'.$this->web_directory; @@ -346,7 +217,6 @@ public function getWebDirectoryPathAttribute(): string /** * @throws SourceControlIsNotConnected - * @throws Throwable */ public function enableAutoDeployment(): void { @@ -354,102 +224,50 @@ public function enableAutoDeployment(): void return; } - if (! $this->sourceControl()) { + if (! $this->sourceControl()?->getRepo($this->repository)) { throw new SourceControlIsNotConnected($this->source_control); } - DB::beginTransaction(); - try { - $gitHook = new GitHook([ - 'site_id' => $this->id, - 'source_control_id' => $this->sourceControl()->id, - 'secret' => Str::uuid()->toString(), - 'actions' => ['deploy'], - 'events' => ['push'], - ]); - $gitHook->save(); - $gitHook->deployHook(); - DB::commit(); - } catch (Exception $e) { - DB::rollBack(); - throw $e; - } + $gitHook = new GitHook([ + 'site_id' => $this->id, + 'source_control_id' => $this->sourceControl()->id, + 'secret' => Str::uuid()->toString(), + 'actions' => ['deploy'], + 'events' => ['push'], + ]); + $gitHook->save(); + $gitHook->deployHook(); } /** - * @throws Throwable + * @throws SourceControlIsNotConnected */ public function disableAutoDeployment(): void { + if (! $this->sourceControl()?->getRepo($this->repository)) { + throw new SourceControlIsNotConnected($this->source_control); + } + $this->gitHook?->destroyHook(); } - public function getAutoDeploymentAttribute(): bool + public function isAutoDeployment(): bool { return (bool) $this->gitHook; } - public function updateBranch(string $branch): void - { - dispatch(new UpdateBranch($this, $branch))->onConnection('ssh'); - } - - public function getSshKeyNameAttribute(): string + public function getSshKeyName(): string { return str('site_'.$this->id)->toString(); } - public function installationFinished(): void - { - $this->update([ - 'status' => SiteStatus::READY, - 'progress' => 100, - ]); - event( - new Broadcast('install-site-finished', [ - 'site' => $this, - ]) - ); - Notifier::send($this, new SiteInstallationSucceed($this)); - } - - /** - * @throws Throwable - */ - public function installationFailed(Throwable $e): void - { - $this->update([ - 'status' => SiteStatus::INSTALLATION_FAILED, - ]); - event( - new Broadcast('install-site-failed', [ - 'site' => $this, - ]) - ); - Notifier::send($this, new SiteInstallationFailed($this)); - Log::error('install-site-error', [ - 'error' => (string) $e, - ]); - - throw $e; - } - public function hasFeature(string $feature): bool { return in_array($feature, $this->type()->supportedFeatures()); } - public function isReady(): bool - { - return $this->status === SiteStatus::READY; - } - public function getEnv(): string { - return $this->server->ssh()->exec( - new GetEnvCommand( - $this->domain - ) - ); + return $this->server->os()->readFile($this->path.'/.env'); } } diff --git a/app/Models/SourceControl.php b/app/Models/SourceControl.php index e3f10c4..93c04f6 100755 --- a/app/Models/SourceControl.php +++ b/app/Models/SourceControl.php @@ -2,11 +2,13 @@ namespace App\Models; -use App\Contracts\SourceControlProvider; +use App\SourceControlProviders\SourceControlProvider; use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @property string $provider + * @property array $provider_data * @property ?string $profile * @property ?string $url * @property string $access_token @@ -17,6 +19,7 @@ class SourceControl extends AbstractModel protected $fillable = [ 'provider', + 'provider_data', 'profile', 'url', 'access_token', @@ -24,6 +27,7 @@ class SourceControl extends AbstractModel protected $casts = [ 'access_token' => 'encrypted', + 'provider_data' => 'encrypted:array', ]; public function provider(): SourceControlProvider @@ -37,4 +41,9 @@ public function getRepo(?string $repo = null): ?array { return $this->provider()->getRepo($repo); } + + public function sites(): HasMany + { + return $this->hasMany(Site::class); + } } diff --git a/app/Models/SshKey.php b/app/Models/SshKey.php index 4f80343..dcb27e5 100644 --- a/app/Models/SshKey.php +++ b/app/Models/SshKey.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Enums\SshKeyStatus; -use App\Jobs\SshKey\DeleteSshKeyFromServer; -use App\Jobs\SshKey\DeploySshKeyToServer; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -47,20 +44,4 @@ public function existsOnServer(Server $server): bool { return (bool) $this->servers()->where('id', $server->id)->first(); } - - public function deployTo(Server $server): void - { - $server->sshKeys()->attach($this, [ - 'status' => SshKeyStatus::ADDING, - ]); - dispatch(new DeploySshKeyToServer($server, $this))->onConnection('ssh'); - } - - public function deleteFrom(Server $server): void - { - $this->servers()->updateExistingPivot($server->id, [ - 'status' => SshKeyStatus::DELETING, - ]); - dispatch(new DeleteSshKeyFromServer($server, $this))->onConnection('ssh'); - } } diff --git a/app/Models/Ssl.php b/app/Models/Ssl.php index b16baf2..29bd96d 100644 --- a/app/Models/Ssl.php +++ b/app/Models/Ssl.php @@ -2,9 +2,6 @@ namespace App\Models; -use App\Enums\SslStatus; -use App\Jobs\Ssl\Deploy; -use App\Jobs\Ssl\Remove; use Carbon\Carbon; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -19,9 +16,6 @@ * @property Carbon $expires_at * @property string $status * @property Site $site - * @property string $certs_directory_path - * @property string $certificate_path - * @property string $pk_path * @property string $ca_path */ class Ssl extends AbstractModel @@ -51,7 +45,7 @@ public function site(): BelongsTo return $this->belongsTo(Site::class); } - public function getCertsDirectoryPathAttribute(): ?string + public function getCertsDirectoryPath(): ?string { if ($this->type == 'letsencrypt') { return '/etc/letsencrypt/live/'.$this->site->domain; @@ -64,57 +58,45 @@ public function getCertsDirectoryPathAttribute(): ?string return ''; } - public function getCertificatePathAttribute(): ?string + public function getCertificatePath(): ?string { if ($this->type == 'letsencrypt') { return $this->certificate; } if ($this->type == 'custom') { - return $this->certs_directory_path.'/cert.pem'; + return $this->getCertsDirectoryPath().'/cert.pem'; } return ''; } - public function getPkPathAttribute(): ?string + public function getPkPath(): ?string { if ($this->type == 'letsencrypt') { return $this->pk; } if ($this->type == 'custom') { - return $this->certs_directory_path.'/privkey.pem'; + return $this->getCertsDirectoryPath().'/privkey.pem'; } return ''; } - public function getCaPathAttribute(): ?string + public function getCaPath(): ?string { if ($this->type == 'letsencrypt') { return $this->ca; } if ($this->type == 'custom') { - return $this->certs_directory_path.'/fullchain.pem'; + return $this->getCertsDirectoryPath().'/fullchain.pem'; } return ''; } - public function deploy(): void - { - dispatch(new Deploy($this))->onConnection('ssh'); - } - - public function remove(): void - { - $this->status = SslStatus::DELETING; - $this->save(); - dispatch(new Remove($this))->onConnection('ssh'); - } - public function validateSetup(string $result): bool { if (! Str::contains($result, 'Successfully received certificate')) { @@ -122,8 +104,8 @@ public function validateSetup(string $result): bool } if ($this->type == 'letsencrypt') { - $this->certificate = $this->certs_directory_path.'/fullchain.pem'; - $this->pk = $this->certs_directory_path.'/privkey.pem'; + $this->certificate = $this->getCertsDirectoryPath().'/fullchain.pem'; + $this->pk = $this->getCertsDirectoryPath().'/privkey.pem'; $this->save(); } diff --git a/app/Models/StorageProvider.php b/app/Models/StorageProvider.php index 6098f5b..7fe9b4c 100644 --- a/app/Models/StorageProvider.php +++ b/app/Models/StorageProvider.php @@ -4,6 +4,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; +use Illuminate\Database\Eloquent\Relations\HasMany; /** * @property int $user_id @@ -33,10 +34,15 @@ public function user(): BelongsTo return $this->belongsTo(User::class); } - public function provider(): \App\Contracts\StorageProvider + public function provider(): \App\StorageProviders\StorageProvider { $providerClass = config('core.storage_providers_class')[$this->provider]; return new $providerClass($this); } + + public function backups(): HasMany + { + return $this->hasMany(Backup::class, 'storage_id'); + } } diff --git a/app/Models/User.php b/app/Models/User.php index 5d18ddb..55aea29 100755 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -9,7 +9,6 @@ use Illuminate\Notifications\Notifiable; use Illuminate\Support\Collection; use Laravel\Fortify\TwoFactorAuthenticatable; -use Laravel\Sanctum\HasApiTokens; /** * @property int $id @@ -34,7 +33,6 @@ */ class User extends Authenticatable { - use HasApiTokens; use HasFactory; use Notifiable; use TwoFactorAuthenticatable; @@ -86,11 +84,6 @@ public function serverProviders(): HasMany return $this->hasMany(ServerProvider::class); } - public function scripts(): HasMany - { - return $this->hasMany(Script::class, 'user_id'); - } - public function sourceControl(string $provider): HasOne { return $this->hasOne(SourceControl::class)->where('provider', $provider); diff --git a/app/NotificationChannels/AbstractNotificationChannel.php b/app/NotificationChannels/AbstractNotificationChannel.php index 07d4468..d894580 100644 --- a/app/NotificationChannels/AbstractNotificationChannel.php +++ b/app/NotificationChannels/AbstractNotificationChannel.php @@ -2,8 +2,8 @@ namespace App\NotificationChannels; -use App\Contracts\NotificationChannel as NotificationChannelInterface; use App\Models\NotificationChannel; +use App\NotificationChannels\NotificationChannel as NotificationChannelInterface; abstract class AbstractNotificationChannel implements NotificationChannelInterface { diff --git a/app/NotificationChannels/Discord.php b/app/NotificationChannels/Discord.php index 069886a..3655adf 100644 --- a/app/NotificationChannels/Discord.php +++ b/app/NotificationChannels/Discord.php @@ -2,8 +2,8 @@ namespace App\NotificationChannels; -use App\Contracts\Notification; use App\Models\NotificationChannel; +use App\Notifications\NotificationInterface; use Illuminate\Support\Facades\Http; class Discord extends AbstractNotificationChannel @@ -59,7 +59,7 @@ private function checkConnection(string $subject, string $text): bool return $connect->ok(); } - public function send(object $notifiable, Notification $notification): void + public function send(object $notifiable, NotificationInterface $notification): void { /** @var NotificationChannel $notifiable */ $this->notificationChannel = $notifiable; diff --git a/app/NotificationChannels/Email.php b/app/NotificationChannels/Email.php index 440296b..6ff6006 100644 --- a/app/NotificationChannels/Email.php +++ b/app/NotificationChannels/Email.php @@ -2,9 +2,9 @@ namespace App\NotificationChannels; -use App\Contracts\Notification; -use App\Mail\NotificationMail; use App\Models\NotificationChannel; +use App\NotificationChannels\Email\NotificationMail; +use App\Notifications\NotificationInterface; use Illuminate\Support\Facades\Mail; use Throwable; @@ -47,7 +47,7 @@ public function connect(): bool return true; } - public function send(object $notifiable, Notification $notification): void + public function send(object $notifiable, NotificationInterface $notification): void { /** @var NotificationChannel $notifiable */ $this->notificationChannel = $notifiable; diff --git a/app/Mail/NotificationMail.php b/app/NotificationChannels/Email/NotificationMail.php similarity index 91% rename from app/Mail/NotificationMail.php rename to app/NotificationChannels/Email/NotificationMail.php index d0ecfe8..50a9cfe 100644 --- a/app/Mail/NotificationMail.php +++ b/app/NotificationChannels/Email/NotificationMail.php @@ -1,6 +1,6 @@ ok(); } - public function send(object $notifiable, Notification $notification): void + public function send(object $notifiable, NotificationInterface $notification): void { /** @var NotificationChannel $notifiable */ $this->notificationChannel = $notifiable; diff --git a/app/NotificationChannels/Telegram.php b/app/NotificationChannels/Telegram.php index d57e627..017399b 100644 --- a/app/NotificationChannels/Telegram.php +++ b/app/NotificationChannels/Telegram.php @@ -2,8 +2,8 @@ namespace App\NotificationChannels; -use App\Contracts\Notification; use App\Models\NotificationChannel; +use App\Notifications\NotificationInterface; use Illuminate\Support\Facades\Http; use Throwable; @@ -46,7 +46,7 @@ public function connect(): bool return true; } - public function send(object $notifiable, Notification $notification): void + public function send(object $notifiable, NotificationInterface $notification): void { /** @var NotificationChannel $notifiable */ $this->notificationChannel = $notifiable; @@ -60,6 +60,6 @@ private function sendToTelegram(string $text): void 'text' => $text, 'parse_mode' => 'markdown', 'disable_web_page_preview' => true, - ]); + ])->throw(); } } diff --git a/app/Notifications/AbstractNotification.php b/app/Notifications/AbstractNotification.php index 9afd974..78aa2f2 100644 --- a/app/Notifications/AbstractNotification.php +++ b/app/Notifications/AbstractNotification.php @@ -2,7 +2,6 @@ namespace App\Notifications; -use App\Contracts\Notification as NotificationInterface; use App\Models\NotificationChannel; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; diff --git a/app/Contracts/Notification.php b/app/Notifications/NotificationInterface.php similarity index 84% rename from app/Contracts/Notification.php rename to app/Notifications/NotificationInterface.php index 62bae46..5c59cc9 100644 --- a/app/Contracts/Notification.php +++ b/app/Notifications/NotificationInterface.php @@ -1,10 +1,10 @@ app->bind('notifier', function () { return new Notifier; }); - - $this->extendSocialite(); + $this->app->bind('toast', function () { + return new Toast; + }); if (str(config('app.url'))->startsWith('https://')) { URL::forceScheme('https'); } } - - /** - * @throws BindingResolutionException - */ - private function extendSocialite(): void - { - $socialite = $this->app->make('Laravel\Socialite\Contracts\Factory'); - $socialite->extend( - 'dropbox', - function ($app) use ($socialite) { - $config = $app['config']['services.dropbox']; - - return $socialite->buildProvider(DropboxProvider::class, $config); - } - ); - } } diff --git a/app/Providers/BroadcastServiceProvider.php b/app/Providers/BroadcastServiceProvider.php deleted file mode 100644 index 2be04f5..0000000 --- a/app/Providers/BroadcastServiceProvider.php +++ /dev/null @@ -1,19 +0,0 @@ -> - */ - protected $listen = [ - Registered::class => [ - SendEmailVerificationNotification::class, - ], - Broadcast::class => [ - BroadcastListener::class, - ], - ]; - - /** - * Register any events for your application. - */ - public function boot(): void - { - // - } - - /** - * Determine if events and listeners should be automatically discovered. - */ - public function shouldDiscoverEvents(): bool - { - return false; - } -} diff --git a/app/Providers/TelescopeServiceProvider.php b/app/Providers/TelescopeServiceProvider.php new file mode 100644 index 0000000..470ef17 --- /dev/null +++ b/app/Providers/TelescopeServiceProvider.php @@ -0,0 +1,64 @@ +hideSensitiveRequestDetails(); + + $isLocal = $this->app->environment('local'); + + Telescope::filter(function (IncomingEntry $entry) use ($isLocal) { + return $isLocal || + $entry->isReportableException() || + $entry->isFailedRequest() || + $entry->isFailedJob() || + $entry->isScheduledTask() || + $entry->hasMonitoredTag(); + }); + } + + /** + * Prevent sensitive request details from being logged by Telescope. + */ + protected function hideSensitiveRequestDetails(): void + { + if ($this->app->environment('local')) { + return; + } + + Telescope::hideRequestParameters(['_token']); + + Telescope::hideRequestHeaders([ + 'cookie', + 'x-csrf-token', + 'x-xsrf-token', + ]); + } + + /** + * Register the Telescope gate. + * + * This gate determines who can access Telescope in non-local environments. + */ + protected function gate(): void + { + Gate::define('viewTelescope', function ($user) { + return in_array($user->email, [ + // + ]); + }); + } +} diff --git a/app/SSH/Composer/Composer.php b/app/SSH/Composer/Composer.php new file mode 100644 index 0000000..654600d --- /dev/null +++ b/app/SSH/Composer/Composer.php @@ -0,0 +1,22 @@ +server->ssh()->exec( + $this->getScript('composer-install.sh', [ + 'path' => $site->path, + ]), + 'composer-install', + $site->id + ); + } +} diff --git a/resources/commands/website/composer-install.sh b/app/SSH/Composer/scripts/composer-install.sh similarity index 100% rename from resources/commands/website/composer-install.sh rename to app/SSH/Composer/scripts/composer-install.sh diff --git a/app/SSH/Cron/Cron.php b/app/SSH/Cron/Cron.php new file mode 100644 index 0000000..5bf69bf --- /dev/null +++ b/app/SSH/Cron/Cron.php @@ -0,0 +1,27 @@ +server->ssh()->exec($command); + } +} diff --git a/app/SSH/Git/Git.php b/app/SSH/Git/Git.php new file mode 100644 index 0000000..22c1f0d --- /dev/null +++ b/app/SSH/Git/Git.php @@ -0,0 +1,38 @@ +server->ssh()->exec( + $this->getScript('clone.sh', [ + 'host' => str($site->getFullRepositoryUrl())->after('@')->before('-'), + 'repo' => $site->getFullRepositoryUrl(), + 'path' => $site->path, + 'branch' => $site->branch, + 'key' => $site->getSshKeyName(), + ]), + 'clone-repository', + $site->id + ); + } + + public function checkout(Site $site): void + { + $site->server->ssh()->exec( + $this->getScript('checkout.sh', [ + 'path' => $site->path, + 'branch' => $site->branch, + ]), + 'checkout-branch', + $site->id + ); + } +} diff --git a/resources/commands/website/update-branch.sh b/app/SSH/Git/scripts/checkout.sh similarity index 100% rename from resources/commands/website/update-branch.sh rename to app/SSH/Git/scripts/checkout.sh diff --git a/resources/commands/website/clone-repository.sh b/app/SSH/Git/scripts/clone.sh similarity index 100% rename from resources/commands/website/clone-repository.sh rename to app/SSH/Git/scripts/clone.sh diff --git a/app/SSH/HasScripts.php b/app/SSH/HasScripts.php new file mode 100644 index 0000000..7a5ddf3 --- /dev/null +++ b/app/SSH/HasScripts.php @@ -0,0 +1,20 @@ +getFileName()).'/scripts'; + $script = file_get_contents($scriptsDir.'/'.$name); + foreach ($vars as $key => $value) { + $script = str_replace('__'.$key.'__', $value, $script); + } + + return $script; + } +} diff --git a/app/SSH/OS/OS.php b/app/SSH/OS/OS.php new file mode 100644 index 0000000..fe0befb --- /dev/null +++ b/app/SSH/OS/OS.php @@ -0,0 +1,134 @@ +server->ssh()->exec( + $this->getScript('install-dependencies.sh'), + 'install-dependencies' + ); + } + + public function upgrade(): void + { + $this->server->ssh()->exec( + $this->getScript('upgrade.sh'), + 'upgrade' + ); + } + + public function createUser(string $user, string $password, string $key): void + { + $this->server->ssh()->exec( + $this->getScript('create-user.sh', [ + 'user' => $user, + 'password' => $password, + 'key' => $key, + ]), + 'create-user' + ); + } + + public function getPublicKey(string $user): string + { + return $this->server->ssh()->exec( + $this->getScript('read-file.sh', [ + 'path' => '/home/'.$user.'/.ssh/id_rsa.pub', + ]) + ); + } + + public function deploySSHKey(string $key): void + { + $this->server->ssh()->exec( + $this->getScript('deploy-ssh-key.sh', [ + 'key' => $key, + ]), + 'deploy-ssh-key' + ); + } + + public function deleteSSHKey(string $key): void + { + $this->server->ssh()->exec( + $this->getScript('delete-ssh-key.sh', [ + 'key' => $key, + 'user' => $this->server->getSshUser(), + ]), + 'delete-ssh-key' + ); + } + + public function generateSSHKey(string $name): void + { + $this->server->ssh()->exec( + $this->getScript('generate-ssh-key.sh', [ + 'name' => $name, + ]), + 'generate-ssh-key' + ); + } + + public function readSSHKey(string $name): string + { + return $this->server->ssh()->exec( + $this->getScript('read-ssh-key.sh', [ + 'name' => $name, + ]), + ); + } + + public function reboot(): void + { + $this->server->ssh()->exec( + $this->getScript('reboot.sh'), + ); + } + + public function editFile(string $path, string $content): void + { + $this->server->ssh()->exec( + $this->getScript('edit-file.sh', [ + 'path' => $path, + 'content' => $content, + ]), + ); + } + + public function readFile(string $path): string + { + return $this->server->ssh()->exec( + $this->getScript('read-file.sh', [ + 'path' => $path, + ]) + ); + } + + public function runScript(string $path, string $script, ?int $siteId = null): ServerLog + { + $ssh = $this->server->ssh(); + $ssh->exec( + $this->getScript('run-script.sh', [ + 'path' => $path, + 'script' => $script, + ]), + 'run-script', + $siteId + ); + + return $ssh->log; + } +} diff --git a/resources/commands/system/create-user.sh b/app/SSH/OS/scripts/create-user.sh similarity index 100% rename from resources/commands/system/create-user.sh rename to app/SSH/OS/scripts/create-user.sh diff --git a/app/SSH/OS/scripts/delete-ssh-key.sh b/app/SSH/OS/scripts/delete-ssh-key.sh new file mode 100644 index 0000000..5a86e04 --- /dev/null +++ b/app/SSH/OS/scripts/delete-ssh-key.sh @@ -0,0 +1 @@ +bash -c 'ssh_key_to_delete="$1"; sed -i "\#${ssh_key_to_delete//\//\\/}#d" /home/vito/.ssh/authorized_keys' bash '__key__' diff --git a/resources/commands/system/deploy-ssh-key.sh b/app/SSH/OS/scripts/deploy-ssh-key.sh similarity index 100% rename from resources/commands/system/deploy-ssh-key.sh rename to app/SSH/OS/scripts/deploy-ssh-key.sh diff --git a/resources/commands/system/edit-file.sh b/app/SSH/OS/scripts/edit-file.sh similarity index 100% rename from resources/commands/system/edit-file.sh rename to app/SSH/OS/scripts/edit-file.sh diff --git a/resources/commands/system/generate-ssh-key.sh b/app/SSH/OS/scripts/generate-ssh-key.sh similarity index 100% rename from resources/commands/system/generate-ssh-key.sh rename to app/SSH/OS/scripts/generate-ssh-key.sh diff --git a/resources/commands/system/get-public-key.sh b/app/SSH/OS/scripts/get-public-key.sh similarity index 100% rename from resources/commands/system/get-public-key.sh rename to app/SSH/OS/scripts/get-public-key.sh diff --git a/app/SSH/OS/scripts/install-dependencies.sh b/app/SSH/OS/scripts/install-dependencies.sh new file mode 100755 index 0000000..c22b0b5 --- /dev/null +++ b/app/SSH/OS/scripts/install-dependencies.sh @@ -0,0 +1,3 @@ +sudo DEBIAN_FRONTEND=noninteractive apt-get install -y software-properties-common curl zip unzip git gcc openssl +git config --global user.email "__email__" +git config --global user.name "__name__" diff --git a/app/SSH/OS/scripts/read-file.sh b/app/SSH/OS/scripts/read-file.sh new file mode 100644 index 0000000..becea3d --- /dev/null +++ b/app/SSH/OS/scripts/read-file.sh @@ -0,0 +1 @@ +[ -f __path__ ] && cat __path__ diff --git a/resources/commands/system/read-ssh-key.sh b/app/SSH/OS/scripts/read-ssh-key.sh similarity index 100% rename from resources/commands/system/read-ssh-key.sh rename to app/SSH/OS/scripts/read-ssh-key.sh diff --git a/resources/commands/system/reboot.sh b/app/SSH/OS/scripts/reboot.sh similarity index 100% rename from resources/commands/system/reboot.sh rename to app/SSH/OS/scripts/reboot.sh diff --git a/resources/commands/system/run-script.sh b/app/SSH/OS/scripts/run-script.sh similarity index 100% rename from resources/commands/system/run-script.sh rename to app/SSH/OS/scripts/run-script.sh diff --git a/app/SSH/OS/scripts/upgrade.sh b/app/SSH/OS/scripts/upgrade.sh new file mode 100755 index 0000000..22e04ae --- /dev/null +++ b/app/SSH/OS/scripts/upgrade.sh @@ -0,0 +1,9 @@ +sudo DEBIAN_FRONTEND=noninteractive apt-get clean +sudo DEBIAN_FRONTEND=noninteractive apt-get update +sudo DEBIAN_FRONTEND=noninteractive apt-get upgrade -y +sudo DEBIAN_FRONTEND=noninteractive apt-get autoremove -y + +# Install Node.js +curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash -; +sudo DEBIAN_FRONTEND=noninteractive apt-get update +sudo DEBIAN_FRONTEND=noninteractive apt-get install nodejs -y diff --git a/app/SSH/Services/Database/AbstractDatabase.php b/app/SSH/Services/Database/AbstractDatabase.php new file mode 100755 index 0000000..538d77d --- /dev/null +++ b/app/SSH/Services/Database/AbstractDatabase.php @@ -0,0 +1,146 @@ +service = $service; + $this->server = $service->server; + } + + public function install(): void + { + $version = $this->service->version; + $command = $this->getScript($this->service->name.'/install-'.$version.'.sh'); + $this->server->ssh()->exec($command, 'install-'.$this->service->name.'-'.$version); + $status = $this->server->systemd()->status($this->service->unit); + $this->service->validateInstall($status); + } + + public function create(string $name): void + { + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/create.sh', [ + 'name' => $name, + ]), + 'create-database' + ); + } + + public function delete(string $name): void + { + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/delete.sh', [ + 'name' => $name, + ]), + 'delete-database' + ); + } + + public function createUser(string $username, string $password, string $host): void + { + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/create-user.sh', [ + 'username' => $username, + 'password' => $password, + 'host' => $host, + ]), + 'create-user' + ); + } + + public function deleteUser(string $username, string $host): void + { + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/delete-user.sh', [ + 'username' => $username, + 'host' => $host, + ]), + 'delete-user' + ); + } + + public function link(string $username, string $host, array $databases): void + { + $ssh = $this->server->ssh(); + + foreach ($databases as $database) { + $ssh->exec( + $this->getScript($this->getScriptsDir().'/link.sh', [ + 'username' => $username, + 'host' => $host, + 'database' => $database, + ]), + 'link-user-to-database' + ); + } + } + + public function unlink(string $username, string $host): void + { + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/unlink.sh', [ + 'username' => $username, + 'host' => $host, + ]), + 'unlink-user-from-databases' + ); + } + + public function runBackup(BackupFile $backupFile): void + { + // backup + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/backup.sh', [ + 'file' => $backupFile->name, + 'database' => $backupFile->backup->database->name, + ]), + 'backup-database' + ); + + // upload to storage + $upload = $backupFile->backup->storage->provider()->ssh($this->server)->upload( + $backupFile->path(), + $backupFile->storagePath(), + ); + + // cleanup + $this->server->ssh()->exec('rm '.$backupFile->name.'.zip'); + + $backupFile->size = $upload['size']; + $backupFile->save(); + } + + public function restoreBackup(BackupFile $backupFile, string $database): void + { + // download + $backupFile->backup->storage->provider()->ssh($this->server)->download( + $backupFile->storagePath(), + $backupFile->name.'.zip', + ); + + $this->server->ssh()->exec( + $this->getScript($this->getScriptsDir().'/restore.sh', [ + 'database' => $database, + 'file' => $backupFile->name, + ]), + 'restore-database' + ); + } +} diff --git a/app/Contracts/Database.php b/app/SSH/Services/Database/Database.php similarity index 94% rename from app/Contracts/Database.php rename to app/SSH/Services/Database/Database.php index aabcb24..a5ab82c 100755 --- a/app/Contracts/Database.php +++ b/app/SSH/Services/Database/Database.php @@ -1,6 +1,6 @@ /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + +sudo DEBIAN_FRONTEND=noninteractive apt-get install postgresql-12 -y + +systemctl status postgresql + +sudo -u postgres psql -c "SELECT version();" diff --git a/app/SSH/Services/Database/scripts/postgresql/install-13.sh b/app/SSH/Services/Database/scripts/postgresql/install-13.sh new file mode 100644 index 0000000..5945f71 --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/install-13.sh @@ -0,0 +1,11 @@ +sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + +sudo DEBIAN_FRONTEND=noninteractive apt-get install postgresql-13 -y + +systemctl status postgresql + +sudo -u postgres psql -c "SELECT version();" diff --git a/app/SSH/Services/Database/scripts/postgresql/install-14.sh b/app/SSH/Services/Database/scripts/postgresql/install-14.sh new file mode 100644 index 0000000..d60cd4f --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/install-14.sh @@ -0,0 +1,11 @@ +sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + +sudo DEBIAN_FRONTEND=noninteractive apt-get install postgresql-14 -y + +systemctl status postgresql + +sudo -u postgres psql -c "SELECT version();" diff --git a/app/SSH/Services/Database/scripts/postgresql/install-15.sh b/app/SSH/Services/Database/scripts/postgresql/install-15.sh new file mode 100644 index 0000000..d8de5fa --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/install-15.sh @@ -0,0 +1,11 @@ +sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + +sudo DEBIAN_FRONTEND=noninteractive apt-get install postgresql-15 -y + +systemctl status postgresql + +sudo -u postgres psql -c "SELECT version();" diff --git a/app/SSH/Services/Database/scripts/postgresql/install-16.sh b/app/SSH/Services/Database/scripts/postgresql/install-16.sh new file mode 100644 index 0000000..3a1f0c4 --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/install-16.sh @@ -0,0 +1,11 @@ +sudo sh -c 'echo "deb https://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + +wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo apt-key add - + +sudo DEBIAN_FRONTEND=noninteractive apt-get update -y + +sudo DEBIAN_FRONTEND=noninteractive apt-get install postgresql-16 -y + +systemctl status postgresql + +sudo -u postgres psql -c "SELECT version();" diff --git a/app/SSH/Services/Database/scripts/postgresql/link.sh b/app/SSH/Services/Database/scripts/postgresql/link.sh new file mode 100755 index 0000000..2459c2e --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/link.sh @@ -0,0 +1,5 @@ +if ! sudo -u postgres psql -c "GRANT ALL PRIVILEGES ON DATABASE __database__ TO __username__;"; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +echo "Linking to __database__ finished" diff --git a/app/SSH/Services/Database/scripts/postgresql/restore.sh b/app/SSH/Services/Database/scripts/postgresql/restore.sh new file mode 100644 index 0000000..eaa0896 --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/restore.sh @@ -0,0 +1,11 @@ +if ! DEBIAN_FRONTEND=noninteractive unzip __file__.zip; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! sudo -u postgres psql -d __database__ -f __file__.sql; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! rm __file__.sql __file__.zip; then + echo 'VITO_SSH_ERROR' && exit 1 +fi diff --git a/app/SSH/Services/Database/scripts/postgresql/unlink.sh b/app/SSH/Services/Database/scripts/postgresql/unlink.sh new file mode 100755 index 0000000..7c4e3f2 --- /dev/null +++ b/app/SSH/Services/Database/scripts/postgresql/unlink.sh @@ -0,0 +1,10 @@ +USER_TO_REVOKE='__username__' + +DATABASES=$(sudo -u postgres psql -t -c "SELECT datname FROM pg_database WHERE datistemplate = false;") + +for DB in $DATABASES; do + echo "Revoking privileges in database: $DB" + sudo -u postgres psql -d "$DB" -c "REVOKE ALL PRIVILEGES ON DATABASE \"$DB\" FROM $USER_TO_REVOKE;" +done + +echo "Privileges revoked from $USER_TO_REVOKE" diff --git a/app/ServiceHandlers/Firewall/AbstractFirewall.php b/app/SSH/Services/Firewall/AbstractFirewall.php similarity index 53% rename from app/ServiceHandlers/Firewall/AbstractFirewall.php rename to app/SSH/Services/Firewall/AbstractFirewall.php index a10673a..19b4724 100755 --- a/app/ServiceHandlers/Firewall/AbstractFirewall.php +++ b/app/SSH/Services/Firewall/AbstractFirewall.php @@ -1,11 +1,11 @@ service->server->ssh()->exec( + $this->getScript('ufw/install-ufw.sh'), + 'install-ufw' + ); + } + + public function addRule(string $type, string $protocol, int $port, string $source, ?string $mask): void + { + $this->service->server->ssh()->exec( + $this->getScript('ufw/add-rule.sh', [ + 'type' => $type, + 'protocol' => $protocol, + 'port' => $port, + 'source' => $source, + 'mask' => $mask || $mask == 0 ? '/'.$mask : '', + ]), + 'add-firewall-rule' + ); + } + + public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void + { + $this->service->server->ssh()->exec( + $this->getScript('ufw/remove-rule.sh', [ + 'type' => $type, + 'protocol' => $protocol, + 'port' => $port, + 'source' => $source, + 'mask' => $mask || $mask == 0 ? '/'.$mask : '', + ]), + 'remove-firewall-rule' + ); + } +} diff --git a/resources/commands/firewall/ufw/add-rule.sh b/app/SSH/Services/Firewall/scripts/ufw/add-rule.sh similarity index 100% rename from resources/commands/firewall/ufw/add-rule.sh rename to app/SSH/Services/Firewall/scripts/ufw/add-rule.sh diff --git a/resources/commands/firewall/ufw/install-ufw.sh b/app/SSH/Services/Firewall/scripts/ufw/install-ufw.sh similarity index 100% rename from resources/commands/firewall/ufw/install-ufw.sh rename to app/SSH/Services/Firewall/scripts/ufw/install-ufw.sh diff --git a/resources/commands/firewall/ufw/remove-rule.sh b/app/SSH/Services/Firewall/scripts/ufw/remove-rule.sh similarity index 100% rename from resources/commands/firewall/ufw/remove-rule.sh rename to app/SSH/Services/Firewall/scripts/ufw/remove-rule.sh diff --git a/app/SSH/Services/PHP/PHP.php b/app/SSH/Services/PHP/PHP.php new file mode 100644 index 0000000..21eb4ab --- /dev/null +++ b/app/SSH/Services/PHP/PHP.php @@ -0,0 +1,88 @@ +service = $service; + } + + public function install(): void + { + $server = $this->service->server; + $server->ssh()->exec( + $this->getScript('install-php.sh', [ + 'version' => $this->service->version, + 'user' => $server->getSshUser(), + ]), + 'install-php-'.$this->service->version + ); + } + + public function uninstall(): void + { + $this->service->server->ssh()->exec( + $this->getScript('uninstall-php.sh', [ + 'version' => $this->service->version, + ]), + 'uninstall-php-'.$this->service->version + ); + } + + public function setDefaultCli(): void + { + $this->service->server->ssh()->exec( + $this->getScript('change-default-php.sh', [ + 'version' => $this->service->version, + ]), + 'change-default-php' + ); + } + + /** + * @throws SSHCommandError + */ + public function installExtension($name): void + { + $result = $this->service->server->ssh()->exec( + $this->getScript('install-php-extension.sh', [ + 'version' => $this->service->version, + 'name' => $name, + ]), + 'install-php-extension-'.$name + ); + $result = Str::substr($result, strpos($result, '[PHP Modules]')); + if (! Str::contains($result, $name)) { + throw new SSHCommandError('Failed to install extension'); + } + } + + public function installComposer(): void + { + $this->service->server->ssh()->exec( + $this->getScript('install-composer.sh'), + 'install-composer' + ); + } + + public function getPHPIni(): string + { + return $this->service->server->ssh()->exec( + $this->getScript('get-php-ini.sh', [ + 'version' => $this->service->version, + ]) + ); + } +} diff --git a/resources/commands/php/change-default-php.sh b/app/SSH/Services/PHP/scripts/change-default-php.sh similarity index 100% rename from resources/commands/php/change-default-php.sh rename to app/SSH/Services/PHP/scripts/change-default-php.sh diff --git a/app/SSH/Services/PHP/scripts/get-php-ini.sh b/app/SSH/Services/PHP/scripts/get-php-ini.sh new file mode 100644 index 0000000..8e38788 --- /dev/null +++ b/app/SSH/Services/PHP/scripts/get-php-ini.sh @@ -0,0 +1,3 @@ +if ! cat /etc/php/__version__/cli/php.ini; then + echo 'VITO_SSH_ERROR' && exit 1 +fi diff --git a/resources/commands/php/install-composer.sh b/app/SSH/Services/PHP/scripts/install-composer.sh similarity index 100% rename from resources/commands/php/install-composer.sh rename to app/SSH/Services/PHP/scripts/install-composer.sh diff --git a/resources/commands/php/install-php-extension.sh b/app/SSH/Services/PHP/scripts/install-php-extension.sh similarity index 55% rename from resources/commands/php/install-php-extension.sh rename to app/SSH/Services/PHP/scripts/install-php-extension.sh index bda498e..726ed97 100644 --- a/resources/commands/php/install-php-extension.sh +++ b/app/SSH/Services/PHP/scripts/install-php-extension.sh @@ -1,4 +1,4 @@ -sudo apt install -y php__version__-__name__ +sudo apt-get install -y php__version__-__name__ sudo service php__version__-fpm restart diff --git a/app/SSH/Services/PHP/scripts/install-php.sh b/app/SSH/Services/PHP/scripts/install-php.sh new file mode 100755 index 0000000..0d1053b --- /dev/null +++ b/app/SSH/Services/PHP/scripts/install-php.sh @@ -0,0 +1,15 @@ +sudo add-apt-repository ppa:ondrej/php -y + +sudo DEBIAN_FRONTEND=noninteractive apt-get update + +if ! sudo DEBIAN_FRONTEND=noninteractive apt-get install -y php__version__ php__version__-fpm php__version__-mbstring php__version__-mysql php__version__-gd php__version__-xml php__version__-curl php__version__-gettext php__version__-zip php__version__-bcmath php__version__-soap php__version__-redis php__version__-sqlite3 php__version__-tokenizer php__version__-pgsql php__version__-pdo; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +if ! sudo sed -i 's/www-data/__user__/g' /etc/php/__version__/fpm/pool.d/www.conf; then + echo 'VITO_SSH_ERROR' && exit 1 +fi + +sudo service php__version__-fpm enable + +sudo service php__version__-fpm start diff --git a/app/SSH/Services/PHP/scripts/uninstall-php.sh b/app/SSH/Services/PHP/scripts/uninstall-php.sh new file mode 100755 index 0000000..6bb5bde --- /dev/null +++ b/app/SSH/Services/PHP/scripts/uninstall-php.sh @@ -0,0 +1,5 @@ +sudo service php__version__-fpm stop + +if ! sudo DEBIAN_FRONTEND=noninteractive apt-get remove -y php__version__ php__version__-fpm php__version__-mbstring php__version__-mysql php__version__-mcrypt php__version__-gd php__version__-xml php__version__-curl php__version__-gettext php__version__-zip php__version__-bcmath php__version__-soap php__version__-redis php__version__-sqlite3; then + echo 'VITO_SSH_ERROR' && exit 1 +fi diff --git a/resources/commands/php/update-php-settings.sh b/app/SSH/Services/PHP/scripts/update-php-settings.sh similarity index 100% rename from resources/commands/php/update-php-settings.sh rename to app/SSH/Services/PHP/scripts/update-php-settings.sh diff --git a/app/ServiceHandlers/ProcessManager/AbstractProcessManager.php b/app/SSH/Services/ProcessManager/AbstractProcessManager.php similarity index 50% rename from app/ServiceHandlers/ProcessManager/AbstractProcessManager.php rename to app/SSH/Services/ProcessManager/AbstractProcessManager.php index d72c24c..229ba69 100644 --- a/app/ServiceHandlers/ProcessManager/AbstractProcessManager.php +++ b/app/SSH/Services/ProcessManager/AbstractProcessManager.php @@ -1,11 +1,11 @@ service->server->ssh()->exec( + $this->getScript('supervisor/install-supervisor.sh'), + 'install-supervisor' + ); + } + /** * @throws Throwable */ @@ -27,9 +31,9 @@ public function create( ?int $siteId = null ): void { $this->service->server->ssh($user)->exec( - new CreateWorkerCommand( - $id, - $this->generateConfigFile( + $this->getScript('supervisor/create-worker.sh', [ + 'id' => $id, + 'config' => $this->generateConfigFile( $id, $command, $user, @@ -37,8 +41,8 @@ public function create( $autoRestart, $numprocs, $logFile - ) - ), + ), + ]), 'create-worker', $siteId ); @@ -50,7 +54,9 @@ public function create( public function delete(int $id, ?int $siteId = null): void { $this->service->server->ssh()->exec( - new DeleteWorkerCommand($id), + $this->getScript('supervisor/delete-worker.sh', [ + 'id' => $id, + ]), 'delete-worker', $siteId ); @@ -62,7 +68,9 @@ public function delete(int $id, ?int $siteId = null): void public function restart(int $id, ?int $siteId = null): void { $this->service->server->ssh()->exec( - new RestartWorkerCommand($id), + $this->getScript('supervisor/restart-worker.sh', [ + 'id' => $id, + ]), 'restart-worker', $siteId ); @@ -74,7 +82,9 @@ public function restart(int $id, ?int $siteId = null): void public function stop(int $id, ?int $siteId = null): void { $this->service->server->ssh()->exec( - new StopWorkerCommand($id), + $this->getScript('supervisor/stop-worker.sh', [ + 'id' => $id, + ]), 'stop-worker', $siteId ); @@ -86,7 +96,9 @@ public function stop(int $id, ?int $siteId = null): void public function start(int $id, ?int $siteId = null): void { $this->service->server->ssh()->exec( - new StartWorkerCommand($id), + $this->getScript('supervisor/start-worker.sh', [ + 'id' => $id, + ]), 'start-worker', $siteId ); @@ -111,14 +123,14 @@ private function generateConfigFile( int $numprocs, string $logFile ): string { - $config = File::get(resource_path('commands/supervisor/worker.conf')); - $config = Str::replace('__name__', (string) $id, $config); - $config = Str::replace('__command__', $command, $config); - $config = Str::replace('__user__', $user, $config); - $config = Str::replace('__auto_start__', var_export($autoStart, true), $config); - $config = Str::replace('__auto_restart__', var_export($autoRestart, true), $config); - $config = Str::replace('__numprocs__', (string) $numprocs, $config); - - return Str::replace('__log_file__', $logFile, $config); + return $this->getScript('supervisor/worker.conf', [ + 'name' => (string) $id, + 'command' => $command, + 'user' => $user, + 'auto_start' => var_export($autoStart, true), + 'auto_restart' => var_export($autoRestart, true), + 'numprocs' => (string) $numprocs, + 'log_file' => $logFile, + ]); } } diff --git a/resources/commands/supervisor/create-worker.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/create-worker.sh similarity index 100% rename from resources/commands/supervisor/create-worker.sh rename to app/SSH/Services/ProcessManager/scripts/supervisor/create-worker.sh diff --git a/resources/commands/supervisor/delete-worker.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/delete-worker.sh similarity index 100% rename from resources/commands/supervisor/delete-worker.sh rename to app/SSH/Services/ProcessManager/scripts/supervisor/delete-worker.sh diff --git a/resources/commands/supervisor/install-supervisor.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/install-supervisor.sh similarity index 100% rename from resources/commands/supervisor/install-supervisor.sh rename to app/SSH/Services/ProcessManager/scripts/supervisor/install-supervisor.sh diff --git a/resources/commands/supervisor/restart-worker.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/restart-worker.sh similarity index 100% rename from resources/commands/supervisor/restart-worker.sh rename to app/SSH/Services/ProcessManager/scripts/supervisor/restart-worker.sh diff --git a/resources/commands/supervisor/start-worker.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/start-worker.sh similarity index 100% rename from resources/commands/supervisor/start-worker.sh rename to app/SSH/Services/ProcessManager/scripts/supervisor/start-worker.sh diff --git a/resources/commands/supervisor/stop-worker.sh b/app/SSH/Services/ProcessManager/scripts/supervisor/stop-worker.sh similarity index 100% rename from resources/commands/supervisor/stop-worker.sh rename to app/SSH/Services/ProcessManager/scripts/supervisor/stop-worker.sh diff --git a/resources/commands/supervisor/worker.conf b/app/SSH/Services/ProcessManager/scripts/supervisor/worker.conf similarity index 100% rename from resources/commands/supervisor/worker.conf rename to app/SSH/Services/ProcessManager/scripts/supervisor/worker.conf diff --git a/app/SSH/Services/Redis/Redis.php b/app/SSH/Services/Redis/Redis.php new file mode 100644 index 0000000..1a1296b --- /dev/null +++ b/app/SSH/Services/Redis/Redis.php @@ -0,0 +1,24 @@ +service->server->ssh()->exec( + $this->getScript('install.sh'), + 'install-redis' + ); + } +} diff --git a/resources/commands/installation/install-redis.sh b/app/SSH/Services/Redis/scripts/install.sh similarity index 64% rename from resources/commands/installation/install-redis.sh rename to app/SSH/Services/Redis/scripts/install.sh index 1e8c733..85df312 100755 --- a/resources/commands/installation/install-redis.sh +++ b/app/SSH/Services/Redis/scripts/install.sh @@ -1,4 +1,4 @@ -sudo DEBIAN_FRONTEND=noninteractive apt install redis-server -y +sudo DEBIAN_FRONTEND=noninteractive apt-get install redis-server -y sudo sed -i 's/bind 127.0.0.1 ::1/bind 0.0.0.0/g' /etc/redis/redis.conf diff --git a/app/SSH/Services/ServiceInterface.php b/app/SSH/Services/ServiceInterface.php new file mode 100644 index 0000000..23646d0 --- /dev/null +++ b/app/SSH/Services/ServiceInterface.php @@ -0,0 +1,8 @@ +service->server->ssh()->exec( + $this->getScript('nginx/install-nginx.sh', [ + 'config' => $this->getScript('nginx/nginx.conf', [ + 'user' => $this->service->server->getSshUser(), + ]), + ]), + 'install-nginx' + ); + } + + public function createVHost(Site $site): void + { + $this->service->server->ssh()->exec( + $this->getScript('nginx/create-vhost.sh', [ + 'domain' => $site->domain, + 'path' => $site->path, + 'vhost' => $this->generateVhost($site), + ]), + 'create-vhost', + $site->id + ); + } + + public function updateVHost(Site $site, bool $noSSL = false, ?string $vhost = null): void + { + $this->service->server->ssh()->exec( + $this->getScript('nginx/update-vhost.sh', [ + 'domain' => $site->domain, + 'path' => $site->path, + 'vhost' => $this->generateVhost($site, $noSSL), + ]), + 'update-vhost', + $site->id + ); + } + + public function getVHost(Site $site): string + { + return $this->service->server->ssh()->exec( + $this->getScript('nginx/get-vhost.sh', [ + 'domain' => $site->domain, + ]), + ); + } + + public function deleteSite(Site $site): void + { + $this->service->server->ssh()->exec( + $this->getScript('nginx/delete-site.sh', [ + 'domain' => $site->domain, + 'path' => $site->path, + ]), + 'delete-vhost', + $site->id + ); + $this->service->restart(); + } + + public function changePHPVersion(Site $site, $version): void + { + $this->service->server->ssh()->exec( + $this->getScript('nginx/change-php-version.sh', [ + 'domain' => $site->domain, + 'old_version' => $site->php_version, + 'new_version' => $version, + ]), + 'change-php-version', + $site->id + ); + } + + /** + * @throws SSLCreationException + */ + public function setupSSL(Ssl $ssl): void + { + $command = $this->getScript('nginx/create-letsencrypt-ssl.sh', [ + 'email' => $ssl->site->server->creator->email, + 'domain' => $ssl->site->domain, + 'web_directory' => $ssl->site->getWebDirectoryPath(), + ]); + if ($ssl->type == 'custom') { + $command = $this->getScript('nginx/create-custom-ssl.sh', [ + 'path' => $ssl->getCertsDirectoryPath(), + 'certificate' => $ssl->certificate, + 'pk' => $ssl->pk, + 'certificate_path' => $ssl->getCertificatePath(), + 'pk_path' => $ssl->getPkPath(), + ]); + } + $result = $this->service->server->ssh()->exec( + $command, + 'create-ssl', + $ssl->site_id + ); + if (! $ssl->validateSetup($result)) { + throw new SSLCreationException(); + } + + $this->updateVHost($ssl->site); + } + + /** + * @throws Throwable + */ + public function removeSSL(Ssl $ssl): void + { + $this->service->server->ssh()->exec( + 'sudo rm -rf '.$ssl->getCertsDirectoryPath().'*', + 'remove-ssl', + $ssl->site_id + ); + + $this->updateVHost($ssl->site, true); + + $this->service->server->systemd()->restart('nginx'); + } + + protected function generateVhost(Site $site, bool $noSSL = false): string + { + $ssl = $site->activeSsl; + if ($noSSL) { + $ssl = null; + } + $vhost = $this->getScript('nginx/vhost.conf'); + if ($ssl) { + $vhost = $this->getScript('nginx/vhost-ssl.conf'); + } + if ($site->type()->language() === 'php') { + $vhost = $this->getScript('nginx/php-vhost.conf'); + if ($ssl) { + $vhost = $this->getScript('nginx/php-vhost-ssl.conf'); + } + } + if ($site->port) { + $vhost = $this->getScript('nginx/reverse-vhost.conf'); + if ($ssl) { + $vhost = $this->getScript('nginx/reverse-vhost-ssl.conf'); + } + $vhost = Str::replace('__port__', (string) $site->port, $vhost); + } + + $vhost = Str::replace('__domain__', $site->domain, $vhost); + $vhost = Str::replace('__aliases__', $site->getAliasesString(), $vhost); + $vhost = Str::replace('__path__', $site->path, $vhost); + $vhost = Str::replace('__web_directory__', $site->web_directory, $vhost); + + if ($ssl) { + $vhost = Str::replace('__certificate__', $ssl->getCertificatePath(), $vhost); + $vhost = Str::replace('__private_key__', $ssl->getPkPath(), $vhost); + } + + if ($site->php_version) { + $vhost = Str::replace('__php_version__', $site->php_version, $vhost); + } + + return $vhost; + } +} diff --git a/app/Contracts/Webserver.php b/app/SSH/Services/Webserver/Webserver.php similarity index 83% rename from app/Contracts/Webserver.php rename to app/SSH/Services/Webserver/Webserver.php index 39698e8..4137d32 100755 --- a/app/Contracts/Webserver.php +++ b/app/SSH/Services/Webserver/Webserver.php @@ -1,6 +1,6 @@ server->ssh()->exec( + $this->getScript('dropbox/upload.sh', [ + 'src' => $src, + 'dest' => $dest, + 'token' => $this->storageProvider->credentials['token'], + ]), + 'upload-to-dropbox' + ); + + $data = json_decode($upload, true); + + if (isset($data['error'])) { + Log::error('Failed to upload to Dropbox', $data); + throw new SSHCommandError('Failed to upload to Dropbox'); + } + + return [ + 'size' => $data['size'] ?? null, + ]; + } + + public function download(string $src, string $dest): void + { + $this->server->ssh()->exec( + $this->getScript('dropbox/download.sh', [ + 'src' => $src, + 'dest' => $dest, + 'token' => $this->storageProvider->credentials['token'], + ]), + 'download-from-dropbox' + ); + } +} diff --git a/app/SSH/Storage/FTP.php b/app/SSH/Storage/FTP.php new file mode 100644 index 0000000..245a055 --- /dev/null +++ b/app/SSH/Storage/FTP.php @@ -0,0 +1,48 @@ +server->ssh()->exec( + $this->getScript('ftp/upload.sh', [ + 'src' => $src, + 'dest' => $this->storageProvider->credentials['path'].'/'.$dest, + 'host' => $this->storageProvider->credentials['host'], + 'port' => $this->storageProvider->credentials['port'], + 'username' => $this->storageProvider->credentials['username'], + 'password' => $this->storageProvider->credentials['password'], + 'ssl' => $this->storageProvider->credentials['ssl'], + 'passive' => $this->storageProvider->credentials['passive'], + ]), + 'upload-to-ftp' + ); + + return [ + 'size' => null, + ]; + } + + public function download(string $src, string $dest): void + { + $this->server->ssh()->exec( + $this->getScript('ftp/download.sh', [ + 'src' => $this->storageProvider->credentials['path'].'/'.$src, + 'dest' => $dest, + 'host' => $this->storageProvider->credentials['host'], + 'port' => $this->storageProvider->credentials['port'], + 'username' => $this->storageProvider->credentials['username'], + 'password' => $this->storageProvider->credentials['password'], + 'ssl' => $this->storageProvider->credentials['ssl'], + 'passive' => $this->storageProvider->credentials['passive'], + ]), + 'download-from-ftp' + ); + } +} diff --git a/app/SSH/Storage/Storage.php b/app/SSH/Storage/Storage.php new file mode 100644 index 0000000..cd54709 --- /dev/null +++ b/app/SSH/Storage/Storage.php @@ -0,0 +1,10 @@ +server->ssh()->exec($command, sprintf('status-%s', $unit)); + } + + public function start(string $unit): string + { + $command = <<server->ssh()->exec($command, sprintf('start-%s', $unit)); + } + + public function stop(string $unit): string + { + $command = <<server->ssh()->exec($command, sprintf('stop-%s', $unit)); + } + + public function restart(string $unit): string + { + $command = <<server->ssh()->exec($command, sprintf('restart-%s', $unit)); + } + + public function enable(string $unit): string + { + $command = <<server->ssh()->exec($command, sprintf('enable-%s', $unit)); + } + + public function disable(string $unit): string + { + $command = <<server->ssh()->exec($command, sprintf('disable-%s', $unit)); + } +} diff --git a/app/SSH/Wordpress/Wordpress.php b/app/SSH/Wordpress/Wordpress.php new file mode 100644 index 0000000..ca48767 --- /dev/null +++ b/app/SSH/Wordpress/Wordpress.php @@ -0,0 +1,31 @@ +server->ssh()->exec( + $this->getScript('install.sh', [ + 'path' => $site->path, + 'domain' => $site->domain, + 'db_name' => $site->type_data['database'], + 'db_user' => $site->type_data['database_user'], + 'db_pass' => $site->type_data['database_password'], + 'db_host' => 'localhost', + 'db_prefix' => 'wp_', + 'username' => $site->type_data['username'], + 'password' => $site->type_data['password'], + 'email' => $site->type_data['email'], + 'title' => $site->type_data['title'], + ]), + 'install-wordpress' + ); + } +} diff --git a/resources/commands/wordpress/install.sh b/app/SSH/Wordpress/scripts/install.sh similarity index 100% rename from resources/commands/wordpress/install.sh rename to app/SSH/Wordpress/scripts/install.sh diff --git a/app/SSHCommands/Command.php b/app/SSHCommands/Command.php deleted file mode 100755 index ff7f7c2..0000000 --- a/app/SSHCommands/Command.php +++ /dev/null @@ -1,10 +0,0 @@ -file()) - ->replace('__user__', $this->user) - ->replace('__data__', $this->data) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/BackupDatabaseCommand.php b/app/SSHCommands/Database/BackupDatabaseCommand.php deleted file mode 100644 index 8164c65..0000000 --- a/app/SSHCommands/Database/BackupDatabaseCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__database__', $this->database) - ->replace('__file__', $this->fileName) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/CreateCommand.php b/app/SSHCommands/Database/CreateCommand.php deleted file mode 100755 index 054b297..0000000 --- a/app/SSHCommands/Database/CreateCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__name__', $this->name) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/CreateUserCommand.php b/app/SSHCommands/Database/CreateUserCommand.php deleted file mode 100755 index f3ff630..0000000 --- a/app/SSHCommands/Database/CreateUserCommand.php +++ /dev/null @@ -1,31 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__username__', $this->username) - ->replace('__password__', $this->password) - ->replace('__host__', $this->host) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/DeleteCommand.php b/app/SSHCommands/Database/DeleteCommand.php deleted file mode 100755 index 91c6bba..0000000 --- a/app/SSHCommands/Database/DeleteCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__name__', $this->name) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/DeleteUserCommand.php b/app/SSHCommands/Database/DeleteUserCommand.php deleted file mode 100755 index 89fd652..0000000 --- a/app/SSHCommands/Database/DeleteUserCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__username__', $this->username) - ->replace('__host__', $this->host) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/InstallMariadbCommand.php b/app/SSHCommands/Database/InstallMariadbCommand.php deleted file mode 100755 index 42a19ff..0000000 --- a/app/SSHCommands/Database/InstallMariadbCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/Database/InstallMysqlCommand.php b/app/SSHCommands/Database/InstallMysqlCommand.php deleted file mode 100755 index 3060ec7..0000000 --- a/app/SSHCommands/Database/InstallMysqlCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -version == '8.0') { - return File::get(resource_path('commands/database/install-mysql-8.sh')); - } - - return File::get(resource_path('commands/database/install-mysql.sh')); - } - - public function content(): string - { - return $this->file(); - } -} diff --git a/app/SSHCommands/Database/LinkCommand.php b/app/SSHCommands/Database/LinkCommand.php deleted file mode 100755 index 249a828..0000000 --- a/app/SSHCommands/Database/LinkCommand.php +++ /dev/null @@ -1,31 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__username__', $this->username) - ->replace('__host__', $this->host) - ->replace('__database__', $this->database) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/RestoreDatabaseCommand.php b/app/SSHCommands/Database/RestoreDatabaseCommand.php deleted file mode 100644 index c19ad35..0000000 --- a/app/SSHCommands/Database/RestoreDatabaseCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__database__', $this->database) - ->replace('__file__', $this->fileName) - ->toString(); - } -} diff --git a/app/SSHCommands/Database/UnlinkCommand.php b/app/SSHCommands/Database/UnlinkCommand.php deleted file mode 100755 index 91b1b8c..0000000 --- a/app/SSHCommands/Database/UnlinkCommand.php +++ /dev/null @@ -1,29 +0,0 @@ -provider))); - } - - public function content(): string - { - return str($this->file()) - ->replace('__username__', $this->username) - ->replace('__host__', $this->host) - ->toString(); - } -} diff --git a/app/SSHCommands/Firewall/AddRuleCommand.php b/app/SSHCommands/Firewall/AddRuleCommand.php deleted file mode 100755 index bf5e212..0000000 --- a/app/SSHCommands/Firewall/AddRuleCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -provider))); - } -} diff --git a/app/SSHCommands/Firewall/CommandContent.php b/app/SSHCommands/Firewall/CommandContent.php deleted file mode 100755 index 86ea4bd..0000000 --- a/app/SSHCommands/Firewall/CommandContent.php +++ /dev/null @@ -1,17 +0,0 @@ -file()) - ->replace('__type__', $this->type) - ->replace('__protocol__', $this->protocol) - ->replace('__source__', $this->source) - ->replace('__mask__', $this->mask || $this->mask == 0 ? '/'.$this->mask : '') - ->replace('__port__', $this->port) - ->toString(); - } -} diff --git a/app/SSHCommands/Firewall/InstallUfwCommand.php b/app/SSHCommands/Firewall/InstallUfwCommand.php deleted file mode 100755 index 14cce4a..0000000 --- a/app/SSHCommands/Firewall/InstallUfwCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/Firewall/RemoveRuleCommand.php b/app/SSHCommands/Firewall/RemoveRuleCommand.php deleted file mode 100755 index 2b9a4c5..0000000 --- a/app/SSHCommands/Firewall/RemoveRuleCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -provider))); - } -} diff --git a/app/SSHCommands/Installation/InstallNodejsCommand.php b/app/SSHCommands/Installation/InstallNodejsCommand.php deleted file mode 100755 index 9173fde..0000000 --- a/app/SSHCommands/Installation/InstallNodejsCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/Installation/InstallRedisCommand.php b/app/SSHCommands/Installation/InstallRedisCommand.php deleted file mode 100755 index 11b7f95..0000000 --- a/app/SSHCommands/Installation/InstallRedisCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/Installation/InstallRequirementsCommand.php b/app/SSHCommands/Installation/InstallRequirementsCommand.php deleted file mode 100755 index a0f57b4..0000000 --- a/app/SSHCommands/Installation/InstallRequirementsCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__email__', $this->email) - ->replace('__name__', $this->name) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/ChangeNginxPHPVersionCommand.php b/app/SSHCommands/Nginx/ChangeNginxPHPVersionCommand.php deleted file mode 100755 index be03101..0000000 --- a/app/SSHCommands/Nginx/ChangeNginxPHPVersionCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__domain__', $this->domain) - ->replace('__old_version__', $this->oldVersion) - ->replace('__new_version__', $this->newVersion) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/CreateNginxVHostCommand.php b/app/SSHCommands/Nginx/CreateNginxVHostCommand.php deleted file mode 100755 index b13eb53..0000000 --- a/app/SSHCommands/Nginx/CreateNginxVHostCommand.php +++ /dev/null @@ -1,30 +0,0 @@ -file()) - ->replace('__domain__', $this->domain) - ->replace('__path__', $this->path) - ->replace('__vhost__', $this->vhost) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/DeleteNginxSiteCommand.php b/app/SSHCommands/Nginx/DeleteNginxSiteCommand.php deleted file mode 100755 index d4dcc3e..0000000 --- a/app/SSHCommands/Nginx/DeleteNginxSiteCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__domain__', $this->domain) - ->replace('__path__', $this->path) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/GetNginxVHostCommand.php b/app/SSHCommands/Nginx/GetNginxVHostCommand.php deleted file mode 100755 index 28dc68b..0000000 --- a/app/SSHCommands/Nginx/GetNginxVHostCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__domain__', $this->domain) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/InstallNginxCommand.php b/app/SSHCommands/Nginx/InstallNginxCommand.php deleted file mode 100755 index 331938a..0000000 --- a/app/SSHCommands/Nginx/InstallNginxCommand.php +++ /dev/null @@ -1,31 +0,0 @@ -file()) - ->replace('__config__', $this->config()) - ->toString(); - } - - protected function config(): string - { - $config = File::get(resource_path('commands/webserver/nginx/nginx.conf')); - - /** TODO: change user to server user */ - return str($config) - ->replace('__user__', config('core.ssh_user')) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/UpdateNginxRedirectsCommand.php b/app/SSHCommands/Nginx/UpdateNginxRedirectsCommand.php deleted file mode 100755 index bd8c25d..0000000 --- a/app/SSHCommands/Nginx/UpdateNginxRedirectsCommand.php +++ /dev/null @@ -1,28 +0,0 @@ -file()) - ->replace('__redirects__', addslashes($this->redirects)) - ->replace('__domain__', $this->domain) - ->toString(); - } -} diff --git a/app/SSHCommands/Nginx/UpdateNginxVHostCommand.php b/app/SSHCommands/Nginx/UpdateNginxVHostCommand.php deleted file mode 100755 index f0f321a..0000000 --- a/app/SSHCommands/Nginx/UpdateNginxVHostCommand.php +++ /dev/null @@ -1,30 +0,0 @@ -file()) - ->replace('__domain__', $this->domain) - ->replace('__path__', $this->path) - ->replace('__vhost__', $this->vhost) - ->toString(); - } -} diff --git a/app/SSHCommands/PHP/ChangeDefaultPHPCommand.php b/app/SSHCommands/PHP/ChangeDefaultPHPCommand.php deleted file mode 100755 index df9fc8a..0000000 --- a/app/SSHCommands/PHP/ChangeDefaultPHPCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__version__', $this->version) - ->toString(); - } -} diff --git a/app/SSHCommands/PHP/GetPHPIniCommand.php b/app/SSHCommands/PHP/GetPHPIniCommand.php deleted file mode 100755 index ef7858d..0000000 --- a/app/SSHCommands/PHP/GetPHPIniCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__version__', $this->version) - ->toString(); - } -} diff --git a/app/SSHCommands/PHP/InstallComposerCommand.php b/app/SSHCommands/PHP/InstallComposerCommand.php deleted file mode 100755 index 60cc39a..0000000 --- a/app/SSHCommands/PHP/InstallComposerCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/PHP/InstallPHPCommand.php b/app/SSHCommands/PHP/InstallPHPCommand.php deleted file mode 100755 index d165d3d..0000000 --- a/app/SSHCommands/PHP/InstallPHPCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__version__', $this->version) - ->replace('__user__', config('core.ssh_user')) - ->toString(); - } -} diff --git a/app/SSHCommands/PHP/InstallPHPExtensionCommand.php b/app/SSHCommands/PHP/InstallPHPExtensionCommand.php deleted file mode 100755 index 331cf58..0000000 --- a/app/SSHCommands/PHP/InstallPHPExtensionCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__version__', $this->version) - ->replace('__name__', $this->name) - ->toString(); - } -} diff --git a/app/SSHCommands/PHP/UninstallPHPCommand.php b/app/SSHCommands/PHP/UninstallPHPCommand.php deleted file mode 100755 index 1401d1b..0000000 --- a/app/SSHCommands/PHP/UninstallPHPCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__version__', $this->version) - ->replace('__user__', config('core.ssh_user')) - ->toString(); - } -} diff --git a/app/SSHCommands/PHPMyAdmin/CreateNginxPHPMyAdminVHostCommand.php b/app/SSHCommands/PHPMyAdmin/CreateNginxPHPMyAdminVHostCommand.php deleted file mode 100755 index 508134e..0000000 --- a/app/SSHCommands/PHPMyAdmin/CreateNginxPHPMyAdminVHostCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__vhost__', $this->vhost) - ->toString(); - } -} diff --git a/app/SSHCommands/PHPMyAdmin/DeleteNginxPHPMyAdminVHostCommand.php b/app/SSHCommands/PHPMyAdmin/DeleteNginxPHPMyAdminVHostCommand.php deleted file mode 100755 index 5a737f5..0000000 --- a/app/SSHCommands/PHPMyAdmin/DeleteNginxPHPMyAdminVHostCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->toString(); - } -} diff --git a/app/SSHCommands/PHPMyAdmin/DownloadPHPMyAdminCommand.php b/app/SSHCommands/PHPMyAdmin/DownloadPHPMyAdminCommand.php deleted file mode 100644 index 15963de..0000000 --- a/app/SSHCommands/PHPMyAdmin/DownloadPHPMyAdminCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/SSL/CreateCustomSSLCommand.php b/app/SSHCommands/SSL/CreateCustomSSLCommand.php deleted file mode 100755 index 86732b9..0000000 --- a/app/SSHCommands/SSL/CreateCustomSSLCommand.php +++ /dev/null @@ -1,34 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->replace('__certificate__', $this->certificate) - ->replace('__pk__', $this->pk) - ->replace('__certificate_path__', $this->certificatePath) - ->replace('__pk_path__', $this->pkPath) - ->toString(); - } -} diff --git a/app/SSHCommands/SSL/CreateLetsencryptSSLCommand.php b/app/SSHCommands/SSL/CreateLetsencryptSSLCommand.php deleted file mode 100755 index 8072b6c..0000000 --- a/app/SSHCommands/SSL/CreateLetsencryptSSLCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__email__', $this->email) - ->replace('__web_directory__', $this->webDirectory) - ->replace('__domain__', $this->domain) - ->toString(); - } -} diff --git a/app/SSHCommands/SSL/InstallCertbotCommand.php b/app/SSHCommands/SSL/InstallCertbotCommand.php deleted file mode 100755 index b13e8f5..0000000 --- a/app/SSHCommands/SSL/InstallCertbotCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/SSL/RemoveSSLCommand.php b/app/SSHCommands/SSL/RemoveSSLCommand.php deleted file mode 100755 index bf0b382..0000000 --- a/app/SSHCommands/SSL/RemoveSSLCommand.php +++ /dev/null @@ -1,22 +0,0 @@ -path.'*'; - } -} diff --git a/app/SSHCommands/Service/RestartServiceCommand.php b/app/SSHCommands/Service/RestartServiceCommand.php deleted file mode 100644 index 14a5ab3..0000000 --- a/app/SSHCommands/Service/RestartServiceCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__service__', $this->unit) - ->toString(); - } -} diff --git a/app/SSHCommands/Service/ServiceStatusCommand.php b/app/SSHCommands/Service/ServiceStatusCommand.php deleted file mode 100755 index 1434546..0000000 --- a/app/SSHCommands/Service/ServiceStatusCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__service__', $this->unit) - ->toString(); - } -} diff --git a/app/SSHCommands/Service/StartServiceCommand.php b/app/SSHCommands/Service/StartServiceCommand.php deleted file mode 100644 index f4b616b..0000000 --- a/app/SSHCommands/Service/StartServiceCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__service__', $this->unit) - ->toString(); - } -} diff --git a/app/SSHCommands/Service/StopServiceCommand.php b/app/SSHCommands/Service/StopServiceCommand.php deleted file mode 100644 index b9a99bb..0000000 --- a/app/SSHCommands/Service/StopServiceCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__service__', $this->unit) - ->toString(); - } -} diff --git a/app/SSHCommands/Storage/DownloadFromDropboxCommand.php b/app/SSHCommands/Storage/DownloadFromDropboxCommand.php deleted file mode 100644 index e6ba695..0000000 --- a/app/SSHCommands/Storage/DownloadFromDropboxCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__src__', $this->src) - ->replace('__dest__', $this->dest) - ->replace('__token__', $this->token) - ->toString(); - } -} diff --git a/app/SSHCommands/Storage/DownloadFromFTPCommand.php b/app/SSHCommands/Storage/DownloadFromFTPCommand.php deleted file mode 100644 index 068df06..0000000 --- a/app/SSHCommands/Storage/DownloadFromFTPCommand.php +++ /dev/null @@ -1,40 +0,0 @@ -file()) - ->replace('__src__', $this->src) - ->replace('__dest__', $this->dest) - ->replace('__host__', $this->host) - ->replace('__port__', $this->port) - ->replace('__username__', $this->username) - ->replace('__password__', $this->password) - ->replace('__ssl__', $this->ssl ? 's' : '') - ->replace('__passive__', $this->passive ? '--ftp-pasv' : '') - ->toString(); - } -} diff --git a/app/SSHCommands/Storage/UploadToDropboxCommand.php b/app/SSHCommands/Storage/UploadToDropboxCommand.php deleted file mode 100644 index 29498f3..0000000 --- a/app/SSHCommands/Storage/UploadToDropboxCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__src__', $this->src) - ->replace('__dest__', $this->dest) - ->replace('__token__', $this->token) - ->toString(); - } -} diff --git a/app/SSHCommands/Storage/UploadToFTPCommand.php b/app/SSHCommands/Storage/UploadToFTPCommand.php deleted file mode 100644 index d9e556a..0000000 --- a/app/SSHCommands/Storage/UploadToFTPCommand.php +++ /dev/null @@ -1,40 +0,0 @@ -file()) - ->replace('__src__', $this->src) - ->replace('__dest__', $this->dest) - ->replace('__host__', $this->host) - ->replace('__port__', $this->port) - ->replace('__username__', $this->username) - ->replace('__password__', $this->password) - ->replace('__ssl__', $this->ssl ? 's' : '') - ->replace('__passive__', $this->passive ? '--ftp-pasv' : '') - ->toString(); - } -} diff --git a/app/SSHCommands/Supervisor/CreateWorkerCommand.php b/app/SSHCommands/Supervisor/CreateWorkerCommand.php deleted file mode 100644 index 197c7a9..0000000 --- a/app/SSHCommands/Supervisor/CreateWorkerCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__id__', $this->id) - ->replace('__config__', $this->config) - ->toString(); - } -} diff --git a/app/SSHCommands/Supervisor/DeleteWorkerCommand.php b/app/SSHCommands/Supervisor/DeleteWorkerCommand.php deleted file mode 100644 index 0171772..0000000 --- a/app/SSHCommands/Supervisor/DeleteWorkerCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__id__', $this->id) - ->toString(); - } -} diff --git a/app/SSHCommands/Supervisor/InstallSupervisorCommand.php b/app/SSHCommands/Supervisor/InstallSupervisorCommand.php deleted file mode 100755 index 8808741..0000000 --- a/app/SSHCommands/Supervisor/InstallSupervisorCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/Supervisor/RestartWorkerCommand.php b/app/SSHCommands/Supervisor/RestartWorkerCommand.php deleted file mode 100644 index 12a1006..0000000 --- a/app/SSHCommands/Supervisor/RestartWorkerCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__id__', $this->id) - ->toString(); - } -} diff --git a/app/SSHCommands/Supervisor/StartWorkerCommand.php b/app/SSHCommands/Supervisor/StartWorkerCommand.php deleted file mode 100644 index f2732bf..0000000 --- a/app/SSHCommands/Supervisor/StartWorkerCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__id__', $this->id) - ->toString(); - } -} diff --git a/app/SSHCommands/Supervisor/StopWorkerCommand.php b/app/SSHCommands/Supervisor/StopWorkerCommand.php deleted file mode 100644 index 32125e0..0000000 --- a/app/SSHCommands/Supervisor/StopWorkerCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__id__', $this->id) - ->toString(); - } -} diff --git a/app/SSHCommands/System/CreateUserCommand.php b/app/SSHCommands/System/CreateUserCommand.php deleted file mode 100755 index adc7e39..0000000 --- a/app/SSHCommands/System/CreateUserCommand.php +++ /dev/null @@ -1,27 +0,0 @@ -file()) - ->replace('__user__', $this->user) - ->replace('__key__', $this->key) - ->replace('__password__', $this->password) - ->toString(); - } -} diff --git a/app/SSHCommands/System/DeleteSshKeyCommand.php b/app/SSHCommands/System/DeleteSshKeyCommand.php deleted file mode 100755 index 6eb46ca..0000000 --- a/app/SSHCommands/System/DeleteSshKeyCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__key__', str($this->key)->replace('/', '\/')) - ->toString(); - } -} diff --git a/app/SSHCommands/System/DeploySshKeyCommand.php b/app/SSHCommands/System/DeploySshKeyCommand.php deleted file mode 100755 index fd228d1..0000000 --- a/app/SSHCommands/System/DeploySshKeyCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__key__', addslashes($this->key)) - ->toString(); - } -} diff --git a/app/SSHCommands/System/EditFileCommand.php b/app/SSHCommands/System/EditFileCommand.php deleted file mode 100644 index 267ae80..0000000 --- a/app/SSHCommands/System/EditFileCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->replace('__content__', $this->content) - ->toString(); - } -} diff --git a/app/SSHCommands/System/GenerateSshKeyCommand.php b/app/SSHCommands/System/GenerateSshKeyCommand.php deleted file mode 100755 index 29077c6..0000000 --- a/app/SSHCommands/System/GenerateSshKeyCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__name__', $this->name) - ->toString(); - } -} diff --git a/app/SSHCommands/System/GetPublicKeyCommand.php b/app/SSHCommands/System/GetPublicKeyCommand.php deleted file mode 100755 index 2ebad7f..0000000 --- a/app/SSHCommands/System/GetPublicKeyCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/System/ReadFileCommand.php b/app/SSHCommands/System/ReadFileCommand.php deleted file mode 100644 index 943dd3a..0000000 --- a/app/SSHCommands/System/ReadFileCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->toString(); - } -} diff --git a/app/SSHCommands/System/ReadSshKeyCommand.php b/app/SSHCommands/System/ReadSshKeyCommand.php deleted file mode 100755 index 7a89529..0000000 --- a/app/SSHCommands/System/ReadSshKeyCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__name__', $this->name) - ->toString(); - } -} diff --git a/app/SSHCommands/System/RebootCommand.php b/app/SSHCommands/System/RebootCommand.php deleted file mode 100644 index 0e0a7da..0000000 --- a/app/SSHCommands/System/RebootCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/System/RunScriptCommand.php b/app/SSHCommands/System/RunScriptCommand.php deleted file mode 100644 index 97bc947..0000000 --- a/app/SSHCommands/System/RunScriptCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->replace('__script__', $this->script) - ->toString(); - } -} diff --git a/app/SSHCommands/System/UpgradeCommand.php b/app/SSHCommands/System/UpgradeCommand.php deleted file mode 100755 index 20607ad..0000000 --- a/app/SSHCommands/System/UpgradeCommand.php +++ /dev/null @@ -1,19 +0,0 @@ -file(); - } -} diff --git a/app/SSHCommands/Website/CloneRepositoryCommand.php b/app/SSHCommands/Website/CloneRepositoryCommand.php deleted file mode 100755 index 2b28b78..0000000 --- a/app/SSHCommands/Website/CloneRepositoryCommand.php +++ /dev/null @@ -1,33 +0,0 @@ -file()) - ->replace('__repo__', $this->repository) - ->replace('__host__', str($this->repository)->after('@')->before('-')) - ->replace('__branch__', $this->branch) - ->replace('__path__', $this->path) - ->replace('__key__', $this->privateKeyPath) - ->toString(); - } -} diff --git a/app/SSHCommands/Website/ComposerInstallCommand.php b/app/SSHCommands/Website/ComposerInstallCommand.php deleted file mode 100755 index 182536e..0000000 --- a/app/SSHCommands/Website/ComposerInstallCommand.php +++ /dev/null @@ -1,25 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->toString(); - } -} diff --git a/app/SSHCommands/Website/GetEnvCommand.php b/app/SSHCommands/Website/GetEnvCommand.php deleted file mode 100755 index 0bc439f..0000000 --- a/app/SSHCommands/Website/GetEnvCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__domain__', $this->domain) - ->toString(); - } -} diff --git a/app/SSHCommands/Website/UpdateBranchCommand.php b/app/SSHCommands/Website/UpdateBranchCommand.php deleted file mode 100644 index 652ae77..0000000 --- a/app/SSHCommands/Website/UpdateBranchCommand.php +++ /dev/null @@ -1,26 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->replace('__branch__', $this->branch) - ->toString(); - } -} diff --git a/app/SSHCommands/Wordpress/InstallWordpressCommand.php b/app/SSHCommands/Wordpress/InstallWordpressCommand.php deleted file mode 100755 index 3432860..0000000 --- a/app/SSHCommands/Wordpress/InstallWordpressCommand.php +++ /dev/null @@ -1,46 +0,0 @@ -file()) - ->replace('__path__', $this->path) - ->replace('__domain__', $this->domain) - ->replace('__db_name__', $this->dbName) - ->replace('__db_user__', $this->dbUser) - ->replace('__db_pass__', $this->dbPass) - ->replace('__db_host__', $this->dbHost) - ->replace('__db_prefix__', $this->dbPrefix) - ->replace('__username__', $this->username) - ->replace('__password__', $this->password) - ->replace('__title__', $this->title) - ->replace('__email__', $this->email) - ->toString(); - } -} diff --git a/app/SSHCommands/Wordpress/UpdateWordpressCommand.php b/app/SSHCommands/Wordpress/UpdateWordpressCommand.php deleted file mode 100755 index db0bb38..0000000 --- a/app/SSHCommands/Wordpress/UpdateWordpressCommand.php +++ /dev/null @@ -1,37 +0,0 @@ -title) { - $command .= 'wp --path='.$this->path.' option update blogname "'.addslashes($this->title).'"'."\n"; - } - if ($this->url) { - $command .= 'wp --path='.$this->path.' option update siteurl "'.addslashes($this->url).'"'."\n"; - $command .= 'wp --path='.$this->path.' option update home "'.addslashes($this->url).'"'."\n"; - } - - return $command; - } -} diff --git a/app/ServerProviders/AWS.php b/app/ServerProviders/AWS.php index ea8cebf..8dd026b 100755 --- a/app/ServerProviders/AWS.php +++ b/app/ServerProviders/AWS.php @@ -2,7 +2,6 @@ namespace App\ServerProviders; -use App\Enums\OperatingSystem; use App\Exceptions\CouldNotConnectToProvider; use App\Facades\Notifier; use App\Notifications\FailedToDeleteServerFromProvider; @@ -11,6 +10,7 @@ use Exception; use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; +use Illuminate\Validation\Rule; use Throwable; class AWS extends AbstractProvider @@ -19,10 +19,13 @@ class AWS extends AbstractProvider protected EC2InstanceConnectClient $ec2InstanceConnectClient; - public function createValidationRules(array $input): array + public function createRules(array $input): array { $rules = [ - 'os' => 'required|in:'.implode(',', OperatingSystem::getValues()), + 'os' => [ + 'required', + Rule::in(config('core.operating_systems')), + ], ]; // plans $plans = []; diff --git a/app/ServerProviders/AbstractProvider.php b/app/ServerProviders/AbstractProvider.php index e6b8f93..c628461 100755 --- a/app/ServerProviders/AbstractProvider.php +++ b/app/ServerProviders/AbstractProvider.php @@ -2,8 +2,8 @@ namespace App\ServerProviders; -use App\Contracts\ServerProvider; use App\Models\Server; +use Illuminate\Filesystem\FilesystemAdapter; use Illuminate\Support\Facades\Storage; abstract class AbstractProvider implements ServerProvider @@ -15,9 +15,9 @@ public function __construct(?Server $server = null) $this->server = $server; } - protected function generateKeyPair(): void + public function generateKeyPair(): void { - /** @var \Illuminate\Filesystem\FilesystemAdapter $storageDisk */ + /** @var FilesystemAdapter $storageDisk */ $storageDisk = Storage::disk(config('core.key_pairs_disk')); generate_key_pair($storageDisk->path((string) $this->server->id)); } diff --git a/app/ServerProviders/Custom.php b/app/ServerProviders/Custom.php index f696dd8..a5fd1cb 100755 --- a/app/ServerProviders/Custom.php +++ b/app/ServerProviders/Custom.php @@ -9,7 +9,7 @@ class Custom extends AbstractProvider { - public function createValidationRules(array $input): array + public function createRules(array $input): array { return [ 'ip' => [ diff --git a/app/ServerProviders/DigitalOcean.php b/app/ServerProviders/DigitalOcean.php index 8e60035..052ff2d 100644 --- a/app/ServerProviders/DigitalOcean.php +++ b/app/ServerProviders/DigitalOcean.php @@ -14,7 +14,7 @@ class DigitalOcean extends AbstractProvider { protected string $apiUrl = 'https://api.digitalocean.com/v2'; - public function createValidationRules(array $input): array + public function createRules(array $input): array { $rules = [ 'os' => 'required|in:'.implode(',', config('core.operating_systems')), diff --git a/app/ServerProviders/Hetzner.php b/app/ServerProviders/Hetzner.php index 6cc42ee..e19971e 100644 --- a/app/ServerProviders/Hetzner.php +++ b/app/ServerProviders/Hetzner.php @@ -13,7 +13,7 @@ class Hetzner extends AbstractProvider { protected string $apiUrl = 'https://api.hetzner.cloud/v1'; - public function createValidationRules(array $input): array + public function createRules(array $input): array { return [ 'os' => 'required|in:'.implode(',', config('core.operating_systems')), diff --git a/app/ServerProviders/Linode.php b/app/ServerProviders/Linode.php index b696c1a..d61e02d 100644 --- a/app/ServerProviders/Linode.php +++ b/app/ServerProviders/Linode.php @@ -13,7 +13,7 @@ class Linode extends AbstractProvider { protected string $apiUrl = 'https://api.linode.com/v4'; - public function createValidationRules($input): array + public function createRules($input): array { $rules = [ 'os' => 'required|in:'.implode(',', config('core.operating_systems')), diff --git a/app/Contracts/ServerProvider.php b/app/ServerProviders/ServerProvider.php similarity index 78% rename from app/Contracts/ServerProvider.php rename to app/ServerProviders/ServerProvider.php index 58d25fa..d717718 100755 --- a/app/Contracts/ServerProvider.php +++ b/app/ServerProviders/ServerProvider.php @@ -1,10 +1,10 @@ 'required|in:'.implode(',', config('core.operating_systems')), diff --git a/app/ServerTypes/AbstractType.php b/app/ServerTypes/AbstractType.php index ffbf703..bf5792a 100755 --- a/app/ServerTypes/AbstractType.php +++ b/app/ServerTypes/AbstractType.php @@ -2,10 +2,8 @@ namespace App\ServerTypes; -use App\Contracts\ServerType; -use App\Events\Broadcast; +use App\Enums\ServiceStatus; use App\Models\Server; -use Closure; abstract class AbstractType implements ServerType { @@ -16,17 +14,110 @@ public function __construct(Server $server) $this->server = $server; } - protected function progress(int $percentage, ?string $step = null): Closure + public function install(): void { - return function () use ($percentage, $step) { - $this->server->progress = $percentage; - $this->server->progress_step = $step; - $this->server->save(); - event(new Broadcast('server-installation-progress', [ - 'server' => $this->server, - 'step' => $step, - 'percentage' => $percentage, - ])); - }; + $this->createUser(); + $this->progress(15, 'installing-updates'); + $this->server->os()->upgrade(); + $this->progress(25, 'installing-dependencies'); + $this->server->os()->installDependencies(); + $services = $this->server->services; + $currentProgress = 45; + $progressPerService = (100 - $currentProgress) / count($services); + foreach ($services as $service) { + $currentProgress += $progressPerService; + $this->progress($currentProgress, 'installing- '.$service->name); + $service->handler()->install(); + $service->update(['status' => ServiceStatus::READY]); + if ($service->type == 'php') { + $this->progress($currentProgress, 'installing-composer'); + $service->handler()->installComposer(); + } + } + $this->progress(100, 'finishing'); + } + + protected function createUser(): void + { + $this->server->os()->createUser( + $this->server->authentication['user'], + $this->server->authentication['pass'], + $this->server->sshKey()['public_key'] + ); + $this->server->ssh_user = config('core.ssh_user'); + $this->server->save(); + $this->server->refresh(); + $this->server->public_key = $this->server->os()->getPublicKey($this->server->getSshUser()); + $this->server->save(); + } + + protected function progress(int $percentage, ?string $step = null): void + { + $this->server->progress = $percentage; + $this->server->progress_step = $step; + $this->server->save(); + } + + protected function addWebserver(string $service): void + { + if ($service != 'none') { + $this->server->services()->create([ + 'type' => 'webserver', + 'name' => $service, + 'version' => 'latest', + ]); + } + } + + protected function addDatabase(string $service): void + { + if ($service != 'none') { + $this->server->services()->create([ + 'type' => 'database', + 'name' => config('core.databases_name.'.$service), + 'version' => config('core.databases_version.'.$service), + ]); + } + } + + protected function addPHP(string $version): void + { + if ($version != 'none') { + $this->server->services()->create([ + 'type' => 'php', + 'type_data' => [ + 'extensions' => [], + ], + 'name' => 'php', + 'version' => $version, + ]); + } + } + + protected function addSupervisor(): void + { + $this->server->services()->create([ + 'type' => 'process_manager', + 'name' => 'supervisor', + 'version' => 'latest', + ]); + } + + protected function addRedis(): void + { + $this->server->services()->create([ + 'type' => 'memory_database', + 'name' => 'redis', + 'version' => 'latest', + ]); + } + + protected function addUfw(): void + { + $this->server->services()->create([ + 'type' => 'firewall', + 'name' => 'ufw', + 'version' => 'latest', + ]); } } diff --git a/app/ServerTypes/Database.php b/app/ServerTypes/Database.php index 984fe85..b8e1925 100755 --- a/app/ServerTypes/Database.php +++ b/app/ServerTypes/Database.php @@ -2,20 +2,9 @@ namespace App\ServerTypes; -use App\Events\Broadcast; -use App\Facades\Notifier; -use App\Jobs\Installation\Initialize; -use App\Jobs\Installation\InstallRequirements; -use App\Jobs\Installation\Upgrade; -use App\Notifications\ServerInstallationFailed; -use App\Notifications\ServerInstallationSucceed; -use Illuminate\Support\Facades\Bus; -use Illuminate\Support\Facades\Log; -use Throwable; - class Database extends AbstractType { - public function createValidationRules(array $input): array + public function createRules(array $input): array { return [ 'database' => [ @@ -33,109 +22,9 @@ public function data(array $input): array public function createServices(array $input): void { $this->server->services()->forceDelete(); - $this->addDatabase($input['database']); $this->addSupervisor(); $this->addRedis(); $this->addUfw(); } - - public function install(): void - { - $jobs = [ - new Initialize($this->server, $this->server->ssh_user, $this->server->provider === 'custom'), - $this->progress(15, 'Installing Updates'), - new Upgrade($this->server), - $this->progress(25, 'Installing Requirements'), - new InstallRequirements($this->server), - ]; - - $services = $this->server->services; - $currentProgress = 25; - $progressPerService = (100 - $currentProgress) / count($services); - foreach ($services as $service) { - $currentProgress += $progressPerService; - $jobs[] = $this->progress($currentProgress, 'Installing '.$service->name); - $jobs[] = $service->installer(); - } - - $jobs[] = function () { - $this->server->update([ - 'status' => 'ready', - 'progress' => 100, - ]); - event( - new Broadcast('install-server-finished', [ - 'server' => $this->server, - ]) - ); - Notifier::send($this->server, new ServerInstallationSucceed($this->server)); - }; - - Bus::chain($jobs) - ->catch(function (Throwable $e) { - $this->server->update([ - 'status' => 'installation_failed', - ]); - event( - new Broadcast('install-server-failed', [ - 'server' => $this->server, - ]) - ); - Notifier::send($this->server, new ServerInstallationFailed($this->server)); - Log::error('server-installation-error', [ - 'error' => (string) $e, - ]); - throw $e; - }) - ->onConnection('ssh-long') - ->dispatch(); - } - - protected function addDatabase(string $service): void - { - if ($service != 'none') { - $this->server->services()->create([ - 'type' => 'database', - 'name' => config('core.databases_name.'.$service), - 'version' => config('core.databases_version.'.$service), - ]); - } - } - - /** - * add supervisor - */ - protected function addSupervisor(): void - { - $this->server->services()->create([ - 'type' => 'process_manager', - 'name' => 'supervisor', - 'version' => 'latest', - ]); - } - - /** - * add supervisor - */ - protected function addRedis(): void - { - $this->server->services()->create([ - 'type' => 'memory_database', - 'name' => 'redis', - 'version' => 'latest', - ]); - } - - /** - * add supervisor - */ - protected function addUfw(): void - { - $this->server->services()->create([ - 'type' => 'firewall', - 'name' => 'ufw', - 'version' => 'latest', - ]); - } } diff --git a/app/ServerTypes/Regular.php b/app/ServerTypes/Regular.php index 9b07815..9ba276a 100755 --- a/app/ServerTypes/Regular.php +++ b/app/ServerTypes/Regular.php @@ -2,24 +2,9 @@ namespace App\ServerTypes; -use App\Enums\ServerStatus; -use App\Events\Broadcast; -use App\Facades\Notifier; -use App\Jobs\Installation\Initialize; -use App\Jobs\Installation\InstallCertbot; -use App\Jobs\Installation\InstallComposer; -use App\Jobs\Installation\InstallNodejs; -use App\Jobs\Installation\InstallRequirements; -use App\Jobs\Installation\Upgrade; -use App\Notifications\ServerInstallationFailed; -use App\Notifications\ServerInstallationSucceed; -use Illuminate\Support\Facades\Bus; -use Illuminate\Support\Facades\Log; -use Throwable; - class Regular extends AbstractType { - public function createValidationRules(array $input): array + public function createRules(array $input): array { return [ 'webserver' => [ @@ -45,7 +30,6 @@ public function data(array $input): array public function createServices(array $input): void { $this->server->services()->forceDelete(); - $this->addWebserver($input['webserver']); $this->addDatabase($input['database']); $this->addPHP($input['php']); @@ -53,136 +37,4 @@ public function createServices(array $input): void $this->addRedis(); $this->addUfw(); } - - public function install(): void - { - $jobs = [ - new Initialize($this->server, $this->server->ssh_user), - $this->progress(15, 'Installing Updates'), - new Upgrade($this->server), - $this->progress(25, 'Installing Requirements'), - new InstallRequirements($this->server), - $this->progress(35, 'Installing Node.js'), - new InstallNodejs($this->server), - $this->progress(45, 'Installing Certbot'), - new InstallCertbot($this->server), - ]; - - $services = $this->server->services; - $currentProgress = 45; - $progressPerService = (100 - $currentProgress) / count($services); - foreach ($services as $service) { - $currentProgress += $progressPerService; - $jobs[] = $this->progress($currentProgress, 'Installing '.$service->name); - $jobs[] = $service->installer(); - if ($service->type == 'php') { - $jobs[] = $this->progress($currentProgress, 'Installing Composer'); - $jobs[] = new InstallComposer($this->server); - } - } - - $jobs[] = function () { - $this->server->update([ - 'status' => ServerStatus::READY, - 'progress' => 100, - ]); - event( - new Broadcast('install-server-finished', [ - 'server' => $this->server, - ]) - ); - Notifier::send($this->server, new ServerInstallationSucceed($this->server)); - }; - - Bus::chain($jobs) - ->catch(function (Throwable $e) { - $this->server->update([ - 'status' => 'installation_failed', - ]); - event( - new Broadcast('install-server-failed', [ - 'server' => $this->server, - ]) - ); - Notifier::send($this->server, new ServerInstallationFailed($this->server)); - Log::error('server-installation-error', [ - 'error' => (string) $e, - ]); - throw $e; - }) - ->onConnection('ssh-long') - ->dispatch(); - } - - protected function addWebserver(string $service): void - { - if ($service != 'none') { - $this->server->services()->create([ - 'type' => 'webserver', - 'name' => $service, - 'version' => 'latest', - ]); - } - } - - protected function addDatabase(string $service): void - { - if ($service != 'none') { - $this->server->services()->create([ - 'type' => 'database', - 'name' => config('core.databases_name.'.$service), - 'version' => config('core.databases_version.'.$service), - ]); - } - } - - protected function addPHP(string $version): void - { - if ($version != 'none') { - $this->server->services()->create([ - 'type' => 'php', - 'type_data' => [ - 'extensions' => [], - ], - 'name' => 'php', - 'version' => $version, - ]); - } - } - - /** - * add supervisor - */ - protected function addSupervisor(): void - { - $this->server->services()->create([ - 'type' => 'process_manager', - 'name' => 'supervisor', - 'version' => 'latest', - ]); - } - - /** - * add redis - */ - protected function addRedis(): void - { - $this->server->services()->create([ - 'type' => 'memory_database', - 'name' => 'redis', - 'version' => 'latest', - ]); - } - - /** - * add ufw - */ - protected function addUfw(): void - { - $this->server->services()->create([ - 'type' => 'firewall', - 'name' => 'ufw', - 'version' => 'latest', - ]); - } } diff --git a/app/Contracts/ServerType.php b/app/ServerTypes/ServerType.php similarity index 66% rename from app/Contracts/ServerType.php rename to app/ServerTypes/ServerType.php index ba9252a..382bd6f 100755 --- a/app/Contracts/ServerType.php +++ b/app/ServerTypes/ServerType.php @@ -1,10 +1,10 @@ service = $service; - } -} diff --git a/app/ServiceHandlers/Database/Mysql.php b/app/ServiceHandlers/Database/Mysql.php deleted file mode 100755 index dffec82..0000000 --- a/app/ServiceHandlers/Database/Mysql.php +++ /dev/null @@ -1,138 +0,0 @@ -service->server->ssh()->exec( - new CreateCommand('mysql', $name), - 'create-database' - ); - } - - /** - * @throws Throwable - */ - public function delete(string $name): void - { - $this->service->server->ssh()->exec( - new DeleteCommand('mysql', $name), - 'delete-database' - ); - } - - /** - * @throws Throwable - */ - public function createUser(string $username, string $password, string $host): void - { - $this->service->server->ssh()->exec( - new CreateUserCommand('mysql', $username, $password, $host), - 'create-user' - ); - } - - /** - * @throws Throwable - */ - public function deleteUser(string $username, string $host): void - { - $this->service->server->ssh()->exec( - new DeleteUserCommand('mysql', $username, $host), - 'delete-user' - ); - } - - /** - * @throws Throwable - */ - public function link(string $username, string $host, array $databases): void - { - $ssh = $this->service->server->ssh(); - foreach ($databases as $database) { - $ssh->exec( - new LinkCommand('mysql', $username, $host, $database), - 'link-user-to-databases' - ); - } - } - - /** - * @throws Throwable - */ - public function unlink(string $username, string $host): void - { - $this->service->server->ssh()->exec( - new UnlinkCommand('mysql', $username, $host), - 'unlink-user-from-database' - ); - } - - /** - * @throws Throwable - */ - public function runBackup(BackupFile $backupFile): void - { - // backup - $this->service->server->ssh()->exec( - new BackupDatabaseCommand( - 'mysql', - $backupFile->backup->database->name, - $backupFile->name - ), - 'backup-database' - ); - - // upload to storage - $upload = $backupFile->backup->storage->provider()->upload( - $backupFile->backup->server, - $backupFile->path, - $backupFile->storage_path, - ); - - // cleanup - $this->service->server->ssh()->exec('rm '.$backupFile->name.'.zip'); - - $backupFile->size = $upload['size']; - $backupFile->save(); - } - - /** - * @throws Throwable - */ - public function restoreBackup(BackupFile $backupFile, string $database): void - { - // download - $backupFile->backup->storage->provider()->download( - $backupFile->backup->server, - $backupFile->storage_path, - $backupFile->name.'.zip', - ); - - // restore - $this->service->server->ssh()->exec( - new RestoreDatabaseCommand( - 'mysql', - $database, - $backupFile->name - ), - 'restore-database' - ); - } -} diff --git a/app/ServiceHandlers/Firewall/Ufw.php b/app/ServiceHandlers/Firewall/Ufw.php deleted file mode 100755 index d6dd8e7..0000000 --- a/app/ServiceHandlers/Firewall/Ufw.php +++ /dev/null @@ -1,32 +0,0 @@ -service->server->ssh()->exec( - new AddRuleCommand('ufw', $type, $protocol, $port, $source, $mask), - 'add-firewall-rule' - ); - } - - /** - * @throws Throwable - */ - public function removeRule(string $type, string $protocol, int $port, string $source, ?string $mask): void - { - $this->service->server->ssh()->exec( - new RemoveRuleCommand('ufw', $type, $protocol, $port, $source, $mask), - 'remove-firewall-rule' - ); - } -} diff --git a/app/ServiceHandlers/PHP.php b/app/ServiceHandlers/PHP.php deleted file mode 100644 index fb337b6..0000000 --- a/app/ServiceHandlers/PHP.php +++ /dev/null @@ -1,30 +0,0 @@ -service = $service; - } - - public function setDefaultCli(): void - { - $this->service->update(['status' => ServiceStatus::RESTARTING]); - - dispatch(new SetDefaultCli($this->service))->onConnection('ssh'); - } - - public function installExtension($name): void - { - dispatch(new InstallPHPExtension($this->service, $name))->onConnection('ssh-long'); - } -} diff --git a/app/ServiceHandlers/Webserver/AbstractWebserver.php b/app/ServiceHandlers/Webserver/AbstractWebserver.php deleted file mode 100755 index b194ff1..0000000 --- a/app/ServiceHandlers/Webserver/AbstractWebserver.php +++ /dev/null @@ -1,16 +0,0 @@ -service = $service; - } -} diff --git a/app/ServiceHandlers/Webserver/Nginx.php b/app/ServiceHandlers/Webserver/Nginx.php deleted file mode 100755 index c0d93f8..0000000 --- a/app/ServiceHandlers/Webserver/Nginx.php +++ /dev/null @@ -1,206 +0,0 @@ -service->server->ssh()->exec( - new CreateNginxVHostCommand( - $site->domain, - $site->path, - $this->generateVhost($site) - ), - 'create-vhost', - $site->id - ); - } - - /** - * @throws Throwable - */ - public function updateVHost(Site $site, bool $noSSL = false, ?string $vhost = null): void - { - $this->service->server->ssh()->exec( - new UpdateNginxVHostCommand( - $site->domain, - $site->path, - $vhost ?? $this->generateVhost($site, $noSSL) - ), - 'update-vhost', - $site->id - ); - } - - public function getVHost(Site $site): string - { - return $this->service->server->ssh()->exec( - new GetNginxVHostCommand( - $site->domain - ), - 'get-vhost', - $site->id - ); - } - - /** - * @throws Throwable - */ - public function deleteSite(Site $site): void - { - $this->service->server->ssh()->exec( - new DeleteNginxSiteCommand( - $site->domain, - $site->path - ), - 'delete-site', - $site->id - ); - $this->service->restart(); - } - - /** - * @throws Throwable - */ - public function changePHPVersion(Site $site, $version): void - { - $this->service->server->ssh()->exec( - new ChangeNginxPHPVersionCommand($site->domain, $site->php_version, $version), - 'change-php-version', - $site->id - ); - } - - /** - * @throws Throwable - */ - public function setupSSL(Ssl $ssl): void - { - $command = new CreateLetsencryptSSLCommand( - $ssl->site->server->creator->email, - $ssl->site->domain, - $ssl->site->web_directory_path - ); - if ($ssl->type == 'custom') { - $command = new CreateCustomSSLCommand( - $ssl->certs_directory_path, - $ssl->certificate, - $ssl->pk, - $ssl->certificate_path, - $ssl->pk_path, - ); - } - $result = $this->service->server->ssh()->exec( - $command, - 'create-ssl', - $ssl->site_id - ); - if (! $ssl->validateSetup($result)) { - throw new SSLCreationException(); - } - - $this->updateVHost($ssl->site); - } - - /** - * @throws Throwable - */ - public function removeSSL(Ssl $ssl): void - { - $this->service->server->ssh()->exec( - new RemoveSSLCommand($ssl->certs_directory_path), - 'remove-ssl', - $ssl->site_id - ); - - $this->updateVHost($ssl->site, true); - } - - /** - * @throws Throwable - */ - public function updateRedirects(Site $site, array $redirects): void - { - $redirectsPlain = ''; - foreach ($redirects as $redirect) { - $rd = File::get(resource_path('commands/webserver/nginx/redirect.conf')); - $rd = Str::replace('__from__', $redirect->from, $rd); - $rd = Str::replace('__mode__', $redirect->mode, $rd); - $rd = Str::replace('__to__', $redirect->to, $rd); - $redirectsPlain .= $rd."\n"; - } - $result = $this->service->server->ssh()->exec( - new UpdateNginxRedirectsCommand( - $site->domain, - $redirectsPlain, - ), - 'update-redirects', - $site->id - ); - if (Str::contains($result, 'journalctl -xe')) { - throw new ErrorUpdatingRedirects(); - } - } - - protected function generateVhost(Site $site, bool $noSSL = false): string - { - $ssl = $site->activeSsl; - if ($noSSL) { - $ssl = null; - } - $vhost = File::get(resource_path('commands/webserver/nginx/vhost.conf')); - if ($ssl) { - $vhost = File::get(resource_path('commands/webserver/nginx/vhost-ssl.conf')); - } - if ($site->type()->language() === 'php') { - $vhost = File::get(resource_path('commands/webserver/nginx/php-vhost.conf')); - if ($ssl) { - $vhost = File::get(resource_path('commands/webserver/nginx/php-vhost-ssl.conf')); - } - } - if ($site->port) { - $vhost = File::get(resource_path('commands/webserver/nginx/reverse-vhost.conf')); - if ($ssl) { - $vhost = File::get(resource_path('commands/webserver/nginx/reverse-vhost-ssl.conf')); - } - $vhost = Str::replace('__port__', (string) $site->port, $vhost); - } - - $vhost = Str::replace('__domain__', $site->domain, $vhost); - $vhost = Str::replace('__aliases__', $site->aliases_string, $vhost); - $vhost = Str::replace('__path__', $site->path, $vhost); - $vhost = Str::replace('__web_directory__', $site->web_directory, $vhost); - - if ($ssl) { - $vhost = Str::replace('__certificate__', $ssl->certificate_path, $vhost); - $vhost = Str::replace('__private_key__', $ssl->pk_path, $vhost); - } - - if ($site->php_version) { - $vhost = Str::replace('__php_version__', $site->php_version, $vhost); - } - - return $vhost; - } -} diff --git a/app/SiteTypes/AbstractSiteType.php b/app/SiteTypes/AbstractSiteType.php index 22a29a8..38998dc 100755 --- a/app/SiteTypes/AbstractSiteType.php +++ b/app/SiteTypes/AbstractSiteType.php @@ -2,11 +2,8 @@ namespace App\SiteTypes; -use App\Contracts\SiteType; -use App\Events\Broadcast; -use App\Jobs\Site\DeleteSite; +use App\Exceptions\SourceControlIsNotConnected; use App\Models\Site; -use Closure; abstract class AbstractSiteType implements SiteType { @@ -17,22 +14,25 @@ public function __construct(Site $site) $this->site = $site; } - public function delete(): void + protected function progress(int $percentage): void { - dispatch(new DeleteSite($this->site))->onConnection('ssh'); + $this->site->progress = $percentage; + $this->site->save(); } - protected function progress(int $percentage): Closure + /** + * @throws SourceControlIsNotConnected + */ + protected function deployKey(): void { - return function () use ($percentage) { - $this->site->progress = $percentage; - $this->site->save(); - event( - new Broadcast('site-installation-progress', [ - 'site' => $this->site, - 'percentage' => $percentage, - ]) - ); - }; + $os = $this->site->server->os(); + $os->generateSSHKey($this->site->getSshKeyName()); + $this->site->ssh_key = $os->readSSHKey($this->site->getSshKeyName()); + $this->site->save(); + $this->site->sourceControl()->provider()->deployKey( + $this->site->domain.'-key-'.$this->site->id, + $this->site->repository, + $this->site->ssh_key + ); } } diff --git a/app/SiteTypes/PHPBlank.php b/app/SiteTypes/PHPBlank.php index f4cd468..36fa2d3 100755 --- a/app/SiteTypes/PHPBlank.php +++ b/app/SiteTypes/PHPBlank.php @@ -3,10 +3,7 @@ namespace App\SiteTypes; use App\Enums\SiteFeature; -use App\Jobs\Site\CreateVHost; -use Illuminate\Support\Facades\Bus; use Illuminate\Validation\Rule; -use Throwable; class PHPBlank extends PHPSite { @@ -20,7 +17,7 @@ public function supportedFeatures(): array ]; } - public function createValidationRules(array $input): array + public function createRules(array $input): array { return [ 'php_version' => [ @@ -45,23 +42,8 @@ public function data(array $input): array public function install(): void { - $chain = [ - new CreateVHost($this->site), - $this->progress(65), - function () { - $this->site->php()?->restart(); - }, - ]; - - $chain[] = function () { - $this->site->installationFinished(); - }; - - Bus::chain($chain) - ->catch(function (Throwable $e) { - $this->site->installationFailed($e); - }) - ->onConnection('ssh-long') - ->dispatch(); + $this->site->server->webserver()->handler()->createVHost($this->site); + $this->progress(65); + $this->site->php()?->restart(); } } diff --git a/app/SiteTypes/PHPSite.php b/app/SiteTypes/PHPSite.php index ec676d4..b7823e6 100755 --- a/app/SiteTypes/PHPSite.php +++ b/app/SiteTypes/PHPSite.php @@ -3,13 +3,10 @@ namespace App\SiteTypes; use App\Enums\SiteFeature; -use App\Jobs\Site\CloneRepository; -use App\Jobs\Site\ComposerInstall; -use App\Jobs\Site\CreateVHost; -use App\Jobs\Site\DeployKey; -use Illuminate\Support\Facades\Bus; +use App\Exceptions\SourceControlIsNotConnected; +use App\SSH\Composer\Composer; +use App\SSH\Git\Git; use Illuminate\Validation\Rule; -use Throwable; class PHPSite extends AbstractSiteType { @@ -28,7 +25,7 @@ public function supportedFeatures(): array ]; } - public function createValidationRules(array $input): array + public function createRules(array $input): array { return [ 'php_version' => [ @@ -66,37 +63,24 @@ public function data(array $input): array ]; } + /** + * @throws SourceControlIsNotConnected + */ public function install(): void { - $chain = [ - new CreateVHost($this->site), - $this->progress(15), - new DeployKey($this->site), - $this->progress(30), - new CloneRepository($this->site), - $this->progress(65), - function () { - $this->site->php()?->restart(); - }, - ]; - + $this->site->server->webserver()->handler()->createVHost($this->site); + $this->progress(15); + $this->deployKey(); + $this->progress(30); + app(Git::class)->clone($this->site); + $this->progress(65); + $this->site->php()?->restart(); if ($this->site->type_data['composer']) { - $chain[] = new ComposerInstall($this->site); + app(Composer::class)->installDependencies($this->site); } - - $chain[] = function () { - $this->site->installationFinished(); - }; - - Bus::chain($chain) - ->catch(function (Throwable $e) { - $this->site->installationFailed($e); - }) - ->onConnection('ssh-long') - ->dispatch(); } - public function editValidationRules(array $input): array + public function editRules(array $input): array { return []; } diff --git a/app/Contracts/SiteType.php b/app/SiteTypes/SiteType.php similarity index 61% rename from app/Contracts/SiteType.php rename to app/SiteTypes/SiteType.php index 7e82038..a65019d 100755 --- a/app/Contracts/SiteType.php +++ b/app/SiteTypes/SiteType.php @@ -1,6 +1,6 @@ [ @@ -70,7 +68,7 @@ public function createFields(array $input): array public function data(array $input): array { return [ - 'url' => $this->site->url, + 'url' => $this->site->getUrl(), 'title' => $input['title'], 'username' => $input['username'], 'email' => $input['email'], @@ -83,79 +81,37 @@ public function data(array $input): array public function install(): void { - $chain = [ - new CreateVHost($this->site), - $this->progress(15), - function () { - /** @var Database $database */ - $database = $this->site->server->databases()->create([ - 'name' => $this->site->type_data['database'], - ]); - $database->createOnServer('sync'); - /** @var DatabaseUser $databaseUser */ - $databaseUser = $this->site->server->databaseUsers()->create([ - 'username' => $this->site->type_data['database_user'], - 'password' => $this->site->type_data['database_password'], - 'databases' => [$this->site->type_data['database']], - ]); - $databaseUser->createOnServer('sync'); - $databaseUser->unlinkUser('sync'); - $databaseUser->linkUser('sync'); - }, - $this->progress(50), - new InstallWordpress($this->site), - $this->progress(75), - function () { - $this->site->php()?->restart(); - $this->site->installationFinished(); - }, - ]; - - Bus::chain($chain) - ->catch(function (Throwable $e) { - $this->site->installationFailed($e); - }) - ->onConnection('ssh-long') - ->dispatch(); + $this->site->server->webserver()->handler()->createVHost($this->site); + $this->progress(30); + /** @var Database $database */ + $database = app(CreateDatabase::class)->create($this->site->server, [ + 'name' => $this->site->type_data['database'], + ]); + /** @var DatabaseUser $databaseUser */ + $databaseUser = app(CreateDatabaseUser::class)->create($this->site->server, [ + 'username' => $this->site->type_data['database_user'], + 'password' => $this->site->type_data['database_password'], + 'remote' => false, + 'host' => 'localhost', + ], [$database->name]); + app(LinkUser::class)->link($databaseUser, [ + 'databases' => [$database->name], + ]); + $this->site->php()?->restart(); + $this->progress(60); + app(\App\SSH\Wordpress\Wordpress::class)->install($this->site); } - public function editValidationRules(array $input): array + public function editRules(array $input): array { return [ 'title' => 'required', 'url' => 'required', - // 'email' => 'required|email', ]; } public function edit(): void { - $this->site->status = 'installing'; - $this->site->progress = 90; - $this->site->save(); - $chain = [ - function () { - $this->site->server->ssh()->exec( - new UpdateWordpressCommand( - $this->site->path, - $this->site->type_data['url'], - $this->site->type_data['username'] ?? '', - $this->site->type_data['password'] ?? '', - $this->site->type_data['email'] ?? '', - $this->site->type_data['title'] ?? '', - ), - 'update-wordpress', - $this->site->id - ); - $this->site->installationFinished(); - }, - ]; - - Bus::chain($chain) - ->catch(function (Throwable $e) { - $this->site->installationFailed($e); - }) - ->onConnection('ssh') - ->dispatch(); + // } } diff --git a/app/SourceControlProviders/AbstractSourceControlProvider.php b/app/SourceControlProviders/AbstractSourceControlProvider.php index 86f1087..9dcbea1 100755 --- a/app/SourceControlProviders/AbstractSourceControlProvider.php +++ b/app/SourceControlProviders/AbstractSourceControlProvider.php @@ -2,7 +2,6 @@ namespace App\SourceControlProviders; -use App\Contracts\SourceControlProvider; use App\Exceptions\RepositoryNotFound; use App\Exceptions\RepositoryPermissionDenied; use App\Exceptions\SourceControlIsNotConnected; @@ -18,6 +17,35 @@ public function __construct(SourceControl $sourceControl) $this->sourceControl = $sourceControl; } + public function createRules(array $input): array + { + return [ + 'token' => 'required', + 'url' => [ + 'nullable', + 'url:http,https', + 'ends_with:/', + ], + ]; + } + + public function createData(array $input): array + { + return [ + 'token' => $input['token'] ?? '', + ]; + } + + public function data(): array + { + // support for older data + $token = $this->sourceControl->access_token ?? ''; + + return [ + 'token' => $this->sourceControl->provider_data['token'] ?? $token, + ]; + } + /** * @throws SourceControlIsNotConnected * @throws RepositoryNotFound diff --git a/app/SourceControlProviders/Bitbucket.php b/app/SourceControlProviders/Bitbucket.php index eddf272..8dcd339 100755 --- a/app/SourceControlProviders/Bitbucket.php +++ b/app/SourceControlProviders/Bitbucket.php @@ -13,9 +13,33 @@ class Bitbucket extends AbstractSourceControlProvider { protected string $apiUrl = 'https://api.bitbucket.org/2.0'; + public function createRules(array $input): array + { + return [ + 'username' => 'required', + 'password' => 'required', + ]; + } + + public function createData(array $input): array + { + return [ + 'username' => $input['username'] ?? '', + 'password' => $input['password'] ?? '', + ]; + } + + public function data(): array + { + return [ + 'username' => $this->sourceControl->provider_data['username'] ?? '', + 'password' => $this->sourceControl->provider_data['password'] ?? '', + ]; + } + public function connect(): bool { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withHeaders($this->getAuthenticationHeaders()) ->get($this->apiUrl.'/repositories'); return $res->successful(); @@ -26,7 +50,7 @@ public function connect(): bool */ public function getRepo(?string $repo = null): mixed { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withHeaders($this->getAuthenticationHeaders()) ->get($this->apiUrl."/repositories/$repo"); $this->handleResponseErrors($res, $repo); @@ -44,14 +68,15 @@ public function fullRepoUrl(string $repo, string $key): string */ public function deployHook(string $repo, array $events, string $secret): array { - $response = Http::withToken($this->sourceControl->access_token)->post($this->apiUrl."/repositories/$repo/hooks", [ - 'description' => 'deploy', - 'url' => url('/api/git-hooks?secret='.$secret), - 'events' => [ - 'repo:'.implode(',', $events), - ], - 'active' => true, - ]); + $response = Http::withHeaders($this->getAuthenticationHeaders()) + ->post($this->apiUrl."/repositories/$repo/hooks", [ + 'description' => 'deploy', + 'url' => url('/api/git-hooks?secret='.$secret), + 'events' => [ + 'repo:'.implode(',', $events), + ], + 'active' => true, + ]); if ($response->status() != 201) { throw new FailedToDeployGitHook($response->json()['error']['message']); @@ -69,7 +94,8 @@ public function deployHook(string $repo, array $events, string $secret): array public function destroyHook(string $repo, string $hookId): void { $hookId = urlencode($hookId); - $response = Http::withToken($this->sourceControl->access_token)->delete($this->apiUrl."/repositories/$repo/hooks/$hookId"); + $response = Http::withHeaders($this->getAuthenticationHeaders()) + ->delete($this->apiUrl."/repositories/$repo/hooks/$hookId"); if ($response->status() != 204) { throw new FailedToDestroyGitHook('Error'); @@ -81,7 +107,7 @@ public function destroyHook(string $repo, string $hookId): void */ public function getLastCommit(string $repo, string $branch): ?array { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withHeaders($this->getAuthenticationHeaders()) ->get($this->apiUrl."/repositories/$repo/commits?include=".$branch); $this->handleResponseErrors($res, $repo); @@ -108,7 +134,7 @@ public function getLastCommit(string $repo, string $branch): ?array */ public function deployKey(string $title, string $repo, string $key): void { - $res = Http::withToken($this->sourceControl->access_token)->post( + $res = Http::withHeaders($this->getAuthenticationHeaders())->post( $this->apiUrl."/repositories/$repo/deploy-keys", [ 'label' => $title, @@ -116,7 +142,7 @@ public function deployKey(string $title, string $repo, string $key): void ] ); - if ($res->status() != 201) { + if ($res->status() != 200) { throw new FailedToDeployGitKey($res->json()['error']['message']); } } @@ -130,4 +156,15 @@ protected function getCommitter(string $raw): array 'email' => Str::replace('>', '', $committer[1]), ]; } + + private function getAuthenticationHeaders(): array + { + $username = $this->data()['username']; + $password = $this->data()['password']; + $basicAuth = base64_encode("$username:$password"); + + return [ + 'Authorization' => 'Basic '.$basicAuth, + ]; + } } diff --git a/app/SourceControlProviders/Github.php b/app/SourceControlProviders/Github.php index f5ea642..9a5b5b9 100755 --- a/app/SourceControlProviders/Github.php +++ b/app/SourceControlProviders/Github.php @@ -16,7 +16,7 @@ public function connect(): bool { $res = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->get($this->apiUrl.'/user/repos'); return $res->successful(); @@ -34,7 +34,7 @@ public function getRepo(?string $repo = null): mixed } $res = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->get($url); $this->handleResponseErrors($res, $repo); @@ -54,7 +54,7 @@ public function deployHook(string $repo, array $events, string $secret): array { $response = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->post($this->apiUrl."/repos/$repo/hooks", [ 'name' => 'web', 'events' => $events, @@ -82,7 +82,7 @@ public function destroyHook(string $repo, string $hookId): void { $response = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->delete($this->apiUrl."/repos/$repo/hooks/$hookId"); if ($response->status() != 204) { @@ -98,7 +98,7 @@ public function getLastCommit(string $repo, string $branch): ?array $url = $this->apiUrl.'/repos/'.$repo.'/commits/'.$branch; $res = Http::withHeaders([ 'Accept' => 'application/vnd.github.v3+json', - 'Authorization' => 'Bearer '.$this->sourceControl->access_token, + 'Authorization' => 'Bearer '.$this->data()['token'], ])->get($url); $this->handleResponseErrors($res, $repo); @@ -124,7 +124,7 @@ public function getLastCommit(string $repo, string $branch): ?array */ public function deployKey(string $title, string $repo, string $key): void { - $response = Http::withToken($this->sourceControl->access_token)->post( + $response = Http::withToken($this->data()['token'])->post( $this->apiUrl.'/repos/'.$repo.'/keys', [ 'title' => $title, diff --git a/app/SourceControlProviders/Gitlab.php b/app/SourceControlProviders/Gitlab.php index 73d4b45..4000a25 100755 --- a/app/SourceControlProviders/Gitlab.php +++ b/app/SourceControlProviders/Gitlab.php @@ -16,7 +16,7 @@ class Gitlab extends AbstractSourceControlProvider public function connect(): bool { - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withToken($this->data()['token']) ->get($this->getApiUrl().'/projects'); return $res->successful(); @@ -28,7 +28,7 @@ public function connect(): bool public function getRepo(?string $repo = null): mixed { $repository = $repo ? urlencode($repo) : null; - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withToken($this->data()['token']) ->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits'); $this->handleResponseErrors($res, $repo); @@ -49,7 +49,7 @@ public function fullRepoUrl(string $repo, string $key): string public function deployHook(string $repo, array $events, string $secret): array { $repository = urlencode($repo); - $response = Http::withToken($this->sourceControl->access_token)->post( + $response = Http::withToken($this->data()['token'])->post( $this->getApiUrl().'/projects/'.$repository.'/hooks', [ 'description' => 'deploy', @@ -84,7 +84,7 @@ public function deployHook(string $repo, array $events, string $secret): array public function destroyHook(string $repo, string $hookId): void { $repository = urlencode($repo); - $response = Http::withToken($this->sourceControl->access_token)->delete( + $response = Http::withToken($this->data()['token'])->delete( $this->getApiUrl().'/projects/'.$repository.'/hooks/'.$hookId ); @@ -99,7 +99,7 @@ public function destroyHook(string $repo, string $hookId): void public function getLastCommit(string $repo, string $branch): ?array { $repository = urlencode($repo); - $res = Http::withToken($this->sourceControl->access_token) + $res = Http::withToken($this->data()['token']) ->get($this->getApiUrl().'/projects/'.$repository.'/repository/commits?ref_name='.$branch); $this->handleResponseErrors($res, $repo); @@ -126,7 +126,7 @@ public function getLastCommit(string $repo, string $branch): ?array public function deployKey(string $title, string $repo, string $key): void { $repository = urlencode($repo); - $response = Http::withToken($this->sourceControl->access_token)->post( + $response = Http::withToken($this->data()['token'])->post( $this->getApiUrl().'/projects/'.$repository.'/deploy_keys', [ 'title' => $title, diff --git a/app/Contracts/SourceControlProvider.php b/app/SourceControlProviders/SourceControlProvider.php similarity index 73% rename from app/Contracts/SourceControlProvider.php rename to app/SourceControlProviders/SourceControlProvider.php index cd2a0ca..a695b75 100755 --- a/app/Contracts/SourceControlProvider.php +++ b/app/SourceControlProviders/SourceControlProvider.php @@ -1,9 +1,15 @@ successful(); } - /** - * @throws Throwable - */ - public function upload(Server $server, string $src, string $dest): array + public function ssh(Server $server): Storage { - $upload = $server->ssh()->exec( - new UploadToDropboxCommand( - $src, - $dest, - $this->storageProvider->credentials['token'] - ), - 'upload-to-dropbox' - ); - - $data = json_decode($upload, true); - - if (isset($data['error'])) { - throw new BackupFileException('Failed to upload to Dropbox '.$data['error_summary'] ?? ''); - } - - return [ - 'size' => $data['size'] ?? null, - ]; - } - - /** - * @throws Throwable - */ - public function download(Server $server, string $src, string $dest): void - { - $server->ssh()->exec( - new DownloadFromDropboxCommand( - $src, - $dest, - $this->storageProvider->credentials['token'] - ), - 'download-from-dropbox' - ); + return new \App\SSH\Storage\Dropbox($server, $this->storageProvider); } public function delete(array $paths): void diff --git a/app/StorageProviders/FTP.php b/app/StorageProviders/FTP.php index 93e8f02..88c2a0d 100644 --- a/app/StorageProviders/FTP.php +++ b/app/StorageProviders/FTP.php @@ -3,10 +3,8 @@ namespace App\StorageProviders; use App\Models\Server; -use App\SSHCommands\Storage\DownloadFromFTPCommand; -use App\SSHCommands\Storage\UploadToFTPCommand; +use App\SSH\Storage\Storage; use FTP\Connection; -use Throwable; class FTP extends AbstractStorageProvider { @@ -49,48 +47,9 @@ public function connect(): bool return $isConnected; } - /** - * @throws Throwable - */ - public function upload(Server $server, string $src, string $dest): array + public function ssh(Server $server): Storage { - $server->ssh()->exec( - new UploadToFTPCommand( - $src, - $this->storageProvider->credentials['path'].'/'.$dest, - $this->storageProvider->credentials['host'], - $this->storageProvider->credentials['port'], - $this->storageProvider->credentials['username'], - $this->storageProvider->credentials['password'], - $this->storageProvider->credentials['ssl'], - $this->storageProvider->credentials['passive'], - ), - 'upload-to-ftp' - ); - - return [ - 'size' => null, - ]; - } - - /** - * @throws Throwable - */ - public function download(Server $server, string $src, string $dest): void - { - $server->ssh()->exec( - new DownloadFromFTPCommand( - $this->storageProvider->credentials['path'].'/'.$src, - $dest, - $this->storageProvider->credentials['host'], - $this->storageProvider->credentials['port'], - $this->storageProvider->credentials['username'], - $this->storageProvider->credentials['password'], - $this->storageProvider->credentials['ssl'], - $this->storageProvider->credentials['passive'], - ), - 'download-from-ftp' - ); + return new \App\SSH\Storage\FTP($server, $this->storageProvider); } public function delete(array $paths): void diff --git a/app/Contracts/StorageProvider.php b/app/StorageProviders/StorageProvider.php similarity index 58% rename from app/Contracts/StorageProvider.php rename to app/StorageProviders/StorageProvider.php index fe5643b..06f57f4 100644 --- a/app/Contracts/StorageProvider.php +++ b/app/StorageProviders/StorageProvider.php @@ -1,8 +1,9 @@ buildAuthUrlFromBase('https://www.dropbox.com/oauth2/authorize', $state); - } - - /** - * {@inheritdoc} - */ - protected function getTokenUrl(): string - { - return 'https://api.dropboxapi.com/oauth2/token'; - } - - /** - * {@inheritdoc} - */ - protected function getTokenFields($code): array - { - return array_merge(parent::getTokenFields($code), [ - 'grant_type' => 'authorization_code', - ]); - } - - /** - * {@inheritdoc} - * - * @throws GuzzleException - */ - protected function getUserByToken($token) - { - $response = $this->getHttpClient()->post('https://api.dropboxapi.com/2/users/get_current_account', [ - 'headers' => [ - 'Authorization' => 'Bearer '.$token, - ], - ]); - - return json_decode($response->getBody(), true); - } - - /** - * {@inheritdoc} - */ - protected function mapUserToObject(array $user): User - { - return (new User)->setRaw($user)->map([ - 'id' => $user['account_id'], - 'nickname' => null, - 'name' => $user['name']['display_name'], - 'email' => $user['email'], - 'avatar' => Arr::get($user, 'profile_photo_url'), - ]); - } -} diff --git a/app/Support/Testing/SSHFake.php b/app/Support/Testing/SSHFake.php index bee86ae..def89d1 100644 --- a/app/Support/Testing/SSHFake.php +++ b/app/Support/Testing/SSHFake.php @@ -2,53 +2,46 @@ namespace App\Support\Testing; -use App\Contracts\SSHCommand; -use App\Models\Server; +use App\Exceptions\SSHConnectionError; +use App\Helpers\SSH; use Illuminate\Support\Traits\ReflectsClosures; -use PHPUnit\Framework\Assert as PHPUnit; +use PHPUnit\Framework\Assert; -class SSHFake +class SSHFake extends SSH { use ReflectsClosures; - protected array $commands; + protected array $commands = []; - protected string $output = ''; + protected ?string $output; - public function init(Server $server, ?string $asUser = null): self - { - return $this; - } + protected bool $connectionWillFail = false; - public function outputShouldBe(string $output): self + public function __construct(?string $output = null) { $this->output = $output; - - return $this; } - public function assertExecuted(array|string $commands): void + public function connectionWillFail(): void { - if (! $this->commands) { - PHPUnit::fail('No commands are executed'); - } - if (! is_array($commands)) { - $commands = [$commands]; - } - $allExecuted = true; - foreach ($commands as $command) { - if (! in_array($command, $commands)) { - $allExecuted = false; - } - } - if (! $allExecuted) { - PHPUnit::fail('The expected commands are not executed'); - } - PHPUnit::assertTrue(true, $allExecuted); + $this->connectionWillFail = true; } - public function exec(string|array|SSHCommand $commands, string $log = '', ?int $siteId = null): string + public function connect(bool $sftp = false): void { + if ($this->connectionWillFail) { + throw new SSHConnectionError('Connection failed'); + } + } + + public function exec(string|array $commands, string $log = '', ?int $siteId = null): string + { + if ($log) { + $this->setLog($log, $siteId); + } else { + $this->log = null; + } + if (! is_array($commands)) { $commands = [$commands]; } @@ -61,6 +54,54 @@ public function exec(string|array|SSHCommand $commands, string $log = '', ?int $ } } - return 'fake output'; + $output = $this->output ?? 'fake output'; + $this->log?->write($output); + + return $output; + } + + public function upload(string $local, string $remote): void + { + $this->log = null; + } + + public function assertExecuted(array|string $commands): void + { + if (! $this->commands) { + Assert::fail('No commands are executed'); + } + if (! is_array($commands)) { + $commands = [$commands]; + } + $allExecuted = true; + foreach ($commands as $command) { + if (! in_array($command, $commands)) { + $allExecuted = false; + } + } + if (! $allExecuted) { + Assert::fail('The expected commands are not executed. executed commands: '.implode(', ', $this->commands)); + } + Assert::assertTrue(true, $allExecuted); + } + + public function assertExecutedContains(string $command): void + { + if (! $this->commands) { + Assert::fail('No commands are executed'); + } + $executed = false; + foreach ($this->commands as $executedCommand) { + if (str($executedCommand)->contains($command)) { + $executed = true; + break; + } + } + if (! $executed) { + Assert::fail( + 'The expected command is not executed in the executed commands: '.implode(', ', $this->commands) + ); + } + Assert::assertTrue(true, $executed); } } diff --git a/app/Support/helpers.php b/app/Support/helpers.php index dc45fba..d22c9a2 100755 --- a/app/Support/helpers.php +++ b/app/Support/helpers.php @@ -1,36 +1,6 @@ format('Y-m-d H:i:s'); } -function cast_to_json(array $json): Illuminate\Database\Query\Expression|Expression +function htmx(): HtmxResponse { - $json = addslashes(json_encode($json)); - - return DB::raw("CAST('{$json}' AS JSON)"); + return new HtmxResponse(); } diff --git a/app/Traits/HasCustomPaginationView.php b/app/Traits/HasCustomPaginationView.php deleted file mode 100644 index 0bf06e9..0000000 --- a/app/Traits/HasCustomPaginationView.php +++ /dev/null @@ -1,15 +0,0 @@ - 'refreshComponent', - 'refreshComponent' => '$refresh', - '$refresh', - ]; - } - - public function refreshComponent(array $data): void - { - $this->dispatch('refreshComponent'); - } -} diff --git a/app/ValidationRules/CronRule.php b/app/ValidationRules/CronRule.php index 3b79b22..8f03a4d 100755 --- a/app/ValidationRules/CronRule.php +++ b/app/ValidationRules/CronRule.php @@ -7,9 +7,16 @@ class CronRule implements Rule { + private bool $acceptCustom; + + public function __construct(bool $acceptCustom = false) + { + $this->acceptCustom = $acceptCustom; + } + public function passes($attribute, $value): bool { - return CronExpression::isValidExpression($value); + return CronExpression::isValidExpression($value) || ($this->acceptCustom && $value === 'custom'); } public function message(): string diff --git a/app/View/Components/Heroicon.php b/app/View/Components/Heroicon.php new file mode 100644 index 0000000..9bd0861 --- /dev/null +++ b/app/View/Components/Heroicon.php @@ -0,0 +1,26 @@ +name); + } +} diff --git a/composer.json b/composer.json index 1540658..5117ba9 100644 --- a/composer.json +++ b/composer.json @@ -2,25 +2,18 @@ "name": "vitodeploy/vito", "type": "project", "description": "The ultimate server management tool", - "keywords": ["framework", "laravel"], + "keywords": [ + "framework", + "laravel" + ], "license": "AGPL-3.0", "require": { - "php": "^8.1", + "php": "^8.2", "ext-ftp": "*", "aws/aws-sdk-php": "^3.158", - "bensampo/laravel-enum": "^6.3", - "blade-ui-kit/blade-heroicons": "^2.2", - "davidhsianturi/blade-bootstrap-icons": "^1.4", - "guzzlehttp/guzzle": "^7.2", - "khatabwedaa/blade-css-icons": "^1.3", "laravel/fortify": "^1.17", "laravel/framework": "^10.0", - "laravel/sanctum": "^3.2", - "laravel/socialite": "^5.2", "laravel/tinker": "^2.8", - "livewire/livewire": "^3.0", - "opcodesio/log-viewer": "^3.0", - "owenvoke/blade-fontawesome": "^2.5", "phpseclib/phpseclib": "~3.0" }, "require-dev": { diff --git a/composer.lock b/composer.lock index 57a8f6b..a141953 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "f2e6a21fc0abada9bc40b4e80df42b26", + "content-hash": "e2a93e3df13daae2cf880eb44ed6f478", "packages": [ { "name": "aws/aws-crt-php", @@ -62,16 +62,16 @@ }, { "name": "aws/aws-sdk-php", - "version": "3.295.4", + "version": "3.300.14", "source": { "type": "git", "url": "https://github.com/aws/aws-sdk-php.git", - "reference": "2372661db989fe4229abd95f4434b37252076d58" + "reference": "f1e0c37d8403d7097c2c808c184137e6517d54be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/2372661db989fe4229abd95f4434b37252076d58", - "reference": "2372661db989fe4229abd95f4434b37252076d58", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/f1e0c37d8403d7097c2c808c184137e6517d54be", + "reference": "f1e0c37d8403d7097c2c808c184137e6517d54be", "shasum": "" }, "require": { @@ -151,9 +151,9 @@ "support": { "forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80", "issues": "https://github.com/aws/aws-sdk-php/issues", - "source": "https://github.com/aws/aws-sdk-php/tree/3.295.4" + "source": "https://github.com/aws/aws-sdk-php/tree/3.300.14" }, - "time": "2023-12-29T19:07:49+00:00" + "time": "2024-03-08T19:05:39+00:00" }, { "name": "bacon/bacon-qr-code", @@ -209,247 +209,6 @@ }, "time": "2022-12-07T17:46:57+00:00" }, - { - "name": "bensampo/laravel-enum", - "version": "v6.7.0", - "source": { - "type": "git", - "url": "https://github.com/BenSampo/laravel-enum.git", - "reference": "b4320ac20d5452b50cc2147038a38c3dc91de876" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/BenSampo/laravel-enum/zipball/b4320ac20d5452b50cc2147038a38c3dc91de876", - "reference": "b4320ac20d5452b50cc2147038a38c3dc91de876", - "shasum": "" - }, - "require": { - "composer/class-map-generator": "^1", - "illuminate/contracts": "^9 || ^10", - "illuminate/support": "^9 || ^10", - "laminas/laminas-code": "^3.4 || ^4", - "nikic/php-parser": "^4.13", - "php": "^8" - }, - "require-dev": { - "doctrine/dbal": "^3.4", - "ergebnis/composer-normalize": "^2.28.3", - "mll-lab/php-cs-fixer-config": "^5.4", - "mockery/mockery": "^1.5", - "nunomaduro/larastan": "^2.6.3", - "orchestra/testbench": "^7.6.1 || ^8", - "phpstan/phpstan": "^1.8.2", - "phpstan/phpstan-mockery": "^1.1", - "phpstan/phpstan-phpunit": "^1.1.1", - "phpunit/phpunit": "^9.5.21 || ^10", - "rector/rector": "^0.17.6", - "symplify/rule-doc-generator": "^11" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "BenSampo\\Enum\\EnumServiceProvider" - ] - }, - "phpstan": { - "includes": [ - "extension.neon" - ] - } - }, - "autoload": { - "psr-4": { - "BenSampo\\Enum\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Sampson", - "homepage": "https://sampo.co.uk", - "role": "Developer" - }, - { - "name": "Benedikt Franke", - "homepage": "https://franke.tech", - "role": "Developer" - } - ], - "description": "Simple, extensible and powerful enumeration implementation for Laravel.", - "homepage": "https://github.com/bensampo/laravel-enum", - "keywords": [ - "bensampo", - "enum", - "laravel", - "package", - "validation" - ], - "support": { - "issues": "https://github.com/BenSampo/laravel-enum/issues", - "source": "https://github.com/BenSampo/laravel-enum/tree/v6.7.0" - }, - "funding": [ - { - "url": "https://github.com/bensampo", - "type": "github" - } - ], - "time": "2023-11-15T15:39:24+00:00" - }, - { - "name": "blade-ui-kit/blade-heroicons", - "version": "2.2.1", - "source": { - "type": "git", - "url": "https://github.com/blade-ui-kit/blade-heroicons.git", - "reference": "bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/blade-ui-kit/blade-heroicons/zipball/bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a", - "reference": "bcf4be8f6bbde0bb4c23f2e3fb189b88dec1580a", - "shasum": "" - }, - "require": { - "blade-ui-kit/blade-icons": "^1.1", - "illuminate/support": "^9.0|^10.0", - "php": "^8.0" - }, - "require-dev": { - "orchestra/testbench": "^7.0|^8.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "BladeUI\\Heroicons\\BladeHeroiconsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "BladeUI\\Heroicons\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dries Vints", - "homepage": "https://driesvints.com" - } - ], - "description": "A package to easily make use of Heroicons in your Laravel Blade views.", - "homepage": "https://github.com/blade-ui-kit/blade-heroicons", - "keywords": [ - "Heroicons", - "blade", - "laravel" - ], - "support": { - "issues": "https://github.com/blade-ui-kit/blade-heroicons/issues", - "source": "https://github.com/blade-ui-kit/blade-heroicons/tree/2.2.1" - }, - "funding": [ - { - "url": "https://github.com/sponsors/driesvints", - "type": "github" - }, - { - "url": "https://www.paypal.com/paypalme/driesvints", - "type": "paypal" - } - ], - "time": "2023-12-18T20:44:03+00:00" - }, - { - "name": "blade-ui-kit/blade-icons", - "version": "1.5.3", - "source": { - "type": "git", - "url": "https://github.com/blade-ui-kit/blade-icons.git", - "reference": "b5e6603218e2347ac81cb780bc6f71c8c3b31f5b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/blade-ui-kit/blade-icons/zipball/b5e6603218e2347ac81cb780bc6f71c8c3b31f5b", - "reference": "b5e6603218e2347ac81cb780bc6f71c8c3b31f5b", - "shasum": "" - }, - "require": { - "illuminate/contracts": "^8.0|^9.0|^10.0", - "illuminate/filesystem": "^8.0|^9.0|^10.0", - "illuminate/support": "^8.0|^9.0|^10.0", - "illuminate/view": "^8.0|^9.0|^10.0", - "php": "^7.4|^8.0", - "symfony/console": "^5.3|^6.0", - "symfony/finder": "^5.3|^6.0" - }, - "require-dev": { - "mockery/mockery": "^1.3", - "orchestra/testbench": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^9.0" - }, - "bin": [ - "bin/blade-icons-generate" - ], - "type": "library", - "extra": { - "laravel": { - "providers": [ - "BladeUI\\Icons\\BladeIconsServiceProvider" - ] - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "BladeUI\\Icons\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Dries Vints", - "homepage": "https://driesvints.com" - } - ], - "description": "A package to easily make use of icons in your Laravel Blade views.", - "homepage": "https://github.com/blade-ui-kit/blade-icons", - "keywords": [ - "blade", - "icons", - "laravel", - "svg" - ], - "support": { - "issues": "https://github.com/blade-ui-kit/blade-icons/issues", - "source": "https://github.com/blade-ui-kit/blade-icons" - }, - "funding": [ - { - "url": "https://github.com/sponsors/driesvints", - "type": "github" - }, - { - "url": "https://www.paypal.com/paypalme/driesvints", - "type": "paypal" - } - ], - "time": "2023-10-18T10:50:13+00:00" - }, { "name": "brick/math", "version": "0.11.0", @@ -574,150 +333,6 @@ ], "time": "2023-12-11T17:09:12+00:00" }, - { - "name": "composer/class-map-generator", - "version": "1.1.0", - "source": { - "type": "git", - "url": "https://github.com/composer/class-map-generator.git", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/class-map-generator/zipball/953cc4ea32e0c31f2185549c7d216d7921f03da9", - "reference": "953cc4ea32e0c31f2185549c7d216d7921f03da9", - "shasum": "" - }, - "require": { - "composer/pcre": "^2.1 || ^3.1", - "php": "^7.2 || ^8.0", - "symfony/finder": "^4.4 || ^5.3 || ^6 || ^7" - }, - "require-dev": { - "phpstan/phpstan": "^1.6", - "phpstan/phpstan-deprecation-rules": "^1", - "phpstan/phpstan-phpunit": "^1", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/filesystem": "^5.4 || ^6", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "1.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\ClassMapGenerator\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "https://seld.be" - } - ], - "description": "Utilities to scan PHP code and generate class maps.", - "keywords": [ - "classmap" - ], - "support": { - "issues": "https://github.com/composer/class-map-generator/issues", - "source": "https://github.com/composer/class-map-generator/tree/1.1.0" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-06-30T13:58:57+00:00" - }, - { - "name": "composer/pcre", - "version": "3.1.1", - "source": { - "type": "git", - "url": "https://github.com/composer/pcre.git", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/composer/pcre/zipball/00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "reference": "00104306927c7a0919b4ced2aaa6782c1e61a3c9", - "shasum": "" - }, - "require": { - "php": "^7.4 || ^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.3", - "phpstan/phpstan-strict-rules": "^1.1", - "symfony/phpunit-bridge": "^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-main": "3.x-dev" - } - }, - "autoload": { - "psr-4": { - "Composer\\Pcre\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Jordi Boggiano", - "email": "j.boggiano@seld.be", - "homepage": "http://seld.be" - } - ], - "description": "PCRE wrapping library that offers type-safe preg_* replacements.", - "keywords": [ - "PCRE", - "preg", - "regex", - "regular expression" - ], - "support": { - "issues": "https://github.com/composer/pcre/issues", - "source": "https://github.com/composer/pcre/tree/3.1.1" - }, - "funding": [ - { - "url": "https://packagist.com", - "type": "custom" - }, - { - "url": "https://github.com/composer", - "type": "github" - }, - { - "url": "https://tidelift.com/funding/github/packagist/composer/composer", - "type": "tidelift" - } - ], - "time": "2023-10-11T07:11:09+00:00" - }, { "name": "dasprid/enum", "version": "1.0.5", @@ -768,67 +383,6 @@ }, "time": "2023-08-25T16:18:39+00:00" }, - { - "name": "davidhsianturi/blade-bootstrap-icons", - "version": "v1.4.0", - "source": { - "type": "git", - "url": "https://github.com/davidhsianturi/blade-bootstrap-icons.git", - "reference": "255040a0058683dd5a0fd36dfa0857a91a95137f" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/davidhsianturi/blade-bootstrap-icons/zipball/255040a0058683dd5a0fd36dfa0857a91a95137f", - "reference": "255040a0058683dd5a0fd36dfa0857a91a95137f", - "shasum": "" - }, - "require": { - "blade-ui-kit/blade-icons": "^1.0", - "illuminate/support": "^8.0|^9.0|^10.0", - "php": "^7.4|^8.0" - }, - "require-dev": { - "orchestra/testbench": "^6.0|^7.0|^8.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Davidhsianturi\\BladeBootstrapIcons\\BladeBootstrapIconsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Davidhsianturi\\BladeBootstrapIcons\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "David H. Sianturi", - "email": "davidhsianturi@gmail.com", - "homepage": "https://davidhsianturi.com", - "role": "Developer" - } - ], - "description": "A package to easily make use of Bootstrap Icons in your Laravel Blade views.", - "homepage": "https://github.com/davidhsianturi/blade-bootstrap-icons", - "keywords": [ - "Bootstrap Icons", - "blade", - "laravel" - ], - "support": { - "issues": "https://github.com/davidhsianturi/blade-bootstrap-icons/issues", - "source": "https://github.com/davidhsianturi/blade-bootstrap-icons/tree/v1.4.0" - }, - "time": "2023-03-17T14:49:47+00:00" - }, { "name": "dflydev/dot-access-data", "version": "v3.0.2", @@ -906,16 +460,16 @@ }, { "name": "doctrine/inflector", - "version": "2.0.8", + "version": "2.0.10", "source": { "type": "git", "url": "https://github.com/doctrine/inflector.git", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff" + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/inflector/zipball/f9301a5b2fb1216b2b08f02ba04dc45423db6bff", - "reference": "f9301a5b2fb1216b2b08f02ba04dc45423db6bff", + "url": "https://api.github.com/repos/doctrine/inflector/zipball/5817d0659c5b50c9b950feb9af7b9668e2c436bc", + "reference": "5817d0659c5b50c9b950feb9af7b9668e2c436bc", "shasum": "" }, "require": { @@ -977,7 +531,7 @@ ], "support": { "issues": "https://github.com/doctrine/inflector/issues", - "source": "https://github.com/doctrine/inflector/tree/2.0.8" + "source": "https://github.com/doctrine/inflector/tree/2.0.10" }, "funding": [ { @@ -993,31 +547,31 @@ "type": "tidelift" } ], - "time": "2023-06-16T13:40:37+00:00" + "time": "2024-02-18T20:23:39+00:00" }, { "name": "doctrine/lexer", - "version": "3.0.0", + "version": "3.0.1", "source": { "type": "git", "url": "https://github.com/doctrine/lexer.git", - "reference": "84a527db05647743d50373e0ec53a152f2cde568" + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/doctrine/lexer/zipball/84a527db05647743d50373e0ec53a152f2cde568", - "reference": "84a527db05647743d50373e0ec53a152f2cde568", + "url": "https://api.github.com/repos/doctrine/lexer/zipball/31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", + "reference": "31ad66abc0fc9e1a1f2d9bc6a42668d2fbbcd6dd", "shasum": "" }, "require": { "php": "^8.1" }, "require-dev": { - "doctrine/coding-standard": "^10", - "phpstan/phpstan": "^1.9", - "phpunit/phpunit": "^9.5", + "doctrine/coding-standard": "^12", + "phpstan/phpstan": "^1.10", + "phpunit/phpunit": "^10.5", "psalm/plugin-phpunit": "^0.18.3", - "vimeo/psalm": "^5.0" + "vimeo/psalm": "^5.21" }, "type": "library", "autoload": { @@ -1054,7 +608,7 @@ ], "support": { "issues": "https://github.com/doctrine/lexer/issues", - "source": "https://github.com/doctrine/lexer/tree/3.0.0" + "source": "https://github.com/doctrine/lexer/tree/3.0.1" }, "funding": [ { @@ -1070,7 +624,7 @@ "type": "tidelift" } ], - "time": "2022-12-15T16:57:16+00:00" + "time": "2024-02-05T11:56:58+00:00" }, { "name": "dragonmantank/cron-expression", @@ -1744,156 +1298,32 @@ ], "time": "2023-12-03T19:50:20+00:00" }, - { - "name": "khatabwedaa/blade-css-icons", - "version": "1.3.0", - "source": { - "type": "git", - "url": "https://github.com/khatabwedaa/blade-css-icons.git", - "reference": "a022e9a0057d9ce4f99728647fb139808c6134d8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/khatabwedaa/blade-css-icons/zipball/a022e9a0057d9ce4f99728647fb139808c6134d8", - "reference": "a022e9a0057d9ce4f99728647fb139808c6134d8", - "shasum": "" - }, - "require": { - "blade-ui-kit/blade-icons": "^1.1", - "illuminate/support": "^9.0|^10", - "php": "^8.0" - }, - "require-dev": { - "orchestra/testbench": "^7.0|^8.0", - "phpunit/phpunit": "^9.0" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Khatabwedaa\\BladeCssIcons\\BladeCssIconsServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Khatabwedaa\\BladeCssIcons\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Khatab Wedaa", - "email": "khatabwedaa@gmail.com", - "homepage": "https://twitter.com/khatabwedaa", - "role": "Developer" - } - ], - "description": "A package to easily make use of Css.gg in your Laravel Blade views.", - "homepage": "https://github.com/khatabwedaa/blade-css-icons", - "keywords": [ - "Css.gg", - "blade", - "laravel" - ], - "support": { - "issues": "https://github.com/khatabwedaa/blade-css-icons/issues", - "source": "https://github.com/khatabwedaa/blade-css-icons/tree/1.3.0" - }, - "time": "2023-02-04T14:04:11+00:00" - }, - { - "name": "laminas/laminas-code", - "version": "4.13.0", - "source": { - "type": "git", - "url": "https://github.com/laminas/laminas-code.git", - "reference": "7353d4099ad5388e84737dd16994316a04f48dbf" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laminas/laminas-code/zipball/7353d4099ad5388e84737dd16994316a04f48dbf", - "reference": "7353d4099ad5388e84737dd16994316a04f48dbf", - "shasum": "" - }, - "require": { - "php": "~8.1.0 || ~8.2.0 || ~8.3.0" - }, - "require-dev": { - "doctrine/annotations": "^2.0.1", - "ext-phar": "*", - "laminas/laminas-coding-standard": "^2.5.0", - "laminas/laminas-stdlib": "^3.17.0", - "phpunit/phpunit": "^10.3.3", - "psalm/plugin-phpunit": "^0.18.4", - "vimeo/psalm": "^5.15.0" - }, - "suggest": { - "doctrine/annotations": "Doctrine\\Common\\Annotations >=1.0 for annotation features", - "laminas/laminas-stdlib": "Laminas\\Stdlib component" - }, - "type": "library", - "autoload": { - "psr-4": { - "Laminas\\Code\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "description": "Extensions to the PHP Reflection API, static code scanning, and code generation", - "homepage": "https://laminas.dev", - "keywords": [ - "code", - "laminas", - "laminasframework" - ], - "support": { - "chat": "https://laminas.dev/chat", - "docs": "https://docs.laminas.dev/laminas-code/", - "forum": "https://discourse.laminas.dev", - "issues": "https://github.com/laminas/laminas-code/issues", - "rss": "https://github.com/laminas/laminas-code/releases.atom", - "source": "https://github.com/laminas/laminas-code" - }, - "funding": [ - { - "url": "https://funding.communitybridge.org/projects/laminas-project", - "type": "community_bridge" - } - ], - "time": "2023-10-18T10:00:55+00:00" - }, { "name": "laravel/fortify", - "version": "v1.19.1", + "version": "v1.20.1", "source": { "type": "git", "url": "https://github.com/laravel/fortify.git", - "reference": "1dde858a520f679b4a2f453fa68f8a0e98751875" + "reference": "ab1a76991a32be21448156419ddc7eb4731b0a8b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/fortify/zipball/1dde858a520f679b4a2f453fa68f8a0e98751875", - "reference": "1dde858a520f679b4a2f453fa68f8a0e98751875", + "url": "https://api.github.com/repos/laravel/fortify/zipball/ab1a76991a32be21448156419ddc7eb4731b0a8b", + "reference": "ab1a76991a32be21448156419ddc7eb4731b0a8b", "shasum": "" }, "require": { "bacon/bacon-qr-code": "^2.0", "ext-json": "*", - "illuminate/support": "^8.82|^9.0|^10.0", - "php": "^7.3|^8.0", - "pragmarx/google2fa": "^7.0|^8.0" + "illuminate/support": "^10.0|^11.0", + "php": "^8.1", + "pragmarx/google2fa": "^8.0" }, "require-dev": { "mockery/mockery": "^1.0", - "orchestra/testbench": "^6.34|^7.31|^8.11", + "orchestra/testbench": "^8.16|^9.0", "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.3" + "phpunit/phpunit": "^10.4" }, "type": "library", "extra": { @@ -1930,24 +1360,24 @@ "issues": "https://github.com/laravel/fortify/issues", "source": "https://github.com/laravel/fortify" }, - "time": "2023-12-11T16:16:45+00:00" + "time": "2024-02-08T14:36:46+00:00" }, { "name": "laravel/framework", - "version": "v10.39.0", + "version": "v10.47.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "114926b07bfb5fbf2545c03aa2ce5c8c37be650c" + "reference": "fce29b8de62733cdecbe12e3bae801f83fff2ea4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/114926b07bfb5fbf2545c03aa2ce5c8c37be650c", - "reference": "114926b07bfb5fbf2545c03aa2ce5c8c37be650c", + "url": "https://api.github.com/repos/laravel/framework/zipball/fce29b8de62733cdecbe12e3bae801f83fff2ea4", + "reference": "fce29b8de62733cdecbe12e3bae801f83fff2ea4", "shasum": "" }, "require": { - "brick/math": "^0.9.3|^0.10.2|^0.11", + "brick/math": "^0.9.3|^0.10.2|^0.11|^0.12", "composer-runtime-api": "^2.2", "doctrine/inflector": "^2.0.5", "dragonmantank/cron-expression": "^3.3.2", @@ -1991,6 +1421,7 @@ "conflict": { "carbonphp/carbon-doctrine-types": ">=3.0", "doctrine/dbal": ">=4.0", + "phpunit/phpunit": ">=11.0.0", "tightenco/collect": "<5.5.33" }, "provide": { @@ -2135,20 +1566,20 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2023-12-27T14:26:28+00:00" + "time": "2024-03-05T15:18:36+00:00" }, { "name": "laravel/prompts", - "version": "v0.1.14", + "version": "v0.1.16", "source": { "type": "git", "url": "https://github.com/laravel/prompts.git", - "reference": "2219fa9c4b944add1e825c3bdb8ecae8bc503bc6" + "reference": "ca6872ab6aec3ab61db3a61f83a6caf764ec7781" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/prompts/zipball/2219fa9c4b944add1e825c3bdb8ecae8bc503bc6", - "reference": "2219fa9c4b944add1e825c3bdb8ecae8bc503bc6", + "url": "https://api.github.com/repos/laravel/prompts/zipball/ca6872ab6aec3ab61db3a61f83a6caf764ec7781", + "reference": "ca6872ab6aec3ab61db3a61f83a6caf764ec7781", "shasum": "" }, "require": { @@ -2190,75 +1621,9 @@ ], "support": { "issues": "https://github.com/laravel/prompts/issues", - "source": "https://github.com/laravel/prompts/tree/v0.1.14" + "source": "https://github.com/laravel/prompts/tree/v0.1.16" }, - "time": "2023-12-27T04:18:09+00:00" - }, - { - "name": "laravel/sanctum", - "version": "v3.3.3", - "source": { - "type": "git", - "url": "https://github.com/laravel/sanctum.git", - "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/8c104366459739f3ada0e994bcd3e6fd681ce3d5", - "reference": "8c104366459739f3ada0e994bcd3e6fd681ce3d5", - "shasum": "" - }, - "require": { - "ext-json": "*", - "illuminate/console": "^9.21|^10.0", - "illuminate/contracts": "^9.21|^10.0", - "illuminate/database": "^9.21|^10.0", - "illuminate/support": "^9.21|^10.0", - "php": "^8.0.2" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "orchestra/testbench": "^7.28.2|^8.8.3", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^9.6" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Sanctum\\SanctumServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "Laravel\\Sanctum\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel Sanctum provides a featherweight authentication system for SPAs and simple APIs.", - "keywords": [ - "auth", - "laravel", - "sanctum" - ], - "support": { - "issues": "https://github.com/laravel/sanctum/issues", - "source": "https://github.com/laravel/sanctum" - }, - "time": "2023-12-19T18:44:48+00:00" + "time": "2024-02-21T19:25:27+00:00" }, { "name": "laravel/serializable-closure", @@ -2320,97 +1685,27 @@ }, "time": "2023-11-08T14:08:06+00:00" }, - { - "name": "laravel/socialite", - "version": "v5.11.0", - "source": { - "type": "git", - "url": "https://github.com/laravel/socialite.git", - "reference": "4f6a8af6f3f7c18da03d19842dd0514315501c10" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/laravel/socialite/zipball/4f6a8af6f3f7c18da03d19842dd0514315501c10", - "reference": "4f6a8af6f3f7c18da03d19842dd0514315501c10", - "shasum": "" - }, - "require": { - "ext-json": "*", - "guzzlehttp/guzzle": "^6.0|^7.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/http": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", - "league/oauth1-client": "^1.10.1", - "php": "^7.2|^8.0" - }, - "require-dev": { - "mockery/mockery": "^1.0", - "orchestra/testbench": "^4.0|^5.0|^6.0|^7.0|^8.0|^9.0", - "phpstan/phpstan": "^1.10", - "phpunit/phpunit": "^8.0|^9.3|^10.4" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "5.x-dev" - }, - "laravel": { - "providers": [ - "Laravel\\Socialite\\SocialiteServiceProvider" - ], - "aliases": { - "Socialite": "Laravel\\Socialite\\Facades\\Socialite" - } - } - }, - "autoload": { - "psr-4": { - "Laravel\\Socialite\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Taylor Otwell", - "email": "taylor@laravel.com" - } - ], - "description": "Laravel wrapper around OAuth 1 & OAuth 2 libraries.", - "homepage": "https://laravel.com", - "keywords": [ - "laravel", - "oauth" - ], - "support": { - "issues": "https://github.com/laravel/socialite/issues", - "source": "https://github.com/laravel/socialite" - }, - "time": "2023-12-02T18:22:36+00:00" - }, { "name": "laravel/tinker", - "version": "v2.8.2", + "version": "v2.9.0", "source": { "type": "git", "url": "https://github.com/laravel/tinker.git", - "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3" + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/tinker/zipball/b936d415b252b499e8c3b1f795cd4fc20f57e1f3", - "reference": "b936d415b252b499e8c3b1f795cd4fc20f57e1f3", + "url": "https://api.github.com/repos/laravel/tinker/zipball/502e0fe3f0415d06d5db1f83a472f0f3b754bafe", + "reference": "502e0fe3f0415d06d5db1f83a472f0f3b754bafe", "shasum": "" }, "require": { - "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0", - "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0", + "illuminate/console": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/contracts": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", + "illuminate/support": "^6.0|^7.0|^8.0|^9.0|^10.0|^11.0", "php": "^7.2.5|^8.0", - "psy/psysh": "^0.10.4|^0.11.1", - "symfony/var-dumper": "^4.3.4|^5.0|^6.0" + "psy/psysh": "^0.11.1|^0.12.0", + "symfony/var-dumper": "^4.3.4|^5.0|^6.0|^7.0" }, "require-dev": { "mockery/mockery": "~1.3.3|^1.4.2", @@ -2418,13 +1713,10 @@ "phpunit/phpunit": "^8.5.8|^9.3.3" }, "suggest": { - "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0)." + "illuminate/database": "The Illuminate Database package (^6.0|^7.0|^8.0|^9.0|^10.0|^11.0)." }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "2.x-dev" - }, "laravel": { "providers": [ "Laravel\\Tinker\\TinkerServiceProvider" @@ -2455,22 +1747,22 @@ ], "support": { "issues": "https://github.com/laravel/tinker/issues", - "source": "https://github.com/laravel/tinker/tree/v2.8.2" + "source": "https://github.com/laravel/tinker/tree/v2.9.0" }, - "time": "2023-08-15T14:27:00+00:00" + "time": "2024-01-04T16:10:04+00:00" }, { "name": "league/commonmark", - "version": "2.4.1", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/thephpleague/commonmark.git", - "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5" + "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/3669d6d5f7a47a93c08ddff335e6d945481a1dd5", - "reference": "3669d6d5f7a47a93c08ddff335e6d945481a1dd5", + "url": "https://api.github.com/repos/thephpleague/commonmark/zipball/91c24291965bd6d7c46c46a12ba7492f83b1cadf", + "reference": "91c24291965bd6d7c46c46a12ba7492f83b1cadf", "shasum": "" }, "require": { @@ -2483,7 +1775,7 @@ }, "require-dev": { "cebe/markdown": "^1.0", - "commonmark/cmark": "0.30.0", + "commonmark/cmark": "0.30.3", "commonmark/commonmark.js": "0.30.0", "composer/package-versions-deprecated": "^1.8", "embed/embed": "^4.4", @@ -2493,10 +1785,10 @@ "michelf/php-markdown": "^1.4 || ^2.0", "nyholm/psr7": "^1.5", "phpstan/phpstan": "^1.8.2", - "phpunit/phpunit": "^9.5.21", + "phpunit/phpunit": "^9.5.21 || ^10.5.9 || ^11.0.0", "scrutinizer/ocular": "^1.8.1", - "symfony/finder": "^5.3 | ^6.0", - "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0", + "symfony/finder": "^5.3 | ^6.0 || ^7.0", + "symfony/yaml": "^2.3 | ^3.0 | ^4.0 | ^5.0 | ^6.0 || ^7.0", "unleashedtech/php-coding-standard": "^3.1.1", "vimeo/psalm": "^4.24.0 || ^5.0.0" }, @@ -2563,7 +1855,7 @@ "type": "tidelift" } ], - "time": "2023-08-30T16:55:00+00:00" + "time": "2024-02-02T11:59:32+00:00" }, { "name": "league/config", @@ -2649,16 +1941,16 @@ }, { "name": "league/flysystem", - "version": "3.23.0", + "version": "3.25.0", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem.git", - "reference": "d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc" + "reference": "4c44347133618cccd9b3df1729647a1577b4ad99" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc", - "reference": "d4ad81e2b67396e33dc9d7e54ec74ccf73151dcc", + "url": "https://api.github.com/repos/thephpleague/flysystem/zipball/4c44347133618cccd9b3df1729647a1577b4ad99", + "reference": "4c44347133618cccd9b3df1729647a1577b4ad99", "shasum": "" }, "require": { @@ -2678,7 +1970,7 @@ "require-dev": { "async-aws/s3": "^1.5 || ^2.0", "async-aws/simple-s3": "^1.1 || ^2.0", - "aws/aws-sdk-php": "^3.220.0", + "aws/aws-sdk-php": "^3.295.10", "composer/semver": "^3.0", "ext-fileinfo": "*", "ext-ftp": "*", @@ -2686,10 +1978,10 @@ "friendsofphp/php-cs-fixer": "^3.5", "google/cloud-storage": "^1.23", "microsoft/azure-storage-blob": "^1.1", - "phpseclib/phpseclib": "^3.0.34", + "phpseclib/phpseclib": "^3.0.36", "phpstan/phpstan": "^1.10", "phpunit/phpunit": "^9.5.11|^10.0", - "sabre/dav": "^4.3.1" + "sabre/dav": "^4.6.0" }, "type": "library", "autoload": { @@ -2723,7 +2015,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem/issues", - "source": "https://github.com/thephpleague/flysystem/tree/3.23.0" + "source": "https://github.com/thephpleague/flysystem/tree/3.25.0" }, "funding": [ { @@ -2735,20 +2027,20 @@ "type": "github" } ], - "time": "2023-12-04T10:16:17+00:00" + "time": "2024-03-09T17:06:45+00:00" }, { "name": "league/flysystem-local", - "version": "3.23.0", + "version": "3.23.1", "source": { "type": "git", "url": "https://github.com/thephpleague/flysystem-local.git", - "reference": "5cf046ba5f059460e86a997c504dd781a39a109b" + "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/5cf046ba5f059460e86a997c504dd781a39a109b", - "reference": "5cf046ba5f059460e86a997c504dd781a39a109b", + "url": "https://api.github.com/repos/thephpleague/flysystem-local/zipball/b884d2bf9b53bb4804a56d2df4902bb51e253f00", + "reference": "b884d2bf9b53bb4804a56d2df4902bb51e253f00", "shasum": "" }, "require": { @@ -2783,7 +2075,7 @@ ], "support": { "issues": "https://github.com/thephpleague/flysystem-local/issues", - "source": "https://github.com/thephpleague/flysystem-local/tree/3.23.0" + "source": "https://github.com/thephpleague/flysystem-local/tree/3.23.1" }, "funding": [ { @@ -2795,20 +2087,20 @@ "type": "github" } ], - "time": "2023-12-04T10:14:46+00:00" + "time": "2024-01-26T18:25:23+00:00" }, { "name": "league/mime-type-detection", - "version": "1.14.0", + "version": "1.15.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "b6a5854368533df0295c5761a0253656a2e52d9e" + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/b6a5854368533df0295c5761a0253656a2e52d9e", - "reference": "b6a5854368533df0295c5761a0253656a2e52d9e", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", + "reference": "ce0f4d1e8a6f4eb0ddff33f57c69c50fd09f4301", "shasum": "" }, "require": { @@ -2839,7 +2131,7 @@ "description": "Mime-type detection for Flysystem", "support": { "issues": "https://github.com/thephpleague/mime-type-detection/issues", - "source": "https://github.com/thephpleague/mime-type-detection/tree/1.14.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.15.0" }, "funding": [ { @@ -2851,158 +2143,7 @@ "type": "tidelift" } ], - "time": "2023-10-17T14:13:20+00:00" - }, - { - "name": "league/oauth1-client", - "version": "v1.10.1", - "source": { - "type": "git", - "url": "https://github.com/thephpleague/oauth1-client.git", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thephpleague/oauth1-client/zipball/d6365b901b5c287dd41f143033315e2f777e1167", - "reference": "d6365b901b5c287dd41f143033315e2f777e1167", - "shasum": "" - }, - "require": { - "ext-json": "*", - "ext-openssl": "*", - "guzzlehttp/guzzle": "^6.0|^7.0", - "guzzlehttp/psr7": "^1.7|^2.0", - "php": ">=7.1||>=8.0" - }, - "require-dev": { - "ext-simplexml": "*", - "friendsofphp/php-cs-fixer": "^2.17", - "mockery/mockery": "^1.3.3", - "phpstan/phpstan": "^0.12.42", - "phpunit/phpunit": "^7.5||9.5" - }, - "suggest": { - "ext-simplexml": "For decoding XML-based responses." - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "1.0-dev", - "dev-develop": "2.0-dev" - } - }, - "autoload": { - "psr-4": { - "League\\OAuth1\\Client\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Ben Corlett", - "email": "bencorlett@me.com", - "homepage": "http://www.webcomm.com.au", - "role": "Developer" - } - ], - "description": "OAuth 1.0 Client Library", - "keywords": [ - "Authentication", - "SSO", - "authorization", - "bitbucket", - "identity", - "idp", - "oauth", - "oauth1", - "single sign on", - "trello", - "tumblr", - "twitter" - ], - "support": { - "issues": "https://github.com/thephpleague/oauth1-client/issues", - "source": "https://github.com/thephpleague/oauth1-client/tree/v1.10.1" - }, - "time": "2022-04-15T14:02:14+00:00" - }, - { - "name": "livewire/livewire", - "version": "v3.4.4", - "source": { - "type": "git", - "url": "https://github.com/livewire/livewire.git", - "reference": "c0489d4a76382f6dcf6e2702112f86aa089d0c8d" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/livewire/livewire/zipball/c0489d4a76382f6dcf6e2702112f86aa089d0c8d", - "reference": "c0489d4a76382f6dcf6e2702112f86aa089d0c8d", - "shasum": "" - }, - "require": { - "illuminate/database": "^10.0|^11.0", - "illuminate/routing": "^10.0|^11.0", - "illuminate/support": "^10.0|^11.0", - "illuminate/validation": "^10.0|^11.0", - "league/mime-type-detection": "^1.9", - "php": "^8.1", - "symfony/http-kernel": "^6.2|^7.0" - }, - "require-dev": { - "calebporzio/sushi": "^2.1", - "laravel/framework": "^10.0|^11.0", - "laravel/prompts": "^0.1.6", - "mockery/mockery": "^1.3.1", - "orchestra/testbench": "8.20.0|^9.0", - "orchestra/testbench-dusk": "8.20.0|^9.0", - "phpunit/phpunit": "^10.4", - "psy/psysh": "^0.11.22|^0.12" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Livewire\\LivewireServiceProvider" - ], - "aliases": { - "Livewire": "Livewire\\Livewire" - } - } - }, - "autoload": { - "files": [ - "src/helpers.php" - ], - "psr-4": { - "Livewire\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Caleb Porzio", - "email": "calebporzio@gmail.com" - } - ], - "description": "A front-end framework for Laravel.", - "support": { - "issues": "https://github.com/livewire/livewire/issues", - "source": "https://github.com/livewire/livewire/tree/v3.4.4" - }, - "funding": [ - { - "url": "https://github.com/livewire", - "type": "github" - } - ], - "time": "2024-01-28T19:07:11+00:00" + "time": "2024-01-28T23:22:08+00:00" }, { "name": "monolog/monolog", @@ -3173,16 +2314,16 @@ }, { "name": "nesbot/carbon", - "version": "2.72.1", + "version": "2.72.3", "source": { "type": "git", "url": "https://github.com/briannesbitt/Carbon.git", - "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78" + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", - "reference": "2b3b3db0a2d0556a177392ff1a3bf5608fa09f78", + "url": "https://api.github.com/repos/briannesbitt/Carbon/zipball/0c6fd108360c562f6e4fd1dedb8233b423e91c83", + "reference": "0c6fd108360c562f6e4fd1dedb8233b423e91c83", "shasum": "" }, "require": { @@ -3276,35 +2417,35 @@ "type": "tidelift" } ], - "time": "2023-12-08T23:47:49+00:00" + "time": "2024-01-25T10:35:09+00:00" }, { "name": "nette/schema", - "version": "v1.2.5", + "version": "v1.3.0", "source": { "type": "git", "url": "https://github.com/nette/schema.git", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a" + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/schema/zipball/0462f0166e823aad657c9224d0f849ecac1ba10a", - "reference": "0462f0166e823aad657c9224d0f849ecac1ba10a", + "url": "https://api.github.com/repos/nette/schema/zipball/a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", + "reference": "a6d3a6d1f545f01ef38e60f375d1cf1f4de98188", "shasum": "" }, "require": { - "nette/utils": "^2.5.7 || ^3.1.5 || ^4.0", - "php": "7.1 - 8.3" + "nette/utils": "^4.0", + "php": "8.1 - 8.3" }, "require-dev": { - "nette/tester": "^2.3 || ^2.4", + "nette/tester": "^2.4", "phpstan/phpstan-nette": "^1.0", - "tracy/tracy": "^2.7" + "tracy/tracy": "^2.8" }, "type": "library", "extra": { "branch-alias": { - "dev-master": "1.2-dev" + "dev-master": "1.3-dev" } }, "autoload": { @@ -3336,22 +2477,22 @@ ], "support": { "issues": "https://github.com/nette/schema/issues", - "source": "https://github.com/nette/schema/tree/v1.2.5" + "source": "https://github.com/nette/schema/tree/v1.3.0" }, - "time": "2023-10-05T20:37:59+00:00" + "time": "2023-12-11T11:54:22+00:00" }, { "name": "nette/utils", - "version": "v4.0.3", + "version": "v4.0.4", "source": { "type": "git", "url": "https://github.com/nette/utils.git", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015" + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nette/utils/zipball/a9d127dd6a203ce6d255b2e2db49759f7506e015", - "reference": "a9d127dd6a203ce6d255b2e2db49759f7506e015", + "url": "https://api.github.com/repos/nette/utils/zipball/d3ad0aa3b9f934602cb3e3902ebccf10be34d218", + "reference": "d3ad0aa3b9f934602cb3e3902ebccf10be34d218", "shasum": "" }, "require": { @@ -3422,31 +2563,33 @@ ], "support": { "issues": "https://github.com/nette/utils/issues", - "source": "https://github.com/nette/utils/tree/v4.0.3" + "source": "https://github.com/nette/utils/tree/v4.0.4" }, - "time": "2023-10-29T21:02:13+00:00" + "time": "2024-01-17T16:50:36+00:00" }, { "name": "nikic/php-parser", - "version": "v4.18.0", + "version": "v5.0.2", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999" + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/1bcbb2179f97633e98bbbc87044ee2611c7d7999", - "reference": "1bcbb2179f97633e98bbbc87044ee2611c7d7999", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/139676794dc1e9231bf7bcd123cfc0c99182cb13", + "reference": "139676794dc1e9231bf7bcd123cfc0c99182cb13", "shasum": "" }, "require": { + "ext-ctype": "*", + "ext-json": "*", "ext-tokenizer": "*", - "php": ">=7.0" + "php": ">=7.4" }, "require-dev": { "ircmaxell/php-yacc": "^0.0.7", - "phpunit/phpunit": "^6.5 || ^7.0 || ^8.0 || ^9.0" + "phpunit/phpunit": "^7.0 || ^8.0 || ^9.0" }, "bin": [ "bin/php-parse" @@ -3454,7 +2597,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-master": "4.9-dev" + "dev-master": "5.0-dev" } }, "autoload": { @@ -3478,9 +2621,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v4.18.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.0.2" }, - "time": "2023-12-10T21:03:43+00:00" + "time": "2024-03-05T20:51:40+00:00" }, { "name": "nunomaduro/termwind", @@ -3568,209 +2711,6 @@ ], "time": "2023-02-08T01:06:31+00:00" }, - { - "name": "opcodesio/log-viewer", - "version": "v3.4.0", - "source": { - "type": "git", - "url": "https://github.com/opcodesio/log-viewer.git", - "reference": "f1d89dc2e54e186f6852533a165fc49a6a83fff8" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opcodesio/log-viewer/zipball/f1d89dc2e54e186f6852533a165fc49a6a83fff8", - "reference": "f1d89dc2e54e186f6852533a165fc49a6a83fff8", - "shasum": "" - }, - "require": { - "illuminate/contracts": "^8.0|^9.0|^10.0|^11.0", - "opcodesio/mail-parser": "^0.1.6", - "php": "^8.0" - }, - "conflict": { - "arcanedev/log-viewer": "^8.0" - }, - "require-dev": { - "guzzlehttp/guzzle": "^7.2", - "itsgoingd/clockwork": "^5.1", - "laravel/pint": "^1.0", - "nunomaduro/collision": "^7.0|^8.0", - "orchestra/testbench": "^7.6|^8.0|^9.0", - "pestphp/pest": "^2.0", - "pestphp/pest-plugin-laravel": "^2.0", - "spatie/test-time": "^1.3" - }, - "suggest": { - "guzzlehttp/guzzle": "Required for multi-host support. ^7.2" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "Opcodes\\LogViewer\\LogViewerServiceProvider" - ], - "aliases": { - "LogViewer": "Opcodes\\LogViewer\\Facades\\LogViewer" - } - } - }, - "autoload": { - "psr-4": { - "Opcodes\\LogViewer\\": "src" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Arunas Skirius", - "email": "arukomp@gmail.com", - "role": "Developer" - } - ], - "description": "Fast and easy-to-use log viewer for your Laravel application", - "homepage": "https://github.com/opcodesio/log-viewer", - "keywords": [ - "arukompas", - "better-log-viewer", - "laravel", - "log viewer", - "logs", - "opcodesio" - ], - "support": { - "issues": "https://github.com/opcodesio/log-viewer/issues", - "source": "https://github.com/opcodesio/log-viewer/tree/v3.4.0" - }, - "funding": [ - { - "url": "https://www.buymeacoffee.com/arunas", - "type": "custom" - }, - { - "url": "https://github.com/arukompas", - "type": "github" - } - ], - "time": "2024-02-14T15:14:59+00:00" - }, - { - "name": "opcodesio/mail-parser", - "version": "v0.1.6", - "source": { - "type": "git", - "url": "https://github.com/opcodesio/mail-parser.git", - "reference": "639ef31cbd146a63416283e75afce152e13233ea" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/opcodesio/mail-parser/zipball/639ef31cbd146a63416283e75afce152e13233ea", - "reference": "639ef31cbd146a63416283e75afce152e13233ea", - "shasum": "" - }, - "require": { - "php": "^8.0" - }, - "require-dev": { - "pestphp/pest": "^2.16", - "symfony/var-dumper": "^6.3" - }, - "type": "library", - "autoload": { - "psr-4": { - "Opcodes\\MailParser\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Arunas Skirius", - "email": "arukomp@gmail.com", - "role": "Developer" - } - ], - "description": "Parse emails without the mailparse extension", - "keywords": [ - "arukompas", - "email", - "email parser", - "mail", - "opcodesio", - "php" - ], - "support": { - "issues": "https://github.com/opcodesio/mail-parser/issues", - "source": "https://github.com/opcodesio/mail-parser/tree/v0.1.6" - }, - "time": "2023-11-19T08:47:43+00:00" - }, - { - "name": "owenvoke/blade-fontawesome", - "version": "v2.5.1", - "source": { - "type": "git", - "url": "https://github.com/owenvoke/blade-fontawesome.git", - "reference": "b3eac80b0f2f1b70083d4acea0da49350f88856e" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/owenvoke/blade-fontawesome/zipball/b3eac80b0f2f1b70083d4acea0da49350f88856e", - "reference": "b3eac80b0f2f1b70083d4acea0da49350f88856e", - "shasum": "" - }, - "require": { - "blade-ui-kit/blade-icons": "^1.5", - "illuminate/support": "^10.34", - "php": "^8.1", - "thecodingmachine/safe": "^2.5" - }, - "require-dev": { - "laravel/pint": "^1.13", - "orchestra/testbench": "^8.12", - "pestphp/pest": "^2.26", - "phpstan/phpstan": "^1.10", - "symfony/var-dumper": "^6.3", - "thecodingmachine/phpstan-safe-rule": "^1.2" - }, - "type": "library", - "extra": { - "laravel": { - "providers": [ - "OwenVoke\\BladeFontAwesome\\BladeFontAwesomeServiceProvider" - ] - } - }, - "autoload": { - "psr-4": { - "OwenVoke\\BladeFontAwesome\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "A package to easily make use of Font Awesome in your Laravel Blade views", - "support": { - "issues": "https://github.com/owenvoke/blade-fontawesome/issues", - "source": "https://github.com/owenvoke/blade-fontawesome/tree/v2.5.1" - }, - "funding": [ - { - "url": "https://ecologi.com/owenvoke?gift-trees", - "type": "custom" - }, - { - "url": "https://github.com/owenvoke", - "type": "github" - } - ], - "time": "2023-12-12T09:07:03+00:00" - }, { "name": "paragonie/constant_time_encoding", "version": "v2.6.3", @@ -3965,16 +2905,16 @@ }, { "name": "phpseclib/phpseclib", - "version": "3.0.35", + "version": "3.0.37", "source": { "type": "git", "url": "https://github.com/phpseclib/phpseclib.git", - "reference": "4b1827beabce71953ca479485c0ae9c51287f2fe" + "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/4b1827beabce71953ca479485c0ae9c51287f2fe", - "reference": "4b1827beabce71953ca479485c0ae9c51287f2fe", + "url": "https://api.github.com/repos/phpseclib/phpseclib/zipball/cfa2013d0f68c062055180dd4328cc8b9d1f30b8", + "reference": "cfa2013d0f68c062055180dd4328cc8b9d1f30b8", "shasum": "" }, "require": { @@ -4055,7 +2995,7 @@ ], "support": { "issues": "https://github.com/phpseclib/phpseclib/issues", - "source": "https://github.com/phpseclib/phpseclib/tree/3.0.35" + "source": "https://github.com/phpseclib/phpseclib/tree/3.0.37" }, "funding": [ { @@ -4071,7 +3011,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T01:59:53+00:00" + "time": "2024-03-03T02:14:58+00:00" }, { "name": "pragmarx/google2fa", @@ -4539,25 +3479,25 @@ }, { "name": "psy/psysh", - "version": "v0.11.22", + "version": "v0.12.0", "source": { "type": "git", "url": "https://github.com/bobthecow/psysh.git", - "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b" + "reference": "750bf031a48fd07c673dbe3f11f72362ea306d0d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/bobthecow/psysh/zipball/128fa1b608be651999ed9789c95e6e2a31b5802b", - "reference": "128fa1b608be651999ed9789c95e6e2a31b5802b", + "url": "https://api.github.com/repos/bobthecow/psysh/zipball/750bf031a48fd07c673dbe3f11f72362ea306d0d", + "reference": "750bf031a48fd07c673dbe3f11f72362ea306d0d", "shasum": "" }, "require": { "ext-json": "*", "ext-tokenizer": "*", - "nikic/php-parser": "^4.0 || ^3.1", - "php": "^8.0 || ^7.0.8", - "symfony/console": "^6.0 || ^5.0 || ^4.0 || ^3.4", - "symfony/var-dumper": "^6.0 || ^5.0 || ^4.0 || ^3.4" + "nikic/php-parser": "^5.0 || ^4.0", + "php": "^8.0 || ^7.4", + "symfony/console": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4", + "symfony/var-dumper": "^7.0 || ^6.0 || ^5.0 || ^4.0 || ^3.4" }, "conflict": { "symfony/console": "4.4.37 || 5.3.14 || 5.3.15 || 5.4.3 || 5.4.4 || 6.0.3 || 6.0.4" @@ -4568,8 +3508,7 @@ "suggest": { "ext-pcntl": "Enabling the PCNTL extension makes PsySH a lot happier :)", "ext-pdo-sqlite": "The doc command requires SQLite to work.", - "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well.", - "ext-readline": "Enables support for arrow-key history navigation, and showing and manipulating command history." + "ext-posix": "If you have PCNTL, you'll want the POSIX extension as well." }, "bin": [ "bin/psysh" @@ -4577,7 +3516,7 @@ "type": "library", "extra": { "branch-alias": { - "dev-0.11": "0.11.x-dev" + "dev-main": "0.12.x-dev" }, "bamarni-bin": { "bin-links": false, @@ -4613,9 +3552,9 @@ ], "support": { "issues": "https://github.com/bobthecow/psysh/issues", - "source": "https://github.com/bobthecow/psysh/tree/v0.11.22" + "source": "https://github.com/bobthecow/psysh/tree/v0.12.0" }, - "time": "2023-10-14T21:56:36+00:00" + "time": "2023-12-20T15:28:09+00:00" }, { "name": "ralouphie/getallheaders", @@ -4844,16 +3783,16 @@ }, { "name": "symfony/console", - "version": "v6.4.2", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/console.git", - "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625" + "reference": "0d9e4eb5ad413075624378f474c4167ea202de78" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/console/zipball/0254811a143e6bc6c8deea08b589a7e68a37f625", - "reference": "0254811a143e6bc6c8deea08b589a7e68a37f625", + "url": "https://api.github.com/repos/symfony/console/zipball/0d9e4eb5ad413075624378f474c4167ea202de78", + "reference": "0d9e4eb5ad413075624378f474c4167ea202de78", "shasum": "" }, "require": { @@ -4918,7 +3857,7 @@ "terminal" ], "support": { - "source": "https://github.com/symfony/console/tree/v6.4.2" + "source": "https://github.com/symfony/console/tree/v6.4.4" }, "funding": [ { @@ -4934,24 +3873,24 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:15:48+00:00" + "time": "2024-02-22T20:27:10+00:00" }, { "name": "symfony/css-selector", - "version": "v6.4.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/css-selector.git", - "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4" + "reference": "ec60a4edf94e63b0556b6a0888548bb400a3a3be" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/css-selector/zipball/d036c6c0d0b09e24a14a35f8292146a658f986e4", - "reference": "d036c6c0d0b09e24a14a35f8292146a658f986e4", + "url": "https://api.github.com/repos/symfony/css-selector/zipball/ec60a4edf94e63b0556b6a0888548bb400a3a3be", + "reference": "ec60a4edf94e63b0556b6a0888548bb400a3a3be", "shasum": "" }, "require": { - "php": ">=8.1" + "php": ">=8.2" }, "type": "library", "autoload": { @@ -4983,7 +3922,7 @@ "description": "Converts CSS selectors to XPath expressions", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/css-selector/tree/v6.4.0" + "source": "https://github.com/symfony/css-selector/tree/v7.0.3" }, "funding": [ { @@ -4999,7 +3938,7 @@ "type": "tidelift" } ], - "time": "2023-10-31T08:40:20+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/deprecation-contracts", @@ -5070,16 +4009,16 @@ }, { "name": "symfony/error-handler", - "version": "v6.4.0", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/error-handler.git", - "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788" + "reference": "c725219bdf2afc59423c32793d5019d2a904e13a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/error-handler/zipball/c873490a1c97b3a0a4838afc36ff36c112d02788", - "reference": "c873490a1c97b3a0a4838afc36ff36c112d02788", + "url": "https://api.github.com/repos/symfony/error-handler/zipball/c725219bdf2afc59423c32793d5019d2a904e13a", + "reference": "c725219bdf2afc59423c32793d5019d2a904e13a", "shasum": "" }, "require": { @@ -5125,7 +4064,7 @@ "description": "Provides tools to manage errors and ease debugging PHP code", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/error-handler/tree/v6.4.0" + "source": "https://github.com/symfony/error-handler/tree/v6.4.4" }, "funding": [ { @@ -5141,28 +4080,28 @@ "type": "tidelift" } ], - "time": "2023-10-18T09:43:34+00:00" + "time": "2024-02-22T20:27:10+00:00" }, { "name": "symfony/event-dispatcher", - "version": "v6.4.2", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/event-dispatcher.git", - "reference": "e95216850555cd55e71b857eb9d6c2674124603a" + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/e95216850555cd55e71b857eb9d6c2674124603a", - "reference": "e95216850555cd55e71b857eb9d6c2674124603a", + "url": "https://api.github.com/repos/symfony/event-dispatcher/zipball/834c28d533dd0636f910909d01b9ff45cc094b5e", + "reference": "834c28d533dd0636f910909d01b9ff45cc094b5e", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/event-dispatcher-contracts": "^2.5|^3" }, "conflict": { - "symfony/dependency-injection": "<5.4", + "symfony/dependency-injection": "<6.4", "symfony/service-contracts": "<2.5" }, "provide": { @@ -5171,13 +4110,13 @@ }, "require-dev": { "psr/log": "^1|^2|^3", - "symfony/config": "^5.4|^6.0|^7.0", - "symfony/dependency-injection": "^5.4|^6.0|^7.0", - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/expression-language": "^5.4|^6.0|^7.0", - "symfony/http-foundation": "^5.4|^6.0|^7.0", + "symfony/config": "^6.4|^7.0", + "symfony/dependency-injection": "^6.4|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/expression-language": "^6.4|^7.0", + "symfony/http-foundation": "^6.4|^7.0", "symfony/service-contracts": "^2.5|^3", - "symfony/stopwatch": "^5.4|^6.0|^7.0" + "symfony/stopwatch": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -5205,7 +4144,7 @@ "description": "Provides tools that allow your application components to communicate with each other by dispatching events and listening to them", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/event-dispatcher/tree/v6.4.2" + "source": "https://github.com/symfony/event-dispatcher/tree/v7.0.3" }, "funding": [ { @@ -5221,7 +4160,7 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:16:42+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "symfony/event-dispatcher-contracts", @@ -5365,16 +4304,16 @@ }, { "name": "symfony/http-foundation", - "version": "v6.4.2", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/http-foundation.git", - "reference": "172d807f9ef3fc3fbed8377cc57c20d389269271" + "reference": "ebc713bc6e6f4b53f46539fc158be85dfcd77304" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-foundation/zipball/172d807f9ef3fc3fbed8377cc57c20d389269271", - "reference": "172d807f9ef3fc3fbed8377cc57c20d389269271", + "url": "https://api.github.com/repos/symfony/http-foundation/zipball/ebc713bc6e6f4b53f46539fc158be85dfcd77304", + "reference": "ebc713bc6e6f4b53f46539fc158be85dfcd77304", "shasum": "" }, "require": { @@ -5422,7 +4361,7 @@ "description": "Defines an object-oriented layer for the HTTP specification", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-foundation/tree/v6.4.2" + "source": "https://github.com/symfony/http-foundation/tree/v6.4.4" }, "funding": [ { @@ -5438,20 +4377,20 @@ "type": "tidelift" } ], - "time": "2023-12-27T22:16:42+00:00" + "time": "2024-02-08T15:01:18+00:00" }, { "name": "symfony/http-kernel", - "version": "v6.4.2", + "version": "v6.4.5", "source": { "type": "git", "url": "https://github.com/symfony/http-kernel.git", - "reference": "13e8387320b5942d0dc408440c888e2d526efef4" + "reference": "f6947cb939d8efee137797382cb4db1af653ef75" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-kernel/zipball/13e8387320b5942d0dc408440c888e2d526efef4", - "reference": "13e8387320b5942d0dc408440c888e2d526efef4", + "url": "https://api.github.com/repos/symfony/http-kernel/zipball/f6947cb939d8efee137797382cb4db1af653ef75", + "reference": "f6947cb939d8efee137797382cb4db1af653ef75", "shasum": "" }, "require": { @@ -5500,7 +4439,7 @@ "symfony/process": "^5.4|^6.0|^7.0", "symfony/property-access": "^5.4.5|^6.0.5|^7.0", "symfony/routing": "^5.4|^6.0|^7.0", - "symfony/serializer": "^6.3|^7.0", + "symfony/serializer": "^6.4.4|^7.0.4", "symfony/stopwatch": "^5.4|^6.0|^7.0", "symfony/translation": "^5.4|^6.0|^7.0", "symfony/translation-contracts": "^2.5|^3", @@ -5535,7 +4474,7 @@ "description": "Provides a structured process for converting a Request into a Response", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/http-kernel/tree/v6.4.2" + "source": "https://github.com/symfony/http-kernel/tree/v6.4.5" }, "funding": [ { @@ -5551,20 +4490,20 @@ "type": "tidelift" } ], - "time": "2023-12-30T15:31:44+00:00" + "time": "2024-03-04T21:00:47+00:00" }, { "name": "symfony/mailer", - "version": "v6.4.2", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/mailer.git", - "reference": "6da89e5c9202f129717a770a03183fb140720168" + "reference": "791c5d31a8204cf3db0c66faab70282307f4376b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mailer/zipball/6da89e5c9202f129717a770a03183fb140720168", - "reference": "6da89e5c9202f129717a770a03183fb140720168", + "url": "https://api.github.com/repos/symfony/mailer/zipball/791c5d31a8204cf3db0c66faab70282307f4376b", + "reference": "791c5d31a8204cf3db0c66faab70282307f4376b", "shasum": "" }, "require": { @@ -5615,7 +4554,7 @@ "description": "Helps sending emails", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/mailer/tree/v6.4.2" + "source": "https://github.com/symfony/mailer/tree/v6.4.4" }, "funding": [ { @@ -5631,20 +4570,20 @@ "type": "tidelift" } ], - "time": "2023-12-19T09:12:31+00:00" + "time": "2024-02-03T21:33:47+00:00" }, { "name": "symfony/mime", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/mime.git", - "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205" + "reference": "5017e0a9398c77090b7694be46f20eb796262a34" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/mime/zipball/ca4f58b2ef4baa8f6cecbeca2573f88cd577d205", - "reference": "ca4f58b2ef4baa8f6cecbeca2573f88cd577d205", + "url": "https://api.github.com/repos/symfony/mime/zipball/5017e0a9398c77090b7694be46f20eb796262a34", + "reference": "5017e0a9398c77090b7694be46f20eb796262a34", "shasum": "" }, "require": { @@ -5699,7 +4638,7 @@ "mime-type" ], "support": { - "source": "https://github.com/symfony/mime/tree/v6.4.0" + "source": "https://github.com/symfony/mime/tree/v6.4.3" }, "funding": [ { @@ -5715,20 +4654,20 @@ "type": "tidelift" } ], - "time": "2023-10-17T11:49:05+00:00" + "time": "2024-01-30T08:32:12+00:00" }, { "name": "symfony/polyfill-ctype", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-ctype.git", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb" + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", - "reference": "ea208ce43cbb04af6867b4fdddb1bdbf84cc28cb", + "url": "https://api.github.com/repos/symfony/polyfill-ctype/zipball/ef4d7e442ca910c4764bce785146269b30cb5fc4", + "reference": "ef4d7e442ca910c4764bce785146269b30cb5fc4", "shasum": "" }, "require": { @@ -5742,9 +4681,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5781,7 +4717,7 @@ "portable" ], "support": { - "source": "https://github.com/symfony/polyfill-ctype/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-ctype/tree/v1.29.0" }, "funding": [ { @@ -5797,20 +4733,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-grapheme", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-grapheme.git", - "reference": "875e90aeea2777b6f135677f618529449334a612" + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/875e90aeea2777b6f135677f618529449334a612", - "reference": "875e90aeea2777b6f135677f618529449334a612", + "url": "https://api.github.com/repos/symfony/polyfill-intl-grapheme/zipball/32a9da87d7b3245e09ac426c83d334ae9f06f80f", + "reference": "32a9da87d7b3245e09ac426c83d334ae9f06f80f", "shasum": "" }, "require": { @@ -5821,9 +4757,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5862,7 +4795,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-grapheme/tree/v1.29.0" }, "funding": [ { @@ -5878,20 +4811,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-idn", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-idn.git", - "reference": "ecaafce9f77234a6a449d29e49267ba10499116d" + "reference": "a287ed7475f85bf6f61890146edbc932c0fff919" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/ecaafce9f77234a6a449d29e49267ba10499116d", - "reference": "ecaafce9f77234a6a449d29e49267ba10499116d", + "url": "https://api.github.com/repos/symfony/polyfill-intl-idn/zipball/a287ed7475f85bf6f61890146edbc932c0fff919", + "reference": "a287ed7475f85bf6f61890146edbc932c0fff919", "shasum": "" }, "require": { @@ -5904,9 +4837,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -5949,7 +4879,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-idn/tree/v1.29.0" }, "funding": [ { @@ -5965,20 +4895,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:30:37+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-intl-normalizer", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-intl-normalizer.git", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92" + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", - "reference": "8c4ad05dd0120b6a53c1ca374dca2ad0a1c4ed92", + "url": "https://api.github.com/repos/symfony/polyfill-intl-normalizer/zipball/bc45c394692b948b4d383a08d7753968bed9a83d", + "reference": "bc45c394692b948b4d383a08d7753968bed9a83d", "shasum": "" }, "require": { @@ -5989,9 +4919,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6033,7 +4960,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-intl-normalizer/tree/v1.29.0" }, "funding": [ { @@ -6049,20 +4976,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-mbstring", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-mbstring.git", - "reference": "42292d99c55abe617799667f454222c54c60e229" + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/42292d99c55abe617799667f454222c54c60e229", - "reference": "42292d99c55abe617799667f454222c54c60e229", + "url": "https://api.github.com/repos/symfony/polyfill-mbstring/zipball/9773676c8a1bb1f8d4340a62efe641cf76eda7ec", + "reference": "9773676c8a1bb1f8d4340a62efe641cf76eda7ec", "shasum": "" }, "require": { @@ -6076,9 +5003,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6116,7 +5040,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-mbstring/tree/v1.29.0" }, "funding": [ { @@ -6132,20 +5056,20 @@ "type": "tidelift" } ], - "time": "2023-07-28T09:04:16+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php72", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php72.git", - "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179" + "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/70f4aebd92afca2f865444d30a4d2151c13c3179", - "reference": "70f4aebd92afca2f865444d30a4d2151c13c3179", + "url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/861391a8da9a04cbad2d232ddd9e4893220d6e25", + "reference": "861391a8da9a04cbad2d232ddd9e4893220d6e25", "shasum": "" }, "require": { @@ -6153,9 +5077,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6192,7 +5113,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php72/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php72/tree/v1.29.0" }, "funding": [ { @@ -6208,20 +5129,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php80", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php80.git", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5" + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/6caa57379c4aec19c0a12a38b59b26487dcfe4b5", - "reference": "6caa57379c4aec19c0a12a38b59b26487dcfe4b5", + "url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", + "reference": "87b68208d5c1188808dd7839ee1e6c8ec3b02f1b", "shasum": "" }, "require": { @@ -6229,9 +5150,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6275,7 +5193,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php80/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php80/tree/v1.29.0" }, "funding": [ { @@ -6291,20 +5209,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-php83", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-php83.git", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11" + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", - "reference": "b0f46ebbeeeda3e9d2faebdfbf4b4eae9b59fa11", + "url": "https://api.github.com/repos/symfony/polyfill-php83/zipball/86fcae159633351e5fd145d1c47de6c528f8caff", + "reference": "86fcae159633351e5fd145d1c47de6c528f8caff", "shasum": "" }, "require": { @@ -6313,9 +5231,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6355,7 +5270,7 @@ "shim" ], "support": { - "source": "https://github.com/symfony/polyfill-php83/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-php83/tree/v1.29.0" }, "funding": [ { @@ -6371,20 +5286,20 @@ "type": "tidelift" } ], - "time": "2023-08-16T06:22:46+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/polyfill-uuid", - "version": "v1.28.0", + "version": "v1.29.0", "source": { "type": "git", "url": "https://github.com/symfony/polyfill-uuid.git", - "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e" + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/9c44518a5aff8da565c8a55dbe85d2769e6f630e", - "reference": "9c44518a5aff8da565c8a55dbe85d2769e6f630e", + "url": "https://api.github.com/repos/symfony/polyfill-uuid/zipball/3abdd21b0ceaa3000ee950097bc3cf9efc137853", + "reference": "3abdd21b0ceaa3000ee950097bc3cf9efc137853", "shasum": "" }, "require": { @@ -6398,9 +5313,6 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-main": "1.28-dev" - }, "thanks": { "name": "symfony/polyfill", "url": "https://github.com/symfony/polyfill" @@ -6437,7 +5349,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/polyfill-uuid/tree/v1.28.0" + "source": "https://github.com/symfony/polyfill-uuid/tree/v1.29.0" }, "funding": [ { @@ -6453,20 +5365,20 @@ "type": "tidelift" } ], - "time": "2023-01-26T09:26:14+00:00" + "time": "2024-01-29T20:11:03+00:00" }, { "name": "symfony/process", - "version": "v6.4.2", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/process.git", - "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241" + "reference": "710e27879e9be3395de2b98da3f52a946039f297" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/process/zipball/c4b1ef0bc80533d87a2e969806172f1c2a980241", - "reference": "c4b1ef0bc80533d87a2e969806172f1c2a980241", + "url": "https://api.github.com/repos/symfony/process/zipball/710e27879e9be3395de2b98da3f52a946039f297", + "reference": "710e27879e9be3395de2b98da3f52a946039f297", "shasum": "" }, "require": { @@ -6498,7 +5410,7 @@ "description": "Executes commands in sub-processes", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/process/tree/v6.4.2" + "source": "https://github.com/symfony/process/tree/v6.4.4" }, "funding": [ { @@ -6514,20 +5426,20 @@ "type": "tidelift" } ], - "time": "2023-12-22T16:42:54+00:00" + "time": "2024-02-20T12:31:00+00:00" }, { "name": "symfony/routing", - "version": "v6.4.2", + "version": "v6.4.5", "source": { "type": "git", "url": "https://github.com/symfony/routing.git", - "reference": "98eab13a07fddc85766f1756129c69f207ffbc21" + "reference": "7fe30068e207d9c31c0138501ab40358eb2d49a4" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/routing/zipball/98eab13a07fddc85766f1756129c69f207ffbc21", - "reference": "98eab13a07fddc85766f1756129c69f207ffbc21", + "url": "https://api.github.com/repos/symfony/routing/zipball/7fe30068e207d9c31c0138501ab40358eb2d49a4", + "reference": "7fe30068e207d9c31c0138501ab40358eb2d49a4", "shasum": "" }, "require": { @@ -6581,7 +5493,7 @@ "url" ], "support": { - "source": "https://github.com/symfony/routing/tree/v6.4.2" + "source": "https://github.com/symfony/routing/tree/v6.4.5" }, "funding": [ { @@ -6597,7 +5509,7 @@ "type": "tidelift" } ], - "time": "2023-12-29T15:34:34+00:00" + "time": "2024-02-27T12:33:30+00:00" }, { "name": "symfony/service-contracts", @@ -6683,20 +5595,20 @@ }, { "name": "symfony/string", - "version": "v6.4.2", + "version": "v7.0.4", "source": { "type": "git", "url": "https://github.com/symfony/string.git", - "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc" + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/string/zipball/7cb80bc10bfcdf6b5492741c0b9357dac66940bc", - "reference": "7cb80bc10bfcdf6b5492741c0b9357dac66940bc", + "url": "https://api.github.com/repos/symfony/string/zipball/f5832521b998b0bec40bee688ad5de98d4cf111b", + "reference": "f5832521b998b0bec40bee688ad5de98d4cf111b", "shasum": "" }, "require": { - "php": ">=8.1", + "php": ">=8.2", "symfony/polyfill-ctype": "~1.8", "symfony/polyfill-intl-grapheme": "~1.0", "symfony/polyfill-intl-normalizer": "~1.0", @@ -6706,11 +5618,11 @@ "symfony/translation-contracts": "<2.5" }, "require-dev": { - "symfony/error-handler": "^5.4|^6.0|^7.0", - "symfony/http-client": "^5.4|^6.0|^7.0", - "symfony/intl": "^6.2|^7.0", + "symfony/error-handler": "^6.4|^7.0", + "symfony/http-client": "^6.4|^7.0", + "symfony/intl": "^6.4|^7.0", "symfony/translation-contracts": "^2.5|^3.0", - "symfony/var-exporter": "^5.4|^6.0|^7.0" + "symfony/var-exporter": "^6.4|^7.0" }, "type": "library", "autoload": { @@ -6749,7 +5661,7 @@ "utf8" ], "support": { - "source": "https://github.com/symfony/string/tree/v6.4.2" + "source": "https://github.com/symfony/string/tree/v7.0.4" }, "funding": [ { @@ -6765,20 +5677,20 @@ "type": "tidelift" } ], - "time": "2023-12-10T16:15:48+00:00" + "time": "2024-02-01T13:17:36+00:00" }, { "name": "symfony/translation", - "version": "v6.4.2", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/translation.git", - "reference": "a2ab2ec1a462e53016de8e8d5e8912bfd62ea681" + "reference": "bce6a5a78e94566641b2594d17e48b0da3184a8e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/translation/zipball/a2ab2ec1a462e53016de8e8d5e8912bfd62ea681", - "reference": "a2ab2ec1a462e53016de8e8d5e8912bfd62ea681", + "url": "https://api.github.com/repos/symfony/translation/zipball/bce6a5a78e94566641b2594d17e48b0da3184a8e", + "reference": "bce6a5a78e94566641b2594d17e48b0da3184a8e", "shasum": "" }, "require": { @@ -6801,7 +5713,7 @@ "symfony/translation-implementation": "2.3|3.0" }, "require-dev": { - "nikic/php-parser": "^4.13", + "nikic/php-parser": "^4.18|^5.0", "psr/log": "^1|^2|^3", "symfony/config": "^5.4|^6.0|^7.0", "symfony/console": "^5.4|^6.0|^7.0", @@ -6844,7 +5756,7 @@ "description": "Provides tools to internationalize your application", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/translation/tree/v6.4.2" + "source": "https://github.com/symfony/translation/tree/v6.4.4" }, "funding": [ { @@ -6860,7 +5772,7 @@ "type": "tidelift" } ], - "time": "2023-12-18T09:25:29+00:00" + "time": "2024-02-20T13:16:58+00:00" }, { "name": "symfony/translation-contracts", @@ -6942,16 +5854,16 @@ }, { "name": "symfony/uid", - "version": "v6.4.0", + "version": "v6.4.3", "source": { "type": "git", "url": "https://github.com/symfony/uid.git", - "reference": "8092dd1b1a41372110d06374f99ee62f7f0b9a92" + "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/uid/zipball/8092dd1b1a41372110d06374f99ee62f7f0b9a92", - "reference": "8092dd1b1a41372110d06374f99ee62f7f0b9a92", + "url": "https://api.github.com/repos/symfony/uid/zipball/1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", + "reference": "1d31267211cc3a2fff32bcfc7c1818dac41b6fc0", "shasum": "" }, "require": { @@ -6996,7 +5908,7 @@ "uuid" ], "support": { - "source": "https://github.com/symfony/uid/tree/v6.4.0" + "source": "https://github.com/symfony/uid/tree/v6.4.3" }, "funding": [ { @@ -7012,20 +5924,20 @@ "type": "tidelift" } ], - "time": "2023-10-31T08:18:17+00:00" + "time": "2024-01-23T14:51:35+00:00" }, { "name": "symfony/var-dumper", - "version": "v6.4.2", + "version": "v6.4.4", "source": { "type": "git", "url": "https://github.com/symfony/var-dumper.git", - "reference": "68d6573ec98715ddcae5a0a85bee3c1c27a4c33f" + "reference": "b439823f04c98b84d4366c79507e9da6230944b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/var-dumper/zipball/68d6573ec98715ddcae5a0a85bee3c1c27a4c33f", - "reference": "68d6573ec98715ddcae5a0a85bee3c1c27a4c33f", + "url": "https://api.github.com/repos/symfony/var-dumper/zipball/b439823f04c98b84d4366c79507e9da6230944b1", + "reference": "b439823f04c98b84d4366c79507e9da6230944b1", "shasum": "" }, "require": { @@ -7081,7 +5993,7 @@ "dump" ], "support": { - "source": "https://github.com/symfony/var-dumper/tree/v6.4.2" + "source": "https://github.com/symfony/var-dumper/tree/v6.4.4" }, "funding": [ { @@ -7097,146 +6009,7 @@ "type": "tidelift" } ], - "time": "2023-12-28T19:16:56+00:00" - }, - { - "name": "thecodingmachine/safe", - "version": "v2.5.0", - "source": { - "type": "git", - "url": "https://github.com/thecodingmachine/safe.git", - "reference": "3115ecd6b4391662b4931daac4eba6b07a2ac1f0" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/thecodingmachine/safe/zipball/3115ecd6b4391662b4931daac4eba6b07a2ac1f0", - "reference": "3115ecd6b4391662b4931daac4eba6b07a2ac1f0", - "shasum": "" - }, - "require": { - "php": "^8.0" - }, - "require-dev": { - "phpstan/phpstan": "^1.5", - "phpunit/phpunit": "^9.5", - "squizlabs/php_codesniffer": "^3.2", - "thecodingmachine/phpstan-strict-rules": "^1.0" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "2.2.x-dev" - } - }, - "autoload": { - "files": [ - "deprecated/apc.php", - "deprecated/array.php", - "deprecated/datetime.php", - "deprecated/libevent.php", - "deprecated/misc.php", - "deprecated/password.php", - "deprecated/mssql.php", - "deprecated/stats.php", - "deprecated/strings.php", - "lib/special_cases.php", - "deprecated/mysqli.php", - "generated/apache.php", - "generated/apcu.php", - "generated/array.php", - "generated/bzip2.php", - "generated/calendar.php", - "generated/classobj.php", - "generated/com.php", - "generated/cubrid.php", - "generated/curl.php", - "generated/datetime.php", - "generated/dir.php", - "generated/eio.php", - "generated/errorfunc.php", - "generated/exec.php", - "generated/fileinfo.php", - "generated/filesystem.php", - "generated/filter.php", - "generated/fpm.php", - "generated/ftp.php", - "generated/funchand.php", - "generated/gettext.php", - "generated/gmp.php", - "generated/gnupg.php", - "generated/hash.php", - "generated/ibase.php", - "generated/ibmDb2.php", - "generated/iconv.php", - "generated/image.php", - "generated/imap.php", - "generated/info.php", - "generated/inotify.php", - "generated/json.php", - "generated/ldap.php", - "generated/libxml.php", - "generated/lzf.php", - "generated/mailparse.php", - "generated/mbstring.php", - "generated/misc.php", - "generated/mysql.php", - "generated/network.php", - "generated/oci8.php", - "generated/opcache.php", - "generated/openssl.php", - "generated/outcontrol.php", - "generated/pcntl.php", - "generated/pcre.php", - "generated/pgsql.php", - "generated/posix.php", - "generated/ps.php", - "generated/pspell.php", - "generated/readline.php", - "generated/rpminfo.php", - "generated/rrd.php", - "generated/sem.php", - "generated/session.php", - "generated/shmop.php", - "generated/sockets.php", - "generated/sodium.php", - "generated/solr.php", - "generated/spl.php", - "generated/sqlsrv.php", - "generated/ssdeep.php", - "generated/ssh2.php", - "generated/stream.php", - "generated/strings.php", - "generated/swoole.php", - "generated/uodbc.php", - "generated/uopz.php", - "generated/url.php", - "generated/var.php", - "generated/xdiff.php", - "generated/xml.php", - "generated/xmlrpc.php", - "generated/yaml.php", - "generated/yaz.php", - "generated/zip.php", - "generated/zlib.php" - ], - "classmap": [ - "lib/DateTime.php", - "lib/DateTimeImmutable.php", - "lib/Exceptions/", - "deprecated/Exceptions/", - "generated/Exceptions/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "description": "PHP core functions that throw exceptions instead of returning FALSE on error", - "support": { - "issues": "https://github.com/thecodingmachine/safe/issues", - "source": "https://github.com/thecodingmachine/safe/tree/v2.5.0" - }, - "time": "2023-04-05T11:54:14+00:00" + "time": "2024-02-15T11:23:52+00:00" }, { "name": "tijsverkoyen/css-to-inline-styles", @@ -7511,16 +6284,16 @@ "packages-dev": [ { "name": "fakerphp/faker", - "version": "v1.23.0", + "version": "v1.23.1", "source": { "type": "git", "url": "https://github.com/FakerPHP/Faker.git", - "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01" + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", - "reference": "e3daa170d00fde61ea7719ef47bb09bb8f1d9b01", + "url": "https://api.github.com/repos/FakerPHP/Faker/zipball/bfb4fe148adbf78eff521199619b93a52ae3554b", + "reference": "bfb4fe148adbf78eff521199619b93a52ae3554b", "shasum": "" }, "require": { @@ -7546,11 +6319,6 @@ "ext-mbstring": "Required for multibyte Unicode string functionality." }, "type": "library", - "extra": { - "branch-alias": { - "dev-main": "v1.21-dev" - } - }, "autoload": { "psr-4": { "Faker\\": "src/Faker/" @@ -7573,9 +6341,9 @@ ], "support": { "issues": "https://github.com/FakerPHP/Faker/issues", - "source": "https://github.com/FakerPHP/Faker/tree/v1.23.0" + "source": "https://github.com/FakerPHP/Faker/tree/v1.23.1" }, - "time": "2023-06-12T08:44:38+00:00" + "time": "2024-01-02T13:46:09+00:00" }, { "name": "filp/whoops", @@ -7701,16 +6469,16 @@ }, { "name": "laravel/pint", - "version": "v1.13.7", + "version": "v1.14.0", "source": { "type": "git", "url": "https://github.com/laravel/pint.git", - "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece" + "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/pint/zipball/4157768980dbd977f1c4b4cc94997416d8b30ece", - "reference": "4157768980dbd977f1c4b4cc94997416d8b30ece", + "url": "https://api.github.com/repos/laravel/pint/zipball/6b127276e3f263f7bb17d5077e9e0269e61b2a0e", + "reference": "6b127276e3f263f7bb17d5077e9e0269e61b2a0e", "shasum": "" }, "require": { @@ -7721,13 +6489,13 @@ "php": "^8.1.0" }, "require-dev": { - "friendsofphp/php-cs-fixer": "^3.38.0", - "illuminate/view": "^10.30.1", + "friendsofphp/php-cs-fixer": "^3.49.0", + "illuminate/view": "^10.43.0", + "larastan/larastan": "^2.8.1", "laravel-zero/framework": "^10.3.0", - "mockery/mockery": "^1.6.6", - "nunomaduro/larastan": "^2.6.4", + "mockery/mockery": "^1.6.7", "nunomaduro/termwind": "^1.15.1", - "pestphp/pest": "^2.24.2" + "pestphp/pest": "^2.33.6" }, "bin": [ "builds/pint" @@ -7763,26 +6531,26 @@ "issues": "https://github.com/laravel/pint/issues", "source": "https://github.com/laravel/pint" }, - "time": "2023-12-05T19:43:12+00:00" + "time": "2024-02-20T17:38:05+00:00" }, { "name": "laravel/sail", - "version": "v1.26.3", + "version": "v1.28.2", "source": { "type": "git", "url": "https://github.com/laravel/sail.git", - "reference": "fa1ad5fbb03686dfc752bfd1861d86091cc1c32d" + "reference": "057777403b8ab79222dcc04983beaab10b6de6a0" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sail/zipball/fa1ad5fbb03686dfc752bfd1861d86091cc1c32d", - "reference": "fa1ad5fbb03686dfc752bfd1861d86091cc1c32d", + "url": "https://api.github.com/repos/laravel/sail/zipball/057777403b8ab79222dcc04983beaab10b6de6a0", + "reference": "057777403b8ab79222dcc04983beaab10b6de6a0", "shasum": "" }, "require": { - "illuminate/console": "^9.0|^10.0|^11.0", - "illuminate/contracts": "^9.0|^10.0|^11.0", - "illuminate/support": "^9.0|^10.0|^11.0", + "illuminate/console": "^9.52.16|^10.0|^11.0", + "illuminate/contracts": "^9.52.16|^10.0|^11.0", + "illuminate/support": "^9.52.16|^10.0|^11.0", "php": "^8.0", "symfony/yaml": "^6.0|^7.0" }, @@ -7795,9 +6563,6 @@ ], "type": "library", "extra": { - "branch-alias": { - "dev-master": "1.x-dev" - }, "laravel": { "providers": [ "Laravel\\Sail\\SailServiceProvider" @@ -7828,7 +6593,7 @@ "issues": "https://github.com/laravel/sail/issues", "source": "https://github.com/laravel/sail" }, - "time": "2023-12-02T18:26:39+00:00" + "time": "2024-03-04T14:58:29+00:00" }, { "name": "mockery/mockery", @@ -8070,20 +6835,21 @@ }, { "name": "phar-io/manifest", - "version": "2.0.3", + "version": "2.0.4", "source": { "type": "git", "url": "https://github.com/phar-io/manifest.git", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53" + "reference": "54750ef60c58e43759730615a392c31c80e23176" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phar-io/manifest/zipball/97803eca37d319dfa7826cc2437fc020857acb53", - "reference": "97803eca37d319dfa7826cc2437fc020857acb53", + "url": "https://api.github.com/repos/phar-io/manifest/zipball/54750ef60c58e43759730615a392c31c80e23176", + "reference": "54750ef60c58e43759730615a392c31c80e23176", "shasum": "" }, "require": { "ext-dom": "*", + "ext-libxml": "*", "ext-phar": "*", "ext-xmlwriter": "*", "phar-io/version": "^3.0.1", @@ -8124,9 +6890,15 @@ "description": "Component for reading phar.io manifest information from a PHP Archive (PHAR)", "support": { "issues": "https://github.com/phar-io/manifest/issues", - "source": "https://github.com/phar-io/manifest/tree/2.0.3" + "source": "https://github.com/phar-io/manifest/tree/2.0.4" }, - "time": "2021-07-20T11:28:43+00:00" + "funding": [ + { + "url": "https://github.com/theseer", + "type": "github" + } + ], + "time": "2024-03-03T12:33:53+00:00" }, { "name": "phar-io/version", @@ -8181,16 +6953,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "10.1.11", + "version": "10.1.13", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "78c3b7625965c2513ee96569a4dbb62601784145" + "reference": "d51c3aec14896d5e80b354fad58e998d1980f8f8" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/78c3b7625965c2513ee96569a4dbb62601784145", - "reference": "78c3b7625965c2513ee96569a4dbb62601784145", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/d51c3aec14896d5e80b354fad58e998d1980f8f8", + "reference": "d51c3aec14896d5e80b354fad58e998d1980f8f8", "shasum": "" }, "require": { @@ -8247,7 +7019,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.11" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/10.1.13" }, "funding": [ { @@ -8255,7 +7027,7 @@ "type": "github" } ], - "time": "2023-12-21T15:38:30+00:00" + "time": "2024-03-09T16:54:15+00:00" }, { "name": "phpunit/php-file-iterator", @@ -8502,16 +7274,16 @@ }, { "name": "phpunit/phpunit", - "version": "10.5.5", + "version": "10.5.12", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/phpunit.git", - "reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856" + "reference": "41a9886b85ac7bf3929853baf96b95361cd69d2b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/ed21115d505b4b4f7dc7b5651464e19a2c7f7856", - "reference": "ed21115d505b4b4f7dc7b5651464e19a2c7f7856", + "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/41a9886b85ac7bf3929853baf96b95361cd69d2b", + "reference": "41a9886b85ac7bf3929853baf96b95361cd69d2b", "shasum": "" }, "require": { @@ -8583,7 +7355,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/phpunit/issues", "security": "https://github.com/sebastianbergmann/phpunit/security/policy", - "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.5" + "source": "https://github.com/sebastianbergmann/phpunit/tree/10.5.12" }, "funding": [ { @@ -8599,20 +7371,20 @@ "type": "tidelift" } ], - "time": "2023-12-27T15:13:52+00:00" + "time": "2024-03-09T12:04:07+00:00" }, { "name": "sebastian/cli-parser", - "version": "2.0.0", + "version": "2.0.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/cli-parser.git", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae" + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/efdc130dbbbb8ef0b545a994fd811725c5282cae", - "reference": "efdc130dbbbb8ef0b545a994fd811725c5282cae", + "url": "https://api.github.com/repos/sebastianbergmann/cli-parser/zipball/c34583b87e7b7a8055bf6c450c2c77ce32a24084", + "reference": "c34583b87e7b7a8055bf6c450c2c77ce32a24084", "shasum": "" }, "require": { @@ -8647,7 +7419,8 @@ "homepage": "https://github.com/sebastianbergmann/cli-parser", "support": { "issues": "https://github.com/sebastianbergmann/cli-parser/issues", - "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.0" + "security": "https://github.com/sebastianbergmann/cli-parser/security/policy", + "source": "https://github.com/sebastianbergmann/cli-parser/tree/2.0.1" }, "funding": [ { @@ -8655,7 +7428,7 @@ "type": "github" } ], - "time": "2023-02-03T06:58:15+00:00" + "time": "2024-03-02T07:12:49+00:00" }, { "name": "sebastian/code-unit", @@ -8905,16 +7678,16 @@ }, { "name": "sebastian/diff", - "version": "5.1.0", + "version": "5.1.1", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f" + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/fbf413a49e54f6b9b17e12d900ac7f6101591b7f", - "reference": "fbf413a49e54f6b9b17e12d900ac7f6101591b7f", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/c41e007b4b62af48218231d6c2275e4c9b975b2e", + "reference": "c41e007b4b62af48218231d6c2275e4c9b975b2e", "shasum": "" }, "require": { @@ -8922,7 +7695,7 @@ }, "require-dev": { "phpunit/phpunit": "^10.0", - "symfony/process": "^4.2 || ^5" + "symfony/process": "^6.4" }, "type": "library", "extra": { @@ -8960,7 +7733,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/diff/issues", "security": "https://github.com/sebastianbergmann/diff/security/policy", - "source": "https://github.com/sebastianbergmann/diff/tree/5.1.0" + "source": "https://github.com/sebastianbergmann/diff/tree/5.1.1" }, "funding": [ { @@ -8968,7 +7741,7 @@ "type": "github" } ], - "time": "2023-12-22T10:55:06+00:00" + "time": "2024-03-02T07:15:17+00:00" }, { "name": "sebastian/environment", @@ -9036,16 +7809,16 @@ }, { "name": "sebastian/exporter", - "version": "5.1.1", + "version": "5.1.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/exporter.git", - "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc" + "reference": "955288482d97c19a372d3f31006ab3f37da47adf" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/64f51654862e0f5e318db7e9dcc2292c63cdbddc", - "reference": "64f51654862e0f5e318db7e9dcc2292c63cdbddc", + "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/955288482d97c19a372d3f31006ab3f37da47adf", + "reference": "955288482d97c19a372d3f31006ab3f37da47adf", "shasum": "" }, "require": { @@ -9102,7 +7875,7 @@ "support": { "issues": "https://github.com/sebastianbergmann/exporter/issues", "security": "https://github.com/sebastianbergmann/exporter/security/policy", - "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.1" + "source": "https://github.com/sebastianbergmann/exporter/tree/5.1.2" }, "funding": [ { @@ -9110,20 +7883,20 @@ "type": "github" } ], - "time": "2023-09-24T13:22:09+00:00" + "time": "2024-03-02T07:17:12+00:00" }, { "name": "sebastian/global-state", - "version": "6.0.1", + "version": "6.0.2", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/global-state.git", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4" + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/7ea9ead78f6d380d2a667864c132c2f7b83055e4", - "reference": "7ea9ead78f6d380d2a667864c132c2f7b83055e4", + "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", + "reference": "987bafff24ecc4c9ac418cab1145b96dd6e9cbd9", "shasum": "" }, "require": { @@ -9157,14 +7930,14 @@ } ], "description": "Snapshotting of global state", - "homepage": "http://www.github.com/sebastianbergmann/global-state", + "homepage": "https://www.github.com/sebastianbergmann/global-state", "keywords": [ "global state" ], "support": { "issues": "https://github.com/sebastianbergmann/global-state/issues", "security": "https://github.com/sebastianbergmann/global-state/security/policy", - "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.1" + "source": "https://github.com/sebastianbergmann/global-state/tree/6.0.2" }, "funding": [ { @@ -9172,7 +7945,7 @@ "type": "github" } ], - "time": "2023-07-19T07:19:23+00:00" + "time": "2024-03-02T07:19:19+00:00" }, { "name": "sebastian/lines-of-code", @@ -9580,21 +8353,20 @@ }, { "name": "spatie/flare-client-php", - "version": "1.4.3", + "version": "1.4.4", "source": { "type": "git", "url": "https://github.com/spatie/flare-client-php.git", - "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec" + "reference": "17082e780752d346c2db12ef5d6bee8e835e399c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", - "reference": "5db2fdd743c3ede33f2a5367d89ec1a7c9c1d1ec", + "url": "https://api.github.com/repos/spatie/flare-client-php/zipball/17082e780752d346c2db12ef5d6bee8e835e399c", + "reference": "17082e780752d346c2db12ef5d6bee8e835e399c", "shasum": "" }, "require": { "illuminate/pipeline": "^8.0|^9.0|^10.0|^11.0", - "nesbot/carbon": "^2.62.1", "php": "^8.0", "spatie/backtrace": "^1.5.2", "symfony/http-foundation": "^5.2|^6.0|^7.0", @@ -9638,7 +8410,7 @@ ], "support": { "issues": "https://github.com/spatie/flare-client-php/issues", - "source": "https://github.com/spatie/flare-client-php/tree/1.4.3" + "source": "https://github.com/spatie/flare-client-php/tree/1.4.4" }, "funding": [ { @@ -9646,20 +8418,20 @@ "type": "github" } ], - "time": "2023-10-17T15:54:07+00:00" + "time": "2024-01-31T14:18:45+00:00" }, { "name": "spatie/ignition", - "version": "1.11.3", + "version": "1.12.0", "source": { "type": "git", "url": "https://github.com/spatie/ignition.git", - "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044" + "reference": "5b6f801c605a593106b623e45ca41496a6e7d56d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/ignition/zipball/3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", - "reference": "3d886de644ff7a5b42e4d27c1e1f67c8b5f00044", + "url": "https://api.github.com/repos/spatie/ignition/zipball/5b6f801c605a593106b623e45ca41496a6e7d56d", + "reference": "5b6f801c605a593106b623e45ca41496a6e7d56d", "shasum": "" }, "require": { @@ -9729,39 +8501,39 @@ "type": "github" } ], - "time": "2023-10-18T14:09:40+00:00" + "time": "2024-01-03T15:49:39+00:00" }, { "name": "spatie/laravel-ignition", - "version": "2.3.3", + "version": "2.4.2", "source": { "type": "git", "url": "https://github.com/spatie/laravel-ignition.git", - "reference": "66499cd3c858642ded56dafb8fa0352057ca20dd" + "reference": "351504f4570e32908839fc5a2dc53bf77d02f85e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/66499cd3c858642ded56dafb8fa0352057ca20dd", - "reference": "66499cd3c858642ded56dafb8fa0352057ca20dd", + "url": "https://api.github.com/repos/spatie/laravel-ignition/zipball/351504f4570e32908839fc5a2dc53bf77d02f85e", + "reference": "351504f4570e32908839fc5a2dc53bf77d02f85e", "shasum": "" }, "require": { "ext-curl": "*", "ext-json": "*", "ext-mbstring": "*", - "illuminate/support": "^10.0", + "illuminate/support": "^10.0|^11.0", "php": "^8.1", "spatie/flare-client-php": "^1.3.5", "spatie/ignition": "^1.9", - "symfony/console": "^6.2.3", - "symfony/var-dumper": "^6.2.3" + "symfony/console": "^6.2.3|^7.0", + "symfony/var-dumper": "^6.2.3|^7.0" }, "require-dev": { - "livewire/livewire": "^2.11", + "livewire/livewire": "^2.11|^3.3.5", "mockery/mockery": "^1.5.1", - "openai-php/client": "^0.3.4", - "orchestra/testbench": "^8.0", - "pestphp/pest": "^1.22.3", + "openai-php/client": "^0.8.1", + "orchestra/testbench": "^8.0|^9.0", + "pestphp/pest": "^2.30", "phpstan/extension-installer": "^1.2", "phpstan/phpstan-deprecation-rules": "^1.1.1", "phpstan/phpstan-phpunit": "^1.3.3", @@ -9821,32 +8593,31 @@ "type": "github" } ], - "time": "2023-12-21T09:43:05+00:00" + "time": "2024-02-09T16:08:40+00:00" }, { "name": "symfony/yaml", - "version": "v6.4.0", + "version": "v7.0.3", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587" + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/4f9237a1bb42455d609e6687d2613dde5b41a587", - "reference": "4f9237a1bb42455d609e6687d2613dde5b41a587", + "url": "https://api.github.com/repos/symfony/yaml/zipball/2d4fca631c00700597e9442a0b2451ce234513d3", + "reference": "2d4fca631c00700597e9442a0b2451ce234513d3", "shasum": "" }, "require": { - "php": ">=8.1", - "symfony/deprecation-contracts": "^2.5|^3", + "php": ">=8.2", "symfony/polyfill-ctype": "^1.8" }, "conflict": { - "symfony/console": "<5.4" + "symfony/console": "<6.4" }, "require-dev": { - "symfony/console": "^5.4|^6.0|^7.0" + "symfony/console": "^6.4|^7.0" }, "bin": [ "Resources/bin/yaml-lint" @@ -9877,7 +8648,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v6.4.0" + "source": "https://github.com/symfony/yaml/tree/v7.0.3" }, "funding": [ { @@ -9893,20 +8664,20 @@ "type": "tidelift" } ], - "time": "2023-11-06T11:00:25+00:00" + "time": "2024-01-23T15:02:46+00:00" }, { "name": "theseer/tokenizer", - "version": "1.2.2", + "version": "1.2.3", "source": { "type": "git", "url": "https://github.com/theseer/tokenizer.git", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96" + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/theseer/tokenizer/zipball/b2ad5003ca10d4ee50a12da31de12a5774ba6b96", - "reference": "b2ad5003ca10d4ee50a12da31de12a5774ba6b96", + "url": "https://api.github.com/repos/theseer/tokenizer/zipball/737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", + "reference": "737eda637ed5e28c3413cb1ebe8bb52cbf1ca7a2", "shasum": "" }, "require": { @@ -9935,7 +8706,7 @@ "description": "A small library for converting tokenized PHP source code into XML and potentially other formats", "support": { "issues": "https://github.com/theseer/tokenizer/issues", - "source": "https://github.com/theseer/tokenizer/tree/1.2.2" + "source": "https://github.com/theseer/tokenizer/tree/1.2.3" }, "funding": [ { @@ -9943,7 +8714,7 @@ "type": "github" } ], - "time": "2023-11-20T00:12:19+00:00" + "time": "2024-03-03T12:36:25+00:00" } ], "aliases": [], @@ -9952,9 +8723,9 @@ "prefer-stable": true, "prefer-lowest": false, "platform": { - "php": "^8.1", + "php": "^8.2", "ext-ftp": "*" }, "platform-dev": [], - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.2.0" } diff --git a/config/app.php b/config/app.php index 5b4d798..9bac67f 100644 --- a/config/app.php +++ b/config/app.php @@ -15,7 +15,7 @@ | */ - 'name' => env('APP_NAME', 'Laravel'), + 'name' => env('APP_NAME', 'Vito'), /* |-------------------------------------------------------------------------- @@ -192,8 +192,6 @@ */ App\Providers\AppServiceProvider::class, App\Providers\AuthServiceProvider::class, - App\Providers\BroadcastServiceProvider::class, - App\Providers\EventServiceProvider::class, App\Providers\RouteServiceProvider::class, ], diff --git a/config/broadcasting.php b/config/broadcasting.php deleted file mode 100644 index 9e4d4aa..0000000 --- a/config/broadcasting.php +++ /dev/null @@ -1,70 +0,0 @@ - env('BROADCAST_DRIVER', 'null'), - - /* - |-------------------------------------------------------------------------- - | Broadcast Connections - |-------------------------------------------------------------------------- - | - | Here you may define all of the broadcast connections that will be used - | to broadcast events to other systems or over websockets. Samples of - | each available type of connection are provided inside this array. - | - */ - - 'connections' => [ - - 'pusher' => [ - 'driver' => 'pusher', - 'key' => env('PUSHER_APP_KEY'), - 'secret' => env('PUSHER_APP_SECRET'), - 'app_id' => env('PUSHER_APP_ID'), - 'options' => [ - 'host' => env('PUSHER_HOST') ?: 'api-'.env('PUSHER_APP_CLUSTER', 'mt1').'.pusher.com', - 'port' => env('PUSHER_PORT', 443), - 'scheme' => env('PUSHER_SCHEME', 'https'), - 'encrypted' => true, - 'useTLS' => env('PUSHER_SCHEME', 'https') === 'https', - ], - 'client_options' => [ - // Guzzle client options: https://docs.guzzlephp.org/en/stable/request-options.html - ], - ], - - 'ably' => [ - 'driver' => 'ably', - 'key' => env('ABLY_KEY'), - ], - - 'redis' => [ - 'driver' => 'redis', - 'connection' => 'default', - ], - - 'log' => [ - 'driver' => 'log', - ], - - 'null' => [ - 'driver' => 'null', - ], - - ], - -]; diff --git a/config/core.php b/config/core.php index 5a45541..4068070 100755 --- a/config/core.php +++ b/config/core.php @@ -1,15 +1,7 @@ env('SSH_USER', 'vito'), 'ssh_public_key_name' => env('SSH_PUBLIC_KEY_NAME', 'ssh-public.key'), 'ssh_private_key_name' => env('SSH_PRIVATE_KEY_NAME', 'ssh-private.pem'), - 'logs_disk' => env('SERVER_LOGS_DISK', 'server-logs-local'), // should to be FilesystemAdapter storage - 'key_pairs_disk' => env('KEY_PAIRS_DISK', 'key-pairs-local'), // should to be FilesystemAdapter storage + 'logs_disk' => env('SERVER_LOGS_DISK', 'server-logs'), // should be FilesystemAdapter storage + 'key_pairs_disk' => env('KEY_PAIRS_DISK', 'key-pairs'), // should be FilesystemAdapter storage /* * General */ 'operating_systems' => [ - // 'ubuntu_18', - 'ubuntu_20', - 'ubuntu_22', + OperatingSystem::UBUNTU20, + OperatingSystem::UBUNTU22, ], 'webservers' => ['none', 'nginx'], 'php_versions' => [ @@ -64,28 +58,71 @@ '8.0', '8.1', '8.2', + '8.3', + ], + 'databases' => [ + 'none', + 'mysql57', + 'mysql80', + 'mariadb103', + 'mariadb104', + 'postgresql12', + 'postgresql13', + 'postgresql14', + 'postgresql15', + 'postgresql16', ], - 'databases' => ['none', 'mysql57', 'mysql80', 'mariadb'], 'databases_name' => [ + 'none' => 'none', 'mysql57' => 'mysql', 'mysql80' => 'mysql', - 'mariadb' => 'mariadb', + 'mariadb103' => 'mariadb', + 'mariadb104' => 'mariadb', + 'postgresql12' => 'postgresql', + 'postgresql13' => 'postgresql', + 'postgresql14' => 'postgresql', + 'postgresql15' => 'postgresql', + 'postgresql16' => 'postgresql', ], 'databases_version' => [ + 'none' => '', 'mysql57' => '5.7', 'mysql80' => '8.0', 'mariadb' => '10.3', + 'mariadb103' => '10.3', + 'mariadb104' => '10.4', + 'postgresql12' => '12', + 'postgresql13' => '13', + 'postgresql14' => '14', + 'postgresql15' => '15', + 'postgresql16' => '16', + ], + 'database_features' => [ + 'remote' => [ + 'mysql', + 'mariadb', + ], ], /* * Server */ - 'server_types' => \App\Enums\ServerType::getValues(), + 'server_types' => [ + \App\Enums\ServerType::REGULAR, + \App\Enums\ServerType::DATABASE, + ], 'server_types_class' => [ \App\Enums\ServerType::REGULAR => \App\ServerTypes\Regular::class, \App\Enums\ServerType::DATABASE => \App\ServerTypes\Database::class, ], - 'server_providers' => \App\Enums\ServerProvider::getValues(), + 'server_providers' => [ + \App\Enums\ServerProvider::CUSTOM, + \App\Enums\ServerProvider::AWS, + \App\Enums\ServerProvider::LINODE, + \App\Enums\ServerProvider::DIGITALOCEAN, + \App\Enums\ServerProvider::VULTR, + \App\Enums\ServerProvider::HETZNER, + ], 'server_providers_class' => [ \App\Enums\ServerProvider::CUSTOM => \App\ServerProviders\Custom::class, \App\Enums\ServerProvider::AWS => AWS::class, @@ -130,24 +167,12 @@ /* * Service */ - 'service_installers' => [ - 'nginx' => InstallNginx::class, - 'mysql' => InstallMysql::class, - 'mariadb' => InstallMariadb::class, - 'php' => InstallPHP::class, - 'redis' => InstallRedis::class, - 'supervisor' => InstallSupervisor::class, - 'ufw' => InstallUfw::class, - 'phpmyadmin' => InstallPHPMyAdmin::class, - ], - 'service_uninstallers' => [ - 'phpmyadmin' => UninstallPHPMyAdmin::class, - 'php' => UninstallPHP::class, - ], 'service_handlers' => [ 'nginx' => Nginx::class, 'mysql' => Mysql::class, - 'mariadb' => Mysql::class, + 'mariadb' => Mariadb::class, + 'postgresql' => Postgresql::class, + 'redis' => Redis::class, 'php' => PHP::class, 'ufw' => Ufw::class, 'supervisor' => Supervisor::class, @@ -181,12 +206,38 @@ 'mariadb' => [ 'ubuntu_18' => [ '10.3' => 'mariadb', + '10.4' => 'mariadb', ], 'ubuntu_20' => [ '10.3' => 'mariadb', + '10.4' => 'mariadb', ], 'ubuntu_22' => [ '10.3' => 'mariadb', + '10.4' => 'mariadb', + ], + ], + 'postgresql' => [ + 'ubuntu_18' => [ + '12' => 'postgresql', + '13' => 'postgresql', + '14' => 'postgresql', + '15' => 'postgresql', + '16' => 'postgresql', + ], + 'ubuntu_20' => [ + '12' => 'postgresql', + '13' => 'postgresql', + '14' => 'postgresql', + '15' => 'postgresql', + '16' => 'postgresql', + ], + 'ubuntu_22' => [ + '12' => 'postgresql', + '13' => 'postgresql', + '14' => 'postgresql', + '15' => 'postgresql', + '16' => 'postgresql', ], ], 'php' => [ @@ -200,6 +251,7 @@ '8.0' => 'php8.0-fpm', '8.1' => 'php8.1-fpm', '8.2' => 'php8.2-fpm', + '8.3' => 'php8.3-fpm', ], 'ubuntu_20' => [ '5.6' => 'php5.6-fpm', @@ -210,7 +262,7 @@ '7.4' => 'php7.4-fpm', '8.0' => 'php8.0-fpm', '8.1' => 'php8.1-fpm', - '8.2' => 'php8.2-fpm', + '8.3' => 'php8.3-fpm', ], 'ubuntu_22' => [ '5.6' => 'php5.6-fpm', @@ -222,6 +274,7 @@ '8.0' => 'php8.0-fpm', '8.1' => 'php8.1-fpm', '8.2' => 'php8.2-fpm', + '8.3' => 'php8.3-fpm', ], ], 'redis' => [ @@ -282,7 +335,6 @@ 'github', 'gitlab', 'bitbucket', - 'custom', ], 'source_control_providers_class' => [ 'github' => Github::class, @@ -323,11 +375,6 @@ * firewall */ 'firewall_protocols_port' => [ - 'ssh' => 22, - 'http' => 80, - 'https' => 443, - 'mysql' => 3306, - 'ftp' => 21, 'tcp' => '', 'udp' => '', ], @@ -360,11 +407,16 @@ * storage providers */ 'storage_providers' => [ - 'dropbox', - 'ftp', + StorageProvider::DROPBOX, + StorageProvider::FTP, ], 'storage_providers_class' => [ 'dropbox' => Dropbox::class, 'ftp' => FTP::class, ], + + 'ssl_types' => [ + \App\Enums\SslType::LETSENCRYPT, + \App\Enums\SslType::CUSTOM, + ], ]; diff --git a/config/cors.php b/config/cors.php index 8a39e6d..558369d 100644 --- a/config/cors.php +++ b/config/cors.php @@ -15,7 +15,7 @@ | */ - 'paths' => ['api/*', 'sanctum/csrf-cookie'], + 'paths' => ['api/*'], 'allowed_methods' => ['*'], diff --git a/config/database.php b/config/database.php index 833bcc8..03641e9 100644 --- a/config/database.php +++ b/config/database.php @@ -15,7 +15,7 @@ | */ - 'default' => env('DB_CONNECTION', 'mysql'), + 'default' => env('DB_CONNECTION', 'sqlite'), /* |-------------------------------------------------------------------------- @@ -37,8 +37,7 @@ 'sqlite' => [ 'driver' => 'sqlite', - 'url' => env('DATABASE_URL'), - 'database' => env('DB_DATABASE', database_path('database.sqlite')), + 'database' => storage_path(env('DB_DATABASE', 'database.sqlite')), 'prefix' => '', 'foreign_key_constraints' => env('DB_FOREIGN_KEYS', true), ], diff --git a/config/filesystems.php b/config/filesystems.php index c2e46c0..8a22cd7 100644 --- a/config/filesystems.php +++ b/config/filesystems.php @@ -57,11 +57,23 @@ 'throw' => false, ], + 'key-pairs' => [ + 'driver' => 'local', + 'root' => storage_path('app/key-pairs'), + ], + + // @deprecated 'key-pairs-local' => [ 'driver' => 'local', 'root' => storage_path('app/key-pairs'), ], + 'server-logs' => [ + 'driver' => 'local', + 'root' => storage_path('app/server-logs'), + ], + + // @deprecated 'server-logs-local' => [ 'driver' => 'local', 'root' => storage_path('app/server-logs'), diff --git a/config/livewire.php b/config/livewire.php deleted file mode 100644 index b660277..0000000 --- a/config/livewire.php +++ /dev/null @@ -1,159 +0,0 @@ - 'App\\Http\\Livewire', - - /* - |--------------------------------------------------------------------------- - | View Path - |--------------------------------------------------------------------------- - | - | This value is used to specify where Livewire component Blade templates are - | stored when running file creation commands like `artisan make:livewire`. - | It is also used if you choose to omit a component's render() method. - | - */ - - 'view_path' => resource_path('views/livewire'), - - /* - |--------------------------------------------------------------------------- - | Layout - |--------------------------------------------------------------------------- - | The view that will be used as the layout when rendering a single component - | as an entire page via `Route::get('/post/create', CreatePost::class);`. - | In this case, the view returned by CreatePost will render into $slot. - | - */ - - 'layout' => 'layouts.app', - - /* - |--------------------------------------------------------------------------- - | Lazy Loading Placeholder - |--------------------------------------------------------------------------- - | Livewire allows you to lazy load components that would otherwise slow down - | the initial page load. Every component can have a custom placeholder or - | you can define the default placeholder view for all components below. - | - */ - - 'lazy_placeholder' => null, - - /* - |--------------------------------------------------------------------------- - | Temporary File Uploads - |--------------------------------------------------------------------------- - | - | Livewire handles file uploads by storing uploads in a temporary directory - | before the file is stored permanently. All file uploads are directed to - | a global endpoint for temporary storage. You may configure this below: - | - */ - - 'temporary_file_upload' => [ - 'disk' => null, // Example: 'local', 's3' | Default: 'default' - 'rules' => null, // Example: ['file', 'mimes:png,jpg'] | Default: ['required', 'file', 'max:12288'] (12MB) - 'directory' => null, // Example: 'tmp' | Default: 'livewire-tmp' - 'middleware' => null, // Example: 'throttle:5,1' | Default: 'throttle:60,1' - 'preview_mimes' => [ // Supported file types for temporary pre-signed file URLs... - 'png', 'gif', 'bmp', 'svg', 'wav', 'mp4', - 'mov', 'avi', 'wmv', 'mp3', 'm4a', - 'jpg', 'jpeg', 'mpga', 'webp', 'wma', - ], - 'max_upload_time' => 5, // Max duration (in minutes) before an upload is invalidated... - ], - - /* - |--------------------------------------------------------------------------- - | Render On Redirect - |--------------------------------------------------------------------------- - | - | This value determines if Livewire will run a component's `render()` method - | after a redirect has been triggered using something like `redirect(...)` - | Setting this to true will render the view once more before redirecting - | - */ - - 'render_on_redirect' => false, - - /* - |--------------------------------------------------------------------------- - | Eloquent Model Binding - |--------------------------------------------------------------------------- - | - | Previous versions of Livewire supported binding directly to eloquent model - | properties using wire:model by default. However, this behavior has been - | deemed too "magical" and has therefore been put under a feature flag. - | - */ - - 'legacy_model_binding' => false, - - /* - |--------------------------------------------------------------------------- - | Auto-inject Frontend Assets - |--------------------------------------------------------------------------- - | - | By default, Livewire automatically injects its JavaScript and CSS into the - | and of pages containing Livewire components. By disabling - | this behavior, you need to use @livewireStyles and @livewireScripts. - | - */ - - 'inject_assets' => true, - - /* - |--------------------------------------------------------------------------- - | Navigate (SPA mode) - |--------------------------------------------------------------------------- - | - | By adding `wire:navigate` to links in your Livewire application, Livewire - | will prevent the default link handling and instead request those pages - | via AJAX, creating an SPA-like effect. Configure this behavior here. - | - */ - - 'navigate' => [ - 'show_progress_bar' => true, - 'progress_bar_color' => '#2299dd', - ], - - /* - |--------------------------------------------------------------------------- - | HTML Morph Markers - |--------------------------------------------------------------------------- - | - | Livewire intelligently "morphs" existing HTML into the newly rendered HTML - | after each update. To make this process more reliable, Livewire injects - | "markers" into the rendered Blade surrounding @if, @class & @foreach. - | - */ - - 'inject_morph_markers' => true, - - /* - |--------------------------------------------------------------------------- - | Pagination Theme - |--------------------------------------------------------------------------- - | - | When enabling Livewire's pagination feature by using the `WithPagination` - | trait, Livewire will use Tailwind templates to render pagination views - | on the page. If you want Bootstrap CSS, you can specify: "bootstrap" - | - */ - - 'pagination_theme' => 'tailwind', -]; diff --git a/config/log-viewer.php b/config/log-viewer.php deleted file mode 100644 index 1f6f9a9..0000000 --- a/config/log-viewer.php +++ /dev/null @@ -1,214 +0,0 @@ - env('LOG_VIEWER_ENABLED', true), - - 'require_auth_in_production' => true, - - /* - |-------------------------------------------------------------------------- - | Log Viewer Domain - |-------------------------------------------------------------------------- - | You may change the domain where Log Viewer should be active. - | If the domain is empty, all domains will be valid. - | - */ - - 'route_domain' => null, - - /* - |-------------------------------------------------------------------------- - | Log Viewer Route - |-------------------------------------------------------------------------- - | Log Viewer will be available under this URL. - | - */ - - 'route_path' => 'log-viewer', - - /* - |-------------------------------------------------------------------------- - | Back to system URL - |-------------------------------------------------------------------------- - | When set, displays a link to easily get back to this URL. - | Set to `null` to hide this link. - | - | Optional label to display for the above URL. - | - */ - - 'back_to_system_url' => config('app.url', null), - - 'back_to_system_label' => null, // Displayed by default: "Back to {{ app.name }}" - - /* - |-------------------------------------------------------------------------- - | Log Viewer time zone. - |-------------------------------------------------------------------------- - | The time zone in which to display the times in the UI. Defaults to - | the application's timezone defined in config/app.php. - | - */ - - 'timezone' => null, - - /* - |-------------------------------------------------------------------------- - | Log Viewer route middleware. - |-------------------------------------------------------------------------- - | Optional middleware to use when loading the initial Log Viewer page. - | - */ - - 'middleware' => [ - 'web', - 'auth', - ], - - /* - |-------------------------------------------------------------------------- - | Log Viewer API middleware. - |-------------------------------------------------------------------------- - | Optional middleware to use on every API request. The same API is also - | used from within the Log Viewer user interface. - | - */ - - 'api_middleware' => [ - \Opcodes\LogViewer\Http\Middleware\EnsureFrontendRequestsAreStateful::class, - 'auth', - ], - - /* - |-------------------------------------------------------------------------- - | Log Viewer Remote hosts. - |-------------------------------------------------------------------------- - | Log Viewer supports viewing Laravel logs from remote hosts. They must - | be running Log Viewer as well. Below you can define the hosts you - | would like to show in this Log Viewer instance. - | - */ - - 'hosts' => [ - 'local' => [ - 'name' => ucfirst(env('APP_ENV', 'local')), - ], - - // 'staging' => [ - // 'name' => 'Staging', - // 'host' => 'https://staging.example.com/log-viewer', - // 'auth' => [ // Example of HTTP Basic auth - // 'username' => 'username', - // 'password' => 'password', - // ], - // ], - // - // 'production' => [ - // 'name' => 'Production', - // 'host' => 'https://example.com/log-viewer', - // 'auth' => [ // Example of Bearer token auth - // 'token' => env('LOG_VIEWER_PRODUCTION_TOKEN'), - // ], - // 'headers' => [ - // 'X-Foo' => 'Bar', - // ], - // ], - ], - - /* - |-------------------------------------------------------------------------- - | Include file patterns - |-------------------------------------------------------------------------- - | - */ - - 'include_files' => [ - '*.log', - '**/*.log', - - // You can include paths to other log types as well, such as apache, nginx, and more. - '/var/log/httpd/*', - '/var/log/nginx/*', - - // MacOS Apple Silicon logs - '/opt/homebrew/var/log/nginx/*', - '/opt/homebrew/var/log/httpd/*', - '/opt/homebrew/var/log/php-fpm.log', - '/opt/homebrew/var/log/postgres*log', - '/opt/homebrew/var/log/redis*log', - '/opt/homebrew/var/log/supervisor*log', - - // '/absolute/paths/supported', - ], - - /* - |-------------------------------------------------------------------------- - | Exclude file patterns. - |-------------------------------------------------------------------------- - | This will take precedence over included files. - | - */ - - 'exclude_files' => [ - // 'my_secret.log' - ], - - /* - |-------------------------------------------------------------------------- - | Hide unknown files. - |-------------------------------------------------------------------------- - | The include/exclude options above might catch files which are not - | logs supported by Log Viewer. In that case, you can hide them - | from the UI and API calls by setting this to true. - | - */ - - 'hide_unknown_files' => true, - - /* - |-------------------------------------------------------------------------- - | Shorter stack trace filters. - |-------------------------------------------------------------------------- - | Lines containing any of these strings will be excluded from the full log. - | This setting is only active when the function is enabled via the user interface. - | - */ - - 'shorter_stack_trace_excludes' => [ - '/vendor/symfony/', - '/vendor/laravel/framework/', - '/vendor/barryvdh/laravel-debugbar/', - ], - - /* - |-------------------------------------------------------------------------- - | Cache driver - |-------------------------------------------------------------------------- - | Cache driver to use for storing the log indices. Indices are used to speed up - | log navigation. Defaults to your application's default cache driver. - | - */ - - 'cache_driver' => env('LOG_VIEWER_CACHE_DRIVER', null), - - /* - |-------------------------------------------------------------------------- - | Chunk size when scanning log files lazily - |-------------------------------------------------------------------------- - | The size in MB of files to scan before updating the progress bar when searching across all files. - | - */ - - 'lazy_scan_chunk_size_in_mb' => 50, - - 'strip_extracted_context' => true, -]; diff --git a/config/queue.php b/config/queue.php index 0f7d124..410319b 100755 --- a/config/queue.php +++ b/config/queue.php @@ -54,15 +54,7 @@ 'ssh' => [ 'driver' => 'database', 'table' => 'jobs', - 'queue' => 'ssh', - 'timeout' => 240, - 'retry_after' => 600, - ], - - 'ssh-long' => [ - 'driver' => 'database', - 'table' => 'jobs', - 'queue' => 'ssh-long', + 'queue' => 'default', 'timeout' => 600, 'retry_after' => 600, ], @@ -82,7 +74,7 @@ 'failed' => [ 'driver' => env('QUEUE_FAILED_DRIVER', 'database-uuids'), - 'database' => env('DB_CONNECTION', 'mysql'), + 'database' => env('DB_CONNECTION', 'sqlite'), 'table' => 'failed_jobs', ], diff --git a/config/sanctum.php b/config/sanctum.php deleted file mode 100644 index 529cfdc..0000000 --- a/config/sanctum.php +++ /dev/null @@ -1,67 +0,0 @@ - explode(',', env('SANCTUM_STATEFUL_DOMAINS', sprintf( - '%s%s', - 'localhost,localhost:3000,127.0.0.1,127.0.0.1:8000,::1', - Sanctum::currentApplicationUrlWithPort() - ))), - - /* - |-------------------------------------------------------------------------- - | Sanctum Guards - |-------------------------------------------------------------------------- - | - | This array contains the authentication guards that will be checked when - | Sanctum is trying to authenticate a request. If none of these guards - | are able to authenticate the request, Sanctum will use the bearer - | token that's present on an incoming request for authentication. - | - */ - - 'guard' => ['web'], - - /* - |-------------------------------------------------------------------------- - | Expiration Minutes - |-------------------------------------------------------------------------- - | - | This value controls the number of minutes until an issued token will be - | considered expired. If this value is null, personal access tokens do - | not expire. This won't tweak the lifetime of first-party sessions. - | - */ - - 'expiration' => null, - - /* - |-------------------------------------------------------------------------- - | Sanctum Middleware - |-------------------------------------------------------------------------- - | - | When authenticating your first-party SPA with Sanctum you may need to - | customize some of the middleware Sanctum uses while processing the - | request. You may change the middleware listed below as required. - | - */ - - 'middleware' => [ - 'verify_csrf_token' => App\Http\Middleware\VerifyCsrfToken::class, - 'encrypt_cookies' => App\Http\Middleware\EncryptCookies::class, - ], - -]; diff --git a/database/factories/RedirectFactory.php b/database/factories/RedirectFactory.php deleted file mode 100644 index b2ab1cf..0000000 --- a/database/factories/RedirectFactory.php +++ /dev/null @@ -1,25 +0,0 @@ - $this->faker->randomNumber(), - 'mode' => $this->faker->randomNumber(), - 'from' => $this->faker->word(), - 'to' => $this->faker->word(), - 'status' => $this->faker->word(), - 'created_at' => Carbon::now(), - 'updated_at' => Carbon::now(), - ]; - } -} diff --git a/database/factories/ScriptExecutionFactory.php b/database/factories/ScriptExecutionFactory.php deleted file mode 100644 index 7140f1a..0000000 --- a/database/factories/ScriptExecutionFactory.php +++ /dev/null @@ -1,26 +0,0 @@ - $this->faker->word(), - 'finished_at' => Carbon::now(), - 'created_at' => Carbon::now(), - 'updated_at' => Carbon::now(), - 'script_id' => Script::factory(), - 'server_id' => Server::factory(), - ]; - } -} diff --git a/database/factories/ScriptFactory.php b/database/factories/ScriptFactory.php deleted file mode 100644 index 983669a..0000000 --- a/database/factories/ScriptFactory.php +++ /dev/null @@ -1,15 +0,0 @@ - 'test-log', 'name' => 'test.log', - 'disk' => 'server-logs-local', + 'disk' => 'server-logs', ]; } } diff --git a/database/factories/ServerProviderFactory.php b/database/factories/ServerProviderFactory.php index 9eb3c21..10398fe 100644 --- a/database/factories/ServerProviderFactory.php +++ b/database/factories/ServerProviderFactory.php @@ -14,7 +14,7 @@ public function definition(): array { return [ 'profile' => $this->faker->word(), - 'provider' => $this->faker->randomElement(\App\Enums\ServerProvider::getValues()), + 'provider' => $this->faker->randomElement(config('core.server_providers')), 'credentials' => [], 'connected' => 1, 'user_id' => User::factory(), diff --git a/database/factories/StorageProviderFactory.php b/database/factories/StorageProviderFactory.php index 32a28f9..1d21cdd 100644 --- a/database/factories/StorageProviderFactory.php +++ b/database/factories/StorageProviderFactory.php @@ -11,8 +11,10 @@ public function definition(): array { return [ 'profile' => $this->faker->word(), - 'provider' => $this->faker->randomElement(\App\Enums\StorageProvider::getValues()), - 'credentials' => [], + 'provider' => $this->faker->randomElement(config('core.storage_providers')), + 'credentials' => [ + 'token' => 'test-token', + ], 'user_id' => User::factory(), ]; } diff --git a/database/migrations/2014_10_12_000000_create_users_table.php b/database/migrations/2014_10_12_000000_create_users_table.php index c97e64f..87edb9d 100755 --- a/database/migrations/2014_10_12_000000_create_users_table.php +++ b/database/migrations/2014_10_12_000000_create_users_table.php @@ -17,7 +17,7 @@ public function up(): void $table->string('profile_photo_path', 2048)->nullable(); $table->text('two_factor_secret')->nullable(); $table->text('two_factor_recovery_codes')->nullable(); - $table->string('timezone')->default('UTC'); + $table->string('timezone')->default('UTC')->nullable(); $table->timestamps(); }); } diff --git a/database/migrations/2018_08_08_100000_create_telescope_entries_table.php b/database/migrations/2018_08_08_100000_create_telescope_entries_table.php new file mode 100644 index 0000000..700a83f --- /dev/null +++ b/database/migrations/2018_08_08_100000_create_telescope_entries_table.php @@ -0,0 +1,70 @@ +getConnection()); + + $schema->create('telescope_entries', function (Blueprint $table) { + $table->bigIncrements('sequence'); + $table->uuid('uuid'); + $table->uuid('batch_id'); + $table->string('family_hash')->nullable(); + $table->boolean('should_display_on_index')->default(true); + $table->string('type', 20); + $table->longText('content'); + $table->dateTime('created_at')->nullable(); + + $table->unique('uuid'); + $table->index('batch_id'); + $table->index('family_hash'); + $table->index('created_at'); + $table->index(['type', 'should_display_on_index']); + }); + + $schema->create('telescope_entries_tags', function (Blueprint $table) { + $table->uuid('entry_uuid'); + $table->string('tag'); + + $table->primary(['entry_uuid', 'tag']); + $table->index('tag'); + + $table->foreign('entry_uuid') + ->references('uuid') + ->on('telescope_entries') + ->onDelete('cascade'); + }); + + $schema->create('telescope_monitoring', function (Blueprint $table) { + $table->string('tag')->primary(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + $schema = Schema::connection($this->getConnection()); + + $schema->dropIfExists('telescope_entries_tags'); + $schema->dropIfExists('telescope_entries'); + $schema->dropIfExists('telescope_monitoring'); + } +}; diff --git a/database/migrations/2021_06_23_211827_create_servers_table.php b/database/migrations/2021_06_23_211827_create_servers_table.php index 3425381..3333b10 100755 --- a/database/migrations/2021_06_23_211827_create_servers_table.php +++ b/database/migrations/2021_06_23_211827_create_servers_table.php @@ -25,7 +25,7 @@ public function up(): void $table->longText('authentication')->nullable(); $table->longText('public_key')->nullable(); $table->string('status')->default('installing'); - $table->tinyInteger('progress')->default(0); + $table->integer('progress')->default(0); $table->string('progress_step')->nullable(); $table->timestamps(); }); diff --git a/database/migrations/2021_06_23_214143_create_services_table.php b/database/migrations/2021_06_23_214143_create_services_table.php index 360db98..1ff1cc4 100755 --- a/database/migrations/2021_06_23_214143_create_services_table.php +++ b/database/migrations/2021_06_23_214143_create_services_table.php @@ -16,7 +16,7 @@ public function up(): void $table->json('type_data')->nullable(); $table->string('name'); $table->string('version'); - $table->enum('status', ServiceStatus::getValues())->default(ServiceStatus::INSTALLING); + $table->string('status')->default(ServiceStatus::INSTALLING); $table->boolean('is_default')->default(1); $table->string('unit')->nullable(); $table->timestamps(); diff --git a/database/migrations/2021_06_25_102220_create_jobs_table.php b/database/migrations/2021_06_25_102220_create_jobs_table.php index cb4e878..bb1daa2 100755 --- a/database/migrations/2021_06_25_102220_create_jobs_table.php +++ b/database/migrations/2021_06_25_102220_create_jobs_table.php @@ -12,10 +12,10 @@ public function up(): void $table->bigIncrements('id'); $table->string('queue')->index(); $table->longText('payload'); - $table->unsignedTinyInteger('attempts'); - $table->unsignedInteger('reserved_at')->nullable(); - $table->unsignedInteger('available_at'); - $table->unsignedInteger('created_at'); + $table->integer('attempts'); + $table->integer('reserved_at')->nullable(); + $table->integer('available_at'); + $table->integer('created_at'); }); } diff --git a/database/migrations/2021_06_26_211903_create_sites_table.php b/database/migrations/2021_06_26_211903_create_sites_table.php index 5944aa3..b8c20b8 100755 --- a/database/migrations/2021_06_26_211903_create_sites_table.php +++ b/database/migrations/2021_06_26_211903_create_sites_table.php @@ -23,7 +23,7 @@ public function up(): void $table->string('branch')->nullable(); $table->integer('port')->nullable(); $table->string('status')->default('installing'); - $table->tinyInteger('progress')->default(0); + $table->integer('progress')->default(0)->nullable(); $table->timestamps(); }); } diff --git a/database/migrations/2021_06_28_085814_create_source_controls_table.php b/database/migrations/2021_06_28_085814_create_source_controls_table.php index 23a9b69..086675b 100755 --- a/database/migrations/2021_06_28_085814_create_source_controls_table.php +++ b/database/migrations/2021_06_28_085814_create_source_controls_table.php @@ -11,7 +11,8 @@ public function up(): void Schema::create('source_controls', function (Blueprint $table) { $table->id(); $table->string('provider'); - $table->longText('access_token'); + $table->json('provider_data')->nullable(); + $table->longText('access_token')->nullable(); $table->timestamps(); }); } diff --git a/database/migrations/2021_07_02_065815_create_deployments_table.php b/database/migrations/2021_07_02_065815_create_deployments_table.php index 1e11391..e93c601 100755 --- a/database/migrations/2021_07_02_065815_create_deployments_table.php +++ b/database/migrations/2021_07_02_065815_create_deployments_table.php @@ -1,6 +1,5 @@ unsignedInteger('log_id')->nullable(); $table->json('commit_data')->nullable(); $table->string('commit_id')->nullable(); - $table->enum('status', DeploymentStatus::getValues()); + $table->string('status'); $table->timestamps(); }); } diff --git a/database/migrations/2021_07_03_133319_create_databases_table.php b/database/migrations/2021_07_03_133319_create_databases_table.php index 62f5a0d..0d9cebb 100755 --- a/database/migrations/2021_07_03_133319_create_databases_table.php +++ b/database/migrations/2021_07_03_133319_create_databases_table.php @@ -13,7 +13,7 @@ public function up(): void $table->id(); $table->unsignedBigInteger('server_id'); $table->string('name'); - $table->enum('status', DatabaseStatus::getValues())->default(DatabaseStatus::CREATING); + $table->string('status')->default(DatabaseStatus::CREATING); $table->timestamps(); }); } diff --git a/database/migrations/2021_07_03_133327_create_database_users_table.php b/database/migrations/2021_07_03_133327_create_database_users_table.php index c829f74..f67aa09 100755 --- a/database/migrations/2021_07_03_133327_create_database_users_table.php +++ b/database/migrations/2021_07_03_133327_create_database_users_table.php @@ -16,7 +16,7 @@ public function up(): void $table->longText('password')->nullable(); $table->json('databases')->nullable(); $table->string('host')->default('localhost'); - $table->enum('status', DatabaseUserStatus::getValues())->default(DatabaseUserStatus::CREATING); + $table->string('status')->default(DatabaseUserStatus::CREATING); $table->timestamps(); }); } diff --git a/database/migrations/2021_07_15_090830_create_firewall_rules_table.php b/database/migrations/2021_07_15_090830_create_firewall_rules_table.php index ac13e58..92020a8 100755 --- a/database/migrations/2021_07_15_090830_create_firewall_rules_table.php +++ b/database/migrations/2021_07_15_090830_create_firewall_rules_table.php @@ -15,7 +15,7 @@ public function up(): void $table->string('protocol'); $table->integer('port'); $table->ipAddress('source')->default('0.0.0.0'); - $table->tinyInteger('mask')->default(0); + $table->string('mask')->nullable(); $table->text('note')->nullable(); $table->string('status')->default('creating'); $table->timestamps(); diff --git a/database/migrations/2021_07_30_204454_create_cron_jobs_table.php b/database/migrations/2021_07_30_204454_create_cron_jobs_table.php index 106093b..c62848b 100755 --- a/database/migrations/2021_07_30_204454_create_cron_jobs_table.php +++ b/database/migrations/2021_07_30_204454_create_cron_jobs_table.php @@ -1,6 +1,5 @@ string('user'); $table->string('frequency'); $table->boolean('hidden')->default(0); - $table->enum('status', CronjobStatus::getValues()); + $table->string('status'); $table->timestamps(); }); } diff --git a/database/migrations/2021_08_14_165326_create_ssls_table.php b/database/migrations/2021_08_14_165326_create_ssls_table.php index 020e297..b85bcbf 100644 --- a/database/migrations/2021_08_14_165326_create_ssls_table.php +++ b/database/migrations/2021_08_14_165326_create_ssls_table.php @@ -1,6 +1,5 @@ longText('pk')->nullable(); $table->longText('ca')->nullable(); $table->timestamp('expires_at'); - $table->enum('status', SslStatus::getValues()); + $table->string('status'); $table->timestamps(); }); } diff --git a/database/migrations/2021_08_27_064512_create_queues_table.php b/database/migrations/2021_08_27_064512_create_queues_table.php index 9937464..23b9457 100644 --- a/database/migrations/2021_08_27_064512_create_queues_table.php +++ b/database/migrations/2021_08_27_064512_create_queues_table.php @@ -1,6 +1,5 @@ string('user'); $table->boolean('auto_start')->default(1); $table->boolean('auto_restart')->default(1); - $table->tinyInteger('numprocs')->default(8); + $table->integer('numprocs')->default(8); $table->boolean('redirect_stderr')->default(1); $table->string('stdout_logfile')->nullable(); - $table->enum('status', QueueStatus::getValues()); + $table->string('status'); $table->timestamps(); }); } diff --git a/database/migrations/2021_08_30_174511_create_server_ssh_keys_table.php b/database/migrations/2021_08_30_174511_create_server_ssh_keys_table.php index 6c91a28..b093179 100644 --- a/database/migrations/2021_08_30_174511_create_server_ssh_keys_table.php +++ b/database/migrations/2021_08_30_174511_create_server_ssh_keys_table.php @@ -1,6 +1,5 @@ id(); $table->unsignedBigInteger('server_id'); $table->unsignedBigInteger('ssh_key_id'); - $table->enum('status', SshKeyStatus::getValues()); + $table->string('status'); $table->timestamps(); }); } diff --git a/database/migrations/2023_07_21_210213_update_firewall_rules_table.php b/database/migrations/2023_07_21_210213_update_firewall_rules_table.php index 5ac104a..2e31c43 100644 --- a/database/migrations/2023_07_21_210213_update_firewall_rules_table.php +++ b/database/migrations/2023_07_21_210213_update_firewall_rules_table.php @@ -7,7 +7,9 @@ { public function up(): void { - DB::statement('ALTER TABLE firewall_rules MODIFY mask varchar(10) null'); + if (DB::getDriverName() === 'mysql') { + DB::statement('ALTER TABLE firewall_rules MODIFY mask varchar(10) null'); + } } public function down(): void diff --git a/database/migrations/2023_08_13_095440_update_storage_providers_table.php b/database/migrations/2023_08_13_095440_update_storage_providers_table.php index 93deebb..200140e 100644 --- a/database/migrations/2023_08_13_095440_update_storage_providers_table.php +++ b/database/migrations/2023_08_13_095440_update_storage_providers_table.php @@ -23,10 +23,10 @@ public function up(): void public function down(): void { Schema::table('storage_providers', function (Blueprint $table) { - $table->string('token'); - $table->string('refresh_token'); - $table->string('token_expires_at'); - $table->string('label'); + $table->string('token')->nullable(); + $table->string('refresh_token')->nullable(); + $table->string('token_expires_at')->nullable(); + $table->string('label')->nullable(); $table->dropColumn('user_id'); $table->dropColumn('profile'); $table->dropColumn('credentials'); diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 134354b..a8ffd52 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -45,6 +45,12 @@ public function run(): void 'version' => 'latest', 'status' => ServiceStatus::READY, ]); + $server->services()->create([ + 'type' => 'firewall', + 'name' => 'ufw', + 'version' => 'latest', + 'status' => ServiceStatus::READY, + ]); Site::factory()->create([ 'server_id' => $server->id, 'type' => SiteType::LARAVEL, diff --git a/docker-compose.yml b/docker-compose.yml index b6b06e0..cbe46ae 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,8 +1,7 @@ -version: '3' services: - vito: + app: build: - context: ./docker/8.1 + context: ./vendor/laravel/sail/runtimes/8.2 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' @@ -17,63 +16,33 @@ services: LARAVEL_SAIL: 1 XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' + IGNITION_LOCAL_SITES_PATH: '${PWD}' volumes: - '.:/var/www/html' networks: - - sail - depends_on: - - mysql - - redis + - vito + depends_on: { } worker: build: - context: ./docker/8.1 + context: ./vendor/laravel/sail/runtimes/8.2 dockerfile: Dockerfile args: WWWGROUP: '${WWWGROUP}' image: vito/app - command: php artisan queue:listen --timeout=600 --queue=default,ssh,ssh-long - extra_hosts: - - 'host.docker.internal:host-gateway' environment: WWWUSER: '${WWWUSER}' LARAVEL_SAIL: 1 XDEBUG_MODE: '${SAIL_XDEBUG_MODE:-off}' XDEBUG_CONFIG: '${SAIL_XDEBUG_CONFIG:-client_host=host.docker.internal}' + IGNITION_LOCAL_SITES_PATH: '${PWD}' + command: 'php artisan queue:listen --sleep=3 --tries=1 --timeout=0' + restart: unless-stopped volumes: - '.:/var/www/html' networks: - - sail - restart: unless-stopped + - vito depends_on: - - mysql - - redis - mysql: - image: 'mysql/mysql-server:8.0' - ports: - - '${FORWARD_DB_PORT:-3306}:3306' - environment: - MYSQL_ROOT_PASSWORD: '${DB_PASSWORD}' - MYSQL_ROOT_HOST: "%" - MYSQL_DATABASE: '${DB_DATABASE}' - MYSQL_USER: '${DB_USERNAME}' - MYSQL_PASSWORD: '${DB_PASSWORD}' - MYSQL_ALLOW_EMPTY_PASSWORD: 1 - volumes: - - 'sail-mysql:/var/lib/mysql' - - './vendor/laravel/sail/database/mysql/create-testing-database.sh:/docker-entrypoint-initdb.d/10-create-testing-database.sh' - networks: - - sail - healthcheck: - test: [ "CMD", "mysqladmin", "ping", "-p${DB_PASSWORD}" ] - retries: 3 - timeout: 5s - redis: - image: redis:latest - networks: - - sail + - app networks: - sail: + vito: driver: bridge -volumes: - sail-mysql: - driver: local diff --git a/docker/8.1/Dockerfile b/docker/8.1/Dockerfile deleted file mode 100644 index 725c9d9..0000000 --- a/docker/8.1/Dockerfile +++ /dev/null @@ -1,61 +0,0 @@ -FROM ubuntu:22.04 - -LABEL maintainer="Taylor Otwell" - -ARG WWWGROUP -ARG NODE_VERSION=18 -ARG POSTGRES_VERSION=15 - -WORKDIR /var/www/html - -ENV DEBIAN_FRONTEND noninteractive -ENV TZ=UTC - -RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone - -RUN apt-get update \ - && mkdir -p /etc/apt/keyrings \ - && apt-get install -y gnupg gosu curl ca-certificates zip unzip git supervisor sqlite3 libcap2-bin libpng-dev python2 dnsutils librsvg2-bin fswatch \ - && curl -sS 'https://keyserver.ubuntu.com/pks/lookup?op=get&search=0x14aa40ec0831756756d7f66c4f4ea0aae5267a6c' | gpg --dearmor | tee /usr/share/keyrings/ppa_ondrej_php.gpg > /dev/null \ - && echo "deb [signed-by=/usr/share/keyrings/ppa_ondrej_php.gpg] https://ppa.launchpadcontent.net/ondrej/php/ubuntu jammy main" > /etc/apt/sources.list.d/ppa_ondrej_php.list \ - && apt-get update \ - && apt-get install -y php8.1-cli php8.1-dev \ - php8.1-pgsql php8.1-sqlite3 php8.1-gd php8.1-imagick \ - php8.1-curl \ - php8.1-imap php8.1-mysql php8.1-mbstring php8.1-ssh2\ - php8.1-xml php8.1-zip php8.1-bcmath php8.1-soap \ - php8.1-intl php8.1-readline \ - php8.1-ldap \ - php8.1-msgpack php8.1-igbinary php8.1-redis php8.1-swoole \ - php8.1-memcached php8.1-pcov php8.1-xdebug \ - && curl -sLS https://getcomposer.org/installer | php -- --install-dir=/usr/bin/ --filename=composer \ - && curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg \ - && echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" > /etc/apt/sources.list.d/nodesource.list \ - && apt-get update \ - && apt-get install -y nodejs \ - && npm install -g npm \ - && curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor | tee /usr/share/keyrings/yarn.gpg >/dev/null \ - && echo "deb [signed-by=/usr/share/keyrings/yarn.gpg] https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list \ - && curl -sS https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /usr/share/keyrings/pgdg.gpg >/dev/null \ - && echo "deb [signed-by=/usr/share/keyrings/pgdg.gpg] http://apt.postgresql.org/pub/repos/apt jammy-pgdg main" > /etc/apt/sources.list.d/pgdg.list \ - && apt-get update \ - && apt-get install -y yarn \ - && apt-get install -y mysql-client \ - && apt-get install -y postgresql-client-$POSTGRES_VERSION \ - && apt-get -y autoremove \ - && apt-get clean \ - && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* - -RUN setcap "cap_net_bind_service=+ep" /usr/bin/php8.1 - -RUN groupadd --force -g $WWWGROUP sail -RUN useradd -ms /bin/bash --no-user-group -g $WWWGROUP -u 1337 sail - -COPY start-container /usr/local/bin/start-container -COPY supervisord.conf /etc/supervisor/conf.d/supervisord.conf -COPY php.ini /etc/php/8.1/cli/conf.d/99-sail.ini -RUN chmod +x /usr/local/bin/start-container - -EXPOSE 8000 - -ENTRYPOINT ["start-container"] diff --git a/docker/8.1/php.ini b/docker/8.1/php.ini deleted file mode 100644 index 66d04d5..0000000 --- a/docker/8.1/php.ini +++ /dev/null @@ -1,4 +0,0 @@ -[PHP] -post_max_size = 100M -upload_max_filesize = 100M -variables_order = EGPCS diff --git a/docker/8.1/start-container b/docker/8.1/start-container deleted file mode 100644 index b864399..0000000 --- a/docker/8.1/start-container +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash - -if [ ! -z "$WWWUSER" ]; then - usermod -u $WWWUSER sail -fi - -if [ ! -d /.composer ]; then - mkdir /.composer -fi - -chmod -R ugo+rw /.composer - -if [ $# -gt 0 ]; then - exec gosu $WWWUSER "$@" -else - exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf -fi diff --git a/docker/8.1/supervisord.conf b/docker/8.1/supervisord.conf deleted file mode 100644 index 9d28479..0000000 --- a/docker/8.1/supervisord.conf +++ /dev/null @@ -1,14 +0,0 @@ -[supervisord] -nodaemon=true -user=root -logfile=/var/log/supervisor/supervisord.log -pidfile=/var/run/supervisord.pid - -[program:php] -command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80 -user=sail -environment=LARAVEL_SAIL="1" -stdout_logfile=/dev/stdout -stdout_logfile_maxbytes=0 -stderr_logfile=/dev/stderr -stderr_logfile_maxbytes=0 diff --git a/Dockerfile b/docker/Dockerfile similarity index 55% rename from Dockerfile rename to docker/Dockerfile index ec01afe..b17f41a 100644 --- a/Dockerfile +++ b/docker/Dockerfile @@ -4,9 +4,6 @@ WORKDIR /var/www/html ENV DEBIAN_FRONTEND noninteractive -RUN echo "mysql-server mysql-server/root_password password password" | debconf-set-selections -RUN echo "mysql-server mysql-server/root_password_again password password" | debconf-set-selections - # upgrade RUN apt clean && apt update && apt update && apt upgrade -y && apt autoremove -y @@ -22,23 +19,13 @@ RUN apt update \ python2 dnsutils librsvg2-bin fswatch wget \ && add-apt-repository ppa:ondrej/php -y \ && apt update \ - && apt install -y php8.1 php8.1-fpm php8.1-mbstring php8.1-mysql php8.1-mcrypt php8.1-gd php8.1-xml \ - php8.1-curl php8.1-gettext php8.1-zip php8.1-bcmath php8.1-soap php8.1-redis -COPY docker/standalone/php.ini /etc/php/8.1/cli/conf.d/99-vito.ini + && apt install -y php8.2 php8.2-fpm php8.2-mbstring php8.2-mcrypt php8.2-gd php8.2-xml \ + php8.2-curl php8.2-gettext php8.2-zip php8.2-bcmath php8.2-soap php8.2-redis php8.2-sqlite3 +COPY docker/php.ini /etc/php/8.2/cli/conf.d/99-vito.ini # composer RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer -# mysql -RUN wget -c https://dev.mysql.com/get/mysql-apt-config_0.8.22-1_all.deb \ - && mkdir -p /etc/apt/keyrings \ - && apt clean \ - && apt update \ - && dpkg -i mysql-apt-config_0.8.22-1_all.deb \ - && apt install mysql-server -y - -RUN service mysql stop - # app COPY . /var/www/html RUN rm -rf /var/www/html/vendor @@ -50,16 +37,16 @@ RUN chown -R www-data:www-data /var/www/html \ # webserver RUN rm /etc/nginx/sites-available/default RUN rm /etc/nginx/sites-enabled/default -COPY docker/standalone/nginx.conf /etc/nginx/sites-available/default +COPY docker/nginx.conf /etc/nginx/sites-available/default RUN ln -s /etc/nginx/sites-available/default /etc/nginx/sites-enabled/default # supervisord -COPY docker/standalone/supervisord.conf /etc/supervisor/conf.d/supervisord.conf +COPY docker/supervisord.conf /etc/supervisor/conf.d/supervisord.conf # start -COPY docker/standalone/start.sh /start.sh +COPY docker/start.sh /start.sh RUN chmod +x /start.sh -EXPOSE 80 3306 +EXPOSE 80 CMD ["/start.sh"] diff --git a/docker/standalone/nginx.conf b/docker/nginx.conf similarity index 90% rename from docker/standalone/nginx.conf rename to docker/nginx.conf index 1633f13..3a8e20c 100644 --- a/docker/standalone/nginx.conf +++ b/docker/nginx.conf @@ -17,7 +17,7 @@ server { location ~ \.php$ { include snippets/fastcgi-php.conf; - fastcgi_pass unix:/var/run/php/php8.1-fpm.sock; + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } diff --git a/docker/standalone/php.ini b/docker/php.ini similarity index 100% rename from docker/standalone/php.ini rename to docker/php.ini diff --git a/docker/standalone/start.sh b/docker/start.sh similarity index 50% rename from docker/standalone/start.sh rename to docker/start.sh index 9c9ee93..23299bb 100644 --- a/docker/standalone/start.sh +++ b/docker/start.sh @@ -1,39 +1,29 @@ #!/bin/bash INIT_FLAG="/var/www/html/storage/.INIT_ENV" -DB_PASSWORD=${DB_PASSWORD:-"password"} NAME=${NAME:-"vito"} -EMAIL=${EMAIL:-"vito@example.com"} +EMAIL=${EMAIL:-"vito@vitodeploy.com"} PASSWORD=${PASSWORD:-"password"} -# Check if the flag file does not exist, indicating a first run +# check if the flag file does not exist, indicating a first run if [ ! -f "$INIT_FLAG" ]; then - echo "First run of the container. Initializing MySQL..." + echo "Initializing..." - # Start MySQL temporarily - service mysql start - - # Wait for MySQL to start up completely (may need to adjust the sleep duration) - sleep 3 - - # Create Database - mysql -u root -p`echo password` -e "CREATE DATABASE IF NOT EXISTS vito CHARACTER SET utf8 COLLATE utf8_general_ci;" - - # Change Password - mysql -u root -p`echo password` -e "ALTER USER 'root'@'localhost' IDENTIFIED BY '$DB_PASSWORD'; FLUSH PRIVILEGES;" - - # Generate SSH keys + # generate SSH keys openssl genpkey -algorithm RSA -out /var/www/html/storage/ssh-private.pem chmod 600 /var/www/html/storage/ssh-private.pem ssh-keygen -y -f /var/www/html/storage/ssh-private.pem > /var/www/html/storage/ssh-public.key - # Create the flag file to indicate completion of initialization tasks + # create sqlite database + touch /var/www/html/storage/database.sqlite + + # create the flag file to indicate completion of initialization tasks touch "$INIT_FLAG" fi -service mysql start - -service php8.1-fpm start +chown -R www-data:www-data /var/www/html \ + && chmod -R 755 /var/www/html/storage /var/www/html/bootstrap/cache +service php8.2-fpm start service nginx start @@ -44,7 +34,6 @@ php /var/www/html/artisan route:clear php /var/www/html/artisan route:cache php /var/www/html/artisan view:clear php /var/www/html/artisan view:cache -php /var/www/html/artisan icons:cache php /var/www/html/artisan user:create "$NAME" "$EMAIL" "$PASSWORD" diff --git a/docker/standalone/supervisord.conf b/docker/supervisord.conf similarity index 100% rename from docker/standalone/supervisord.conf rename to docker/supervisord.conf diff --git a/package-lock.json b/package-lock.json index 50f137b..372fb4a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5,17 +5,20 @@ "packages": { "": { "devDependencies": { - "@ryangjchandler/alpine-clipboard": "^2.2.0", "@tailwindcss/forms": "^0.5.2", "@tailwindcss/typography": "^0.5.9", "alpinejs": "^3.4.2", "autoprefixer": "^10.4.2", - "axios": "^1.6.0", + "flowbite": "^2.3.0", + "htmx.org": "^1.9.10", "laravel-echo": "^1.15.0", "laravel-vite-plugin": "^0.7.2", "postcss": "^8.4.31", - "pusher-js": "^4.3.1", + "prettier": "^3.2.5", + "prettier-plugin-blade": "^2.1.6", + "prettier-plugin-tailwindcss": "^0.5.11", "tailwindcss": "^3.1.0", + "tippy.js": "^6.3.7", "toastr": "^2.1.4", "vite": "^4.5.2" } @@ -461,11 +464,15 @@ "node": ">= 8" } }, - "node_modules/@ryangjchandler/alpine-clipboard": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ryangjchandler/alpine-clipboard/-/alpine-clipboard-2.2.0.tgz", - "integrity": "sha512-2kKHd2mA6K7RuYlC+1fikIUPVJeJLQlY2w9rNGrOgVfzXUZRotjTP+EjxouDizTEvqNRkVTJnmmNle32Uhb4zw==", - "dev": true + "node_modules/@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", + "dev": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } }, "node_modules/@tailwindcss/forms": { "version": "0.5.3", @@ -556,12 +563,6 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "node_modules/autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -595,17 +596,6 @@ "postcss": "^8.1.0" } }, - "node_modules/axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", - "dev": true, - "dependencies": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -745,18 +735,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -784,15 +762,6 @@ "node": ">=4" } }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -894,18 +863,6 @@ "reusify": "^1.0.4" } }, - "node_modules/faye-websocket": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.9.4.tgz", - "integrity": "sha512-5i9HTcrJrCCcmztcjIHpt+eWwu89Ed66rNBf5LtTy69QHbP/mtvjOTGqiOR01QrSbfoT1EP0v4XXvydpJj0r9w==", - "dev": true, - "dependencies": { - "websocket-driver": ">=0.5.1" - }, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -918,38 +875,14 @@ "node": ">=8" } }, - "node_modules/follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "node_modules/flowbite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.3.0.tgz", + "integrity": "sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==", "dev": true, "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" } }, "node_modules/fraction.js": { @@ -1035,10 +968,10 @@ "node": ">= 0.4.0" } }, - "node_modules/http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "node_modules/htmx.org": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.10.tgz", + "integrity": "sha512-UgchasltTCrTuU2DQLom3ohHrBvwr7OqpwyAVJ9VxtNBng4XKkVsqrv0Qr3srqvM9ZNI3f1MmvVQQqK7KW/bTA==", "dev": true }, "node_modules/inflight": { @@ -1206,27 +1139,6 @@ "node": ">=8.6" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -1510,22 +1422,100 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/pusher-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-4.4.0.tgz", - "integrity": "sha512-oxSEG764hqeGAqW9Ryq5KdGQrbM/2sBy5L6Jsh62GyRbRO4z0qI9EjQ6IfQSDhR59b/tY0ANuXD8+ZOZY9AOyg==", + "node_modules/prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true, - "dependencies": { - "faye-websocket": "0.9.4", - "tweetnacl": "^1.0.0", - "tweetnacl-util": "^0.15.0", - "xmlhttprequest": "^1.8.0" + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/prettier-plugin-blade": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-blade/-/prettier-plugin-blade-2.1.6.tgz", + "integrity": "sha512-zuLan1zhaDcX1NVBo71KKW5QY59+3dLWRGapVOyVq7WrRUyaUR5jkCTRlcAky9GlMw+f+ryQMYy4jIo3HOzGxQ==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "prettier": ">=3" + } + }, + "node_modules/prettier-plugin-tailwindcss": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.11.tgz", + "integrity": "sha512-AvI/DNyMctyyxGOjyePgi/gqj5hJYClZ1avtQvLlqMT3uDZkRbi4HhGUpok3DRzv9z7Lti85Kdj3s3/1CeNI0w==", + "dev": true, + "engines": { + "node": ">=14.21.3" + }, + "peerDependencies": { + "@ianvs/prettier-plugin-sort-imports": "*", + "@prettier/plugin-pug": "*", + "@shopify/prettier-plugin-liquid": "*", + "@trivago/prettier-plugin-sort-imports": "*", + "prettier": "^3.0", + "prettier-plugin-astro": "*", + "prettier-plugin-css-order": "*", + "prettier-plugin-import-sort": "*", + "prettier-plugin-jsdoc": "*", + "prettier-plugin-marko": "*", + "prettier-plugin-organize-attributes": "*", + "prettier-plugin-organize-imports": "*", + "prettier-plugin-style-order": "*", + "prettier-plugin-svelte": "*" + }, + "peerDependenciesMeta": { + "@ianvs/prettier-plugin-sort-imports": { + "optional": true + }, + "@prettier/plugin-pug": { + "optional": true + }, + "@shopify/prettier-plugin-liquid": { + "optional": true + }, + "@trivago/prettier-plugin-sort-imports": { + "optional": true + }, + "prettier-plugin-astro": { + "optional": true + }, + "prettier-plugin-css-order": { + "optional": true + }, + "prettier-plugin-import-sort": { + "optional": true + }, + "prettier-plugin-jsdoc": { + "optional": true + }, + "prettier-plugin-marko": { + "optional": true + }, + "prettier-plugin-organize-attributes": { + "optional": true + }, + "prettier-plugin-organize-imports": { + "optional": true + }, + "prettier-plugin-style-order": { + "optional": true + }, + "prettier-plugin-svelte": { + "optional": true + }, + "prettier-plugin-twig-melody": { + "optional": true + } } }, "node_modules/queue-microtask": { @@ -1647,26 +1637,6 @@ "queue-microtask": "^1.2.2" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -1773,6 +1743,15 @@ "node": ">=0.8" } }, + "node_modules/tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dev": true, + "dependencies": { + "@popperjs/core": "^2.9.0" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -1800,18 +1779,6 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "node_modules/tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "node_modules/tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, "node_modules/update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -1912,44 +1879,12 @@ "vite": "^2 || ^3 || ^4" } }, - "node_modules/websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "dependencies": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - }, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "node_modules/xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", @@ -2188,10 +2123,10 @@ "fastq": "^1.6.0" } }, - "@ryangjchandler/alpine-clipboard": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@ryangjchandler/alpine-clipboard/-/alpine-clipboard-2.2.0.tgz", - "integrity": "sha512-2kKHd2mA6K7RuYlC+1fikIUPVJeJLQlY2w9rNGrOgVfzXUZRotjTP+EjxouDizTEvqNRkVTJnmmNle32Uhb4zw==", + "@popperjs/core": { + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", + "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "dev": true }, "@tailwindcss/forms": { @@ -2273,12 +2208,6 @@ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", "dev": true }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, "autoprefixer": { "version": "10.4.14", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", @@ -2293,17 +2222,6 @@ "postcss-value-parser": "^4.2.0" } }, - "axios": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.0.tgz", - "integrity": "sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg==", - "dev": true, - "requires": { - "follow-redirects": "^1.15.0", - "form-data": "^4.0.0", - "proxy-from-env": "^1.1.0" - } - }, "balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2392,15 +2310,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "dev": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, "commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -2419,12 +2328,6 @@ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", "dev": true }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true - }, "didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2512,15 +2415,6 @@ "reusify": "^1.0.4" } }, - "faye-websocket": { - "version": "0.9.4", - "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.9.4.tgz", - "integrity": "sha512-5i9HTcrJrCCcmztcjIHpt+eWwu89Ed66rNBf5LtTy69QHbP/mtvjOTGqiOR01QrSbfoT1EP0v4XXvydpJj0r9w==", - "dev": true, - "requires": { - "websocket-driver": ">=0.5.1" - } - }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -2530,21 +2424,14 @@ "to-regex-range": "^5.0.1" } }, - "follow-redirects": { - "version": "1.15.5", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz", - "integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==", - "dev": true - }, - "form-data": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", - "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "flowbite": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/flowbite/-/flowbite-2.3.0.tgz", + "integrity": "sha512-pm3JRo8OIJHGfFYWgaGpPv8E+UdWy0Z3gEAGufw+G/1dusaU/P1zoBLiQpf2/+bYAi+GBQtPVG86KYlV0W+AFQ==", "dev": true, "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "mime-types": "^2.1.12" + "@popperjs/core": "^2.9.3", + "mini-svg-data-uri": "^1.4.3" } }, "fraction.js": { @@ -2604,10 +2491,10 @@ "function-bind": "^1.1.1" } }, - "http-parser-js": { - "version": "0.5.8", - "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", - "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==", + "htmx.org": { + "version": "1.9.10", + "resolved": "https://registry.npmjs.org/htmx.org/-/htmx.org-1.9.10.tgz", + "integrity": "sha512-UgchasltTCrTuU2DQLom3ohHrBvwr7OqpwyAVJ9VxtNBng4XKkVsqrv0Qr3srqvM9ZNI3f1MmvVQQqK7KW/bTA==", "dev": true }, "inflight": { @@ -2739,21 +2626,6 @@ "picomatch": "^2.3.1" } }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true - }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, - "requires": { - "mime-db": "1.52.0" - } - }, "mini-svg-data-uri": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/mini-svg-data-uri/-/mini-svg-data-uri-1.4.4.tgz", @@ -2927,23 +2799,25 @@ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", "dev": true }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "prettier": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz", + "integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==", "dev": true }, - "pusher-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/pusher-js/-/pusher-js-4.4.0.tgz", - "integrity": "sha512-oxSEG764hqeGAqW9Ryq5KdGQrbM/2sBy5L6Jsh62GyRbRO4z0qI9EjQ6IfQSDhR59b/tY0ANuXD8+ZOZY9AOyg==", + "prettier-plugin-blade": { + "version": "2.1.6", + "resolved": "https://registry.npmjs.org/prettier-plugin-blade/-/prettier-plugin-blade-2.1.6.tgz", + "integrity": "sha512-zuLan1zhaDcX1NVBo71KKW5QY59+3dLWRGapVOyVq7WrRUyaUR5jkCTRlcAky9GlMw+f+ryQMYy4jIo3HOzGxQ==", "dev": true, - "requires": { - "faye-websocket": "0.9.4", - "tweetnacl": "^1.0.0", - "tweetnacl-util": "^0.15.0", - "xmlhttprequest": "^1.8.0" - } + "requires": {} + }, + "prettier-plugin-tailwindcss": { + "version": "0.5.11", + "resolved": "https://registry.npmjs.org/prettier-plugin-tailwindcss/-/prettier-plugin-tailwindcss-0.5.11.tgz", + "integrity": "sha512-AvI/DNyMctyyxGOjyePgi/gqj5hJYClZ1avtQvLlqMT3uDZkRbi4HhGUpok3DRzv9z7Lti85Kdj3s3/1CeNI0w==", + "dev": true, + "requires": {} }, "queue-microtask": { "version": "1.2.3", @@ -3010,12 +2884,6 @@ "queue-microtask": "^1.2.2" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true - }, "source-map-js": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", @@ -3093,6 +2961,15 @@ "thenify": ">= 3.1.0 < 4" } }, + "tippy.js": { + "version": "6.3.7", + "resolved": "https://registry.npmjs.org/tippy.js/-/tippy.js-6.3.7.tgz", + "integrity": "sha512-E1d3oP2emgJ9dRQZdf3Kkn0qJgI6ZLpyS5z6ZkY1DF3kaQaBsGZsndEpHwx+eC+tYM41HaSNvNtLx8tU57FzTQ==", + "dev": true, + "requires": { + "@popperjs/core": "^2.9.0" + } + }, "to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -3117,18 +2994,6 @@ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", "dev": true }, - "tweetnacl": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", - "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", - "dev": true - }, - "tweetnacl-util": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz", - "integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw==", - "dev": true - }, "update-browserslist-db": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", @@ -3167,35 +3032,12 @@ "picomatch": "^2.3.1" } }, - "websocket-driver": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", - "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", - "dev": true, - "requires": { - "http-parser-js": ">=0.5.1", - "safe-buffer": ">=5.1.0", - "websocket-extensions": ">=0.1.1" - } - }, - "websocket-extensions": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", - "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", - "dev": true - }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "dev": true }, - "xmlhttprequest": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz", - "integrity": "sha512-58Im/U0mlVBLM38NdZjHyhuMtCqa61469k2YP/AaPbvCoV9aQGUpbJBj1QRm2ytRiVQBD/fsw7L2bJGDVQswBA==", - "dev": true - }, "yaml": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", diff --git a/package.json b/package.json index 3a31d36..5b197f2 100644 --- a/package.json +++ b/package.json @@ -2,20 +2,25 @@ "private": true, "scripts": { "dev": "vite", - "build": "vite build" + "build": "vite build", + "lint": "prettier --check ./resources/views", + "lint:fix": "prettier --write ./resources/views" }, "devDependencies": { - "@ryangjchandler/alpine-clipboard": "^2.2.0", "@tailwindcss/forms": "^0.5.2", "@tailwindcss/typography": "^0.5.9", "alpinejs": "^3.4.2", "autoprefixer": "^10.4.2", - "axios": "^1.6.0", + "flowbite": "^2.3.0", + "htmx.org": "^1.9.10", "laravel-echo": "^1.15.0", "laravel-vite-plugin": "^0.7.2", "postcss": "^8.4.31", - "pusher-js": "^4.3.1", + "prettier": "^3.2.5", + "prettier-plugin-blade": "^2.1.6", + "prettier-plugin-tailwindcss": "^0.5.11", "tailwindcss": "^3.1.0", + "tippy.js": "^6.3.7", "toastr": "^2.1.4", "vite": "^4.5.2" } diff --git a/phpunit.xml b/phpunit.xml index 66fee18..1e628e6 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -11,12 +11,12 @@ + + - - - + diff --git a/public/build/assets/app-887de6f7.css b/public/build/assets/app-887de6f7.css deleted file mode 100644 index b878ca9..0000000 --- a/public/build/assets/app-887de6f7.css +++ /dev/null @@ -1 +0,0 @@ -.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#fff;-webkit-text-shadow:0 1px 0 #ffffff;text-shadow:0 1px 0 #ffffff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{box-sizing:border-box}#toast-container>div{border-radius:.5rem;position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;background-position:15px center;background-repeat:no-repeat;color:#fff}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{cursor:pointer;--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-top-center>div,#toast-container.toast-bottom-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-top-full-width>div,#toast-container.toast-bottom-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}.toast-error{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.toast-info{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.toast-warning{--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width: 240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width: 241px) and (max-width: 480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width: 481px) and (max-width: 768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}/*! tailwindcss v3.3.1 | MIT License | https://tailwindcss.com*/*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e2e8f0}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Figtree,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#94a3b8}input::placeholder,textarea::placeholder{opacity:1;color:#94a3b8}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#64748b;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#2563eb}input::-moz-placeholder,textarea::-moz-placeholder{color:#64748b;opacity:1}input::placeholder,textarea::placeholder{color:#64748b;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2364748b' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#2563eb;background-color:#fff;border-color:#64748b;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #2563eb;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(59 130 246 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-y-0{top:0;bottom:0}.left-0{left:0}.right-0{right:0}.top-0{top:0}.top-1{top:.25rem}.z-0{z-index:0}.z-10{z-index:10}.z-50{z-index:50}.float-right{float:right}.mx-5{margin-left:1.25rem;margin-right:1.25rem}.mx-auto{margin-left:auto;margin-right:auto}.-ml-px{margin-left:-1px}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-20{height:5rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-96{height:24rem}.max-h-\[350px\]{max-height:350px}.min-h-screen{min-height:100vh}.w-1{width:.25rem}.w-10{width:2.5rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-full{min-width:100%}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.flex-1{flex:1 1 0%}.flex-none{flex:none}.flex-grow{flex-grow:1}.origin-top{transform-origin:top}.origin-top-left{transform-origin:top left}.origin-top-right{transform-origin:top right}.translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-4{--tw-translate-y: 1rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-items-center{justify-items:center}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-5{gap:1.25rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-10>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2.5rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-b-md{border-bottom-right-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-l-md{border-top-left-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.rounded-tr-md{border-top-right-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.border-primary-200{--tw-border-opacity: 1;border-color:rgb(199 210 254 / var(--tw-border-opacity))}.border-primary-400{--tw-border-opacity: 1;border-color:rgb(129 140 248 / var(--tw-border-opacity))}.border-primary-600{--tw-border-opacity: 1;border-color:rgb(79 70 229 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-yellow-500{--tw-border-opacity: 1;border-color:rgb(234 179 8 / var(--tw-border-opacity))}.border-t-transparent{border-top-color:transparent}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(240 253 244 / var(--tw-bg-opacity))}.bg-primary-200{--tw-bg-opacity: 1;background-color:rgb(199 210 254 / var(--tw-bg-opacity))}.bg-primary-50{--tw-bg-opacity: 1;background-color:rgb(238 242 255 / var(--tw-bg-opacity))}.bg-primary-500{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.bg-primary-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(254 242 242 / var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(254 249 195 / var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity: 1;background-color:rgb(254 252 232 / var(--tw-bg-opacity))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-3{padding:.75rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-7{padding:1.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pl-3{padding-left:.75rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-1{padding-top:.25rem}.pt-3{padding-top:.75rem}.pt-6{padding-top:1.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-sans{font-family:Figtree,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-gray-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(34 197 94 / var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgb(22 163 74 / var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity: 1;color:rgb(79 70 229 / var(--tw-text-opacity))}.text-primary-500{--tw-text-opacity: 1;color:rgb(99 102 241 / var(--tw-text-opacity))}.text-primary-600{--tw-text-opacity: 1;color:rgb(79 70 229 / var(--tw-text-opacity))}.text-primary-700{--tw-text-opacity: 1;color:rgb(67 56 202 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(239 68 68 / var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgb(220 38 38 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity))}.text-yellow-700{--tw-text-opacity: 1;color:rgb(161 98 7 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline-0{outline-width:0px}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity))}.ring-gray-300{--tw-ring-opacity: 1;--tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}[x-cloak]{display:none!important}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.hover\:bg-gray-900:hover{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.hover\:bg-primary-600:hover{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.hover\:bg-primary-700:hover{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity))}.hover\:bg-red-500:hover{--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:opacity-50:hover{opacity:.5}.focus\:z-10:focus{z-index:10}.focus\:border-blue-300:focus{--tw-border-opacity: 1;border-color:rgb(147 197 253 / var(--tw-border-opacity))}.focus\:border-gray-300:focus{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.focus\:border-primary-300:focus{--tw-border-opacity: 1;border-color:rgb(165 180 252 / var(--tw-border-opacity))}.focus\:border-primary-500:focus{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.focus\:border-primary-700:focus{--tw-border-opacity: 1;border-color:rgb(67 56 202 / var(--tw-border-opacity))}.focus\:border-red-700:focus{--tw-border-opacity: 1;border-color:rgb(185 28 28 / var(--tw-border-opacity))}.focus\:bg-gray-100:focus{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.focus\:bg-gray-50:focus{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.focus\:bg-primary-100:focus{--tw-bg-opacity: 1;background-color:rgb(224 231 255 / var(--tw-bg-opacity))}.focus\:text-gray-700:focus{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.focus\:text-gray-800:focus{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.focus\:text-primary-800:focus{--tw-text-opacity: 1;color:rgb(55 48 163 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-gray-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(148 163 184 / var(--tw-ring-opacity))}.focus\:ring-gray-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity))}.focus\:ring-gray-700:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(51 65 85 / var(--tw-ring-opacity))}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}.focus\:ring-primary-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity))}.focus\:ring-primary-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}.focus\:ring-red-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(254 202 202 / var(--tw-ring-opacity))}.focus\:ring-opacity-50:focus{--tw-ring-opacity: .5}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.active\:bg-gray-100:active{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.active\:bg-primary-700:active{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity))}.active\:bg-red-600:active{--tw-bg-opacity: 1;background-color:rgb(220 38 38 / var(--tw-bg-opacity))}.active\:text-gray-500:active{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.active\:text-gray-700:active{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.disabled\:opacity-25:disabled{opacity:.25}:is(.dark .dark\:border-r-2){border-right-width:2px}:is(.dark .dark\:border-gray-500){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-700){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-800){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-900){--tw-border-opacity: 1;border-color:rgb(15 23 42 / var(--tw-border-opacity))}:is(.dark .dark\:border-primary-600){--tw-border-opacity: 1;border-color:rgb(79 70 229 / var(--tw-border-opacity))}:is(.dark .dark\:border-t-transparent){border-top-color:transparent}:is(.dark .dark\:border-opacity-20){--tw-border-opacity: .2}:is(.dark .dark\:bg-gray-500){--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-700){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800\/50){background-color:#1e293b80}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-green-500){--tw-bg-opacity: 1;background-color:rgb(34 197 94 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-primary-500){--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-primary-900\/50){background-color:#312e8180}:is(.dark .dark\:bg-red-500){--tw-bg-opacity: 1;background-color:rgb(239 68 68 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-yellow-500){--tw-bg-opacity: 1;background-color:rgb(234 179 8 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-opacity-10){--tw-bg-opacity: .1}:is(.dark .dark\:bg-opacity-20){--tw-bg-opacity: .2}:is(.dark .dark\:bg-opacity-30){--tw-bg-opacity: .3}:is(.dark .dark\:bg-opacity-70){--tw-bg-opacity: .7}:is(.dark .dark\:text-gray-100){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-200){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-300){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}:is(.dark .dark\:text-green-400){--tw-text-opacity: 1;color:rgb(74 222 128 / var(--tw-text-opacity))}:is(.dark .dark\:text-primary-300){--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity: 1;color:rgb(248 113 113 / var(--tw-text-opacity))}:is(.dark .dark\:text-white){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-500){--tw-text-opacity: 1;color:rgb(234 179 8 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:border-gray-600:hover){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:border-gray-700:hover){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:bg-gray-700:hover){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-800:hover){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:text-gray-100:hover){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-200:hover){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-300:hover){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:border-gray-600:focus){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-gray-700:focus){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-300:focus){--tw-border-opacity: 1;border-color:rgb(165 180 252 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-600:focus){--tw-border-opacity: 1;border-color:rgb(79 70 229 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-700:focus){--tw-border-opacity: 1;border-color:rgb(67 56 202 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:bg-gray-700:focus){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:focus\:bg-primary-900:focus){--tw-bg-opacity: 1;background-color:rgb(49 46 129 / var(--tw-bg-opacity))}:is(.dark .dark\:focus\:text-gray-200:focus){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:text-gray-300:focus){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:text-primary-200:focus){--tw-text-opacity: 1;color:rgb(199 210 254 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:ring-indigo-600:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-600:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-700:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(67 56 202 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-opacity-40:focus){--tw-ring-opacity: .4}:is(.dark .dark\:focus\:ring-offset-gray-800:focus){--tw-ring-offset-color: #1e293b}@media (min-width: 640px){.sm\:mx-auto{margin-left:auto;margin-right:auto}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:w-full{width:100%}.sm\:max-w-2xl{max-width:42rem}.sm\:max-w-3xl{max-width:48rem}.sm\:max-w-4xl{max-width:56rem}.sm\:max-w-lg{max-width:32rem}.sm\:max-w-md{max-width:28rem}.sm\:max-w-sm{max-width:24rem}.sm\:max-w-xl{max-width:36rem}.sm\:flex-1{flex:1 1 0%}.sm\:translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:items-center{align-items:center}.sm\:justify-center{justify-content:center}.sm\:justify-between{justify-content:space-between}.sm\:rounded-md{border-radius:.375rem}.sm\:rounded-bl-md{border-bottom-left-radius:.375rem}.sm\:rounded-br-md{border-bottom-right-radius:.375rem}.sm\:rounded-tl-md{border-top-left-radius:.375rem}.sm\:rounded-tr-md{border-top-right-radius:.375rem}.sm\:p-6{padding:1.5rem}.sm\:px-0{padding-left:0;padding-right:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\:col-span-1{grid-column:span 1 / span 1}.md\:block{display:block}.md\:justify-start{justify-content:flex-start}.md\:text-left{text-align:left}}@media (min-width: 1024px){.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:px-8{padding-left:2rem;padding-right:2rem}} diff --git a/public/build/assets/app-986a0fb7.css b/public/build/assets/app-986a0fb7.css new file mode 100644 index 0000000..81ad31f --- /dev/null +++ b/public/build/assets/app-986a0fb7.css @@ -0,0 +1 @@ +.toast-title{font-weight:700}.toast-message{-ms-word-wrap:break-word;word-wrap:break-word}.toast-message a,.toast-message label{color:#fff}.toast-message a:hover{color:#ccc;text-decoration:none}.toast-close-button{position:relative;right:-.3em;top:-.3em;float:right;font-size:20px;font-weight:700;color:#fff;-webkit-text-shadow:0 1px 0 #ffffff;text-shadow:0 1px 0 #ffffff;opacity:.8;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=80);filter:alpha(opacity=80);line-height:1}.toast-close-button:hover,.toast-close-button:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}.rtl .toast-close-button{left:-.3em;float:left;right:.3em}button.toast-close-button{padding:0;cursor:pointer;background:transparent;border:0;-webkit-appearance:none}.toast-top-center{top:0;right:0;width:100%}.toast-bottom-center{bottom:0;right:0;width:100%}.toast-top-full-width{top:0;right:0;width:100%}.toast-bottom-full-width{bottom:0;right:0;width:100%}.toast-top-left{top:12px;left:12px}.toast-top-right{top:12px;right:12px}.toast-bottom-right{right:12px;bottom:12px}.toast-bottom-left{bottom:12px;left:12px}#toast-container{position:fixed;z-index:999999;pointer-events:none}#toast-container *{box-sizing:border-box}#toast-container>div{border-radius:.5rem;position:relative;pointer-events:auto;overflow:hidden;margin:0 0 6px;padding:15px 15px 15px 50px;width:300px;background-position:15px center;background-repeat:no-repeat;color:#fff}#toast-container>div.rtl{direction:rtl;padding:15px 50px 15px 15px;background-position:right 15px center}#toast-container>div:hover{cursor:pointer;--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}#toast-container>.toast-info{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGwSURBVEhLtZa9SgNBEMc9sUxxRcoUKSzSWIhXpFMhhYWFhaBg4yPYiWCXZxBLERsLRS3EQkEfwCKdjWJAwSKCgoKCcudv4O5YLrt7EzgXhiU3/4+b2ckmwVjJSpKkQ6wAi4gwhT+z3wRBcEz0yjSseUTrcRyfsHsXmD0AmbHOC9Ii8VImnuXBPglHpQ5wwSVM7sNnTG7Za4JwDdCjxyAiH3nyA2mtaTJufiDZ5dCaqlItILh1NHatfN5skvjx9Z38m69CgzuXmZgVrPIGE763Jx9qKsRozWYw6xOHdER+nn2KkO+Bb+UV5CBN6WC6QtBgbRVozrahAbmm6HtUsgtPC19tFdxXZYBOfkbmFJ1VaHA1VAHjd0pp70oTZzvR+EVrx2Ygfdsq6eu55BHYR8hlcki+n+kERUFG8BrA0BwjeAv2M8WLQBtcy+SD6fNsmnB3AlBLrgTtVW1c2QN4bVWLATaIS60J2Du5y1TiJgjSBvFVZgTmwCU+dAZFoPxGEEs8nyHC9Bwe2GvEJv2WXZb0vjdyFT4Cxk3e/kIqlOGoVLwwPevpYHT+00T+hWwXDf4AJAOUqWcDhbwAAAAASUVORK5CYII=)!important}#toast-container>.toast-error{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAHOSURBVEhLrZa/SgNBEMZzh0WKCClSCKaIYOED+AAKeQQLG8HWztLCImBrYadgIdY+gIKNYkBFSwu7CAoqCgkkoGBI/E28PdbLZmeDLgzZzcx83/zZ2SSXC1j9fr+I1Hq93g2yxH4iwM1vkoBWAdxCmpzTxfkN2RcyZNaHFIkSo10+8kgxkXIURV5HGxTmFuc75B2RfQkpxHG8aAgaAFa0tAHqYFfQ7Iwe2yhODk8+J4C7yAoRTWI3w/4klGRgR4lO7Rpn9+gvMyWp+uxFh8+H+ARlgN1nJuJuQAYvNkEnwGFck18Er4q3egEc/oO+mhLdKgRyhdNFiacC0rlOCbhNVz4H9FnAYgDBvU3QIioZlJFLJtsoHYRDfiZoUyIxqCtRpVlANq0EU4dApjrtgezPFad5S19Wgjkc0hNVnuF4HjVA6C7QrSIbylB+oZe3aHgBsqlNqKYH48jXyJKMuAbiyVJ8KzaB3eRc0pg9VwQ4niFryI68qiOi3AbjwdsfnAtk0bCjTLJKr6mrD9g8iq/S/B81hguOMlQTnVyG40wAcjnmgsCNESDrjme7wfftP4P7SP4N3CJZdvzoNyGq2c/HWOXJGsvVg+RA/k2MC/wN6I2YA2Pt8GkAAAAASUVORK5CYII=)!important}#toast-container>.toast-success{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAADsSURBVEhLY2AYBfQMgf///3P8+/evAIgvA/FsIF+BavYDDWMBGroaSMMBiE8VC7AZDrIFaMFnii3AZTjUgsUUWUDA8OdAH6iQbQEhw4HyGsPEcKBXBIC4ARhex4G4BsjmweU1soIFaGg/WtoFZRIZdEvIMhxkCCjXIVsATV6gFGACs4Rsw0EGgIIH3QJYJgHSARQZDrWAB+jawzgs+Q2UO49D7jnRSRGoEFRILcdmEMWGI0cm0JJ2QpYA1RDvcmzJEWhABhD/pqrL0S0CWuABKgnRki9lLseS7g2AlqwHWQSKH4oKLrILpRGhEQCw2LiRUIa4lwAAAABJRU5ErkJggg==)!important}#toast-container>.toast-warning{background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABgAAAAYCAYAAADgdz34AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAGYSURBVEhL5ZSvTsNQFMbXZGICMYGYmJhAQIJAICYQPAACiSDB8AiICQQJT4CqQEwgJvYASAQCiZiYmJhAIBATCARJy+9rTsldd8sKu1M0+dLb057v6/lbq/2rK0mS/TRNj9cWNAKPYIJII7gIxCcQ51cvqID+GIEX8ASG4B1bK5gIZFeQfoJdEXOfgX4QAQg7kH2A65yQ87lyxb27sggkAzAuFhbbg1K2kgCkB1bVwyIR9m2L7PRPIhDUIXgGtyKw575yz3lTNs6X4JXnjV+LKM/m3MydnTbtOKIjtz6VhCBq4vSm3ncdrD2lk0VgUXSVKjVDJXJzijW1RQdsU7F77He8u68koNZTz8Oz5yGa6J3H3lZ0xYgXBK2QymlWWA+RWnYhskLBv2vmE+hBMCtbA7KX5drWyRT/2JsqZ2IvfB9Y4bWDNMFbJRFmC9E74SoS0CqulwjkC0+5bpcV1CZ8NMej4pjy0U+doDQsGyo1hzVJttIjhQ7GnBtRFN1UarUlH8F3xict+HY07rEzoUGPlWcjRFRr4/gChZgc3ZL2d8oAAAAASUVORK5CYII=)!important}#toast-container.toast-top-center>div,#toast-container.toast-bottom-center>div{width:300px;margin-left:auto;margin-right:auto}#toast-container.toast-top-full-width>div,#toast-container.toast-bottom-full-width>div{width:96%;margin-left:auto;margin-right:auto}.toast{background-color:#030303}.toast-success{--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity))}.toast-error{--tw-bg-opacity: 1;background-color:rgb(240 82 82 / var(--tw-bg-opacity))}.toast-info{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.toast-warning{--tw-bg-opacity: 1;background-color:rgb(194 120 3 / var(--tw-bg-opacity))}.toast-progress{position:absolute;left:0;bottom:0;height:4px;background-color:#000;opacity:.4;-ms-filter:progid:DXImageTransform.Microsoft.Alpha(Opacity=40);filter:alpha(opacity=40)}@media all and (max-width: 240px){#toast-container>div{padding:8px 8px 8px 50px;width:11em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width: 241px) and (max-width: 480px){#toast-container>div{padding:8px 8px 8px 50px;width:18em}#toast-container>div.rtl{padding:8px 50px 8px 8px}#toast-container .toast-close-button{right:-.2em;top:-.2em}#toast-container .rtl .toast-close-button{left:-.2em;right:.2em}}@media all and (min-width: 481px) and (max-width: 768px){#toast-container>div{padding:15px 15px 15px 50px;width:25em}#toast-container>div.rtl{padding:15px 50px 15px 15px}}/*! tailwindcss v3.3.1 | MIT License | https://tailwindcss.com*/*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#e2e8f0}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Figtree,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji";font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#94a3b8}input::placeholder,textarea::placeholder{opacity:1;color:#94a3b8}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}::-webkit-datetime-edit,::-webkit-datetime-edit-year-field,::-webkit-datetime-edit-month-field,::-webkit-datetime-edit-day-field,::-webkit-datetime-edit-hour-field,::-webkit-datetime-edit-minute-field,::-webkit-datetime-edit-second-field,::-webkit-datetime-edit-millisecond-field,::-webkit-datetime-edit-meridiem-field{padding-top:0;padding-bottom:0}select{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%2364748b' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='M6 8l4 4 4-4'/%3e%3c/svg%3e");background-position:right .5rem center;background-repeat:no-repeat;background-size:1.5em 1.5em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}[type=checkbox]:checked,[type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3cpath d='M12.207 4.793a1 1 0 010 1.414l-5 5a1 1 0 01-1.414 0l-2-2a1 1 0 011.414-1.414L6.5 9.086l4.293-4.293a1 1 0 011.414 0z'/%3e%3c/svg%3e")}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e")}[type=checkbox]:checked:hover,[type=checkbox]:checked:focus,[type=radio]:checked:hover,[type=radio]:checked:focus{border-color:transparent;background-color:currentColor}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 16'%3e%3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M4 8h8'/%3e%3c/svg%3e");border-color:transparent;background-color:currentColor;background-size:100% 100%;background-position:center;background-repeat:no-repeat}[type=file]:focus{outline:1px solid ButtonText;outline:1px auto -webkit-focus-ring-color}.tooltip-arrow,.tooltip-arrow:before{position:absolute;width:8px;height:8px;background:inherit}.tooltip-arrow{visibility:hidden}.tooltip-arrow:before{content:"";visibility:visible;transform:rotate(45deg)}[data-tooltip-style^=light]+.tooltip>.tooltip-arrow:before{border-style:solid;border-color:#e5e7eb}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=top]>.tooltip-arrow:before{border-bottom-width:1px;border-right-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=right]>.tooltip-arrow:before{border-bottom-width:1px;border-left-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=bottom]>.tooltip-arrow:before{border-top-width:1px;border-left-width:1px}[data-tooltip-style^=light]+.tooltip[data-popper-placement^=left]>.tooltip-arrow:before{border-top-width:1px;border-right-width:1px}.tooltip[data-popper-placement^=top]>.tooltip-arrow{bottom:-4px}.tooltip[data-popper-placement^=bottom]>.tooltip-arrow{top:-4px}.tooltip[data-popper-placement^=left]>.tooltip-arrow{right:-4px}.tooltip[data-popper-placement^=right]>.tooltip-arrow{left:-4px}.tooltip.invisible>.tooltip-arrow:before{visibility:hidden}[data-popper-arrow],[data-popper-arrow]:before{position:absolute;width:8px;height:8px;background:inherit}[data-popper-arrow]{visibility:hidden}[data-popper-arrow]:before{content:"";visibility:visible;transform:rotate(45deg)}[data-popper-arrow]:after{content:"";visibility:visible;transform:rotate(45deg);position:absolute;width:9px;height:9px;background:inherit}[role=tooltip]>[data-popper-arrow]:before{border-style:solid;border-color:#e5e7eb}.dark [role=tooltip]>[data-popper-arrow]:before{border-style:solid;border-color:#4b5563}[role=tooltip]>[data-popper-arrow]:after{border-style:solid;border-color:#e5e7eb}.dark [role=tooltip]>[data-popper-arrow]:after{border-style:solid;border-color:#4b5563}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]:before{border-bottom-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]:after{border-bottom-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]:before{border-bottom-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]:after{border-bottom-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]:before{border-top-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]:after{border-top-width:1px;border-left-width:1px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]:before{border-top-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]:after{border-top-width:1px;border-right-width:1px}[data-popover][role=tooltip][data-popper-placement^=top]>[data-popper-arrow]{bottom:-5px}[data-popover][role=tooltip][data-popper-placement^=bottom]>[data-popper-arrow]{top:-5px}[data-popover][role=tooltip][data-popper-placement^=left]>[data-popper-arrow]{right:-5px}[data-popover][role=tooltip][data-popper-placement^=right]>[data-popper-arrow]{left:-5px}[role=tooltip].invisible>[data-popper-arrow]:before{visibility:hidden}[role=tooltip].invisible>[data-popper-arrow]:after{visibility:hidden}[type=text],[type=email],[type=url],[type=password],[type=number],[type=date],[type=datetime-local],[type=month],[type=search],[type=tel],[type=time],[type=week],[multiple],textarea,select{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:#fff;border-color:#64748b;border-width:1px;border-radius:0;padding:.5rem .75rem;font-size:1rem;line-height:1.5rem;--tw-shadow: 0 0 #0000}[type=text]:focus,[type=email]:focus,[type=url]:focus,[type=password]:focus,[type=number]:focus,[type=date]:focus,[type=datetime-local]:focus,[type=month]:focus,[type=search]:focus,[type=tel]:focus,[type=time]:focus,[type=week]:focus,[multiple]:focus,textarea:focus,select:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: #1C64F2;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow);border-color:#1c64f2}input::-moz-placeholder,textarea::-moz-placeholder{color:#64748b;opacity:1}input::placeholder,textarea::placeholder{color:#64748b;opacity:1}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-date-and-time-value{min-height:1.5em}select:not([size]){background-image:url("data:image/svg+xml,%3csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 10 6'%3e %3cpath stroke='%2364748b' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='m1 1 4 4 4-4'/%3e %3c/svg%3e");background-position:right .75rem center;background-repeat:no-repeat;background-size:.75em .75em;padding-right:2.5rem;-webkit-print-color-adjust:exact;print-color-adjust:exact}:is([dir=rtl]) select:not([size]){background-position:left .75rem center;padding-right:.75rem;padding-left:0}[multiple]{background-image:initial;background-position:initial;background-repeat:unset;background-size:initial;padding-right:.75rem;-webkit-print-color-adjust:unset;print-color-adjust:unset}[type=checkbox],[type=radio]{-webkit-appearance:none;-moz-appearance:none;appearance:none;padding:0;-webkit-print-color-adjust:exact;print-color-adjust:exact;display:inline-block;vertical-align:middle;background-origin:border-box;-webkit-user-select:none;-moz-user-select:none;user-select:none;flex-shrink:0;height:1rem;width:1rem;color:#1c64f2;background-color:#fff;border-color:#64748b;border-width:1px;--tw-shadow: 0 0 #0000}[type=checkbox]{border-radius:0}[type=radio]{border-radius:100%}[type=checkbox]:focus,[type=radio]:focus{outline:2px solid transparent;outline-offset:2px;--tw-ring-inset: var(--tw-empty, );--tw-ring-offset-width: 2px;--tw-ring-offset-color: #fff;--tw-ring-color: #1C64F2;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}[type=checkbox]:checked,[type=radio]:checked,.dark [type=checkbox]:checked,.dark [type=radio]:checked{border-color:transparent;background-color:currentColor;background-size:.55em .55em;background-position:center;background-repeat:no-repeat}[type=checkbox]:checked{background-image:url("data:image/svg+xml,%3csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 12'%3e %3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M1 5.917 5.724 10.5 15 1.5'/%3e %3c/svg%3e");background-repeat:no-repeat;background-size:.55em .55em;-webkit-print-color-adjust:exact;print-color-adjust:exact}[type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");background-size:1em 1em}.dark [type=radio]:checked{background-image:url("data:image/svg+xml,%3csvg viewBox='0 0 16 16' fill='white' xmlns='http://www.w3.org/2000/svg'%3e%3ccircle cx='8' cy='8' r='3'/%3e%3c/svg%3e");background-size:1em 1em}[type=checkbox]:indeterminate{background-image:url("data:image/svg+xml,%3csvg aria-hidden='true' xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 16 12'%3e %3cpath stroke='white' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='M0.5 6h14'/%3e %3c/svg%3e");background-color:currentColor;border-color:transparent;background-position:center;background-repeat:no-repeat;background-size:.55em .55em;-webkit-print-color-adjust:exact;print-color-adjust:exact}[type=checkbox]:indeterminate:hover,[type=checkbox]:indeterminate:focus{border-color:transparent;background-color:currentColor}[type=file]{background:unset;border-color:inherit;border-width:0;border-radius:0;padding:0;font-size:unset;line-height:inherit}[type=file]:focus{outline:1px auto inherit}input[type=file]::file-selector-button{color:#fff;background:#1e293b;border:0;font-weight:500;font-size:.875rem;cursor:pointer;padding:.625rem 1rem .625rem 2rem;-webkit-margin-start:-1rem;margin-inline-start:-1rem;-webkit-margin-end:1rem;margin-inline-end:1rem}input[type=file]::file-selector-button:hover{background:#334155}:is([dir=rtl]) input[type=file]::file-selector-button{padding-right:2rem;padding-left:1rem}.dark input[type=file]::file-selector-button{color:#fff;background:#475569}.dark input[type=file]::file-selector-button:hover{background:#64748b}input[type=range]::-webkit-slider-thumb{height:1.25rem;width:1.25rem;background:#1C64F2;border-radius:9999px;border:0;appearance:none;-moz-appearance:none;-webkit-appearance:none;cursor:pointer}input[type=range]:disabled::-webkit-slider-thumb{background:#94a3b8}.dark input[type=range]:disabled::-webkit-slider-thumb{background:#64748b}input[type=range]:focus::-webkit-slider-thumb{outline:2px solid transparent;outline-offset:2px;--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000);--tw-ring-opacity: 1px;--tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity))}input[type=range]::-moz-range-thumb{height:1.25rem;width:1.25rem;background:#1C64F2;border-radius:9999px;border:0;appearance:none;-moz-appearance:none;-webkit-appearance:none;cursor:pointer}input[type=range]:disabled::-moz-range-thumb{background:#94a3b8}.dark input[type=range]:disabled::-moz-range-thumb{background:#64748b}input[type=range]::-moz-range-progress{background:#3F83F8}input[type=range]::-ms-fill-lower{background:#3F83F8}.toggle-bg:after{content:"";position:absolute;top:.125rem;left:.125rem;background:white;border-color:#cbd5e1;border-width:1px;border-radius:9999px;height:1.25rem;width:1.25rem;transition-property:background-color,border-color,color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-duration:.15s;box-shadow:var(--tw-ring-inset) 0 0 0 calc(0px + var(--tw-ring-offset-width)) var(--tw-ring-color)}input:checked+.toggle-bg:after{transform:translate(100%);border-color:#fff}input:checked+.toggle-bg{background:#1C64F2;border-color:#1c64f2}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(63 131 248 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(63 131 248 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 640px){.container{max-width:640px}}@media (min-width: 768px){.container{max-width:768px}}@media (min-width: 1024px){.container{max-width:1024px}}@media (min-width: 1280px){.container{max-width:1280px}}@media (min-width: 1536px){.container{max-width:1536px}}.apexcharts-canvas .apexcharts-tooltip{background-color:#fff;color:#64748b;border:0!important;border-radius:.25rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a}.dark .apexcharts-canvas .apexcharts-tooltip{background-color:#334155;color:#94a3b8;border-color:transparent;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a}.apexcharts-canvas .apexcharts-tooltip .apexcharts-tooltip-title{padding:.5rem .75rem;margin-bottom:.75rem;background-color:#f1f5f9;border-bottom-color:#e2e8f0;font-size:.875rem!important;font-weight:400;color:#64748b}.dark .apexcharts-canvas .apexcharts-tooltip .apexcharts-tooltip-title{background-color:#475569;border-color:#64748b;color:#94a3b8}.apexcharts-canvas .apexcharts-xaxistooltip{color:#64748b;padding:.5rem .75rem;border-color:transparent;background-color:#fff;border-radius:.25rem;box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -2px #0000001a}.dark .apexcharts-canvas .apexcharts-xaxistooltip{color:#94a3b8;background-color:#334155}.apexcharts-canvas .apexcharts-tooltip .apexcharts-tooltip-text-y-label{color:#64748b;font-size:.875rem}.dark .apexcharts-canvas .apexcharts-tooltip .apexcharts-tooltip-text-y-label{color:#94a3b8}.apexcharts-canvas .apexcharts-tooltip .apexcharts-tooltip-text-y-value{color:#0f172a;font-size:.875rem}:is([dir=rtl]) .apexcharts-tooltip .apexcharts-tooltip-marker{margin-right:0;margin-left:e}.dark .apexcharts-canvas .apexcharts-tooltip .apexcharts-tooltip-text-y-value{color:#fff}.apexcharts-canvas .apexcharts-xaxistooltip-text{font-weight:400;font-size:.875rem!important}.apexcharts-canvas .apexcharts-xaxistooltip:after,.apexcharts-canvas .apexcharts-xaxistooltip:before{border-bottom-color:#fff}.apexcharts-canvas .apexcharts-xaxistooltip:after{border-width:8px;margin-left:-8px}.apexcharts-canvas .apexcharts-xaxistooltip:before{border-width:10px;margin-left:-10px}.dark .apexcharts-canvas .apexcharts-xaxistooltip:after,.dark .apexcharts-canvas .apexcharts-xaxistooltip:before{border-bottom-color:#334155}.apexcharts-canvas .apexcharts-tooltip-series-group.apexcharts-active .apexcharts-tooltip-y-group{padding:0}.apexcharts-canvas .apexcharts-tooltip-series-group.apexcharts-active{padding-left:.75rem;padding-right:.75rem;padding-bottom:.75rem;background-color:#fff!important;color:#64748b!important}.dark .apexcharts-canvas .apexcharts-tooltip-series-group.apexcharts-active{background-color:#334155!important;color:#94a3b8!important}.apexcharts-canvas .apexcharts-tooltip-series-group.apexcharts-active:first-of-type{padding-top:.75rem}.apexcharts-canvas .apexcharts-legend{padding:0!important}.apexcharts-canvas .apexcharts-legend-text{font-size:.75rem;font-weight:500!important;padding-left:1.25rem;color:#64748b!important}:is([dir=rtl]) .apexcharts-canvas .apexcharts-legend-text{padding-right:.5rem}.apexcharts-canvas .apexcharts-legend-text:not(.apexcharts-inactive-legend):hover{color:#0f172a!important}.dark .apexcharts-canvas .apexcharts-legend-text{color:#94a3b8!important}.dark .apexcharts-canvas .apexcharts-legend-text:not(.apexcharts-inactive-legend):hover{color:#fff!important}.apexcharts-canvas .apexcharts-legend-series{margin-left:.5rem;margin-right:.5rem;margin-bottom:.25rem!important;display:flex;align-items:center}.apexcharts-datalabels-group .apexcharts-text.apexcharts-datalabel-value{fill:#0f172a!important;font-size:1.875rem;font-weight:700}.dark .apexcharts-canvas .apexcharts-datalabels-group .apexcharts-text.apexcharts-datalabel-value{fill:#fff!important}.apexcharts-canvas .apexcharts-datalabels-group .apexcharts-text.apexcharts-datalabel-label{fill:#64748b!important;font-size:1rem;font-weight:400}.dark .apexcharts-canvas .apexcharts-datalabels-group .apexcharts-text.apexcharts-datalabel-label{fill:#94a3b8!important}.apexcharts-canvas .apexcharts-datalabels .apexcharts-text.apexcharts-pie-label{font-size:.75rem!important;font-weight:600!important;text-shadow:none!important;filter:none!important}.apexcharts-gridline,.apexcharts-xcrosshairs,.apexcharts-ycrosshairs{stroke:#e2e8f0!important}.dark .apexcharts-gridline,.dark .apexcharts-xcrosshairs,.dark .apexcharts-ycrosshairs{stroke:#334155!important}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.visible{visibility:visible}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.absolute{position:absolute}.relative{position:relative}.inset-0{top:0;right:0;bottom:0;left:0}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.bottom-\[60px\]{bottom:60px}.left-0{left:0}.right-0{right:0}.top-0{top:0}.top-1{top:.25rem}.z-0{z-index:0}.z-10{z-index:10}.z-20{z-index:20}.z-30{z-index:30}.z-40{z-index:40}.z-50{z-index:50}.z-\[1000\]{z-index:1000}.float-right{float:right}.mx-5{margin-left:1.25rem;margin-right:1.25rem}.mx-auto{margin-left:auto;margin-right:auto}.my-3{margin-top:.75rem;margin-bottom:.75rem}.-ml-px{margin-left:-1px}.-mt-px{margin-top:-1px}.mb-1{margin-bottom:.25rem}.mb-10{margin-bottom:2.5rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.mb-5{margin-bottom:1.25rem}.mb-6{margin-bottom:1.5rem}.ml-1{margin-left:.25rem}.ml-12{margin-left:3rem}.ml-2{margin-left:.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mr-3{margin-right:.75rem}.ms-2{-webkit-margin-start:.5rem;margin-inline-start:.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-20{margin-top:5rem}.mt-3{margin-top:.75rem}.mt-4{margin-top:1rem}.mt-5{margin-top:1.25rem}.mt-6{margin-top:1.5rem}.mt-8{margin-top:2rem}.mt-\[64px\]{margin-top:64px}.block{display:block}.inline-block{display:inline-block}.inline{display:inline}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.hidden{display:none}.h-10{height:2.5rem}.h-16{height:4rem}.h-20{height:5rem}.h-4{height:1rem}.h-48{height:12rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-7{height:1.75rem}.h-8{height:2rem}.h-9{height:2.25rem}.h-\[500px\]{height:500px}.h-\[64px\]{height:64px}.h-full{height:100%}.h-screen{height:100vh}.max-h-\[350px\]{max-height:350px}.max-h-screen{max-height:100vh}.min-h-\[400px\]{min-height:400px}.min-h-screen{min-height:100vh}.w-1{width:.25rem}.w-1\/2{width:50%}.w-10{width:2.5rem}.w-4{width:1rem}.w-48{width:12rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-7{width:1.75rem}.w-8{width:2rem}.w-9{width:2.25rem}.w-\[500px\]{width:500px}.w-auto{width:auto}.w-full{width:100%}.w-max{width:-moz-max-content;width:max-content}.min-w-full{min-width:100%}.min-w-max{min-width:-moz-max-content;min-width:max-content}.max-w-6xl{max-width:72rem}.max-w-7xl{max-width:80rem}.max-w-\[500px\]{max-width:500px}.max-w-full{max-width:100%}.max-w-xl{max-width:36rem}.flex-1{flex:1 1 0%}.flex-none{flex:none}.flex-shrink{flex-shrink:1}.flex-grow{flex-grow:1}.origin-top{transform-origin:top}.origin-top-left{transform-origin:top left}.origin-top-right{transform-origin:top right}.-translate-x-full{--tw-translate-x: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.-translate-y-full{--tw-translate-y: -100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-x-full{--tw-translate-x: 100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-4{--tw-translate-y: 1rem;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.translate-y-full{--tw-translate-y: 100%;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.rotate-180{--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform{transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.transform-none{transform:none}.cursor-default{cursor:default}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.select-none{-webkit-user-select:none;-moz-user-select:none;user-select:none}.resize{resize:both}.list-none{list-style-type:none}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.grid-cols-6{grid-template-columns:repeat(6,minmax(0,1fr))}.grid-cols-7{grid-template-columns:repeat(7,minmax(0,1fr))}.flex-col{flex-direction:column}.items-start{align-items:flex-start}.items-end{align-items:flex-end}.items-center{align-items:center}.justify-start{justify-content:flex-start}.justify-end{justify-content:flex-end}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.justify-items-center{justify-items:center}.gap-2{gap:.5rem}.gap-3{gap:.75rem}.gap-5{gap:1.25rem}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 0;margin-right:calc(.5rem * var(--tw-space-x-reverse));margin-left:calc(.5rem * calc(1 - var(--tw-space-x-reverse)))}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.25rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem * var(--tw-space-y-reverse))}.space-y-10>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(2.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(2.5rem * var(--tw-space-y-reverse))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem * var(--tw-space-y-reverse))}.space-y-3>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(.75rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.75rem * var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse: 0;margin-top:calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem * var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse: 0;border-top-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px * var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(241 245 249 / var(--tw-divide-opacity))}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.overflow-y-hidden{overflow-y:hidden}.truncate{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-line{white-space:pre-line}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-sm{border-radius:.125rem}.rounded-xl{border-radius:.75rem}.rounded-b-md{border-bottom-right-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-e-lg{border-start-end-radius:.5rem;border-end-end-radius:.5rem}.rounded-l-lg{border-top-left-radius:.5rem;border-bottom-left-radius:.5rem}.rounded-l-md{border-top-left-radius:.375rem;border-bottom-left-radius:.375rem}.rounded-r-lg{border-top-right-radius:.5rem;border-bottom-right-radius:.5rem}.rounded-r-md{border-top-right-radius:.375rem;border-bottom-right-radius:.375rem}.rounded-s-lg{border-start-start-radius:.5rem;border-end-start-radius:.5rem}.rounded-t-md{border-top-left-radius:.375rem;border-top-right-radius:.375rem}.border{border-width:1px}.border-0{border-width:0px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-b-2{border-bottom-width:2px}.border-l{border-left-width:1px}.border-l-4{border-left-width:4px}.border-r{border-right-width:1px}.border-t{border-top-width:1px}.\!border-primary-700{--tw-border-opacity: 1 !important;border-color:rgb(67 56 202 / var(--tw-border-opacity))!important}.border-blue-600{--tw-border-opacity: 1;border-color:rgb(28 100 242 / var(--tw-border-opacity))}.border-blue-700{--tw-border-opacity: 1;border-color:rgb(26 86 219 / var(--tw-border-opacity))}.border-gray-100{--tw-border-opacity: 1;border-color:rgb(241 245 249 / var(--tw-border-opacity))}.border-gray-200{--tw-border-opacity: 1;border-color:rgb(226 232 240 / var(--tw-border-opacity))}.border-gray-300{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.border-gray-400{--tw-border-opacity: 1;border-color:rgb(148 163 184 / var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}.border-green-500{--tw-border-opacity: 1;border-color:rgb(16 185 129 / var(--tw-border-opacity))}.border-primary-200{--tw-border-opacity: 1;border-color:rgb(199 210 254 / var(--tw-border-opacity))}.border-primary-400{--tw-border-opacity: 1;border-color:rgb(129 140 248 / var(--tw-border-opacity))}.border-primary-600{--tw-border-opacity: 1;border-color:rgb(79 70 229 / var(--tw-border-opacity))}.border-red-500{--tw-border-opacity: 1;border-color:rgb(240 82 82 / var(--tw-border-opacity))}.border-transparent{border-color:transparent}.border-yellow-500{--tw-border-opacity: 1;border-color:rgb(194 120 3 / var(--tw-border-opacity))}.border-t-transparent{border-top-color:transparent}.border-opacity-50{--tw-border-opacity: .5}.\!bg-primary-600{--tw-bg-opacity: 1 !important;background-color:rgb(79 70 229 / var(--tw-bg-opacity))!important}.\!bg-primary-700{--tw-bg-opacity: 1 !important;background-color:rgb(67 56 202 / var(--tw-bg-opacity))!important}.bg-blue-700{--tw-bg-opacity: 1;background-color:rgb(26 86 219 / var(--tw-bg-opacity))}.bg-gray-100{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity: 1;background-color:rgb(226 232 240 / var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.bg-gray-500{--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.bg-gray-900\/50{background-color:#0f172a80}.bg-green-50{--tw-bg-opacity: 1;background-color:rgb(236 253 245 / var(--tw-bg-opacity))}.bg-primary-100{--tw-bg-opacity: 1;background-color:rgb(224 231 255 / var(--tw-bg-opacity))}.bg-primary-200{--tw-bg-opacity: 1;background-color:rgb(199 210 254 / var(--tw-bg-opacity))}.bg-primary-50{--tw-bg-opacity: 1;background-color:rgb(238 242 255 / var(--tw-bg-opacity))}.bg-primary-500{--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}.bg-primary-600{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.bg-primary-700{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity: 1;background-color:rgb(253 242 242 / var(--tw-bg-opacity))}.bg-red-600{--tw-bg-opacity: 1;background-color:rgb(224 36 36 / var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.bg-white\/50{background-color:#ffffff80}.bg-yellow-100{--tw-bg-opacity: 1;background-color:rgb(253 246 178 / var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity: 1;background-color:rgb(253 253 234 / var(--tw-bg-opacity))}.p-1{padding:.25rem}.p-2{padding:.5rem}.p-2\.5{padding:.625rem}.p-3{padding:.75rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.p-7{padding:1.75rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.px-8{padding-left:2rem;padding-right:2rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-1{padding-bottom:.25rem}.pb-2{padding-bottom:.5rem}.pb-4{padding-bottom:1rem}.pl-3{padding-left:.75rem}.pr-10{padding-right:2.5rem}.pr-2{padding-right:.5rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-1{padding-top:.25rem}.pt-2{padding-top:.5rem}.pt-20{padding-top:5rem}.pt-3{padding-top:.75rem}.pt-6{padding-top:1.5rem}.pt-8{padding-top:2rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-sans{font-family:Figtree,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,Noto Sans,sans-serif,"Apple Color Emoji","Segoe UI Emoji",Segoe UI Symbol,"Noto Color Emoji"}.text-2xl{font-size:1.5rem;line-height:2rem}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-extrabold{font-weight:800}.font-medium{font-weight:500}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.lowercase{text-transform:lowercase}.capitalize{text-transform:capitalize}.leading-4{line-height:1rem}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.leading-9{line-height:2.25rem}.tracking-wider{letter-spacing:.05em}.text-black{--tw-text-opacity: 1;color:rgb(0 0 0 / var(--tw-text-opacity))}.text-blue-600{--tw-text-opacity: 1;color:rgb(28 100 242 / var(--tw-text-opacity))}.text-gray-100{--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}.text-gray-200{--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.text-gray-50{--tw-text-opacity: 1;color:rgb(248 250 252 / var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.text-gray-600{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.text-green-500{--tw-text-opacity: 1;color:rgb(16 185 129 / var(--tw-text-opacity))}.text-green-600{--tw-text-opacity: 1;color:rgb(5 150 105 / var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity: 1;color:rgb(88 80 236 / var(--tw-text-opacity))}.text-primary-500{--tw-text-opacity: 1;color:rgb(99 102 241 / var(--tw-text-opacity))}.text-primary-600{--tw-text-opacity: 1;color:rgb(79 70 229 / var(--tw-text-opacity))}.text-primary-700{--tw-text-opacity: 1;color:rgb(67 56 202 / var(--tw-text-opacity))}.text-red-500{--tw-text-opacity: 1;color:rgb(240 82 82 / var(--tw-text-opacity))}.text-red-600{--tw-text-opacity: 1;color:rgb(224 36 36 / var(--tw-text-opacity))}.text-white{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.text-yellow-500{--tw-text-opacity: 1;color:rgb(194 120 3 / var(--tw-text-opacity))}.text-yellow-700{--tw-text-opacity: 1;color:rgb(142 75 16 / var(--tw-text-opacity))}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.opacity-100{opacity:1}.opacity-25{opacity:.25}.opacity-75{opacity:.75}.shadow{--tw-shadow: 0 1px 3px 0 rgb(0 0 0 / .1), 0 1px 2px -1px rgb(0 0 0 / .1);--tw-shadow-colored: 0 1px 3px 0 var(--tw-shadow-color), 0 1px 2px -1px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-lg{--tw-shadow: 0 10px 15px -3px rgb(0 0 0 / .1), 0 4px 6px -4px rgb(0 0 0 / .1);--tw-shadow-colored: 0 10px 15px -3px var(--tw-shadow-color), 0 4px 6px -4px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-md{--tw-shadow: 0 4px 6px -1px rgb(0 0 0 / .1), 0 2px 4px -2px rgb(0 0 0 / .1);--tw-shadow-colored: 0 4px 6px -1px var(--tw-shadow-color), 0 2px 4px -2px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-none{--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow: 0 1px 2px 0 rgb(0 0 0 / .05);--tw-shadow-colored: 0 1px 2px 0 var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.shadow-xl{--tw-shadow: 0 20px 25px -5px rgb(0 0 0 / .1), 0 8px 10px -6px rgb(0 0 0 / .1);--tw-shadow-colored: 0 20px 25px -5px var(--tw-shadow-color), 0 8px 10px -6px var(--tw-shadow-color);box-shadow:var(--tw-ring-offset-shadow, 0 0 #0000),var(--tw-ring-shadow, 0 0 #0000),var(--tw-shadow)}.outline{outline-style:solid}.outline-0{outline-width:0px}.ring-1{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.ring-black{--tw-ring-opacity: 1;--tw-ring-color: rgb(0 0 0 / var(--tw-ring-opacity))}.ring-gray-300{--tw-ring-opacity: 1;--tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity))}.ring-opacity-5{--tw-ring-opacity: .05}.blur{--tw-blur: blur(8px);filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.transition{transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,-webkit-backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter;transition-property:color,background-color,border-color,text-decoration-color,fill,stroke,opacity,box-shadow,transform,filter,backdrop-filter,-webkit-backdrop-filter;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-all{transition-property:all;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-opacity{transition-property:opacity;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.transition-transform{transition-property:transform;transition-timing-function:cubic-bezier(.4,0,.2,1);transition-duration:.15s}.duration-100{transition-duration:.1s}.duration-150{transition-duration:.15s}.duration-200{transition-duration:.2s}.duration-300{transition-duration:.3s}.duration-75{transition-duration:75ms}.ease-in{transition-timing-function:cubic-bezier(.4,0,1,1)}.ease-in-out{transition-timing-function:cubic-bezier(.4,0,.2,1)}.ease-out{transition-timing-function:cubic-bezier(0,0,.2,1)}[x-cloak]{display:none!important}body{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}:is(.dark body){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}.last\:border-b-0:last-child{border-bottom-width:0px}.hover\:border-gray-300:hover{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.hover\:\!bg-primary-800:hover{--tw-bg-opacity: 1 !important;background-color:rgb(55 48 163 / var(--tw-bg-opacity))!important}.hover\:bg-blue-800:hover{--tw-bg-opacity: 1;background-color:rgb(30 66 159 / var(--tw-bg-opacity))}.hover\:bg-gray-100:hover{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.hover\:bg-gray-50:hover{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.hover\:bg-gray-900:hover{--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}.hover\:bg-primary-600:hover{--tw-bg-opacity: 1;background-color:rgb(79 70 229 / var(--tw-bg-opacity))}.hover\:bg-primary-700:hover{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity))}.hover\:bg-red-500:hover{--tw-bg-opacity: 1;background-color:rgb(240 82 82 / var(--tw-bg-opacity))}.hover\:bg-white:hover{--tw-bg-opacity: 1;background-color:rgb(255 255 255 / var(--tw-bg-opacity))}.hover\:text-blue-600:hover{--tw-text-opacity: 1;color:rgb(28 100 242 / var(--tw-text-opacity))}.hover\:text-gray-400:hover{--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}.hover\:text-gray-500:hover{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.hover\:text-gray-600:hover{--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.hover\:text-gray-800:hover{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.hover\:text-gray-900:hover{--tw-text-opacity: 1;color:rgb(15 23 42 / var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}.hover\:opacity-50:hover{opacity:.5}.focus\:z-10:focus{z-index:10}.focus\:border-blue-300:focus{--tw-border-opacity: 1;border-color:rgb(164 202 254 / var(--tw-border-opacity))}.focus\:border-gray-300:focus{--tw-border-opacity: 1;border-color:rgb(203 213 225 / var(--tw-border-opacity))}.focus\:border-primary-300:focus{--tw-border-opacity: 1;border-color:rgb(165 180 252 / var(--tw-border-opacity))}.focus\:border-primary-500:focus{--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}.focus\:border-primary-700:focus{--tw-border-opacity: 1;border-color:rgb(67 56 202 / var(--tw-border-opacity))}.focus\:border-red-700:focus{--tw-border-opacity: 1;border-color:rgb(200 30 30 / var(--tw-border-opacity))}.focus\:bg-gray-100:focus{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.focus\:bg-gray-50:focus{--tw-bg-opacity: 1;background-color:rgb(248 250 252 / var(--tw-bg-opacity))}.focus\:bg-primary-100:focus{--tw-bg-opacity: 1;background-color:rgb(224 231 255 / var(--tw-bg-opacity))}.focus\:text-gray-700:focus{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.focus\:text-gray-800:focus{--tw-text-opacity: 1;color:rgb(30 41 59 / var(--tw-text-opacity))}.focus\:text-primary-800:focus{--tw-text-opacity: 1;color:rgb(55 48 163 / var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(3px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-1:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-2:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:ring-4:focus{--tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow, 0 0 #0000)}.focus\:\!ring-primary-300:focus{--tw-ring-opacity: 1 !important;--tw-ring-color: rgb(165 180 252 / var(--tw-ring-opacity)) !important}.focus\:ring-blue-300:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(164 202 254 / var(--tw-ring-opacity))}.focus\:ring-gray-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(226 232 240 / var(--tw-ring-opacity))}.focus\:ring-gray-300:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(203 213 225 / var(--tw-ring-opacity))}.focus\:ring-gray-400:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(148 163 184 / var(--tw-ring-opacity))}.focus\:ring-gray-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(100 116 139 / var(--tw-ring-opacity))}.focus\:ring-gray-700:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(51 65 85 / var(--tw-ring-opacity))}.focus\:ring-indigo-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(104 117 245 / var(--tw-ring-opacity))}.focus\:ring-primary-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(199 210 254 / var(--tw-ring-opacity))}.focus\:ring-primary-500:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}.focus\:ring-red-200:focus{--tw-ring-opacity: 1;--tw-ring-color: rgb(251 213 213 / var(--tw-ring-opacity))}.focus\:ring-opacity-50:focus{--tw-ring-opacity: .5}.focus\:ring-offset-2:focus{--tw-ring-offset-width: 2px}.active\:bg-gray-100:active{--tw-bg-opacity: 1;background-color:rgb(241 245 249 / var(--tw-bg-opacity))}.active\:bg-primary-700:active{--tw-bg-opacity: 1;background-color:rgb(67 56 202 / var(--tw-bg-opacity))}.active\:bg-red-600:active{--tw-bg-opacity: 1;background-color:rgb(224 36 36 / var(--tw-bg-opacity))}.active\:text-gray-500:active{--tw-text-opacity: 1;color:rgb(100 116 139 / var(--tw-text-opacity))}.active\:text-gray-700:active{--tw-text-opacity: 1;color:rgb(51 65 85 / var(--tw-text-opacity))}.disabled\:opacity-25:disabled{opacity:.25}:is([dir=rtl] .rtl\:rotate-180){--tw-rotate: 180deg;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}:is([dir=rtl] .rtl\:flex-row-reverse){flex-direction:row-reverse}:is([dir=rtl] .rtl\:space-x-reverse)>:not([hidden])~:not([hidden]){--tw-space-x-reverse: 1}:is(.dark .dark\:divide-gray-600)>:not([hidden])~:not([hidden]){--tw-divide-opacity: 1;border-color:rgb(71 85 105 / var(--tw-divide-opacity))}:is(.dark .dark\:divide-gray-700\/50)>:not([hidden])~:not([hidden]){border-color:#33415580}:is(.dark .dark\:border-r-2){border-right-width:2px}:is(.dark .dark\:border-blue-500){--tw-border-opacity: 1;border-color:rgb(63 131 248 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-500){--tw-border-opacity: 1;border-color:rgb(100 116 139 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-600){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-700){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-800){--tw-border-opacity: 1;border-color:rgb(30 41 59 / var(--tw-border-opacity))}:is(.dark .dark\:border-gray-900){--tw-border-opacity: 1;border-color:rgb(15 23 42 / var(--tw-border-opacity))}:is(.dark .dark\:border-primary-600){--tw-border-opacity: 1;border-color:rgb(79 70 229 / var(--tw-border-opacity))}:is(.dark .dark\:border-transparent){border-color:transparent}:is(.dark .dark\:border-t-transparent){border-top-color:transparent}:is(.dark .dark\:border-opacity-20){--tw-border-opacity: .2}:is(.dark .dark\:\!bg-primary-600){--tw-bg-opacity: 1 !important;background-color:rgb(79 70 229 / var(--tw-bg-opacity))!important}:is(.dark .dark\:\!bg-primary-700){--tw-bg-opacity: 1 !important;background-color:rgb(67 56 202 / var(--tw-bg-opacity))!important}:is(.dark .dark\:bg-blue-600){--tw-bg-opacity: 1;background-color:rgb(28 100 242 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-500){--tw-bg-opacity: 1;background-color:rgb(100 116 139 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-600){--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-700){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-800\/50){background-color:#1e293b80}:is(.dark .dark\:bg-gray-900){--tw-bg-opacity: 1;background-color:rgb(15 23 42 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-gray-900\/80){background-color:#0f172acc}:is(.dark .dark\:bg-green-500){--tw-bg-opacity: 1;background-color:rgb(16 185 129 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-primary-500){--tw-bg-opacity: 1;background-color:rgb(99 102 241 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-primary-900\/50){background-color:#312e8180}:is(.dark .dark\:bg-red-500){--tw-bg-opacity: 1;background-color:rgb(240 82 82 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-yellow-500){--tw-bg-opacity: 1;background-color:rgb(194 120 3 / var(--tw-bg-opacity))}:is(.dark .dark\:bg-opacity-10){--tw-bg-opacity: .1}:is(.dark .dark\:bg-opacity-20){--tw-bg-opacity: .2}:is(.dark .dark\:bg-opacity-30){--tw-bg-opacity: .3}:is(.dark .dark\:bg-opacity-70){--tw-bg-opacity: .7}:is(.dark .dark\:text-blue-500){--tw-text-opacity: 1;color:rgb(63 131 248 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-100){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-200){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-300){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-400){--tw-text-opacity: 1;color:rgb(148 163 184 / var(--tw-text-opacity))}:is(.dark .dark\:text-gray-600){--tw-text-opacity: 1;color:rgb(71 85 105 / var(--tw-text-opacity))}:is(.dark .dark\:text-green-400){--tw-text-opacity: 1;color:rgb(52 211 153 / var(--tw-text-opacity))}:is(.dark .dark\:text-primary-300){--tw-text-opacity: 1;color:rgb(165 180 252 / var(--tw-text-opacity))}:is(.dark .dark\:text-red-400){--tw-text-opacity: 1;color:rgb(249 128 128 / var(--tw-text-opacity))}:is(.dark .dark\:text-white){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .dark\:text-yellow-500){--tw-text-opacity: 1;color:rgb(194 120 3 / var(--tw-text-opacity))}:is(.dark .dark\:placeholder-gray-400)::-moz-placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity))}:is(.dark .dark\:placeholder-gray-400)::placeholder{--tw-placeholder-opacity: 1;color:rgb(148 163 184 / var(--tw-placeholder-opacity))}:is(.dark .dark\:hover\:border-gray-600:hover){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:border-gray-700:hover){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:hover\:\!bg-primary-700:hover){--tw-bg-opacity: 1 !important;background-color:rgb(67 56 202 / var(--tw-bg-opacity))!important}:is(.dark .dark\:hover\:bg-blue-700:hover){--tw-bg-opacity: 1;background-color:rgb(26 86 219 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-600:hover){--tw-bg-opacity: 1;background-color:rgb(71 85 105 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-700:hover){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-700\/50:hover){background-color:#33415580}:is(.dark .dark\:hover\:bg-gray-800:hover){--tw-bg-opacity: 1;background-color:rgb(30 41 59 / var(--tw-bg-opacity))}:is(.dark .dark\:hover\:bg-gray-800\/50:hover){background-color:#1e293b80}:is(.dark .dark\:hover\:bg-gray-800\/70:hover){background-color:#1e293bb3}:is(.dark .dark\:hover\:text-blue-500:hover){--tw-text-opacity: 1;color:rgb(63 131 248 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-100:hover){--tw-text-opacity: 1;color:rgb(241 245 249 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-200:hover){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-gray-300:hover){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:hover\:text-white:hover){--tw-text-opacity: 1;color:rgb(255 255 255 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:border-blue-700:focus){--tw-border-opacity: 1;border-color:rgb(26 86 219 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-blue-800:focus){--tw-border-opacity: 1;border-color:rgb(30 66 159 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-gray-600:focus){--tw-border-opacity: 1;border-color:rgb(71 85 105 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-gray-700:focus){--tw-border-opacity: 1;border-color:rgb(51 65 85 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-300:focus){--tw-border-opacity: 1;border-color:rgb(165 180 252 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-500:focus){--tw-border-opacity: 1;border-color:rgb(99 102 241 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-600:focus){--tw-border-opacity: 1;border-color:rgb(79 70 229 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:border-primary-700:focus){--tw-border-opacity: 1;border-color:rgb(67 56 202 / var(--tw-border-opacity))}:is(.dark .dark\:focus\:bg-gray-700:focus){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:focus\:bg-primary-900:focus){--tw-bg-opacity: 1;background-color:rgb(49 46 129 / var(--tw-bg-opacity))}:is(.dark .dark\:focus\:text-gray-200:focus){--tw-text-opacity: 1;color:rgb(226 232 240 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:text-gray-300:focus){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:text-primary-200:focus){--tw-text-opacity: 1;color:rgb(199 210 254 / var(--tw-text-opacity))}:is(.dark .dark\:focus\:ring-gray-600:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(71 85 105 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-indigo-600:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(88 80 236 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-500:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(99 102 241 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-600:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(79 70 229 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-primary-700:focus){--tw-ring-opacity: 1;--tw-ring-color: rgb(67 56 202 / var(--tw-ring-opacity))}:is(.dark .dark\:focus\:ring-opacity-40:focus){--tw-ring-opacity: .4}:is(.dark .dark\:focus\:ring-offset-gray-800:focus){--tw-ring-offset-color: #1e293b}:is(.dark .dark\:active\:bg-gray-700:active){--tw-bg-opacity: 1;background-color:rgb(51 65 85 / var(--tw-bg-opacity))}:is(.dark .dark\:active\:text-gray-300:active){--tw-text-opacity: 1;color:rgb(203 213 225 / var(--tw-text-opacity))}@media (min-width: 640px){.sm\:mx-auto{margin-left:auto;margin-right:auto}.sm\:ml-64{margin-left:16rem}.sm\:block{display:block}.sm\:flex{display:flex}.sm\:hidden{display:none}.sm\:w-full{width:100%}.sm\:max-w-2xl{max-width:42rem}.sm\:max-w-3xl{max-width:48rem}.sm\:max-w-4xl{max-width:56rem}.sm\:max-w-lg{max-width:32rem}.sm\:max-w-md{max-width:28rem}.sm\:max-w-sm{max-width:24rem}.sm\:max-w-xl{max-width:36rem}.sm\:flex-1{flex:1 1 0%}.sm\:translate-x-0{--tw-translate-x: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:translate-y-0{--tw-translate-y: 0px;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:scale-100{--tw-scale-x: 1;--tw-scale-y: 1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:scale-95{--tw-scale-x: .95;--tw-scale-y: .95;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.sm\:items-center{align-items:center}.sm\:justify-start{justify-content:flex-start}.sm\:justify-center{justify-content:center}.sm\:justify-between{justify-content:space-between}.sm\:rounded-md{border-radius:.375rem}.sm\:rounded-bl-md{border-bottom-left-radius:.375rem}.sm\:rounded-br-md{border-bottom-right-radius:.375rem}.sm\:rounded-tl-md{border-top-left-radius:.375rem}.sm\:rounded-tr-md{border-top-right-radius:.375rem}.sm\:p-6{padding:1.5rem}.sm\:px-0{padding-left:0;padding-right:0}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}.sm\:pt-0{padding-top:0}.sm\:text-sm{font-size:.875rem;line-height:1.25rem}}@media (min-width: 768px){.md\:col-span-1{grid-column:span 1 / span 1}.md\:me-24{-webkit-margin-end:6rem;margin-inline-end:6rem}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:items-center{align-items:center}.md\:justify-start{justify-content:flex-start}.md\:px-0{padding-left:0;padding-right:0}.md\:text-left{text-align:left}}@media (min-width: 1024px){.lg\:mb-0{margin-bottom:0}.lg\:mr-2{margin-right:.5rem}.lg\:block{display:block}.lg\:scale-110{--tw-scale-x: 1.1;--tw-scale-y: 1.1;transform:translate(var(--tw-translate-x),var(--tw-translate-y)) rotate(var(--tw-rotate)) skew(var(--tw-skew-x)) skewY(var(--tw-skew-y)) scaleX(var(--tw-scale-x)) scaleY(var(--tw-scale-y))}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:px-5{padding-left:1.25rem;padding-right:1.25rem}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:pl-3{padding-left:.75rem}}@media (min-width: 1280px){.xl\:block{display:block}} diff --git a/public/build/assets/app-a1ae07b3.css b/public/build/assets/app-a1ae07b3.css new file mode 100644 index 0000000..2a52516 --- /dev/null +++ b/public/build/assets/app-a1ae07b3.css @@ -0,0 +1 @@ +.tippy-box[data-animation=fade][data-state=hidden]{opacity:0}[data-tippy-root]{max-width:calc(100vw - 10px)}.tippy-box{position:relative;background-color:#333;color:#fff;border-radius:4px;font-size:14px;line-height:1.4;white-space:normal;outline:0;transition-property:transform,visibility,opacity}.tippy-box[data-placement^=top]>.tippy-arrow{bottom:0}.tippy-box[data-placement^=top]>.tippy-arrow:before{bottom:-7px;left:0;border-width:8px 8px 0;border-top-color:initial;transform-origin:center top}.tippy-box[data-placement^=bottom]>.tippy-arrow{top:0}.tippy-box[data-placement^=bottom]>.tippy-arrow:before{top:-7px;left:0;border-width:0 8px 8px;border-bottom-color:initial;transform-origin:center bottom}.tippy-box[data-placement^=left]>.tippy-arrow{right:0}.tippy-box[data-placement^=left]>.tippy-arrow:before{border-width:8px 0 8px 8px;border-left-color:initial;right:-7px;transform-origin:center left}.tippy-box[data-placement^=right]>.tippy-arrow{left:0}.tippy-box[data-placement^=right]>.tippy-arrow:before{left:-7px;border-width:8px 8px 8px 0;border-right-color:initial;transform-origin:center right}.tippy-box[data-inertia][data-state=visible]{transition-timing-function:cubic-bezier(.54,1.5,.38,1.11)}.tippy-arrow{width:16px;height:16px;color:#333}.tippy-arrow:before{content:"";position:absolute;border-color:transparent;border-style:solid}.tippy-content{position:relative;padding:5px 9px;z-index:1} diff --git a/public/build/assets/app-e6b0cd9c.js b/public/build/assets/app-e6b0cd9c.js deleted file mode 100644 index 91744cc..0000000 --- a/public/build/assets/app-e6b0cd9c.js +++ /dev/null @@ -1,101 +0,0 @@ -var Ps=Object.create,Ya=Object.defineProperty,ks=Object.getOwnPropertyDescriptor,Xa=Object.getOwnPropertyNames,Rs=Object.getPrototypeOf,Ns=Object.prototype.hasOwnProperty,Gt=(e,r)=>function(){return r||(0,e[Xa(e)[0]])((r={exports:{}}).exports,r),r.exports},Ls=(e,r,i,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let s of Xa(r))!Ns.call(e,s)&&s!==i&&Ya(e,s,{get:()=>r[s],enumerable:!(a=ks(r,s))||a.enumerable});return e},Ge=(e,r,i)=>(i=e!=null?Ps(Rs(e)):{},Ls(r||!e||!e.__esModule?Ya(i,"default",{value:e,enumerable:!0}):i,e)),ht=Gt({"../alpine/packages/alpinejs/dist/module.cjs.js"(e,r){var i=Object.create,a=Object.defineProperty,s=Object.getOwnPropertyDescriptor,l=Object.getOwnPropertyNames,y=Object.getPrototypeOf,v=Object.prototype.hasOwnProperty,B=(t,n)=>function(){return n||(0,t[l(t)[0]])((n={exports:{}}).exports,n),n.exports},q=(t,n)=>{for(var o in n)a(t,o,{get:n[o],enumerable:!0})},ie=(t,n,o,c)=>{if(n&&typeof n=="object"||typeof n=="function")for(let p of l(n))!v.call(t,p)&&p!==o&&a(t,p,{get:()=>n[p],enumerable:!(c=s(n,p))||c.enumerable});return t},te=(t,n,o)=>(o=t!=null?i(y(t)):{},ie(n||!t||!t.__esModule?a(o,"default",{value:t,enumerable:!0}):o,t)),W=t=>ie(a({},"__esModule",{value:!0}),t),Y=B({"node_modules/@vue/shared/dist/shared.cjs.prod.js"(t){Object.defineProperty(t,"__esModule",{value:!0});function n(E,V){const J=Object.create(null),Se=E.split(",");for(let We=0;We!!J[We.toLowerCase()]:We=>!!J[We]}var o={1:"TEXT",2:"CLASS",4:"STYLE",8:"PROPS",16:"FULL_PROPS",32:"HYDRATE_EVENTS",64:"STABLE_FRAGMENT",128:"KEYED_FRAGMENT",256:"UNKEYED_FRAGMENT",512:"NEED_PATCH",1024:"DYNAMIC_SLOTS",2048:"DEV_ROOT_FRAGMENT",[-1]:"HOISTED",[-2]:"BAIL"},c={1:"STABLE",2:"DYNAMIC",3:"FORWARDED"},p="Infinity,undefined,NaN,isFinite,isNaN,parseFloat,parseInt,decodeURI,decodeURIComponent,encodeURI,encodeURIComponent,Math,Number,Date,Array,Object,Boolean,String,RegExp,Map,Set,JSON,Intl,BigInt",d=n(p),g=2;function w(E,V=0,J=E.length){let Se=E.split(/(\r?\n)/);const We=Se.filter((St,ft)=>ft%2===1);Se=Se.filter((St,ft)=>ft%2===0);let nt=0;const mt=[];for(let St=0;St=V){for(let ft=St-g;ft<=St+g||J>nt;ft++){if(ft<0||ft>=Se.length)continue;const Ln=ft+1;mt.push(`${Ln}${" ".repeat(Math.max(3-String(Ln).length,0))}| ${Se[ft]}`);const Ar=Se[ft].length,Mn=We[ft]&&We[ft].length||0;if(ft===St){const Tr=V-(nt-(Ar+Mn)),vi=Math.max(1,J>nt?Ar-Tr:J-V);mt.push(" | "+" ".repeat(Tr)+"^".repeat(vi))}else if(ft>St){if(J>nt){const Tr=Math.max(Math.min(J-nt,Ar),1);mt.push(" | "+"^".repeat(Tr))}nt+=Ar+Mn}}break}return mt.join(` -`)}var N="itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly",Z=n(N),Ne=n(N+",async,autofocus,autoplay,controls,default,defer,disabled,hidden,loop,open,required,reversed,scoped,seamless,checked,muted,multiple,selected"),et=/[>/="'\u0009\u000a\u000c\u0020]/,Fe={};function Je(E){if(Fe.hasOwnProperty(E))return Fe[E];const V=et.test(E);return V&&console.error(`unsafe attribute name: ${E}`),Fe[E]=!V}var Tt={acceptCharset:"accept-charset",className:"class",htmlFor:"for",httpEquiv:"http-equiv"},$t=n("animation-iteration-count,border-image-outset,border-image-slice,border-image-width,box-flex,box-flex-group,box-ordinal-group,column-count,columns,flex,flex-grow,flex-positive,flex-shrink,flex-negative,flex-order,grid-row,grid-row-end,grid-row-span,grid-row-start,grid-column,grid-column-end,grid-column-span,grid-column-start,font-weight,line-clamp,line-height,opacity,order,orphans,tab-size,widows,z-index,zoom,fill-opacity,flood-opacity,stop-opacity,stroke-dasharray,stroke-dashoffset,stroke-miterlimit,stroke-opacity,stroke-width"),xe=n("accept,accept-charset,accesskey,action,align,allow,alt,async,autocapitalize,autocomplete,autofocus,autoplay,background,bgcolor,border,buffered,capture,challenge,charset,checked,cite,class,code,codebase,color,cols,colspan,content,contenteditable,contextmenu,controls,coords,crossorigin,csp,data,datetime,decoding,default,defer,dir,dirname,disabled,download,draggable,dropzone,enctype,enterkeyhint,for,form,formaction,formenctype,formmethod,formnovalidate,formtarget,headers,height,hidden,high,href,hreflang,http-equiv,icon,id,importance,integrity,ismap,itemprop,keytype,kind,label,lang,language,loading,list,loop,low,manifest,max,maxlength,minlength,media,min,multiple,muted,name,novalidate,open,optimum,pattern,ping,placeholder,poster,preload,radiogroup,readonly,referrerpolicy,rel,required,reversed,rows,rowspan,sandbox,scope,scoped,selected,shape,size,sizes,slot,span,spellcheck,src,srcdoc,srclang,srcset,start,step,style,summary,tabindex,target,title,translate,type,usemap,value,width,wrap");function Ue(E){if(Rt(E)){const V={};for(let J=0;J{if(J){const Se=J.split(He);Se.length>1&&(V[Se[0].trim()]=Se[1].trim())}}),V}function kt(E){let V="";if(!E)return V;for(const J in E){const Se=E[J],We=J.startsWith("--")?J:ur(J);(or(Se)||typeof Se=="number"&&$t(We))&&(V+=`${We}:${Se};`)}return V}function Bt(E){let V="";if(or(E))V=E;else if(Rt(E))for(let J=0;J]/;function si(E){const V=""+E,J=oi.exec(V);if(!J)return V;let Se="",We,nt,mt=0;for(nt=J.index;nt||--!>|yr(J,V))}var wn=E=>E==null?"":Ut(E)?JSON.stringify(E,ci,2):String(E),ci=(E,V)=>ar(V)?{[`Map(${V.size})`]:[...V.entries()].reduce((J,[Se,We])=>(J[`${Se} =>`]=We,J),{})}:Nt(V)?{[`Set(${V.size})`]:[...V.values()]}:Ut(V)&&!Rt(V)&&!An(V)?String(V):V,fi=["bigInt","optionalChaining","nullishCoalescingOperator"],Yr={},Xr=[],Qr=()=>{},_r=()=>!1,wr=/^on[^a-z]/,xr=E=>wr.test(E),Sr=E=>E.startsWith("onUpdate:"),xn=Object.assign,Sn=(E,V)=>{const J=E.indexOf(V);J>-1&&E.splice(J,1)},En=Object.prototype.hasOwnProperty,On=(E,V)=>En.call(E,V),Rt=Array.isArray,ar=E=>sr(E)==="[object Map]",Nt=E=>sr(E)==="[object Set]",Zr=E=>E instanceof Date,en=E=>typeof E=="function",or=E=>typeof E=="string",di=E=>typeof E=="symbol",Ut=E=>E!==null&&typeof E=="object",Er=E=>Ut(E)&&en(E.then)&&en(E.catch),Cn=Object.prototype.toString,sr=E=>Cn.call(E),pi=E=>sr(E).slice(8,-1),An=E=>sr(E)==="[object Object]",Tn=E=>or(E)&&E!=="NaN"&&E[0]!=="-"&&""+parseInt(E,10)===E,Pn=n(",key,ref,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),lr=E=>{const V=Object.create(null);return J=>V[J]||(V[J]=E(J))},kn=/-(\w)/g,hi=lr(E=>E.replace(kn,(V,J)=>J?J.toUpperCase():"")),gi=/\B([A-Z])/g,ur=lr(E=>E.replace(gi,"-$1").toLowerCase()),Rn=lr(E=>E.charAt(0).toUpperCase()+E.slice(1)),tn=lr(E=>E?`on${Rn(E)}`:""),mi=(E,V)=>E!==V&&(E===E||V===V),Or=(E,V)=>{for(let J=0;J{Object.defineProperty(E,V,{configurable:!0,enumerable:!1,value:J})},rn=E=>{const V=parseFloat(E);return isNaN(V)?E:V},Nn,qe=()=>Nn||(Nn=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});t.EMPTY_ARR=Xr,t.EMPTY_OBJ=Yr,t.NO=_r,t.NOOP=Qr,t.PatchFlagNames=o,t.babelParserDefaultPlugins=fi,t.camelize=hi,t.capitalize=Rn,t.def=Cr,t.escapeHtml=si,t.escapeHtmlComment=li,t.extend=xn,t.generateCodeFrame=w,t.getGlobalThis=qe,t.hasChanged=mi,t.hasOwn=On,t.hyphenate=ur,t.invokeArrayFns=Or,t.isArray=Rt,t.isBooleanAttr=Ne,t.isDate=Zr,t.isFunction=en,t.isGloballyWhitelisted=d,t.isHTMLTag=vr,t.isIntegerKey=Tn,t.isKnownAttr=xe,t.isMap=ar,t.isModelListener=Sr,t.isNoUnitNumericStyleProp=$t,t.isObject=Ut,t.isOn=xr,t.isPlainObject=An,t.isPromise=Er,t.isReservedProp=Pn,t.isSSRSafeAttrName=Je,t.isSVGTag=ai,t.isSet=Nt,t.isSpecialBooleanAttr=Z,t.isString=or,t.isSymbol=di,t.isVoidTag=br,t.looseEqual=yr,t.looseIndexOf=_n,t.makeMap=n,t.normalizeClass=Bt,t.normalizeStyle=Ue,t.objectToString=Cn,t.parseStringStyle=bt,t.propsToAttrMap=Tt,t.remove=Sn,t.slotFlagsText=c,t.stringifyStyle=kt,t.toDisplayString=wn,t.toHandlerKey=tn,t.toNumber=rn,t.toRawType=pi,t.toTypeString=sr}}),S=B({"node_modules/@vue/shared/index.js"(t,n){n.exports=Y()}}),m=B({"node_modules/@vue/reactivity/dist/reactivity.cjs.prod.js"(t){Object.defineProperty(t,"__esModule",{value:!0});var n=S(),o=new WeakMap,c=[],p,d=Symbol(""),g=Symbol("");function w(u){return u&&u._isEffect===!0}function N(u,P=n.EMPTY_OBJ){w(u)&&(u=u.raw);const j=et(u,P);return P.lazy||j(),j}function Z(u){u.active&&(Fe(u),u.options.onStop&&u.options.onStop(),u.active=!1)}var Ne=0;function et(u,P){const j=function(){if(!j.active)return u();if(!c.includes(j)){Fe(j);try{return xe(),c.push(j),p=j,u()}finally{c.pop(),Ue(),p=c[c.length-1]}}};return j.id=Ne++,j.allowRecurse=!!P.allowRecurse,j._isEffect=!0,j.active=!0,j.raw=u,j.deps=[],j.options=P,j}function Fe(u){const{deps:P}=u;if(P.length){for(let j=0;j{Ot&&Ot.forEach(Lt=>{(Lt!==p||Lt.allowRecurse)&&at.add(Lt)})};if(P==="clear")De.forEach(yt);else if(j==="length"&&n.isArray(u))De.forEach((Ot,Lt)=>{(Lt==="length"||Lt>=ue)&&yt(Ot)});else switch(j!==void 0&&yt(De.get(j)),P){case"add":n.isArray(u)?n.isIntegerKey(j)&&yt(De.get("length")):(yt(De.get(d)),n.isMap(u)&&yt(De.get(g)));break;case"delete":n.isArray(u)||(yt(De.get(d)),n.isMap(u)&&yt(De.get(g)));break;case"set":n.isMap(u)&&yt(De.get(d));break}const nn=Ot=>{Ot.options.scheduler?Ot.options.scheduler(Ot):Ot()};at.forEach(nn)}var bt=n.makeMap("__proto__,__v_isRef,__isVue"),kt=new Set(Object.getOwnPropertyNames(Symbol).map(u=>Symbol[u]).filter(n.isSymbol)),Bt=br(),mr=br(!1,!0),Jr=br(!0),Gr=br(!0,!0),vr=ai();function ai(){const u={};return["includes","indexOf","lastIndexOf"].forEach(P=>{u[P]=function(...j){const ue=qe(this);for(let me=0,De=this.length;me{u[P]=function(...j){$t();const ue=qe(this)[P].apply(this,j);return Ue(),ue}}),u}function br(u=!1,P=!1){return function(ue,ee,me){if(ee==="__v_isReactive")return!u;if(ee==="__v_isReadonly")return u;if(ee==="__v_raw"&&me===(u?P?kn:lr:P?Pn:Tn).get(ue))return ue;const De=n.isArray(ue);if(!u&&De&&n.hasOwn(vr,ee))return Reflect.get(vr,ee,me);const at=Reflect.get(ue,ee,me);return(n.isSymbol(ee)?kt.has(ee):bt(ee))||(u||Me(ue,"get",ee),P)?at:J(at)?!De||!n.isIntegerKey(ee)?at.value:at:n.isObject(at)?u?tn(at):ur(at):at}}var oi=yn(),si=yn(!0);function yn(u=!1){return function(j,ue,ee,me){let De=j[ue];if(!u&&(ee=qe(ee),De=qe(De),!n.isArray(j)&&J(De)&&!J(ee)))return De.value=ee,!0;const at=n.isArray(j)&&n.isIntegerKey(ue)?Number(ue)n.isObject(u)?ur(u):u,Xr=u=>n.isObject(u)?tn(u):u,Qr=u=>u,_r=u=>Reflect.getPrototypeOf(u);function wr(u,P,j=!1,ue=!1){u=u.__v_raw;const ee=qe(u),me=qe(P);P!==me&&!j&&Me(ee,"get",P),!j&&Me(ee,"get",me);const{has:De}=_r(ee),at=ue?Qr:j?Xr:Yr;if(De.call(ee,P))return at(u.get(P));if(De.call(ee,me))return at(u.get(me));u!==ee&&u.get(P)}function xr(u,P=!1){const j=this.__v_raw,ue=qe(j),ee=qe(u);return u!==ee&&!P&&Me(ue,"has",u),!P&&Me(ue,"has",ee),u===ee?j.has(u):j.has(u)||j.has(ee)}function Sr(u,P=!1){return u=u.__v_raw,!P&&Me(qe(u),"iterate",d),Reflect.get(u,"size",u)}function xn(u){u=qe(u);const P=qe(this);return _r(P).has.call(P,u)||(P.add(u),He(P,"add",u,u)),this}function Sn(u,P){P=qe(P);const j=qe(this),{has:ue,get:ee}=_r(j);let me=ue.call(j,u);me||(u=qe(u),me=ue.call(j,u));const De=ee.call(j,u);return j.set(u,P),me?n.hasChanged(P,De)&&He(j,"set",u,P):He(j,"add",u,P),this}function En(u){const P=qe(this),{has:j,get:ue}=_r(P);let ee=j.call(P,u);ee||(u=qe(u),ee=j.call(P,u)),ue&&ue.call(P,u);const me=P.delete(u);return ee&&He(P,"delete",u,void 0),me}function On(){const u=qe(this),P=u.size!==0,j=u.clear();return P&&He(u,"clear",void 0,void 0),j}function Rt(u,P){return function(ue,ee){const me=this,De=me.__v_raw,at=qe(De),yt=P?Qr:u?Xr:Yr;return!u&&Me(at,"iterate",d),De.forEach((nn,Ot)=>ue.call(ee,yt(nn),yt(Ot),me))}}function ar(u,P,j){return function(...ue){const ee=this.__v_raw,me=qe(ee),De=n.isMap(me),at=u==="entries"||u===Symbol.iterator&&De,yt=u==="keys"&&De,nn=ee[u](...ue),Ot=j?Qr:P?Xr:Yr;return!P&&Me(me,"iterate",yt?g:d),{next(){const{value:Lt,done:bi}=nn.next();return bi?{value:Lt,done:bi}:{value:at?[Ot(Lt[0]),Ot(Lt[1])]:Ot(Lt),done:bi}},[Symbol.iterator](){return this}}}}function Nt(u){return function(...P){return u==="delete"?!1:this}}function Zr(){const u={get(me){return wr(this,me)},get size(){return Sr(this)},has:xr,add:xn,set:Sn,delete:En,clear:On,forEach:Rt(!1,!1)},P={get(me){return wr(this,me,!1,!0)},get size(){return Sr(this)},has:xr,add:xn,set:Sn,delete:En,clear:On,forEach:Rt(!1,!0)},j={get(me){return wr(this,me,!0)},get size(){return Sr(this,!0)},has(me){return xr.call(this,me,!0)},add:Nt("add"),set:Nt("set"),delete:Nt("delete"),clear:Nt("clear"),forEach:Rt(!0,!1)},ue={get(me){return wr(this,me,!0,!0)},get size(){return Sr(this,!0)},has(me){return xr.call(this,me,!0)},add:Nt("add"),set:Nt("set"),delete:Nt("delete"),clear:Nt("clear"),forEach:Rt(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(me=>{u[me]=ar(me,!1,!1),j[me]=ar(me,!0,!1),P[me]=ar(me,!1,!0),ue[me]=ar(me,!0,!0)}),[u,j,P,ue]}var[en,or,di,Ut]=Zr();function Er(u,P){const j=P?u?Ut:di:u?or:en;return(ue,ee,me)=>ee==="__v_isReactive"?!u:ee==="__v_isReadonly"?u:ee==="__v_raw"?ue:Reflect.get(n.hasOwn(j,ee)&&ee in ue?j:ue,ee,me)}var Cn={get:Er(!1,!1)},sr={get:Er(!1,!0)},pi={get:Er(!0,!1)},An={get:Er(!0,!0)},Tn=new WeakMap,Pn=new WeakMap,lr=new WeakMap,kn=new WeakMap;function hi(u){switch(u){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function gi(u){return u.__v_skip||!Object.isExtensible(u)?0:hi(n.toRawType(u))}function ur(u){return u&&u.__v_isReadonly?u:Or(u,!1,_n,Cn,Tn)}function Rn(u){return Or(u,!1,ci,sr,Pn)}function tn(u){return Or(u,!0,wn,pi,lr)}function mi(u){return Or(u,!0,fi,An,kn)}function Or(u,P,j,ue,ee){if(!n.isObject(u)||u.__v_raw&&!(P&&u.__v_isReactive))return u;const me=ee.get(u);if(me)return me;const De=gi(u);if(De===0)return u;const at=new Proxy(u,De===2?ue:j);return ee.set(u,at),at}function Cr(u){return rn(u)?Cr(u.__v_raw):!!(u&&u.__v_isReactive)}function rn(u){return!!(u&&u.__v_isReadonly)}function Nn(u){return Cr(u)||rn(u)}function qe(u){return u&&qe(u.__v_raw)||u}function E(u){return n.def(u,"__v_skip",!0),u}var V=u=>n.isObject(u)?ur(u):u;function J(u){return!!(u&&u.__v_isRef===!0)}function Se(u){return mt(u)}function We(u){return mt(u,!0)}var nt=class{constructor(u,P=!1){this._shallow=P,this.__v_isRef=!0,this._rawValue=P?u:qe(u),this._value=P?u:V(u)}get value(){return Me(qe(this),"get","value"),this._value}set value(u){u=this._shallow?u:qe(u),n.hasChanged(u,this._rawValue)&&(this._rawValue=u,this._value=this._shallow?u:V(u),He(qe(this),"set","value",u))}};function mt(u,P=!1){return J(u)?u:new nt(u,P)}function St(u){He(qe(u),"set","value",void 0)}function ft(u){return J(u)?u.value:u}var Ln={get:(u,P,j)=>ft(Reflect.get(u,P,j)),set:(u,P,j,ue)=>{const ee=u[P];return J(ee)&&!J(j)?(ee.value=j,!0):Reflect.set(u,P,j,ue)}};function Ar(u){return Cr(u)?u:new Proxy(u,Ln)}var Mn=class{constructor(u){this.__v_isRef=!0;const{get:P,set:j}=u(()=>Me(this,"get","value"),()=>He(this,"set","value"));this._get=P,this._set=j}get value(){return this._get()}set value(u){this._set(u)}};function Tr(u){return new Mn(u)}function vi(u){const P=n.isArray(u)?new Array(u.length):{};for(const j in u)P[j]=va(u,j);return P}var Cs=class{constructor(u,P){this._object=u,this._key=P,this.__v_isRef=!0}get value(){return this._object[this._key]}set value(u){this._object[this._key]=u}};function va(u,P){return J(u[P])?u[P]:new Cs(u,P)}var As=class{constructor(u,P,j){this._setter=P,this._dirty=!0,this.__v_isRef=!0,this.effect=N(u,{lazy:!0,scheduler:()=>{this._dirty||(this._dirty=!0,He(qe(this),"set","value"))}}),this.__v_isReadonly=j}get value(){const u=qe(this);return u._dirty&&(u._value=this.effect(),u._dirty=!1),Me(u,"get","value"),u._value}set value(u){this._setter(u)}};function Ts(u){let P,j;return n.isFunction(u)?(P=u,j=n.NOOP):(P=u.get,j=u.set),new As(P,j,n.isFunction(u)||!u.set)}t.ITERATE_KEY=d,t.computed=Ts,t.customRef=Tr,t.effect=N,t.enableTracking=xe,t.isProxy=Nn,t.isReactive=Cr,t.isReadonly=rn,t.isRef=J,t.markRaw=E,t.pauseTracking=$t,t.proxyRefs=Ar,t.reactive=ur,t.readonly=tn,t.ref=Se,t.resetTracking=Ue,t.shallowReactive=Rn,t.shallowReadonly=mi,t.shallowRef=We,t.stop=Z,t.toRaw=qe,t.toRef=va,t.toRefs=vi,t.track=Me,t.trigger=He,t.triggerRef=St,t.unref=ft}}),b=B({"node_modules/@vue/reactivity/index.js"(t,n){n.exports=m()}}),A={};q(A,{Alpine:()=>ma,default:()=>Os}),r.exports=W(A);var k=!1,L=!1,U=[],Te=-1;function I(t){O(t)}function O(t){U.includes(t)||U.push(t),re()}function R(t){let n=U.indexOf(t);n!==-1&&n>Te&&U.splice(n,1)}function re(){!L&&!k&&(k=!0,queueMicrotask(be))}function be(){k=!1,L=!0;for(let t=0;tt.effect(n,{scheduler:o=>{Xe?I(o):o()}}),Ye=t.raw}function pt(t){Q=t}function wt(t){let n=()=>{};return[c=>{let p=Q(c);return t._x_effects||(t._x_effects=new Set,t._x_runEffects=()=>{t._x_effects.forEach(d=>d())}),t._x_effects.add(p),n=()=>{p!==void 0&&(t._x_effects.delete(p),ke(p))},p},()=>{n()}]}function Et(t,n){let o=!0,c,p=Q(()=>{let d=t();JSON.stringify(d),o?c=d:queueMicrotask(()=>{n(d,c),c=d}),o=!1});return()=>ke(p)}function _e(t,n,o={}){t.dispatchEvent(new CustomEvent(n,{detail:o,bubbles:!0,composed:!0,cancelable:!0}))}function le(t,n){if(typeof ShadowRoot=="function"&&t instanceof ShadowRoot){Array.from(t.children).forEach(p=>le(p,n));return}let o=!1;if(n(t,()=>o=!0),o)return;let c=t.firstElementChild;for(;c;)le(c,n),c=c.nextElementSibling}function ce(t,...n){console.warn(`Alpine Warning: ${t}`,...n)}var Ee=!1;function ge(){Ee&&ce("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),Ee=!0,document.body||ce("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's ` + diff --git a/resources/views/components/icon-button.blade.php b/resources/views/components/icon-button.blade.php index 01a6fe7..433ba86 100644 --- a/resources/views/components/icon-button.blade.php +++ b/resources/views/components/icon-button.blade.php @@ -1,15 +1,18 @@ -@props(['href']) +@props([ + "href", +]) @php - $class = 'w-max inline-flex items-center justify-center px-2 py-1 font-semibold capitalize transition hover:opacity-50 outline-0 focus:ring focus:ring-primary-200 disabled:opacity-25 dark:focus:ring-primary-700 dark:focus:ring-opacity-40'; + $class = + "inline-flex w-max items-center justify-center px-2 py-1 font-semibold capitalize outline-0 transition hover:opacity-50 focus:ring focus:ring-primary-200 disabled:opacity-25 dark:focus:ring-primary-700 dark:focus:ring-opacity-40"; @endphp -@if(isset($href)) - merge(['class' => $class]) }}> +@if (isset($href)) + merge(["class" => $class]) }}> {{ $slot }} @else - @endif diff --git a/resources/views/components/input-error.blade.php b/resources/views/components/input-error.blade.php index ad95f6b..4351d5a 100644 --- a/resources/views/components/input-error.blade.php +++ b/resources/views/components/input-error.blade.php @@ -1,7 +1,9 @@ -@props(['messages']) +@props([ + "messages", +]) @if ($messages) -
    merge(['class' => 'text-sm text-red-600 dark:text-red-400 space-y-1']) }}> +
      merge(["class" => "text-sm text-red-600 dark:text-red-400 space-y-1"]) }}> @foreach ((array) $messages as $message)
    • {{ $message }}
    • @endforeach diff --git a/resources/views/components/input-help.blade.php b/resources/views/components/input-help.blade.php index 808655e..62faf84 100644 --- a/resources/views/components/input-help.blade.php +++ b/resources/views/components/input-help.blade.php @@ -1 +1,5 @@ -

      merge(['class' => 'mt-2 text-sm text-gray-500 dark:text-gray-300']) }}>{{ $slot }}

      +

      merge(["class" => "mt-2 text-sm text-gray-500 dark:text-gray-300"]) }} +> + {{ $slot }} +

      diff --git a/resources/views/components/input-label.blade.php b/resources/views/components/input-label.blade.php index e93b059..8afad86 100644 --- a/resources/views/components/input-label.blade.php +++ b/resources/views/components/input-label.blade.php @@ -1,5 +1,9 @@ -@props(['value']) +@props([ + "value", +]) -