From 6312c0ba84d1363917bef5b39c372f37e3933f90 Mon Sep 17 00:00:00 2001 From: Andras Bacsai Date: Thu, 1 Feb 2024 15:38:12 +0100 Subject: [PATCH] feat: tags and tag deploy webhooks --- app/Livewire/Project/Resource/Index.php | 2 +- app/Livewire/Project/Shared/Tags.php | 62 +++++++++++++ app/Livewire/Tags/Index.php | 18 ++++ app/Livewire/Tags/Show.php | 27 ++++++ app/Models/Application.php | 4 + app/Models/Tag.php | 32 +++++++ bootstrap/helpers/shared.php | 13 ++- config/toaster.php | 48 ---------- .../2024_02_01_111228_create_tags_table.php | 39 ++++++++ resources/css/app.css | 2 +- resources/views/components/navbar.blade.php | 30 ++++-- resources/views/components/toast.blade.php | 15 +-- .../application/configuration.blade.php | 6 ++ .../project/application/general.blade.php | 2 - .../livewire/project/resource/index.blade.php | 49 ++++++---- .../livewire/project/shared/tags.blade.php | 13 +++ resources/views/livewire/tags/index.blade.php | 10 ++ resources/views/livewire/tags/show.blade.php | 18 ++++ routes/api.php | 93 ++++++++++++++++++- routes/web.php | 9 +- 20 files changed, 394 insertions(+), 98 deletions(-) create mode 100644 app/Livewire/Project/Shared/Tags.php create mode 100644 app/Livewire/Tags/Index.php create mode 100644 app/Livewire/Tags/Show.php create mode 100644 app/Models/Tag.php delete mode 100644 config/toaster.php create mode 100644 database/migrations/2024_02_01_111228_create_tags_table.php create mode 100644 resources/views/livewire/project/shared/tags.blade.php create mode 100644 resources/views/livewire/tags/index.blade.php create mode 100644 resources/views/livewire/tags/show.blade.php diff --git a/app/Livewire/Project/Resource/Index.php b/app/Livewire/Project/Resource/Index.php index 7b1da85a1..b296f855c 100644 --- a/app/Livewire/Project/Resource/Index.php +++ b/app/Livewire/Project/Resource/Index.php @@ -29,7 +29,7 @@ public function mount() } $this->project = $project; $this->environment = $environment; - $this->applications = $environment->applications->sortBy('name'); + $this->applications = $environment->applications->load(['tags'])->sortBy('name'); $this->applications = $this->applications->map(function ($application) { if (data_get($application, 'environment.project.uuid')) { $application->hrefLink = route('project.application.configuration', [ diff --git a/app/Livewire/Project/Shared/Tags.php b/app/Livewire/Project/Shared/Tags.php new file mode 100644 index 000000000..32b9679ae --- /dev/null +++ b/app/Livewire/Project/Shared/Tags.php @@ -0,0 +1,62 @@ + '$refresh', + ]; + public function mount() + { + } + public function deleteTag($id, $name) + { + try { + $found_more_tags = Tag::where(['name' => $name, 'team_id' => currentTeam()->id])->first(); + $this->resource->tags()->detach($id); + if ($found_more_tags->resources()->get()->count() == 0) { + $found_more_tags->delete(); + } + $this->refresh(); + } catch (\Exception $e) { + return handleError($e, $this); + } + } + public function refresh() + { + $this->resource->load(['tags']); + $this->new_tag = null; + } + public function submit() + { + try { + $this->validate([ + 'new_tag' => 'required|string|min:2' + ]); + $tags = str($this->new_tag)->trim()->explode(' '); + foreach ($tags as $tag) { + $found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first(); + if (!$found) { + $found = Tag::create([ + 'name' => $tag, + 'team_id' => currentTeam()->id + ]); + } + $this->resource->tags()->syncWithoutDetaching($found->id); + } + $this->refresh(); + } catch (\Exception $e) { + return handleError($e, $this); + } + } + public function render() + { + return view('livewire.project.shared.tags'); + } +} diff --git a/app/Livewire/Tags/Index.php b/app/Livewire/Tags/Index.php new file mode 100644 index 000000000..eba25a750 --- /dev/null +++ b/app/Livewire/Tags/Index.php @@ -0,0 +1,18 @@ +tags = Tag::where('team_id', currentTeam()->id)->get()->unique('name')->sortBy('name'); + } + public function render() + { + return view('livewire.tags.index'); + } +} diff --git a/app/Livewire/Tags/Show.php b/app/Livewire/Tags/Show.php new file mode 100644 index 000000000..0363445c8 --- /dev/null +++ b/app/Livewire/Tags/Show.php @@ -0,0 +1,27 @@ +where('name', request()->tag_name)->first(); + if (!$tag) { + return redirect()->route('tags.index'); + } + $this->webhook = generatTagDeployWebhook($tag->name); + $this->resources = $tag->resources()->get(); + $this->tag = $tag; + } + public function render() + { + return view('livewire.tags.show'); + } +} diff --git a/app/Models/Application.php b/app/Models/Application.php index 1f9614baa..e1528d56b 100644 --- a/app/Models/Application.php +++ b/app/Models/Application.php @@ -211,6 +211,10 @@ public function portsExposesArray(): Attribute : explode(',', $this->ports_exposes) ); } + public function tags() + { + return $this->morphToMany(Tag::class, 'taggable'); + } public function team() { return data_get($this, 'environment.project.team'); diff --git a/app/Models/Tag.php b/app/Models/Tag.php new file mode 100644 index 000000000..02b3a7ff5 --- /dev/null +++ b/app/Models/Tag.php @@ -0,0 +1,32 @@ + strtolower($value), + set: fn ($value) => strtolower($value) + ); + } + static public function ownedByCurrentTeam() + { + return Tag::whereTeamId(currentTeam()->id)->orderBy('name'); + } + public function applications() + { + return $this->morphedByMany(Application::class, 'taggable'); + } + + public function resources() { + return $this->applications(); + } + +} diff --git a/bootstrap/helpers/shared.php b/bootstrap/helpers/shared.php index 2a6ce9647..812c23598 100644 --- a/bootstrap/helpers/shared.php +++ b/bootstrap/helpers/shared.php @@ -110,9 +110,9 @@ function handleError(?Throwable $error = null, ?Livewire\Component $livewire = n } if ($error instanceof UniqueConstraintViolationException) { if (isset($livewire)) { - return $livewire->dispatch('error', "A resource with the same name already exists."); + return $livewire->dispatch('error', "Duplicate entry found.","Please use a different name."); } - return "A resource with the same name already exists."; + return "Duplicate entry found. Please use a different name."; } if ($error instanceof Throwable) { @@ -481,7 +481,14 @@ function queryResourcesByUuid(string $uuid) if ($mariadb) return $mariadb; return $resource; } - +function generatTagDeployWebhook($tag_name) +{ + $baseUrl = base_url(); + $api = Url::fromString($baseUrl) . '/api/v1'; + $endpoint = "/deploy/tag/$tag_name"; + $url = $api . $endpoint . "?force=false"; + return $url; +} function generateDeployWebhook($resource) { $baseUrl = base_url(); diff --git a/config/toaster.php b/config/toaster.php deleted file mode 100644 index 43565a9c6..000000000 --- a/config/toaster.php +++ /dev/null @@ -1,48 +0,0 @@ - true, - - /** - * The vertical alignment of the toast container. - * - * Supported: "bottom", "middle" or "top" - */ - 'alignment' => 'top', - - /** - * Allow users to close toast messages prematurely. - * - * Supported: true | false - */ - 'closeable' => true, - - /** - * The on-screen duration of each toast. - * - * Minimum: 3000 (in milliseconds) - */ - 'duration' => 5000, - - /** - * The horizontal position of each toast. - * - * Supported: "center", "left" or "right" - */ - 'position' => 'center', - - /** - * Whether messages passed as translation keys should be translated automatically. - * - * Supported: true | false - */ - 'translate' => true, -]; diff --git a/database/migrations/2024_02_01_111228_create_tags_table.php b/database/migrations/2024_02_01_111228_create_tags_table.php new file mode 100644 index 000000000..c922d8fa9 --- /dev/null +++ b/database/migrations/2024_02_01_111228_create_tags_table.php @@ -0,0 +1,39 @@ +id(); + $table->string('uuid')->unique(); + $table->string('name')->unique(); + $table->foreignId('team_id')->nullable()->constrained()->onDelete('cascade'); + $table->timestamps(); + }); + Schema::create('taggables', function (Blueprint $table) { + $table->unsignedBigInteger('tag_id'); + $table->unsignedBigInteger('taggable_id'); + $table->string('taggable_type'); + $table->foreign('tag_id')->references('id')->on('tags')->onDelete('cascade'); + $table->unique(['tag_id', 'taggable_id', 'taggable_type'], 'taggable_unique'); // Composite unique index + + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('taggables'); + Schema::dropIfExists('tags'); + } +}; diff --git a/resources/css/app.css b/resources/css/app.css index 38a452d85..924369e98 100644 --- a/resources/css/app.css +++ b/resources/css/app.css @@ -76,7 +76,7 @@ .box { } .box-without-bg { - @apply flex p-2 transition-colors min-h-full hover:text-white hover:no-underline min-h-[4rem]; + @apply flex p-2 transition-colors hover:text-white hover:no-underline min-h-[4rem]; } .description { diff --git a/resources/views/components/navbar.blade.php b/resources/views/components/navbar.blade.php index cad924e7b..9256dc502 100644 --- a/resources/views/components/navbar.blade.php +++ b/resources/views/components/navbar.blade.php @@ -4,7 +4,7 @@ class="transition rounded w-11 h-11" src="{{ asset('coolify-transparent.png') }}">