rails_event_viewer 0.3.0 → 0.4.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +42 -0
- data/README.md +8 -0
- data/app/assets/javascripts/rails_event_viewer/application.js +2 -1
- data/app/assets/stylesheets/rails_event_viewer/application.css +1 -1
- data/app/controllers/rails_event_viewer/application_controller.rb +7 -16
- data/app/controllers/rails_event_viewer/events_controller.rb +16 -29
- data/app/controllers/rails_event_viewer/groups_controller.rb +0 -9
- data/app/helpers/rails_event_viewer/application_helper.rb +23 -77
- data/app/models/rails_event_viewer/entry.rb +0 -26
- data/app/models/rails_event_viewer/page.rb +9 -0
- data/app/views/layouts/rails_event_viewer/application.html.erb +11 -4
- data/app/views/rails_event_viewer/analytics/overview.html.erb +13 -9
- data/app/views/rails_event_viewer/dashboard/index.html.erb +16 -15
- data/app/views/rails_event_viewer/event_types/index.html.erb +7 -7
- data/app/views/rails_event_viewer/event_types/show.html.erb +18 -16
- data/app/views/rails_event_viewer/events/_event_row.html.erb +9 -9
- data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +18 -15
- data/app/views/rails_event_viewer/events/index.html.erb +10 -10
- data/app/views/rails_event_viewer/events/show.html.erb +28 -28
- data/app/views/rails_event_viewer/groups/index.html.erb +8 -8
- data/app/views/rails_event_viewer/groups/show.html.erb +12 -16
- data/app/views/rails_event_viewer/shared/_navigation.html.erb +13 -17
- data/app/views/rails_event_viewer/shared/_pagination.html.erb +10 -10
- data/app/views/rails_event_viewer/shared/_stat_card.html.erb +5 -5
- data/config/routes.rb +1 -5
- data/db/migrate/20261002121337_create_rails_event_viewer_entries.rb +0 -1
- data/lib/generators/rails_event_viewer/install/templates/create_rails_event_viewer_entries.rb.erb +0 -1
- data/lib/rails_event_viewer/adapter.rb +0 -8
- data/lib/rails_event_viewer/adapters/active_record.rb +12 -44
- data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +7 -7
- data/lib/rails_event_viewer/adapters/memory.rb +0 -4
- data/lib/rails_event_viewer/adapters/null.rb +0 -8
- data/lib/rails_event_viewer/adapters/redis.rb +6 -20
- data/lib/rails_event_viewer/events_relation.rb +1 -1
- data/lib/rails_event_viewer/flusher.rb +5 -5
- data/lib/rails_event_viewer/logging.rb +1 -11
- data/lib/rails_event_viewer/version.rb +1 -1
- data/lib/rails_event_viewer.rb +5 -5
- data/lib/tasks/rails_event_viewer_tasks.rake +1 -1
- metadata +7 -2
- data/app/assets/stylesheets/rails_event_viewer/tailwind.css +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 935a008c1e9519c485ee47d472d8980a36e1e6e4778e83c76e38629350ed93e9
|
|
4
|
+
data.tar.gz: 9244a13f193475b62847af18366b4e53246a9602f84c13cd00114ec6ead86c72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af5581280410b7b5eb3a79ce61b3dc53ca8dac9ddb083d79554c61f950977319049714334c6fb19713d6aa8c67b38bb63a2bb1dd9042a08a71da7def2bd6b5e1
|
|
7
|
+
data.tar.gz: bc898a58889c8786fcc83c6467084275637c5e27ddc8ee33a233c571d917724b65445f235eebcf3e99be1e2aea70c4eb6a301d764aa5a3bb2ac53ce40be16750
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (2026-10-03)
|
|
4
|
+
|
|
5
|
+
Breaking:
|
|
6
|
+
|
|
7
|
+
- Removed `Adapter#distinct_group_values` and `Adapter#supports_retention?`. Nothing called them.
|
|
8
|
+
- Removed `Entry#source_location`, `#short_filepath`, `#tagged?`, `#has_context?` and `#tag`.
|
|
9
|
+
- The ActiveRecord adapter raises on unknown `adapter_options`, like the other adapters.
|
|
10
|
+
- Times show in the host app's time zone. The engine no longer reads a `timezone` cookie.
|
|
11
|
+
|
|
12
|
+
Changed:
|
|
13
|
+
|
|
14
|
+
- New installs skip the index on `name` alone. The `(name, occurred_at)` index covers it. Existing apps can drop it with `remove_index :rails_event_viewer_entries, :name`.
|
|
15
|
+
- Chartkick is pinned below 6, to match the bundled Chartkick.js.
|
|
16
|
+
- Dashboard scripts load with `defer`.
|
|
17
|
+
|
|
18
|
+
Fixed:
|
|
19
|
+
|
|
20
|
+
- Events with the same timestamp keep a stable order across pages.
|
|
21
|
+
- Pagination renders only the page links it shows, not one loop per page.
|
|
22
|
+
- Related events look one hour either side of the event, so the lookup no longer scans the whole table.
|
|
23
|
+
- Form fields have labels, tables have column headers, and the current page is marked for screen readers. Grey text has more contrast.
|
|
24
|
+
|
|
25
|
+
## 0.3.1 (2026-10-03)
|
|
26
|
+
|
|
27
|
+
Changed:
|
|
28
|
+
|
|
29
|
+
- The nav search goes to the events list and works together with the filters. The `/events/search` page is removed.
|
|
30
|
+
- The Analytics link is hidden when the adapter has no analytics.
|
|
31
|
+
- The group page no longer shows an Event Types count. It counted only the current page.
|
|
32
|
+
|
|
33
|
+
Fixed:
|
|
34
|
+
|
|
35
|
+
- Array, hash and invalid values in query params are ignored instead of raising.
|
|
36
|
+
- Event rows contain a real link, so keyboard and new-tab clicks work. Selecting text in a row no longer opens the event.
|
|
37
|
+
- `rails_event_viewer:clear` works with the ActiveRecord adapter.
|
|
38
|
+
- Redis keeps event times to the microsecond, and `delete_before` keeps an event at the exact cutoff.
|
|
39
|
+
- Payloads show `&`, `<` and `>` instead of unicode escapes.
|
|
40
|
+
- A banner explains a missing events table. A filtered empty list says no events match.
|
|
41
|
+
- Pages fit narrow screens, and long names and payloads wrap.
|
|
42
|
+
- Charts render when the host app sets its own Chartkick `content_for`.
|
|
43
|
+
- `with_name` accepts a symbol.
|
|
44
|
+
|
|
3
45
|
## 0.3.0 (2026-10-02)
|
|
4
46
|
|
|
5
47
|
Breaking:
|
data/README.md
CHANGED
|
@@ -8,6 +8,9 @@ A Rails engine that captures events emitted with `Rails.event` and gives you a d
|
|
|
8
8
|
|
|
9
9
|
- Rails 8.1+
|
|
10
10
|
- Ruby 3.3+
|
|
11
|
+
- SQLite, PostgreSQL, or MySQL 8 for the default ActiveRecord adapter
|
|
12
|
+
|
|
13
|
+
API-only apps need an asset pipeline to serve the dashboard's CSS and JavaScript. Add `gem "propshaft"`, and run `assets:precompile` when you deploy.
|
|
11
14
|
|
|
12
15
|
## Installation
|
|
13
16
|
|
|
@@ -43,6 +46,7 @@ RailsEventViewer.events
|
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
Other filters: `with_context`, `search`, `until`, `offset`, and `count`.
|
|
49
|
+
Without `limit`, a query returns `config.per_page` events, 25 by default.
|
|
46
50
|
|
|
47
51
|
## Configuration
|
|
48
52
|
|
|
@@ -90,6 +94,8 @@ config.authentication = ->(controller) {
|
|
|
90
94
|
|
|
91
95
|
Set `RAILS_ENV` on every server. Without it, Rails falls back to development and the dashboard is open.
|
|
92
96
|
|
|
97
|
+
If both are set, HTTP Basic Auth is used and `config.authentication` is ignored.
|
|
98
|
+
|
|
93
99
|
## Storage adapters
|
|
94
100
|
|
|
95
101
|
Set `config.storage_adapter` to `:active_record` (default), `:redis`, `:memory`, or `:null`. Redis needs the `redis` gem and takes options through `config.adapter_options`.
|
|
@@ -127,6 +133,8 @@ production:
|
|
|
127
133
|
|
|
128
134
|
## Development
|
|
129
135
|
|
|
136
|
+
The dashboard ships Chart.js 4.4.1, chartjs-adapter-date-fns 3.0.0 and Chartkick.js 5.0.1.
|
|
137
|
+
|
|
130
138
|
```bash
|
|
131
139
|
bundle install
|
|
132
140
|
bundle exec rake test
|
|
@@ -22,7 +22,8 @@ document.addEventListener("click", (event) => {
|
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
const row = event.target.closest("[data-event-viewer-href]")
|
|
25
|
-
|
|
25
|
+
const selectingText = window.getSelection().toString() !== ""
|
|
26
|
+
if (row && !selectingText && !event.target.closest("a, button")) {
|
|
26
27
|
window.location = row.dataset.eventViewerHref
|
|
27
28
|
}
|
|
28
29
|
})
|
|
@@ -1 +1 @@
|
|
|
1
|
-
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.left-0{left:0}.left-10{left:2.5rem}.left-12{left:3rem}.top-0{top:0}.top-4{top:1rem}.top-6{top:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-12{height:3rem}.h-16{height:4rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-full{height:100%}.min-h-full{min-height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1\/5{width:20%}.w-12{width:3rem}.w-2\/5{width:40%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-72{width:18rem}.w-9{width:2.25rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.table-fixed{table-layout:fixed}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.border-indigo-600{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity,1))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity,1))}.bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-10{padding-left:2.5rem}.pl-16{padding-left:4rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-6{padding-right:1.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-indigo-200{--tw-text-opacity:1;color:rgb(199 210 254/var(--tw-text-opacity,1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity,1))}.text-indigo-800{--tw-text-opacity:1;color:rgb(55 48 163/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.placeholder-indigo-200::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.placeholder-indigo-200::placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.filter{filter:var(--tw-blur) var(--tw-brightness) var(--tw-contrast) var(--tw-grayscale) var(--tw-hue-rotate) var(--tw-invert) var(--tw-saturate) var(--tw-sepia) var(--tw-drop-shadow)}.json-tree{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace;font-size:.875rem}.json-tree-key{color:#4b5563;font-weight:500}.json-tree-string{color:#059669}.json-tree-number{color:#2563eb}.json-tree-boolean{color:#7c3aed}.json-tree-null{color:#9ca3af}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.hover\:text-gray-600:hover{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.hover\:text-indigo-500:hover{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.focus\:text-gray-900:focus{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.focus\:placeholder-gray-400:focus::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:placeholder-gray-400:focus::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:px-8{padding-left:2rem;padding-right:2rem}}
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.left-0{left:0}.left-10{left:2.5rem}.left-12{left:3rem}.top-0{top:0}.top-4{top:1rem}.top-6{top:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-12{height:3rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-full{height:100%}.min-h-16{min-height:4rem}.min-h-full{min-height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1\/5{width:20%}.w-12{width:3rem}.w-2\/5{width:40%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-9{width:2.25rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.table-fixed{table-layout:fixed}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-y-2{row-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.border-indigo-600{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity,1))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity,1))}.bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-10{padding-left:2.5rem}.pl-16{padding-left:4rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-6{padding-right:1.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-indigo-200{--tw-text-opacity:1;color:rgb(199 210 254/var(--tw-text-opacity,1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity,1))}.text-indigo-800{--tw-text-opacity:1;color:rgb(55 48 163/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.placeholder-indigo-200::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.placeholder-indigo-200::placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.hover\:text-indigo-500:hover{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.focus\:text-gray-900:focus{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.focus\:placeholder-gray-400:focus::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:placeholder-gray-400:focus::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:w-72{width:18rem}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:px-8{padding-left:2rem;padding-right:2rem}}
|
|
@@ -3,9 +3,8 @@ module RailsEventViewer
|
|
|
3
3
|
layout "rails_event_viewer/application"
|
|
4
4
|
|
|
5
5
|
before_action :authenticate!
|
|
6
|
-
around_action :use_viewer_time_zone
|
|
7
6
|
|
|
8
|
-
helper_method :
|
|
7
|
+
helper_method :current_adapter, :analytics_supported?, :persistence_supported?
|
|
9
8
|
|
|
10
9
|
private
|
|
11
10
|
|
|
@@ -37,28 +36,20 @@ module RailsEventViewer
|
|
|
37
36
|
render plain: "Unauthorized", status: :unauthorized unless auth.call(self) || performed?
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
def use_viewer_time_zone(&block)
|
|
41
|
-
zone = ActiveSupport::TimeZone[cookies[:timezone].to_s] || ActiveSupport::TimeZone["UTC"]
|
|
42
|
-
Time.use_zone(zone, &block)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
39
|
def string_param(key)
|
|
46
40
|
value = params[key]
|
|
47
41
|
value if value.is_a?(String) && !value.empty?
|
|
48
42
|
end
|
|
49
43
|
|
|
50
44
|
def safe_parse_date(date_string, default: nil)
|
|
51
|
-
return default
|
|
45
|
+
return default unless date_string.is_a?(String) && date_string.present?
|
|
52
46
|
|
|
53
|
-
Date.parse(date_string)
|
|
54
|
-
|
|
47
|
+
date = Date.parse(date_string)
|
|
48
|
+
date.year.between?(1000, 9999) ? date : default
|
|
49
|
+
rescue ArgumentError
|
|
55
50
|
default
|
|
56
51
|
end
|
|
57
52
|
|
|
58
|
-
def current_timezone
|
|
59
|
-
Time.zone.name
|
|
60
|
-
end
|
|
61
|
-
|
|
62
53
|
def current_adapter
|
|
63
54
|
RailsEventViewer.adapter
|
|
64
55
|
end
|
|
@@ -72,11 +63,11 @@ module RailsEventViewer
|
|
|
72
63
|
end
|
|
73
64
|
|
|
74
65
|
def per_page
|
|
75
|
-
(
|
|
66
|
+
(string_param(:per_page) || RailsEventViewer.per_page).to_i.clamp(1, 100)
|
|
76
67
|
end
|
|
77
68
|
|
|
78
69
|
def paginate(relation)
|
|
79
|
-
pagination = Page.new(count: relation.count, per_page: per_page, page:
|
|
70
|
+
pagination = Page.new(count: relation.count, per_page: per_page, page: string_param(:page))
|
|
80
71
|
[pagination, relation.offset(pagination.offset).limit(pagination.per_page).to_a]
|
|
81
72
|
end
|
|
82
73
|
end
|
|
@@ -2,6 +2,7 @@ module RailsEventViewer
|
|
|
2
2
|
class EventsController < ApplicationController
|
|
3
3
|
def index
|
|
4
4
|
relation = apply_filters(RailsEventViewer.events)
|
|
5
|
+
@filtered = relation.filtered?
|
|
5
6
|
@pagination, @events = paginate(relation)
|
|
6
7
|
@event_names = current_adapter.distinct_event_names
|
|
7
8
|
end
|
|
@@ -17,55 +18,41 @@ module RailsEventViewer
|
|
|
17
18
|
@related_events = find_related_events(@event)
|
|
18
19
|
end
|
|
19
20
|
|
|
20
|
-
def search
|
|
21
|
-
@query = params[:q]
|
|
22
|
-
relation = RailsEventViewer.events.search(@query)
|
|
23
|
-
@pagination, @events = paginate(relation)
|
|
24
|
-
@event_names = current_adapter.distinct_event_names
|
|
25
|
-
|
|
26
|
-
render :index
|
|
27
|
-
end
|
|
28
|
-
|
|
29
21
|
private
|
|
30
22
|
|
|
31
23
|
def apply_filters(relation)
|
|
32
|
-
|
|
24
|
+
name = string_param(:name)
|
|
25
|
+
tag_key = string_param(:tag_key)
|
|
26
|
+
context_key = string_param(:context_key)
|
|
27
|
+
@query = string_param(:q)
|
|
33
28
|
|
|
34
29
|
@start_date = safe_parse_date(params[:start_date])
|
|
35
30
|
@end_date = safe_parse_date(params[:end_date])
|
|
31
|
+
|
|
32
|
+
relation = relation.with_name(name) if name
|
|
36
33
|
relation = relation.since(@start_date.beginning_of_day) if @start_date
|
|
37
34
|
relation = relation.until(@end_date.end_of_day) if @end_date
|
|
38
|
-
|
|
39
|
-
if
|
|
40
|
-
|
|
41
|
-
end
|
|
42
|
-
|
|
43
|
-
if params[:context_key].present?
|
|
44
|
-
relation = relation.with_context(params[:context_key], params[:context_value].presence)
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
if params[:q].present?
|
|
48
|
-
relation = relation.search(params[:q])
|
|
49
|
-
end
|
|
50
|
-
|
|
35
|
+
relation = relation.with_tag(tag_key, string_param(:tag_value)) if tag_key
|
|
36
|
+
relation = relation.with_context(context_key, string_param(:context_value)) if context_key
|
|
37
|
+
relation = relation.search(@query) if @query
|
|
51
38
|
relation
|
|
52
39
|
end
|
|
53
40
|
|
|
54
41
|
def find_related_events(event)
|
|
55
|
-
context = event
|
|
42
|
+
context = event[:context]
|
|
56
43
|
return [] unless context.present?
|
|
57
44
|
|
|
58
|
-
# Find events with matching request_id in context
|
|
59
45
|
request_id = context["request_id"] || context[:request_id]
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
event_id = event.respond_to?(:id) ? event.id : event[:id]
|
|
46
|
+
occurred_at = event[:occurred_at]
|
|
47
|
+
return [] unless request_id && occurred_at
|
|
63
48
|
|
|
64
49
|
RailsEventViewer.events
|
|
65
50
|
.with_context("request_id", request_id)
|
|
51
|
+
.since(occurred_at - 1.hour)
|
|
52
|
+
.until(occurred_at + 1.hour)
|
|
66
53
|
.limit(11)
|
|
67
54
|
.to_a
|
|
68
|
-
.reject { |
|
|
55
|
+
.reject { |related| related[:id] == event[:id] }
|
|
69
56
|
.first(10)
|
|
70
57
|
end
|
|
71
58
|
end
|
|
@@ -31,7 +31,6 @@ module RailsEventViewer
|
|
|
31
31
|
|
|
32
32
|
@pagination, @events = paginate(events)
|
|
33
33
|
@total_count = @pagination.count
|
|
34
|
-
@event_names = @events.map { |e| event_name(e) }.uniq
|
|
35
34
|
|
|
36
35
|
@first_event_at, @last_event_at = current_adapter.event_time_span(events)
|
|
37
36
|
@duration = @last_event_at - @first_event_at if @first_event_at && @last_event_at
|
|
@@ -42,13 +41,5 @@ module RailsEventViewer
|
|
|
42
41
|
def source_param
|
|
43
42
|
params[:source].presence_in(%w[context tags])&.to_sym || :context
|
|
44
43
|
end
|
|
45
|
-
|
|
46
|
-
def event_name(event)
|
|
47
|
-
event.respond_to?(:name) ? event.name : event[:name]
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def event_occurred_at(event)
|
|
51
|
-
event.respond_to?(:occurred_at) ? event.occurred_at : event[:occurred_at]
|
|
52
|
-
end
|
|
53
44
|
end
|
|
54
45
|
end
|
|
@@ -1,101 +1,55 @@
|
|
|
1
1
|
module RailsEventViewer
|
|
2
2
|
module ApplicationHelper
|
|
3
|
-
def
|
|
4
|
-
|
|
3
|
+
def nav_link(label, path, controller)
|
|
4
|
+
active = controller_name == controller
|
|
5
|
+
state_classes = active ? "bg-indigo-700 text-white" : "text-indigo-200 hover:bg-indigo-500 hover:text-white"
|
|
5
6
|
|
|
6
|
-
if
|
|
7
|
-
"#{base_classes} bg-indigo-700 text-white"
|
|
8
|
-
else
|
|
9
|
-
"#{base_classes} text-indigo-200 hover:bg-indigo-500 hover:text-white"
|
|
10
|
-
end
|
|
7
|
+
link_to label, path, class: "rounded-md px-3 py-2 text-sm font-medium #{state_classes}", aria: { current: ("page" if active) }
|
|
11
8
|
end
|
|
12
9
|
|
|
13
|
-
# Format event time - works with both Entry model and Hash
|
|
14
10
|
def format_event_time(event)
|
|
15
|
-
time =
|
|
11
|
+
time = event[:occurred_at]
|
|
16
12
|
return "N/A" unless time
|
|
17
13
|
|
|
18
|
-
time.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
14
|
+
time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
19
15
|
end
|
|
20
16
|
|
|
21
17
|
def time_ago_with_title(time)
|
|
22
18
|
return "N/A" unless time
|
|
23
19
|
|
|
24
|
-
content_tag(:span, time_ago_in_words(time) + " ago", title: time.strftime("%Y-%m-%d %H:%M:%S %Z"))
|
|
20
|
+
content_tag(:span, time_ago_in_words(time) + " ago", title: time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S %Z"))
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
def truncate_json(json, length: 100)
|
|
28
|
-
text = json.is_a?(Hash) ? json
|
|
24
|
+
text = json.is_a?(Hash) ? JSON.generate(json) : json.to_s
|
|
29
25
|
truncate(text, length: length)
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
def json_tree(data
|
|
33
|
-
return content_tag(:span, "null", class: "text-gray-
|
|
34
|
-
return content_tag(:span, data
|
|
35
|
-
return content_tag(:span, collapsed_summary(data), class: "text-gray-400") if max_depth && depth >= max_depth
|
|
28
|
+
def json_tree(data)
|
|
29
|
+
return content_tag(:span, "null", class: "text-gray-500") if data.nil?
|
|
30
|
+
return content_tag(:span, JSON.generate(data), class: json_value_class(data)) unless data.is_a?(Hash) || data.is_a?(Array)
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
render_hash_tree(data, depth, max_depth)
|
|
39
|
-
else
|
|
40
|
-
render_array_tree(data, depth, max_depth)
|
|
41
|
-
end
|
|
32
|
+
data.is_a?(Hash) ? render_hash_tree(data) : render_array_tree(data)
|
|
42
33
|
end
|
|
43
34
|
|
|
44
35
|
def page_path(page)
|
|
45
36
|
"#{request.path}?#{request.query_parameters.merge("page" => page.to_s).to_query}"
|
|
46
37
|
end
|
|
47
38
|
|
|
48
|
-
def timezone_options
|
|
49
|
-
ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.name] }
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Helper to get attribute from either Entry model or Hash
|
|
53
|
-
def event_attribute(event, attr)
|
|
54
|
-
if event.respond_to?(attr)
|
|
55
|
-
event.public_send(attr)
|
|
56
|
-
elsif event.is_a?(Hash)
|
|
57
|
-
event[attr] || event[attr.to_s]
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def event_id(event)
|
|
62
|
-
event_attribute(event, :id)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def event_name(event)
|
|
66
|
-
event_attribute(event, :name)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
39
|
def event_payload(event)
|
|
70
|
-
|
|
40
|
+
event[:payload] || {}
|
|
71
41
|
end
|
|
72
42
|
|
|
73
43
|
def event_tags(event)
|
|
74
|
-
|
|
44
|
+
event[:tags] || {}
|
|
75
45
|
end
|
|
76
46
|
|
|
77
47
|
def event_context(event)
|
|
78
|
-
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def event_occurred_at(event)
|
|
82
|
-
event_attribute(event, :occurred_at)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def event_source_file(event)
|
|
86
|
-
event_attribute(event, :source_file)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def event_source_line(event)
|
|
90
|
-
event_attribute(event, :source_line)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def event_source_label(event)
|
|
94
|
-
event_attribute(event, :source_label)
|
|
48
|
+
event[:context] || {}
|
|
95
49
|
end
|
|
96
50
|
|
|
97
51
|
def event_short_filepath(event)
|
|
98
|
-
filepath =
|
|
52
|
+
filepath = event[:source_file]
|
|
99
53
|
return nil unless filepath
|
|
100
54
|
|
|
101
55
|
filepath.gsub(%r{.*/app/}, "app/")
|
|
@@ -130,35 +84,27 @@ module RailsEventViewer
|
|
|
130
84
|
end
|
|
131
85
|
end
|
|
132
86
|
|
|
133
|
-
def
|
|
134
|
-
if
|
|
135
|
-
data.empty? ? "{}" : "{#{data.size} #{"key".pluralize(data.size)}}"
|
|
136
|
-
else
|
|
137
|
-
data.empty? ? "[]" : "[#{data.size} #{"item".pluralize(data.size)}]"
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def render_hash_tree(hash, depth, max_depth)
|
|
142
|
-
return content_tag(:span, "{}", class: "text-gray-400") if hash.empty?
|
|
87
|
+
def render_hash_tree(hash)
|
|
88
|
+
return content_tag(:span, "{}", class: "text-gray-500") if hash.empty?
|
|
143
89
|
|
|
144
90
|
content_tag(:div, class: "pl-4") do
|
|
145
91
|
hash.map do |key, value|
|
|
146
92
|
content_tag(:div, class: "py-0.5") do
|
|
147
93
|
content_tag(:span, "#{key}:", class: "text-gray-600 font-medium mr-2") +
|
|
148
|
-
json_tree(value
|
|
94
|
+
json_tree(value)
|
|
149
95
|
end
|
|
150
96
|
end.join.html_safe
|
|
151
97
|
end
|
|
152
98
|
end
|
|
153
99
|
|
|
154
|
-
def render_array_tree(array
|
|
155
|
-
return content_tag(:span, "[]", class: "text-gray-
|
|
100
|
+
def render_array_tree(array)
|
|
101
|
+
return content_tag(:span, "[]", class: "text-gray-500") if array.empty?
|
|
156
102
|
|
|
157
103
|
content_tag(:div, class: "pl-4") do
|
|
158
104
|
array.each_with_index.map do |value, index|
|
|
159
105
|
content_tag(:div, class: "py-0.5") do
|
|
160
|
-
content_tag(:span, "[#{index}]:", class: "text-gray-
|
|
161
|
-
json_tree(value
|
|
106
|
+
content_tag(:span, "[#{index}]:", class: "text-gray-500 mr-2") +
|
|
107
|
+
json_tree(value)
|
|
162
108
|
end
|
|
163
109
|
end.join.html_safe
|
|
164
110
|
end
|
|
@@ -4,31 +4,5 @@ module RailsEventViewer
|
|
|
4
4
|
|
|
5
5
|
validates :name, presence: true
|
|
6
6
|
validates :occurred_at, presence: true
|
|
7
|
-
|
|
8
|
-
def source_location
|
|
9
|
-
{
|
|
10
|
-
filepath: source_file,
|
|
11
|
-
lineno: source_line,
|
|
12
|
-
label: source_label
|
|
13
|
-
}.compact
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def short_filepath
|
|
17
|
-
return nil unless source_file
|
|
18
|
-
|
|
19
|
-
source_file.gsub(%r{.*/app/}, "app/")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def tagged?
|
|
23
|
-
tags.present? && tags.any?
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def has_context?
|
|
27
|
-
context.present? && context.any?
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def tag(key)
|
|
31
|
-
tags&.dig(key.to_s)
|
|
32
|
-
end
|
|
33
7
|
end
|
|
34
8
|
end
|
|
@@ -31,5 +31,14 @@ module RailsEventViewer
|
|
|
31
31
|
def next
|
|
32
32
|
page + 1 if page < pages
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
def window
|
|
36
|
+
numbers = [1, *(page - 2..page + 2), pages].select { |number| number.between?(1, pages) }.uniq.sort
|
|
37
|
+
|
|
38
|
+
numbers.each_with_object([]) do |number, items|
|
|
39
|
+
items << :gap if items.any? && number > items.last + 1
|
|
40
|
+
items << number
|
|
41
|
+
end
|
|
42
|
+
end
|
|
34
43
|
end
|
|
35
44
|
end
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
"rails_event_viewer/chartjs-adapter-date-fns.bundle.min",
|
|
12
12
|
"rails_event_viewer/chartkick.min",
|
|
13
13
|
"rails_event_viewer/application",
|
|
14
|
+
defer: true,
|
|
14
15
|
nonce: true %>
|
|
15
|
-
|
|
16
|
-
<%= yield :head %>
|
|
17
16
|
</head>
|
|
18
17
|
<body class="h-full">
|
|
19
18
|
<div class="min-h-full">
|
|
@@ -22,17 +21,25 @@
|
|
|
22
21
|
<main class="py-6">
|
|
23
22
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
24
23
|
<% if notice.present? %>
|
|
25
|
-
<div class="mb-4 rounded-md bg-green-50 p-4">
|
|
24
|
+
<div class="mb-4 rounded-md bg-green-50 p-4" role="status">
|
|
26
25
|
<p class="text-sm font-medium text-green-800"><%= notice %></p>
|
|
27
26
|
</div>
|
|
28
27
|
<% end %>
|
|
29
28
|
|
|
30
29
|
<% if alert.present? %>
|
|
31
|
-
<div class="mb-4 rounded-md bg-red-50 p-4">
|
|
30
|
+
<div class="mb-4 rounded-md bg-red-50 p-4" role="alert">
|
|
32
31
|
<p class="text-sm font-medium text-red-800"><%= alert %></p>
|
|
33
32
|
</div>
|
|
34
33
|
<% end %>
|
|
35
34
|
|
|
35
|
+
<% if RailsEventViewer.storage_adapter == :active_record && !persistence_supported? %>
|
|
36
|
+
<div class="mb-4 rounded-md bg-red-50 p-4" role="alert">
|
|
37
|
+
<p class="text-sm font-medium text-red-800">
|
|
38
|
+
The events table is missing, so no events are being saved. Run <code>bin/rails generate rails_event_viewer:install</code> and <code>bin/rails db:migrate</code>, then restart the app.
|
|
39
|
+
</p>
|
|
40
|
+
</div>
|
|
41
|
+
<% end %>
|
|
42
|
+
|
|
36
43
|
<% if RailsEventViewer.sample_rate.to_f < 1.0 %>
|
|
37
44
|
<div class="mb-4 rounded-md bg-yellow-50 border border-yellow-200 p-4">
|
|
38
45
|
<p class="text-sm font-medium text-yellow-800">
|