diff --git a/app/Http/Controllers/Controller.php b/app/Http/Controllers/Controller.php index c9620f4bd..60549c381 100644 --- a/app/Http/Controllers/Controller.php +++ b/app/Http/Controllers/Controller.php @@ -8,6 +8,7 @@ use App\Models\Server; use App\Models\TeamInvitation; use App\Models\User; +use App\Models\S3Storage; use Illuminate\Foundation\Auth\Access\AuthorizesRequests; use Illuminate\Foundation\Validation\ValidatesRequests; use Illuminate\Routing\Controller as BaseController; @@ -39,7 +40,7 @@ public function dashboard() { $projects = Project::ownedByCurrentTeam()->get(); $servers = Server::ownedByCurrentTeam()->get(); - + $s3s = S3Storage::ownedByCurrentTeam()->get(); $resources = 0; foreach ($projects as $project) { $resources += $project->applications->count(); @@ -49,6 +50,7 @@ public function dashboard() 'servers' => $servers->count(), 'projects' => $projects->count(), 'resources' => $resources, + 's3s' => $s3s, ]); } public function settings() @@ -83,6 +85,18 @@ public function team() 'invitations' => $invitations, ]); } + public function storages() { + $s3 = S3Storage::ownedByCurrentTeam()->get(); + return view('team.storages.all', [ + 's3' => $s3, + ]); + } + public function storages_show() { + $storage = S3Storage::ownedByCurrentTeam()->whereUuid(request()->storage_uuid)->firstOrFail(); + return view('team.storages.show', [ + 'storage' => $storage, + ]); + } public function members() { $invitations = []; @@ -136,4 +150,4 @@ public function revokeInvitation() throw $th; } } -} +} \ No newline at end of file diff --git a/app/Http/Livewire/S3Test.php b/app/Http/Livewire/S3Test.php new file mode 100644 index 000000000..57ef3728e --- /dev/null +++ b/app/Http/Livewire/S3Test.php @@ -0,0 +1,37 @@ +s3 = S3Storage::first(); + ray($this->s3); + } + public function save() { + try { + $this->validate([ + 'file' => 'required|max:150', // 1MB Max + ]); + set_s3_target($this->s3); + $this->file->storeAs('files', $this->file->getClientOriginalName(),'custom-s3'); + $this->emit('success', 'File uploaded successfully.'); + } catch (\Throwable $th) { + return general_error_handler($th, $this, false); + } + + } + public function get_files() + { + set_s3_target($this->s3); + dd(Storage::disk('custom-s3')->files('files')); + } +} \ No newline at end of file diff --git a/app/Http/Livewire/Team/Storage/Create.php b/app/Http/Livewire/Team/Storage/Create.php new file mode 100644 index 000000000..5ac19d743 --- /dev/null +++ b/app/Http/Livewire/Team/Storage/Create.php @@ -0,0 +1,79 @@ + 'nullable|min:3|max:255', + 'description' => 'nullable|min:3|max:255', + 'region' => 'required|max:255', + 'key' => 'required|max:255', + 'secret' => 'required|max:255', + 'bucket' => 'required|max:255', + 'endpoint' => 'nullable|url|max:255', + ]; + protected $validationAttributes = [ + 'name' => 'Name', + 'description' => 'Description', + 'region' => 'Region', + 'key' => 'Key', + 'secret' => "Secret", + 'bucket' => 'Bucket', + 'endpoint' => 'Endpoint', + ]; + public function mount() { + if (isDev()) { + $this->name = 'Local MinIO'; + $this->description = 'Local MinIO'; + $this->key = 'minioadmin'; + $this->secret = 'minioadmin'; + $this->bucket = 'local'; + $this->endpoint = 'http://coolify-minio:9000'; + } + } + private function test_s3_connection() { + try { + $this->storage->testConnection(); + return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + } catch(\Throwable $th) { + return general_error_handler($th, $this); + } + } + public function submit() { + try { + $this->validate(); + $this->storage = new S3Storage(); + $this->storage->name = $this->name; + $this->storage->description = $this->description; + $this->storage->region = $this->region; + $this->storage->key = $this->key; + $this->storage->secret = $this->secret; + $this->storage->bucket = $this->bucket; + if (empty($this->endpoint)) { + $this->storage->endpoint = "https://s3.{$this->region}.amazonaws.com"; + } else { + $this->storage->endpoint = $this->endpoint; + } + $this->storage->team_id = auth()->user()->currentTeam()->id; + $this->storage->testConnection(); + $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + $this->storage->save(); + return redirect()->route('team.storages.show', $this->storage->uuid); + } catch(\Throwable $th) { + return general_error_handler($th, $this); + } + + } +} \ No newline at end of file diff --git a/app/Http/Livewire/Team/Storage/Form.php b/app/Http/Livewire/Team/Storage/Form.php new file mode 100644 index 000000000..bb106e378 --- /dev/null +++ b/app/Http/Livewire/Team/Storage/Form.php @@ -0,0 +1,57 @@ + 'nullable|min:3|max:255', + 'storage.description' => 'nullable|min:3|max:255', + 'storage.region' => 'required|max:255', + 'storage.key' => 'required|max:255', + 'storage.secret' => 'required|max:255', + 'storage.bucket' => 'required|max:255', + 'storage.endpoint' => 'required|url|max:255', + ]; + protected $validationAttributes = [ + 'storage.name' => 'Name', + 'storage.description' => 'Description', + 'storage.region' => 'Region', + 'storage.key' => 'Key', + 'storage.secret' => "Secret", + 'storage.bucket' => 'Bucket', + 'storage.endpoint' => 'Endpoint', + ]; + public function test_s3_connection() { + try { + $this->storage->testConnection(); + return $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + } catch(\Throwable $th) { + return general_error_handler($th, $this); + } + } + public function delete() { + try { + $this->storage->delete(); + return redirect()->route('team.storages.all'); + } catch(\Throwable $th) { + return general_error_handler($th, $this); + } + } + public function submit() + { + $this->validate(); + try { + $this->storage->testConnection(); + $this->emit('success', 'Connection is working. Tested with "ListObjectsV2" action.'); + $this->storage->save(); + $this->emit('success', 'Storage settings saved.'); + } catch (\Throwable $th) { + return general_error_handler($th, $this); + } + } +} \ No newline at end of file diff --git a/app/Models/S3Storage.php b/app/Models/S3Storage.php new file mode 100644 index 000000000..46fc37783 --- /dev/null +++ b/app/Models/S3Storage.php @@ -0,0 +1,25 @@ +concat(['id']); + return S3Storage::whereTeamId(session('currentTeam')->id)->select($selectArray->all())->orderBy('name'); + } + public function awsUrl() { + return "{$this->endpoint}/{$this->bucket}"; + } + public function testConnection() { + set_s3_target($this); + return \Storage::disk('custom-s3')->files(); + } + +} \ No newline at end of file diff --git a/bootstrap/helpers/s3.php b/bootstrap/helpers/s3.php new file mode 100644 index 000000000..12f53cd02 --- /dev/null +++ b/bootstrap/helpers/s3.php @@ -0,0 +1,22 @@ +endpoint) { + $is_digital_ocean = Str::contains($s3->endpoint,'digitaloceanspaces.com'); + } + config()->set('filesystems.disks.custom-s3', [ + 'driver' => 's3', + 'region' => $s3['region'], + 'key' => $s3['key'], + 'secret' => $s3['secret'], + 'bucket' => $s3['bucket'], + 'endpoint' => $s3['endpoint'], + 'use_path_style_endpoint' => true, + 'bucket_endpoint' => $is_digital_ocean, + 'aws_url' => $s3->awsUrl(), + ]); +} \ No newline at end of file diff --git a/composer.json b/composer.json index 9f6d3ae10..157b15e5b 100644 --- a/composer.json +++ b/composer.json @@ -18,6 +18,7 @@ "laravel/tinker": "^v2.8.1", "laravel/ui": "^4.2", "lcobucci/jwt": "^5.0.0", + "league/flysystem-aws-s3-v3": "^3.0", "livewire/livewire": "^v2.12.3", "masmerise/livewire-toaster": "^1.2", "nubs/random-name-generator": "^2.2", diff --git a/composer.lock b/composer.lock index 17732b569..f9a5cdb41 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,157 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "fa99d49c392ba6560f15eb6f3c5e4baa", + "content-hash": "ca293cd95f3fcb41f9122aaf474adb69", "packages": [ + { + "name": "aws/aws-crt-php", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/awslabs/aws-crt-php.git", + "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/awslabs/aws-crt-php/zipball/2f1dc7b7eda080498be96a4a6d683a41583030e9", + "reference": "2f1dc7b7eda080498be96a4a6d683a41583030e9", + "shasum": "" + }, + "require": { + "php": ">=5.5" + }, + "require-dev": { + "phpunit/phpunit": "^4.8.35||^5.6.3||^9.5", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "ext-awscrt": "Make sure you install awscrt native extension to use any of the functionality." + }, + "type": "library", + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "AWS SDK Common Runtime Team", + "email": "aws-sdk-common-runtime@amazon.com" + } + ], + "description": "AWS Common Runtime for PHP", + "homepage": "https://github.com/awslabs/aws-crt-php", + "keywords": [ + "amazon", + "aws", + "crt", + "sdk" + ], + "support": { + "issues": "https://github.com/awslabs/aws-crt-php/issues", + "source": "https://github.com/awslabs/aws-crt-php/tree/v1.2.2" + }, + "time": "2023-07-20T16:49:55+00:00" + }, + { + "name": "aws/aws-sdk-php", + "version": "3.269.0", + "source": { + "type": "git", + "url": "https://github.com/aws/aws-sdk-php.git", + "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "reference": "6d759ef9f24f0c7f271baf8014f41fc0cfdfbf78", + "shasum": "" + }, + "require": { + "aws/aws-crt-php": "^1.0.4", + "ext-json": "*", + "ext-pcre": "*", + "ext-simplexml": "*", + "guzzlehttp/guzzle": "^6.5.8 || ^7.4.5", + "guzzlehttp/promises": "^1.4.0", + "guzzlehttp/psr7": "^1.9.1 || ^2.4.5", + "mtdowling/jmespath.php": "^2.6", + "php": ">=5.5" + }, + "require-dev": { + "andrewsville/php-token-reflection": "^1.4", + "aws/aws-php-sns-message-validator": "~1.0", + "behat/behat": "~3.0", + "composer/composer": "^1.10.22", + "dms/phpunit-arraysubset-asserts": "^0.4.0", + "doctrine/cache": "~1.4", + "ext-dom": "*", + "ext-openssl": "*", + "ext-pcntl": "*", + "ext-sockets": "*", + "nette/neon": "^2.3", + "paragonie/random_compat": ">= 2", + "phpunit/phpunit": "^4.8.35 || ^5.6.3 || ^9.5", + "psr/cache": "^1.0", + "psr/http-message": "^1.0", + "psr/simple-cache": "^1.0", + "sebastian/comparator": "^1.2.3 || ^4.0", + "yoast/phpunit-polyfills": "^1.0" + }, + "suggest": { + "aws/aws-php-sns-message-validator": "To validate incoming SNS notifications", + "doctrine/cache": "To use the DoctrineCacheAdapter", + "ext-curl": "To send requests using cURL", + "ext-openssl": "Allows working with CloudFront private distributions and verifying received SNS messages", + "ext-sockets": "To use client-side monitoring" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "3.0-dev" + } + }, + "autoload": { + "files": [ + "src/functions.php" + ], + "psr-4": { + "Aws\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "Apache-2.0" + ], + "authors": [ + { + "name": "Amazon Web Services", + "homepage": "http://aws.amazon.com" + } + ], + "description": "AWS SDK for PHP - Use Amazon Web Services in your PHP project", + "homepage": "http://aws.amazon.com/sdkforphp", + "keywords": [ + "amazon", + "aws", + "cloud", + "dynamodb", + "ec2", + "glacier", + "s3", + "sdk" + ], + "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.269.0" + }, + "time": "2023-04-26T18:21:04+00:00" + }, { "name": "bacon/bacon-qr-code", "version": "2.0.8", @@ -1206,33 +1355,29 @@ }, { "name": "guzzlehttp/promises", - "version": "2.0.0", + "version": "1.5.3", "source": { "type": "git", "url": "https://github.com/guzzle/promises.git", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6" + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/promises/zipball/3a494dc7dc1d7d12e511890177ae2d0e6c107da6", - "reference": "3a494dc7dc1d7d12e511890177ae2d0e6c107da6", + "url": "https://api.github.com/repos/guzzle/promises/zipball/67ab6e18aaa14d753cc148911d273f6e6cb6721e", + "reference": "67ab6e18aaa14d753cc148911d273f6e6cb6721e", "shasum": "" }, "require": { - "php": "^7.2.5 || ^8.0" + "php": ">=5.5" }, "require-dev": { - "bamarni/composer-bin-plugin": "^1.8.1", - "phpunit/phpunit": "^8.5.29 || ^9.5.23" + "symfony/phpunit-bridge": "^4.4 || ^5.1" }, "type": "library", - "extra": { - "bamarni-bin": { - "bin-links": true, - "forward-command": false - } - }, "autoload": { + "files": [ + "src/functions_include.php" + ], "psr-4": { "GuzzleHttp\\Promise\\": "src/" } @@ -1269,7 +1414,7 @@ ], "support": { "issues": "https://github.com/guzzle/promises/issues", - "source": "https://github.com/guzzle/promises/tree/2.0.0" + "source": "https://github.com/guzzle/promises/tree/1.5.3" }, "funding": [ { @@ -1285,20 +1430,20 @@ "type": "tidelift" } ], - "time": "2023-05-21T13:50:22+00:00" + "time": "2023-05-21T12:31:43+00:00" }, { "name": "guzzlehttp/psr7", - "version": "2.5.0", + "version": "2.6.0", "source": { "type": "git", "url": "https://github.com/guzzle/psr7.git", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6" + "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/guzzle/psr7/zipball/b635f279edd83fc275f822a1188157ffea568ff6", - "reference": "b635f279edd83fc275f822a1188157ffea568ff6", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/8bd7c33a0734ae1c5d074360512beb716bef3f77", + "reference": "8bd7c33a0734ae1c5d074360512beb716bef3f77", "shasum": "" }, "require": { @@ -1385,7 +1530,7 @@ ], "support": { "issues": "https://github.com/guzzle/psr7/issues", - "source": "https://github.com/guzzle/psr7/tree/2.5.0" + "source": "https://github.com/guzzle/psr7/tree/2.6.0" }, "funding": [ { @@ -1401,7 +1546,7 @@ "type": "tidelift" } ], - "time": "2023-04-17T16:11:26+00:00" + "time": "2023-08-03T15:06:02+00:00" }, { "name": "guzzlehttp/uri-template", @@ -2553,6 +2698,72 @@ ], "time": "2023-05-04T09:04:26+00:00" }, + { + "name": "league/flysystem-aws-s3-v3", + "version": "3.15.0", + "source": { + "type": "git", + "url": "https://github.com/thephpleague/flysystem-aws-s3-v3.git", + "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/thephpleague/flysystem-aws-s3-v3/zipball/d8de61ee10b6a607e7996cff388c5a3a663e8c8a", + "reference": "d8de61ee10b6a607e7996cff388c5a3a663e8c8a", + "shasum": "" + }, + "require": { + "aws/aws-sdk-php": "^3.220.0", + "league/flysystem": "^3.10.0", + "league/mime-type-detection": "^1.0.0", + "php": "^8.0.2" + }, + "conflict": { + "guzzlehttp/guzzle": "<7.0", + "guzzlehttp/ringphp": "<1.1.1" + }, + "type": "library", + "autoload": { + "psr-4": { + "League\\Flysystem\\AwsS3V3\\": "" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Frank de Jonge", + "email": "info@frankdejonge.nl" + } + ], + "description": "AWS S3 filesystem adapter for Flysystem.", + "keywords": [ + "Flysystem", + "aws", + "file", + "files", + "filesystem", + "s3", + "storage" + ], + "support": { + "issues": "https://github.com/thephpleague/flysystem-aws-s3-v3/issues", + "source": "https://github.com/thephpleague/flysystem-aws-s3-v3/tree/3.15.0" + }, + "funding": [ + { + "url": "https://ecologi.com/frankdejonge", + "type": "custom" + }, + { + "url": "https://github.com/frankdejonge", + "type": "github" + } + ], + "time": "2023-05-02T20:02:14+00:00" + }, { "name": "league/flysystem-local", "version": "3.15.0", @@ -2615,26 +2826,26 @@ }, { "name": "league/mime-type-detection", - "version": "1.11.0", + "version": "1.13.0", "source": { "type": "git", "url": "https://github.com/thephpleague/mime-type-detection.git", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd" + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/ff6248ea87a9f116e78edd6002e39e5128a0d4dd", - "reference": "ff6248ea87a9f116e78edd6002e39e5128a0d4dd", + "url": "https://api.github.com/repos/thephpleague/mime-type-detection/zipball/a6dfb1194a2946fcdc1f38219445234f65b35c96", + "reference": "a6dfb1194a2946fcdc1f38219445234f65b35c96", "shasum": "" }, "require": { "ext-fileinfo": "*", - "php": "^7.2 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { "friendsofphp/php-cs-fixer": "^3.2", "phpstan/phpstan": "^0.12.68", - "phpunit/phpunit": "^8.5.8 || ^9.3" + "phpunit/phpunit": "^8.5.8 || ^9.3 || ^10.0" }, "type": "library", "autoload": { @@ -2655,7 +2866,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.11.0" + "source": "https://github.com/thephpleague/mime-type-detection/tree/1.13.0" }, "funding": [ { @@ -2667,7 +2878,7 @@ "type": "tidelift" } ], - "time": "2022-04-17T13:12:02+00:00" + "time": "2023-08-05T12:09:49+00:00" }, { "name": "livewire/livewire", @@ -2911,6 +3122,67 @@ ], "time": "2023-06-21T08:46:11+00:00" }, + { + "name": "mtdowling/jmespath.php", + "version": "2.6.1", + "source": { + "type": "git", + "url": "https://github.com/jmespath/jmespath.php.git", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/jmespath/jmespath.php/zipball/9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "reference": "9b87907a81b87bc76d19a7fb2d61e61486ee9edb", + "shasum": "" + }, + "require": { + "php": "^5.4 || ^7.0 || ^8.0", + "symfony/polyfill-mbstring": "^1.17" + }, + "require-dev": { + "composer/xdebug-handler": "^1.4 || ^2.0", + "phpunit/phpunit": "^4.8.36 || ^7.5.15" + }, + "bin": [ + "bin/jp.php" + ], + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + }, + "autoload": { + "files": [ + "src/JmesPath.php" + ], + "psr-4": { + "JmesPath\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + } + ], + "description": "Declaratively specify how to extract elements from a JSON document", + "keywords": [ + "json", + "jsonpath" + ], + "support": { + "issues": "https://github.com/jmespath/jmespath.php/issues", + "source": "https://github.com/jmespath/jmespath.php/tree/2.6.1" + }, + "time": "2021-06-14T00:11:39+00:00" + }, { "name": "nesbot/carbon", "version": "2.68.1", diff --git a/config/livewire.php b/config/livewire.php new file mode 100644 index 000000000..59f8d5c4d --- /dev/null +++ b/config/livewire.php @@ -0,0 +1,158 @@ + 'App\\Http\\Livewire', + + /* + |-------------------------------------------------------------------------- + | View Path + |-------------------------------------------------------------------------- + | + | This value sets the path for Livewire component views. This affects + | file manipulation helper commands like `artisan make:livewire`. + | + */ + + 'view_path' => resource_path('views/livewire'), + + /* + |-------------------------------------------------------------------------- + | Layout + |-------------------------------------------------------------------------- + | The default layout view that will be used when rendering a component via + | Route::get('/some-endpoint', SomeComponent::class);. In this case the + | the view returned by SomeComponent will be wrapped in "layouts.app" + | + */ + + 'layout' => 'layouts.app', + + /* + |-------------------------------------------------------------------------- + | Livewire Assets URL + |-------------------------------------------------------------------------- + | + | This value sets the path to Livewire JavaScript assets, for cases where + | your app's domain root is not the correct path. By default, Livewire + | will load its JavaScript assets from the app's "relative root". + | + | Examples: "/assets", "myurl.com/app". + | + */ + + 'asset_url' => null, + + /* + |-------------------------------------------------------------------------- + | Livewire App URL + |-------------------------------------------------------------------------- + | + | This value should be used if livewire assets are served from CDN. + | Livewire will communicate with an app through this url. + | + | Examples: "https://my-app.com", "myurl.com/app". + | + */ + + 'app_url' => null, + + /* + |-------------------------------------------------------------------------- + | Livewire Endpoint Middleware Group + |-------------------------------------------------------------------------- + | + | This value sets the middleware group that will be applied to the main + | Livewire "message" endpoint (the endpoint that gets hit everytime + | a Livewire component updates). It is set to "web" by default. + | + */ + + 'middleware_group' => 'web', + + /* + |-------------------------------------------------------------------------- + | Livewire Temporary File Uploads Endpoint Configuration + |-------------------------------------------------------------------------- + | + | Livewire handles file uploads by storing uploads in a temporary directory + | before the file is validated and stored permanently. All file uploads + | are directed to a global endpoint for temporary storage. The config + | items below are used for customizing the way the endpoint works. + | + */ + + '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 gets invalidated. + ], + + /* + |-------------------------------------------------------------------------- + | Manifest File Path + |-------------------------------------------------------------------------- + | + | This value sets the path to the Livewire manifest file. + | The default should work for most cases (which is + | "/bootstrap/cache/livewire-components.php"), but for specific + | cases like when hosting on Laravel Vapor, it could be set to a different value. + | + | Example: for Laravel Vapor, it would be "/tmp/storage/bootstrap/cache/livewire-components.php". + | + */ + + 'manifest_path' => null, + + /* + |-------------------------------------------------------------------------- + | Back Button Cache + |-------------------------------------------------------------------------- + | + | This value determines whether the back button cache will be used on pages + | that contain Livewire. By disabling back button cache, it ensures that + | the back button shows the correct state of components, instead of + | potentially stale, cached data. + | + | Setting it to "false" (default) will disable back button cache. + | + */ + + 'back_button_cache' => false, + + /* + |-------------------------------------------------------------------------- + | Render On Redirect + |-------------------------------------------------------------------------- + | + | This value determines whether Livewire will render before it's redirected + | or not. Setting it to "false" (default) will mean the render method is + | skipped when redirecting. And "true" will mean the render method is + | run before redirecting. Browsers bfcache can store a potentially + | stale view if render is skipped on redirect. + | + */ + + 'render_on_redirect' => false, + +]; diff --git a/database/migrations/2023_08_07_073651_create_s3_storages_table.php b/database/migrations/2023_08_07_073651_create_s3_storages_table.php new file mode 100644 index 000000000..711707e9e --- /dev/null +++ b/database/migrations/2023_08_07_073651_create_s3_storages_table.php @@ -0,0 +1,37 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name')->nullable(); + $table->longText('description')->nullable(); + $table->string('region')->default('us-east-1'); + $table->longText('key'); + $table->longText('secret'); + $table->longText('bucket'); + $table->longText('endpoint')->nullable(); + + $table->foreignId('team_id'); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('s3_storages'); + } +}; \ No newline at end of file diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 1f19fca02..bba3978ac 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -31,6 +31,7 @@ public function run(): void ServiceSeeder::class, EnvironmentVariableSeeder::class, LocalPersistentVolumeSeeder::class, + S3StorageSeeder::class, ]); } -} +} \ No newline at end of file diff --git a/database/seeders/S3StorageSeeder.php b/database/seeders/S3StorageSeeder.php new file mode 100644 index 000000000..348455cff --- /dev/null +++ b/database/seeders/S3StorageSeeder.php @@ -0,0 +1,35 @@ + 'Local MinIO', + 'description' => 'Local MinIO S3 Storage', + 'key' => 'minioadmin', + 'secret' => 'minioadmin', + 'bucket' => 'local', + 'endpoint' => 'http://coolify-minio:9000', + 'team_id' => 0, + ]); + S3Storage::create([ + 'name' => 'DO Spaces', + 'description' => 'DO S3 Storage', + 'key' => 'DO003UBFTACPQGUXUANY', + 'secret' => 'eXDSco/04+5RHti19X8O/QE1aWIhZHAyyuOEs4J1JWA', + 'bucket' => 'files', + 'endpoint' => 'https://test-coolify.ams3.digitaloceanspaces.com', + 'team_id' => 0, + ]); + } +} \ No newline at end of file diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 752787831..bcd06e788 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -69,7 +69,20 @@ services: - "${FORWARD_MAILPIT_DASHBOARD_PORT:-8025}:8025" networks: - coolify - + minio: + image: minio/minio:latest + container_name: coolify-minio + command: server /data --console-address ":9001" + ports: + - "${FORWARD_MINIO_PORT:-9000}:9000" + - "${FORWARD_MINIO_PORT_CONSOLE:-9001}:9001" + environment: + MINIO_ACCESS_KEY: "${MINIO_ACCESS_KEY:-minioadmin}" + MINIO_SECRET_KEY: "${MINIO_SECRET_KEY:-minioadmin}" + volumes: + - ./_data/_volumes/minio/:/data + networks: + - coolify # buggregator: # image: ghcr.io/buggregator/server:latest # container_name: coolify-debug diff --git a/resources/views/components/team/navbar.blade.php b/resources/views/components/team/navbar.blade.php index 7572d646c..91f7e863e 100644 --- a/resources/views/components/team/navbar.blade.php +++ b/resources/views/components/team/navbar.blade.php @@ -27,6 +27,10 @@ + + + diff --git a/resources/views/dashboard.blade.php b/resources/views/dashboard.blade.php index f9d5dd043..609f487cd 100644 --- a/resources/views/dashboard.blade.php +++ b/resources/views/dashboard.blade.php @@ -17,5 +17,12 @@
{{ $resources }}
Applications, databases, etc...
+
+
S3 Storages
+
{{ $s3s->count() }}
+
+ @if (isDev()) + + @endif diff --git a/resources/views/livewire/s3-test.blade.php b/resources/views/livewire/s3-test.blade.php new file mode 100644 index 000000000..ff6afa5f1 --- /dev/null +++ b/resources/views/livewire/s3-test.blade.php @@ -0,0 +1,15 @@ +
+

S3 Test

+
+ + @error('file') + {{ $message }} + @enderror +
Uploading to server...
+ @if ($file) + Upload file to s3:/files + @endif +
+

Functions

+ Get s3:/files +
diff --git a/resources/views/livewire/team/storage/create.blade.php b/resources/views/livewire/team/storage/create.blade.php new file mode 100644 index 000000000..ec644f5eb --- /dev/null +++ b/resources/views/livewire/team/storage/create.blade.php @@ -0,0 +1,23 @@ +
+

Create a new S3 Storage

+
S3 Storage used to save backups / files
+
+
+ + +
+
+ + + +
+
+ + +
+ + + Save New S3 Storage + +
+
diff --git a/resources/views/livewire/team/storage/form.blade.php b/resources/views/livewire/team/storage/form.blade.php new file mode 100644 index 000000000..5c824e984 --- /dev/null +++ b/resources/views/livewire/team/storage/form.blade.php @@ -0,0 +1,38 @@ +
+ + +

This storage will be deleted. It is not reversible. Your data won't be touched!
Please think again.. +

+
+
+
+
+
+

Storage Details

+
{{ $storage->name }}
+
+ + Save + + + Test Connection + + + Delete + +
+
+ + +
+
+ + + +
+
+ + +
+
+
diff --git a/resources/views/team/members.blade.php b/resources/views/team/members.blade.php index 4494dda8a..f6c785290 100644 --- a/resources/views/team/members.blade.php +++ b/resources/views/team/members.blade.php @@ -1,6 +1,6 @@ -

Members

+

Members

diff --git a/resources/views/team/notifications.blade.php b/resources/views/team/notifications.blade.php index d7dad0f02..60a446714 100644 --- a/resources/views/team/notifications.blade.php +++ b/resources/views/team/notifications.blade.php @@ -1,5 +1,6 @@ +

Notifications

+ + +
+ @forelse ($s3 as $storage) +
+
+
+ {{ $storage->name }} +
+
+ {{ $storage->description }}
+
+
+ @empty +
+
No storage found.
+ +
+ @endforelse +
+ + diff --git a/resources/views/team/storages/create.blade.php b/resources/views/team/storages/create.blade.php new file mode 100644 index 000000000..711c521e3 --- /dev/null +++ b/resources/views/team/storages/create.blade.php @@ -0,0 +1,3 @@ + + + diff --git a/resources/views/team/storages/show.blade.php b/resources/views/team/storages/show.blade.php new file mode 100644 index 000000000..75b81d9f5 --- /dev/null +++ b/resources/views/team/storages/show.blade.php @@ -0,0 +1,4 @@ + + + + diff --git a/routes/web.php b/routes/web.php index 18d57474b..91a4aa2ab 100644 --- a/routes/web.php +++ b/routes/web.php @@ -92,6 +92,9 @@ Route::get('/team', [Controller::class, 'team'])->name('team.show'); Route::get('/team/new', fn () => view('team.create'))->name('team.create'); Route::get('/team/notifications', fn () => view('team.notifications'))->name('team.notifications'); + Route::get('/team/storages', [Controller::class, 'storages'])->name('team.storages.all'); + Route::get('/team/storages/new', fn () => view('team.storages.create'))->name('team.storages.new'); + Route::get('/team/storages/{storage_uuid}', [Controller::class, 'storages_show'])->name('team.storages.show'); Route::get('/team/members', [Controller::class, 'members'])->name('team.members'); Route::get('/command-center', fn () => view('command-center', ['servers' => Server::isReachable()->get()]))->name('command-center'); Route::get('/invitations/{uuid}', [Controller::class, 'acceptInvitation'])->name('team.invitation.accept');