plain.observer 0.0.0__py3-none-any.whl → 0.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of plain.observer might be problematic. Click here for more details.
- plain/observer/CHANGELOG.md +29 -0
- plain/observer/admin.py +0 -8
- plain/observer/cli.py +558 -5
- plain/observer/config.py +28 -20
- plain/observer/migrations/0002_trace_share_created_at_trace_share_id_trace_summary_and_more.py +58 -0
- plain/observer/models.py +56 -27
- plain/observer/otel.py +63 -2
- plain/observer/templates/observer/{_trace_detail.html → trace.html} +155 -98
- plain/observer/templates/observer/trace_detail.html +24 -0
- plain/observer/templates/observer/trace_share.html +19 -0
- plain/observer/templates/observer/traces.html +161 -135
- plain/observer/templates/toolbar/observer_button.html +27 -43
- plain/observer/urls.py +3 -1
- plain/observer/views.py +90 -56
- {plain_observer-0.0.0.dist-info → plain_observer-0.2.0.dist-info}/METADATA +1 -1
- plain_observer-0.2.0.dist-info/RECORD +25 -0
- plain/observer/templates/admin/observer/trace_detail.html +0 -10
- plain_observer-0.0.0.dist-info/RECORD +0 -23
- {plain_observer-0.0.0.dist-info → plain_observer-0.2.0.dist-info}/WHEEL +0 -0
- {plain_observer-0.0.0.dist-info → plain_observer-0.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,70 +1,122 @@
|
|
|
1
|
-
<
|
|
2
|
-
<
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
<header class="flex items-start justify-between mb-2">
|
|
2
|
+
<h2 class="text-lg font-semibold text-white">{{ trace.root_span_name }}</h2>
|
|
3
|
+
<div class="flex items-start space-x-2">
|
|
4
|
+
{% if not is_share_view|default(False) %}
|
|
5
|
+
{% htmxfragment "share" %}
|
|
6
|
+
{% if trace.share_id %}
|
|
7
|
+
<div class="flex items-center h-8 space-x-2 bg-emerald-900/20 border border-emerald-700/50 rounded-md px-2">
|
|
8
|
+
<svg class="w-4 h-4" fill="currentColor" class="text-emerald-500 flex-shrink-0" viewBox="0 0 16 16">
|
|
9
|
+
<path d="M4.715 6.542 3.343 7.914a3 3 0 1 0 4.243 4.243l1.828-1.829A3 3 0 0 0 8.586 5.5L8 6.086a1.002 1.002 0 0 0-.154.199 2 2 0 0 1 .861 3.337L6.88 11.45a2 2 0 1 1-2.83-2.83l.793-.792a4.018 4.018 0 0 1-.128-1.287z"/>
|
|
10
|
+
<path d="M6.586 4.672A3 3 0 0 0 7.414 9.5l.775-.776a2 2 0 0 1-.896-3.346L9.12 3.55a2 2 0 1 1 2.83 2.83l-.793.792c.112.42.155.855.128 1.287l1.372-1.372a3 3 0 1 0-4.243-4.243L6.586 4.672z"/>
|
|
11
|
+
</svg>
|
|
12
|
+
<span class="text-xs text-emerald-400 font-mono block max-w-xs overflow-hidden text-ellipsis whitespace-nowrap mr-2" style="direction: rtl; text-align: left;" id="share-url-{{ trace.id }}">{{ request.get_host() }}{{ url('observer:trace_shared', trace.share_id) }}</span>
|
|
13
|
+
<button
|
|
14
|
+
onclick="copyShareUrl(this, '{{ trace.id }}')"
|
|
15
|
+
data-share-url="{{ request.build_absolute_uri(url('observer:trace_shared', trace.share_id)) }}"
|
|
16
|
+
class="h-6 w-6 flex items-center justify-center rounded-sm bg-emerald-700 text-emerald-400 hover:bg-emerald-600 cursor-pointer transition-colors flex-shrink-0"
|
|
17
|
+
title="Copy shareable URL">
|
|
18
|
+
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 16 16">
|
|
19
|
+
<path d="M4 1.5H3a2 2 0 0 0-2 2V14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V3.5a2 2 0 0 0-2-2h-1v1h1a1 1 0 0 1 1 1V14a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1V3.5a1 1 0 0 1 1-1h1v-1z"/>
|
|
20
|
+
<path d="M9.5 1a.5.5 0 0 1 .5.5v1a.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5v-1a.5.5 0 0 1 .5-.5h3zm-3-1A1.5 1.5 0 0 0 5 1.5v1A1.5 1.5 0 0 0 6.5 4h3A1.5 1.5 0 0 0 11 2.5v-1A1.5 1.5 0 0 0 9.5 0h-3z"/>
|
|
21
|
+
</svg>
|
|
22
|
+
</button>
|
|
23
|
+
<button
|
|
24
|
+
hx-delete="{{ trace.get_absolute_url() }}"
|
|
25
|
+
plain-hx-action="share"
|
|
26
|
+
class="h-6 w-6 flex items-center justify-center rounded-sm bg-white/20 text-white/70 hover:bg-red-600 hover:text-white cursor-pointer transition-colors flex-shrink-0"
|
|
27
|
+
title="Remove shareable URL">
|
|
28
|
+
<svg class="w-3.5 h-3.5" fill="currentColor" viewBox="0 0 16 16">
|
|
29
|
+
<path d="M2.146 2.854a.5.5 0 1 1 .708-.708L8 7.293l5.146-5.147a.5.5 0 0 1 .708.708L8.707 8l5.147 5.146a.5.5 0 0 1-.708.708L8 8.707l-5.146 5.147a.5.5 0 0 1-.708-.708L7.293 8 2.146 2.854Z"/>
|
|
30
|
+
</svg>
|
|
31
|
+
</button>
|
|
6
32
|
</div>
|
|
7
|
-
|
|
8
|
-
<div class="flex items-center space-x-2">
|
|
33
|
+
{% else %}
|
|
9
34
|
<button
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
<path d="
|
|
35
|
+
hx-post="{{ trace.get_absolute_url() }}"
|
|
36
|
+
plain-hx-action="share"
|
|
37
|
+
class="h-8 w-8 flex items-center justify-center rounded-sm bg-white/10 text-white/70 hover:bg-white/20 cursor-pointer transition-colors border border-transparent"
|
|
38
|
+
title="Generate shareable URL">
|
|
39
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
|
|
40
|
+
<path d="M11 2.5a2.5 2.5 0 1 1 .603 1.628l-6.718 3.12a2.5 2.5 0 0 1 0 1.504l6.718 3.12a2.5 2.5 0 1 1-.488.876l-6.718-3.12a2.5 2.5 0 1 1 0-3.256l6.718-3.12A2.5 2.5 0 0 1 11 2.5"/>
|
|
16
41
|
</svg>
|
|
17
42
|
</button>
|
|
18
|
-
{%
|
|
43
|
+
{% endif %}
|
|
44
|
+
{% endhtmxfragment %}
|
|
45
|
+
{% endif %}
|
|
46
|
+
<a
|
|
47
|
+
href="{{ trace.get_absolute_url() }}?format=json"
|
|
48
|
+
target="_blank"
|
|
49
|
+
class="h-8 w-8 flex items-center justify-center rounded-sm bg-white/10 text-white/70 hover:bg-white/20 cursor-pointer transition-colors"
|
|
50
|
+
title="View as JSON">
|
|
51
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
|
|
52
|
+
<path fill-rule="evenodd" d="M14 4.5V11h-1V4.5h-2A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v9H2V2a2 2 0 0 1 2-2h5.5zM4.151 15.29a1.2 1.2 0 0 1-.111-.449h.764a.58.58 0 0 0 .255.384q.105.073.25.114.142.041.319.041.245 0 .413-.07a.56.56 0 0 0 .255-.193.5.5 0 0 0 .084-.29.39.39 0 0 0-.152-.326q-.152-.12-.463-.193l-.618-.143a1.7 1.7 0 0 1-.539-.214 1 1 0 0 1-.352-.367 1.1 1.1 0 0 1-.123-.524q0-.366.19-.639.192-.272.528-.422.337-.15.777-.149.456 0 .779.152.326.153.5.41.18.255.2.566h-.75a.56.56 0 0 0-.12-.258.6.6 0 0 0-.246-.181.9.9 0 0 0-.37-.068q-.324 0-.512.152a.47.47 0 0 0-.185.384q0 .18.144.3a1 1 0 0 0 .404.175l.621.143q.326.075.566.211a1 1 0 0 1 .375.358q.135.222.135.56 0 .37-.188.656a1.2 1.2 0 0 1-.539.439q-.351.158-.858.158-.381 0-.665-.09a1.4 1.4 0 0 1-.478-.252 1.1 1.1 0 0 1-.29-.375m-3.104-.033a1.3 1.3 0 0 1-.082-.466h.764a.6.6 0 0 0 .074.27.5.5 0 0 0 .454.246q.285 0 .422-.164.137-.165.137-.466v-2.745h.791v2.725q0 .66-.357 1.005-.355.345-.985.345a1.6 1.6 0 0 1-.568-.094 1.15 1.15 0 0 1-.407-.266 1.1 1.1 0 0 1-.243-.39m9.091-1.585v.522q0 .384-.117.641a.86.86 0 0 1-.322.387.9.9 0 0 1-.47.126.9.9 0 0 1-.47-.126.87.87 0 0 1-.32-.387 1.55 1.55 0 0 1-.117-.641v-.522q0-.386.117-.641a.87.87 0 0 1 .32-.387.87.87 0 0 1 .47-.129q.265 0 .47.129a.86.86 0 0 1 .322.387q.117.255.117.641m.803.519v-.513q0-.565-.205-.973a1.46 1.46 0 0 0-.59-.63q-.38-.22-.916-.22-.534 0-.92.22a1.44 1.44 0 0 0-.589.628q-.205.407-.205.975v.513q0 .562.205.973.205.407.589.626.386.217.92.217.536 0 .917-.217.384-.22.589-.626.204-.41.205-.973m1.29-.935v2.675h-.746v-3.999h.662l1.752 2.66h.032v-2.66h.75v4h-.656l-1.761-2.676z"/>
|
|
53
|
+
</svg>
|
|
54
|
+
</a>
|
|
55
|
+
{% if not is_share_view|default(False) %}
|
|
19
56
|
<button
|
|
20
|
-
hx-delete="
|
|
21
|
-
|
|
22
|
-
hx-swap="morph:innerHTML"
|
|
23
|
-
hx-target="#main-content"
|
|
57
|
+
hx-delete="{{ trace.get_absolute_url() }}"
|
|
58
|
+
hx-swap="none"
|
|
24
59
|
hx-confirm="Delete this trace?"
|
|
25
|
-
class="
|
|
60
|
+
class="h-8 w-8 flex items-center justify-center rounded-sm bg-white/10 text-white/70 hover:bg-red-600 hover:text-white cursor-pointer transition-colors"
|
|
26
61
|
title="Delete this trace">
|
|
27
|
-
<svg
|
|
62
|
+
<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
|
|
28
63
|
<path d="M5.5 5.5A.5.5 0 0 1 6 6v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm2.5 0a.5.5 0 0 1 .5.5v6a.5.5 0 0 1-1 0V6a.5.5 0 0 1 .5-.5Zm3 .5a.5.5 0 0 0-1 0v6a.5.5 0 0 0 1 0V6Z"/>
|
|
29
64
|
<path d="M14.5 3a1 1 0 0 1-1 1H13v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V4h-.5a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1H6a1 1 0 0 1 1-1h2a1 1 0 0 1 1 1h3.5a1 1 0 0 1 1 1v1ZM4.118 4 4 4.059V13a1 1 0 0 0 1 1h6a1 1 0 0 0 1-1V4.059L11.882 4H4.118ZM2.5 3h11V2h-11v1Z"/>
|
|
30
65
|
</svg>
|
|
31
66
|
</button>
|
|
32
67
|
{% endif %}
|
|
33
68
|
</div>
|
|
34
|
-
</
|
|
35
|
-
<div class="
|
|
36
|
-
|
|
37
|
-
<span class="text-stone-400">Trace ID:</span> <span class="font-mono">{{ trace.trace_id }}</span>
|
|
38
|
-
</div>
|
|
39
|
-
{% if trace.request_id %}
|
|
69
|
+
</header>
|
|
70
|
+
<div class="space-y-2">
|
|
71
|
+
{% if trace.summary %}
|
|
40
72
|
<div>
|
|
41
|
-
<span class="text-
|
|
73
|
+
<span class="text-xs inline-flex items-center px-2 py-0.5 bg-white/10 text-white/80 rounded-md border border-white/10">
|
|
74
|
+
{{ trace.summary }}
|
|
75
|
+
</span>
|
|
42
76
|
</div>
|
|
43
77
|
{% endif %}
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
78
|
+
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-white/60">
|
|
79
|
+
<div>
|
|
80
|
+
<span class="text-white/50">Trace ID:</span> {{ trace.trace_id }}
|
|
81
|
+
</div>
|
|
82
|
+
<div>
|
|
83
|
+
<span class="text-white/50">Started:</span> {{ trace.start_time|localtime|strftime("%b %-d, %-I:%M %p") }}
|
|
84
|
+
</div>
|
|
85
|
+
<div>
|
|
86
|
+
<span class="text-white/50">Duration:</span> {{ "%.1f"|format(trace.duration_ms() or 0) }}ms
|
|
87
|
+
</div>
|
|
47
88
|
</div>
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
89
|
+
<div class="flex flex-wrap gap-x-4 gap-y-1 text-xs text-white/60">
|
|
90
|
+
{% if trace.request_id %}
|
|
91
|
+
<div>
|
|
92
|
+
<span class="text-white/50">Request:</span> {{ trace.request_id }}
|
|
93
|
+
</div>
|
|
94
|
+
{% endif %}
|
|
95
|
+
{% if trace.user_id %}
|
|
96
|
+
<div>
|
|
97
|
+
<span class="text-white/50">User:</span> {{ trace.user_id }}
|
|
98
|
+
</div>
|
|
99
|
+
{% endif %}
|
|
100
|
+
{% if trace.session_id %}
|
|
101
|
+
<div>
|
|
102
|
+
<span class="text-white/50">Session:</span> {{ trace.session_id }}
|
|
103
|
+
</div>
|
|
104
|
+
{% endif %}
|
|
52
105
|
</div>
|
|
53
|
-
{% endif %}
|
|
54
106
|
</div>
|
|
55
107
|
|
|
56
108
|
<!-- Spans waterfall visualization -->
|
|
57
109
|
<div class="mt-4 space-y-1 text-xs">
|
|
58
|
-
{% for span in trace.
|
|
110
|
+
{% for span in trace.spans.all().annotate_spans() %}
|
|
59
111
|
|
|
60
112
|
<!-- Calculate relative positioning for waterfall -->
|
|
61
113
|
{% set span_start_offset = ((span.start_time - trace.start_time).total_seconds() * 1000) %}
|
|
62
114
|
{% set start_percent = (span_start_offset / trace.duration_ms() * 100) if trace.duration_ms() > 0 else 0 %}
|
|
63
115
|
{% set width_percent = (span.duration_ms() / trace.duration_ms() * 100) if trace.duration_ms() > 0 else 0 %}
|
|
64
116
|
|
|
65
|
-
<div style="padding-left: {{ span.level * 1 }}rem;" class="border-l border-
|
|
66
|
-
<details id="{{ trace.id }}-span-{{ loop.index }}" class="rounded bg-white/5
|
|
67
|
-
<summary class="cursor-pointer p-2 list-none [&::-webkit-details-marker]:hidden">
|
|
117
|
+
<div style="padding-left: {{ span.level * 1 }}rem;" class="border-l border-white/20">
|
|
118
|
+
<details id="{{ trace.id }}-span-{{ loop.index }}" class="rounded bg-white/5 min-w-[600px] ml-px">
|
|
119
|
+
<summary class="cursor-pointer hover:bg-white/10 transition-colors p-2 list-none [&::-webkit-details-marker]:hidden">
|
|
68
120
|
<div class="flex items-center">
|
|
69
121
|
<div class="w-4 h-4 mr-2 flex items-center justify-center">
|
|
70
122
|
<svg class="w-3 h-3 transform transition-transform details-open:rotate-90" fill="currentColor" viewBox="0 0 20 20">
|
|
@@ -73,10 +125,10 @@
|
|
|
73
125
|
</div>
|
|
74
126
|
|
|
75
127
|
<div class="w-80 flex items-center space-x-2">
|
|
76
|
-
<div class="text-
|
|
128
|
+
<div class="text-white/40 whitespace-nowrap">
|
|
77
129
|
{{ span.start_time|localtime|strftime("%-I:%M:%S %p") }}
|
|
78
130
|
</div>
|
|
79
|
-
<div class="flex-grow whitespace-nowrap truncate">{{ span.name }}</div>
|
|
131
|
+
<div class="flex-grow whitespace-nowrap truncate text-white/90">{{ span.name }}</div>
|
|
80
132
|
|
|
81
133
|
{% if span.annotations %}
|
|
82
134
|
<div class="flex items-center space-x-1 flex-shrink-0">
|
|
@@ -98,7 +150,7 @@
|
|
|
98
150
|
</div>
|
|
99
151
|
|
|
100
152
|
<div class="flex-1 px-4 min-w-[300px]">
|
|
101
|
-
<div class="relative h-6 bg-
|
|
153
|
+
<div class="relative h-6 bg-white/2 rounded-sm">
|
|
102
154
|
<div
|
|
103
155
|
class="absolute top-1 bottom-1 rounded-sm transition-opacity hover:opacity-80
|
|
104
156
|
data-[kind='SERVER']:bg-blue-500
|
|
@@ -106,7 +158,7 @@
|
|
|
106
158
|
data-[kind='CONSUMER']:bg-amber-500
|
|
107
159
|
data-[kind='PRODUCER']:bg-purple-500
|
|
108
160
|
data-[kind='INTERNAL']:bg-gray-500
|
|
109
|
-
bg-
|
|
161
|
+
bg-white/30"
|
|
110
162
|
data-kind="{{ span.kind }}"
|
|
111
163
|
style="left: {{ start_percent }}%; width: {{ width_percent }}%;"
|
|
112
164
|
title="{{ span.name }} - {{ span.duration_ms() }}ms">
|
|
@@ -120,12 +172,12 @@
|
|
|
120
172
|
</div>
|
|
121
173
|
</div>
|
|
122
174
|
</summary>
|
|
123
|
-
<div class="p-4 pt-2 bg-
|
|
175
|
+
<div class="p-4 pt-2 bg-white/3 border-t border-white/20">
|
|
124
176
|
{% if span.sql_query %}
|
|
125
|
-
<div class="mb-6 bg-
|
|
177
|
+
<div class="mb-6 bg-white/3 rounded-lg border border-white/20 overflow-hidden
|
|
126
178
|
{% if span.annotations %}ring-2 ring-amber-500/50{% endif %}">
|
|
127
|
-
<div class="bg-
|
|
128
|
-
<h4 class="text-sm font-semibold text-emerald-
|
|
179
|
+
<div class="bg-white/10 px-4 py-2 border-b border-white/20 flex items-center justify-between">
|
|
180
|
+
<h4 class="text-sm font-semibold text-emerald-400 flex items-center">
|
|
129
181
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-database-fill w-4 h-4 mr-2" viewBox="0 0 16 16">
|
|
130
182
|
<path d="M3.904 1.777C4.978 1.289 6.427 1 8 1s3.022.289 4.096.777C13.125 2.245 14 2.993 14 4s-.875 1.755-1.904 2.223C11.022 6.711 9.573 7 8 7s-3.022-.289-4.096-.777C2.875 5.755 2 5.007 2 4s.875-1.755 1.904-2.223"/>
|
|
131
183
|
<path d="M2 6.161V7c0 1.007.875 1.755 1.904 2.223C4.978 9.71 6.427 10 8 10s3.022-.289 4.096-.777C13.125 8.755 14 8.007 14 7v-.839c-.457.432-1.004.751-1.49.972C11.278 7.693 9.682 8 8 8s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
|
|
@@ -152,16 +204,16 @@
|
|
|
152
204
|
{% endif %}
|
|
153
205
|
</div>
|
|
154
206
|
<div class="p-4">
|
|
155
|
-
<pre class="text-xs text-
|
|
207
|
+
<pre class="text-xs text-white/80 font-mono whitespace-pre-wrap overflow-x-auto"><code>{{ span.get_formatted_sql() }}</code></pre>
|
|
156
208
|
|
|
157
209
|
{% if span.sql_query_params %}
|
|
158
|
-
<div class="mt-4 pt-4 border-t border-
|
|
159
|
-
<h5 class="text-xs font-semibold text-
|
|
210
|
+
<div class="mt-4 pt-4 border-t border-white/20">
|
|
211
|
+
<h5 class="text-xs font-semibold text-white/40 mb-2">Query Parameters</h5>
|
|
160
212
|
<div class="space-y-1">
|
|
161
213
|
{% for param_key, param_value in span.sql_query_params.items() %}
|
|
162
214
|
<div class="flex text-xs">
|
|
163
|
-
<span class="text-
|
|
164
|
-
<span class="text-
|
|
215
|
+
<span class="text-white/50 min-w-0 flex-shrink-0 pr-2 font-mono">{{ param_key }}:</span>
|
|
216
|
+
<span class="text-white/70 break-words font-mono">{{ param_value }}</span>
|
|
165
217
|
</div>
|
|
166
218
|
{% endfor %}
|
|
167
219
|
</div>
|
|
@@ -189,18 +241,18 @@
|
|
|
189
241
|
|
|
190
242
|
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
191
243
|
<div>
|
|
192
|
-
<h4 class="text-sm font-semibold text-
|
|
244
|
+
<h4 class="text-sm font-semibold text-white/70 mb-3">Basic Information</h4>
|
|
193
245
|
<div class="space-y-2 text-xs">
|
|
194
246
|
<div class="flex">
|
|
195
|
-
<span class="text-
|
|
196
|
-
<span class="text-
|
|
247
|
+
<span class="text-white/40 w-20">ID:</span>
|
|
248
|
+
<span class="text-white/80 font-mono">{{ span.span_id }}</span>
|
|
197
249
|
</div>
|
|
198
250
|
<div class="flex">
|
|
199
|
-
<span class="text-
|
|
200
|
-
<span class="text-
|
|
251
|
+
<span class="text-white/40 w-20">Name:</span>
|
|
252
|
+
<span class="text-white/80">{{ span.name }}</span>
|
|
201
253
|
</div>
|
|
202
254
|
<div class="flex">
|
|
203
|
-
<span class="text-
|
|
255
|
+
<span class="text-white/40 w-20">Kind:</span>
|
|
204
256
|
<span class="px-2 py-0.5 rounded text-xs font-medium
|
|
205
257
|
data-[kind='SERVER']:bg-blue-500/20 data-[kind='SERVER']:text-blue-300
|
|
206
258
|
data-[kind='CLIENT']:bg-emerald-500/20 data-[kind='CLIENT']:text-emerald-300
|
|
@@ -213,32 +265,32 @@
|
|
|
213
265
|
</span>
|
|
214
266
|
</div>
|
|
215
267
|
<div class="flex">
|
|
216
|
-
<span class="text-
|
|
217
|
-
<span class="text-
|
|
268
|
+
<span class="text-white/40 w-20">Duration:</span>
|
|
269
|
+
<span class="text-white/80">{{ "%.2f"|format(span.duration_ms() or 0) }}ms</span>
|
|
218
270
|
</div>
|
|
219
271
|
{% if span.parent_id %}
|
|
220
272
|
<div class="flex">
|
|
221
|
-
<span class="text-
|
|
222
|
-
<span class="text-
|
|
273
|
+
<span class="text-white/40 w-20">Parent:</span>
|
|
274
|
+
<span class="text-white/80 font-mono text-xs">{{ span.parent_id }}</span>
|
|
223
275
|
</div>
|
|
224
276
|
{% endif %}
|
|
225
277
|
</div>
|
|
226
278
|
</div>
|
|
227
279
|
|
|
228
280
|
<div>
|
|
229
|
-
<h4 class="text-sm font-semibold text-
|
|
281
|
+
<h4 class="text-sm font-semibold text-white/70 mb-3">Timing</h4>
|
|
230
282
|
<div class="space-y-2 text-xs">
|
|
231
283
|
<div class="flex">
|
|
232
|
-
<span class="text-
|
|
233
|
-
<span class="text-
|
|
284
|
+
<span class="text-white/40 w-20">Started:</span>
|
|
285
|
+
<span class="text-white/80">{{ span.start_time|localtime|strftime("%-I:%M:%S.%f %p") }}</span>
|
|
234
286
|
</div>
|
|
235
287
|
<div class="flex">
|
|
236
|
-
<span class="text-
|
|
237
|
-
<span class="text-
|
|
288
|
+
<span class="text-white/40 w-20">Ended:</span>
|
|
289
|
+
<span class="text-white/80">{{ span.end_time|localtime|strftime("%-I:%M:%S.%f %p") }}</span>
|
|
238
290
|
</div>
|
|
239
291
|
{% if span.status and span.status != '' and span.status != 'UNSET' %}
|
|
240
292
|
<div class="flex">
|
|
241
|
-
<span class="text-
|
|
293
|
+
<span class="text-white/40 w-20">Status:</span>
|
|
242
294
|
<span class="px-2 py-0.5 rounded text-xs font-medium
|
|
243
295
|
data-[status='ERROR']:bg-red-500/20 data-[status='ERROR']:text-red-300
|
|
244
296
|
data-[status='OK']:bg-green-500/20 data-[status='OK']:text-green-300
|
|
@@ -254,13 +306,13 @@
|
|
|
254
306
|
|
|
255
307
|
{% if span.attributes %}
|
|
256
308
|
<div class="mt-6">
|
|
257
|
-
<h4 class="text-sm font-semibold text-
|
|
258
|
-
<div class="bg-
|
|
309
|
+
<h4 class="text-sm font-semibold text-white/70 mb-3">Attributes</h4>
|
|
310
|
+
<div class="bg-white/3 rounded p-3 max-h-48 overflow-y-auto">
|
|
259
311
|
<div class="space-y-1 text-xs">
|
|
260
312
|
{% for key, value in span.attributes.items() %}
|
|
261
313
|
<div class="flex">
|
|
262
|
-
<span class="text-
|
|
263
|
-
<span class="text-
|
|
314
|
+
<span class="text-white/40 min-w-0 flex-shrink-0 pr-2">{{ key }}:</span>
|
|
315
|
+
<span class="text-white/80 break-words">{{ value }}</span>
|
|
264
316
|
</div>
|
|
265
317
|
{% endfor %}
|
|
266
318
|
</div>
|
|
@@ -270,14 +322,14 @@
|
|
|
270
322
|
|
|
271
323
|
{% if span.events %}
|
|
272
324
|
<div class="mt-6">
|
|
273
|
-
<h4 class="text-sm font-semibold text-
|
|
274
|
-
<div class="bg-
|
|
325
|
+
<h4 class="text-sm font-semibold text-white/70 mb-3">Events ({{ span.events|length }})</h4>
|
|
326
|
+
<div class="bg-white/3 rounded p-3 max-h-48 overflow-y-auto">
|
|
275
327
|
<div class="space-y-3 text-xs">
|
|
276
328
|
{% for event in span.events %}
|
|
277
|
-
<div class="border-l-2 border-
|
|
329
|
+
<div class="border-l-2 border-white/20 pl-3">
|
|
278
330
|
<div class="flex items-center justify-between mb-1">
|
|
279
|
-
<div class="text-
|
|
280
|
-
<div class="text-
|
|
331
|
+
<div class="text-white/80 font-medium">{{ event.name }}</div>
|
|
332
|
+
<div class="text-white/40 text-xs">
|
|
281
333
|
{% set formatted_time = span.format_event_timestamp(event.timestamp) %}
|
|
282
334
|
{% if formatted_time.__class__.__name__ == 'datetime' %}
|
|
283
335
|
{{ formatted_time|localtime|strftime("%-I:%M:%S.%f %p") }}
|
|
@@ -290,8 +342,8 @@
|
|
|
290
342
|
<div class="space-y-1">
|
|
291
343
|
{% for key, value in event.attributes.items() %}
|
|
292
344
|
<div class="flex">
|
|
293
|
-
<span class="text-
|
|
294
|
-
<pre class="text-
|
|
345
|
+
<span class="text-white/40 min-w-0 flex-shrink-0 pr-2">{{ key }}:</span>
|
|
346
|
+
<pre class="text-white/80 whitespace-pre-wrap break-words font-mono text-xs">{{ value }}</pre>
|
|
295
347
|
</div>
|
|
296
348
|
{% endfor %}
|
|
297
349
|
</div>
|
|
@@ -305,13 +357,13 @@
|
|
|
305
357
|
|
|
306
358
|
{% if span.links %}
|
|
307
359
|
<div class="mt-6">
|
|
308
|
-
<h4 class="text-sm font-semibold text-
|
|
360
|
+
<h4 class="text-sm font-semibold text-white/70 mb-3">Links ({{ span.links|length }})</h4>
|
|
309
361
|
<div class="bg-stone-800/50 rounded p-3">
|
|
310
362
|
<div class="space-y-2 text-xs">
|
|
311
363
|
{% for link in span.links %}
|
|
312
|
-
<div class="border-l-2 border-
|
|
313
|
-
<div class="text-
|
|
314
|
-
<div class="text-
|
|
364
|
+
<div class="border-l-2 border-white/20 pl-2">
|
|
365
|
+
<div class="text-white/80 font-mono">{{ link.context.trace_id }}</div>
|
|
366
|
+
<div class="text-white/40 font-mono">{{ link.context.span_id }}</div>
|
|
315
367
|
</div>
|
|
316
368
|
{% endfor %}
|
|
317
369
|
</div>
|
|
@@ -334,31 +386,36 @@
|
|
|
334
386
|
</style>
|
|
335
387
|
|
|
336
388
|
<script>
|
|
337
|
-
async function
|
|
389
|
+
async function copyShareUrl(button, traceId) {
|
|
338
390
|
try {
|
|
339
|
-
const
|
|
340
|
-
const data = await response.json();
|
|
341
|
-
|
|
342
|
-
// Pretty print the JSON
|
|
343
|
-
const jsonString = JSON.stringify(data, null, 2);
|
|
391
|
+
const shareUrl = button.getAttribute('data-share-url');
|
|
344
392
|
|
|
345
393
|
// Copy to clipboard
|
|
346
|
-
await navigator.clipboard.writeText(
|
|
394
|
+
await navigator.clipboard.writeText(shareUrl);
|
|
347
395
|
|
|
348
|
-
// Show success feedback
|
|
349
|
-
const
|
|
350
|
-
button.
|
|
351
|
-
button.classList.remove('bg-
|
|
396
|
+
// Show success feedback on button
|
|
397
|
+
const originalHTML = button.innerHTML;
|
|
398
|
+
button.innerHTML = '<svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16"><path d="M13.854 3.646a.5.5 0 0 1 0 .708l-7 7a.5.5 0 0 1-.708 0l-3.5-3.5a.5.5 0 1 1 .708-.708L6.5 10.293l6.646-6.647a.5.5 0 0 1 .708 0z"/></svg>';
|
|
399
|
+
button.classList.remove('bg-emerald-700', 'hover:bg-emerald-600');
|
|
352
400
|
button.classList.add('bg-green-600', 'hover:bg-green-700');
|
|
353
401
|
|
|
402
|
+
// Also flash the URL text
|
|
403
|
+
const urlSpan = document.getElementById(`share-url-${traceId}`);
|
|
404
|
+
if (urlSpan) {
|
|
405
|
+
urlSpan.classList.add('text-green-400', 'font-bold');
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
urlSpan.classList.remove('text-green-400', 'font-bold');
|
|
408
|
+
}, 2000);
|
|
409
|
+
}
|
|
410
|
+
|
|
354
411
|
setTimeout(() => {
|
|
355
|
-
button.
|
|
412
|
+
button.innerHTML = originalHTML;
|
|
356
413
|
button.classList.remove('bg-green-600', 'hover:bg-green-700');
|
|
357
|
-
button.classList.add('bg-
|
|
414
|
+
button.classList.add('bg-emerald-700', 'hover:bg-emerald-600');
|
|
358
415
|
}, 2000);
|
|
359
416
|
} catch (error) {
|
|
360
|
-
console.error('Failed to copy
|
|
361
|
-
alert('Failed to copy
|
|
417
|
+
console.error('Failed to copy share URL:', error);
|
|
418
|
+
alert('Failed to copy share URL. See console for details.');
|
|
362
419
|
}
|
|
363
420
|
}
|
|
364
421
|
</script>
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Trace {{ trace.trace_id }} - Observer</title>
|
|
7
|
+
{% tailwind_css %}
|
|
8
|
+
{% htmx_js %}
|
|
9
|
+
</head>
|
|
10
|
+
<body class="bg-stone-950 text-stone-300 min-h-screen">
|
|
11
|
+
<div class="container mx-auto p-6 max-w-6xl">
|
|
12
|
+
<div class="mb-4">
|
|
13
|
+
<a href="{{ url('observer:traces') }}" class="text-stone-400 hover:text-stone-200 text-sm flex items-center gap-1">
|
|
14
|
+
<svg width="16" height="16" fill="currentColor" viewBox="0 0 16 16">
|
|
15
|
+
<path fill-rule="evenodd" d="M12 8a.5.5 0 0 1-.5.5H5.707l2.147 2.146a.5.5 0 0 1-.708.708l-3-3a.5.5 0 0 1 0-.708l3-3a.5.5 0 1 1 .708.708L5.707 7.5H11.5a.5.5 0 0 1 .5.5z"/>
|
|
16
|
+
</svg>
|
|
17
|
+
Back to traces
|
|
18
|
+
</a>
|
|
19
|
+
</div>
|
|
20
|
+
|
|
21
|
+
{% include "observer/trace.html" %}
|
|
22
|
+
</div>
|
|
23
|
+
</body>
|
|
24
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Shared Trace - {{ trace.trace_id }} - Observer</title>
|
|
7
|
+
{% tailwind_css %}
|
|
8
|
+
{% htmx_js %}
|
|
9
|
+
</head>
|
|
10
|
+
<body class="bg-stone-950 text-stone-300 min-h-screen">
|
|
11
|
+
<div class="container mx-auto p-6 max-w-6xl">
|
|
12
|
+
<div class="mb-4 text-sm text-stone-400">
|
|
13
|
+
Shared trace
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
{% include "observer/trace.html" %}
|
|
17
|
+
</div>
|
|
18
|
+
</body>
|
|
19
|
+
</html>
|