feat: logs and execute commands with several servers

This commit is contained in:
Andras Bacsai 2024-03-02 14:55:39 +01:00
parent 60be51dbe0
commit fda5d23d32
6 changed files with 174 additions and 96 deletions

View File

@ -10,29 +10,21 @@
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Collection;
use Livewire\Component;
class ExecuteContainerCommand extends Component
{
public string $command;
public string $container;
public $containers;
public Collection $containers;
public $parameters;
public $resource;
public string $type;
public string $workDir = '';
public Server $server;
public $servers = [];
public function getListeners()
{
return [
"serviceStatusChanged",
];
}
public function serviceStatusChanged()
{
$this->getContainers();
}
public Collection $servers;
protected $rules = [
'server' => 'required',
'container' => 'required',
@ -43,20 +35,18 @@ public function serviceStatusChanged()
public function mount()
{
$this->parameters = get_route_parameters();
$this->getContainers();
}
public function getContainers()
{
$this->containers = collect();
$this->servers = collect();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->server = $this->resource->destination->server;
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, 0);
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers->push(str_replace('/', '', $container['Names']));
});
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
foreach ($this->resource->additional_servers as $server) {
if ($server->isFunctional()) {
$this->servers = $this->servers->push($server);
}
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
@ -77,47 +67,85 @@ public function getContainers()
}
}
$this->resource = $resource;
$this->server = $this->resource->destination->server;
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
$this->container = $this->resource->uuid;
// if (!str(data_get($this,'resource.status'))->startsWith('exited')) {
$this->containers->push($this->container);
// }
$this->containers->push($this->container);
} else if (data_get($this->parameters, 'service_uuid')) {
$this->type = 'service';
$this->resource = Service::where('uuid', $this->parameters['service_uuid'])->firstOrFail();
$this->resource->applications()->get()->each(function ($application) {
// if (str(data_get($application, 'status'))->contains('running')) {
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
// }
$this->containers->push(data_get($application, 'name') . '-' . data_get($this->resource, 'uuid'));
});
$this->resource->databases()->get()->each(function ($database) {
// if (str(data_get($database, 'status'))->contains('running')) {
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
// }
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
});
$this->server = $this->resource->server;
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
}
if ($this->containers->count() > 0) {
$this->container = $this->containers->first();
}
}
public function loadContainers()
{
foreach ($this->servers as $server) {
if (data_get($this->parameters, 'application_uuid')) {
if ($server->isSwarm()) {
$containers = collect([
[
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
]
]);
} else {
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
foreach ($containers as $container) {
$payload = [
'server' => $server,
'container' => $container,
];
$this->containers = $this->containers->push($payload);
}
}
}
if ($containers->count() > 0) {
if (data_get($this->parameters, 'application_uuid')) {
$this->container = data_get($this->containers->first(), 'container.Names');
} elseif (data_get($this->parameters, 'database_uuid')) {
$this->container = $this->containers->first();
} elseif (data_get($this->parameters, 'service_uuid')) {
$this->container = $this->containers->first();
}
}
}
public function runCommand()
{
$this->validate();
try {
if ($this->server->isForceDisabled()) {
if (data_get($this->parameters, 'application_uuid')) {
$container = $this->containers->where('container.Names', $this->container)->first();
$container_name = data_get($container, 'container.Names');
if (is_null($container)) {
throw new \RuntimeException('Container not found.');
}
$server = data_get($container, 'server');
} else {
$container_name = $this->container;
$server = $this->servers->first();
}
if ($server->isForceDisabled()) {
throw new \RuntimeException('Server is disabled.');
}
// Wrap command to prevent escaped execution in the host.
$cmd = 'sh -c "if [ -f ~/.profile ]; then . ~/.profile; fi; ' . str_replace('"', '\"', $this->command) . '"';
if (!empty($this->workDir)) {
$exec = "docker exec -w {$this->workDir} {$this->container} {$cmd}";
$exec = "docker exec -w {$this->workDir} {$container_name} {$cmd}";
} else {
$exec = "docker exec {$this->container} {$cmd}";
$exec = "docker exec {$container_name} {$cmd}";
}
$activity = remote_process([$exec], $this->server, ignore_errors: true);
$activity = remote_process([$exec], $server, ignore_errors: true);
$this->dispatch('activityMonitor', $activity->id);
} catch (\Throwable $e) {
return handleError($e, $this);

View File

@ -10,50 +10,57 @@
use App\Models\StandaloneMysql;
use App\Models\StandalonePostgresql;
use App\Models\StandaloneRedis;
use Illuminate\Support\Collection;
use Livewire\Component;
class Logs extends Component
{
public ?string $type = null;
public Application|Service|StandalonePostgresql|StandaloneRedis|StandaloneMongodb|StandaloneMysql|StandaloneMariadb $resource;
public Server $server;
public Collection $servers;
public Collection $containers;
public $container = [];
public $containers;
public $parameters;
public $query;
public $status;
public $serviceSubType;
public function mount()
public function loadContainers($server_id)
{
$this->containers = collect();
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
if ($this->server->isSwarm()) {
try {
$server = $this->servers->firstWhere('id', $server_id);
if ($server->isSwarm()) {
$containers = collect([
[
'Names' => $this->resource->uuid . '_' . $this->resource->uuid,
]
]);
} else {
$containers = getCurrentApplicationContainerStatus($this->server, $this->resource->id, includePullrequests: true);
$containers = getCurrentApplicationContainerStatus($server, $this->resource->id, includePullrequests: true);
}
if ($containers->count() > 0) {
$containers->each(function ($container) {
$this->containers->push(str_replace('/', '', $container['Names']));
});
$server->containers = $containers;
} catch (\Exception $e) {
return handleError($e, $this);
}
}
public function mount()
{
$this->containers = collect();
$this->servers = collect();
$this->parameters = get_route_parameters();
$this->query = request()->query();
if (data_get($this->parameters, 'application_uuid')) {
$this->type = 'application';
$this->resource = Application::where('uuid', $this->parameters['application_uuid'])->firstOrFail();
$this->status = $this->resource->status;
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
$this->containers = $this->containers->sortByDesc(function ($container) {
if (str_contains($container, '-pr-')) {
return explode('-pr-', $container)[1];
foreach ($this->resource->additional_servers as $server) {
if ($server->isFunctional()) {
$this->servers = $this->servers->push($server);
}
return $container;
});
}
} else if (data_get($this->parameters, 'database_uuid')) {
$this->type = 'database';
$resource = StandalonePostgresql::where('uuid', $this->parameters['database_uuid'])->first();
@ -74,7 +81,9 @@ public function mount()
}
$this->resource = $resource;
$this->status = $this->resource->status;
$this->server = $this->resource->destination->server;
if ($this->resource->destination->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->destination->server);
}
$this->container = $this->resource->uuid;
$this->containers->push($this->container);
} else if (data_get($this->parameters, 'service_uuid')) {
@ -87,7 +96,9 @@ public function mount()
$this->containers->push(data_get($database, 'name') . '-' . data_get($this->resource, 'uuid'));
});
$this->server = $this->resource->server;
if ($this->resource->server->isFunctional()) {
$this->servers = $this->servers->push($this->resource->server);
}
}
}

View File

@ -27,10 +27,17 @@
<a :class="activeTab === 'source' && 'text-white'"
@click.prevent="activeTab = 'source'; window.location.hash = 'source'" href="#">Source</a>
@endif
<a :class="activeTab === 'servers' && 'text-white'"
<a :class="activeTab === 'servers' && 'text-white'" class="flex items-center gap-2"
@click.prevent="activeTab = 'servers'; window.location.hash = 'servers'" href="#">Servers
@if (str($application->status)->contains('degraded'))
<span title="Some servers are unavailable">
<svg class="w-4 h-4 text-error" viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg">
<path fill="currentColor"
d="M240.26 186.1L152.81 34.23a28.74 28.74 0 0 0-49.62 0L15.74 186.1a27.45 27.45 0 0 0 0 27.71A28.31 28.31 0 0 0 40.55 228h174.9a28.31 28.31 0 0 0 24.79-14.19a27.45 27.45 0 0 0 .02-27.71m-20.8 15.7a4.46 4.46 0 0 1-4 2.2H40.55a4.46 4.46 0 0 1-4-2.2a3.56 3.56 0 0 1 0-3.73L124 46.2a4.77 4.77 0 0 1 8 0l87.44 151.87a3.56 3.56 0 0 1 .02 3.73M116 136v-32a12 12 0 0 1 24 0v32a12 12 0 0 1-24 0m28 40a16 16 0 1 1-16-16a16 16 0 0 1 16 16" />
</svg>
</span>
@endif
</a>
<a :class="activeTab === 'scheduled-tasks' && 'text-white'"
@click.prevent="activeTab = 'scheduled-tasks'; window.location.hash = 'scheduled-tasks'"
href="#">Scheduled Tasks

View File

@ -12,23 +12,44 @@
@elseif ($type === 'service')
<h2>Execute Command</h2>
@endif
@if (count($containers) > 0)
<form class="flex flex-col gap-2 pt-4" wire:submit='runCommand'>
<div class="flex gap-2">
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
<x-forms.input id="workDir" label="Working directory" />
</div>
<x-forms.select label="Container" id="container" required>
<option disabled selected>Select container</option>
@foreach ($containers as $container)
<option value="{{ $container }}">{{ $container }}</option>
@endforeach
</x-forms.select>
<x-forms.button type="submit">Run</x-forms.button>
</form>
@else
<div class="pt-4">No containers are not running.</div>
@endif
<div x-init="$wire.loadContainers">
<div class="pt-4" wire:loading wire:target='loadContainers'>
Loading containers...
</div>
<div wire:loading.remove wire:target='loadContainers'>
@if (count($containers) > 0)
<form class="flex flex-col gap-2 pt-4" wire:submit='runCommand'>
<div class="flex gap-2">
<x-forms.input placeholder="ls -l" autofocus id="command" label="Command" required />
<x-forms.input id="workDir" label="Working directory" />
</div>
<x-forms.select label="Container" id="container" required>
<option disabled selected>Select container</option>
@if (data_get($this->parameters, 'application_uuid'))
@foreach ($containers as $container)
<option value="{{ data_get($container, 'container.Names') }}">
{{ data_get($container, 'container.Names') }}
</option>
@endforeach
@elseif(data_get($this->parameters, 'service_uuid'))
@foreach ($containers as $container)
<option value="{{ $container }}">
{{ $container }}
</option>
@endforeach
@else
<option value="{{ $container }}">
{{ $container }}
</option>
@endif
</x-forms.select>
<x-forms.button type="submit">Run</x-forms.button>
</form>
@else
<div class="pt-4">No containers are not running.</div>
@endif
</div>
</div>
<div class="container w-full pt-10 mx-auto">
<livewire:activity-monitor header="Command output" />
</div>

View File

@ -1,7 +1,12 @@
<div>
<div x-init="$wire.getLogs" id="screen" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }">
<div class="flex items-center gap-2">
<h3>{{ str($container)->beforeLast('-')->headline() }}</h3>
@if ($resource->type() === 'application')
<h3>{{ $container }}</h3>
@else
<h3>{{ str($container)->beforeLast('-')->headline() }}</h3>
@endif
<div>Server: {{ $server->name }} </div>
@if ($pull_request)
<div>({{ $pull_request }})</div>
@endif

View File

@ -3,14 +3,20 @@
<h1>Logs</h1>
<livewire:project.application.heading :application="$resource" />
<div class="pt-4">
@forelse ($containers as $container)
@if ($loop->first)
<h2 class="pb-4">Logs</h2>
@endif
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="$container" />
@empty
<div>No containers are not running.</div>
@endforelse
<h2 class="pb-4">Logs</h2>
<div class="pt-2" wire:loading wire:target="loadContainers">
Loading containers...
</div>
@foreach ($servers as $server)
<h3 x-init="$wire.loadContainers({{ $server->id }})"></h3>
<div wire:loading.remove wire:target="loadContainers">
@forelse (data_get($server,'containers',[]) as $container)
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="data_get($container,'Names')" />
@empty
<div class="pt-2">No containers are not running on server: {{$server->name}}</div>
@endforelse
</div>
@endforeach
</div>
@elseif ($type === 'database')
<h1>Logs</h1>
@ -20,9 +26,9 @@
@if ($loop->first)
<h2 class="pb-4">Logs</h2>
@endif
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="$container" />
<livewire:project.shared.get-logs :server="$servers[0]" :resource="$resource" :container="$container" />
@empty
<div>No containers are not running.</div>
<div class="pt-2">No containers are not running.</div>
@endforelse
</div>
@elseif ($type === 'service')
@ -31,9 +37,9 @@
@if ($loop->first)
<h2 class="pb-4">Logs</h2>
@endif
<livewire:project.shared.get-logs :server="$server" :resource="$resource" :container="$container" />
<livewire:project.shared.get-logs :server="$servers[0]" :resource="$resource" :container="$container" />
@empty
<div>No containers are not running.</div>
<div class="pt-2">No containers are not running.</div>
@endforelse
</div>
@endif