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.

@@ -3,27 +3,50 @@
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
- <title>Querystats</title>
6
+ <title>Observer Traces</title>
7
7
  {% tailwind_css %}
8
8
  {% htmx_js %}
9
+ <script>
10
+ if (window.self !== window.top) {
11
+ document.addEventListener('DOMContentLoaded', function() {
12
+ document.body.setAttribute('data-iframe', 'true');
13
+ });
14
+ }
15
+ </script>
9
16
  </head>
10
- <body class="text-stone-300 overscroll-contain" hx-ext="morph">
17
+ <body class="text-white/70 overscroll-contain bg-black data-[iframe=true]:bg-transparent" hx-ext="morph">
11
18
 
12
19
  <div id="main-content">
13
20
  {% if traces %}
14
21
  <div class="flex h-full">
15
- <aside id="sidebar" class="fixed left-0 top-0 bottom-0 w-82 overflow-auto bg-stone-950 border-r border-stone-800">
16
- <div class="sticky top-0 bg-stone-950 p-4 pb-2 border-b border-stone-800/50 z-10">
17
- <div class="flex items-center justify-between mb-3">
18
- <h3 class="text-sm font-semibold text-stone-300">Traces ({{ traces|length }})</h3>
22
+ <aside class="fixed left-0 top-0 bottom-0 w-80 border-r border-white/10 flex flex-col">
23
+ <div class="flex-shrink-0 p-3 border-b border-white/10 z-10">
24
+ <div class="flex items-center justify-between">
25
+ <h3 class="text-sm text-white/90">Traces ({{ traces|length }})</h3>
19
26
  <div class="flex items-center space-x-2">
27
+ {% if observer.is_persisting() %}
28
+ <div class="w-2 h-2 bg-red-500 rounded-full animate-pulse" title="Recording"></div>
29
+ {% endif %}
30
+ <select
31
+ hx-put="."
32
+ hx-trigger="change"
33
+ hx-swap="morph:innerHTML"
34
+ hx-target="#main-content"
35
+ plain-hx-action="mode"
36
+ name="mode"
37
+ class="h-8 bg-white/10 text-white/70 rounded-sm px-2 py-0 text-xs border border-white/10 focus:border-white/20 focus:outline-none cursor-pointer">
38
+ <option value="summary" {% if observer.is_summarizing() %}selected{% endif %}>Summary</option>
39
+ <option value="persist" {% if observer.is_persisting() %}selected{% endif %}>Recording</option>
40
+ <option disabled>───────</option>
41
+ <option value="disable" {% if observer.is_disabled() %}selected{% endif %} class="text-white/50">Disabled</option>
42
+ </select>
20
43
  <button
21
44
  hx-get="."
22
45
  hx-swap="morph:innerHTML"
23
46
  hx-target="#main-content"
24
- class="p-1.5 rounded-sm bg-stone-800 text-stone-300 hover:bg-stone-700 cursor-pointer transition-colors"
47
+ 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"
25
48
  title="Refresh traces">
26
- <svg class="htmx-request:animate-spin" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
49
+ <svg class="htmx-request:animate-spin w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
27
50
  <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
28
51
  <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
29
52
  </svg>
@@ -33,73 +56,73 @@
33
56
  plain-hx-action="traces"
34
57
  hx-swap="morph:innerHTML"
35
58
  hx-target="#main-content"
36
- class="p-1.5 rounded-sm bg-stone-800 text-stone-300 hover:bg-red-600 hover:text-white cursor-pointer transition-colors"
59
+ 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"
37
60
  title="Clear all traces">
38
- <svg width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
61
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
39
62
  <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"/>
40
63
  <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"/>
41
64
  </svg>
42
65
  </button>
43
66
  </div>
44
67
  </div>
45
- <!-- Simplified mode control -->
46
- <div class="flex items-center justify-between text-xs">
47
- <div class="flex items-center space-x-2">
48
- <span class="text-stone-400">Mode:</span>
49
- <select
50
- hx-post="."
51
- hx-trigger="change"
52
- hx-swap="morph:innerHTML"
53
- hx-target="#main-content"
54
- name="observe_action"
55
- class="bg-stone-800 text-stone-300 rounded px-2 py-1 text-xs border border-stone-700 focus:border-stone-600 focus:outline-none cursor-pointer">
56
- <option value="summary" {% if observer.is_summarizing() %}selected{% endif %}>Summary</option>
57
- <option value="persist" {% if observer.is_persisting() %}selected{% endif %}>Recording</option>
58
- <option disabled>───────</option>
59
- <option value="disable" {% if observer.is_disabled() %}selected{% endif %} class="text-stone-500">Disabled</option>
60
- </select>
61
- </div>
62
- <div class="text-stone-500">
63
- {% if observer.is_persisting() %}
64
- <div class="w-2 h-2 bg-red-500 rounded-full animate-pulse" title="Recording"></div>
65
- {% endif %}
66
- </div>
67
- </div>
68
68
  </div>
69
- <div class="p-4 pt-2">
70
- <ul class="space-y-1">
69
+ <div class="flex-1 overflow-y-auto">
70
+ <ul class="divide-y divide-white/5">
71
71
  {% for trace_item in traces %}
72
72
  <li>
73
73
  <a
74
- href="?trace_id={{ trace_item.id }}"
75
- hx-boost="true"
76
- class="block w-full text-left p-3 rounded-lg border transition-all duration-200 group focus:outline-none focus:ring-2 focus:ring-blue-500/50 focus:border-blue-500 {% if trace and trace_item.id == trace.id %}bg-stone-800 border-stone-600 text-white{% else %}border-stone-800 hover:border-stone-600 hover:bg-stone-900/50{% endif %}"
77
- data-trace-id="{{ trace_item.id }}">
78
- <div class="flex items-start justify-between mb-2">
79
- {% if trace_item.root_span_name %}
80
- <div class="text-sm font-medium text-stone-200 truncate pr-2">{{ trace_item.root_span_name }}</div>
81
- {% else %}
82
- <div class="text-sm font-medium text-stone-400 truncate pr-2">{{ trace_item.trace_id }}</div>
83
- {% endif %}
84
- <div class="text-xs text-stone-500 bg-stone-800 px-2 py-0.5 rounded-full font-medium whitespace-nowrap">
85
- {{ "%.1f"|format(trace_item.duration_ms() or 0) }}ms
74
+ href="{{ trace_item.get_absolute_url() }}"
75
+ hx-get="{{ trace_item.get_absolute_url() }}"
76
+ hx-target="#trace"
77
+ hx-swap="innerHTML"
78
+ class="block w-full text-left p-3 transition-colors hover:bg-white/5 focus:outline-none focus:bg-white/5"
79
+ data-trace-id="{{ trace_item.trace_id }}">
80
+ <div class="flex items-start justify-between gap-2">
81
+ <div class="flex-1 min-w-0">
82
+ {% if trace_item.root_span_name %}
83
+ <div class="text-sm font-medium text-white/90 truncate">{{ trace_item.root_span_name }}</div>
84
+ {% else %}
85
+ <div class="text-sm font-medium text-white/30 font-mono truncate">{{ trace_item.trace_id }}</div>
86
+ {% endif %}
87
+ {% if trace_item.summary %}
88
+ <div class="text-xs text-white/75 mt-1">{{ trace_item.summary }}</div>
89
+ {% endif %}
90
+ </div>
91
+ <div class="flex items-center gap-1.5 flex-shrink-0">
92
+ <span class="text-white/40 text-xs">{{ trace_item.start_time|localtime|strftime("%-I:%M %p") }}</span>
86
93
  </div>
87
94
  </div>
88
- <div class="text-xs text-stone-400 mb-1">
89
- {{ trace_item.start_time|localtime|strftime("%-I:%M %p") }}
90
- </div>
91
- {% if trace_item.request_id %}
92
- <div class="text-xs text-stone-600 truncate">
93
- <span class="text-stone-500">Request</span> <span class="font-mono">{{ trace_item.request_id }}</span>
94
- </div>
95
- {% endif %}
96
- <div class="flex items-center justify-between mt-2 pt-2 border-t border-stone-800 group-hover:border-stone-700">
97
- <div class="text-xs text-stone-500">
98
- {{ trace_item.spans.count() }} span{{ trace_item.spans.count()|pluralize }}
95
+ <div class="mt-1 space-y-1">
96
+ {% if trace_item.root_span_name %}
97
+ <div class="flex items-center gap-1.5">
98
+ {% if trace_item.share_id %}
99
+ <div class="text-emerald-500 flex-shrink-0" title="Has active share link">
100
+ <svg class="w-3 h-3" fill="currentColor" viewBox="0 0 16 16">
101
+ <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"/>
102
+ </svg>
103
+ </div>
104
+ {% endif %}
105
+ <div class="text-xs text-white/30 font-mono truncate">{{ trace_item.trace_id }}</div>
99
106
  </div>
100
- {% if trace_item.user_id %}
101
- <div class="text-xs text-stone-500 bg-stone-800/50 px-1.5 py-0.5 rounded">
102
- User: {{ trace_item.user_id }}
107
+ {% endif %}
108
+ {% if trace_item.user_id or trace_item.session_id %}
109
+ <div class="flex items-center gap-3 text-xs text-white/50">
110
+ {% if trace_item.user_id %}
111
+ <span class="flex items-center gap-1">
112
+ <svg class="w-3 h-3 text-white/70" fill="currentColor" viewBox="0 0 16 16">
113
+ <path d="M3 14s-1 0-1-1 1-4 6-4 6 3 6 4-1 1-1 1zm5-6a3 3 0 1 0 0-6 3 3 0 0 0 0 6"/>
114
+ </svg>
115
+ {{ trace_item.user_id }}
116
+ </span>
117
+ {% endif %}
118
+ {% if trace_item.session_id %}
119
+ <span class="flex items-center gap-1 min-w-0">
120
+ <svg class="w-3 h-3 text-white/70 flex-shrink-0" fill="currentColor" viewBox="0 0 16 16">
121
+ <path d="M13.5 3a.5.5 0 0 1 .5.5V11H2V3.5a.5.5 0 0 1 .5-.5zm-11-1A1.5 1.5 0 0 0 1 3.5V12h14V3.5A1.5 1.5 0 0 0 13.5 2zM0 12.5h16a1.5 1.5 0 0 1-1.5 1.5h-13A1.5 1.5 0 0 1 0 12.5"/>
122
+ </svg>
123
+ <span class="truncate" title="{{ trace_item.session_id }}">{{ trace_item.session_id }}</span>
124
+ </span>
125
+ {% endif %}
103
126
  </div>
104
127
  {% endif %}
105
128
  </div>
@@ -110,20 +133,20 @@
110
133
  </div>
111
134
  </aside>
112
135
 
113
- <main id="content" class="flex-1 p-6 overflow-auto overscroll-contain ml-82">
114
- {% htmxfragment "trace" %}
115
- {% set show_delete_button = True %}
116
- {% include "observer/_trace_detail.html" %}
117
- {% endhtmxfragment %}
136
+ <main id="trace" class="flex-1 px-6 py-4 overflow-auto overscroll-contain ml-80">
137
+ <div hx-get="{{ traces.first().get_absolute_url() }}" hx-trigger="load" hx-swap="innerHTML">
138
+ <div class="flex items-center justify-center h-full">
139
+ <div class="text-white/50">Loading trace...</div>
140
+ </div>
141
+ </div>
118
142
  </main>
119
143
  </div>
120
144
  {% elif observer.is_enabled() %}
121
145
  <div class="flex items-center justify-center min-h-screen p-6">
122
146
  <div class="text-center max-w-2xl w-full">
123
147
  <div class="flex flex-col sm:flex-row items-center sm:items-start gap-6">
124
- <!-- Icon and status -->
125
148
  <div class="flex-shrink-0">
126
- <div class="p-3 bg-stone-800/50 rounded-full mb-3 sm:mb-0">
149
+ <div class="p-3 bg-white/10 rounded-full mb-3 sm:mb-0">
127
150
  {% if observer.is_summarizing() %}
128
151
  <svg width="32" height="32" fill="currentColor" class="text-yellow-500" viewBox="0 0 16 16">
129
152
  <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
@@ -137,16 +160,15 @@
137
160
  </div>
138
161
  </div>
139
162
 
140
- <!-- Content -->
141
163
  <div class="flex-1 text-center sm:text-left">
142
- <h3 class="text-lg font-medium text-stone-200 mb-1">
164
+ <h3 class="text-lg font-medium text-white/80 mb-1">
143
165
  {% if observer.is_summarizing() %}
144
166
  Toolbar Summary Only
145
167
  {% else %}
146
168
  Recording Traces
147
169
  {% endif %}
148
170
  </h3>
149
- <p class="text-sm text-stone-400 mb-4">
171
+ <p class="text-sm text-white/40 mb-4">
150
172
  {% if observer.is_summarizing() %}
151
173
  Performance summary is displayed in real-time. No traces are being stored.
152
174
  {% else %}
@@ -154,53 +176,57 @@
154
176
  {% endif %}
155
177
  </p>
156
178
 
157
- <!-- Actions -->
158
179
  <div class="flex flex-col sm:flex-row items-center justify-center sm:justify-start gap-2">
159
180
  {% if observer.is_summarizing() %}
160
- <form method="post" action="." style="display: inline;">
161
- {{ csrf_input }}
162
- <input type="hidden" name="observe_action" value="persist">
163
- <button type="submit" class="flex items-center space-x-2 px-3 py-1.5 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700 cursor-pointer transition-colors">
164
- <svg width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
165
- <circle cx="8" cy="8" r="8"/>
166
- </svg>
167
- <span>Record Session Traces</span>
168
- </button>
169
- </form>
181
+ <button
182
+ hx-put="."
183
+ plain-hx-action="mode"
184
+ hx-vals='{"mode": "persist"}'
185
+ hx-swap="morph:innerHTML"
186
+ hx-target="#main-content"
187
+ class="flex items-center space-x-2 px-3 py-1.5 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700 cursor-pointer transition-colors">
188
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
189
+ <circle cx="8" cy="8" r="8"/>
190
+ </svg>
191
+ <span>Record Session Traces</span>
192
+ </button>
170
193
  {% elif observer.is_persisting() %}
171
194
  <button
172
195
  hx-get="."
173
196
  hx-swap="morph:innerHTML"
174
197
  hx-target="#main-content"
175
- class="flex items-center space-x-2 px-3 py-1.5 text-sm rounded-lg bg-stone-800 text-stone-300 hover:bg-stone-700 cursor-pointer transition-colors">
176
- <svg class="htmx-request:animate-spin" width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
198
+ class="flex items-center space-x-2 px-3 py-1.5 text-sm rounded-lg bg-white/10 text-white/70 hover:bg-white/20 cursor-pointer transition-colors">
199
+ <svg class="htmx-request:animate-spin w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
177
200
  <path fill-rule="evenodd" d="M8 3a5 5 0 1 0 4.546 2.914.5.5 0 0 1 .908-.417A6 6 0 1 1 8 2v1z"/>
178
201
  <path d="M8 4.466V.534a.25.25 0 0 1 .41-.192l2.36 1.966c.12.1.12.284 0 .384L8.41 4.658A.25.25 0 0 1 8 4.466z"/>
179
202
  </svg>
180
203
  <span class="htmx-request:hidden">Check for Traces</span>
181
204
  <span class="hidden htmx-request:inline">Checking...</span>
182
205
  </button>
183
- <form method="post" action="." style="display: inline;">
184
- {{ csrf_input }}
185
- <input type="hidden" name="observe_action" value="summary">
186
- <button type="submit" class="flex items-center space-x-2 px-3 py-1.5 text-sm rounded-lg bg-stone-700 text-stone-200 hover:bg-stone-600 cursor-pointer transition-colors">
187
- <svg width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
188
- <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
189
- <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
190
- </svg>
191
- <span>Stop Recording</span>
192
- </button>
193
- </form>
206
+ <button
207
+ hx-put="."
208
+ plain-hx-action="mode"
209
+ hx-vals='{"mode": "summary"}'
210
+ hx-swap="morph:innerHTML"
211
+ hx-target="#main-content"
212
+ class="flex items-center space-x-2 px-3 py-1.5 text-sm rounded-lg bg-white/20 text-white/80 hover:bg-white/30 cursor-pointer transition-colors">
213
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
214
+ <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
215
+ <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
216
+ </svg>
217
+ <span>Stop Recording</span>
218
+ </button>
194
219
  {% endif %}
195
220
 
196
- <!-- Subtle disable option -->
197
- <form method="post" action="." style="display: inline;">
198
- {{ csrf_input }}
199
- <input type="hidden" name="observe_action" value="disable">
200
- <button type="submit" class="px-3 py-1.5 text-sm text-stone-500 hover:text-stone-400 transition-colors">
201
- Disable Observer
202
- </button>
203
- </form>
221
+ <button
222
+ hx-put="."
223
+ plain-hx-action="mode"
224
+ hx-vals='{"mode": "disable"}'
225
+ hx-swap="morph:innerHTML"
226
+ hx-target="#main-content"
227
+ class="px-3 py-1.5 text-sm text-white/50 hover:text-white/40 transition-colors">
228
+ Disable Observer
229
+ </button>
204
230
  </div>
205
231
  </div>
206
232
  </div>
@@ -210,10 +236,9 @@
210
236
  <div class="flex items-center justify-center min-h-screen p-6">
211
237
  <div class="text-center max-w-2xl w-full">
212
238
  <div class="flex flex-col sm:flex-row items-center sm:items-start gap-6">
213
- <!-- Icon -->
214
239
  <div class="flex-shrink-0">
215
- <div class="p-3 bg-stone-800/50 rounded-full mb-3 sm:mb-0">
216
- <svg width="32" height="32" fill="currentColor" class="text-stone-500" viewBox="0 0 16 16">
240
+ <div class="p-3 bg-white/10 rounded-full mb-3 sm:mb-0">
241
+ <svg width="32" height="32" fill="currentColor" class="text-white/50" viewBox="0 0 16 16">
217
242
  <path d="M13.359 11.238C15.06 9.72 16 8 16 8s-3-5.5-8-5.5a7.028 7.028 0 0 0-2.79.588l.77.771A5.944 5.944 0 0 1 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.134 13.134 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755-.165.165-.337.328-.517.486l.708.709z"/>
218
243
  <path d="M11.297 9.176a3.5 3.5 0 0 0-4.474-4.474l.823.823a2.5 2.5 0 0 1 2.829 2.829l.822.822zm-2.943 1.299.822.822a3.5 3.5 0 0 1-4.474-4.474l.823.823a2.5 2.5 0 0 0 2.829 2.829z"/>
219
244
  <path d="M3.35 5.47c-.18.16-.353.322-.518.487A13.134 13.134 0 0 0 1.172 8l.195.288c.335.48.83 1.12 1.465 1.755C4.121 11.332 5.881 12.5 8 12.5c.716 0 1.39-.133 2.02-.36l.77.772A7.029 7.029 0 0 1 8 13.5C3 13.5 0 8 0 8s.939-1.721 2.641-3.238l.708.708zm10.296 8.884-12-12 .708-.708 12 12-.708.708z"/>
@@ -221,15 +246,13 @@
221
246
  </div>
222
247
  </div>
223
248
 
224
- <!-- Content -->
225
249
  <div class="flex-1 text-center sm:text-left">
226
- <h3 class="text-lg font-medium text-stone-200 mb-1">Observer is Disabled</h3>
227
- <p class="text-sm text-stone-400 mb-4">
250
+ <h3 class="text-lg font-medium text-white/80 mb-1">Observer is Disabled</h3>
251
+ <p class="text-sm text-white/40 mb-4">
228
252
  Enable observer to start monitoring your application's performance and traces.
229
253
  </p>
230
254
 
231
- <!-- Mode descriptions in compact grid -->
232
- <div class="bg-stone-800/30 rounded-lg p-3 mb-4">
255
+ <div class="bg-white/10 rounded-lg p-3 mb-4">
233
256
  <div class="grid grid-cols-1 sm:grid-cols-2 gap-3">
234
257
  <div class="text-left">
235
258
  <h4 class="text-xs font-medium text-yellow-400 mb-1 flex items-center">
@@ -239,7 +262,7 @@
239
262
  </svg>
240
263
  Summary Mode
241
264
  </h4>
242
- <p class="text-xs text-stone-400">Monitor performance in real-time without saving traces.</p>
265
+ <p class="text-xs text-white/40">Monitor performance in real-time without saving traces.</p>
243
266
  </div>
244
267
  <div class="text-left">
245
268
  <h4 class="text-xs font-medium text-red-400 mb-1 flex items-center">
@@ -248,34 +271,37 @@
248
271
  </svg>
249
272
  Recording Mode
250
273
  </h4>
251
- <p class="text-xs text-stone-400">Record and store traces for detailed analysis.</p>
274
+ <p class="text-xs text-white/40">Record and store traces for detailed analysis.</p>
252
275
  </div>
253
276
  </div>
254
277
  </div>
255
278
 
256
- <!-- Actions -->
257
279
  <div class="flex flex-col sm:flex-row items-center sm:items-start gap-2">
258
- <form method="post" action=".">
259
- {{ csrf_input }}
260
- <input type="hidden" name="observe_action" value="summary">
261
- <button type="submit" class="flex items-center justify-center space-x-2 px-4 py-1.5 text-sm rounded-lg bg-stone-700 text-stone-200 hover:bg-stone-600 cursor-pointer transition-colors w-full sm:w-auto">
262
- <svg width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
263
- <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
264
- <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
265
- </svg>
266
- <span>Enable Summary</span>
267
- </button>
268
- </form>
269
- <form method="post" action=".">
270
- {{ csrf_input }}
271
- <input type="hidden" name="observe_action" value="persist">
272
- <button type="submit" class="flex items-center justify-center space-x-2 px-4 py-1.5 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700 cursor-pointer transition-colors w-full sm:w-auto">
273
- <svg width="14" height="14" fill="currentColor" viewBox="0 0 16 16">
274
- <circle cx="8" cy="8" r="8"/>
275
- </svg>
276
- <span>Start Recording Session</span>
277
- </button>
278
- </form>
280
+ <button
281
+ hx-put="."
282
+ plain-hx-action="mode"
283
+ hx-vals='{"mode": "summary"}'
284
+ hx-swap="morph:innerHTML"
285
+ hx-target="#main-content"
286
+ class="flex items-center justify-center space-x-2 px-4 py-1.5 text-sm rounded-lg bg-white/20 text-white/80 hover:bg-white/30 cursor-pointer transition-colors w-full sm:w-auto">
287
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
288
+ <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/>
289
+ <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/>
290
+ </svg>
291
+ <span>Enable Summary</span>
292
+ </button>
293
+ <button
294
+ hx-put="."
295
+ plain-hx-action="mode"
296
+ hx-vals='{"mode": "persist"}'
297
+ hx-swap="morph:innerHTML"
298
+ hx-target="#main-content"
299
+ class="flex items-center justify-center space-x-2 px-4 py-1.5 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700 cursor-pointer transition-colors w-full sm:w-auto">
300
+ <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
301
+ <circle cx="8" cy="8" r="8"/>
302
+ </svg>
303
+ <span>Start Recording Session</span>
304
+ </button>
279
305
  </div>
280
306
  </div>
281
307
  </div>
@@ -1,45 +1,29 @@
1
- {% if observer.is_enabled() %}
2
- <form method="post" action="{{ url('observer:traces') }}" style="display: inline;">
3
- <input type="hidden" name="redirect_url" value="{{ request.get_full_path() }}">
4
- {% if observer.is_summarizing() %}
5
- {# Toggle from summary to persist #}
6
- <input type="hidden" name="observe_action" value="persist">
7
- {% else %}
8
- {# Toggle from persist to summary #}
9
- <input type="hidden" name="observe_action" value="summary">
1
+ <button
2
+ type="button"
3
+ class="inline-flex items-center cursor-pointer text-xs rounded-full px-2.5 py-px bg-white/20 text-white/80 whitespace-nowrap hover:bg-white/30"
4
+ data-toolbar-tab="Observer">
5
+ {% if observer.is_persisting() %}
6
+ <span class="relative inline-flex size-2 mr-2.5">
7
+ <span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75"></span>
8
+ <span class="relative inline-flex size-2 rounded-full bg-red-500"></span>
9
+ </span>
10
+ {% elif observer.is_summarizing() %}
11
+ <span class="relative inline-flex size-2 mr-2">
12
+ <span class="relative inline-flex size-2 rounded-full bg-zinc-500"></span>
13
+ </span>
14
+ {% elif not observer.is_enabled() %}
15
+ <span class="rounded-full bg-zinc-500 w-2 h-2 inline-block mr-2"></span>
10
16
  {% endif %}
11
- <button
12
- class="inline-flex items-center cursor-pointer text-xs rounded-full px-2.5 py-px bg-white/20 text-white/80 whitespace-nowrap hover:bg-white/30"
13
- type="submit"
14
- data-toolbar-tab="Observer"
15
- title="Toggle observer mode ({% if observer.is_summarizing() %}summary{% elif observer.is_persisting() %}persist{% else %}disabled{% endif %} mode){% if observer.get_current_trace_summary() %} - {{ observer.get_current_trace_summary() }}{% endif %} - Click to switch to {% if observer.is_summarizing() %}persist{% else %}summary{% endif %} mode">
16
- {% if observer.is_persisting() %}
17
- <span class="relative inline-flex size-2 mr-2.5">
18
- <span class="absolute inline-flex h-full w-full animate-ping rounded-full bg-red-400 opacity-75"></span>
19
- <span class="relative inline-flex size-2 rounded-full bg-red-500"></span>
20
- </span>
21
- {% elif observer.is_summarizing() %}
22
- <span class="relative inline-flex size-2 mr-2.5">
23
- <span class="relative inline-flex size-2 rounded-full bg-zinc-500"></span>
24
- </span>
25
- {% endif %}
26
- {% if observer.get_current_trace_summary() %}
27
- <span class="font-mono tracking-tight">{{ observer.get_current_trace_summary() }}</span>
28
- {% else %}
29
- {% if observer.is_persisting() %}Recording{% elif observer.is_summarizing() %}Summary{% else %}Observing{% endif %}
30
- {% endif %}
31
- </button>
32
- </form>
33
- {% else %}
34
- <form method="post" action="{{ url('observer:traces') }}" style="display: inline;">
35
- <input type="hidden" name="redirect_url" value="{{ request.get_full_path() }}">
36
- <input type="hidden" name="observe_action" value="summary">
37
- <button
38
- type="submit"
39
- class="cursor-pointer text-xs rounded-full px-2 py-px bg-white/20 text-white/80 whitespace-nowrap hover:bg-white/30"
40
- title="Enable observer">
41
- <span class="rounded-full bg-zinc-500 w-2 h-2 inline-block mr-1"></span>
17
+
18
+ {% if observer.get_current_trace_summary() %}
19
+ {{ observer.get_current_trace_summary() }}
20
+ {% elif observer.is_persisting() %}
21
+ Recording
22
+ {% elif observer.is_summarizing() %}
23
+ Summary
24
+ {% elif observer.is_enabled() %}
25
+ Observing
26
+ {% else %}
42
27
  Observer disabled
43
- </button>
44
- </form>
45
- {% endif %}
28
+ {% endif %}
29
+ </button>
plain/observer/urls.py CHANGED
@@ -6,5 +6,7 @@ from . import views
6
6
  class ObserverRouter(Router):
7
7
  namespace = "observer"
8
8
  urls = [
9
- path("", views.ObserverTracesView, name="traces"),
9
+ path("traces/", views.ObserverTracesView, name="traces"),
10
+ path("traces/<trace_id>/", views.ObserverTraceDetailView, name="trace_detail"),
11
+ path("share/<share_id>/", views.ObserverTraceSharedView, name="trace_shared"),
10
12
  ]