feat: tags
ui: improvements
This commit is contained in:
parent
6312c0ba84
commit
e7fdff0f69
@ -45,6 +45,7 @@ class DeleteService
|
|||||||
foreach ($service->databases()->get() as $database) {
|
foreach ($service->databases()->get() as $database) {
|
||||||
$database->forceDelete();
|
$database->forceDelete();
|
||||||
}
|
}
|
||||||
|
$service->tags()->detach();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
154
app/Http/Controllers/Api/Deploy.php
Normal file
154
app/Http/Controllers/Api/Deploy.php
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers\Api;
|
||||||
|
|
||||||
|
use App\Actions\Database\StartMariadb;
|
||||||
|
use App\Actions\Database\StartMongodb;
|
||||||
|
use App\Actions\Database\StartMysql;
|
||||||
|
use App\Actions\Database\StartPostgresql;
|
||||||
|
use App\Actions\Database\StartRedis;
|
||||||
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use App\Models\Tag;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Collection;
|
||||||
|
use Visus\Cuid2\Cuid2;
|
||||||
|
|
||||||
|
class Deploy extends Controller
|
||||||
|
{
|
||||||
|
public function deploy(Request $request)
|
||||||
|
{
|
||||||
|
$token = auth()->user()->currentAccessToken();
|
||||||
|
$teamId = data_get($token, 'team_id');
|
||||||
|
$uuids = $request->query->get('uuid');
|
||||||
|
$tags = $request->query->get('tag');
|
||||||
|
$force = $request->query->get('force') ?? false;
|
||||||
|
|
||||||
|
if ($uuids && $tags) {
|
||||||
|
return response()->json(['error' => 'You can only use uuid or tag, not both.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
if (is_null($teamId)) {
|
||||||
|
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
||||||
|
}
|
||||||
|
if ($tags) {
|
||||||
|
return $this->by_tags($tags, $teamId, $force);
|
||||||
|
} else if ($uuids) {
|
||||||
|
return $this->by_uuids($uuids, $teamId, $force);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => 'You must provide uuid or tag.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
private function by_uuids(string $uuid, int $teamId, bool $force = false)
|
||||||
|
{
|
||||||
|
$uuids = explode(',', $uuid);
|
||||||
|
$uuids = collect(array_filter($uuids));
|
||||||
|
|
||||||
|
if (count($uuids) === 0) {
|
||||||
|
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($uuids as $uuid) {
|
||||||
|
$resource = getResourceByUuid($uuid, $teamId);
|
||||||
|
if ($resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function by_tags(string $tags, int $team_id, bool $force = false)
|
||||||
|
{
|
||||||
|
$tags = explode(',', $tags);
|
||||||
|
$tags = collect(array_filter($tags));
|
||||||
|
|
||||||
|
if (count($tags) === 0) {
|
||||||
|
return response()->json(['error' => 'No TAGs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
||||||
|
}
|
||||||
|
$message = collect([]);
|
||||||
|
foreach ($tags as $tag) {
|
||||||
|
$found_tag = Tag::where(['name' => $tag, 'team_id' => $team_id])->first();
|
||||||
|
if (!$found_tag) {
|
||||||
|
$message->push("Tag {$tag} not found.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$resources = $found_tag->resources()->get();
|
||||||
|
if ($resources->count() === 0) {
|
||||||
|
$message->push("No resources found for tag {$tag}.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach ($resources as $resource) {
|
||||||
|
$return_message = $this->deploy_resource($resource, $force);
|
||||||
|
$message = $message->merge($return_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($message->count() > 0) {
|
||||||
|
return response()->json(['message' => $message->toArray()], 200);
|
||||||
|
}
|
||||||
|
|
||||||
|
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
||||||
|
}
|
||||||
|
public function deploy_resource($resource, bool $force = false): Collection
|
||||||
|
{
|
||||||
|
$message = collect([]);
|
||||||
|
$type = $resource->getMorphClass();
|
||||||
|
if ($type === 'App\Models\Application') {
|
||||||
|
queue_application_deployment(
|
||||||
|
application: $resource,
|
||||||
|
deployment_uuid: new Cuid2(7),
|
||||||
|
force_rebuild: $force,
|
||||||
|
);
|
||||||
|
$message->push("Application {$resource->name} deployment queued.");
|
||||||
|
} else if ($type === 'App\Models\StandalonePostgresql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartPostgresql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneRedis') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartRedis::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMongodb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMongodb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMysql') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMysql::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\StandaloneMariadb') {
|
||||||
|
if (str($resource->status)->startsWith('running')) {
|
||||||
|
$message->push("Database {$resource->name} already running.");
|
||||||
|
}
|
||||||
|
StartMariadb::run($resource);
|
||||||
|
$resource->update([
|
||||||
|
'started_at' => now(),
|
||||||
|
]);
|
||||||
|
$message->push("Database {$resource->name} started.");
|
||||||
|
} else if ($type === 'App\Models\Service') {
|
||||||
|
StartService::run($resource);
|
||||||
|
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
||||||
|
}
|
||||||
|
return $message;
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
$this->project = $project;
|
$this->project = $project;
|
||||||
$this->environment = $environment;
|
$this->environment = $environment;
|
||||||
$this->applications = $environment->applications->load(['tags'])->sortBy('name');
|
$this->applications = $environment->applications->load(['tags']);
|
||||||
$this->applications = $this->applications->map(function ($application) {
|
$this->applications = $this->applications->map(function ($application) {
|
||||||
if (data_get($application, 'environment.project.uuid')) {
|
if (data_get($application, 'environment.project.uuid')) {
|
||||||
$application->hrefLink = route('project.application.configuration', [
|
$application->hrefLink = route('project.application.configuration', [
|
||||||
@ -40,7 +40,8 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $application;
|
return $application;
|
||||||
});
|
});
|
||||||
$this->postgresqls = $environment->postgresqls->sortBy('name');
|
ray($this->applications);
|
||||||
|
$this->postgresqls = $environment->postgresqls->load(['tags'])->sortBy('name');
|
||||||
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
$this->postgresqls = $this->postgresqls->map(function ($postgresql) {
|
||||||
if (data_get($postgresql, 'environment.project.uuid')) {
|
if (data_get($postgresql, 'environment.project.uuid')) {
|
||||||
$postgresql->hrefLink = route('project.database.configuration', [
|
$postgresql->hrefLink = route('project.database.configuration', [
|
||||||
@ -51,7 +52,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $postgresql;
|
return $postgresql;
|
||||||
});
|
});
|
||||||
$this->redis = $environment->redis->sortBy('name');
|
$this->redis = $environment->redis->load(['tags'])->sortBy('name');
|
||||||
$this->redis = $this->redis->map(function ($redis) {
|
$this->redis = $this->redis->map(function ($redis) {
|
||||||
if (data_get($redis, 'environment.project.uuid')) {
|
if (data_get($redis, 'environment.project.uuid')) {
|
||||||
$redis->hrefLink = route('project.database.configuration', [
|
$redis->hrefLink = route('project.database.configuration', [
|
||||||
@ -62,7 +63,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $redis;
|
return $redis;
|
||||||
});
|
});
|
||||||
$this->mongodbs = $environment->mongodbs->sortBy('name');
|
$this->mongodbs = $environment->mongodbs->load(['tags'])->sortBy('name');
|
||||||
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
$this->mongodbs = $this->mongodbs->map(function ($mongodb) {
|
||||||
if (data_get($mongodb, 'environment.project.uuid')) {
|
if (data_get($mongodb, 'environment.project.uuid')) {
|
||||||
$mongodb->hrefLink = route('project.database.configuration', [
|
$mongodb->hrefLink = route('project.database.configuration', [
|
||||||
@ -73,7 +74,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mongodb;
|
return $mongodb;
|
||||||
});
|
});
|
||||||
$this->mysqls = $environment->mysqls->sortBy('name');
|
$this->mysqls = $environment->mysqls->load(['tags'])->sortBy('name');
|
||||||
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
$this->mysqls = $this->mysqls->map(function ($mysql) {
|
||||||
if (data_get($mysql, 'environment.project.uuid')) {
|
if (data_get($mysql, 'environment.project.uuid')) {
|
||||||
$mysql->hrefLink = route('project.database.configuration', [
|
$mysql->hrefLink = route('project.database.configuration', [
|
||||||
@ -84,7 +85,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mysql;
|
return $mysql;
|
||||||
});
|
});
|
||||||
$this->mariadbs = $environment->mariadbs->sortBy('name');
|
$this->mariadbs = $environment->mariadbs->load(['tags'])->sortBy('name');
|
||||||
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
$this->mariadbs = $this->mariadbs->map(function ($mariadb) {
|
||||||
if (data_get($mariadb, 'environment.project.uuid')) {
|
if (data_get($mariadb, 'environment.project.uuid')) {
|
||||||
$mariadb->hrefLink = route('project.database.configuration', [
|
$mariadb->hrefLink = route('project.database.configuration', [
|
||||||
@ -95,7 +96,7 @@ class Index extends Component
|
|||||||
}
|
}
|
||||||
return $mariadb;
|
return $mariadb;
|
||||||
});
|
});
|
||||||
$this->services = $environment->services->sortBy('name');
|
$this->services = $environment->services->load(['tags'])->sortBy('name');
|
||||||
$this->services = $this->services->map(function ($service) {
|
$this->services = $this->services->map(function ($service) {
|
||||||
if (data_get($service, 'environment.project.uuid')) {
|
if (data_get($service, 'environment.project.uuid')) {
|
||||||
$service->hrefLink = route('project.service.configuration', [
|
$service->hrefLink = route('project.service.configuration', [
|
||||||
|
@ -9,11 +9,33 @@ class Tags extends Component
|
|||||||
{
|
{
|
||||||
public $resource = null;
|
public $resource = null;
|
||||||
public ?string $new_tag = null;
|
public ?string $new_tag = null;
|
||||||
|
public $tags = [];
|
||||||
protected $listeners = [
|
protected $listeners = [
|
||||||
'refresh' => '$refresh',
|
'refresh' => '$refresh',
|
||||||
];
|
];
|
||||||
|
protected $rules = [
|
||||||
|
'resource.tags.*.name' => 'required|string|min:2',
|
||||||
|
'new_tag' => 'required|string|min:2'
|
||||||
|
];
|
||||||
|
protected $validationAttributes = [
|
||||||
|
'new_tag' => 'tag'
|
||||||
|
];
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
|
$this->tags = Tag::ownedByCurrentTeam()->get();
|
||||||
|
}
|
||||||
|
public function addTag(string $id, string $name)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if ($this->resource->tags()->where('id', $id)->exists()) {
|
||||||
|
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$name</span> already added.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
$this->resource->tags()->syncWithoutDetaching($id);
|
||||||
|
$this->refresh();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
public function deleteTag($id, $name)
|
public function deleteTag($id, $name)
|
||||||
{
|
{
|
||||||
@ -41,6 +63,10 @@ class Tags extends Component
|
|||||||
]);
|
]);
|
||||||
$tags = str($this->new_tag)->trim()->explode(' ');
|
$tags = str($this->new_tag)->trim()->explode(' ');
|
||||||
foreach ($tags as $tag) {
|
foreach ($tags as $tag) {
|
||||||
|
if ($this->resource->tags()->where('name', $tag)->exists()) {
|
||||||
|
$this->dispatch('error', 'Duplicate tags.', "Tag <span class='text-warning'>$tag</span> already added.");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
$found = Tag::where(['name' => $tag, 'team_id' => currentTeam()->id])->first();
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
$found = Tag::create([
|
$found = Tag::create([
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace App\Livewire\Tags;
|
namespace App\Livewire\Tags;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Api\Deploy;
|
||||||
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
@ -10,6 +12,38 @@ class Show extends Component
|
|||||||
public Tag $tag;
|
public Tag $tag;
|
||||||
public $resources;
|
public $resources;
|
||||||
public $webhook = null;
|
public $webhook = null;
|
||||||
|
public $deployments_per_tag_per_server = [];
|
||||||
|
|
||||||
|
public function get_deployments()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$resource_ids = $this->resources->pluck('id');
|
||||||
|
$this->deployments_per_tag_per_server = ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->whereIn('application_id', $resource_ids)->get([
|
||||||
|
"id",
|
||||||
|
"application_id",
|
||||||
|
"application_name",
|
||||||
|
"deployment_url",
|
||||||
|
"pull_request_id",
|
||||||
|
"server_name",
|
||||||
|
"server_id",
|
||||||
|
"status"
|
||||||
|
])->sortBy('id')->groupBy('server_name')->toArray();
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public function redeploy_all()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->resources->each(function ($resource) {
|
||||||
|
$deploy = new Deploy();
|
||||||
|
$deploy->deploy_resource($resource);
|
||||||
|
});
|
||||||
|
$this->dispatch('success', 'Mass deployment started.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$tag = Tag::ownedByCurrentTeam()->where('name', request()->tag_name)->first();
|
$tag = Tag::ownedByCurrentTeam()->where('name', request()->tag_name)->first();
|
||||||
@ -19,6 +53,7 @@ class Show extends Component
|
|||||||
$this->webhook = generatTagDeployWebhook($tag->name);
|
$this->webhook = generatTagDeployWebhook($tag->name);
|
||||||
$this->resources = $tag->resources()->get();
|
$this->resources = $tag->resources()->get();
|
||||||
$this->tag = $tag;
|
$this->tag = $tag;
|
||||||
|
$this->get_deployments();
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
@ -49,6 +49,7 @@ class Application extends BaseModel
|
|||||||
$application->persistentStorages()->delete();
|
$application->persistentStorages()->delete();
|
||||||
$application->environment_variables()->delete();
|
$application->environment_variables()->delete();
|
||||||
$application->environment_variables_preview()->delete();
|
$application->environment_variables_preview()->delete();
|
||||||
|
$application->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,10 @@ class Service extends BaseModel
|
|||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
}
|
}
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function extraFields()
|
public function extraFields()
|
||||||
{
|
{
|
||||||
$fields = collect([]);
|
$fields = collect([]);
|
||||||
|
@ -40,8 +40,14 @@ class StandaloneMariadb extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
@ -43,8 +43,14 @@ class StandaloneMongodb extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
@ -40,8 +40,14 @@ class StandaloneMysql extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
@ -40,8 +40,14 @@ class StandalonePostgresql extends BaseModel
|
|||||||
$database->scheduledBackups()->delete();
|
$database->scheduledBackups()->delete();
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function link()
|
public function link()
|
||||||
{
|
{
|
||||||
if (data_get($this, 'environment.project.uuid')) {
|
if (data_get($this, 'environment.project.uuid')) {
|
||||||
|
@ -35,8 +35,14 @@ class StandaloneRedis extends BaseModel
|
|||||||
}
|
}
|
||||||
$database->persistentStorages()->delete();
|
$database->persistentStorages()->delete();
|
||||||
$database->environment_variables()->delete();
|
$database->environment_variables()->delete();
|
||||||
|
$database->tags()->detach();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function tags()
|
||||||
|
{
|
||||||
|
return $this->morphToMany(Tag::class, 'taggable');
|
||||||
|
}
|
||||||
public function team()
|
public function team()
|
||||||
{
|
{
|
||||||
return data_get($this, 'environment.project.team');
|
return data_get($this, 'environment.project.team');
|
||||||
|
@ -485,8 +485,8 @@ function generatTagDeployWebhook($tag_name)
|
|||||||
{
|
{
|
||||||
$baseUrl = base_url();
|
$baseUrl = base_url();
|
||||||
$api = Url::fromString($baseUrl) . '/api/v1';
|
$api = Url::fromString($baseUrl) . '/api/v1';
|
||||||
$endpoint = "/deploy/tag/$tag_name";
|
$endpoint = "/deploy?tag=$tag_name";
|
||||||
$url = $api . $endpoint . "?force=false";
|
$url = $api . $endpoint;
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
function generateDeployWebhook($resource)
|
function generateDeployWebhook($resource)
|
||||||
|
@ -14,6 +14,10 @@ button[isError] {
|
|||||||
@apply bg-red-600 hover:bg-red-700;
|
@apply bg-red-600 hover:bg-red-700;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
button[isHighlighted] {
|
||||||
|
@apply bg-coollabs hover:bg-coollabs-100;
|
||||||
|
}
|
||||||
|
|
||||||
.scrollbar {
|
.scrollbar {
|
||||||
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
|
@apply scrollbar-thumb-coollabs-100 scrollbar-track-coolgray-200 scrollbar-w-2;
|
||||||
}
|
}
|
||||||
|
@ -46,9 +46,16 @@
|
|||||||
type="button">Cancel
|
type="button">Cancel
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
<div class="flex-1"></div>
|
<div class="flex-1"></div>
|
||||||
|
@if ($isErrorButton)
|
||||||
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
|
<x-forms.button @click="modalOpen=false" class="w-24" isError type="button"
|
||||||
wire:click.prevent='{{ $action }}'>Continue
|
wire:click.prevent='{{ $action }}'>Continue
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
|
@else
|
||||||
|
<x-forms.button @click="modalOpen=false" class="w-24" isHighlighted type="button"
|
||||||
|
wire:click.prevent='{{ $action }}'>Continue
|
||||||
|
</x-forms.button>
|
||||||
|
@endif
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -107,15 +107,14 @@
|
|||||||
<x-loading />
|
<x-loading />
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
{{-- <div wire:poll.4000ms="get_deployments" class="grid grid-cols-1 gap-2 lg:grid-cols-3"> --}}
|
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
|
||||||
<div class="grid grid-cols-1">
|
|
||||||
@forelse ($deployments_per_server as $server_name => $deployments)
|
@forelse ($deployments_per_server as $server_name => $deployments)
|
||||||
<h4 class="py-4">{{ $server_name }}</h4>
|
<h4 class="py-4">{{ $server_name }}</h4>
|
||||||
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||||
@foreach ($deployments as $deployment)
|
@foreach ($deployments as $deployment)
|
||||||
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
||||||
'gap-2 cursor-pointer box group border-l-2 border-dotted',
|
'gap-2 cursor-pointer box group border-l-2 border-dotted',
|
||||||
'border-white' => data_get($deployment, 'status') === 'queued',
|
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
|
||||||
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
||||||
])>
|
])>
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
@ -136,7 +135,7 @@
|
|||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
@empty
|
@empty
|
||||||
<div>No queued / in progress deployments</div>
|
<div>No deployments running.</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
<script>
|
<script>
|
||||||
|
@ -5,13 +5,14 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex items-end gap-2">
|
<div class="flex items-end gap-2">
|
||||||
<x-forms.input required id="newProjectName" label="New Project Name" />
|
<x-forms.input required id="newProjectName" label="New Project Name" />
|
||||||
<x-forms.button type="submit">Clone</x-forms.button>
|
<x-forms.button isHighlighted type="submit">Clone</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<h3 class="pt-4 pb-2">Servers</h3>
|
<h3 class="pt-4 pb-2">Servers</h3>
|
||||||
|
<div>Choose the server and network to clone the resources to.</div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
@foreach ($servers->sortBy('id') as $server)
|
@foreach ($servers->sortBy('id') as $server)
|
||||||
<div class="p-4 border border-coolgray-500">
|
<div class="p-4">
|
||||||
<h3>{{ $server->name }}</h3>
|
<h4>{{ $server->name }}</h4>
|
||||||
<h5>{{ $server->description }}</h5>
|
<h5>{{ $server->description }}</h5>
|
||||||
<div class="pt-4 pb-2">Docker Networks</div>
|
<div class="pt-4 pb-2">Docker Networks</div>
|
||||||
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
|
<div class="grid grid-cols-1 gap-2 pb-4 lg:grid-cols-4">
|
||||||
@ -28,7 +29,8 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h3 class="pt-4 pb-2">Resources</h3>
|
<h3 class="pt-4 pb-2">Resources</h3>
|
||||||
<div class="grid grid-cols-1 gap-2 p-4 border border-coolgray-500">
|
<div>These will be cloned to the new project</div>
|
||||||
|
<div class="grid grid-cols-1 gap-2 p-4 ">
|
||||||
@foreach ($environment->applications->sortBy('name') as $application)
|
@foreach ($environment->applications->sortBy('name') as $application)
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
|
@ -48,6 +48,9 @@
|
|||||||
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
||||||
href="#">Resource Operations
|
href="#">Resource Operations
|
||||||
</a>
|
</a>
|
||||||
|
<a :class="activeTab === 'tags' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'text-white'"
|
<a :class="activeTab === 'danger' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'danger';
|
@click.prevent="activeTab = 'danger';
|
||||||
window.location.hash = 'danger'"
|
window.location.hash = 'danger'"
|
||||||
@ -89,6 +92,9 @@
|
|||||||
<div x-cloak x-show="activeTab === 'resource-operations'">
|
<div x-cloak x-show="activeTab === 'resource-operations'">
|
||||||
<livewire:project.shared.resource-operations :resource="$database" />
|
<livewire:project.shared.resource-operations :resource="$database" />
|
||||||
</div>
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
|
<livewire:project.shared.tags :resource="$database" />
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$database" />
|
<livewire:project.shared.danger :resource="$database" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -76,7 +76,8 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredPostgresqls" :key="item.id">
|
<template x-for="item in filteredPostgresqls" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white" x-text="item.name"></div>
|
<div class="font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -91,9 +92,19 @@
|
|||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredRedis" :key="item.id">
|
<template x-for="item in filteredRedis" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white" x-text="item.name"></div>
|
<div class="font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -107,10 +118,21 @@
|
|||||||
<template x-if="item.status.startsWith('restarting')">
|
<template x-if="item.status.startsWith('restarting')">
|
||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredMongodbs" :key="item.id">
|
<template x-for="item in filteredMongodbs" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white" x-text="item.name"></div>
|
<div class="font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -125,9 +147,19 @@
|
|||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredMysqls" :key="item.id">
|
<template x-for="item in filteredMysqls" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white" x-text="item.name"></div>
|
<div class="font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -142,9 +174,19 @@
|
|||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredMariadbs" :key="item.id">
|
<template x-for="item in filteredMariadbs" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white" x-text="item.name"></div>
|
<div class="font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -159,9 +201,19 @@
|
|||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
<template x-for="item in filteredServices" :key="item.id">
|
<template x-for="item in filteredServices" :key="item.id">
|
||||||
<a class="relative box group" :href="item.hrefLink">
|
<span class="relative">
|
||||||
|
<a class="h-24 box group" :href="item.hrefLink">
|
||||||
<div class="flex flex-col mx-6">
|
<div class="flex flex-col mx-6">
|
||||||
<div class="font-bold text-white" x-text="item.name"></div>
|
<div class="font-bold text-white" x-text="item.name"></div>
|
||||||
<div class="description" x-text="item.description"></div>
|
<div class="description" x-text="item.description"></div>
|
||||||
@ -176,6 +228,15 @@
|
|||||||
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
<div class="absolute bg-warning -top-1 -left-1 badge badge-xs"></div>
|
||||||
</template>
|
</template>
|
||||||
</a>
|
</a>
|
||||||
|
<div class="flex gap-1 pt-1 group-hover:text-white group min-h-6">
|
||||||
|
<template x-for="tag in item.tags">
|
||||||
|
<div class="px-2 py-1 cursor-pointer description bg-coolgray-100 hover:bg-coolgray-300"
|
||||||
|
@click.prevent="gotoTag(tag.name)" x-text="tag.name"></div>
|
||||||
|
</template>
|
||||||
|
<div class="flex items-center px-2 text-xs cursor-pointer text-neutral-500/20 group-hover:text-white hover:bg-coolgray-300"
|
||||||
|
@click.prevent="goto(item)">Add tag</div>
|
||||||
|
</div>
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -184,6 +245,10 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
function sortFn(a, b) {
|
||||||
|
return a.name.localeCompare(b.name)
|
||||||
|
}
|
||||||
|
|
||||||
function searchComponent() {
|
function searchComponent() {
|
||||||
return {
|
return {
|
||||||
search: '',
|
search: '',
|
||||||
@ -203,74 +268,81 @@
|
|||||||
},
|
},
|
||||||
get filteredApplications() {
|
get filteredApplications() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.applications;
|
return Object.values(this.applications).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.applications = Object.values(this.applications);
|
this.applications = Object.values(this.applications);
|
||||||
return this.applications.filter(item => {
|
return this.applications.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.fqdn?.toLowerCase().includes(this.search.toLowerCase()) ||
|
item.fqdn?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get filteredPostgresqls() {
|
get filteredPostgresqls() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.postgresqls;
|
return Object.values(this.postgresqls).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.postgresqls = Object.values(this.postgresqls);
|
this.postgresqls = Object.values(this.postgresqls);
|
||||||
return this.postgresqls.filter(item => {
|
return this.postgresqls.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get filteredRedis() {
|
get filteredRedis() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.redis;
|
return Object.values(this.redis).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.redis = Object.values(this.redis);
|
this.redis = Object.values(this.redis);
|
||||||
return this.redis.filter(item => {
|
return this.redis.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get filteredMongodbs() {
|
get filteredMongodbs() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.mongodbs;
|
return Object.values(this.mongodbs).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.mongodbs = Object.values(this.mongodbs);
|
this.mongodbs = Object.values(this.mongodbs);
|
||||||
return this.mongodbs.filter(item => {
|
return this.mongodbs.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get filteredMysqls() {
|
get filteredMysqls() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.mysqls;
|
return Object.values(this.mysqls).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.mysqls = Object.values(this.mysqls);
|
this.mysqls = Object.values(this.mysqls);
|
||||||
return this.mysqls.filter(item => {
|
return this.mysqls.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get filteredMariadbs() {
|
get filteredMariadbs() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.mariadbs;
|
return Object.values(this.mariadbs).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.mariadbs = Object.values(this.mariadbs);
|
this.mariadbs = Object.values(this.mariadbs);
|
||||||
return this.mariadbs.filter(item => {
|
return this.mariadbs.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
get filteredServices() {
|
get filteredServices() {
|
||||||
if (this.search === '') {
|
if (this.search === '') {
|
||||||
return this.services;
|
return Object.values(this.services).sort(sortFn);
|
||||||
}
|
}
|
||||||
this.services = Object.values(this.services);
|
this.services = Object.values(this.services);
|
||||||
return this.services.filter(item => {
|
return this.services.filter(item => {
|
||||||
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
return item.name.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
item.description?.toLowerCase().includes(this.search.toLowerCase());
|
item.description?.toLowerCase().includes(this.search.toLowerCase()) ||
|
||||||
});
|
item.tags?.some(tag => tag.name.toLowerCase().includes(this.search.toLowerCase()));
|
||||||
|
}).sort(sortFn);
|
||||||
},
|
},
|
||||||
|
|
||||||
};
|
};
|
||||||
|
@ -30,6 +30,9 @@
|
|||||||
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
@click.prevent="activeTab = 'resource-operations'; window.location.hash = 'resource-operations'"
|
||||||
href="#">Resource Operations
|
href="#">Resource Operations
|
||||||
</a>
|
</a>
|
||||||
|
<a :class="activeTab === 'tags' && 'text-white'"
|
||||||
|
@click.prevent="activeTab = 'tags'; window.location.hash = 'tags'" href="#">Tags
|
||||||
|
</a>
|
||||||
<a :class="activeTab === 'danger' && 'text-white'"
|
<a :class="activeTab === 'danger' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'danger';
|
@click.prevent="activeTab = 'danger';
|
||||||
window.location.hash = 'danger'"
|
window.location.hash = 'danger'"
|
||||||
@ -164,6 +167,9 @@
|
|||||||
<div x-cloak x-show="activeTab === 'resource-operations'">
|
<div x-cloak x-show="activeTab === 'resource-operations'">
|
||||||
<livewire:project.shared.resource-operations :resource="$service" />
|
<livewire:project.shared.resource-operations :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
|
<div x-cloak x-show="activeTab === 'tags'">
|
||||||
|
<livewire:project.shared.tags :resource="$service" />
|
||||||
|
</div>
|
||||||
<div x-cloak x-show="activeTab === 'danger'">
|
<div x-cloak x-show="activeTab === 'danger'">
|
||||||
<livewire:project.shared.danger :resource="$service" />
|
<livewire:project.shared.danger :resource="$service" />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,13 +1,32 @@
|
|||||||
<div>
|
<div>
|
||||||
<h2>Tags</h2>
|
<h2>Tags</h2>
|
||||||
@foreach ($this->resource->tags as $tag)
|
<div class="flex gap-2 pt-4">
|
||||||
<div>
|
@forelse ($this->resource->tags as $tagId => $tag)
|
||||||
<div>{{ $tag->name }}</div>
|
<div class="px-2 py-1 text-center text-white select-none w-fit bg-coolgray-100 hover:bg-coolgray-200">
|
||||||
<x-forms.button isError wire:click="deleteTag('{{ $tag->id }}','{{ $tag->name }}')">Delete</x-forms.button>
|
{{ $tag->name }}
|
||||||
|
<svg wire:click="deleteTag('{{ $tag->id }}','{{ $tag->name }}')"
|
||||||
|
xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||||
|
class="inline-block w-3 h-3 rounded cursor-pointer stroke-current hover:bg-red-500">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div>No tags yet</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
|
<form wire:submit='submit' class="flex items-end gap-2 pt-4">
|
||||||
|
<div class="w-64">
|
||||||
|
<x-forms.input label="Create new or assign existing tags"
|
||||||
|
helper="You add more at once with space seperated list: web api something<br><br>If the tag does not exists, it will be created." wire:model="new_tag" />
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
|
||||||
<form wire:submit='submit'>
|
|
||||||
<x-forms.input label="Add/Assign a tag" wire:model="new_tag" wire:confirm="Are you sure you want to delete this post?" />
|
|
||||||
<x-forms.button type="submit">Add</x-forms.button>
|
<x-forms.button type="submit">Add</x-forms.button>
|
||||||
</form>
|
</form>
|
||||||
|
<h3 class="pt-4">Already defined tags</h3>
|
||||||
|
<div>Click to quickly add</div>
|
||||||
|
<div class="flex gap-2 pt-4">
|
||||||
|
@foreach ($tags as $tag)
|
||||||
|
<x-forms.button wire:click="addTag('{{ $tag->id }}','{{ $tag->name }}')">
|
||||||
|
{{ $tag->name }}</x-forms.button>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
<div>
|
<div>
|
||||||
<h1>Tags</h1>
|
<h1>Tags</h1>
|
||||||
|
<div>Here you can see all the tags here</div>
|
||||||
<div class="flex gap-2 pt-10">
|
<div class="flex gap-2 pt-10">
|
||||||
@forelse ($tags as $tag)
|
@forelse ($tags as $tag)
|
||||||
<a class="box" href="{{ route('tags.show', ['tag_name' => $tag->name]) }}">{{ $tag->name }}</a>
|
<a class="box" href="{{ route('tags.show', ['tag_name' => $tag->name]) }}">{{ $tag->name }}</a>
|
||||||
@empty
|
@empty
|
||||||
<p>No tags yet</p>
|
<div>No tags yet defined yet. Go to a resource and add a tag there.</div>
|
||||||
@endforelse
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,18 +1,58 @@
|
|||||||
|
<div>
|
||||||
|
<div class="flex items-start gap-2">
|
||||||
<div>
|
<div>
|
||||||
<h1>Tag: {{ $tag->name }}</h1>
|
<h1>Tag: {{ $tag->name }}</h1>
|
||||||
<div class="">Tag details</div>
|
<div class="pt-2">Tag details</div>
|
||||||
<div class="lg:w-[500px] pt-4">
|
</div>
|
||||||
<x-forms.input readonly label="Tag Deploy Webhook URL" id="webhook" />
|
|
||||||
</div>
|
</div>
|
||||||
<div class="pt-4">
|
<div class="pt-4">
|
||||||
<div class="flex items-end gap-2 ">
|
<div class="flex items-end gap-2 ">
|
||||||
<h3>Resources</h3>
|
<div class="w-[500px]">
|
||||||
<x-forms.button>Redeploy All</x-forms.button>
|
<x-forms.input readonly label="Deploy Webhook URL" id="webhook" />
|
||||||
</div>
|
</div>
|
||||||
<div class="grid gap-2 pt-4 lg:grid-cols-2">
|
<x-new-modal buttonTitle="Redeploy All" action="redeploy_all" class="mt-1">
|
||||||
|
All resources will be redeployed.
|
||||||
|
</x-new-modal>
|
||||||
|
</div>
|
||||||
|
<div class="grid gap-2 pt-4 lg:grid-cols-4">
|
||||||
@foreach ($resources as $resource)
|
@foreach ($resources as $resource)
|
||||||
<div class="box">{{ data_get($resource, 'name') }}</div>
|
<a href="{{ $resource->link() }}" class="flex flex-col box group">
|
||||||
|
<span class="font-bold text-white">{{ $resource->name }}</span>
|
||||||
|
<span class="description">{{ $resource->description }}</span>
|
||||||
|
</a>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
<div class="flex items-center gap-2">
|
||||||
|
<h3 class="py-4">Deployments</h3>
|
||||||
|
@if (count($deployments_per_tag_per_server) > 0)
|
||||||
|
<x-loading />
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div wire:poll.1000ms="get_deployments" class="grid grid-cols-1">
|
||||||
|
@forelse ($deployments_per_tag_per_server as $server_name => $deployments)
|
||||||
|
<h4 class="py-4">{{ $server_name }}</h4>
|
||||||
|
<div class="grid grid-cols-1 gap-2 lg:grid-cols-3">
|
||||||
|
@foreach ($deployments as $deployment)
|
||||||
|
<a href="{{ data_get($deployment, 'deployment_url') }}" @class([
|
||||||
|
'gap-2 cursor-pointer box group border-l-2 border-dotted',
|
||||||
|
'border-coolgray-500' => data_get($deployment, 'status') === 'queued',
|
||||||
|
'border-yellow-500' => data_get($deployment, 'status') === 'in_progress',
|
||||||
|
])>
|
||||||
|
<div class="flex flex-col mx-6">
|
||||||
|
<div class="font-bold text-white">
|
||||||
|
{{ data_get($deployment, 'application_name') }}
|
||||||
|
</div>
|
||||||
|
<div class="description">
|
||||||
|
{{ str(data_get($deployment, 'status'))->headline() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex-1"></div>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@empty
|
||||||
|
<div>No deployments running.</div>
|
||||||
|
@endforelse
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
197
routes/api.php
197
routes/api.php
@ -6,6 +6,7 @@ use App\Actions\Database\StartMysql;
|
|||||||
use App\Actions\Database\StartPostgresql;
|
use App\Actions\Database\StartPostgresql;
|
||||||
use App\Actions\Database\StartRedis;
|
use App\Actions\Database\StartRedis;
|
||||||
use App\Actions\Service\StartService;
|
use App\Actions\Service\StartService;
|
||||||
|
use App\Http\Controllers\Api\Deploy;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
use App\Models\Tag;
|
use App\Models\Tag;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
@ -33,191 +34,25 @@ if (isDev()) {
|
|||||||
Route::get('/health', function () {
|
Route::get('/health', function () {
|
||||||
return 'OK';
|
return 'OK';
|
||||||
});
|
});
|
||||||
Route::group([
|
// Route::group([
|
||||||
'middleware' => $middlewares,
|
// 'middleware' => $middlewares,
|
||||||
'prefix' => 'v1'
|
// 'prefix' => 'v1'
|
||||||
], function () {
|
// ], function () {
|
||||||
Route::get('/deployments', function () {
|
// Route::get('/deployments', function () {
|
||||||
return ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->get([
|
// return ApplicationDeploymentQueue::whereIn("status", ["in_progress", "queued"])->get([
|
||||||
"id",
|
// "id",
|
||||||
"server_id",
|
// "server_id",
|
||||||
"status"
|
// "status"
|
||||||
])->groupBy("server_id")->map(function ($item) {
|
// ])->groupBy("server_id")->map(function ($item) {
|
||||||
return $item;
|
// return $item;
|
||||||
})->toArray();
|
// })->toArray();
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
Route::group([
|
Route::group([
|
||||||
'middleware' => ['auth:sanctum'],
|
'middleware' => ['auth:sanctum'],
|
||||||
'prefix' => 'v1'
|
'prefix' => 'v1'
|
||||||
], function () {
|
], function () {
|
||||||
Route::get('/deploy', function (Request $request) {
|
Route::get('/deploy', [Deploy::class, 'deploy']);
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$teamId = data_get($token, 'team_id');
|
|
||||||
$uuid = $request->query->get('uuid');
|
|
||||||
$uuids = explode(',', $uuid);
|
|
||||||
$uuids = collect(array_filter($uuids));
|
|
||||||
$force = $request->query->get('force') ?? false;
|
|
||||||
if (is_null($teamId)) {
|
|
||||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
|
||||||
}
|
|
||||||
if (count($uuids) === 0) {
|
|
||||||
return response()->json(['error' => 'No UUIDs provided.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 400);
|
|
||||||
}
|
|
||||||
$message = collect([]);
|
|
||||||
foreach ($uuids as $uuid) {
|
|
||||||
$resource = getResourceByUuid($uuid, $teamId);
|
|
||||||
if ($resource) {
|
|
||||||
$type = $resource->getMorphClass();
|
|
||||||
if ($type === 'App\Models\Application') {
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $resource,
|
|
||||||
deployment_uuid: new Cuid2(7),
|
|
||||||
force_rebuild: $force,
|
|
||||||
);
|
|
||||||
$message->push("Application {$resource->name} deployment queued.");
|
|
||||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartPostgresql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneRedis') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartRedis::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMongodb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMysql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMariadb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\Service') {
|
|
||||||
StartService::run($resource);
|
|
||||||
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ($message->count() > 0) {
|
|
||||||
return response()->json(['message' => $message->toArray()], 200);
|
|
||||||
}
|
|
||||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
|
||||||
});
|
|
||||||
Route::get('/deploy/tag/{tag_name}', function (Request $request) {
|
|
||||||
$token = auth()->user()->currentAccessToken();
|
|
||||||
$team_id = data_get($token, 'team_id');
|
|
||||||
$tag_name = $request->route('tag_name');
|
|
||||||
$force = $request->query->get('force') ?? false;
|
|
||||||
if (is_null($team_id)) {
|
|
||||||
return response()->json(['error' => 'Invalid token.', 'docs' => 'https://coolify.io/docs/api/authentication'], 400);
|
|
||||||
}
|
|
||||||
|
|
||||||
$message = collect([]);
|
|
||||||
$tag = Tag::where(['name' => $tag_name, 'team_id' => $team_id])->first();
|
|
||||||
if (!$tag) {
|
|
||||||
return response()->json(['error' => 'Tag not found.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
$resources = $tag->resources()->get();
|
|
||||||
if ($resources->count() === 0) {
|
|
||||||
return response()->json(['error' => 'No resources found.', 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
|
||||||
}
|
|
||||||
foreach ($resources as $resource) {
|
|
||||||
if ($resource) {
|
|
||||||
$type = $resource->getMorphClass();
|
|
||||||
if ($type === 'App\Models\Application') {
|
|
||||||
queue_application_deployment(
|
|
||||||
application: $resource,
|
|
||||||
deployment_uuid: new Cuid2(7),
|
|
||||||
force_rebuild: $force,
|
|
||||||
);
|
|
||||||
$message->push("Application {$resource->name} deployment queued.");
|
|
||||||
} else if ($type === 'App\Models\StandalonePostgresql') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartPostgresql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneRedis') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartRedis::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneMongodb') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMongodb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneMysql') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMysql::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\StandaloneMariadb') {
|
|
||||||
if (str($resource->status)->startsWith('running')) {
|
|
||||||
$message->push("Database {$resource->name} already running.");
|
|
||||||
}
|
|
||||||
StartMariadb::run($resource);
|
|
||||||
$resource->update([
|
|
||||||
'started_at' => now(),
|
|
||||||
]);
|
|
||||||
$message->push("Database {$resource->name} started.");
|
|
||||||
} else if ($type === 'App\Models\Service') {
|
|
||||||
StartService::run($resource);
|
|
||||||
$message->push("Service {$resource->name} started. It could take a while, be patient.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ray($resources);
|
|
||||||
|
|
||||||
if ($message->count() > 0) {
|
|
||||||
return response()->json(['message' => $message->toArray()], 200);
|
|
||||||
}
|
|
||||||
return response()->json(['error' => "No resources found.", 'docs' => 'https://coolify.io/docs/api/deploy-webhook'], 404);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
Route::middleware(['throttle:5'])->group(function () {
|
Route::middleware(['throttle:5'])->group(function () {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user