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.
Files changed (29) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +20 -0
  3. data/README.md +3 -0
  4. data/app/assets/javascripts/rails_event_viewer/application.js +2 -1
  5. data/app/assets/stylesheets/rails_event_viewer/application.css +1 -1
  6. data/app/controllers/rails_event_viewer/application_controller.rb +6 -5
  7. data/app/controllers/rails_event_viewer/events_controller.rb +10 -23
  8. data/app/controllers/rails_event_viewer/groups_controller.rb +0 -9
  9. data/app/helpers/rails_event_viewer/application_helper.rb +2 -2
  10. data/app/views/layouts/rails_event_viewer/application.html.erb +8 -0
  11. data/app/views/rails_event_viewer/analytics/overview.html.erb +2 -0
  12. data/app/views/rails_event_viewer/dashboard/index.html.erb +4 -3
  13. data/app/views/rails_event_viewer/event_types/index.html.erb +1 -1
  14. data/app/views/rails_event_viewer/event_types/show.html.erb +5 -3
  15. data/app/views/rails_event_viewer/events/_event_row.html.erb +3 -3
  16. data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +2 -1
  17. data/app/views/rails_event_viewer/events/index.html.erb +4 -4
  18. data/app/views/rails_event_viewer/events/show.html.erb +7 -7
  19. data/app/views/rails_event_viewer/groups/index.html.erb +1 -1
  20. data/app/views/rails_event_viewer/groups/show.html.erb +1 -5
  21. data/app/views/rails_event_viewer/shared/_navigation.html.erb +7 -5
  22. data/config/routes.rb +1 -5
  23. data/lib/rails_event_viewer/adapters/active_record.rb +4 -0
  24. data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +7 -0
  25. data/lib/rails_event_viewer/adapters/redis.rb +3 -3
  26. data/lib/rails_event_viewer/events_relation.rb +1 -1
  27. data/lib/rails_event_viewer/version.rb +1 -1
  28. data/lib/tasks/rails_event_viewer_tasks.rake +1 -1
  29. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c9535859f9ef02bb389bd7b17f4d9c34329c199c4af3cf6e97effeaa5ad27b52
4
- data.tar.gz: 404478f207dae7bcfc10152b3832bb6a9200d4ec1e36c9a1a36ce1cd2ac4ff0c
3
+ metadata.gz: 876a5c4676cfb1f5512d82e73464c1e69a19bd9b1a1b5c2854f93aef7c412a60
4
+ data.tar.gz: 766e263c994dc9552472e065e1abaea18064cc08858c45ef524148923693922f
5
5
  SHA512:
6
- metadata.gz: 15c2c7235db0b5e5b4694c5ddffd7f71c543560670011db7984d19e68dd4ba7848fa71934a6755cac6055f2e29414b4f9d3318ee4e0d3b7382c37ba23303cd0f
7
- data.tar.gz: a32771c3b8d554d69445b037aa8e8b64a1fb2dd2d7e84169a665c61bd83b632cac2f863ec3d5c65c195f772fd49c0af7af56cdfa0af08712d4381478bb7c614e
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
- if (row && !event.target.closest("a, button")) {
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 if date_string.blank?
51
+ return default unless date_string.is_a?(String) && date_string.present?
52
52
 
53
- Date.parse(date_string)
54
- rescue Date::Error, TypeError
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
- (params[:per_page] || RailsEventViewer.per_page).to_i.clamp(1, 100)
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: params[: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
- relation = relation.with_name(params[:name]) if params[:name].present?
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 params[:tag_key].present?
40
- relation = relation.with_tag(params[:tag_key], params[:tag_value].presence)
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.to_json : json.to_s
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.to_json, class: json_value_class(data)) unless data.is_a?(Hash) || data.is_a?(Array)
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
- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800">
89
- <%= event_name(event) %>
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,7 +9,7 @@
9
9
  </div>
10
10
 
11
11
  <% if @event_types.any? %>
12
- <div class="bg-white shadow rounded-lg overflow-hidden">
12
+ <div class="bg-white shadow rounded-lg overflow-x-auto">
13
13
  <table class="min-w-full divide-y divide-gray-200">
14
14
  <thead class="bg-gray-50">
15
15
  <tr>
@@ -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-flex items-center px-3 py-1 rounded-full text-xl font-medium bg-indigo-100 text-indigo-800">
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
- <code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 80) %></code>
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
- <span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800 w-fit">
5
- <%= event_name(event) %>
6
- </span>
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, data: { turbo_frame: "events_list" }, class: "space-y-5" do |f| %>
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 %> total events
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 @query.present? || params[:name].present? %>
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).to_json %>"
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-hidden">
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-4">
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
- <%= link_to "Analytics", rails_event_viewer.analytics_overview_path,
20
- class: nav_link_class("analytics") %>
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.search_events_path, method: :get, class: "relative", data: { turbo_frame: "_top" } do |f| %>
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
@@ -1,11 +1,7 @@
1
1
  RailsEventViewer::Engine.routes.draw do
2
2
  root to: "dashboard#index"
3
3
 
4
- resources :events, only: [:index, :show] do
5
- collection do
6
- get :search
7
- end
8
- end
4
+ resources :events, only: [:index, :show]
9
5
 
10
6
  namespace :analytics do
11
7
  get :overview
@@ -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
- event_with_id = event.merge(id: "#{timestamp_usec}-#{SecureRandom.hex(8)}")
159
- JSON.generate(event_with_id)
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) }
@@ -1,3 +1,3 @@
1
1
  module RailsEventViewer
2
- VERSION = "0.3.0"
2
+ VERSION = "0.3.1"
3
3
  end
@@ -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!
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_event_viewer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Keshav Biswa