rails_event_viewer 0.3.0 → 0.3.1
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 +20 -0
- data/README.md +3 -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 +6 -5
- data/app/controllers/rails_event_viewer/events_controller.rb +10 -23
- data/app/controllers/rails_event_viewer/groups_controller.rb +0 -9
- data/app/helpers/rails_event_viewer/application_helper.rb +2 -2
- data/app/views/layouts/rails_event_viewer/application.html.erb +8 -0
- data/app/views/rails_event_viewer/analytics/overview.html.erb +2 -0
- data/app/views/rails_event_viewer/dashboard/index.html.erb +4 -3
- data/app/views/rails_event_viewer/event_types/index.html.erb +1 -1
- data/app/views/rails_event_viewer/event_types/show.html.erb +5 -3
- data/app/views/rails_event_viewer/events/_event_row.html.erb +3 -3
- data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +2 -1
- data/app/views/rails_event_viewer/events/index.html.erb +4 -4
- data/app/views/rails_event_viewer/events/show.html.erb +7 -7
- data/app/views/rails_event_viewer/groups/index.html.erb +1 -1
- data/app/views/rails_event_viewer/groups/show.html.erb +1 -5
- data/app/views/rails_event_viewer/shared/_navigation.html.erb +7 -5
- data/config/routes.rb +1 -5
- data/lib/rails_event_viewer/adapters/active_record.rb +4 -0
- data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +7 -0
- data/lib/rails_event_viewer/adapters/redis.rb +3 -3
- data/lib/rails_event_viewer/events_relation.rb +1 -1
- data/lib/rails_event_viewer/version.rb +1 -1
- data/lib/tasks/rails_event_viewer_tasks.rake +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 876a5c4676cfb1f5512d82e73464c1e69a19bd9b1a1b5c2854f93aef7c412a60
|
|
4
|
+
data.tar.gz: 766e263c994dc9552472e065e1abaea18064cc08858c45ef524148923693922f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b3b9d72c97349b344106c874be9988e605c05efda5cafdb24e8230603830d313cb3a1b2fb72722a59ce6a27dab43e08751abebcd39606fd23ff2337c84392eac
|
|
7
|
+
data.tar.gz: 4e0cbc54525cdaa6b177832bd53e7207d1f3fc4c0a6167c3da250e85b00bea27bdbf1073e7a555b65887514bcc6e4e1ccdfe18b53c8a6fd1ffd49b74c82511a0
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.3.1 (2026-10-03)
|
|
4
|
+
|
|
5
|
+
Changed:
|
|
6
|
+
|
|
7
|
+
- The nav search goes to the events list and works together with the filters. The `/events/search` page is removed.
|
|
8
|
+
- The Analytics link is hidden when the adapter has no analytics.
|
|
9
|
+
- The group page no longer shows an Event Types count. It counted only the current page.
|
|
10
|
+
|
|
11
|
+
Fixed:
|
|
12
|
+
|
|
13
|
+
- Array, hash and invalid values in query params are ignored instead of raising.
|
|
14
|
+
- Event rows contain a real link, so keyboard and new-tab clicks work. Selecting text in a row no longer opens the event.
|
|
15
|
+
- `rails_event_viewer:clear` works with the ActiveRecord adapter.
|
|
16
|
+
- Redis keeps event times to the microsecond, and `delete_before` keeps an event at the exact cutoff.
|
|
17
|
+
- Payloads show `&`, `<` and `>` instead of unicode escapes.
|
|
18
|
+
- A banner explains a missing events table. A filtered empty list says no events match.
|
|
19
|
+
- Pages fit narrow screens, and long names and payloads wrap.
|
|
20
|
+
- Charts render when the host app sets its own Chartkick `content_for`.
|
|
21
|
+
- `with_name` accepts a symbol.
|
|
22
|
+
|
|
3
23
|
## 0.3.0 (2026-10-02)
|
|
4
24
|
|
|
5
25
|
Breaking:
|
data/README.md
CHANGED
|
@@ -43,6 +43,7 @@ RailsEventViewer.events
|
|
|
43
43
|
```
|
|
44
44
|
|
|
45
45
|
Other filters: `with_context`, `search`, `until`, `offset`, and `count`.
|
|
46
|
+
Without `limit`, a query returns `config.per_page` events, 25 by default.
|
|
46
47
|
|
|
47
48
|
## Configuration
|
|
48
49
|
|
|
@@ -90,6 +91,8 @@ config.authentication = ->(controller) {
|
|
|
90
91
|
|
|
91
92
|
Set `RAILS_ENV` on every server. Without it, Rails falls back to development and the dashboard is open.
|
|
92
93
|
|
|
94
|
+
If both are set, HTTP Basic Auth is used and `config.authentication` is ignored.
|
|
95
|
+
|
|
93
96
|
## Storage adapters
|
|
94
97
|
|
|
95
98
|
Set `config.storage_adapter` to `:active_record` (default), `:redis`, `:memory`, or `:null`. Redis needs the `redis` gem and takes options through `config.adapter_options`.
|
|
@@ -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}.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)}.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\: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}}
|
|
@@ -48,10 +48,11 @@ module RailsEventViewer
|
|
|
48
48
|
end
|
|
49
49
|
|
|
50
50
|
def safe_parse_date(date_string, default: nil)
|
|
51
|
-
return default
|
|
51
|
+
return default unless date_string.is_a?(String) && date_string.present?
|
|
52
52
|
|
|
53
|
-
Date.parse(date_string)
|
|
54
|
-
|
|
53
|
+
date = Date.parse(date_string)
|
|
54
|
+
date.year.between?(1000, 9999) ? date : default
|
|
55
|
+
rescue ArgumentError
|
|
55
56
|
default
|
|
56
57
|
end
|
|
57
58
|
|
|
@@ -72,11 +73,11 @@ module RailsEventViewer
|
|
|
72
73
|
end
|
|
73
74
|
|
|
74
75
|
def per_page
|
|
75
|
-
(
|
|
76
|
+
(string_param(:per_page) || RailsEventViewer.per_page).to_i.clamp(1, 100)
|
|
76
77
|
end
|
|
77
78
|
|
|
78
79
|
def paginate(relation)
|
|
79
|
-
pagination = Page.new(count: relation.count, per_page: per_page, page:
|
|
80
|
+
pagination = Page.new(count: relation.count, per_page: per_page, page: string_param(:page))
|
|
80
81
|
[pagination, relation.offset(pagination.offset).limit(pagination.per_page).to_a]
|
|
81
82
|
end
|
|
82
83
|
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,37 +18,23 @@ 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
|
|
|
@@ -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
|
|
@@ -25,13 +25,13 @@ module RailsEventViewer
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def truncate_json(json, length: 100)
|
|
28
|
-
text = json.is_a?(Hash) ? json
|
|
28
|
+
text = json.is_a?(Hash) ? JSON.generate(json) : json.to_s
|
|
29
29
|
truncate(text, length: length)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
32
|
def json_tree(data, depth: 0, max_depth: nil)
|
|
33
33
|
return content_tag(:span, "null", class: "text-gray-400") if data.nil?
|
|
34
|
-
return content_tag(:span, data
|
|
34
|
+
return content_tag(:span, JSON.generate(data), class: json_value_class(data)) unless data.is_a?(Hash) || data.is_a?(Array)
|
|
35
35
|
return content_tag(:span, collapsed_summary(data), class: "text-gray-400") if max_depth && depth >= max_depth
|
|
36
36
|
|
|
37
37
|
if data.is_a?(Hash)
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
</div>
|
|
34
34
|
<% end %>
|
|
35
35
|
|
|
36
|
+
<% if RailsEventViewer.storage_adapter == :active_record && !persistence_supported? %>
|
|
37
|
+
<div class="mb-4 rounded-md bg-red-50 p-4" role="alert">
|
|
38
|
+
<p class="text-sm font-medium text-red-800">
|
|
39
|
+
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.
|
|
40
|
+
</p>
|
|
41
|
+
</div>
|
|
42
|
+
<% end %>
|
|
43
|
+
|
|
36
44
|
<% if RailsEventViewer.sample_rate.to_f < 1.0 %>
|
|
37
45
|
<div class="mb-4 rounded-md bg-yellow-50 border border-yellow-200 p-4">
|
|
38
46
|
<p class="text-sm font-medium text-yellow-800">
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
<%= line_chart @events_over_time,
|
|
48
48
|
id: "analytics-timeline",
|
|
49
49
|
html: '<div id="%{id}" class="h-full w-full">%{loading}</div>',
|
|
50
|
+
content_for: nil,
|
|
50
51
|
colors: ["#4F46E5"],
|
|
51
52
|
library: {
|
|
52
53
|
plugins: { legend: { display: false } },
|
|
@@ -67,6 +68,7 @@
|
|
|
67
68
|
<%= bar_chart @events_by_type,
|
|
68
69
|
id: "analytics-by-type",
|
|
69
70
|
html: '<div id="%{id}" class="h-full w-full">%{loading}</div>',
|
|
71
|
+
content_for: nil,
|
|
70
72
|
colors: ["#4F46E5"],
|
|
71
73
|
library: {
|
|
72
74
|
indexAxis: 'y',
|
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
<%= line_chart @events_over_time,
|
|
35
35
|
id: "events-timeline",
|
|
36
36
|
html: '<div id="%{id}" class="h-full w-full">%{loading}</div>',
|
|
37
|
+
content_for: nil,
|
|
37
38
|
colors: ["#4F46E5"],
|
|
38
39
|
library: { plugins: { legend: { display: false } } } %>
|
|
39
40
|
<% else %>
|
|
@@ -52,6 +53,7 @@
|
|
|
52
53
|
<%= pie_chart @events_by_type,
|
|
53
54
|
id: "events-by-type",
|
|
54
55
|
html: '<div id="%{id}" class="h-full w-full">%{loading}</div>',
|
|
56
|
+
content_for: nil,
|
|
55
57
|
donut: true,
|
|
56
58
|
colors: ["#4F46E5", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"] %>
|
|
57
59
|
<% else %>
|
|
@@ -85,9 +87,8 @@
|
|
|
85
87
|
<% @recent_events.each do |event| %>
|
|
86
88
|
<tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(event_id(event)) %>">
|
|
87
89
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</span>
|
|
90
|
+
<%= link_to event_name(event), rails_event_viewer.event_path(event_id(event)),
|
|
91
|
+
class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800" %>
|
|
91
92
|
</td>
|
|
92
93
|
<td class="px-6 py-4 text-sm text-gray-500">
|
|
93
94
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 60) %></code>
|
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
10
10
|
</svg>
|
|
11
11
|
<% end %>
|
|
12
|
-
<div>
|
|
12
|
+
<div class="min-w-0">
|
|
13
13
|
<h1 class="text-2xl font-bold text-gray-900">
|
|
14
|
-
<span class="inline-
|
|
14
|
+
<span class="inline-block max-w-full break-words px-3 py-1 rounded-full text-xl font-medium bg-indigo-100 text-indigo-800">
|
|
15
15
|
<%= @event_type_name %>
|
|
16
16
|
</span>
|
|
17
17
|
</h1>
|
|
@@ -56,7 +56,9 @@
|
|
|
56
56
|
<% @events.each do |event| %>
|
|
57
57
|
<tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(event_id(event)) %>">
|
|
58
58
|
<td class="px-6 py-4">
|
|
59
|
-
|
|
59
|
+
<%= link_to rails_event_viewer.event_path(event_id(event)) do %>
|
|
60
|
+
<code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 80) %></code>
|
|
61
|
+
<% end %>
|
|
60
62
|
</td>
|
|
61
63
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
62
64
|
<% tags = event_tags(event) %>
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(event_id(event)) %>">
|
|
2
2
|
<td class="px-6 py-4">
|
|
3
3
|
<div class="flex flex-col max-w-md">
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
<%= link_to event_name(event), rails_event_viewer.event_path(event_id(event)),
|
|
5
|
+
title: event_name(event),
|
|
6
|
+
class: "inline-block max-w-full truncate px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800 w-fit" %>
|
|
7
7
|
<div class="text-sm text-gray-500 mt-1 truncate">
|
|
8
8
|
<code class="text-xs"><%= truncate_json(event_payload(event), length: 50) %></code>
|
|
9
9
|
</div>
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
<div class="bg-white rounded-lg shadow p-5 space-y-5">
|
|
2
2
|
<h3 class="font-medium text-gray-900">Filters</h3>
|
|
3
3
|
|
|
4
|
-
<%= form_with url: rails_event_viewer.events_path, method: :get,
|
|
4
|
+
<%= form_with url: rails_event_viewer.events_path, method: :get, class: "space-y-5" do |f| %>
|
|
5
|
+
<%= f.hidden_field :q, value: @query if @query %>
|
|
5
6
|
|
|
6
7
|
<!-- Event name filter -->
|
|
7
8
|
<div>
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
<% end %>
|
|
12
12
|
</h1>
|
|
13
13
|
<div class="text-sm text-gray-500">
|
|
14
|
-
<%= @pagination.count %>
|
|
14
|
+
<%= @pagination.count %> events
|
|
15
15
|
</div>
|
|
16
16
|
</div>
|
|
17
17
|
|
|
18
|
-
<div class="flex gap-6">
|
|
18
|
+
<div class="flex flex-col lg:flex-row gap-6">
|
|
19
19
|
<!-- Filters sidebar -->
|
|
20
|
-
<aside class="w-72 flex-shrink-0">
|
|
20
|
+
<aside class="w-full lg:w-72 flex-shrink-0">
|
|
21
21
|
<%= render "rails_event_viewer/events/filters_sidebar", event_names: @event_names %>
|
|
22
22
|
</aside>
|
|
23
23
|
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
</svg>
|
|
52
52
|
<h3 class="mt-2 text-sm font-medium text-gray-900">No events found</h3>
|
|
53
53
|
<p class="mt-1 text-sm text-gray-500">
|
|
54
|
-
<% if @
|
|
54
|
+
<% if @filtered %>
|
|
55
55
|
Try adjusting your filters or search query.
|
|
56
56
|
<% else %>
|
|
57
57
|
Events will appear here once they are captured.
|
|
@@ -8,21 +8,21 @@
|
|
|
8
8
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 19l-7-7 7-7" />
|
|
9
9
|
</svg>
|
|
10
10
|
<% end %>
|
|
11
|
-
<div>
|
|
12
|
-
<h1 class="text-2xl font-bold text-gray-900"><%= event_name(@event) %></h1>
|
|
11
|
+
<div class="min-w-0">
|
|
12
|
+
<h1 class="text-2xl font-bold text-gray-900 break-words"><%= event_name(@event) %></h1>
|
|
13
13
|
<p class="text-sm text-gray-500">Event ID: <%= event_id(@event) %></p>
|
|
14
14
|
</div>
|
|
15
15
|
</div>
|
|
16
16
|
|
|
17
17
|
<div class="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
|
18
18
|
<!-- Main content -->
|
|
19
|
-
<div class="lg:col-span-2 space-y-6">
|
|
19
|
+
<div class="lg:col-span-2 space-y-6 min-w-0">
|
|
20
20
|
<!-- Payload -->
|
|
21
21
|
<div class="bg-white rounded-lg shadow">
|
|
22
22
|
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
|
23
23
|
<h3 class="text-lg font-medium text-gray-900">Payload</h3>
|
|
24
24
|
<button type="button"
|
|
25
|
-
data-event-viewer-copy="<%= event_payload(@event)
|
|
25
|
+
data-event-viewer-copy="<%= JSON.generate(event_payload(@event)) %>"
|
|
26
26
|
class="text-indigo-600 hover:text-indigo-500 text-sm font-medium">
|
|
27
27
|
Copy
|
|
28
28
|
</button>
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
<div class="p-6">
|
|
31
31
|
<% payload = event_payload(@event) %>
|
|
32
32
|
<% if payload.present? %>
|
|
33
|
-
<div class="font-mono text-sm">
|
|
33
|
+
<div class="font-mono text-sm break-words">
|
|
34
34
|
<%= json_tree(payload) %>
|
|
35
35
|
</div>
|
|
36
36
|
<% else %>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
<h3 class="text-lg font-medium text-gray-900">Tags</h3>
|
|
48
48
|
</div>
|
|
49
49
|
<div class="p-6">
|
|
50
|
-
<div class="font-mono text-sm">
|
|
50
|
+
<div class="font-mono text-sm break-words">
|
|
51
51
|
<%= json_tree(tags) %>
|
|
52
52
|
</div>
|
|
53
53
|
</div>
|
|
@@ -62,7 +62,7 @@
|
|
|
62
62
|
<h3 class="text-lg font-medium text-gray-900">Context</h3>
|
|
63
63
|
</div>
|
|
64
64
|
<div class="p-6">
|
|
65
|
-
<div class="font-mono text-sm">
|
|
65
|
+
<div class="font-mono text-sm break-words">
|
|
66
66
|
<%= json_tree(context) %>
|
|
67
67
|
</div>
|
|
68
68
|
</div>
|
|
@@ -49,7 +49,7 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
</div>
|
|
51
51
|
<% elsif @groups.any? %>
|
|
52
|
-
<div class="bg-white shadow rounded-lg overflow-
|
|
52
|
+
<div class="bg-white shadow rounded-lg overflow-x-auto">
|
|
53
53
|
<table class="min-w-full divide-y divide-gray-200">
|
|
54
54
|
<thead class="bg-gray-50">
|
|
55
55
|
<tr>
|
|
@@ -19,15 +19,11 @@
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
21
|
<!-- Stats -->
|
|
22
|
-
<div class="grid grid-cols-1 gap-5 sm:grid-cols-
|
|
22
|
+
<div class="grid grid-cols-1 gap-5 sm:grid-cols-3">
|
|
23
23
|
<%= render "rails_event_viewer/shared/stat_card",
|
|
24
24
|
title: "Total Events",
|
|
25
25
|
value: number_with_delimiter(@total_count),
|
|
26
26
|
icon: "database" %>
|
|
27
|
-
<%= render "rails_event_viewer/shared/stat_card",
|
|
28
|
-
title: "Event Types",
|
|
29
|
-
value: @event_names.size,
|
|
30
|
-
icon: "tag" %>
|
|
31
27
|
<%= render "rails_event_viewer/shared/stat_card",
|
|
32
28
|
title: "Duration",
|
|
33
29
|
value: @duration ? format_duration(@duration) : "-",
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
<nav class="bg-indigo-600">
|
|
2
2
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
3
|
-
<div class="flex h-16 items-center justify-between">
|
|
3
|
+
<div class="flex min-h-16 flex-wrap items-center justify-between gap-y-2 py-2">
|
|
4
4
|
<div class="flex items-center">
|
|
5
5
|
<div class="flex-shrink-0">
|
|
6
6
|
<%= link_to rails_event_viewer.root_path, class: "text-white text-xl font-bold" do %>
|
|
7
7
|
Event Viewer
|
|
8
8
|
<% end %>
|
|
9
9
|
</div>
|
|
10
|
-
<div class="ml-10 flex items-baseline space-x-4">
|
|
10
|
+
<div class="ml-10 flex flex-wrap items-baseline space-x-4">
|
|
11
11
|
<%= link_to "Dashboard", rails_event_viewer.root_path,
|
|
12
12
|
class: nav_link_class("dashboard") %>
|
|
13
13
|
<%= link_to "Events", rails_event_viewer.events_path,
|
|
@@ -16,14 +16,16 @@
|
|
|
16
16
|
class: nav_link_class("event_types") %>
|
|
17
17
|
<%= link_to "Groups", rails_event_viewer.groups_path,
|
|
18
18
|
class: nav_link_class("groups") %>
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
<% if analytics_supported? %>
|
|
20
|
+
<%= link_to "Analytics", rails_event_viewer.analytics_overview_path,
|
|
21
|
+
class: nav_link_class("analytics") %>
|
|
22
|
+
<% end %>
|
|
21
23
|
</div>
|
|
22
24
|
</div>
|
|
23
25
|
|
|
24
26
|
<div class="flex items-center space-x-4">
|
|
25
27
|
<!-- Search -->
|
|
26
|
-
<%= form_with url: rails_event_viewer.
|
|
28
|
+
<%= form_with url: rails_event_viewer.events_path, method: :get, class: "relative" do |f| %>
|
|
27
29
|
<div class="relative">
|
|
28
30
|
<div class="pointer-events-none absolute inset-y-0 left-0 flex items-center pl-3">
|
|
29
31
|
<svg class="h-4 w-4 text-indigo-200" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
data/config/routes.rb
CHANGED
|
@@ -57,6 +57,10 @@ module RailsEventViewer
|
|
|
57
57
|
Entry.where("occurred_at < ?", timestamp).in_batches.delete_all
|
|
58
58
|
end
|
|
59
59
|
|
|
60
|
+
def clear!
|
|
61
|
+
Entry.in_batches.delete_all
|
|
62
|
+
end
|
|
63
|
+
|
|
60
64
|
def events_over_time(range:, interval:)
|
|
61
65
|
Entry.where(occurred_at: range).group_by_period(interval, :occurred_at).count
|
|
62
66
|
end
|
|
@@ -41,6 +41,13 @@ module RailsEventViewer
|
|
|
41
41
|
(limit ? counts.first(limit) : counts).to_h
|
|
42
42
|
end
|
|
43
43
|
|
|
44
|
+
def event_type_statistics
|
|
45
|
+
statistics = in_memory_events.group_by { |e| e[:name] }.map do |name, events|
|
|
46
|
+
{ name: name, count: events.size, last_event_at: events.filter_map { |e| e[:occurred_at] }.max }
|
|
47
|
+
end
|
|
48
|
+
statistics.sort_by { |statistic| -statistic[:count] }
|
|
49
|
+
end
|
|
50
|
+
|
|
44
51
|
private
|
|
45
52
|
|
|
46
53
|
def events_in(range)
|
|
@@ -99,7 +99,7 @@ module RailsEventViewer
|
|
|
99
99
|
def delete_before(timestamp)
|
|
100
100
|
score = (timestamp.to_f * 1_000_000).to_i
|
|
101
101
|
@redis.with do |conn|
|
|
102
|
-
deleted = conn.zremrangebyscore(events_key, "-inf", score)
|
|
102
|
+
deleted = conn.zremrangebyscore(events_key, "-inf", "(#{score}")
|
|
103
103
|
prune_names(conn)
|
|
104
104
|
deleted
|
|
105
105
|
end
|
|
@@ -155,8 +155,8 @@ module RailsEventViewer
|
|
|
155
155
|
|
|
156
156
|
def serialize_event(event, timestamp_usec = nil)
|
|
157
157
|
timestamp_usec ||= (event[:occurred_at].to_f * 1_000_000).to_i
|
|
158
|
-
|
|
159
|
-
JSON.generate(
|
|
158
|
+
stored = event.merge(id: "#{timestamp_usec}-#{SecureRandom.hex(8)}", occurred_at: event[:occurred_at]&.iso8601(6))
|
|
159
|
+
JSON.generate(stored)
|
|
160
160
|
end
|
|
161
161
|
|
|
162
162
|
def deserialize_event(json)
|
|
@@ -18,7 +18,7 @@ module RailsEventViewer
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def with_name(*event_names)
|
|
21
|
-
event_names = event_names.flatten.compact
|
|
21
|
+
event_names = event_names.flatten.compact.map(&:to_s)
|
|
22
22
|
return self if event_names.empty?
|
|
23
23
|
|
|
24
24
|
clone_with { |rel| rel.names.concat(event_names) }
|
|
@@ -57,7 +57,7 @@ namespace :rails_event_viewer do
|
|
|
57
57
|
|
|
58
58
|
if adapter.respond_to?(:clear!)
|
|
59
59
|
print "Are you sure you want to delete ALL events? [y/N] "
|
|
60
|
-
response = $stdin.gets.chomp.downcase
|
|
60
|
+
response = $stdin.gets.to_s.chomp.downcase
|
|
61
61
|
|
|
62
62
|
if response == "y"
|
|
63
63
|
adapter.clear!
|