fix: complex container status

feat: able to change primary server
feat: links inside the logs
This commit is contained in:
Andras Bacsai 2024-02-22 10:57:05 +01:00
parent 6a00d8c88c
commit 5179129a6b
15 changed files with 115 additions and 49 deletions

View File

@ -66,6 +66,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private Server $mainServer; private Server $mainServer;
private ?ApplicationPreview $preview = null; private ?ApplicationPreview $preview = null;
private ?string $git_type = null; private ?string $git_type = null;
private bool $only_this_server = false;
private string $container_name; private string $container_name;
private ?string $currently_running_container_name = null; private ?string $currently_running_container_name = null;
@ -115,6 +116,7 @@ public function __construct(int $application_deployment_queue_id)
$this->commit = $this->application_deployment_queue->commit; $this->commit = $this->application_deployment_queue->commit;
$this->force_rebuild = $this->application_deployment_queue->force_rebuild; $this->force_rebuild = $this->application_deployment_queue->force_rebuild;
$this->restart_only = $this->application_deployment_queue->restart_only; $this->restart_only = $this->application_deployment_queue->restart_only;
$this->only_this_server = $this->application_deployment_queue->only_this_server;
$this->git_type = data_get($this->application_deployment_queue, 'git_type'); $this->git_type = data_get($this->application_deployment_queue, 'git_type');
@ -887,7 +889,7 @@ private function deploy_to_additional_destinations()
destination: $destination, destination: $destination,
no_questions_asked: true, no_questions_asked: true,
); );
$this->application_deployment_queue->addLogEntry("Deploying to additional server: {$server->name}. Click here to see the deployment status: " . route('project.application.deployment.show', [ $this->application_deployment_queue->addLogEntry("Deployment to {$server->name}. Logs: " . route('project.application.deployment.show', [
'project_uuid' => data_get($this->application, 'environment.project.uuid'), 'project_uuid' => data_get($this->application, 'environment.project.uuid'),
'application_uuid' => data_get($this->application, 'uuid'), 'application_uuid' => data_get($this->application, 'uuid'),
'deployment_uuid' => $deployment_uuid, 'deployment_uuid' => $deployment_uuid,
@ -1619,7 +1621,9 @@ private function next(string $status)
return; return;
} }
if ($status === ApplicationDeploymentStatus::FINISHED->value) { if ($status === ApplicationDeploymentStatus::FINISHED->value) {
if (!$this->only_this_server) {
$this->deploy_to_additional_destinations(); $this->deploy_to_additional_destinations();
}
$this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview)); $this->application->environment->project->team?->notify(new DeploymentSuccess($this->application, $this->deployment_uuid, $this->preview));
} }
} }

View File

@ -44,17 +44,19 @@ public function uniqueId(): int
public function handle() public function handle()
{ {
$applications = $this->server->applications(); $applications = $this->server->applications();
$skip_these_applications = collect([]);
foreach ($applications as $application) { foreach ($applications as $application) {
if ($application->additional_servers->count() > 0) { if ($application->additional_servers->count() > 0) {
$is_main_server = $application->destination->server->id === $this->server->id; $skip_these_applications->push($application);
if ($is_main_server) {
ComplexStatusCheck::run($application); ComplexStatusCheck::run($application);
$applications = $applications->filter(function ($value, $key) use ($application) { $applications = $applications->filter(function ($value, $key) use ($application) {
return $value->id !== $application->id; return $value->id !== $application->id;
}); });
} }
} }
} $applications = $applications->filter(function ($value, $key) use ($skip_these_applications) {
return !$skip_these_applications->pluck('id')->contains($value->id);
});
if (!$this->server->isFunctional()) { if (!$this->server->isFunctional()) {
return 'Server is not ready.'; return 'Server is not ready.';

View File

@ -33,15 +33,11 @@ public function check_status($showNotification = false)
{ {
if ($this->application->destination->server->isFunctional()) { if ($this->application->destination->server->isFunctional()) {
dispatch(new ContainerStatusJob($this->application->destination->server)); dispatch(new ContainerStatusJob($this->application->destination->server));
// $this->application->refresh();
// $this->application->previews->each(function ($preview) {
// $preview->refresh();
// });
} else { } else {
dispatch(new ServerStatusJob($this->application->destination->server)); dispatch(new ServerStatusJob($this->application->destination->server));
} }
if ($showNotification) $this->dispatch('success', "Application status updated."); if ($showNotification) $this->dispatch('success', "Success", "Application status updated.");
} }
public function force_deploy_without_cache() public function force_deploy_without_cache()

View File

@ -38,7 +38,7 @@ public function check_status($showNotification = false)
{ {
dispatch_sync(new ContainerStatusJob($this->database->destination->server)); dispatch_sync(new ContainerStatusJob($this->database->destination->server));
$this->database->refresh(); $this->database->refresh();
if ($showNotification) $this->dispatch('success', 'Database status updated.'); if ($showNotification) $this->dispatch('success', 'Success', 'Database status updated.');
} }
public function mount() public function mount()

View File

@ -4,6 +4,7 @@
use App\Actions\Application\StopApplicationOneServer; use App\Actions\Application\StopApplicationOneServer;
use App\Events\ApplicationStatusChanged; use App\Events\ApplicationStatusChanged;
use App\Jobs\ContainerStatusJob;
use App\Models\Server; use App\Models\Server;
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Livewire\Component; use Livewire\Component;
@ -19,7 +20,6 @@ public function getListeners()
$teamId = auth()->user()->currentTeam()->id; $teamId = auth()->user()->currentTeam()->id;
return [ return [
"echo-private:team.{$teamId},ApplicationStatusChanged" => 'loadData', "echo-private:team.{$teamId},ApplicationStatusChanged" => 'loadData',
"loadData",
]; ];
} }
public function mount() public function mount()
@ -41,10 +41,18 @@ public function loadData()
$this->networks = $this->networks->reject(function ($network) { $this->networks = $this->networks->reject(function ($network) {
return $this->resource->destination->server->id == $network->server->id; return $this->resource->destination->server->id == $network->server->id;
}); });
if ($this->resource?->additional_servers?->count() > 0) {
$this->networks = $this->networks->reject(function ($network) { $this->networks = $this->networks->reject(function ($network) {
return $this->resource->additional_servers->pluck('id')->contains($network->server->id); return $this->resource->additional_servers->pluck('id')->contains($network->server->id);
}); });
} }
}
public function stop(int $server_id)
{
$server = Server::find($server_id);
StopApplicationOneServer::run($this->resource, $server);
$this->refreshServers();
}
public function redeploy(int $network_id, int $server_id) public function redeploy(int $network_id, int $server_id)
{ {
if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) { if ($this->resource->additional_servers->count() > 0 && str($this->resource->docker_registry_image_name)->isEmpty()) {
@ -59,6 +67,7 @@ public function redeploy(int $network_id, int $server_id)
application: $this->resource, application: $this->resource,
server: $server, server: $server,
destination: $destination, destination: $destination,
only_this_server: true,
no_questions_asked: true, no_questions_asked: true,
); );
return redirect()->route('project.application.deployment.show', [ return redirect()->route('project.application.deployment.show', [
@ -68,11 +77,28 @@ public function redeploy(int $network_id, int $server_id)
'environment_name' => data_get($this->resource, 'environment.name'), 'environment_name' => data_get($this->resource, 'environment.name'),
]); ]);
} }
public function promote(int $network_id, int $server_id)
{
$main_destination = $this->resource->destination;
$this->resource->update([
'destination_id' => $network_id,
'destination_type' => StandaloneDocker::class,
]);
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
$this->resource->additional_networks()->attach($main_destination->id, ['server_id' => $main_destination->server->id]);
$this->refreshServers();
}
public function refreshServers()
{
ContainerStatusJob::dispatchSync($this->resource->destination->server);
$this->loadData();
$this->dispatch('refresh');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
}
public function addServer(int $network_id, int $server_id) public function addServer(int $network_id, int $server_id)
{ {
$this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]); $this->resource->additional_networks()->attach($network_id, ['server_id' => $server_id]);
$this->resource->load(['additional_networks']); $this->loadData();
$this->dispatch('loadData');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
} }
public function removeServer(int $network_id, int $server_id) public function removeServer(int $network_id, int $server_id)
@ -84,8 +110,7 @@ public function removeServer(int $network_id, int $server_id)
$server = Server::find($server_id); $server = Server::find($server_id);
StopApplicationOneServer::run($this->resource, $server); StopApplicationOneServer::run($this->resource, $server);
$this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]); $this->resource->additional_networks()->detach($network_id, ['server_id' => $server_id]);
$this->resource->load(['additional_networks']); $this->loadData();
$this->dispatch('loadData');
ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id')); ApplicationStatusChanged::dispatch(data_get($this->resource, 'environment.project.team.id'));
} }
} }

View File

@ -6,7 +6,6 @@
use Illuminate\Database\Eloquent\Casts\Attribute; use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Support\Collection;
use Spatie\Activitylog\Models\Activity; use Spatie\Activitylog\Models\Activity;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use RuntimeException; use RuntimeException;
@ -274,17 +273,13 @@ public function status(): Attribute
foreach ($additional_servers_status as $status) { foreach ($additional_servers_status as $status) {
$server_status = str($status)->before(':')->value(); $server_status = str($status)->before(':')->value();
$server_health = str($status)->after(':')->value() ?? 'unhealthy'; $server_health = str($status)->after(':')->value() ?? 'unhealthy';
if ($server_status !== 'running') {
if ($main_server_status !== $server_status) { if ($main_server_status !== $server_status) {
$complex_status = 'degraded'; $complex_status = 'degraded';
} }
}
if ($server_health !== 'healthy') {
if ($main_server_health !== $server_health) { if ($main_server_health !== $server_health) {
$complex_health = 'unhealthy'; $complex_health = 'unhealthy';
} }
} }
}
return "$complex_status:$complex_health"; return "$complex_status:$complex_health";
} }
}, },

View File

@ -11,11 +11,12 @@ class Index extends Component
/** /**
* Create a new component instance. * Create a new component instance.
*/ */
public $status = "exited:unhealthy";
public function __construct( public function __construct(
public string $status = 'exited', public $resource = null,
) public bool $showRefreshButton = true,
{ ) {
// $this->status = $resource->status;
} }
/** /**

View File

@ -8,7 +8,7 @@
use App\Models\StandaloneDocker; use App\Models\StandaloneDocker;
use Spatie\Url\Url; use Spatie\Url\Url;
function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null) function queue_application_deployment(Application $application, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null, bool $no_questions_asked = false, Server $server = null, StandaloneDocker $destination = null, bool $only_this_server = false)
{ {
$application_id = $application->id; $application_id = $application->id;
$deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}"); $deployment_link = Url::fromString($application->link() . "/deployment/{$deployment_uuid}");
@ -37,7 +37,8 @@ function queue_application_deployment(Application $application, string $deployme
'is_webhook' => $is_webhook, 'is_webhook' => $is_webhook,
'restart_only' => $restart_only, 'restart_only' => $restart_only,
'commit' => $commit, 'commit' => $commit,
'git_type' => $git_type 'git_type' => $git_type,
'only_this_server' => $only_this_server
]); ]);
if ($no_questions_asked) { if ($no_questions_asked) {

View File

@ -0,0 +1,28 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->boolean('only_this_server')->default(false);
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn('only_this_server');
});
}
};

View File

@ -41,7 +41,7 @@
@if ($resource->getMorphClass() == 'App\Models\Service') @if ($resource->getMorphClass() == 'App\Models\Service')
<x-status.services :service="$resource" /> <x-status.services :service="$resource" />
@else @else
<x-status.index :status="$resource->status" /> <x-status.index :resource="$resource" />
@endif @endif
</ol> </ol>
</nav> </nav>

View File

@ -1,4 +1,3 @@
@props(['status', 'showRefreshButton' => true])
@if (str($status)->startsWith('running')) @if (str($status)->startsWith('running'))
<x-status.running :status="$status" /> <x-status.running :status="$status" />
@elseif(str($status)->startsWith('restarting') || @elseif(str($status)->startsWith('restarting') ||
@ -6,7 +5,7 @@
str($status)->startsWith('degraded')) str($status)->startsWith('degraded'))
<x-status.restarting :status="$status" /> <x-status.restarting :status="$status" />
@else @else
<x-status.stopped :status="$status"/> <x-status.stopped :status="$status" />
@endif @endif
@if (!str($status)->contains('exited') && $showRefreshButton) @if (!str($status)->contains('exited') && $showRefreshButton)

View File

@ -12,5 +12,4 @@
data_get($application_deployment_queue, 'status') === 'queued') data_get($application_deployment_queue, 'status') === 'queued')
<x-forms.button isError wire:click.prevent="cancel">Cancel</x-forms.button> <x-forms.button isError wire:click.prevent="cancel">Cancel</x-forms.button>
@endif @endif
</div> </div>

View File

@ -51,12 +51,17 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox
@if (decode_remote_command_output($application_deployment_queue)->count() > 0) @if (decode_remote_command_output($application_deployment_queue)->count() > 0)
@foreach (decode_remote_command_output($application_deployment_queue) as $line) @foreach (decode_remote_command_output($application_deployment_queue) as $line)
<div @class([ <div @class([
'font-mono whitespace-pre-line', 'font-mono',
'text-warning' => $line['hidden'], 'text-warning' => $line['hidden'],
'text-red-500' => $line['type'] == 'stderr', 'text-red-500' => $line['type'] == 'stderr',
])>[{{ $line['timestamp'] }}] @if ($line['hidden']) ])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT: <br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
@endif{{ $line['output'] }}@if ($line['hidden']) @endif @if (str($line['output'])->contains('http://') || str($line['output'])->contains('https://'))
@php
$line['output'] = preg_replace('/(https?:\/\/[^\s]+)/', '<a href="$1" target="_blank" class="underline text-neutral-400">$1</a>', $line['output']);
@endphp {!! $line['output'] !!}
@else
{{ $line['output'] }}
@endif @endif
</div> </div>
@endforeach @endforeach

View File

@ -1,4 +1,4 @@
<nav wire:poll.30000ms="check_status"> <nav wire:poll.5000ms="check_status">
<x-resources.breadcrumbs :resource="$application" :parameters="$parameters" /> <x-resources.breadcrumbs :resource="$application" :parameters="$parameters" />
<div class="navbar-main"> <div class="navbar-main">
<a class="{{ request()->routeIs('project.application.configuration') ? 'text-white' : '' }}" <a class="{{ request()->routeIs('project.application.configuration') ? 'text-white' : '' }}"

View File

@ -4,7 +4,7 @@
<div class="grid grid-cols-1 gap-4 py-4"> <div class="grid grid-cols-1 gap-4 py-4">
<div class="flex gap-2"> <div class="flex gap-2">
<div class="relative flex flex-col text-white cursor-default box-without-bg bg-coolgray-100 w-96"> <div class="relative flex flex-col text-white cursor-default box-without-bg bg-coolgray-100 w-96">
<div class="font-bold">Main Server</div> <div class="text-xl font-bold">Primary Server</div>
@if (str($resource->realStatus())->startsWith('running')) @if (str($resource->realStatus())->startsWith('running'))
<div title="{{ $resource->realStatus() }}" class="absolute bg-success -top-1 -left-1 badge badge-xs"> <div title="{{ $resource->realStatus() }}" class="absolute bg-success -top-1 -left-1 badge badge-xs">
</div> </div>
@ -21,7 +21,11 @@
</div> </div>
@if ($resource?->additional_networks?->count() > 0) @if ($resource?->additional_networks?->count() > 0)
<x-forms.button <x-forms.button
wire:click="redeploy('{{ data_get($resource, 'destination.id') }}','{{ data_get($resource, 'destination.server.id') }}')">Redeploy</x-forms.button> wire:click="redeploy('{{ data_get($resource, 'destination.id') }}','{{ data_get($resource, 'destination.server.id') }}')">Deploy</x-forms.button>
@if (str($resource->realStatus())->startsWith('running'))
<x-forms.button isError
wire:click="stop('{{ data_get($resource, 'destination.server.id') }}')">Stop</x-forms.button>
@endif
@endif @endif
</div> </div>
@if ($resource?->additional_networks?->count() > 0) @if ($resource?->additional_networks?->count() > 0)
@ -41,16 +45,23 @@ class="absolute bg-error -top-1 -left-1 badge badge-xs"></div>
<div> <div>
Network: {{ data_get($destination, 'network') }} Network: {{ data_get($destination, 'network') }}
</div> </div>
</div> </div>
<x-forms.button <x-forms.button
wire:click="redeploy('{{ data_get($destination, 'id') }}','{{ data_get($destination, 'server.id') }}')">Redeploy</x-forms.button> wire:click="redeploy('{{ data_get($destination, 'id') }}','{{ data_get($destination, 'server.id') }}')">Deploy</x-forms.button>
<x-forms.button
wire:click="promote('{{ data_get($destination, 'id') }}','{{ data_get($destination, 'server.id') }}')">Promote
to Primary </x-forms.button>
@if (data_get_str($destination, 'pivot.status')->startsWith('running'))
<x-forms.button isError
wire:click="stop('{{ data_get($destination, 'server.id') }}')">Stop</x-forms.button>
@endif
<x-new-modal <x-new-modal
action="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})" action="removeServer({{ data_get($destination, 'id') }},{{ data_get($destination, 'server.id') }})"
isErrorButton buttonTitle="Remove Server"> isErrorButton buttonTitle="Remove Server">
This will stop the running application in this server and remove it as a deployment This will stop the running application in this server and remove it as a deployment
destination.<br><br>Please think again. destination.<br><br>Please think again.
</x-new-modal> </x-new-modal>
</div> </div>
@endforeach @endforeach
@endif @endif