plain.observer 0.11.2__tar.gz → 0.13.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.11.2 → plain_observer-0.13.0}/.gitignore +1 -1
  2. {plain_observer-0.11.2 → plain_observer-0.13.0}/PKG-INFO +24 -1
  3. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/CHANGELOG.md +29 -0
  4. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/README.md +23 -0
  5. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/admin.py +4 -6
  6. plain_observer-0.13.0/plain/observer/assets/observer/observer.css +72 -0
  7. plain_observer-0.13.0/plain/observer/assets/observer/observer.js +69 -0
  8. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/default_settings.py +0 -1
  9. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/observer/partials/log.html +1 -1
  10. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/observer/partials/span.html +7 -3
  11. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/observer/trace.html +3 -45
  12. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/observer/trace_detail.html +2 -0
  13. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/observer/trace_share.html +2 -0
  14. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/observer/traces.html +3 -1
  15. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/toolbar/observer.html +3 -3
  16. {plain_observer-0.11.2 → plain_observer-0.13.0}/pyproject.toml +1 -1
  17. {plain_observer-0.11.2 → plain_observer-0.13.0}/LICENSE +0 -0
  18. {plain_observer-0.11.2 → plain_observer-0.13.0}/README.md +0 -0
  19. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/AGENTS.md +0 -0
  20. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/__init__.py +0 -0
  21. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/cli.py +0 -0
  22. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/config.py +0 -0
  23. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/core.py +0 -0
  24. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/logging.py +0 -0
  25. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/0001_initial.py +0 -0
  26. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/0002_trace_share_created_at_trace_share_id_trace_summary_and_more.py +0 -0
  27. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/0003_span_plainobserv_span_id_e7ade3_idx.py +0 -0
  28. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/0004_trace_app_name_trace_app_version.py +0 -0
  29. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/0005_log_log_plainobserv_trace_i_fcfb7d_idx_and_more.py +0 -0
  30. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/0006_remove_log_logger.py +0 -0
  31. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/migrations/__init__.py +0 -0
  32. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/models.py +0 -0
  33. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/otel.py +0 -0
  34. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/templates/toolbar/observer_button.html +0 -0
  35. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/toolbar.py +0 -0
  36. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/urls.py +0 -0
  37. {plain_observer-0.11.2 → plain_observer-0.13.0}/plain/observer/views.py +0 -0
@@ -1,5 +1,5 @@
1
1
  .venv
2
- .env
2
+ /.env
3
3
  *.egg-info
4
4
  *.py[co]
5
5
  __pycache__
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.observer
3
- Version: 0.11.2
3
+ Version: 0.13.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,25 @@ 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
+ def DEFAULT_RESPONSE_HEADERS(request):
72
+ nonce = request.csp_nonce
73
+ return {
74
+ "Content-Security-Policy": (
75
+ f"default-src 'self'; "
76
+ f"script-src 'self' 'nonce-{nonce}'; "
77
+ f"style-src 'self' 'nonce-{nonce}'; "
78
+ f"frame-ancestors 'self'; " # Required for Observer toolbar
79
+ # ... other directives
80
+ ),
81
+ }
82
+ ```
@@ -1,5 +1,34 @@
1
1
  # plain-observer changelog
2
2
 
3
+ ## [0.13.0](https://github.com/dropseed/plain/releases/plain-observer@0.13.0) (2025-10-29)
4
+
5
+ ### What's changed
6
+
7
+ - Inline JavaScript and CSS extracted to separate asset files for Content Security Policy (CSP) compatibility ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
8
+ - Added CSP nonce support to inline scripts for improved security ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
9
+ - Added comprehensive CSP configuration documentation in README, including required `frame-ancestors 'self'` directive for toolbar panel ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
10
+ - Span and log indentation now uses CSS classes with data attributes instead of inline styles ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
11
+ - Timeline bar positioning now uses CSS custom properties set via JavaScript instead of inline styles ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
12
+ - Copy share URL button now uses data attributes and event delegation instead of inline onclick handlers ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
13
+ - Toolbar iframe now uses HTML attributes instead of inline styles ([784f3dd](https://github.com/dropseed/plain/commit/784f3dd972))
14
+
15
+ ### Upgrade instructions
16
+
17
+ - No changes required
18
+
19
+ ## [0.12.0](https://github.com/dropseed/plain/releases/plain-observer@0.12.0) (2025-10-24)
20
+
21
+ ### What's changed
22
+
23
+ - Admin viewsets now use `presets` instead of `displays` for predefined queryset filters ([0ecc60f](https://github.com/dropseed/plain/commit/0ecc60f19e))
24
+ - Removed `logger` field from Log admin interface for simplified display ([ae43138](https://github.com/dropseed/plain/commit/ae43138863))
25
+ - Removed `/admin/.*` from default ignored URL patterns, allowing admin pages to be traced ([daadf1a](https://github.com/dropseed/plain/commit/daadf1a53d))
26
+
27
+ ### Upgrade instructions
28
+
29
+ - If you have custom admin viewsets using the `displays` attribute, rename it to `presets`
30
+ - If you reference the `display` property in custom admin code (e.g., `self.display`), rename it to `self.preset`
31
+
3
32
  ## [0.11.2](https://github.com/dropseed/plain/releases/plain-observer@0.11.2) (2025-10-20)
4
33
 
5
34
  ### 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,25 @@ 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
+ def DEFAULT_RESPONSE_HEADERS(request):
59
+ nonce = request.csp_nonce
60
+ return {
61
+ "Content-Security-Policy": (
62
+ f"default-src 'self'; "
63
+ f"script-src 'self' 'nonce-{nonce}'; "
64
+ f"style-src 'self' 'nonce-{nonce}'; "
65
+ f"frame-ancestors 'self'; " # Required for Observer toolbar
66
+ # ... other directives
67
+ ),
68
+ }
69
+ ```
@@ -53,7 +53,7 @@ class SpanViewset(AdminViewset):
53
53
  ]
54
54
  queryset_order = ["-id"]
55
55
  allow_global_search = False
56
- displays = ["Parents only"]
56
+ presets = ["Parents only"]
57
57
  search_fields = ["name", "span_id", "parent_id"]
58
58
  actions = ["Delete"]
59
59
 
@@ -76,7 +76,7 @@ class SpanViewset(AdminViewset):
76
76
 
77
77
  def get_initial_queryset(self) -> models.QuerySet:
78
78
  queryset = super().get_initial_queryset()
79
- if self.display == "Parents only":
79
+ if self.preset == "Parents only":
80
80
  queryset = queryset.filter(parent_id="")
81
81
  return queryset
82
82
 
@@ -93,15 +93,14 @@ class LogViewset(AdminViewset):
93
93
  fields = [
94
94
  "timestamp",
95
95
  "level",
96
- "logger",
97
96
  "message",
98
97
  "trace",
99
98
  "span",
100
99
  ]
101
100
  queryset_order = ["-timestamp"]
102
101
  allow_global_search = False
103
- search_fields = ["logger", "message", "level"]
104
- filters = ["level", "logger"]
102
+ search_fields = ["message", "level"]
103
+ filters = ["level"]
105
104
  actions = ["Delete selected", "Delete all"]
106
105
 
107
106
  def perform_action(self, action: str, target_ids: Sequence[int]) -> None:
@@ -118,7 +117,6 @@ class LogViewset(AdminViewset):
118
117
  .only(
119
118
  "timestamp",
120
119
  "level",
121
- "logger",
122
120
  "message",
123
121
  "span__span_id",
124
122
  "trace__trace_id",
@@ -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,5 +1,4 @@
1
1
  OBSERVER_IGNORE_URL_PATTERNS: list[str] = [
2
- "/admin/.*",
3
2
  "/assets/.*",
4
3
  "/observer/.*",
5
4
  "/pageviews/.*",
@@ -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.11.2"
3
+ version = "0.13.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