diff --git a/app/Livewire/Project/Application/Deployment/Show.php b/app/Livewire/Project/Application/Deployment/Show.php index cdac47d3d..e3756eab2 100644 --- a/app/Livewire/Project/Application/Deployment/Show.php +++ b/app/Livewire/Project/Application/Deployment/Show.php @@ -18,6 +18,8 @@ class Show extends Component public $isKeepAliveOn = true; + public bool $is_debug_enabled = false; + public function getListeners() { $teamId = auth()->user()->currentTeam()->id; @@ -56,9 +58,18 @@ public function mount() $this->application_deployment_queue = $application_deployment_queue; $this->horizon_job_status = $this->application_deployment_queue->getHorizonJobStatus(); $this->deployment_uuid = $deploymentUuid; + $this->is_debug_enabled = $this->application->settings->is_debug_enabled; $this->isKeepAliveOn(); } + public function toggleDebug() + { + $this->application->settings->is_debug_enabled = ! $this->application->settings->is_debug_enabled; + $this->application->settings->save(); + $this->is_debug_enabled = $this->application->settings->is_debug_enabled; + $this->application_deployment_queue->refresh(); + } + public function refreshQueue() { $this->application_deployment_queue->refresh(); diff --git a/app/Livewire/Project/Shared/GetLogs.php b/app/Livewire/Project/Shared/GetLogs.php index 304f7b411..e225f1e39 100644 --- a/app/Livewire/Project/Shared/GetLogs.php +++ b/app/Livewire/Project/Shared/GetLogs.php @@ -43,6 +43,8 @@ class GetLogs extends Component public ?int $numberOfLines = 100; + public bool $expandByDefault = false; + public function mount() { if (! is_null($this->resource)) { @@ -92,6 +94,18 @@ public function instantSave() } } + public function toggleTimestamps() + { + $this->showTimeStamps = ! $this->showTimeStamps; + $this->instantSave(); + $this->getLogs(true); + } + + public function toggleStreamLogs() + { + $this->streamLogs = ! $this->streamLogs; + } + public function getLogs($refresh = false) { if (! $this->server->isFunctional()) { diff --git a/resources/views/livewire/project/application/deployment-navbar.blade.php b/resources/views/livewire/project/application/deployment-navbar.blade.php index 60c660bf7..8d0fc18fb 100644 --- a/resources/views/livewire/project/application/deployment-navbar.blade.php +++ b/resources/views/livewire/project/application/deployment-navbar.blade.php @@ -1,18 +1,12 @@

Deployment Log

- @if ($is_debug_enabled) - Hide Debug Logs - @else - Show Debug Logs - @endif - @if (isDev()) - Copy Logs - @endif @if (data_get($application_deployment_queue, 'status') === 'queued') Force Start @endif - @if (data_get($application_deployment_queue, 'status') === 'in_progress' || - data_get($application_deployment_queue, 'status') === 'queued') + @if ( + data_get($application_deployment_queue, 'status') === 'in_progress' || + data_get($application_deployment_queue, 'status') === 'queued' + ) Cancel @endif -
+ \ No newline at end of file diff --git a/resources/views/livewire/project/application/deployment/show.blade.php b/resources/views/livewire/project/application/deployment/show.blade.php index b52a6eaf1..d054f083e 100644 --- a/resources/views/livewire/project/application/deployment/show.blade.php +++ b/resources/views/livewire/project/application/deployment/show.blade.php @@ -1,15 +1,17 @@
{{ data_get_str($application, 'name')->limit(10) }} > Deployment | Coolify - -

Deployment

- - -
Deployment + + +
- - @if (data_get($application_deployment_queue, 'status') === 'in_progress') -
Deployment is -
- {{ Str::headline(data_get($this->application_deployment_queue, 'status')) }}. -
- -
- {{--
Logs will be updated automatically.
--}} - @else -
Deployment is {{ Str::headline(data_get($application_deployment_queue, 'status')) }}. -
- @endif -
-
-
-
- - - - - + + @if (data_get($application_deployment_queue, 'status') === 'in_progress') +
Deployment is +
+ {{ Str::headline(data_get($this->application_deployment_queue, 'status')) }}.
+
- -
- @forelse ($this->logLines as $line) -
isset($line['command']) && $line['command'], - 'flex gap-2 dark:hover:bg-coolgray-500 hover:bg-gray-100', - ])> - {{ $line['timestamp'] }} - $line['hidden'], - 'text-red-500' => $line['stderr'], - 'font-bold' => isset($line['command']) && $line['command'], - 'whitespace-pre-wrap', - ])>{!! (isset($line['command']) && $line['command'] ? '[CMD]: ' : '') . trim($line['line']) !!} + {{--
Logs will be updated automatically.
--}} + @else +
Deployment is {{ Str::headline(data_get($application_deployment_queue, 'status')) }}. +
+ @endif +
+
+
+ + +
+
+ + + + + +
+ + + + + +
- @empty - No logs yet. - @endforelse +
+
+
+
+ No matches found. +
+ @forelse ($this->logLines as $line) + @php + $lineContent = (isset($line['command']) && $line['command'] ? '[CMD]: ' : '') . trim($line['line']); + $searchableContent = $line['timestamp'] . ' ' . $lineContent; + @endphp +
isset($line['command']) && $line['command'], + 'flex gap-2 dark:hover:bg-coolgray-500 hover:bg-gray-100', + ])> + {{ $line['timestamp'] }} + $line['hidden'], + 'text-red-500' => $line['stderr'], + 'font-bold' => isset($line['command']) && $line['command'], + 'whitespace-pre-wrap', + ]) + x-html="highlightMatch($el.dataset.lineText)">{!! htmlspecialchars($lineContent) !!} +
+ @empty + No logs yet. + @endforelse +
+
-
-
+
\ No newline at end of file diff --git a/resources/views/livewire/project/shared/get-logs.blade.php b/resources/views/livewire/project/shared/get-logs.blade.php index bc4eff557..89f6a1904 100644 --- a/resources/views/livewire/project/shared/get-logs.blade.php +++ b/resources/views/livewire/project/shared/get-logs.blade.php @@ -1,8 +1,12 @@ -
-
+
{ - const screen = document.getElementById('screen'); - const logs = document.getElementById('logs'); - if (screen.scrollTop !== logs.scrollHeight) { - screen.scrollTop = logs.scrollHeight; + const logsContainer = document.getElementById('logsContainer'); + if (logsContainer) { + this.isScrolling = true; + logsContainer.scrollTop = logsContainer.scrollHeight; + setTimeout(() => { this.isScrolling = false; }, 50); } }, 100); } else { @@ -26,14 +31,76 @@ this.intervalId = null; } }, - goTop() { - this.alwaysScroll = false; - clearInterval(this.intervalId); - const screen = document.getElementById('screen'); - screen.scrollTop = 0; + handleScroll(event) { + if (!this.alwaysScroll || this.isScrolling) return; + const el = event.target; + // Check if user scrolled away from the bottom + const distanceFromBottom = el.scrollHeight - el.scrollTop - el.clientHeight; + if (distanceFromBottom > 50) { + this.alwaysScroll = false; + clearInterval(this.intervalId); + this.intervalId = null; + } + }, + matchesSearch(line) { + if (!this.searchQuery.trim()) return true; + return line.toLowerCase().includes(this.searchQuery.toLowerCase()); + }, + decodeHtml(text) { + const doc = new DOMParser().parseFromString(text, 'text/html'); + return doc.documentElement.textContent; + }, + highlightMatch(text) { + const decoded = this.decodeHtml(text); + if (!this.searchQuery.trim()) return this.styleTimestamp(decoded); + const escaped = this.searchQuery.replace(/[.*+?^${}()|[\]\\]/g, String.fromCharCode(92) + '$&'); + const regex = new RegExp('(' + escaped + ')', 'gi'); + const highlighted = decoded.replace(regex, '$1'); + return this.styleTimestamp(highlighted); + }, + styleTimestamp(text) { + return text.replace(/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)/g, '$1'); + }, + getMatchCount() { + if (!this.searchQuery.trim()) return 0; + const logs = document.getElementById('logs'); + if (!logs) return 0; + const lines = logs.querySelectorAll('[data-log-line]'); + let count = 0; + lines.forEach(line => { + if (line.textContent.toLowerCase().includes(this.searchQuery.toLowerCase())) { + count++; + } + }); + return count; + }, + downloadLogs() { + const logs = document.getElementById('logs'); + if (!logs) return; + const visibleLines = logs.querySelectorAll('[data-log-line]:not(.hidden)'); + let content = ''; + visibleLines.forEach(line => { + const text = line.textContent.replace(/\s+/g, ' ').trim(); + if (text) { + content += text + String.fromCharCode(10); + } + }); + const blob = new Blob([content], { type: 'text/plain' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + const timestamp = new Date().toISOString().slice(0,19).replace(/[T:]/g, '-'); + a.download = this.containerName + '-logs-' + timestamp + '.txt'; + a.click(); + URL.revokeObjectURL(url); } - }"> -
+ }" x-init="if (expanded) { $wire.getLogs(); }"> +
+ + + @if ($displayName)

{{ $displayName }}

@elseif ($resource?->type() === 'application' || str($resource?->type())->startsWith('standalone')) @@ -48,26 +115,90 @@ @endif
-
-
- -
-
- Refresh - - -
-
-
-
-
-
- +
+ + + + + + -
- @if ($outputs) -
- @foreach (explode("\n", $outputs) as $line) - @php - // Skip empty lines - if (trim($line) === '') { - continue; - } +
+ @if ($outputs) +
+
+ No matches found. +
+ @foreach (explode("\n", $outputs) as $line) + @php + // Skip empty lines + if (trim($line) === '') { + continue; + } - // Style timestamps by replacing them inline - $styledLine = preg_replace( + // Escape HTML for safety + $escapedLine = htmlspecialchars($line); + @endphp +
+ {!! preg_replace( '/(\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+Z)/', '$1', - htmlspecialchars($line), - ); - @endphp -
- {!! $styledLine !!} -
- @endforeach -
- @else -
Refresh to get the logs...
- @endif + $escapedLine, + ) !!} +
+ @endforeach +
+ @else +
Refresh to get the logs...
+ @endif +
-
+
\ No newline at end of file diff --git a/resources/views/livewire/project/shared/logs.blade.php b/resources/views/livewire/project/shared/logs.blade.php index 87bb1a6b6..3a1afaa1c 100644 --- a/resources/views/livewire/project/shared/logs.blade.php +++ b/resources/views/livewire/project/shared/logs.blade.php @@ -17,13 +17,17 @@
@forelse ($servers as $server)
-

Server: {{ $server->name }}

+

Server: {{ $server->name }}

@if ($server->isFunctional()) @if (isset($serverContainers[$server->id]) && count($serverContainers[$server->id]) > 0) + @php + $totalContainers = collect($serverContainers)->flatten(1)->count(); + @endphp @foreach ($serverContainers[$server->id] as $container) + :resource="$resource" :container="data_get($container, 'Names')" + :expandByDefault="$totalContainers === 1" /> @endforeach @else
No containers are running on server: {{ $server->name }}
@@ -53,7 +57,8 @@ @forelse ($containers as $container) @if (data_get($servers, '0')) + :resource="$resource" :container="$container" + :expandByDefault="count($containers) === 1" /> @else
No functional server found for the database.
@endif @@ -77,7 +82,8 @@ @forelse ($containers as $container) @if (data_get($servers, '0')) + :resource="$resource" :container="$container" + :expandByDefault="count($containers) === 1" /> @else
No functional server found for the service.
@endif