rails_event_viewer 0.3.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +22 -0
- data/README.md +5 -0
- data/app/assets/stylesheets/rails_event_viewer/application.css +1 -1
- data/app/controllers/rails_event_viewer/application_controller.rb +1 -11
- data/app/controllers/rails_event_viewer/events_controller.rb +6 -6
- data/app/helpers/rails_event_viewer/application_helper.rb +21 -75
- data/app/models/rails_event_viewer/entry.rb +0 -26
- data/app/models/rails_event_viewer/page.rb +9 -0
- data/app/views/layouts/rails_event_viewer/application.html.erb +3 -4
- data/app/views/rails_event_viewer/analytics/overview.html.erb +11 -9
- data/app/views/rails_event_viewer/dashboard/index.html.erb +13 -13
- data/app/views/rails_event_viewer/event_types/index.html.erb +6 -6
- data/app/views/rails_event_viewer/event_types/show.html.erb +14 -14
- data/app/views/rails_event_viewer/events/_event_row.html.erb +8 -8
- data/app/views/rails_event_viewer/events/_filters_sidebar.html.erb +16 -14
- data/app/views/rails_event_viewer/events/index.html.erb +6 -6
- data/app/views/rails_event_viewer/events/show.html.erb +22 -22
- data/app/views/rails_event_viewer/groups/index.html.erb +7 -7
- data/app/views/rails_event_viewer/groups/show.html.erb +11 -11
- data/app/views/rails_event_viewer/shared/_navigation.html.erb +8 -14
- data/app/views/rails_event_viewer/shared/_pagination.html.erb +10 -10
- data/app/views/rails_event_viewer/shared/_stat_card.html.erb +5 -5
- data/db/migrate/20261002121337_create_rails_event_viewer_entries.rb +0 -1
- data/lib/generators/rails_event_viewer/install/templates/create_rails_event_viewer_entries.rb.erb +0 -1
- data/lib/rails_event_viewer/adapter.rb +0 -8
- data/lib/rails_event_viewer/adapters/active_record.rb +8 -44
- data/lib/rails_event_viewer/adapters/concerns/in_memory_filtering.rb +0 -7
- data/lib/rails_event_viewer/adapters/memory.rb +0 -4
- data/lib/rails_event_viewer/adapters/null.rb +0 -8
- data/lib/rails_event_viewer/adapters/redis.rb +3 -17
- data/lib/rails_event_viewer/flusher.rb +5 -5
- data/lib/rails_event_viewer/logging.rb +1 -11
- data/lib/rails_event_viewer/version.rb +1 -1
- data/lib/rails_event_viewer.rb +5 -5
- metadata +7 -2
- data/app/assets/stylesheets/rails_event_viewer/tailwind.css +0 -29
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 935a008c1e9519c485ee47d472d8980a36e1e6e4778e83c76e38629350ed93e9
|
|
4
|
+
data.tar.gz: 9244a13f193475b62847af18366b4e53246a9602f84c13cd00114ec6ead86c72
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af5581280410b7b5eb3a79ce61b3dc53ca8dac9ddb083d79554c61f950977319049714334c6fb19713d6aa8c67b38bb63a2bb1dd9042a08a71da7def2bd6b5e1
|
|
7
|
+
data.tar.gz: bc898a58889c8786fcc83c6467084275637c5e27ddc8ee33a233c571d917724b65445f235eebcf3e99be1e2aea70c4eb6a301d764aa5a3bb2ac53ce40be16750
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.0 (2026-10-03)
|
|
4
|
+
|
|
5
|
+
Breaking:
|
|
6
|
+
|
|
7
|
+
- Removed `Adapter#distinct_group_values` and `Adapter#supports_retention?`. Nothing called them.
|
|
8
|
+
- Removed `Entry#source_location`, `#short_filepath`, `#tagged?`, `#has_context?` and `#tag`.
|
|
9
|
+
- The ActiveRecord adapter raises on unknown `adapter_options`, like the other adapters.
|
|
10
|
+
- Times show in the host app's time zone. The engine no longer reads a `timezone` cookie.
|
|
11
|
+
|
|
12
|
+
Changed:
|
|
13
|
+
|
|
14
|
+
- New installs skip the index on `name` alone. The `(name, occurred_at)` index covers it. Existing apps can drop it with `remove_index :rails_event_viewer_entries, :name`.
|
|
15
|
+
- Chartkick is pinned below 6, to match the bundled Chartkick.js.
|
|
16
|
+
- Dashboard scripts load with `defer`.
|
|
17
|
+
|
|
18
|
+
Fixed:
|
|
19
|
+
|
|
20
|
+
- Events with the same timestamp keep a stable order across pages.
|
|
21
|
+
- Pagination renders only the page links it shows, not one loop per page.
|
|
22
|
+
- Related events look one hour either side of the event, so the lookup no longer scans the whole table.
|
|
23
|
+
- Form fields have labels, tables have column headers, and the current page is marked for screen readers. Grey text has more contrast.
|
|
24
|
+
|
|
3
25
|
## 0.3.1 (2026-10-03)
|
|
4
26
|
|
|
5
27
|
Changed:
|
data/README.md
CHANGED
|
@@ -8,6 +8,9 @@ A Rails engine that captures events emitted with `Rails.event` and gives you a d
|
|
|
8
8
|
|
|
9
9
|
- Rails 8.1+
|
|
10
10
|
- Ruby 3.3+
|
|
11
|
+
- SQLite, PostgreSQL, or MySQL 8 for the default ActiveRecord adapter
|
|
12
|
+
|
|
13
|
+
API-only apps need an asset pipeline to serve the dashboard's CSS and JavaScript. Add `gem "propshaft"`, and run `assets:precompile` when you deploy.
|
|
11
14
|
|
|
12
15
|
## Installation
|
|
13
16
|
|
|
@@ -130,6 +133,8 @@ production:
|
|
|
130
133
|
|
|
131
134
|
## Development
|
|
132
135
|
|
|
136
|
+
The dashboard ships Chart.js 4.4.1, chartjs-adapter-date-fns 3.0.0 and Chartkick.js 5.0.1.
|
|
137
|
+
|
|
133
138
|
```bash
|
|
134
139
|
bundle install
|
|
135
140
|
bundle exec rake test
|
|
@@ -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}.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}}
|
|
1
|
+
*,:after,:before{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }::backdrop{--tw-border-spacing-x:0;--tw-border-spacing-y:0;--tw-translate-x:0;--tw-translate-y:0;--tw-rotate:0;--tw-skew-x:0;--tw-skew-y:0;--tw-scale-x:1;--tw-scale-y:1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness:proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-color:rgba(59,130,246,.5);--tw-ring-offset-shadow:0 0 #0000;--tw-ring-shadow:0 0 #0000;--tw-shadow:0 0 #0000;--tw-shadow-colored:0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: ;--tw-contain-size: ;--tw-contain-layout: ;--tw-contain-paint: ;--tw-contain-style: }/*! tailwindcss v3.4.19 | MIT License | https://tailwindcss.com*/*,:after,:before{box-sizing:border-box;border:0 solid #e5e7eb}:after,:before{--tw-content:""}:host,html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:ui-sans-serif,system-ui,sans-serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol,Noto Color Emoji;font-feature-settings:normal;font-variation-settings:normal;-webkit-tap-highlight-color:transparent}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,pre,samp{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-feature-settings:normal;font-variation-settings:normal;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;letter-spacing:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,input:where([type=button]),input:where([type=reset]),input:where([type=submit]){-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dd,dl,figure,h1,h2,h3,h4,h5,h6,hr,p,pre{margin:0}fieldset{margin:0}fieldset,legend{padding:0}menu,ol,ul{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#9ca3af}input::placeholder,textarea::placeholder{opacity:1;color:#9ca3af}[role=button],button{cursor:pointer}:disabled{cursor:default}audio,canvas,embed,iframe,img,object,svg,video{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]:where(:not([hidden=until-found])){display:none}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border-width:0}.pointer-events-none{pointer-events:none}.absolute{position:absolute}.relative{position:relative}.inset-y-0{top:0;bottom:0}.bottom-0{bottom:0}.left-0{left:0}.left-10{left:2.5rem}.left-12{left:3rem}.top-0{top:0}.top-4{top:1rem}.top-6{top:1.5rem}.mx-auto{margin-left:auto;margin-right:auto}.mb-1{margin-bottom:.25rem}.mb-2{margin-bottom:.5rem}.mb-4{margin-bottom:1rem}.ml-1{margin-left:.25rem}.ml-10{margin-left:2.5rem}.ml-3{margin-left:.75rem}.ml-4{margin-left:1rem}.ml-5{margin-left:1.25rem}.mr-1{margin-right:.25rem}.mr-2{margin-right:.5rem}.mt-1{margin-top:.25rem}.mt-2{margin-top:.5rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.grid{display:grid}.h-12{height:3rem}.h-2{height:.5rem}.h-4{height:1rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-72{height:18rem}.h-full{height:100%}.min-h-16{min-height:4rem}.min-h-full{min-height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1\/5{width:20%}.w-12{width:3rem}.w-2\/5{width:40%}.w-4{width:1rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-64{width:16rem}.w-9{width:2.25rem}.w-fit{width:-moz-fit-content;width:fit-content}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.max-w-full{max-width:100%}.max-w-md{max-width:28rem}.max-w-xs{max-width:20rem}.flex-1{flex:1 1 0%}.flex-shrink-0{flex-shrink:0}.table-fixed{table-layout:fixed}.cursor-not-allowed{cursor:not-allowed}.cursor-pointer{cursor:pointer}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-start{align-items:flex-start}.items-center{align-items:center}.items-baseline{align-items:baseline}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-1{gap:.25rem}.gap-5{gap:1.25rem}.gap-6{gap:1.5rem}.gap-y-2{row-gap:.5rem}.space-x-1>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.25rem*var(--tw-space-x-reverse));margin-left:calc(.25rem*(1 - var(--tw-space-x-reverse)))}.space-x-2>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.5rem*var(--tw-space-x-reverse));margin-left:calc(.5rem*(1 - var(--tw-space-x-reverse)))}.space-x-3>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(.75rem*var(--tw-space-x-reverse));margin-left:calc(.75rem*(1 - var(--tw-space-x-reverse)))}.space-x-4>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(1rem*var(--tw-space-x-reverse));margin-left:calc(1rem*(1 - var(--tw-space-x-reverse)))}.space-y-2>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.5rem*var(--tw-space-y-reverse))}.space-y-5>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.25rem*var(--tw-space-y-reverse))}.space-y-6>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(1.5rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(1.5rem*var(--tw-space-y-reverse))}.divide-y>:not([hidden])~:not([hidden]){--tw-divide-y-reverse:0;border-top-width:calc(1px*(1 - var(--tw-divide-y-reverse)));border-bottom-width:calc(1px*var(--tw-divide-y-reverse))}.divide-gray-100>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(243 244 246/var(--tw-divide-opacity,1))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity,1))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.rounded{border-radius:.25rem}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.border{border-width:1px}.border-2{border-width:2px}.border-b{border-bottom-width:1px}.border-t{border-top-width:1px}.border-gray-200{--tw-border-opacity:1;border-color:rgb(229 231 235/var(--tw-border-opacity,1))}.border-gray-300{--tw-border-opacity:1;border-color:rgb(209 213 219/var(--tw-border-opacity,1))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.border-indigo-600{--tw-border-opacity:1;border-color:rgb(79 70 229/var(--tw-border-opacity,1))}.border-yellow-200{--tw-border-opacity:1;border-color:rgb(254 240 138/var(--tw-border-opacity,1))}.bg-gray-100{--tw-bg-opacity:1;background-color:rgb(243 244 246/var(--tw-bg-opacity,1))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity,1))}.bg-indigo-100{--tw-bg-opacity:1;background-color:rgb(224 231 255/var(--tw-bg-opacity,1))}.bg-indigo-500{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.bg-indigo-600{--tw-bg-opacity:1;background-color:rgb(79 70 229/var(--tw-bg-opacity,1))}.bg-indigo-700{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity,1))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.bg-yellow-100{--tw-bg-opacity:1;background-color:rgb(254 249 195/var(--tw-bg-opacity,1))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity,1))}.p-4{padding:1rem}.p-5{padding:1.25rem}.p-6{padding:1.5rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-2\.5{padding-left:.625rem;padding-right:.625rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.px-5{padding-left:1.25rem;padding-right:1.25rem}.px-6{padding-left:1.5rem;padding-right:1.5rem}.py-0\.5{padding-top:.125rem;padding-bottom:.125rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-12{padding-top:3rem;padding-bottom:3rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-2\.5{padding-top:.625rem;padding-bottom:.625rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pl-10{padding-left:2.5rem}.pl-16{padding-left:4rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-6{padding-right:1.5rem}.pt-3{padding-top:.75rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.font-mono{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace}.text-2xl{font-size:1.5rem;line-height:2rem}.text-lg{font-size:1.125rem;line-height:1.75rem}.text-sm{font-size:.875rem;line-height:1.25rem}.text-xl{font-size:1.25rem;line-height:1.75rem}.text-xs{font-size:.75rem;line-height:1rem}.font-bold{font-weight:700}.font-medium{font-weight:500}.font-normal{font-weight:400}.font-semibold{font-weight:600}.uppercase{text-transform:uppercase}.tracking-wider{letter-spacing:.05em}.text-blue-600{--tw-text-opacity:1;color:rgb(37 99 235/var(--tw-text-opacity,1))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity,1))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity,1))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity,1))}.text-gray-600{--tw-text-opacity:1;color:rgb(75 85 99/var(--tw-text-opacity,1))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity,1))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.text-green-600{--tw-text-opacity:1;color:rgb(22 163 74/var(--tw-text-opacity,1))}.text-green-800{--tw-text-opacity:1;color:rgb(22 101 52/var(--tw-text-opacity,1))}.text-indigo-200{--tw-text-opacity:1;color:rgb(199 210 254/var(--tw-text-opacity,1))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity,1))}.text-indigo-800{--tw-text-opacity:1;color:rgb(55 48 163/var(--tw-text-opacity,1))}.text-purple-600{--tw-text-opacity:1;color:rgb(147 51 234/var(--tw-text-opacity,1))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity,1))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.text-yellow-400{--tw-text-opacity:1;color:rgb(250 204 21/var(--tw-text-opacity,1))}.text-yellow-700{--tw-text-opacity:1;color:rgb(161 98 7/var(--tw-text-opacity,1))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity,1))}.placeholder-indigo-200::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.placeholder-indigo-200::placeholder{--tw-placeholder-opacity:1;color:rgb(199 210 254/var(--tw-placeholder-opacity,1))}.shadow{--tw-shadow:0 1px 3px 0 rgba(0,0,0,.1),0 1px 2px -1px rgba(0,0,0,.1);--tw-shadow-colored:0 1px 3px 0 var(--tw-shadow-color),0 1px 2px -1px var(--tw-shadow-color)}.shadow,.shadow-sm{box-shadow:var(--tw-ring-offset-shadow,0 0 #0000),var(--tw-ring-shadow,0 0 #0000),var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 2px 0 rgba(0,0,0,.05);--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.hover\:bg-gray-200:hover{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity,1))}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity,1))}.hover\:bg-indigo-500:hover{--tw-bg-opacity:1;background-color:rgb(99 102 241/var(--tw-bg-opacity,1))}.hover\:bg-indigo-700:hover{--tw-bg-opacity:1;background-color:rgb(67 56 202/var(--tw-bg-opacity,1))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity,1))}.hover\:text-indigo-500:hover{--tw-text-opacity:1;color:rgb(99 102 241/var(--tw-text-opacity,1))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity,1))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity,1))}.focus\:border-indigo-500:focus{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity,1))}.focus\:bg-white:focus{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity,1))}.focus\:text-gray-900:focus{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity,1))}.focus\:placeholder-gray-400:focus::-moz-placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:placeholder-gray-400:focus::placeholder{--tw-placeholder-opacity:1;color:rgb(156 163 175/var(--tw-placeholder-opacity,1))}.focus\:outline-none:focus{outline:2px solid transparent;outline-offset:2px}.focus\:ring-2:focus{--tw-ring-offset-shadow:var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);--tw-ring-shadow:var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);box-shadow:var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow,0 0 #0000)}.focus\:ring-indigo-500:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(99 102 241/var(--tw-ring-opacity,1))}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity,1))}@media (min-width:640px){.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:px-6{padding-left:1.5rem;padding-right:1.5rem}}@media (min-width:1024px){.lg\:col-span-2{grid-column:span 2/span 2}.lg\:w-72{width:18rem}.lg\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:flex-row{flex-direction:row}.lg\:px-8{padding-left:2rem;padding-right:2rem}}
|
|
@@ -3,9 +3,8 @@ module RailsEventViewer
|
|
|
3
3
|
layout "rails_event_viewer/application"
|
|
4
4
|
|
|
5
5
|
before_action :authenticate!
|
|
6
|
-
around_action :use_viewer_time_zone
|
|
7
6
|
|
|
8
|
-
helper_method :
|
|
7
|
+
helper_method :current_adapter, :analytics_supported?, :persistence_supported?
|
|
9
8
|
|
|
10
9
|
private
|
|
11
10
|
|
|
@@ -37,11 +36,6 @@ module RailsEventViewer
|
|
|
37
36
|
render plain: "Unauthorized", status: :unauthorized unless auth.call(self) || performed?
|
|
38
37
|
end
|
|
39
38
|
|
|
40
|
-
def use_viewer_time_zone(&block)
|
|
41
|
-
zone = ActiveSupport::TimeZone[cookies[:timezone].to_s] || ActiveSupport::TimeZone["UTC"]
|
|
42
|
-
Time.use_zone(zone, &block)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
39
|
def string_param(key)
|
|
46
40
|
value = params[key]
|
|
47
41
|
value if value.is_a?(String) && !value.empty?
|
|
@@ -56,10 +50,6 @@ module RailsEventViewer
|
|
|
56
50
|
default
|
|
57
51
|
end
|
|
58
52
|
|
|
59
|
-
def current_timezone
|
|
60
|
-
Time.zone.name
|
|
61
|
-
end
|
|
62
|
-
|
|
63
53
|
def current_adapter
|
|
64
54
|
RailsEventViewer.adapter
|
|
65
55
|
end
|
|
@@ -39,20 +39,20 @@ module RailsEventViewer
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def find_related_events(event)
|
|
42
|
-
context = event
|
|
42
|
+
context = event[:context]
|
|
43
43
|
return [] unless context.present?
|
|
44
44
|
|
|
45
|
-
# Find events with matching request_id in context
|
|
46
45
|
request_id = context["request_id"] || context[:request_id]
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
event_id = event.respond_to?(:id) ? event.id : event[:id]
|
|
46
|
+
occurred_at = event[:occurred_at]
|
|
47
|
+
return [] unless request_id && occurred_at
|
|
50
48
|
|
|
51
49
|
RailsEventViewer.events
|
|
52
50
|
.with_context("request_id", request_id)
|
|
51
|
+
.since(occurred_at - 1.hour)
|
|
52
|
+
.until(occurred_at + 1.hour)
|
|
53
53
|
.limit(11)
|
|
54
54
|
.to_a
|
|
55
|
-
.reject { |
|
|
55
|
+
.reject { |related| related[:id] == event[:id] }
|
|
56
56
|
.first(10)
|
|
57
57
|
end
|
|
58
58
|
end
|
|
@@ -1,27 +1,23 @@
|
|
|
1
1
|
module RailsEventViewer
|
|
2
2
|
module ApplicationHelper
|
|
3
|
-
def
|
|
4
|
-
|
|
3
|
+
def nav_link(label, path, controller)
|
|
4
|
+
active = controller_name == controller
|
|
5
|
+
state_classes = active ? "bg-indigo-700 text-white" : "text-indigo-200 hover:bg-indigo-500 hover:text-white"
|
|
5
6
|
|
|
6
|
-
if
|
|
7
|
-
"#{base_classes} bg-indigo-700 text-white"
|
|
8
|
-
else
|
|
9
|
-
"#{base_classes} text-indigo-200 hover:bg-indigo-500 hover:text-white"
|
|
10
|
-
end
|
|
7
|
+
link_to label, path, class: "rounded-md px-3 py-2 text-sm font-medium #{state_classes}", aria: { current: ("page" if active) }
|
|
11
8
|
end
|
|
12
9
|
|
|
13
|
-
# Format event time - works with both Entry model and Hash
|
|
14
10
|
def format_event_time(event)
|
|
15
|
-
time =
|
|
11
|
+
time = event[:occurred_at]
|
|
16
12
|
return "N/A" unless time
|
|
17
13
|
|
|
18
|
-
time.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
14
|
+
time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S.%3N")
|
|
19
15
|
end
|
|
20
16
|
|
|
21
17
|
def time_ago_with_title(time)
|
|
22
18
|
return "N/A" unless time
|
|
23
19
|
|
|
24
|
-
content_tag(:span, time_ago_in_words(time) + " ago", title: time.strftime("%Y-%m-%d %H:%M:%S %Z"))
|
|
20
|
+
content_tag(:span, time_ago_in_words(time) + " ago", title: time.in_time_zone.strftime("%Y-%m-%d %H:%M:%S %Z"))
|
|
25
21
|
end
|
|
26
22
|
|
|
27
23
|
def truncate_json(json, length: 100)
|
|
@@ -29,73 +25,31 @@ module RailsEventViewer
|
|
|
29
25
|
truncate(text, length: length)
|
|
30
26
|
end
|
|
31
27
|
|
|
32
|
-
def json_tree(data
|
|
33
|
-
return content_tag(:span, "null", class: "text-gray-
|
|
28
|
+
def json_tree(data)
|
|
29
|
+
return content_tag(:span, "null", class: "text-gray-500") if data.nil?
|
|
34
30
|
return content_tag(:span, JSON.generate(data), class: json_value_class(data)) unless data.is_a?(Hash) || data.is_a?(Array)
|
|
35
|
-
return content_tag(:span, collapsed_summary(data), class: "text-gray-400") if max_depth && depth >= max_depth
|
|
36
31
|
|
|
37
|
-
|
|
38
|
-
render_hash_tree(data, depth, max_depth)
|
|
39
|
-
else
|
|
40
|
-
render_array_tree(data, depth, max_depth)
|
|
41
|
-
end
|
|
32
|
+
data.is_a?(Hash) ? render_hash_tree(data) : render_array_tree(data)
|
|
42
33
|
end
|
|
43
34
|
|
|
44
35
|
def page_path(page)
|
|
45
36
|
"#{request.path}?#{request.query_parameters.merge("page" => page.to_s).to_query}"
|
|
46
37
|
end
|
|
47
38
|
|
|
48
|
-
def timezone_options
|
|
49
|
-
ActiveSupport::TimeZone.all.map { |tz| [tz.to_s, tz.name] }
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
# Helper to get attribute from either Entry model or Hash
|
|
53
|
-
def event_attribute(event, attr)
|
|
54
|
-
if event.respond_to?(attr)
|
|
55
|
-
event.public_send(attr)
|
|
56
|
-
elsif event.is_a?(Hash)
|
|
57
|
-
event[attr] || event[attr.to_s]
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def event_id(event)
|
|
62
|
-
event_attribute(event, :id)
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def event_name(event)
|
|
66
|
-
event_attribute(event, :name)
|
|
67
|
-
end
|
|
68
|
-
|
|
69
39
|
def event_payload(event)
|
|
70
|
-
|
|
40
|
+
event[:payload] || {}
|
|
71
41
|
end
|
|
72
42
|
|
|
73
43
|
def event_tags(event)
|
|
74
|
-
|
|
44
|
+
event[:tags] || {}
|
|
75
45
|
end
|
|
76
46
|
|
|
77
47
|
def event_context(event)
|
|
78
|
-
|
|
79
|
-
end
|
|
80
|
-
|
|
81
|
-
def event_occurred_at(event)
|
|
82
|
-
event_attribute(event, :occurred_at)
|
|
83
|
-
end
|
|
84
|
-
|
|
85
|
-
def event_source_file(event)
|
|
86
|
-
event_attribute(event, :source_file)
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
def event_source_line(event)
|
|
90
|
-
event_attribute(event, :source_line)
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def event_source_label(event)
|
|
94
|
-
event_attribute(event, :source_label)
|
|
48
|
+
event[:context] || {}
|
|
95
49
|
end
|
|
96
50
|
|
|
97
51
|
def event_short_filepath(event)
|
|
98
|
-
filepath =
|
|
52
|
+
filepath = event[:source_file]
|
|
99
53
|
return nil unless filepath
|
|
100
54
|
|
|
101
55
|
filepath.gsub(%r{.*/app/}, "app/")
|
|
@@ -130,35 +84,27 @@ module RailsEventViewer
|
|
|
130
84
|
end
|
|
131
85
|
end
|
|
132
86
|
|
|
133
|
-
def
|
|
134
|
-
if
|
|
135
|
-
data.empty? ? "{}" : "{#{data.size} #{"key".pluralize(data.size)}}"
|
|
136
|
-
else
|
|
137
|
-
data.empty? ? "[]" : "[#{data.size} #{"item".pluralize(data.size)}]"
|
|
138
|
-
end
|
|
139
|
-
end
|
|
140
|
-
|
|
141
|
-
def render_hash_tree(hash, depth, max_depth)
|
|
142
|
-
return content_tag(:span, "{}", class: "text-gray-400") if hash.empty?
|
|
87
|
+
def render_hash_tree(hash)
|
|
88
|
+
return content_tag(:span, "{}", class: "text-gray-500") if hash.empty?
|
|
143
89
|
|
|
144
90
|
content_tag(:div, class: "pl-4") do
|
|
145
91
|
hash.map do |key, value|
|
|
146
92
|
content_tag(:div, class: "py-0.5") do
|
|
147
93
|
content_tag(:span, "#{key}:", class: "text-gray-600 font-medium mr-2") +
|
|
148
|
-
json_tree(value
|
|
94
|
+
json_tree(value)
|
|
149
95
|
end
|
|
150
96
|
end.join.html_safe
|
|
151
97
|
end
|
|
152
98
|
end
|
|
153
99
|
|
|
154
|
-
def render_array_tree(array
|
|
155
|
-
return content_tag(:span, "[]", class: "text-gray-
|
|
100
|
+
def render_array_tree(array)
|
|
101
|
+
return content_tag(:span, "[]", class: "text-gray-500") if array.empty?
|
|
156
102
|
|
|
157
103
|
content_tag(:div, class: "pl-4") do
|
|
158
104
|
array.each_with_index.map do |value, index|
|
|
159
105
|
content_tag(:div, class: "py-0.5") do
|
|
160
|
-
content_tag(:span, "[#{index}]:", class: "text-gray-
|
|
161
|
-
json_tree(value
|
|
106
|
+
content_tag(:span, "[#{index}]:", class: "text-gray-500 mr-2") +
|
|
107
|
+
json_tree(value)
|
|
162
108
|
end
|
|
163
109
|
end.join.html_safe
|
|
164
110
|
end
|
|
@@ -4,31 +4,5 @@ module RailsEventViewer
|
|
|
4
4
|
|
|
5
5
|
validates :name, presence: true
|
|
6
6
|
validates :occurred_at, presence: true
|
|
7
|
-
|
|
8
|
-
def source_location
|
|
9
|
-
{
|
|
10
|
-
filepath: source_file,
|
|
11
|
-
lineno: source_line,
|
|
12
|
-
label: source_label
|
|
13
|
-
}.compact
|
|
14
|
-
end
|
|
15
|
-
|
|
16
|
-
def short_filepath
|
|
17
|
-
return nil unless source_file
|
|
18
|
-
|
|
19
|
-
source_file.gsub(%r{.*/app/}, "app/")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def tagged?
|
|
23
|
-
tags.present? && tags.any?
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def has_context?
|
|
27
|
-
context.present? && context.any?
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
def tag(key)
|
|
31
|
-
tags&.dig(key.to_s)
|
|
32
|
-
end
|
|
33
7
|
end
|
|
34
8
|
end
|
|
@@ -31,5 +31,14 @@ module RailsEventViewer
|
|
|
31
31
|
def next
|
|
32
32
|
page + 1 if page < pages
|
|
33
33
|
end
|
|
34
|
+
|
|
35
|
+
def window
|
|
36
|
+
numbers = [1, *(page - 2..page + 2), pages].select { |number| number.between?(1, pages) }.uniq.sort
|
|
37
|
+
|
|
38
|
+
numbers.each_with_object([]) do |number, items|
|
|
39
|
+
items << :gap if items.any? && number > items.last + 1
|
|
40
|
+
items << number
|
|
41
|
+
end
|
|
42
|
+
end
|
|
34
43
|
end
|
|
35
44
|
end
|
|
@@ -11,9 +11,8 @@
|
|
|
11
11
|
"rails_event_viewer/chartjs-adapter-date-fns.bundle.min",
|
|
12
12
|
"rails_event_viewer/chartkick.min",
|
|
13
13
|
"rails_event_viewer/application",
|
|
14
|
+
defer: true,
|
|
14
15
|
nonce: true %>
|
|
15
|
-
|
|
16
|
-
<%= yield :head %>
|
|
17
16
|
</head>
|
|
18
17
|
<body class="h-full">
|
|
19
18
|
<div class="min-h-full">
|
|
@@ -22,13 +21,13 @@
|
|
|
22
21
|
<main class="py-6">
|
|
23
22
|
<div class="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
|
|
24
23
|
<% if notice.present? %>
|
|
25
|
-
<div class="mb-4 rounded-md bg-green-50 p-4">
|
|
24
|
+
<div class="mb-4 rounded-md bg-green-50 p-4" role="status">
|
|
26
25
|
<p class="text-sm font-medium text-green-800"><%= notice %></p>
|
|
27
26
|
</div>
|
|
28
27
|
<% end %>
|
|
29
28
|
|
|
30
29
|
<% if alert.present? %>
|
|
31
|
-
<div class="mb-4 rounded-md bg-red-50 p-4">
|
|
30
|
+
<div class="mb-4 rounded-md bg-red-50 p-4" role="alert">
|
|
32
31
|
<p class="text-sm font-medium text-red-800"><%= alert %></p>
|
|
33
32
|
</div>
|
|
34
33
|
<% end %>
|
|
@@ -8,10 +8,12 @@
|
|
|
8
8
|
<%= form_with url: rails_event_viewer.analytics_overview_path, method: :get, class: "flex items-center space-x-3" do |f| %>
|
|
9
9
|
<%= f.date_field :start_date,
|
|
10
10
|
value: @date_range.begin.to_date,
|
|
11
|
+
aria: { label: "Start date" },
|
|
11
12
|
class: "rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
|
|
12
13
|
<span class="text-gray-500">to</span>
|
|
13
14
|
<%= f.date_field :end_date,
|
|
14
15
|
value: @date_range.end.to_date,
|
|
16
|
+
aria: { label: "End date" },
|
|
15
17
|
class: "rounded-md border border-gray-300 shadow-sm focus:border-indigo-500 focus:ring-indigo-500 py-2 px-3" %>
|
|
16
18
|
<%= f.submit "Apply",
|
|
17
19
|
class: "bg-indigo-600 text-white px-5 py-2.5 rounded-md font-medium hover:bg-indigo-700 cursor-pointer" %>
|
|
@@ -41,7 +43,7 @@
|
|
|
41
43
|
<!-- Charts -->
|
|
42
44
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
43
45
|
<div class="bg-white rounded-lg shadow p-6 overflow-hidden">
|
|
44
|
-
<
|
|
46
|
+
<h2 class="text-lg font-medium text-gray-900 mb-4">Events Over Time</h2>
|
|
45
47
|
<div class="h-72 overflow-hidden">
|
|
46
48
|
<% if @events_over_time.any? %>
|
|
47
49
|
<%= line_chart @events_over_time,
|
|
@@ -54,7 +56,7 @@
|
|
|
54
56
|
scales: { y: { beginAtZero: true } }
|
|
55
57
|
} %>
|
|
56
58
|
<% else %>
|
|
57
|
-
<div class="flex items-center justify-center h-full text-gray-
|
|
59
|
+
<div class="flex items-center justify-center h-full text-gray-500">
|
|
58
60
|
No data for the selected period
|
|
59
61
|
</div>
|
|
60
62
|
<% end %>
|
|
@@ -62,7 +64,7 @@
|
|
|
62
64
|
</div>
|
|
63
65
|
|
|
64
66
|
<div class="bg-white rounded-lg shadow p-6 overflow-hidden">
|
|
65
|
-
<
|
|
67
|
+
<h2 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h2>
|
|
66
68
|
<div class="h-72 overflow-hidden">
|
|
67
69
|
<% if @events_by_type.any? %>
|
|
68
70
|
<%= bar_chart @events_by_type,
|
|
@@ -75,7 +77,7 @@
|
|
|
75
77
|
plugins: { legend: { display: false } }
|
|
76
78
|
} %>
|
|
77
79
|
<% else %>
|
|
78
|
-
<div class="flex items-center justify-center h-full text-gray-
|
|
80
|
+
<div class="flex items-center justify-center h-full text-gray-500">
|
|
79
81
|
No data for the selected period
|
|
80
82
|
</div>
|
|
81
83
|
<% end %>
|
|
@@ -86,7 +88,7 @@
|
|
|
86
88
|
<!-- Event type breakdown table -->
|
|
87
89
|
<div class="bg-white rounded-lg shadow">
|
|
88
90
|
<div class="px-6 py-4 border-b border-gray-200">
|
|
89
|
-
<
|
|
91
|
+
<h2 class="text-lg font-medium text-gray-900">Event Type Breakdown</h2>
|
|
90
92
|
</div>
|
|
91
93
|
|
|
92
94
|
<% if @events_by_type.any? %>
|
|
@@ -94,10 +96,10 @@
|
|
|
94
96
|
<table class="min-w-full divide-y divide-gray-200">
|
|
95
97
|
<thead class="bg-gray-50">
|
|
96
98
|
<tr>
|
|
97
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
|
|
98
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
|
|
99
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Percentage</th>
|
|
100
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distribution</th>
|
|
99
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
|
|
100
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
|
|
101
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Percentage</th>
|
|
102
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Distribution</th>
|
|
101
103
|
</tr>
|
|
102
104
|
</thead>
|
|
103
105
|
<tbody class="divide-y divide-gray-200">
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
|
|
29
29
|
<!-- Events over time chart -->
|
|
30
30
|
<div class="bg-white rounded-lg shadow p-6 overflow-hidden">
|
|
31
|
-
<
|
|
31
|
+
<h2 class="text-lg font-medium text-gray-900 mb-4">Events (Last 24 Hours)</h2>
|
|
32
32
|
<div class="h-72 overflow-hidden">
|
|
33
33
|
<% if @events_over_time.any? %>
|
|
34
34
|
<%= line_chart @events_over_time,
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
colors: ["#4F46E5"],
|
|
39
39
|
library: { plugins: { legend: { display: false } } } %>
|
|
40
40
|
<% else %>
|
|
41
|
-
<div class="flex items-center justify-center h-full text-gray-
|
|
41
|
+
<div class="flex items-center justify-center h-full text-gray-500">
|
|
42
42
|
No events in the last 24 hours
|
|
43
43
|
</div>
|
|
44
44
|
<% end %>
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
|
|
48
48
|
<!-- Events by type chart -->
|
|
49
49
|
<div class="bg-white rounded-lg shadow p-6 overflow-hidden">
|
|
50
|
-
<
|
|
50
|
+
<h2 class="text-lg font-medium text-gray-900 mb-4">Events by Type</h2>
|
|
51
51
|
<div class="h-72 overflow-hidden">
|
|
52
52
|
<% if @events_by_type.any? %>
|
|
53
53
|
<%= pie_chart @events_by_type,
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
donut: true,
|
|
58
58
|
colors: ["#4F46E5", "#10B981", "#F59E0B", "#EF4444", "#8B5CF6", "#EC4899", "#06B6D4", "#84CC16"] %>
|
|
59
59
|
<% else %>
|
|
60
|
-
<div class="flex items-center justify-center h-full text-gray-
|
|
60
|
+
<div class="flex items-center justify-center h-full text-gray-500">
|
|
61
61
|
No events to display
|
|
62
62
|
</div>
|
|
63
63
|
<% end %>
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
<!-- Recent events -->
|
|
70
70
|
<div class="bg-white rounded-lg shadow">
|
|
71
71
|
<div class="px-6 py-4 border-b border-gray-200 flex justify-between items-center">
|
|
72
|
-
<
|
|
72
|
+
<h2 class="text-lg font-medium text-gray-900">Recent Events</h2>
|
|
73
73
|
<%= link_to "View all", rails_event_viewer.events_path, class: "text-indigo-600 hover:text-indigo-500 text-sm font-medium" %>
|
|
74
74
|
</div>
|
|
75
75
|
|
|
@@ -78,23 +78,23 @@
|
|
|
78
78
|
<table class="min-w-full divide-y divide-gray-200">
|
|
79
79
|
<thead class="bg-gray-50">
|
|
80
80
|
<tr>
|
|
81
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event</th>
|
|
82
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payload</th>
|
|
83
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
|
|
81
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event</th>
|
|
82
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Payload</th>
|
|
83
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Time</th>
|
|
84
84
|
</tr>
|
|
85
85
|
</thead>
|
|
86
86
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
87
87
|
<% @recent_events.each do |event| %>
|
|
88
|
-
<tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(
|
|
88
|
+
<tr class="hover:bg-gray-50 cursor-pointer" data-event-viewer-href="<%= rails_event_viewer.event_path(event[:id]) %>">
|
|
89
89
|
<td class="px-6 py-4 whitespace-nowrap">
|
|
90
|
-
<%= link_to
|
|
90
|
+
<%= link_to event[:name], rails_event_viewer.event_path(event[:id]),
|
|
91
91
|
class: "inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-indigo-100 text-indigo-800" %>
|
|
92
92
|
</td>
|
|
93
93
|
<td class="px-6 py-4 text-sm text-gray-500">
|
|
94
94
|
<code class="text-xs bg-gray-100 px-2 py-1 rounded"><%= truncate_json(event_payload(event), length: 60) %></code>
|
|
95
95
|
</td>
|
|
96
96
|
<td class="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
|
97
|
-
<%= time_ago_with_title(
|
|
97
|
+
<%= time_ago_with_title(event[:occurred_at]) %>
|
|
98
98
|
</td>
|
|
99
99
|
</tr>
|
|
100
100
|
<% end %>
|
|
@@ -103,10 +103,10 @@
|
|
|
103
103
|
</div>
|
|
104
104
|
<% else %>
|
|
105
105
|
<div class="px-6 py-12 text-center text-gray-500">
|
|
106
|
-
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
106
|
+
<svg aria-hidden="true" class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
107
107
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2M9 5a2 2 0 002 2h2a2 2 0 002-2M9 5a2 2 0 012-2h2a2 2 0 012 2" />
|
|
108
108
|
</svg>
|
|
109
|
-
<
|
|
109
|
+
<h2 class="mt-2 text-sm font-medium text-gray-900">No events yet</h2>
|
|
110
110
|
<p class="mt-1 text-sm text-gray-500">Events will appear here once they are captured.</p>
|
|
111
111
|
</div>
|
|
112
112
|
<% end %>
|
|
@@ -13,10 +13,10 @@
|
|
|
13
13
|
<table class="min-w-full divide-y divide-gray-200">
|
|
14
14
|
<thead class="bg-gray-50">
|
|
15
15
|
<tr>
|
|
16
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
|
|
17
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
|
|
18
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
|
|
19
|
-
<th class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"></th>
|
|
16
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Event Type</th>
|
|
17
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Count</th>
|
|
18
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">Last Event</th>
|
|
19
|
+
<th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"><span class="sr-only">Actions</span></th>
|
|
20
20
|
</tr>
|
|
21
21
|
</thead>
|
|
22
22
|
<tbody class="bg-white divide-y divide-gray-200">
|
|
@@ -49,10 +49,10 @@
|
|
|
49
49
|
</div>
|
|
50
50
|
<% else %>
|
|
51
51
|
<div class="bg-white rounded-lg shadow px-6 py-12 text-center text-gray-500">
|
|
52
|
-
<svg class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
52
|
+
<svg aria-hidden="true" class="mx-auto h-12 w-12 text-gray-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
53
53
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 7h.01M7 3h5c.512 0 1.024.195 1.414.586l7 7a2 2 0 010 2.828l-7 7a2 2 0 01-2.828 0l-7-7A2 2 0 013 12V7a4 4 0 014-4z" />
|
|
54
54
|
</svg>
|
|
55
|
-
<
|
|
55
|
+
<h2 class="mt-2 text-sm font-medium text-gray-900">No event types</h2>
|
|
56
56
|
<p class="mt-1 text-sm text-gray-500">Event types will appear here once events are captured.</p>
|
|
57
57
|
</div>
|
|
58
58
|
<% end %>
|