plain.observer 0.12.0__tar.gz → 0.14.0__tar.gz

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.

Files changed (37) hide show
  1. {plain_observer-0.12.0 → plain_observer-0.14.0}/PKG-INFO +22 -1
  2. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/CHANGELOG.md +26 -0
  3. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/README.md +21 -0
  4. plain_observer-0.14.0/plain/observer/assets/observer/observer.css +72 -0
  5. plain_observer-0.14.0/plain/observer/assets/observer/observer.js +69 -0
  6. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/observer/partials/log.html +1 -1
  7. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/observer/partials/span.html +7 -3
  8. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/observer/trace.html +3 -45
  9. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/observer/trace_detail.html +2 -0
  10. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/observer/trace_share.html +2 -0
  11. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/observer/traces.html +3 -1
  12. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/toolbar/observer.html +3 -3
  13. {plain_observer-0.12.0 → plain_observer-0.14.0}/pyproject.toml +1 -1
  14. {plain_observer-0.12.0 → plain_observer-0.14.0}/.gitignore +0 -0
  15. {plain_observer-0.12.0 → plain_observer-0.14.0}/LICENSE +0 -0
  16. {plain_observer-0.12.0 → plain_observer-0.14.0}/README.md +0 -0
  17. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/AGENTS.md +0 -0
  18. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/__init__.py +0 -0
  19. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/admin.py +0 -0
  20. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/cli.py +0 -0
  21. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/config.py +0 -0
  22. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/core.py +0 -0
  23. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/default_settings.py +0 -0
  24. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/logging.py +0 -0
  25. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/0001_initial.py +0 -0
  26. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/0002_trace_share_created_at_trace_share_id_trace_summary_and_more.py +0 -0
  27. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/0003_span_plainobserv_span_id_e7ade3_idx.py +0 -0
  28. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/0004_trace_app_name_trace_app_version.py +0 -0
  29. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/0005_log_log_plainobserv_trace_i_fcfb7d_idx_and_more.py +0 -0
  30. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/0006_remove_log_logger.py +0 -0
  31. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/migrations/__init__.py +0 -0
  32. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/models.py +0 -0
  33. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/otel.py +0 -0
  34. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/templates/toolbar/observer_button.html +0 -0
  35. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/toolbar.py +0 -0
  36. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/urls.py +0 -0
  37. {plain_observer-0.12.0 → plain_observer-0.14.0}/plain/observer/views.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.observer
3
- Version: 0.12.0
3
+ Version: 0.14.0
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
@@ -16,6 +16,7 @@ Description-Content-Type: text/markdown
16
16
  **On-page telemetry and observability tools for Plain.**
17
17
 
18
18
  - [Installation](#installation)
19
+ - [Content Security Policy (CSP)](#content-security-policy-csp)
19
20
 
20
21
  ## Installation
21
22
 
@@ -57,3 +58,23 @@ plain migrate
57
58
  ```
58
59
 
59
60
  After installation, Observer will automatically integrate with your application's toolbar (if using `plain.admin`). You can access the web interface at `/observer/traces/` or use the CLI commands to analyze traces.
61
+
62
+ ## Content Security Policy (CSP)
63
+
64
+ If you're using a Content Security Policy (CSP), the Observer toolbar panel requires `frame-ancestors 'self'` to display trace information in an iframe.
65
+
66
+ Without this directive, the toolbar panel will fail to load with a CSP error: `"Refused to frame... because an ancestor violates the following Content Security Policy directive: 'frame-ancestors 'none'"`.
67
+
68
+ Example CSP configuration:
69
+
70
+ ```python
71
+ DEFAULT_RESPONSE_HEADERS = {
72
+ "Content-Security-Policy": (
73
+ "default-src 'self'; "
74
+ "script-src 'self' 'nonce-{request.csp_nonce}'; "
75
+ "style-src 'self' 'nonce-{request.csp_nonce}'; "
76
+ "frame-ancestors 'self'; " # Required for Observer toolbar
77
+ # ... other directives
78
+ ),
79
+ }
80
+ ```
@@ -1,5 +1,31 @@
1
1
  # plain-observer changelog
2
2
 
3
+ ## [0.14.0](https://github.com/dropseed/plain/releases/plain-observer@0.14.0) (2025-10-29)
4
+
5
+ ### What's changed
6
+
7
+ - Updated CSP configuration example in documentation to use the new `DEFAULT_RESPONSE_HEADERS` format with string templates instead of callable functions ([5199383](https://github.com/dropseed/plain/commit/519938312835b73dfd9be8c93abae6e896094fc2))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - No changes required
12
+
13
+ ## [0.13.0](https://github.com/dropseed/plain/releases/plain-observer@0.13.0) (2025-10-29)
14
+
15
+ ### What's changed
16
+
17
+ - Inline JavaScript and CSS extracted to separate asset files for Content Security Policy (CSP) compatibility ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
18
+ - Added CSP nonce support to inline scripts for improved security ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
19
+ - Added comprehensive CSP configuration documentation in README, including required `frame-ancestors 'self'` directive for toolbar panel ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
20
+ - Span and log indentation now uses CSS classes with data attributes instead of inline styles ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
21
+ - Timeline bar positioning now uses CSS custom properties set via JavaScript instead of inline styles ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
22
+ - Copy share URL button now uses data attributes and event delegation instead of inline onclick handlers ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
23
+ - Toolbar iframe now uses HTML attributes instead of inline styles ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
24
+
25
+ ### Upgrade instructions
26
+
27
+ - No changes required
28
+
3
29
  ## [0.12.0](https://github.com/dropseed/plain/releases/plain-observer@0.12.0) (2025-10-24)
4
30
 
5
31
  ### What's changed
@@ -3,6 +3,7 @@
3
3
  **On-page telemetry and observability tools for Plain.**
4
4
 
5
5
  - [Installation](#installation)
6
+ - [Content Security Policy (CSP)](#content-security-policy-csp)
6
7
 
7
8
  ## Installation
8
9
 
@@ -44,3 +45,23 @@ plain migrate
44
45
  ```
45
46
 
46
47
  After installation, Observer will automatically integrate with your application's toolbar (if using `plain.admin`). You can access the web interface at `/observer/traces/` or use the CLI commands to analyze traces.
48
+
49
+ ## Content Security Policy (CSP)
50
+
51
+ If you're using a Content Security Policy (CSP), the Observer toolbar panel requires `frame-ancestors 'self'` to display trace information in an iframe.
52
+
53
+ Without this directive, the toolbar panel will fail to load with a CSP error: `"Refused to frame... because an ancestor violates the following Content Security Policy directive: 'frame-ancestors 'none'"`.
54
+
55
+ Example CSP configuration:
56
+
57
+ ```python
58
+ DEFAULT_RESPONSE_HEADERS = {
59
+ "Content-Security-Policy": (
60
+ "default-src 'self'; "
61
+ "script-src 'self' 'nonce-{request.csp_nonce}'; "
62
+ "style-src 'self' 'nonce-{request.csp_nonce}'; "
63
+ "frame-ancestors 'self'; " # Required for Observer toolbar
64
+ # ... other directives
65
+ ),
66
+ }
67
+ ```
@@ -0,0 +1,72 @@
1
+ /* Custom details arrow animation */
2
+ details[open] summary svg {
3
+ transform: rotate(90deg);
4
+ }
5
+
6
+ /* Apply padding based on span level - generated for levels 0-19 */
7
+ [data-span-level="0"] {
8
+ padding-left: 0rem;
9
+ }
10
+ [data-span-level="1"] {
11
+ padding-left: 1rem;
12
+ }
13
+ [data-span-level="2"] {
14
+ padding-left: 2rem;
15
+ }
16
+ [data-span-level="3"] {
17
+ padding-left: 3rem;
18
+ }
19
+ [data-span-level="4"] {
20
+ padding-left: 4rem;
21
+ }
22
+ [data-span-level="5"] {
23
+ padding-left: 5rem;
24
+ }
25
+ [data-span-level="6"] {
26
+ padding-left: 6rem;
27
+ }
28
+ [data-span-level="7"] {
29
+ padding-left: 7rem;
30
+ }
31
+ [data-span-level="8"] {
32
+ padding-left: 8rem;
33
+ }
34
+ [data-span-level="9"] {
35
+ padding-left: 9rem;
36
+ }
37
+ [data-span-level="10"] {
38
+ padding-left: 10rem;
39
+ }
40
+ [data-span-level="11"] {
41
+ padding-left: 11rem;
42
+ }
43
+ [data-span-level="12"] {
44
+ padding-left: 12rem;
45
+ }
46
+ [data-span-level="13"] {
47
+ padding-left: 13rem;
48
+ }
49
+ [data-span-level="14"] {
50
+ padding-left: 14rem;
51
+ }
52
+ [data-span-level="15"] {
53
+ padding-left: 15rem;
54
+ }
55
+ [data-span-level="16"] {
56
+ padding-left: 16rem;
57
+ }
58
+ [data-span-level="17"] {
59
+ padding-left: 17rem;
60
+ }
61
+ [data-span-level="18"] {
62
+ padding-left: 18rem;
63
+ }
64
+ [data-span-level="19"] {
65
+ padding-left: 19rem;
66
+ }
67
+
68
+ /* Apply positioning for timeline bars using CSS custom properties */
69
+ [data-span-id] {
70
+ left: var(--start, 0%);
71
+ width: var(--width, 100%);
72
+ }
@@ -0,0 +1,69 @@
1
+ // Observer JS - CSP-compliant timeline positioning and utilities
2
+
3
+ // Apply CSS custom properties for timeline positioning
4
+ function applyTimelinePositioning() {
5
+ document
6
+ .querySelectorAll("[data-start-percent][data-width-percent]")
7
+ .forEach((el) => {
8
+ const start = el.dataset.startPercent;
9
+ const width = el.dataset.widthPercent;
10
+ el.style.setProperty("--start", `${start}%`);
11
+ el.style.setProperty("--width", `${width}%`);
12
+ });
13
+ }
14
+
15
+ // Apply on page load
16
+ document.addEventListener("DOMContentLoaded", applyTimelinePositioning);
17
+
18
+ // Re-apply after htmx swaps content
19
+ document.addEventListener("htmx:afterSwap", applyTimelinePositioning);
20
+
21
+ // Copy share URL to clipboard with visual feedback
22
+ async function copyShareUrl(button, traceId) {
23
+ try {
24
+ const shareUrl = button.getAttribute("data-share-url");
25
+
26
+ // Copy to clipboard
27
+ await navigator.clipboard.writeText(shareUrl);
28
+
29
+ // Show success feedback on button
30
+ const originalHTML = button.innerHTML;
31
+ button.innerHTML =
32
+ '<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>';
33
+ button.classList.remove("bg-emerald-700", "hover:bg-emerald-600");
34
+ button.classList.add("bg-green-600", "hover:bg-green-700");
35
+
36
+ // Also flash the URL text
37
+ const urlSpan = document.getElementById(`share-url-${traceId}`);
38
+ if (urlSpan) {
39
+ urlSpan.classList.add("text-green-400", "font-bold");
40
+ setTimeout(() => {
41
+ urlSpan.classList.remove("text-green-400", "font-bold");
42
+ }, 2000);
43
+ }
44
+
45
+ setTimeout(() => {
46
+ button.innerHTML = originalHTML;
47
+ button.classList.remove("bg-green-600", "hover:bg-green-700");
48
+ button.classList.add("bg-emerald-700", "hover:bg-emerald-600");
49
+ }, 2000);
50
+ } catch (error) {
51
+ console.error("Failed to copy share URL:", error);
52
+ alert("Failed to copy share URL. See console for details.");
53
+ }
54
+ }
55
+
56
+ // Set up event delegation for copy share URL buttons
57
+ function setupCopyShareUrlHandlers() {
58
+ document.addEventListener("click", (event) => {
59
+ const button = event.target.closest("[data-copy-share-url]");
60
+ if (button) {
61
+ event.preventDefault();
62
+ const traceId = button.getAttribute("data-trace-id");
63
+ copyShareUrl(button, traceId);
64
+ }
65
+ });
66
+ }
67
+
68
+ // Initialize on page load
69
+ document.addEventListener("DOMContentLoaded", setupCopyShareUrlHandlers);
@@ -1,4 +1,4 @@
1
- <div style="padding-left: {{ event.span_level * 1 }}rem;" class="border-l border-white/10">
1
+ <div data-span-level="{{ event.span_level }}" class="border-l border-white/10">
2
2
  <div class="ml-px px-2 py-1 text-xs flex items-start space-x-2">
3
3
  <div class="w-4 h-4 mr-2 flex items-center justify-center">
4
4
  <svg class="w-3 h-3 transform transition-transform" fill="currentColor" viewBox="0 0 20 20">
@@ -2,7 +2,7 @@
2
2
  {% set start_percent = (span_start_offset / trace.duration_ms() * 100) if trace.duration_ms() > 0 else 0 %}
3
3
  {% set width_percent = (span.duration_ms() / trace.duration_ms() * 100) if trace.duration_ms() > 0 else 0 %}
4
4
 
5
- <div style="padding-left: {{ event.span_level * 1 }}rem;" class="border-l border-white/20">
5
+ <div data-span-level="{{ event.span_level }}" class="border-l border-white/20">
6
6
  <details class="rounded bg-white/5 min-w-[600px] ml-px">
7
7
  <summary class="cursor-pointer hover:bg-white/10 transition-colors px-2 py-1 list-none [&::-webkit-details-marker]:hidden">
8
8
  <div class="flex items-center">
@@ -48,12 +48,16 @@
48
48
  data-[kind='INTERNAL']:bg-gray-500
49
49
  bg-white/30"
50
50
  data-kind="{{ span.kind }}"
51
- style="left: {{ start_percent }}%; width: {{ width_percent }}%;"
51
+ data-span-id="{{ span.span_id }}"
52
+ data-start-percent="{{ start_percent }}"
53
+ data-width-percent="{{ width_percent }}"
52
54
  title="{{ span.name }} - {{ span.duration_ms() }}ms">
53
55
  </div>
54
56
  <div
55
57
  class="absolute inset-0 flex items-center justify-start pl-1 text-xs text-white/80 font-medium whitespace-nowrap pointer-events-none"
56
- style="left: {{ start_percent }}%; width: {{ width_percent }}%;">
58
+ data-span-id="{{ span.span_id }}-label"
59
+ data-start-percent="{{ start_percent }}"
60
+ data-width-percent="{{ width_percent }}">
57
61
  {{ "%.2f"|format(span.duration_ms()) }}ms
58
62
  </div>
59
63
  </div>
@@ -4,10 +4,11 @@
4
4
  <div class="mb-2">
5
5
  <div class="flex items-center gap-2 bg-emerald-900/20 border border-emerald-700/50 rounded px-2 py-1">
6
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) }}
7
+ {{ request.host }}{{ url('observer:trace_shared', trace.share_id) }}
8
8
  </span>
9
9
  <button
10
- onclick="copyShareUrl(this, '{{ trace.id }}')"
10
+ data-copy-share-url
11
+ data-trace-id="{{ trace.id }}"
11
12
  data-share-url="{{ request.build_absolute_uri(url('observer:trace_shared', trace.share_id)) }}"
12
13
  class="px-2 py-0.5 text-xs bg-emerald-700 text-emerald-100 hover:bg-emerald-600 rounded transition-colors flex-shrink-0"
13
14
  title="Copy shareable URL">
@@ -148,46 +149,3 @@
148
149
  {% endfor %}
149
150
  </div>
150
151
  </div>
151
-
152
-
153
- <style>
154
- /* Custom details arrow animation */
155
- details[open] summary svg {
156
- transform: rotate(90deg);
157
- }
158
- </style>
159
-
160
- <script>
161
- async function copyShareUrl(button, traceId) {
162
- try {
163
- const shareUrl = button.getAttribute('data-share-url');
164
-
165
- // Copy to clipboard
166
- await navigator.clipboard.writeText(shareUrl);
167
-
168
- // Show success feedback on button
169
- const originalHTML = button.innerHTML;
170
- 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>';
171
- button.classList.remove('bg-emerald-700', 'hover:bg-emerald-600');
172
- button.classList.add('bg-green-600', 'hover:bg-green-700');
173
-
174
- // Also flash the URL text
175
- const urlSpan = document.getElementById(`share-url-${traceId}`);
176
- if (urlSpan) {
177
- urlSpan.classList.add('text-green-400', 'font-bold');
178
- setTimeout(() => {
179
- urlSpan.classList.remove('text-green-400', 'font-bold');
180
- }, 2000);
181
- }
182
-
183
- setTimeout(() => {
184
- button.innerHTML = originalHTML;
185
- button.classList.remove('bg-green-600', 'hover:bg-green-700');
186
- button.classList.add('bg-emerald-700', 'hover:bg-emerald-600');
187
- }, 2000);
188
- } catch (error) {
189
- console.error('Failed to copy share URL:', error);
190
- alert('Failed to copy share URL. See console for details.');
191
- }
192
- }
193
- </script>
@@ -6,6 +6,8 @@
6
6
  <title>Trace {{ trace.trace_id }} - Observer</title>
7
7
  {% tailwind_css %}
8
8
  {% htmx_js %}
9
+ <link rel="stylesheet" href="{{ asset('observer/observer.css') }}">
10
+ <script src="{{ asset('observer/observer.js') }}" defer></script>
9
11
  </head>
10
12
  <body class="bg-stone-950 text-stone-300 min-h-screen">
11
13
  <div class="container mx-auto p-6 max-w-6xl">
@@ -6,6 +6,8 @@
6
6
  <title>Shared Trace - {{ trace.trace_id }} - Observer</title>
7
7
  {% tailwind_css %}
8
8
  {% htmx_js %}
9
+ <link rel="stylesheet" href="{{ asset('observer/observer.css') }}">
10
+ <script src="{{ asset('observer/observer.js') }}" defer></script>
9
11
  </head>
10
12
  <body class="bg-stone-950 text-stone-300 min-h-screen">
11
13
  <div class="container mx-auto p-6 max-w-6xl">
@@ -6,7 +6,9 @@
6
6
  <title>Observer Traces</title>
7
7
  {% tailwind_css %}
8
8
  {% htmx_js %}
9
- <script>
9
+ <link rel="stylesheet" href="{{ asset('observer/observer.css') }}">
10
+ <script src="{{ asset('observer/observer.js') }}" defer></script>
11
+ <script nonce="{{ request.csp_nonce }}">
10
12
  if (window.self !== window.top) {
11
13
  document.addEventListener('DOMContentLoaded', function() {
12
14
  document.body.setAttribute('data-iframe', 'true');
@@ -3,7 +3,7 @@
3
3
  <p>Loading spans...</p>
4
4
  </div>
5
5
  </div>
6
- <script>
6
+ <script nonce="{{ request.csp_nonce }}">
7
7
  (function() {
8
8
  var container = document.getElementById('observer-traces');
9
9
  var loaded = false;
@@ -29,8 +29,8 @@
29
29
  var iframe = document.createElement('iframe');
30
30
  iframe.src = "{{ url('observer:traces') }}";
31
31
  iframe.frameBorder = "0";
32
- iframe.style.width = "100%";
33
- iframe.style.height = "100%";
32
+ iframe.width = "100%";
33
+ iframe.height = "100%";
34
34
  container.innerHTML = '';
35
35
  container.appendChild(iframe);
36
36
  observer.disconnect();
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.observer"
3
- version = "0.12.0"
3
+ version = "0.14.0"
4
4
  description = "On-page telemetry and observability tools for Plain."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  license = "BSD-3-Clause"
File without changes