plain.observer 0.5.0__py3-none-any.whl → 0.6.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.

@@ -1,383 +1,155 @@
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>
1
+ <header class="mb-2">
2
+ {% htmxfragment "header" %}
3
+ {% if trace.share_id and not is_share_view|default(False) %}
4
+ <div class="mb-2">
5
+ <div class="flex items-center gap-2 bg-emerald-900/20 border border-emerald-700/50 rounded px-2 py-1">
6
+ <span class="text-xs text-emerald-400 font-mono flex-1 truncate" id="share-url-{{ trace.id }}">
7
+ {{ request.get_host() }}{{ url('observer:trace_shared', trace.share_id) }}
8
+ </span>
13
9
  <button
14
10
  onclick="copyShareUrl(this, '{{ trace.id }}')"
15
11
  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"
12
+ class="px-2 py-0.5 text-xs bg-emerald-700 text-emerald-100 hover:bg-emerald-600 rounded transition-colors flex-shrink-0"
17
13
  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>
14
+ Copy
22
15
  </button>
23
16
  <button
24
17
  hx-delete="{{ trace.get_absolute_url() }}"
25
18
  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"
19
+ class="px-2 py-0.5 text-xs bg-red-600 text-red-100 hover:bg-red-700 rounded transition-colors flex-shrink-0"
27
20
  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>
21
+ Remove
31
22
  </button>
32
23
  </div>
33
- {% else %}
34
- <button
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"/>
41
- </svg>
42
- </button>
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) %}
56
- <button
57
- hx-delete="{{ trace.get_absolute_url() }}"
58
- hx-swap="none"
59
- hx-confirm="Delete this trace?"
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"
61
- title="Delete this trace">
62
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
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"/>
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"/>
65
- </svg>
66
- </button>
67
- {% endif %}
68
24
  </div>
25
+ {% endif %}
26
+ <div class="flex items-start justify-between">
27
+ <h2 class="text-lg font-semibold text-white flex-1 min-w-0">{{ trace.root_span_name }}</h2>
28
+ <div class="relative group">
29
+ <div class="flex items-center gap-2 hover:bg-white/10 rounded-lg p-1 transition-colors cursor-pointer">
30
+ <svg class="w-4 h-4 text-white/70" fill="currentColor" viewBox="0 0 16 16">
31
+ <path d="M3 9.5a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3zm5 0a1.5 1.5 0 1 1 0-3 1.5 1.5 0 0 1 0 3z"/>
32
+ </svg>
33
+ </div>
34
+ <div class="absolute right-0 mt-3 w-64 bg-stone-900 rounded-lg shadow-lg border border-stone-700 py-2 z-50 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
35
+ {% if not is_share_view|default(False) and not trace.share_id %}
36
+ <div class="py-1">
37
+ <button
38
+ hx-post="{{ trace.get_absolute_url() }}"
39
+ plain-hx-action="share"
40
+ class="cursor-pointer w-full text-left px-4 py-3 text-sm text-stone-200 hover:bg-stone-800 transition-colors flex items-center gap-3">
41
+ <svg class="w-4 h-4 text-stone-400" fill="currentColor" viewBox="0 0 16 16">
42
+ <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"/>
43
+ </svg>
44
+ Generate Share URL
45
+ </button>
46
+ </div>
47
+ {% endif %}
48
+ <div class="py-1">
49
+ <a
50
+ href="{{ trace.get_absolute_url() }}?format=json"
51
+ target="_blank"
52
+ class="cursor-pointer w-full text-left px-4 py-3 text-sm text-stone-200 hover:bg-stone-800 transition-colors flex items-center gap-3">
53
+ <svg class="w-4 h-4 text-stone-400" fill="currentColor" viewBox="0 0 16 16">
54
+ <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.56q0 .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"/>
55
+ </svg>
56
+ View as JSON
57
+ </a>
58
+ {% if not is_share_view|default(False) %}
59
+ <a
60
+ href="{{ trace.get_absolute_url() }}?logs=true"
61
+ target="_blank"
62
+ class="cursor-pointer w-full text-left px-4 py-3 text-sm text-stone-200 hover:bg-stone-800 transition-colors flex items-center gap-3">
63
+ <svg class="w-4 h-4 text-stone-400" fill="currentColor" viewBox="0 0 16 16">
64
+ <path d="M1 2.828c.885-.37 2.154-.769 3.388-.893 1.33-.134 2.458.063 3.112.752v9.746c-.935-.53-2.12-.603-3.213-.493-1.18.12-2.37.461-3.287.811V2.828zm7.5-.141c.654-.689 1.782-.886 3.112-.752 1.234.124 2.503.523 3.388.893v9.923c-.918-.35-2.107-.692-3.287-.81-1.094-.111-2.278-.039-3.213.492V2.687zM8 1.783C7.015.936 5.587.81 4.287.94c-1.514.153-3.042.672-3.994 1.105A.5.5 0 0 0 0 2.5v11a.5.5 0 0 0 .707.455c.882-.4 2.303-.881 3.68-1.02 1.409-.142 2.59.087 3.223.877a.5.5 0 0 0 .78 0c.633-.79 1.814-1.019 3.222-.877 1.378.139 2.8.62 3.681 1.02A.5.5 0 0 0 16 13.5v-11a.5.5 0 0 0-.293-.455c-.952-.433-2.48-.952-3.994-1.105C10.413.809 8.985.936 8 1.783z"/>
65
+ </svg>
66
+ View Logs
67
+ </a>
68
+ <button
69
+ hx-delete="{{ trace.get_absolute_url() }}"
70
+ hx-swap="none"
71
+ hx-confirm="Delete this trace?"
72
+ class="cursor-pointer w-full text-left px-4 py-3 text-sm text-red-200 hover:bg-red-900/30 transition-colors flex items-center gap-3">
73
+ <svg class="w-4 h-4 text-red-400" fill="currentColor" viewBox="0 0 16 16">
74
+ <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"/>
75
+ <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"/>
76
+ </svg>
77
+ Delete Trace
78
+ </button>
79
+ {% endif %}
80
+ </div>
81
+ </div>
82
+ </div>
83
+ {% endhtmxfragment %}
69
84
  </header>
70
85
  <div class="space-y-2">
71
- {% if trace.summary %}
72
- <div>
86
+ <div class="flex items-center justify-between">
87
+ {% if trace.summary %}
73
88
  <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
89
  {{ trace.summary }}
75
90
  </span>
76
- </div>
77
- {% endif %}
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>
88
- </div>
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>
91
+ {% else %}
92
+ <div></div>
94
93
  {% endif %}
95
- {% if trace.user_id %}
96
- <div>
97
- <span class="text-white/50">User:</span> {{ trace.user_id }}
94
+ <div class="text-xs text-white/40">
95
+ <span class="text-white/30">Trace ID:</span> <span class="font-mono">{{ trace.trace_id }}</span>
98
96
  </div>
99
- {% endif %}
100
- {% if trace.session_id %}
101
- <div>
102
- <span class="text-white/50">Session:</span> {{ trace.session_id }}
97
+ </div>
98
+
99
+ {% if trace.request_id or trace.user_id or trace.session_id or trace.app_version %}
100
+ <div class="bg-white/5 rounded-lg border border-white/10 p-3">
101
+ <div class="flex flex-wrap gap-x-4 gap-y-2 text-xs text-white/70">
102
+ {% if trace.request_id %}
103
+ <div>
104
+ <span class="text-white/50">Request:</span> {{ trace.request_id }}
105
+ </div>
106
+ {% endif %}
107
+ {% if trace.user_id %}
108
+ <div>
109
+ <span class="text-white/50">User:</span> {{ trace.user_id }}
110
+ </div>
111
+ {% endif %}
112
+ {% if trace.session_id %}
113
+ <div>
114
+ <span class="text-white/50">Session:</span> {{ trace.session_id }}
115
+ </div>
116
+ {% endif %}
117
+ {% if trace.app_version %}
118
+ <div>
119
+ <span class="text-white/50">App Version:</span> {{ trace.app_version }}
120
+ </div>
121
+ {% endif %}
103
122
  </div>
104
- {% endif %}
105
123
  </div>
124
+ {% endif %}
106
125
  </div>
107
126
 
108
- <!-- Spans waterfall visualization -->
109
- <div class="mt-4 space-y-1 text-xs">
110
- {% for span in trace.spans.all().annotate_spans() %}
111
-
112
- <!-- Calculate relative positioning for waterfall -->
113
- {% set span_start_offset = ((span.start_time - trace.start_time).total_seconds() * 1000) %}
114
- {% set start_percent = (span_start_offset / trace.duration_ms() * 100) if trace.duration_ms() > 0 else 0 %}
115
- {% set width_percent = (span.duration_ms() / trace.duration_ms() * 100) if trace.duration_ms() > 0 else 0 %}
116
-
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">
120
- <div class="flex items-center">
121
- <div class="w-4 h-4 mr-2 flex items-center justify-center">
122
- <svg class="w-3 h-3 transform transition-transform details-open:rotate-90" fill="currentColor" viewBox="0 0 20 20">
123
- <path fill-rule="evenodd" d="M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z" clip-rule="evenodd" />
124
- </svg>
125
- </div>
126
-
127
- <div class="w-80 flex items-center space-x-2">
128
- <div class="text-white/40 whitespace-nowrap">
129
- {{ span.start_time|localtime|strftime("%-I:%M:%S %p") }}
130
- </div>
131
- <div class="flex-grow whitespace-nowrap truncate text-white/90">{{ span.name }}</div>
132
-
133
- {% if span.annotations %}
134
- <div class="flex items-center space-x-1 flex-shrink-0">
135
- {% for annotation in span.annotations %}
136
- <span class="w-4 h-4 inline-flex justify-center items-center text-xs rounded-full
137
- data-[severity='warning']:bg-amber-500/20
138
- data-[severity='warning']:text-amber-400
139
- data-[severity='error']:bg-red-500/20
140
- data-[severity='error']:text-red-400
141
- data-[severity='info']:bg-blue-500/20
142
- data-[severity='info']:text-blue-400"
143
- data-severity="{{ annotation.severity }}"
144
- title="{{ annotation.message }}">
145
- !
146
- </span>
147
- {% endfor %}
148
- </div>
149
- {% endif %}
150
- </div>
151
-
152
- <div class="flex-1 px-4 min-w-[300px]">
153
- <div class="relative h-6 bg-white/2 rounded-sm">
154
- <div
155
- class="absolute top-1 bottom-1 rounded-sm transition-opacity hover:opacity-80
156
- data-[kind='SERVER']:bg-blue-500
157
- data-[kind='CLIENT']:bg-emerald-500
158
- data-[kind='CONSUMER']:bg-amber-500
159
- data-[kind='PRODUCER']:bg-purple-500
160
- data-[kind='INTERNAL']:bg-gray-500
161
- bg-white/30"
162
- data-kind="{{ span.kind }}"
163
- style="left: {{ start_percent }}%; width: {{ width_percent }}%;"
164
- title="{{ span.name }} - {{ span.duration_ms() }}ms">
165
- </div>
166
- <div
167
- class="absolute inset-0 flex items-center justify-start pl-1 text-xs text-white/80 font-medium whitespace-nowrap pointer-events-none"
168
- style="left: {{ start_percent }}%; width: {{ width_percent }}%;">
169
- {{ "%.2f"|format(span.duration_ms()) }}ms
170
- </div>
171
- </div>
172
- </div>
173
- </div>
174
- </summary>
175
- <div class="p-4 pt-2 bg-white/3 border-t border-white/20">
176
- {% if span.sql_query %}
177
- <div class="mb-6 bg-white/3 rounded-lg border border-white/20 overflow-hidden
178
- {% if span.annotations %}ring-2 ring-amber-500/50{% endif %}">
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">
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">
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"/>
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"/>
184
- <path d="M2 9.161V10c0 1.007.875 1.755 1.904 2.223C4.978 12.711 6.427 13 8 13s3.022-.289 4.096-.777C13.125 11.755 14 11.007 14 10v-.839c-.457.432-1.004.751-1.49.972-1.232.56-2.828.867-4.51.867s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
185
- <path d="M2 12.161V13c0 1.007.875 1.755 1.904 2.223C4.978 15.711 6.427 16 8 16s3.022-.289 4.096-.777C13.125 14.755 14 14.007 14 13v-.839c-.457.432-1.004.751-1.49.972-1.232.56-2.828.867-4.51.867s-3.278-.307-4.51-.867c-.486-.22-1.033-.54-1.49-.972"/>
186
- </svg>
187
- Database Query
188
- </h4>
189
- {% if span.annotations %}
190
- <div class="flex items-center space-x-1">
191
- {% for annotation in span.annotations %}
192
- <span class="px-2 py-0.5 text-xs rounded-full
193
- data-[severity='warning']:bg-amber-500/20
194
- data-[severity='warning']:text-amber-400
195
- data-[severity='error']:bg-red-500/20
196
- data-[severity='error']:text-red-400
197
- data-[severity='info']:bg-blue-500/20
198
- data-[severity='info']:text-blue-400"
199
- data-severity="{{ annotation.severity }}">
200
- {{ annotation.message }}
201
- </span>
202
- {% endfor %}
203
- </div>
204
- {% endif %}
205
- </div>
206
- <div class="p-4">
207
- <pre class="text-xs text-white/80 font-mono whitespace-pre-wrap overflow-x-auto"><code>{{ span.get_formatted_sql() }}</code></pre>
208
-
209
- {% if span.sql_query_params %}
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>
212
- <div class="space-y-1">
213
- {% for param_key, param_value in span.sql_query_params.items() %}
214
- <div class="flex text-xs">
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>
217
- </div>
218
- {% endfor %}
219
- </div>
220
- </div>
221
- {% endif %}
222
- </div>
223
- </div>
224
- {% endif %}
225
-
226
- {% if span.get_exception_stacktrace() %}
227
- <div class="mb-6 bg-red-900/20 rounded-lg border border-red-600/30 overflow-hidden">
228
- <div class="bg-red-900/40 px-4 py-2 border-b border-red-600/30">
229
- <h4 class="text-sm font-semibold text-red-300 flex items-center">
230
- <svg class="w-4 h-4 mr-2" fill="currentColor" viewBox="0 0 20 20">
231
- <path fill-rule="evenodd" d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z" clip-rule="evenodd"/>
232
- </svg>
233
- Exception Stacktrace
234
- </h4>
235
- </div>
236
- <div class="p-4">
237
- <pre class="text-xs text-red-100 font-mono whitespace-pre-wrap overflow-x-auto"><code>{{ span.get_exception_stacktrace() }}</code></pre>
238
- </div>
239
- </div>
240
- {% endif %}
241
-
242
- <div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
243
- <div>
244
- <h4 class="text-sm font-semibold text-white/70 mb-3">Basic Information</h4>
245
- <div class="space-y-2 text-xs">
246
- <div class="flex">
247
- <span class="text-white/40 w-20">ID:</span>
248
- <span class="text-white/80 font-mono">{{ span.span_id }}</span>
249
- </div>
250
- <div class="flex">
251
- <span class="text-white/40 w-20">Name:</span>
252
- <span class="text-white/80">{{ span.name }}</span>
253
- </div>
254
- <div class="flex">
255
- <span class="text-white/40 w-20">Kind:</span>
256
- <span class="px-2 py-0.5 rounded text-xs font-medium
257
- data-[kind='SERVER']:bg-blue-500/20 data-[kind='SERVER']:text-blue-300
258
- data-[kind='CLIENT']:bg-emerald-500/20 data-[kind='CLIENT']:text-emerald-300
259
- data-[kind='CONSUMER']:bg-amber-500/20 data-[kind='CONSUMER']:text-amber-300
260
- data-[kind='PRODUCER']:bg-purple-500/20 data-[kind='PRODUCER']:text-purple-300
261
- data-[kind='INTERNAL']:bg-gray-500/20 data-[kind='INTERNAL']:text-gray-300
262
- bg-gray-500/20 text-gray-300"
263
- data-kind="{{ span.kind }}">
264
- {{ span.kind }}
265
- </span>
266
- </div>
267
- <div class="flex">
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>
270
- </div>
271
- {% if span.parent_id %}
272
- <div class="flex">
273
- <span class="text-white/40 w-20">Parent:</span>
274
- <span class="text-white/80 font-mono text-xs">{{ span.parent_id }}</span>
275
- </div>
276
- {% endif %}
277
- </div>
278
- </div>
279
-
280
- <div>
281
- <h4 class="text-sm font-semibold text-white/70 mb-3">Timing</h4>
282
- <div class="space-y-2 text-xs">
283
- <div class="flex">
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>
286
- </div>
287
- <div class="flex">
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>
290
- </div>
291
- {% if span.status and span.status != '' and span.status != 'UNSET' %}
292
- <div class="flex">
293
- <span class="text-white/40 w-20">Status:</span>
294
- <span class="px-2 py-0.5 rounded text-xs font-medium
295
- data-[status='ERROR']:bg-red-500/20 data-[status='ERROR']:text-red-300
296
- data-[status='OK']:bg-green-500/20 data-[status='OK']:text-green-300
297
- bg-yellow-500/20 text-yellow-300"
298
- data-status="{{ span.status }}">
299
- {{ span.status }}
300
- </span>
301
- </div>
302
- {% endif %}
303
- </div>
304
- </div>
305
- </div>
306
-
307
- {% if span.attributes %}
308
- <div class="mt-6">
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">
311
- <div class="space-y-1 text-xs">
312
- {% for key, value in span.attributes.items() %}
313
- <div class="flex">
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>
316
- </div>
317
- {% endfor %}
318
- </div>
319
- </div>
320
- </div>
321
- {% endif %}
322
-
323
- {% if span.events %}
324
- <div class="mt-6">
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">
327
- <div class="space-y-3 text-xs">
328
- {% for event in span.events %}
329
- <div class="border-l-2 border-white/20 pl-3">
330
- <div class="flex items-center justify-between mb-1">
331
- <div class="text-white/80 font-medium">{{ event.name }}</div>
332
- <div class="text-white/40 text-xs">
333
- {% set formatted_time = span.format_event_timestamp(event.timestamp) %}
334
- {% if formatted_time.__class__.__name__ == 'datetime' %}
335
- {{ formatted_time|localtime|strftime("%-I:%M:%S.%f %p") }}
336
- {% else %}
337
- {{ formatted_time }}
338
- {% endif %}
339
- </div>
340
- </div>
341
- {% if event.attributes %}
342
- <div class="space-y-1">
343
- {% for key, value in event.attributes.items() %}
344
- <div class="flex">
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>
347
- </div>
348
- {% endfor %}
349
- </div>
350
- {% endif %}
351
- </div>
352
- {% endfor %}
353
- </div>
354
- </div>
355
- </div>
356
- {% endif %}
357
-
358
- {% if span.links %}
359
- <div class="mt-6">
360
- <h4 class="text-sm font-semibold text-white/70 mb-3">Links ({{ span.links|length }})</h4>
361
- <div class="bg-stone-800/50 rounded p-3">
362
- <div class="space-y-2 text-xs">
363
- {% for link in span.links %}
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>
367
- </div>
368
- {% endfor %}
369
- </div>
370
- </div>
371
- </div>
372
- {% endif %}
373
- </div>
374
- </details>
127
+ <div class="mt-6">
128
+ <div class="flex items-center justify-between mb-3">
129
+ <h3 class="text-sm font-medium text-white/80">Spans ({{ trace.spans.count() }}) + Logs ({{ trace.logs.count() }})</h3>
130
+ <div class="text-xs text-white/60">
131
+ <span class="text-white/50">Started:</span> {{ trace.start_time|localtime|strftime("%b %-d, %-I:%M %p") }}
132
+ </div>
375
133
  </div>
134
+ <div class="space-y-1 text-xs">
135
+ {% for event in trace.get_timeline_events() %}
136
+ {% if event.type == 'span' %}
137
+ {% with span = event.instance %}
138
+ {% include "observer/partials/span.html" %}
139
+ {% endwith %}
140
+ {% elif event.type == 'log' %}
141
+ {% with log = event.instance %}
142
+ {% include "observer/partials/log.html" %}
143
+ {% endwith %}
144
+ {% endif %}
145
+
376
146
  {% else %}
377
- <div>No spans...</div>
147
+ <div>No spans or logs...</div>
378
148
  {% endfor %}
149
+ </div>
379
150
  </div>
380
151
 
152
+
381
153
  <style>
382
154
  /* Custom details arrow animation */
383
155
  details[open] summary svg {