fix: add finished_at to app deployment jobs

fix: show deployment job running measurements
fix: terminal should not be wire:navigated
This commit is contained in:
Andras Bacsai
2025-01-16 12:05:59 +01:00
parent 11414d347f
commit 55d61ffaee
13 changed files with 65 additions and 29 deletions

View File

@@ -316,6 +316,10 @@ class ApplicationDeploymentJob implements ShouldBeEncrypted, ShouldQueue
$this->fail($e); $this->fail($e);
throw $e; throw $e;
} finally { } finally {
$this->application_deployment_queue->update([
'finished_at' => now(),
]);
if ($this->use_build_server) { if ($this->use_build_server) {
$this->server = $this->build_server; $this->server = $this->build_server;
} else { } else {

View File

@@ -18,7 +18,7 @@ class Index extends Component
public int $skip = 0; public int $skip = 0;
public int $default_take = 40; public int $default_take = 10;
public bool $show_next = false; public bool $show_next = false;
@@ -42,7 +42,7 @@ class Index extends Component
if (! $application) { if (! $application) {
return redirect()->route('dashboard'); return redirect()->route('dashboard');
} }
['deployments' => $deployments, 'count' => $count] = $application->deployments(0, 40); ['deployments' => $deployments, 'count' => $count] = $application->deployments(0, $this->default_take);
$this->application = $application; $this->application = $application;
$this->deployments = $deployments; $this->deployments = $deployments;
$this->deployments_count = $count; $this->deployments_count = $count;

View File

@@ -0,0 +1,22 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up()
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->timestamp('finished_at')->nullable();
});
}
public function down()
{
Schema::table('application_deployment_queues', function (Blueprint $table) {
$table->dropColumn('finished_at');
});
}
};

View File

@@ -0,0 +1 @@
!function(r,e){"object"==typeof exports&&"undefined"!=typeof module?module.exports=e():"function"==typeof define&&define.amd?define(e):(r="undefined"!=typeof globalThis?globalThis:r||self).dayjs_plugin_relativeTime=e()}(this,(function(){"use strict";return function(r,e,t){r=r||{};var n=e.prototype,o={future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function i(r,e,t,o){return n.fromToBase(r,e,t,o)}t.en.relativeTime=o,n.fromToBase=function(e,n,i,d,u){for(var f,a,s,l=i.$locale().relativeTime||o,h=r.thresholds||[{l:"s",r:44,d:"second"},{l:"m",r:89},{l:"mm",r:44,d:"minute"},{l:"h",r:89},{l:"hh",r:21,d:"hour"},{l:"d",r:35},{l:"dd",r:25,d:"day"},{l:"M",r:45},{l:"MM",r:10,d:"month"},{l:"y",r:17},{l:"yy",d:"year"}],m=h.length,c=0;c<m;c+=1){var y=h[c];y.d&&(f=d?t(e).diff(i,y.d,!0):i.diff(e,y.d,!0));var p=(r.rounding||Math.round)(Math.abs(f));if(s=f>0,p<=y.r||!y.r){p<=1&&c>0&&(y=h[c-1]);var v=l[y.l];u&&(p=u(""+p)),a="string"==typeof v?v.replace("%d",p):v(p,n,y.l,s);break}}if(n)return a;var M=s?l.future:l.past;return"function"==typeof M?M(a):M.replace("%s",a)},n.to=function(r,e){return i(r,e,this,!0)},n.from=function(r,e){return i(r,e,this)};var d=function(r){return r.$u?t.utc():t()};n.toNow=function(r){return this.to(d(this),r)},n.fromNow=function(r){return this.from(d(this),r)}}}));

View File

@@ -0,0 +1 @@
!function(t,i){"object"==typeof exports&&"undefined"!=typeof module?module.exports=i():"function"==typeof define&&define.amd?define(i):(t="undefined"!=typeof globalThis?globalThis:t||self).dayjs_plugin_utc=i()}(this,(function(){"use strict";var t="minute",i=/[+-]\d\d(?::?\d\d)?/g,e=/([+-]|\d\d)/g;return function(s,f,n){var u=f.prototype;n.utc=function(t){var i={date:t,utc:!0,args:arguments};return new f(i)},u.utc=function(i){var e=n(this.toDate(),{locale:this.$L,utc:!0});return i?e.add(this.utcOffset(),t):e},u.local=function(){return n(this.toDate(),{locale:this.$L,utc:!1})};var o=u.parse;u.parse=function(t){t.utc&&(this.$u=!0),this.$utils().u(t.$offset)||(this.$offset=t.$offset),o.call(this,t)};var r=u.init;u.init=function(){if(this.$u){var t=this.$d;this.$y=t.getUTCFullYear(),this.$M=t.getUTCMonth(),this.$D=t.getUTCDate(),this.$W=t.getUTCDay(),this.$H=t.getUTCHours(),this.$m=t.getUTCMinutes(),this.$s=t.getUTCSeconds(),this.$ms=t.getUTCMilliseconds()}else r.call(this)};var a=u.utcOffset;u.utcOffset=function(s,f){var n=this.$utils().u;if(n(s))return this.$u?0:n(this.$offset)?a.call(this):this.$offset;if("string"==typeof s&&(s=function(t){void 0===t&&(t="");var s=t.match(i);if(!s)return null;var f=(""+s[0]).match(e)||["-",0,0],n=f[0],u=60*+f[1]+ +f[2];return 0===u?0:"+"===n?u:-u}(s),null===s))return this;var u=Math.abs(s)<=16?60*s:s,o=this;if(f)return o.$offset=u,o.$u=0===s,o;if(0!==s){var r=this.$u?this.toDate().getTimezoneOffset():-1*this.utcOffset();(o=this.local().add(u+r,t)).$offset=u,o.$x.$localOffset=r}else o=this.utc();return o};var h=u.format;u.format=function(t){var i=t||(this.$u?"YYYY-MM-DDTHH:mm:ss[Z]":"");return h.call(this,i)},u.valueOf=function(){var t=this.$utils().u(this.$offset)?0:this.$offset+(this.$x.$localOffset||this.$d.getTimezoneOffset());return this.$d.valueOf()-6e4*t},u.isUTC=function(){return!!this.$u},u.toISOString=function(){return this.toDate().toISOString()},u.toString=function(){return this.toDate().toUTCString()};var l=u.toDate;u.toDate=function(t){return"s"===t&&this.$offset?n(this.format("YYYY-MM-DD HH:mm:ss:SSS")).toDate():l.call(this)};var c=u.diff;u.diff=function(t,i,e){if(t&&this.$u===t.$u)return c.call(this,t,i,e);var s=this.local(),f=n(t).local();return c.call(s,f,i,e)}}}));

1
public/js/dayjs.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@@ -261,7 +261,7 @@
</a> </a>
</li> </li>
<li> <li>
<a title="Terminal" wire:navigate <a title="Terminal"
class="{{ request()->is('terminal*') ? 'menu-item-active menu-item' : 'menu-item' }}" class="{{ request()->is('terminal*') ? 'menu-item-active menu-item' : 'menu-item' }}"
href="{{ route('terminal') }}"> href="{{ route('terminal') }}">
<svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" <svg class="icon" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"

View File

@@ -39,7 +39,7 @@
]) }}"> ]) }}">
<button>Resources</button> <button>Resources</button>
</a> </a>
<a wire:navigate class="{{ request()->routeIs('server.command') ? 'dark:text-white' : '' }}" <a class="{{ request()->routeIs('server.command') ? 'dark:text-white' : '' }}"
href="{{ route('server.command', [ href="{{ route('server.command', [
'server_uuid' => data_get($server, 'uuid'), 'server_uuid' => data_get($server, 'uuid'),
]) }}"> ]) }}">

View File

@@ -40,7 +40,9 @@
<script type="text/javascript" src="{{ URL::asset('js/echo.js') }}"></script> <script type="text/javascript" src="{{ URL::asset('js/echo.js') }}"></script>
<script type="text/javascript" src="{{ URL::asset('js/pusher.js') }}"></script> <script type="text/javascript" src="{{ URL::asset('js/pusher.js') }}"></script>
<script type="text/javascript" src="{{ URL::asset('js/apexcharts.js') }}"></script> <script type="text/javascript" src="{{ URL::asset('js/apexcharts.js') }}"></script>
<script type="text/javascript" src="{{ URL::asset('js/dayjs.min.js') }}"></script>
<script type="text/javascript" src="{{ URL::asset('js/dayjs-plugin-utc.js') }}"></script>
<script type="text/javascript" src="{{ URL::asset('js/dayjs-plugin-relativeTime.js') }}"></script>
@endauth @endauth
</head> </head>
@section('body') @section('body')

View File

@@ -5,12 +5,11 @@
<h1>Deployments</h1> <h1>Deployments</h1>
<livewire:project.shared.configuration-checker :resource="$application" /> <livewire:project.shared.configuration-checker :resource="$application" />
<livewire:project.application.heading :application="$application" /> <livewire:project.application.heading :application="$application" />
{{-- <livewire:project.application.deployment.show :application="$application" :deployments="$deployments" :deployments_count="$deployments_count" /> --}}
<div class="flex flex-col gap-2 pb-10" <div class="flex flex-col gap-2 pb-10"
@if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif> @if ($skip == 0) wire:poll.5000ms='reload_deployments' @endif>
<div class="flex items-end gap-2 pt-4"> <div class="flex items-end gap-2 pt-4">
<h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2> <h2>Deployments <span class="text-xs">({{ $deployments_count }})</span></h2>
@if ($deployments_count > 0) @if ($deployments_count > 0 && $deployments_count > $default_take)
<x-forms.button disabled="{{ !$show_prev }}" wire:click="previous_page('{{ $default_take }}')"><svg <x-forms.button disabled="{{ !$show_prev }}" wire:click="previous_page('{{ $default_take }}')"><svg
class="w-6 h-6" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"> class="w-6 h-6" 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"
@@ -38,8 +37,8 @@
'border-error border-dashed ' => 'border-error border-dashed ' =>
data_get($deployment, 'status') === 'failed', data_get($deployment, 'status') === 'failed',
'border-success' => data_get($deployment, 'status') === 'finished', 'border-success' => data_get($deployment, 'status') === 'finished',
]) ]) wire:navigate
wire:navigate href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}"> href="{{ $current_url . '/' . data_get($deployment, 'deployment_uuid') }}">
<div class="flex flex-col justify-start"> <div class="flex flex-col justify-start">
<div class="flex gap-1"> <div class="flex gap-1">
{{ $deployment->created_at }} UTC {{ $deployment->created_at }} UTC
@@ -58,8 +57,8 @@
Pull Request #{{ data_get($deployment, 'pull_request_id') }} Pull Request #{{ data_get($deployment, 'pull_request_id') }}
@endif @endif
@if (data_get($deployment, 'commit')) @if (data_get($deployment, 'commit'))
<div class="dark:hover:text-white" <div class="dark:hover:text-white" wire:navigate.prevent
wire:navigate.prevent href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}"> href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}">
<div class="text-xs underline"> <div class="text-xs underline">
@if ($deployment->commitMessage()) @if ($deployment->commitMessage())
({{ data_get_str($deployment, 'commit')->limit(7) }} - ({{ data_get_str($deployment, 'commit')->limit(7) }} -
@@ -83,8 +82,8 @@
@endif @endif
@endif @endif
@if (data_get($deployment, 'commit')) @if (data_get($deployment, 'commit'))
<div class="dark:hover:text-white" <div class="dark:hover:text-white" wire:navigate.prevent
wire:navigate.prevent href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}"> href="{{ $application->gitCommitLink(data_get($deployment, 'commit')) }}">
<div class="text-xs underline"> <div class="text-xs underline">
@if ($deployment->commitMessage()) @if ($deployment->commitMessage())
({{ data_get_str($deployment, 'commit')->limit(7) }} - ({{ data_get_str($deployment, 'commit')->limit(7) }} -
@@ -104,13 +103,12 @@
@endif @endif
</div> </div>
<div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->updated_at }}')"> <div class="flex flex-col" x-data="elapsedTime('{{ $deployment->deployment_uuid }}', '{{ $deployment->status }}', '{{ $deployment->created_at }}', '{{ $deployment->finished_at }}')">
<div> <div>
@if ($deployment->status !== 'in_progress') @if ($deployment->status !== 'in_progress')
Finished <span x-text="measure_since_started()">0s</span> ago in <span x-html="measurementText()" />
<span class="font-bold" x-text="measure_finished_time()">0s</span>
@else @else
Running for <span class="font-bold" x-text="measure_since_started()">0s</span> Running for <span class="font-bold" x-text="measureSinceStarted()">0s</span>
@endif @endif
</div> </div>
@@ -121,16 +119,13 @@
@endforelse @endforelse
@if ($deployments_count > 0) @if ($deployments_count > 0)
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/dayjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/utc.js"></script>
<script src="https://cdn.jsdelivr.net/npm/dayjs@1/plugin/relativeTime.js"></script>
<script> <script>
let timers = {}; let timers = {};
dayjs.extend(window.dayjs_plugin_utc); dayjs.extend(window.dayjs_plugin_utc);
dayjs.extend(window.dayjs_plugin_relativeTime); dayjs.extend(window.dayjs_plugin_relativeTime);
Alpine.data('elapsedTime', (uuid, status, created_at, updated_at) => ({ Alpine.data('elapsedTime', (uuid, status, created_at, finished_at) => ({
finished_time: 'calculating...', finished_time: 'calculating...',
started_time: 'calculating...', started_time: 'calculating...',
init() { init() {
@@ -143,20 +138,29 @@
'second') + 's' 'second') + 's'
}, 1000); }, 1000);
} else { } else {
let seconds = dayjs.utc(updated_at).diff(dayjs.utc(created_at), 'second') this.finished_time = dayjs.utc(finished_at).diff(dayjs.utc(created_at), 'second')
this.finished_time = seconds + 's'; if (isNaN(this.finished_time)) {
this.finished_time = 0;
}
} }
}, },
measure_finished_time() { measureFinishedTime() {
if (this.finished_time > 2000) { if (this.finished_time > 2000) {
return 0; return 0;
} else { } else {
return this.finished_time; return this.finished_time;
} }
}, },
measure_since_started() { measureSinceStarted() {
return dayjs.utc(created_at).fromNow(true); // "true" prevents the "ago" suffix return dayjs.utc(created_at).fromNow(true); // "true" prevents the "ago" suffix
}, },
measurementText() {
if (this.measureFinishedTime() === 0) {
return 'Finished <span x-text="measureSinceStarted()"></span> ago';
} else {
return 'Finished <span x-text="measureSinceStarted()"></span> ago in <span class="font-bold" x-text="measureFinishedTime()"></span><span class="font-bold">s</span>';
}
}
})) }))
</script> </script>
@endif @endif

View File

@@ -16,7 +16,7 @@
</a> </a>
@if (!$application->destination->server->isSwarm()) @if (!$application->destination->server->isSwarm())
<a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}" <a class="{{ request()->routeIs('project.application.command') ? 'dark:text-white' : '' }}"
wire:navigate href="{{ route('project.application.command', $parameters) }}"> href="{{ route('project.application.command', $parameters) }}">
<button>Terminal</button> <button>Terminal</button>
</a> </a>
@endif @endif

View File

@@ -18,7 +18,7 @@
href="{{ route('project.database.logs', $parameters) }}"> href="{{ route('project.database.logs', $parameters) }}">
<button>Logs</button> <button>Logs</button>
</a> </a>
<a wire:navigate class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}" <a class="{{ request()->routeIs('project.database.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.command', $parameters) }}"> href="{{ route('project.database.command', $parameters) }}">
<button>Terminal</button> <button>Terminal</button>
</a> </a>
@@ -27,7 +27,8 @@
$database->getMorphClass() === 'App\Models\StandaloneMongodb' || $database->getMorphClass() === 'App\Models\StandaloneMongodb' ||
$database->getMorphClass() === 'App\Models\StandaloneMysql' || $database->getMorphClass() === 'App\Models\StandaloneMysql' ||
$database->getMorphClass() === 'App\Models\StandaloneMariadb') $database->getMorphClass() === 'App\Models\StandaloneMariadb')
<a wire:navigate class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}" <a wire:navigate
class="{{ request()->routeIs('project.database.backup.index') ? 'dark:text-white' : '' }}"
href="{{ route('project.database.backup.index', $parameters) }}"> href="{{ route('project.database.backup.index', $parameters) }}">
<button>Backups</button> <button>Backups</button>
</a> </a>

View File

@@ -18,7 +18,7 @@
href="{{ route('project.service.logs', $parameters) }}"> href="{{ route('project.service.logs', $parameters) }}">
<button>Logs</button> <button>Logs</button>
</a> </a>
<a wire:navigate class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}" <a class="{{ request()->routeIs('project.service.command') ? 'dark:text-white' : '' }}"
href="{{ route('project.service.command', $parameters) }}"> href="{{ route('project.service.command', $parameters) }}">
<button>Terminal</button> <button>Terminal</button>
</a> </a>