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

@@ -23,7 +23,7 @@
23
23
  <div class="flex-shrink-0 p-3 border-b border-white/10 z-10">
24
24
  <div class="flex items-center justify-between">
25
25
  <h3 class="text-sm text-white/90">Traces ({{ traces|length }})</h3>
26
- <div class="flex items-center space-x-2">
26
+ <div class="flex items-center gap-2">
27
27
  {% if observer.is_persisting() %}
28
28
  <div class="w-2 h-2 bg-red-500 rounded-full animate-pulse" title="Recording"></div>
29
29
  {% endif %}
@@ -40,29 +40,42 @@
40
40
  <option disabled>───────</option>
41
41
  <option value="disable" {% if observer.is_disabled() %}selected{% endif %} class="text-white/50">Disabled</option>
42
42
  </select>
43
- <button
44
- hx-get="."
45
- hx-swap="morph:innerHTML"
46
- hx-target="#main-content"
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"
48
- title="Refresh traces">
49
- <svg class="htmx-request:animate-spin w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
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"/>
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"/>
52
- </svg>
53
- </button>
54
- <button
55
- hx-delete="."
56
- plain-hx-action="traces"
57
- hx-swap="morph:innerHTML"
58
- hx-target="#main-content"
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"
60
- title="Clear all traces">
61
- <svg class="w-4 h-4" fill="currentColor" viewBox="0 0 16 16">
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"/>
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"/>
64
- </svg>
65
- </button>
43
+ <div class="relative group">
44
+ <div class="flex items-center gap-2 hover:bg-white/10 rounded-lg p-1 transition-colors cursor-pointer">
45
+ <svg class="w-4 h-4 text-white/70" fill="currentColor" viewBox="0 0 16 16">
46
+ <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"/>
47
+ </svg>
48
+ </div>
49
+ <div class="absolute right-0 mt-3 w-48 bg-black rounded-lg shadow-lg border border-white/10 py-2 z-50 opacity-0 invisible group-hover:opacity-100 group-hover:visible transition-all duration-200">
50
+ <div class="py-1">
51
+ <button
52
+ hx-get="."
53
+ hx-swap="morph:innerHTML"
54
+ hx-target="#main-content"
55
+ class="cursor-pointer w-full text-left px-4 py-3 text-sm text-white/70 hover:bg-white/5 transition-colors flex items-center gap-3">
56
+ <svg class="htmx-request:animate-spin w-4 h-4 text-white/50" fill="currentColor" viewBox="0 0 16 16">
57
+ <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"/>
58
+ <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"/>
59
+ </svg>
60
+ <span class="htmx-request:hidden">Refresh Traces</span>
61
+ <span class="hidden htmx-request:inline">Refreshing...</span>
62
+ </button>
63
+ <button
64
+ hx-delete="."
65
+ plain-hx-action="traces"
66
+ hx-swap="morph:innerHTML"
67
+ hx-target="#main-content"
68
+ hx-confirm="Clear all traces?"
69
+ class="cursor-pointer w-full text-left px-4 py-3 text-sm text-red-200 hover:bg-red-900/20 transition-colors flex items-center gap-3">
70
+ <svg class="w-4 h-4 text-red-400" fill="currentColor" viewBox="0 0 16 16">
71
+ <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"/>
72
+ <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"/>
73
+ </svg>
74
+ Clear All Traces
75
+ </button>
76
+ </div>
77
+ </div>
78
+ </div>
66
79
  </div>
67
80
  </div>
68
81
  </div>
@@ -89,7 +102,7 @@
89
102
  {% endif %}
90
103
  </div>
91
104
  <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>
105
+ <span class="text-white/40 text-xs" title="{{ trace_item.start_time|localtime }}">{{ trace_item.start_time|timesince }} ago</span>
93
106
  </div>
94
107
  </div>
95
108
  <div class="mt-1 space-y-1">
@@ -105,10 +118,10 @@
105
118
  <div class="text-xs text-white/30 font-mono truncate">{{ trace_item.trace_id }}</div>
106
119
  </div>
107
120
  {% endif %}
108
- {% if trace_item.user_id or trace_item.session_id %}
121
+ {% if trace_item.user_id or trace_item.session_id or trace_item.app_version %}
109
122
  <div class="flex items-center gap-3 text-xs text-white/50">
110
123
  {% if trace_item.user_id %}
111
- <span class="flex items-center gap-1">
124
+ <span class="flex items-center gap-1 flex-shrink-0">
112
125
  <svg class="w-3 h-3 text-white/70" fill="currentColor" viewBox="0 0 16 16">
113
126
  <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
127
  </svg>
@@ -116,13 +129,22 @@
116
129
  </span>
117
130
  {% endif %}
118
131
  {% if trace_item.session_id %}
119
- <span class="flex items-center gap-1 min-w-0">
132
+ <span class="flex items-center gap-1 min-w-0 flex-shrink">
120
133
  <svg class="w-3 h-3 text-white/70 flex-shrink-0" fill="currentColor" viewBox="0 0 16 16">
121
134
  <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
135
  </svg>
123
136
  <span class="truncate" title="{{ trace_item.session_id }}">{{ trace_item.session_id }}</span>
124
137
  </span>
125
138
  {% endif %}
139
+ {% if trace_item.app_version %}
140
+ <span class="flex items-center gap-1 flex-shrink-0">
141
+ <svg class="w-3 h-3 text-white/70" fill="currentColor" viewBox="0 0 16 16">
142
+ <path d="M6 4.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0zm-1 0a.5.5 0 1 0-1 0 .5.5 0 0 0 1 0z"/>
143
+ <path d="M2 1h4.586a1 1 0 0 1 .707.293l7 7a1 1 0 0 1 0 1.414l-4.586 4.586a1 1 0 0 1-1.414 0l-7-7A1 1 0 0 1 1 6.586V2a1 1 0 0 1 1-1zm0 5.586 7 7L13.586 9l-7-7H2v4.586z"/>
144
+ </svg>
145
+ {{ trace_item.app_version }}
146
+ </span>
147
+ {% endif %}
126
148
  </div>
127
149
  {% endif %}
128
150
  </div>
plain/observer/views.py CHANGED
@@ -89,12 +89,21 @@ class ObserverTraceDetailView(AuthViewMixin, HTMXViewMixin, DetailView):
89
89
  super().check_auth()
90
90
 
91
91
  def get(self):
92
- """Return trace data as HTML or JSON based on content negotiation."""
92
+ """Return trace data as HTML, JSON, or logs based on content negotiation."""
93
93
  if (
94
94
  "application/json" in self.request.headers.get("Accept", "")
95
95
  or self.request.query_params.get("format") == "json"
96
96
  ):
97
- return self.get_object().as_dict()
97
+ return self.object.as_dict()
98
+
99
+ if self.request.query_params.get("logs") == "true":
100
+ logs = self.object.logs.all().order_by("timestamp")
101
+ log_lines = []
102
+ for log in logs:
103
+ timestamp = log.timestamp.strftime("%Y-%m-%d %H:%M:%S.%f")
104
+ log_lines.append(f"{timestamp} [{log.level}]: {log.message}")
105
+
106
+ return Response("\n".join(log_lines), content_type="text/plain")
98
107
 
99
108
  return super().get()
100
109
 
@@ -105,8 +114,7 @@ class ObserverTraceDetailView(AuthViewMixin, HTMXViewMixin, DetailView):
105
114
  return super().get_template_names()
106
115
 
107
116
  def htmx_delete(self):
108
- trace = self.get_object()
109
- trace.delete()
117
+ self.object.delete()
110
118
 
111
119
  # Redirect to traces list after deletion
112
120
  response = Response(status_code=204)
@@ -114,13 +122,11 @@ class ObserverTraceDetailView(AuthViewMixin, HTMXViewMixin, DetailView):
114
122
  return response
115
123
 
116
124
  def htmx_post_share(self):
117
- trace = self.get_object()
118
- trace.generate_share_id()
125
+ self.object.generate_share_id()
119
126
  return super().get()
120
127
 
121
128
  def htmx_delete_share(self):
122
- trace = self.get_object()
123
- trace.remove_share_id()
129
+ self.object.remove_share_id()
124
130
  return super().get()
125
131
 
126
132
 
@@ -144,6 +150,6 @@ class ObserverTraceSharedView(DetailView):
144
150
  "application/json" in self.request.headers.get("Accept", "")
145
151
  or self.request.query_params.get("format") == "json"
146
152
  ):
147
- return JsonResponse(self.get_object().as_dict())
153
+ return JsonResponse(self.object.as_dict())
148
154
 
149
155
  return super().get()
@@ -1,11 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.observer
3
- Version: 0.5.0
3
+ Version: 0.6.1
4
4
  Summary: On-page telemetry and observability tools for Plain.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-Expression: BSD-3-Clause
7
7
  License-File: LICENSE
8
- Requires-Python: >=3.11
8
+ Requires-Python: >=3.13
9
9
  Requires-Dist: opentelemetry-sdk>=1.34.1
10
10
  Requires-Dist: plain-admin<1.0.0
11
11
  Requires-Dist: plain<1.0.0
@@ -0,0 +1,33 @@
1
+ plain/observer/CHANGELOG.md,sha256=pZEAYcaGjPvV3paHKXoM7Nw_vIYyEi4R3vd1yocEn8o,8968
2
+ plain/observer/README.md,sha256=39RA17fgcyOkqeIWBOAg4Be6YZjoiDzK5PVOG-nseuY,988
3
+ plain/observer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
+ plain/observer/admin.py,sha256=yvfkXD4kIosodEqkvJkqACIb6WOdNaHFRX_LGca5Aks,3391
5
+ plain/observer/cli.py,sha256=Jh5OwTtKmV6TTymfS9xv1R_tQL1tgfKKUS__19Wcnbk,20647
6
+ plain/observer/config.py,sha256=8c5aX2_rzVL4SSFO4Q0nMyUSqgpu3Zv3aJkeFZONOpM,2434
7
+ plain/observer/core.py,sha256=ZHwQAXlqKa1BvHh3Xr6R0iOgkSNTEJ_Wg2lUlgMzWsE,2420
8
+ plain/observer/default_settings.py,sha256=JN2jT2wfa6f80EqU0p4Ox_47xyxL-Ym5-_pftY7xj2U,197
9
+ plain/observer/logging.py,sha256=5_fcmUE0x2lg7968V4lhduaTbRLCr8w-ooRFo9BC1ms,2795
10
+ plain/observer/models.py,sha256=EJTUCDeJkG55XgTT1AUpYvEDU8KDgCVr1nexKKt4GQQ,17045
11
+ plain/observer/otel.py,sha256=h7LI7Nur8Y6XheriuNWPmeSFpf2Vsl94Sgxv02W8ZXY,16170
12
+ plain/observer/toolbar.py,sha256=He7SqjED-7smLDWoomZ9df35qN4WcslNXi5eaO7X4lY,617
13
+ plain/observer/urls.py,sha256=oLJoDjB7YMUx8z-MGql_xXSXkfacVKFp-aHNXaKzs38,376
14
+ plain/observer/views.py,sha256=8KoBwXBj2q-mbuscA80h7nlglsOraTz-ScTzTQwLWP0,4949
15
+ plain/observer/migrations/0001_initial.py,sha256=HVoSrd5V-IOqD1adADIAzqMH8xMlPwyLOFH6JcGFniI,3312
16
+ plain/observer/migrations/0002_trace_share_created_at_trace_share_id_trace_summary_and_more.py,sha256=lgFqdn66zq7AoCvkC3MIvLXiE_Omup5EEMDSe1jpKOg,1765
17
+ plain/observer/migrations/0003_span_plainobserv_span_id_e7ade3_idx.py,sha256=54G8GXi-PiFbcTU-HrXO14UN9PxhjDCY05TA9HHkkmk,527
18
+ plain/observer/migrations/0004_trace_app_name_trace_app_version.py,sha256=Qfp62tlv0UfKKPIM1NcbqVfA9TXhsNSoq9qzbnjgCPk,640
19
+ plain/observer/migrations/0005_log_log_plainobserv_trace_i_fcfb7d_idx_and_more.py,sha256=ubUAPgN_jKK_o9J3G38MihGgsP_LvBwAzgvc1N-Td9M,2167
20
+ plain/observer/migrations/0006_remove_log_logger.py,sha256=ruuwsDEcilSFTimU52JMiHz9ENxRCmLyheX_71ZXxfo,362
21
+ plain/observer/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
+ plain/observer/templates/observer/trace.html,sha256=Hd92O1lX0xjvDR7cb79NFVeJhxaVEA1mmyzvsRVKuwU,11362
23
+ plain/observer/templates/observer/trace_detail.html,sha256=86-YGCpg9gy31Pd2vDHB3LYRkJile-DsGD1Enjx9s5Y,943
24
+ plain/observer/templates/observer/trace_share.html,sha256=HrYLti5BpX96-6Bm_37OObFvAJSYbZ3GZD-MwCi9hgA,525
25
+ plain/observer/templates/observer/traces.html,sha256=auU9tYUan4P0En48VwfOKgKr7wRNSF7Jc7X-V--7fUE,24575
26
+ plain/observer/templates/observer/partials/log.html,sha256=Vpp0j-GLBfwTBFyZp_cv0jkOLpgxfd-8-RMBe6V4HbU,964
27
+ plain/observer/templates/observer/partials/span.html,sha256=PaWZONsABwlS7mdKsJlYbhMSOzidq_WyOp6ifvFUgmM,18672
28
+ plain/observer/templates/toolbar/observer.html,sha256=uaDKiWR7EYqC1kEXE-uHDlE7nfFEMR_zmOgvlKwQHJ4,1365
29
+ plain/observer/templates/toolbar/observer_button.html,sha256=FMBJHKMGqpHWs-3Ei2PBCdVYZ_6vFw6-eKH_i4jQu-Q,1215
30
+ plain_observer-0.6.1.dist-info/METADATA,sha256=WMOZIw-IltBu-VI2VdgU_HbdtqAyy_PSJwB4pT7KPto,1386
31
+ plain_observer-0.6.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
32
+ plain_observer-0.6.1.dist-info/licenses/LICENSE,sha256=YZdq6Pz8ivjs97eSVLRmoGDI1hjEikX6N49DfM0DWio,1500
33
+ plain_observer-0.6.1.dist-info/RECORD,,
@@ -1,27 +0,0 @@
1
- plain/observer/CHANGELOG.md,sha256=dY_jT_IisTqQOzhirekm0fNhsJQcx6MrGML5OhCuHYo,6735
2
- plain/observer/README.md,sha256=39RA17fgcyOkqeIWBOAg4Be6YZjoiDzK5PVOG-nseuY,988
3
- plain/observer/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
4
- plain/observer/admin.py,sha256=AsX6uGjIHN7EWu1Ez4VY8yYHiCydZ6bv7HCig9W9SI4,2096
5
- plain/observer/cli.py,sha256=Jh5OwTtKmV6TTymfS9xv1R_tQL1tgfKKUS__19Wcnbk,20647
6
- plain/observer/config.py,sha256=FuJi1jiDSvOTcmP-6Ea4OlGZt5cRf4iTp1e0dgpJ45E,1494
7
- plain/observer/core.py,sha256=ZHwQAXlqKa1BvHh3Xr6R0iOgkSNTEJ_Wg2lUlgMzWsE,2420
8
- plain/observer/default_settings.py,sha256=JN2jT2wfa6f80EqU0p4Ox_47xyxL-Ym5-_pftY7xj2U,197
9
- plain/observer/models.py,sha256=ZHu5SLWEpb4wOqQelGqAFmwSORCLH2H7T9GUYAoeLN0,13118
10
- plain/observer/otel.py,sha256=9OUvAJfEXBVuFIIs3CZZ8IYHu-QxRFklbPBtEVHHz54,14645
11
- plain/observer/toolbar.py,sha256=He7SqjED-7smLDWoomZ9df35qN4WcslNXi5eaO7X4lY,617
12
- plain/observer/urls.py,sha256=oLJoDjB7YMUx8z-MGql_xXSXkfacVKFp-aHNXaKzs38,376
13
- plain/observer/views.py,sha256=bPBByjs1ZlsN-9wgiO4OqQYR9uPXBYnasqV2ZZLJU0Q,4626
14
- plain/observer/migrations/0001_initial.py,sha256=HVoSrd5V-IOqD1adADIAzqMH8xMlPwyLOFH6JcGFniI,3312
15
- plain/observer/migrations/0002_trace_share_created_at_trace_share_id_trace_summary_and_more.py,sha256=lgFqdn66zq7AoCvkC3MIvLXiE_Omup5EEMDSe1jpKOg,1765
16
- plain/observer/migrations/0003_span_plainobserv_span_id_e7ade3_idx.py,sha256=54G8GXi-PiFbcTU-HrXO14UN9PxhjDCY05TA9HHkkmk,527
17
- plain/observer/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
- plain/observer/templates/observer/trace.html,sha256=42yuvqAH7poRMFbueXT-MeSIClmUaLSHohG9LF0sepk,27098
19
- plain/observer/templates/observer/trace_detail.html,sha256=86-YGCpg9gy31Pd2vDHB3LYRkJile-DsGD1Enjx9s5Y,943
20
- plain/observer/templates/observer/trace_share.html,sha256=HrYLti5BpX96-6Bm_37OObFvAJSYbZ3GZD-MwCi9hgA,525
21
- plain/observer/templates/observer/traces.html,sha256=cFnlIuCf6XJEpHRD2_yfVmPDGZfiqMq6Cg-iMlT3CWY,22184
22
- plain/observer/templates/toolbar/observer.html,sha256=uaDKiWR7EYqC1kEXE-uHDlE7nfFEMR_zmOgvlKwQHJ4,1365
23
- plain/observer/templates/toolbar/observer_button.html,sha256=FMBJHKMGqpHWs-3Ei2PBCdVYZ_6vFw6-eKH_i4jQu-Q,1215
24
- plain_observer-0.5.0.dist-info/METADATA,sha256=wpz5Pi6iYS5jpWgbusejdnoPrQir1nZ2VQiRZt4BOnE,1386
25
- plain_observer-0.5.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
- plain_observer-0.5.0.dist-info/licenses/LICENSE,sha256=YZdq6Pz8ivjs97eSVLRmoGDI1hjEikX6N49DfM0DWio,1500
27
- plain_observer-0.5.0.dist-info/RECORD,,