fix: pull requests

feat: add follow for full screen logs
This commit is contained in:
Andras Bacsai 2023-11-01 20:55:21 +01:00
parent b557ea1e1d
commit 0838343841
7 changed files with 98 additions and 52 deletions

View File

@ -138,9 +138,16 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
public function handle(): void public function handle(): void
{ {
// ray()->measure(); // ray()->measure();
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id); $containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
if ($containers->count() > 0) { if ($containers->count() === 1) {
$this->currently_running_container_name = data_get($containers[0], 'Names'); $this->currently_running_container_name = data_get($containers[0], 'Names');
} else {
$foundContainer = $containers->filter(function ($container) {
return !str(data_get($container, 'Names'))->startsWith("{$this->application->uuid}-pr-");
})->first();
if ($foundContainer) {
$this->currently_running_container_name = data_get($foundContainer, 'Names');
}
} }
if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) { if ($this->pull_request_id !== 0 && $this->pull_request_id !== null) {
$this->currently_running_container_name = $this->container_name; $this->currently_running_container_name = $this->container_name;
@ -747,14 +754,21 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$labels = collect(generateLabelsApplication($this->application, $this->preview)); $labels = collect(generateLabelsApplication($this->application, $this->preview));
} }
if ($this->pull_request_id !== 0) { if ($this->pull_request_id !== 0) {
$newLabels = collect(generateLabelsApplication($this->application, $this->preview));
$newHostLabel = $newLabels->filter(function ($label) {
return str($label)->contains('Host');
});
$labels = $labels->reject(function ($label) { $labels = $labels->reject(function ($label) {
return str($label)->contains('Host'); return str($label)->contains('Host');
}); });
$newLabels = collect(generateLabelsApplication($this->application, $this->preview));
$hostLabels = $newLabels->filter(function ($label) { $labels = $labels->map(function ($label) {
return str($label)->contains('Host'); $pattern = '/([a-zA-Z0-9]+)-(\d+)-(http|https)/';
$replacement = "$1-pr-{$this->pull_request_id}-$2-$3";
$newLabel = preg_replace($pattern, $replacement, $label);
return $newLabel;
}); });
$labels = $labels->merge($hostLabels); $labels = $labels->merge($newHostLabel);
} }
$labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray(); $labels = $labels->merge(defaultLabels($this->application->id, $this->application->uuid, $this->pull_request_id))->toArray();
$docker_compose = [ $docker_compose = [

View File

@ -18,7 +18,6 @@ use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Queue\Middleware\WithoutOverlapping; use Illuminate\Queue\Middleware\WithoutOverlapping;
use Illuminate\Queue\SerializesModels; use Illuminate\Queue\SerializesModels;
use Illuminate\Support\Arr; use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
{ {
@ -26,6 +25,7 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
public function __construct(public Server $server) public function __construct(public Server $server)
{ {
$this->handle();
} }
public function middleware(): array public function middleware(): array
{ {

View File

@ -6,11 +6,14 @@ use App\Models\Server;
use Illuminate\Support\Collection; use Illuminate\Support\Collection;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use Spatie\Url\Url; use Spatie\Url\Url;
use Visus\Cuid2\Cuid2;
function getCurrentApplicationContainerStatus(Server $server, int $id): Collection function getCurrentApplicationContainerStatus(Server $server, int $id, ?int $pullRequestId = null): Collection
{ {
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}' "], $server); if ($pullRequestId) {
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --filter='label=coolify.pullRequestId={$pullRequestId}' --format '{{json .}}' "], $server);
} else {
$containers = instant_remote_process(["docker ps -a --filter='label=coolify.applicationId={$id}' --format '{{json .}}'"], $server);
}
if (!$containers) { if (!$containers) {
return collect([]); return collect([]);
} }
@ -77,20 +80,6 @@ function executeInDocker(string $containerId, string $command)
// return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'"; // return "docker exec {$this->deployment_uuid} bash -c '{$command} |& tee -a /proc/1/fd/1; [ \$PIPESTATUS -eq 0 ] || exit \$PIPESTATUS'";
} }
function getApplicationContainerStatus(Application $application)
{
$server = data_get($application, 'destination.server');
$id = $application->id;
if (!$server) {
return 'exited';
}
$containers = getCurrentApplicationContainerStatus($server, $id);
if ($containers->count() > 0) {
$status = data_get($containers[0], 'State', 'exited');
return $status;
}
return 'exited';
}
function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false) function getContainerStatus(Server $server, string $container_id, bool $all_data = false, bool $throwError = false)
{ {
$container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError); $container = instant_remote_process(["docker inspect --format '{{json .}}' {$container_id}"], $server, $throwError);
@ -212,9 +201,9 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$onlyPort = $ports[0]; $onlyPort = $ports[0];
} }
$pull_request_id = data_get($preview, 'pull_request_id', 0); $pull_request_id = data_get($preview, 'pull_request_id', 0);
$appId = $application->id; $appUuid = $application->uuid;
if ($pull_request_id !== 0 && $pull_request_id !== null) { if ($pull_request_id !== 0) {
$appId = $appId . '-pr-' . $pull_request_id; $appUuid = $appUuid . '-pr-' . $pull_request_id;
} }
$labels = collect([]); $labels = collect([]);
if ($application->fqdn) { if ($application->fqdn) {
@ -224,7 +213,7 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
$domains = Str::of(data_get($application, 'fqdn'))->explode(','); $domains = Str::of(data_get($application, 'fqdn'))->explode(',');
} }
// Add Traefik labels no matter which proxy is selected // Add Traefik labels no matter which proxy is selected
$labels = $labels->merge(fqdnLabelsForTraefik($application->uuid, $domains, $application->settings->is_force_https_enabled, $onlyPort)); $labels = $labels->merge(fqdnLabelsForTraefik($appUuid, $domains, $application->settings->is_force_https_enabled, $onlyPort));
} }
return $labels->all(); return $labels->all();
} }

View File

@ -122,5 +122,5 @@ tr td:first-child {
@apply pt-2 pb-10; @apply pt-2 pb-10;
} }
.fullscreen { .fullscreen {
@apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-200 overflow-y-auto scrollbar ; @apply fixed top-0 left-0 w-full h-full z-[9999] bg-coolgray-100 overflow-y-auto scrollbar pb-4 ;
} }

View File

@ -1,4 +1,4 @@
<div class="pt-4" x-data="{ fullscreen: false }"> <div class="pt-4" x-data="{ fullscreen: false, alwaysScroll: false, intervalId: null }">
<livewire:project.application.deployment-navbar :application_deployment_queue="$application_deployment_queue" /> <livewire:project.application.deployment-navbar :application_deployment_queue="$application_deployment_queue" />
@if (data_get($application_deployment_queue, 'status') === 'in_progress') @if (data_get($application_deployment_queue, 'status') === 'in_progress')
<div class="flex items-center gap-1 pt-2 ">Deployment is <div class="flex items-center gap-1 pt-2 ">Deployment is
@ -6,24 +6,35 @@
</div> </div>
<x-loading class="loading-ring" /> <x-loading class="loading-ring" />
</div> </div>
<div class="">Logs will be updated automatically.</div> {{-- <div class="">Logs will be updated automatically.</div> --}}
@else @else
<div class="pt-2 ">Deployment is <span <div class="pt-2 ">Deployment is <span
class="text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}</span>. class="text-warning">{{ Str::headline(data_get($application_deployment_queue, 'status')) }}</span>.
</div> </div>
@endif @endif
<div :class="fullscreen ? 'fullscreen' : ''"> <div id="screen" :class="fullscreen ? 'fullscreen' : ''">
<div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif <div @if ($isKeepAliveOn) wire:poll.2000ms="polling" @endif
class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto text-xs border border-dotted rounded scrollbar border-coolgray-400" class="relative flex flex-col-reverse w-full p-2 px-4 mt-4 overflow-y-auto scrollbar border-coolgray-400"
:class="fullscreen ? '' : 'max-h-[32rem]'"> :class="fullscreen ? '' : 'max-h-[40rem] border border-dotted rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-2 right-2" <button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4" x-on:click="makeFullscreen"><svg
x-on:click="fullscreen = !fullscreen"><svg class="icon" viewBox="0 0 24 24" class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" /> stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button> </svg></button>
<button title="Go Top" x-show="fullscreen" class="fixed top-4 right-28" x-on:click="goTop"> <svg
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-10l-4-4M8 9l4-4" />
</svg></button>
<button title="Follow Logs" x-show="fullscreen" :class="alwaysScroll ? 'text-warning' : ''"
class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M12 5v14m4-4l-4 4m-4-4l4 4" />
</svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8" <button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
x-on:click="fullscreen = !fullscreen"><svg class="fixed icon" viewBox="0 0 24 24" x-on:click="makeFullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"> xmlns="http://www.w3.org/2000/svg">
<g fill="none"> <g fill="none">
<path <path
@ -32,7 +43,7 @@
d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" /> d="M9.793 12.793a1 1 0 0 1 1.497 1.32l-.083.094L6.414 19H9a1 1 0 0 1 .117 1.993L9 21H4a1 1 0 0 1-.993-.883L3 20v-5a1 1 0 0 1 1.993-.117L5 15v2.586l4.793-4.793ZM20 3a1 1 0 0 1 .993.883L21 4v5a1 1 0 0 1-1.993.117L19 9V6.414l-4.793 4.793a1 1 0 0 1-1.497-1.32l.083-.094L17.586 5H15a1 1 0 0 1-.117-1.993L15 3h5Z" />
</g> </g>
</svg></button> </svg></button>
<span class="flex flex-col"> <div id="logs" class="flex flex-col">
@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([
@ -49,7 +60,40 @@
@else @else
<span class="font-mono text-neutral-400">No logs yet.</span> <span class="font-mono text-neutral-400">No logs yet.</span>
@endif @endif
</span> </div>
</div> </div>
</div> </div>
<script>
function makeFullscreen() {
this.fullscreen = !this.fullscreen;
if (this.fullscreen === false) {
this.alwaysScroll = false;
clearInterval(this.intervalId);
}
}
function toggleScroll() {
this.alwaysScroll = !this.alwaysScroll;
if (this.alwaysScroll) {
this.intervalId = setInterval(() => {
const screen = document.getElementById('screen');
const logs = document.getElementById('logs');
if (screen.scrollTop !== logs.scrollHeight) {
screen.scrollTop = logs.scrollHeight;
}
}, 100);
} else {
clearInterval(this.intervalId);
this.intervalId = null;
}
}
function goTop() {
this.alwaysScroll = false;
clearInterval(this.intervalId);
const screen = document.getElementById('screen');
screen.scrollTop = 0;
}
</script>
</div> </div>

View File

@ -78,11 +78,9 @@
Redeploy Redeploy
@endif @endif
</x-forms.button> </x-forms.button>
@if (data_get($preview, 'status') !== 'exited') <x-forms.button wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Remove
<x-forms.button wire:click="stop({{ data_get($preview, 'pull_request_id') }})">Remove Preview
Preview </x-forms.button>
</x-forms.button>
@endif
<a <a
href="{{ route('project.application.deployments', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}"> href="{{ route('project.application.deployments', [...$parameters, 'pull_request_id' => data_get($preview, 'pull_request_id')]) }}">
<x-forms.button> <x-forms.button>

View File

@ -13,17 +13,18 @@
<x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input> <x-forms.input label="Only Show Number of Lines" placeholder="1000" required id="numberOfLines"></x-forms.input>
<x-forms.button type="submit">Refresh</x-forms.button> <x-forms.button type="submit">Refresh</x-forms.button>
</form> </form>
<div x-data="{ fullscreen: false }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'"> <div x-data="{ fullscreen: false }" :class="fullscreen ? 'fullscreen' : 'container w-full pt-4 mx-auto'">
<div <div class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-white scrollbar border-coolgray-300"
class="relative flex flex-col-reverse w-full p-4 pt-6 overflow-y-auto text-xs text-white border border-solid rounded scrollbar border-coolgray-300" :class="fullscreen ? '' : 'max-h-[32rem]'"> :class="fullscreen ? '' : 'max-h-[40rem] border border-solid rounded'">
<button title="Minimize" x-show="fullscreen" class="fixed top-2 right-2" x-on:click="fullscreen = !fullscreen"><svg <button title="Minimize" x-show="fullscreen" class="fixed top-4 right-4"
class="icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> x-on:click="fullscreen = !fullscreen"><svg class="icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" <path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" /> stroke-width="2" d="M6 14h4m0 0v4m0-4l-6 6m14-10h-4m0 0V6m0 4l6-6" />
</svg></button> </svg></button>
<button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8" x-on:click="fullscreen = !fullscreen"><svg <button title="Fullscreen" x-show="!fullscreen" class="absolute top-2 right-8"
class="fixed icon" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> x-on:click="fullscreen = !fullscreen"><svg class="fixed icon" viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg">
<g fill="none"> <g fill="none">
<path <path
d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" /> d="M24 0v24H0V0h24ZM12.593 23.258l-.011.002l-.071.035l-.02.004l-.014-.004l-.071-.035c-.01-.004-.019-.001-.024.005l-.004.01l-.017.428l.005.02l.01.013l.104.074l.015.004l.012-.004l.104-.074l.012-.016l.004-.017l-.017-.427c-.002-.01-.009-.017-.017-.018Zm.265-.113l-.013.002l-.185.093l-.01.01l-.003.011l.018.43l.005.012l.008.007l.201.093c.012.004.023 0 .029-.008l.004-.014l-.034-.614c-.003-.012-.01-.02-.02-.022Zm-.715.002a.023.023 0 0 0-.027.006l-.006.014l-.034.614c0 .012.007.02.017.024l.015-.002l.201-.093l.01-.008l.004-.011l.017-.43l-.003-.012l-.01-.01l-.184-.092Z" />