Files
trackbugs/resources/views/issues/show.blade.php
T
2026-03-21 15:25:05 +02:00

357 lines
23 KiB
PHP

<x-app-layout>
@php($currentProject = $project)
<x-slot name="header">
<div class="flex items-center justify-between">
<div class="min-w-0 flex-1">
<div class="flex items-center gap-2 mb-1">
<a href="{{ route('projects.issues.index', $project) }}" class="text-sm text-gray-500 hover:text-gray-700">&larr; Issues</a>
</div>
<h2 class="font-semibold text-xl text-gray-800 leading-tight truncate">{{ $issue->title }}</h2>
@if($issue->culprit)
<p class="text-sm text-gray-500 mt-0.5">{{ $issue->culprit }}</p>
@endif
</div>
<div class="flex items-center gap-2 shrink-0 ml-4">
<form method="POST" action="{{ route('projects.issues.status', [$project, $issue]) }}">
@csrf
@method('PATCH')
@if($issue->status === 'unresolved')
<button name="action" value="resolve" class="px-3 py-1.5 text-sm font-medium rounded-md bg-green-100 text-green-800 hover:bg-green-200 transition">Resolve</button>
<button name="action" value="ignore" class="px-3 py-1.5 text-sm font-medium rounded-md bg-yellow-100 text-yellow-800 hover:bg-yellow-200 transition">Ignore</button>
@elseif($issue->status === 'resolved')
<button name="action" value="unresolve" class="px-3 py-1.5 text-sm font-medium rounded-md bg-red-100 text-red-800 hover:bg-red-200 transition">Unresolve</button>
@else
<button name="action" value="unresolve" class="px-3 py-1.5 text-sm font-medium rounded-md bg-red-100 text-red-800 hover:bg-red-200 transition">Unresolve</button>
<button name="action" value="resolve" class="px-3 py-1.5 text-sm font-medium rounded-md bg-green-100 text-green-800 hover:bg-green-200 transition">Resolve</button>
@endif
</form>
</div>
</div>
</x-slot>
<div class="py-8">
<div class="max-w-7xl mx-auto sm:px-6 lg:px-8 space-y-6">
@if(session('status'))
<div class="bg-green-50 border border-green-200 text-green-700 px-4 py-3 rounded-md text-sm">
{{ session('status') }}
</div>
@endif
{{-- Environment Filter --}}
@if($environments->isNotEmpty())
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-4">
<form method="GET" action="{{ route('projects.issues.show', [$project, $issue]) }}" class="flex items-end gap-4">
<div>
<x-input-label for="environment" value="Filter by Environment" />
<select id="environment" name="environment" class="mt-1 block w-full border-gray-300 focus:border-indigo-500 focus:ring-indigo-500 rounded-md shadow-sm text-sm" onchange="this.form.submit()">
<option value="">All Environments</option>
@foreach($environments as $env)
<option value="{{ $env }}" {{ $currentEnvironment === $env ? 'selected' : '' }}>{{ ucfirst($env) }}</option>
@endforeach
</select>
</div>
@if($currentEnvironment)
<a href="{{ route('projects.issues.show', [$project, $issue]) }}" class="text-sm text-gray-500 hover:text-gray-700 pb-2">Reset</a>
@endif
</form>
</div>
@endif
{{-- Issue Meta --}}
@php
$statusColors = [
'unresolved' => 'bg-red-100 text-red-800',
'resolved' => 'bg-green-100 text-green-800',
'ignored' => 'bg-yellow-100 text-yellow-800',
'muted' => 'bg-gray-100 text-gray-600',
];
$levelColors = [
'fatal' => 'bg-red-100 text-red-800',
'error' => 'bg-red-50 text-red-700',
'warning' => 'bg-yellow-50 text-yellow-700',
'info' => 'bg-blue-50 text-blue-700',
'debug' => 'bg-gray-100 text-gray-600',
];
@endphp
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<div class="flex flex-wrap gap-6 text-sm">
<div>
<span class="text-gray-500">Status</span>
<div class="mt-1">
<span class="inline-flex px-2 py-0.5 rounded-full text-xs font-medium {{ $statusColors[$issue->status] ?? '' }}">
{{ ucfirst($issue->status) }}
@if($issue->is_regression)
(regression)
@endif
</span>
</div>
</div>
<div>
<span class="text-gray-500">Level</span>
<div class="mt-1">
<span class="inline-flex px-2 py-0.5 rounded-full text-xs font-medium {{ $levelColors[$issue->level] ?? '' }}">
{{ ucfirst($issue->level) }}
</span>
</div>
</div>
<div>
<span class="text-gray-500">Events</span>
<div class="mt-1 font-mono font-medium text-gray-900">{{ number_format($issue->times_seen) }}</div>
</div>
<div>
<span class="text-gray-500">First Seen</span>
<div class="mt-1 text-gray-900" title="{{ $issue->first_seen_at }}">{{ $issue->first_seen_at->diffForHumans() }}</div>
</div>
<div>
<span class="text-gray-500">Last Seen</span>
<div class="mt-1 text-gray-900" title="{{ $issue->last_seen_at }}">{{ $issue->last_seen_at->diffForHumans() }}</div>
</div>
@if($issue->first_release)
<div>
<span class="text-gray-500">First Release</span>
<div class="mt-1 font-mono text-sm text-gray-900">{{ $issue->first_release }}</div>
</div>
@endif
@if($issue->resolved_in_release)
<div>
<span class="text-gray-500">Resolved In</span>
<div class="mt-1 font-mono text-sm text-green-700">{{ $issue->resolved_in_release }}</div>
</div>
@endif
</div>
</div>
@if($latestEvent)
{{-- Stacktrace --}}
@if(!empty($latestEvent->exception['values']))
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Stacktrace</h3>
</div>
@foreach($latestEvent->exception['values'] as $exc)
<div class="border-b border-gray-100 last:border-b-0">
<div class="px-6 py-3 bg-gray-50">
<span class="font-mono text-sm font-semibold text-red-700">{{ $exc['type'] ?? 'Exception' }}</span>
@if(!empty($exc['value']))
<span class="text-sm text-gray-600 ml-2">{{ $exc['value'] }}</span>
@endif
</div>
@if(!empty($exc['stacktrace']['frames']))
<div x-data="{ expandedFrames: {} }" class="divide-y divide-gray-100">
@foreach(array_reverse($exc['stacktrace']['frames']) as $i => $frame)
@php
$file = $frame['filename'] ?? $frame['abs_path'] ?? '?';
$fn = $frame['function'] ?? null;
$lineno = $frame['lineno'] ?? null;
$inApp = $frame['in_app'] ?? false;
$context = $frame['context_line'] ?? null;
$preContext = $frame['pre_context'] ?? [];
$postContext = $frame['post_context'] ?? [];
@endphp
<div class="{{ $inApp ? 'bg-white' : 'bg-gray-50/50' }}">
<button @click="expandedFrames[{{ $i }}] = !expandedFrames[{{ $i }}]"
class="w-full px-6 py-2 text-left text-sm flex items-center gap-2 hover:bg-gray-50 transition">
<svg :class="expandedFrames[{{ $i }}] ? 'rotate-90' : ''" class="w-3 h-3 text-gray-400 transition-transform shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path d="M6 4l8 6-8 6V4z"/>
</svg>
<span class="font-mono {{ $inApp ? 'text-gray-900 font-medium' : 'text-gray-500' }} truncate">
{{ $file }}@if($lineno):{{ $lineno }}@endif
</span>
@if($fn)
<span class="text-gray-400 shrink-0">in</span>
<span class="font-mono {{ $inApp ? 'text-indigo-700' : 'text-gray-500' }} truncate">{{ $fn }}</span>
@endif
@if($inApp)
<span class="ml-auto shrink-0 inline-flex px-1.5 py-0.5 rounded text-[10px] font-medium bg-indigo-50 text-indigo-600">app</span>
@endif
</button>
<div x-show="expandedFrames[{{ $i }}]" x-cloak class="px-6 pb-3">
@if($context || !empty($preContext) || !empty($postContext))
<pre class="bg-gray-900 rounded-md p-3 text-xs text-gray-300 overflow-x-auto"><code>@foreach($preContext as $ci => $line)
<span class="text-gray-600">{{ ($lineno ?? 0) - count($preContext) + $ci }} {{ $line }}</span>
@endforeach
@if($context)<span class="text-white bg-red-900/40 block -mx-3 px-3">{{ $lineno }} {{ $context }}</span>@endif
@foreach($postContext as $ci => $line)
<span class="text-gray-600">{{ ($lineno ?? 0) + $ci + 1 }} {{ $line }}</span>
@endforeach</code></pre>
@elseif(!empty($frame['vars']))
<pre class="bg-gray-900 rounded-md p-3 text-xs text-gray-300 overflow-x-auto"><code>{{ json_encode($frame['vars'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}</code></pre>
@else
<p class="text-xs text-gray-400 italic">No source context available.</p>
@endif
</div>
</div>
@endforeach
</div>
@endif
</div>
@endforeach
</div>
@endif
{{-- Breadcrumbs --}}
@if(!empty($latestEvent->breadcrumbs['values'] ?? $latestEvent->breadcrumbs))
@php
$crumbs = $latestEvent->breadcrumbs['values'] ?? $latestEvent->breadcrumbs;
if (!is_array($crumbs)) $crumbs = [];
@endphp
@if(count($crumbs) > 0)
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Breadcrumbs</h3>
</div>
<div class="divide-y divide-gray-100 max-h-96 overflow-y-auto">
@foreach($crumbs as $crumb)
<div class="px-6 py-2 flex items-start gap-4 text-sm">
<span class="shrink-0 text-xs text-gray-400 font-mono w-20 pt-0.5">
{{ isset($crumb['timestamp']) ? \Carbon\Carbon::parse($crumb['timestamp'])->format('H:i:s') : '' }}
</span>
@php
$catColor = match($crumb['type'] ?? $crumb['category'] ?? '') {
'error' => 'text-red-600',
'warning' => 'text-yellow-600',
'http', 'navigation' => 'text-blue-600',
default => 'text-gray-600',
};
@endphp
<span class="shrink-0 w-20 text-xs font-medium {{ $catColor }} truncate">{{ $crumb['category'] ?? $crumb['type'] ?? 'default' }}</span>
<span class="text-gray-800 flex-1 truncate">{{ $crumb['message'] ?? json_encode($crumb['data'] ?? '') }}</span>
</div>
@endforeach
</div>
</div>
@endif
@endif
{{-- Tags --}}
@if(!empty($latestEvent->tags))
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Tags</h3>
<div class="flex flex-wrap gap-2">
@foreach($latestEvent->tags as $key => $value)
<a href="{{ route('projects.issues.index', [$project, 'search' => $key . ':' . $value]) }}"
class="inline-flex items-center gap-1 px-2.5 py-1 rounded-md bg-gray-100 hover:bg-gray-200 text-sm transition">
<span class="font-medium text-gray-700">{{ $key }}</span>
<span class="text-gray-400">=</span>
<span class="text-gray-600">{{ $value }}</span>
</a>
@endforeach
</div>
</div>
@endif
{{-- Context Panels --}}
@php
$contexts = $latestEvent->contexts ?? [];
$userCtx = $latestEvent->user_context;
$requestCtx = $latestEvent->request_context;
@endphp
@if(!empty($contexts) || $userCtx || $requestCtx)
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
@if($requestCtx)
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<h3 class="text-sm font-medium text-gray-900 mb-3">Request</h3>
<dl class="space-y-2 text-sm">
@if(!empty($requestCtx['method']))
<div class="flex gap-2"><dt class="font-medium text-gray-500 w-20">Method</dt><dd class="text-gray-900 font-mono">{{ $requestCtx['method'] }}</dd></div>
@endif
@if(!empty($requestCtx['url']))
<div class="flex gap-2"><dt class="font-medium text-gray-500 w-20">URL</dt><dd class="text-gray-900 font-mono truncate">{{ $requestCtx['url'] }}</dd></div>
@endif
@if(!empty($requestCtx['headers']))
<div>
<dt class="font-medium text-gray-500 mb-1">Headers</dt>
<dd><pre class="bg-gray-50 rounded p-2 text-xs overflow-x-auto">{{ json_encode($requestCtx['headers'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}</pre></dd>
</div>
@endif
</dl>
</div>
@endif
@if($userCtx)
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<h3 class="text-sm font-medium text-gray-900 mb-3">User</h3>
<dl class="space-y-2 text-sm">
@foreach($userCtx as $k => $v)
<div class="flex gap-2">
<dt class="font-medium text-gray-500 w-20">{{ ucfirst($k) }}</dt>
<dd class="text-gray-900">{{ is_string($v) ? $v : json_encode($v) }}</dd>
</div>
@endforeach
</dl>
</div>
@endif
@foreach($contexts as $ctxName => $ctxData)
@if(is_array($ctxData))
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<h3 class="text-sm font-medium text-gray-900 mb-3">{{ ucfirst(str_replace('_', ' ', $ctxName)) }}</h3>
<dl class="space-y-2 text-sm">
@foreach($ctxData as $k => $v)
@if($k !== 'type')
<div class="flex gap-2">
<dt class="font-medium text-gray-500 w-28 shrink-0">{{ ucfirst(str_replace('_', ' ', $k)) }}</dt>
<dd class="text-gray-900 truncate">{{ is_string($v) || is_numeric($v) ? $v : json_encode($v) }}</dd>
</div>
@endif
@endforeach
</dl>
</div>
@endif
@endforeach
</div>
@endif
{{-- Extra Data --}}
@if(!empty($latestEvent->extra))
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg p-6">
<h3 class="text-lg font-medium text-gray-900 mb-4">Extra Data</h3>
<pre class="bg-gray-50 rounded-md p-4 text-sm text-gray-800 overflow-x-auto">{{ json_encode($latestEvent->extra, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) }}</pre>
</div>
@endif
@endif
{{-- Events List --}}
<div class="bg-white overflow-hidden shadow-sm sm:rounded-lg">
<div class="p-6 border-b border-gray-200">
<h3 class="text-lg font-medium text-gray-900">Events</h3>
</div>
@if($events->isEmpty())
<div class="p-6 text-sm text-gray-500">No events recorded.</div>
@else
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Event ID</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Environment</th>
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase">Release</th>
<th class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase">Timestamp</th>
</tr>
</thead>
<tbody class="bg-white divide-y divide-gray-200">
@foreach($events as $ev)
<tr class="hover:bg-gray-50">
<td class="px-6 py-3">
<a href="{{ route('projects.issues.events.show', [$project, $issue, $ev]) }}" class="text-indigo-600 hover:text-indigo-900 font-mono text-sm">
{{ Str::limit($ev->event_id, 16) }}
</a>
</td>
<td class="px-6 py-3 text-sm text-gray-500">{{ $ev->environment ?? '—' }}</td>
<td class="px-6 py-3 text-sm text-gray-500 font-mono">{{ $ev->release ?? '—' }}</td>
<td class="px-6 py-3 text-sm text-gray-500 text-right" title="{{ $ev->timestamp }}">{{ $ev->timestamp->diffForHumans() }}</td>
</tr>
@endforeach
</tbody>
</table>
<div class="px-6 py-3 border-t border-gray-200">
{{ $events->links() }}
</div>
@endif
</div>
</div>
</div>
</x-app-layout>