rails_live_dashboard 0.1.2 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (27) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +12 -0
  3. data/app/assets/builds/rails_live_dashboard/application.css +1 -1
  4. data/app/components/rails_live_dashboard/job_status_badge_component.html.erb +3 -0
  5. data/app/components/rails_live_dashboard/job_status_badge_component.rb +21 -0
  6. data/app/controllers/rails_live_dashboard/jobs_controller.rb +12 -0
  7. data/app/helpers/rails_live_dashboard/jobs_helper.rb +29 -0
  8. data/app/models/rails_live_dashboard/current.rb +10 -0
  9. data/app/models/rails_live_dashboard/job.rb +15 -0
  10. data/app/models/rails_live_dashboard/types/job_content.rb +49 -0
  11. data/app/views/rails_live_dashboard/jobs/_queries.html.erb +11 -0
  12. data/app/views/rails_live_dashboard/jobs/_relateds.html.erb +16 -0
  13. data/app/views/rails_live_dashboard/jobs/_tabs.html.erb +14 -0
  14. data/app/views/rails_live_dashboard/jobs/index.html.erb +54 -0
  15. data/app/views/rails_live_dashboard/jobs/show.html.erb +65 -0
  16. data/app/views/rails_live_dashboard/shared/_header.html.erb +2 -0
  17. data/config/routes.rb +1 -0
  18. data/lib/rails_live_dashboard/engine.rb +4 -0
  19. data/lib/rails_live_dashboard/exceptions/entry_not_found.rb +9 -0
  20. data/lib/rails_live_dashboard/recorders/jobs/discard_recorder.rb +46 -0
  21. data/lib/rails_live_dashboard/recorders/jobs/enqueue_recorder.rb +40 -0
  22. data/lib/rails_live_dashboard/recorders/jobs/perform_recorder.rb +46 -0
  23. data/lib/rails_live_dashboard/recorders/jobs/start_recorder.rb +41 -0
  24. data/lib/rails_live_dashboard/subscribers/active_job_subscriber.rb +52 -0
  25. data/lib/rails_live_dashboard/version.rb +1 -1
  26. data/lib/rails_live_dashboard.rb +6 -0
  27. metadata +21 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 681e481e630cd45d6418af3965fa1e68c73382fc2fce84989c1c2ec25fc913d4
4
- data.tar.gz: 010a88562af62e9a674abcc40b9832f6b12c1a8a1e6eaafe717345612f4a8855
3
+ metadata.gz: 69a7b82a9771de7bdd92f6533e90ccd756df95f0bcc90d596ac67e4e1c7293f1
4
+ data.tar.gz: 3a0c008151f505f0b0a78519e7d180405e66b3ddcd9c0a4f2655ad4b9de049ad
5
5
  SHA512:
6
- metadata.gz: 84de4cbcb1fd5b8ad210eb1d39a6ca93e146e52529349f06457d863d3d4a8697a61f101fd23c265ae97eb70b8838acdaaf2c1b036e647e1d98b28598afcc4c59
7
- data.tar.gz: 21e8131e50bc888b5e4e78bdd9d9a649b198f29f0ed8c0c999c588bd3c4af64fd1c53f9ae499037aafa2b37526c17fb15461423a1a71af77ab1e3c16836cc87e
6
+ metadata.gz: 1e81b72444d9fda7991414e07f48ce2b0fdce26ba7275dc37d78e18737a8d55190a1084b7749283814e1906443322c318e86e29f0ca9141d57fbcf7f47841818
7
+ data.tar.gz: a36fafe7403b6e7bc5aae9da1f89e4a3843b12eab024e956764d70628d5b047bb0f70c1ec5c4038c82665226f93bf964a9ef7bab0e3e97145b3d9838bb4d14b9
data/README.md CHANGED
@@ -4,6 +4,18 @@
4
4
 
5
5
  RailsLiveDashboard is a real-time debug dashboard for Rails applications. It provides insights into requests, queries, and exceptions, empowering developers to debug their applications effectively in development environments.
6
6
 
7
+ ## Modules Implemented
8
+
9
+ - Dashboard: Show environment ruby and rails version, slowest requests and slowest queries.
10
+
11
+ - Requests: Show requests data, like controller, action, status code, params and duration.
12
+
13
+ - Exceptions: Show exception data, like traces and file.
14
+
15
+ - Queries: Show queries data, like SQL and duration.
16
+
17
+ - Jobs: Show jobs data, like name, job_id, paramers, history, status and duration.
18
+
7
19
  **Note: This gem is not recommended for production use, as it may potentially slow down your application and significantly increase your database size.**
8
20
 
9
21
  ![Dashboard screen](docs/dashboard.png "Dashboard screen")
@@ -1 +1 @@
1
- /*! tailwindcss v3.4.1 | 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:initial}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;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-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]{display:none}*,::backdrop,: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:#3b82f680;--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: }.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}.relative{position:relative}.isolate{isolation:isolate}.z-10{z-index:10}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-my-2{margin-top:-.5rem;margin-bottom:-.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.flow-root{display:flow-root}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-16{height:4rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-16{width:4rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.flex-shrink-0,.shrink-0{flex-shrink:0}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-10{gap:2.5rem}.gap-5{gap:1.25rem}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-x-1\.5{-moz-column-gap:.375rem;column-gap:.375rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.space-y-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*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))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(209 213 219/var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-orange-700{--tw-gradient-from:#c2410c var(--tw-gradient-from-position);--tw-gradient-to:#c2410c00 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-red-700{--tw-gradient-from:#b91c1c var(--tw-gradient-from-position);--tw-gradient-to:#b91c1c00 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-orange-500{--tw-gradient-to:#f97316 var(--tw-gradient-to-position)}.to-red-500{--tw-gradient-to:#ef4444 var(--tw-gradient-to-position)}.fill-blue-500{fill:#3b82f6}.fill-green-500{fill:#22c55e}.fill-indigo-500{fill:#6366f1}.fill-purple-500{fill:#a855f7}.fill-red-500{fill:#ef4444}.fill-yellow-500{fill:#eab308}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-10{padding-bottom:2.5rem}.pb-3{padding-bottom:.75rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.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-medium{font-weight:500}.font-semibold{font-weight:600}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.tracking-tight{letter-spacing:-.025em}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity))}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--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 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.ring-1{--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(1px + 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)}.ring-inset{--tw-ring-inset:inset}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-blue-700\/10{--tw-ring-color:#1d4ed81a}.ring-gray-200{--tw-ring-opacity:1;--tw-ring-color:rgb(229 231 235/var(--tw-ring-opacity))}.ring-gray-900\/5{--tw-ring-color:#1118270d}.ring-green-600\/20{--tw-ring-color:#16a34a33}.ring-orange-600\/10{--tw-ring-color:#ea580c1a}.ring-red-600\/10{--tw-ring-color:#dc26261a}.ring-yellow-600\/20{--tw-ring-color:#ca8a0433}.ring-opacity-5{--tw-ring-opacity:0.05}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;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-inset:focus{--tw-ring-inset:inset}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:mt-0{margin-top:0}.sm\:flex{display:flex}.sm\:grid{display:grid}.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\:flex-col{flex-direction:column}.sm\:items-end{align-items:flex-end}.sm\:gap-4{gap:1rem}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-6{padding:1.5rem}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:px-6{padding-right:1.5rem}.sm\:pl-6,.sm\:px-6{padding-left:1.5rem}.sm\:pr-6{padding-right:1.5rem}}@media (min-width:1024px){.lg\:relative{position:relative}.lg\:z-10{z-index:10}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:ml-4{margin-left:1rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:items-center{align-items:center}.lg\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.lg\: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))}.lg\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:py-2{padding-top:.5rem;padding-bottom:.5rem}}
1
+ /*! tailwindcss v3.4.1 | 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:initial}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;color:inherit;margin:0;padding:0}button,select{text-transform:none}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button;background-color:initial;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:initial}::-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]{display:none}*,::backdrop,: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:#3b82f680;--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: }.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}.absolute{position:absolute}.relative{position:relative}.left-4{left:1rem}.top-4{top:1rem}.isolate{isolation:isolate}.z-10{z-index:10}.-mx-4{margin-left:-1rem;margin-right:-1rem}.-my-2{margin-top:-.5rem;margin-bottom:-.5rem}.mx-auto{margin-left:auto;margin-right:auto}.my-2{margin-top:.5rem;margin-bottom:.5rem}.-mb-8{margin-bottom:-2rem}.-ml-px{margin-left:-1px}.mt-1{margin-top:.25rem}.mt-10{margin-top:2.5rem}.mt-2{margin-top:.5rem}.mt-3{margin-top:.75rem}.mt-5{margin-top:1.25rem}.mt-8{margin-top:2rem}.block{display:block}.inline-block{display:inline-block}.flex{display:flex}.inline-flex{display:inline-flex}.table{display:table}.flow-root{display:flow-root}.grid{display:grid}.contents{display:contents}.hidden{display:none}.h-1{height:.25rem}.h-1\.5{height:.375rem}.h-16{height:4rem}.h-5{height:1.25rem}.h-6{height:1.5rem}.h-64{height:16rem}.h-8{height:2rem}.h-full{height:100%}.w-0{width:0}.w-0\.5{width:.125rem}.w-1{width:.25rem}.w-1\.5{width:.375rem}.w-1\/2{width:50%}.w-1\/3{width:33.333333%}.w-16{width:4rem}.w-5{width:1.25rem}.w-6{width:1.5rem}.w-8{width:2rem}.w-full{width:100%}.min-w-0{min-width:0}.min-w-full{min-width:100%}.max-w-7xl{max-width:80rem}.flex-1{flex:1 1 0%}.flex-auto{flex:1 1 auto}.flex-none{flex:none}.flex-shrink-0,.shrink-0{flex-shrink:0}@keyframes spin{to{transform:rotate(1turn)}}.animate-spin{animation:spin 1s linear infinite}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.justify-center{justify-content:center}.justify-between{justify-content:space-between}.gap-10{gap:2.5rem}.gap-5{gap:1.25rem}.gap-8{gap:2rem}.gap-x-1{-moz-column-gap:.25rem;column-gap:.25rem}.gap-x-1\.5{-moz-column-gap:.375rem;column-gap:.375rem}.gap-x-4{-moz-column-gap:1rem;column-gap:1rem}.gap-x-6{-moz-column-gap:1.5rem;column-gap:1.5rem}.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-1>:not([hidden])~:not([hidden]){--tw-space-y-reverse:0;margin-top:calc(.25rem*(1 - var(--tw-space-y-reverse)));margin-bottom:calc(.25rem*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))}.divide-gray-200>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(229 231 235/var(--tw-divide-opacity))}.divide-gray-300>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(209 213 219/var(--tw-divide-opacity))}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.truncate{overflow:hidden;text-overflow:ellipsis}.truncate,.whitespace-nowrap{white-space:nowrap}.rounded-full{border-radius:9999px}.rounded-lg{border-radius:.5rem}.rounded-md{border-radius:.375rem}.rounded-t-lg{border-top-left-radius:.5rem;border-top-right-radius:.5rem}.border-b-2{border-bottom-width:2px}.border-t{border-top-width:1px}.border-gray-100{--tw-border-opacity:1;border-color:rgb(243 244 246/var(--tw-border-opacity))}.border-gray-500{--tw-border-opacity:1;border-color:rgb(107 114 128/var(--tw-border-opacity))}.border-indigo-500{--tw-border-opacity:1;border-color:rgb(99 102 241/var(--tw-border-opacity))}.bg-blue-50{--tw-bg-opacity:1;background-color:rgb(239 246 255/var(--tw-bg-opacity))}.bg-gray-200{--tw-bg-opacity:1;background-color:rgb(229 231 235/var(--tw-bg-opacity))}.bg-gray-50{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.bg-gray-800{--tw-bg-opacity:1;background-color:rgb(31 41 55/var(--tw-bg-opacity))}.bg-gray-900{--tw-bg-opacity:1;background-color:rgb(17 24 39/var(--tw-bg-opacity))}.bg-green-50{--tw-bg-opacity:1;background-color:rgb(240 253 244/var(--tw-bg-opacity))}.bg-orange-50{--tw-bg-opacity:1;background-color:rgb(255 247 237/var(--tw-bg-opacity))}.bg-red-50{--tw-bg-opacity:1;background-color:rgb(254 242 242/var(--tw-bg-opacity))}.bg-white{--tw-bg-opacity:1;background-color:rgb(255 255 255/var(--tw-bg-opacity))}.bg-yellow-50{--tw-bg-opacity:1;background-color:rgb(254 252 232/var(--tw-bg-opacity))}.bg-gradient-to-r{background-image:linear-gradient(to right,var(--tw-gradient-stops))}.from-orange-700{--tw-gradient-from:#c2410c var(--tw-gradient-from-position);--tw-gradient-to:#c2410c00 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.from-red-700{--tw-gradient-from:#b91c1c var(--tw-gradient-from-position);--tw-gradient-to:#b91c1c00 var(--tw-gradient-to-position);--tw-gradient-stops:var(--tw-gradient-from),var(--tw-gradient-to)}.to-orange-500{--tw-gradient-to:#f97316 var(--tw-gradient-to-position)}.to-red-500{--tw-gradient-to:#ef4444 var(--tw-gradient-to-position)}.fill-blue-500{fill:#3b82f6}.fill-green-500{fill:#22c55e}.fill-indigo-500{fill:#6366f1}.fill-purple-500{fill:#a855f7}.fill-red-500{fill:#ef4444}.fill-yellow-500{fill:#eab308}.p-1{padding:.25rem}.p-1\.5{padding:.375rem}.p-2{padding:.5rem}.p-4{padding:1rem}.p-5{padding:1.25rem}.px-1{padding-left:.25rem;padding-right:.25rem}.px-2{padding-left:.5rem;padding-right:.5rem}.px-3{padding-left:.75rem;padding-right:.75rem}.px-4{padding-left:1rem;padding-right:1rem}.py-1{padding-top:.25rem;padding-bottom:.25rem}.py-10{padding-top:2.5rem;padding-bottom:2.5rem}.py-2{padding-top:.5rem;padding-bottom:.5rem}.py-3{padding-top:.75rem;padding-bottom:.75rem}.py-3\.5{padding-top:.875rem;padding-bottom:.875rem}.py-4{padding-top:1rem;padding-bottom:1rem}.py-5{padding-top:1.25rem;padding-bottom:1.25rem}.py-6{padding-top:1.5rem;padding-bottom:1.5rem}.pb-10{padding-bottom:2.5rem}.pb-3{padding-bottom:.75rem}.pb-8{padding-bottom:2rem}.pl-3{padding-left:.75rem}.pl-4{padding-left:1rem}.pr-3{padding-right:.75rem}.pr-4{padding-right:1rem}.pt-1{padding-top:.25rem}.pt-1\.5{padding-top:.375rem}.pt-2{padding-top:.5rem}.text-left{text-align:left}.text-center{text-align:center}.text-right{text-align:right}.align-middle{vertical-align:middle}.text-3xl{font-size:1.875rem;line-height:2.25rem}.text-base{font-size:1rem;line-height:1.5rem}.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-medium{font-weight:500}.font-semibold{font-weight:600}.leading-5{line-height:1.25rem}.leading-6{line-height:1.5rem}.leading-7{line-height:1.75rem}.tracking-tight{letter-spacing:-.025em}.text-black{--tw-text-opacity:1;color:rgb(0 0 0/var(--tw-text-opacity))}.text-blue-700{--tw-text-opacity:1;color:rgb(29 78 216/var(--tw-text-opacity))}.text-gray-300{--tw-text-opacity:1;color:rgb(209 213 219/var(--tw-text-opacity))}.text-gray-400{--tw-text-opacity:1;color:rgb(156 163 175/var(--tw-text-opacity))}.text-gray-500{--tw-text-opacity:1;color:rgb(107 114 128/var(--tw-text-opacity))}.text-gray-700{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.text-gray-800{--tw-text-opacity:1;color:rgb(31 41 55/var(--tw-text-opacity))}.text-gray-900{--tw-text-opacity:1;color:rgb(17 24 39/var(--tw-text-opacity))}.text-green-700{--tw-text-opacity:1;color:rgb(21 128 61/var(--tw-text-opacity))}.text-indigo-600{--tw-text-opacity:1;color:rgb(79 70 229/var(--tw-text-opacity))}.text-orange-700{--tw-text-opacity:1;color:rgb(194 65 12/var(--tw-text-opacity))}.text-red-700{--tw-text-opacity:1;color:rgb(185 28 28/var(--tw-text-opacity))}.text-red-800{--tw-text-opacity:1;color:rgb(153 27 27/var(--tw-text-opacity))}.text-white{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.text-yellow-800{--tw-text-opacity:1;color:rgb(133 77 14/var(--tw-text-opacity))}.shadow{--tw-shadow:0 1px 3px 0 #0000001a,0 1px 2px -1px #0000001a;--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 #0000000d;--tw-shadow-colored:0 1px 2px 0 var(--tw-shadow-color)}.ring-1{--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(1px + 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)}.ring-inset{--tw-ring-inset:inset}.ring-black{--tw-ring-opacity:1;--tw-ring-color:rgb(0 0 0/var(--tw-ring-opacity))}.ring-blue-700\/10{--tw-ring-color:#1d4ed81a}.ring-gray-200{--tw-ring-opacity:1;--tw-ring-color:rgb(229 231 235/var(--tw-ring-opacity))}.ring-gray-900\/5{--tw-ring-color:#1118270d}.ring-green-600\/20{--tw-ring-color:#16a34a33}.ring-green-700\/10{--tw-ring-color:#15803d1a}.ring-orange-600\/10{--tw-ring-color:#ea580c1a}.ring-orange-700\/10{--tw-ring-color:#c2410c1a}.ring-red-600\/10{--tw-ring-color:#dc26261a}.ring-red-600\/20{--tw-ring-color:#dc262633}.ring-red-700\/10{--tw-ring-color:#b91c1c1a}.ring-yellow-600\/20{--tw-ring-color:#ca8a0433}.ring-opacity-5{--tw-ring-opacity:0.05}.hover\:bg-gray-50:hover{--tw-bg-opacity:1;background-color:rgb(249 250 251/var(--tw-bg-opacity))}.hover\:bg-gray-700:hover{--tw-bg-opacity:1;background-color:rgb(55 65 81/var(--tw-bg-opacity))}.hover\:text-gray-700:hover{--tw-text-opacity:1;color:rgb(55 65 81/var(--tw-text-opacity))}.hover\:text-indigo-900:hover{--tw-text-opacity:1;color:rgb(49 46 129/var(--tw-text-opacity))}.hover\:text-white:hover{--tw-text-opacity:1;color:rgb(255 255 255/var(--tw-text-opacity))}.focus\:outline-none:focus{outline:2px solid #0000;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-inset:focus{--tw-ring-inset:inset}.focus\:ring-white:focus{--tw-ring-opacity:1;--tw-ring-color:rgb(255 255 255/var(--tw-ring-opacity))}@media (min-width:640px){.sm\:col-span-2{grid-column:span 2/span 2}.sm\:-mx-6{margin-left:-1.5rem;margin-right:-1.5rem}.sm\:mt-0{margin-top:0}.sm\:flex{display:flex}.sm\:grid{display:grid}.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\:flex-col{flex-direction:column}.sm\:items-end{align-items:flex-end}.sm\:gap-4{gap:1rem}.sm\:rounded-lg{border-radius:.5rem}.sm\:p-6{padding:1.5rem}.sm\:px-4{padding-left:1rem;padding-right:1rem}.sm\:px-6{padding-right:1.5rem}.sm\:pl-6,.sm\:px-6{padding-left:1.5rem}.sm\:pr-6{padding-right:1.5rem}}@media (min-width:768px){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (min-width:1024px){.lg\:relative{position:relative}.lg\:z-10{z-index:10}.lg\:-mx-8{margin-left:-2rem;margin-right:-2rem}.lg\:ml-4{margin-left:1rem}.lg\:flex{display:flex}.lg\:hidden{display:none}.lg\:items-center{align-items:center}.lg\:space-x-8>:not([hidden])~:not([hidden]){--tw-space-x-reverse:0;margin-right:calc(2rem*var(--tw-space-x-reverse));margin-left:calc(2rem*(1 - var(--tw-space-x-reverse)))}.lg\: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))}.lg\:divide-gray-700>:not([hidden])~:not([hidden]){--tw-divide-opacity:1;border-color:rgb(55 65 81/var(--tw-divide-opacity))}.lg\:px-0{padding-left:0;padding-right:0}.lg\:px-8{padding-left:2rem;padding-right:2rem}.lg\:py-2{padding-top:.5rem;padding-bottom:.5rem}}
@@ -0,0 +1,3 @@
1
+ <span class="<%= class_names('inline-flex items-center rounded-md px-3 py-2 text-xs font-semibold ring-1 ring-inset', @classes) %>">
2
+ <%= @status %>
3
+ </span>
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RailsLiveDashboard
4
+ class JobStatusBadgeComponent < ViewComponent::Base
5
+ CLASSES = {
6
+ enqueued: 'bg-orange-50 text-orange-700 ring-orange-700/10',
7
+ started: 'bg-blue-50 text-blue-700 ring-blue-700/10',
8
+ performed: 'bg-green-50 text-green-700 ring-green-600/20',
9
+ discarted: 'bg-red-50 text-red-800 ring-red-600/20'
10
+ }.freeze
11
+
12
+ def initialize(status)
13
+ super
14
+
15
+ return if status.nil?
16
+
17
+ @status = status.upcase
18
+ @classes = CLASSES[status.downcase.to_sym]
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ module RailsLiveDashboard
2
+ class JobsController < ApplicationController
3
+ def index
4
+ @jobs = Job.order(created_at: :desc)
5
+ end
6
+
7
+ def show
8
+ @job = Job.find(params[:id])
9
+ @queries = Query.where(batch_id: @job.batch_id)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,29 @@
1
+ module RailsLiveDashboard
2
+ module JobsHelper
3
+ def job_history_icon(status)
4
+ case status
5
+ when 'enqueued'
6
+ lucide_icon('list-start',
7
+ class: 'h-8 w-8 p-1.5 rounded-full flex items-center justify-center ring-1 ring-inset bg-orange-50 text-orange-700 ring-orange-700/10')
8
+ when 'started'
9
+ lucide_icon('list-video',
10
+ class: 'h-8 w-8 p-1.5 rounded-full flex items-center justify-center ring-1 ring-inset bg-blue-50 text-blue-700 ring-blue-700/10')
11
+ when 'performed'
12
+ lucide_icon('check',
13
+ class: 'h-8 w-8 p-1.5 rounded-full flex items-center justify-center ring-1 ring-inset bg-green-50 text-green-700 ring-green-700/10')
14
+ when 'discarted'
15
+ lucide_icon('trash',
16
+ class: 'h-8 w-8 p-1.5 rounded-full flex items-center justify-center ring-1 ring-inset bg-red-50 text-red-700 ring-red-700/10')
17
+ else
18
+ lucide_icon('refresh-cw',
19
+ class: 'h-8 w-8 p-1.5 rounded-full flex items-center justify-center ring-1 ring-inset bg-orange-50 text-orange-700 ring-orange-700/10')
20
+ end
21
+ end
22
+
23
+ def job_duration_text(duration)
24
+ return "#{duration} ms" if duration < 1000
25
+
26
+ "#{(duration / 1000).round} s"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,10 @@
1
+ module RailsLiveDashboard
2
+ class Current < ActiveSupport::CurrentAttributes
3
+ attribute :batch_id
4
+
5
+ def initialize
6
+ super
7
+ self.batch_id = SecureRandom.uuid
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,15 @@
1
+ module RailsLiveDashboard
2
+ class Job < Entry
3
+ attribute :content, Types::JobContent.new
4
+
5
+ scope :same, ->(job_id) { where("content->>'job_id' = ?", job_id) }
6
+
7
+ def performed?
8
+ content.status == 'performed'
9
+ end
10
+
11
+ def discarted?
12
+ content.status == 'discarted'
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,49 @@
1
+ module RailsLiveDashboard
2
+ module Types
3
+ class JobContent < ActiveRecord::Type::Value
4
+ PAYLOAD =
5
+ %i[
6
+ job_name
7
+ job_id
8
+ params
9
+ status
10
+ queue_name
11
+ started_at
12
+ finished_at
13
+ duration
14
+ db_duration
15
+ error
16
+ history
17
+ ].freeze
18
+
19
+ CONTENT_STRUCT = Struct.new(*PAYLOAD)
20
+
21
+ def type
22
+ :jsonb
23
+ end
24
+
25
+ def cast(value)
26
+ return value if value.is_a?(CONTENT_STRUCT)
27
+
28
+ sanitized = sanitize_input(value)
29
+ CONTENT_STRUCT.new(**sanitized)
30
+ end
31
+
32
+ def deserialize(value)
33
+ decoded = ActiveSupport::JSON.decode(value)&.symbolize_keys
34
+ sanitized = sanitize_input(decoded)
35
+ CONTENT_STRUCT.new(**sanitized)
36
+ end
37
+
38
+ def serialize(value)
39
+ ActiveSupport::JSON.encode(value)
40
+ end
41
+
42
+ private
43
+
44
+ def sanitize_input(input)
45
+ input.slice(*PAYLOAD)
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,11 @@
1
+ <% if @queries.empty? %>
2
+ <p class="py-10 text-center">No queries for this job</p>
3
+ <% else %>
4
+ <div class="flow-root">
5
+ <div class="overflow-x-auto">
6
+ <div class="inline-block min-w-full align-middle">
7
+ <%= render 'rails_live_dashboard/queries/list' %>
8
+ </div>
9
+ </div>
10
+ </div>
11
+ <% end %>
@@ -0,0 +1,16 @@
1
+ <div
2
+ class="overflow-hidden bg-white shadow sm:rounded-lg"
3
+ data-controller="tabs"
4
+ data-tabs-active-class="border-indigo-500 text-indigo-600"
5
+ data-tabs-default-tab-value="tab_queries"
6
+ >
7
+ <nav class="isolate flex rounded-t-lg shadow" aria-label="Tabs">
8
+ <button id="tab_queries" class="w-full text-gray-500 hover:text-gray-700 border-b-2 py-4 px-1 text-center text-sm font-medium" data-tabs-target="button" data-action="click->tabs#select">
9
+ Queries (<%= @queries.size %>)
10
+ </button>
11
+ </nav>
12
+
13
+ <div data-tabs-target="tab" id="tab_queries">
14
+ <%= render 'queries' %>
15
+ </div>
16
+ </div>
@@ -0,0 +1,14 @@
1
+ <div
2
+ class="overflow-hidden bg-white shadow sm:rounded-lg"
3
+ data-controller="tabs"
4
+ data-tabs-active-class="border-indigo-500 text-indigo-600"
5
+ data-tabs-default-tab-value="tab_params"
6
+ >
7
+ <nav class="isolate flex rounded-t-lg shadow" aria-label="Tabs">
8
+ <button id="tab_params" class="w-full text-gray-500 hover:text-gray-700 border-b-2 py-4 px-1 text-center text-sm font-medium" data-tabs-target="button" data-action="click->tabs#select">Params</button>
9
+ </nav>
10
+
11
+ <div class="p-5 bg-gray-800 text-white">
12
+ <pre data-tabs-target="tab" id="tab_params"><%= JSON.pretty_generate(@job.content.params) %></pre>
13
+ </div>
14
+ </div>
@@ -0,0 +1,54 @@
1
+ <div class="mt-10">
2
+ <div class="mt-8 flow-root">
3
+ <div class="-mx-4 -my-2 overflow-x-auto sm:-mx-6 lg:-mx-8">
4
+ <div class="inline-block min-w-full py-2 align-middle sm:px-6 lg:px-8">
5
+ <div class="overflow-hidden shadow ring-1 ring-black ring-opacity-5 sm:rounded-lg">
6
+ <table class="min-w-full divide-y divide-gray-300">
7
+ <thead class="bg-gray-50">
8
+ <tr>
9
+ <th scope="col" class="py-3.5 pl-4 pr-3 text-left text-sm font-semibold text-gray-900 sm:pl-6">Job</th>
10
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Status</th>
11
+ <th scope="col" class="px-3 py-3.5 text-left text-sm font-semibold text-gray-900">Date</th>
12
+ <th scope="col" class="relative py-3.5 pl-3 pr-4 sm:pr-6">
13
+ <span class="sr-only">Show</span>
14
+ </th>
15
+ </tr>
16
+ </thead>
17
+ <tbody class="divide-y divide-gray-200 bg-white">
18
+ <% if @jobs.empty? %>
19
+ <tr>
20
+ <td colspan="4">
21
+ <p class="p-5 text-center">No job registered</p>
22
+ </td>
23
+ </tr>
24
+ <% end %>
25
+ <% @jobs.each do |job| %>
26
+ <tr>
27
+ <td class="whitespace-nowrap py-4 pl-4 pr-3 sm:pl-6">
28
+ <div class="min-w-0 flex-auto">
29
+ <p class="text-base font-semibold text-gray-900">
30
+ <%= job.content.job_name %>
31
+ </p>
32
+ <p class="mt-1 text-sm text-gray-500">
33
+ Job ID: <%= job.content.job_id %>
34
+ </p>
35
+ </div>
36
+ </td>
37
+ <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
38
+ <%= render RailsLiveDashboard::JobStatusBadgeComponent.new(job.content.status) %>
39
+ </td>
40
+ <td class="whitespace-nowrap px-3 py-4 text-sm text-gray-500">
41
+ <%= job.created_at.strftime("%Y-%m-%d %H:%M:%S") %>
42
+ </td>
43
+ <td class="relative whitespace-nowrap py-4 pl-3 pr-4 text-right text-sm font-medium sm:pr-6">
44
+ <%= link_to 'Show', rails_live_dashboard.job_path(job), class: 'text-indigo-600 hover:text-indigo-900' %>
45
+ </td>
46
+ </tr>
47
+ <% end %>
48
+ </tbody>
49
+ </table>
50
+ </div>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ </div>
@@ -0,0 +1,65 @@
1
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-8 mt-10">
2
+ <div class="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
3
+ <div class="px-4 py-5 sm:px-6">
4
+ <h3 class="text-lg font-semibold text-gray-900"><%= @job.content.job_name %></h3>
5
+ <p class="mt-1 text-gray-500">Job ID: <%= @job.content.job_id %></p>
6
+ </div>
7
+
8
+ <dl class="divide-y divide-gray-100">
9
+ <div class="p-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
10
+ <dt class="text-sm font-medium text-gray-900">Date</dt>
11
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><%= @job.created_at.strftime("%Y-%m-%d %H:%M:%S") %></dd>
12
+ </div>
13
+
14
+ <% if @job.performed? || @job.discarted? %>
15
+ <div class="p-4 sm:grid sm:grid-cols-3 sm:gap-4 sm:px-6">
16
+ <dt class="text-sm font-medium text-gray-900">Duration</dt>
17
+ <dd class="mt-1 text-sm leading-6 text-gray-700 sm:col-span-2 sm:mt-0"><%= job_duration_text(@job.content.duration) %></dd>
18
+ </div>
19
+ <% end %>
20
+ </dl>
21
+ </div>
22
+
23
+ <div class="divide-y divide-gray-200 overflow-hidden rounded-lg bg-white shadow">
24
+ <div class="px-4 py-5 sm:px-6">
25
+ <p class="font-semibold">Job History</p>
26
+ </div>
27
+ <div class="px-4 py-5 sm:p-6">
28
+ <div class="flow-root">
29
+ <ul role="list" class="-mb-8">
30
+ <% @job.content.history.each do |history| %>
31
+ <li>
32
+ <div class="relative pb-8">
33
+ <% unless history == @job.content.history.last %>
34
+ <span class="absolute left-4 top-4 -ml-px h-full w-0.5 bg-gray-200" aria-hidden="true"></span>
35
+ <% end %>
36
+
37
+ <div class="relative flex space-x-3">
38
+ <div>
39
+ <%= job_history_icon(history["status"]) %>
40
+ </div>
41
+ <div class="flex min-w-0 flex-1 justify-between space-x-4 pt-1.5">
42
+ <div>
43
+ <p class="text-sm text-gray-500">Changed status to <b class="text-black"><%= history["status"] %></b></p>
44
+ </div>
45
+ <div class="whitespace-nowrap text-right text-sm text-gray-500">
46
+ <time><%= DateTime.parse(history["date"]).strftime("%Y-%m-%d %H:%M:%S") %></time>
47
+ </div>
48
+ </div>
49
+ </div>
50
+ </div>
51
+ </li>
52
+ <% end %>
53
+ </ul>
54
+ </div>
55
+ </div>
56
+ </div>
57
+ </div>
58
+
59
+ <div class="mt-10">
60
+ <%= render 'tabs' %>
61
+ </div>
62
+
63
+ <div class="mt-10">
64
+ <%= render 'relateds' %>
65
+ </div>
@@ -25,6 +25,7 @@
25
25
  <%= link_to 'Requests', rails_live_dashboard.requests_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white inline-flex items-center rounded-md py-2 px-3 text-sm font-medium', 'bg-gray-900 text-white': current_page?(requests_path)) %>
26
26
  <%= link_to 'Exceptions', rails_live_dashboard.exceptions_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white inline-flex items-center rounded-md py-2 px-3 text-sm font-medium', 'bg-gray-900 text-white': current_page?(exceptions_path)) %>
27
27
  <%= link_to 'Queries', rails_live_dashboard.queries_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white inline-flex items-center rounded-md py-2 px-3 text-sm font-medium', 'bg-gray-900 text-white': current_page?(queries_path)) %>
28
+ <%= link_to 'Jobs', rails_live_dashboard.jobs_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white inline-flex items-center rounded-md py-2 px-3 text-sm font-medium', 'bg-gray-900 text-white': current_page?(jobs_path)) %>
28
29
  </nav>
29
30
  </div>
30
31
 
@@ -38,6 +39,7 @@
38
39
  <%= link_to 'Requests', rails_live_dashboard.requests_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md py-2 px-3 text-base font-medium', 'bg-gray-900 text-white': current_page?(requests_path)) %>
39
40
  <%= link_to 'Exceptions', rails_live_dashboard.exceptions_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md py-2 px-3 text-base font-medium', 'bg-gray-900 text-white': current_page?(exceptions_path)) %>
40
41
  <%= link_to 'Queries', rails_live_dashboard.queries_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md py-2 px-3 text-base font-medium', 'bg-gray-900 text-white': current_page?(queries_path)) %>
42
+ <%= link_to 'Jobs', rails_live_dashboard.jobs_path, class: class_names('text-gray-300 hover:bg-gray-700 hover:text-white block rounded-md py-2 px-3 text-base font-medium', 'bg-gray-900 text-white': current_page?(jobs_path)) %>
41
43
  </div>
42
44
  </nav>
43
45
  </header>
data/config/routes.rb CHANGED
@@ -6,6 +6,7 @@ RailsLiveDashboard::Engine.routes.draw do
6
6
  resources :requests, only: %i[index show]
7
7
  resources :exceptions, only: %i[index show]
8
8
  resources :queries, only: %i[index show]
9
+ resources :jobs, only: %i[index show]
9
10
 
10
11
  namespace :widgets do
11
12
  get 'slowest-requests', action: :show, controller: :slowest_requests
@@ -24,5 +24,9 @@ module RailsLiveDashboard
24
24
  initializer 'rails_live_dashboard.action_record' do
25
25
  Subscribers::ActiveRecordSubscriber.new
26
26
  end
27
+
28
+ initializer 'rails_live_dashboard.action_job' do
29
+ Subscribers::ActiveJobSubscriber.new
30
+ end
27
31
  end
28
32
  end
@@ -0,0 +1,9 @@
1
+ module RailsLiveDashboard
2
+ module Exceptions
3
+ class EntryNotFound < StandardError
4
+ def initialize(type, id)
5
+ super("#{type}(id: #{id}) entry not found")
6
+ end
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,46 @@
1
+ module RailsLiveDashboard
2
+ module Recorders
3
+ module Jobs
4
+ class DiscardRecorder
5
+ def initialize(event)
6
+ @event = event
7
+ @job = Job.same(@event.payload[:job].job_id).first
8
+ end
9
+
10
+ def execute
11
+ raise Exceptions::EntryNotFound.new(:job, @event.payload[:job].job_id) if @job.nil?
12
+
13
+ build_content
14
+ build_history
15
+
16
+ @job.update(content:)
17
+ end
18
+
19
+ private
20
+
21
+ def content
22
+ @content ||= @job.content
23
+ end
24
+
25
+ def build_content
26
+ content.status = :discarded
27
+ content.finished_at = Time.now
28
+ content.duration = duration
29
+ end
30
+
31
+ def build_history
32
+ content.history.push({
33
+ status: :discarded,
34
+ date: Time.now
35
+ })
36
+ end
37
+
38
+ def duration
39
+ return 0 if content.started_at.nil?
40
+
41
+ (content.finished_at - Time.parse(content.started_at)).in_milliseconds.round
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,40 @@
1
+ module RailsLiveDashboard
2
+ module Recorders
3
+ module Jobs
4
+ class EnqueueRecorder
5
+ def initialize(event)
6
+ @event = event
7
+ end
8
+
9
+ def execute
10
+ Job.create(
11
+ batch_id: Current.batch_id,
12
+ content: build_content
13
+ )
14
+ end
15
+
16
+ private
17
+
18
+ def build_content
19
+ {
20
+ job_name: @event.payload[:job].class,
21
+ job_id: @event.payload[:job].job_id,
22
+ params: @event.payload[:job].arguments || {},
23
+ status: :enqueued,
24
+ queue_name: @event.payload[:job].queue_name,
25
+ history: build_history
26
+ }
27
+ end
28
+
29
+ def build_history
30
+ [
31
+ {
32
+ status: :enqueued,
33
+ date: Time.now
34
+ }
35
+ ]
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,46 @@
1
+ module RailsLiveDashboard
2
+ module Recorders
3
+ module Jobs
4
+ class PerformRecorder
5
+ def initialize(event)
6
+ @event = event
7
+ @job = Job.same(@event.payload[:job].job_id).first
8
+ end
9
+
10
+ def execute
11
+ raise Exceptions::EntryNotFound.new(:job, @event.payload[:job].job_id) if @job.nil?
12
+
13
+ build_content
14
+ build_history
15
+
16
+ @job.update(content:)
17
+ end
18
+
19
+ private
20
+
21
+ def content
22
+ @content ||= @job.content
23
+ end
24
+
25
+ def build_content
26
+ content.status = :performed
27
+ content.finished_at = Time.now
28
+ content.duration = duration
29
+ end
30
+
31
+ def build_history
32
+ content.history.push({
33
+ status: :performed,
34
+ date: Time.now
35
+ })
36
+ end
37
+
38
+ def duration
39
+ return 0 if content.started_at.nil?
40
+
41
+ (content.finished_at - Time.parse(content.started_at)).in_milliseconds.round
42
+ end
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,41 @@
1
+ module RailsLiveDashboard
2
+ module Recorders
3
+ module Jobs
4
+ class StartRecorder
5
+ def initialize(event)
6
+ @event = event
7
+ @job = Job.same(@event.payload[:job].job_id).first
8
+ end
9
+
10
+ def execute
11
+ raise Exceptions::EntryNotFound.new(:job, @event.payload[:job].job_id) if @job.nil?
12
+
13
+ Current.batch_id = @job.batch_id
14
+
15
+ build_content
16
+ build_history
17
+
18
+ @job.update(content:)
19
+ end
20
+
21
+ private
22
+
23
+ def content
24
+ @content ||= @job.content
25
+ end
26
+
27
+ def build_content
28
+ content.status = :started
29
+ content.started_at = Time.now
30
+ end
31
+
32
+ def build_history
33
+ content.history.push({
34
+ status: :started,
35
+ date: Time.now
36
+ })
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
1
+ module RailsLiveDashboard
2
+ module Subscribers
3
+ class ActiveJobSubscriber
4
+ def initialize
5
+ enqueue
6
+ start
7
+ perform
8
+ discard
9
+ end
10
+
11
+ private
12
+
13
+ def enqueue
14
+ ActiveSupport::Notifications.subscribe 'enqueue_at.active_job' do |event|
15
+ Recorders::Jobs::EnqueueRecorder.new(event).execute
16
+ rescue StandardError => e
17
+ Rails.logger.error "ActiveJobSubscriber - Error on handle enqueue job event: #{e.message}"
18
+ end
19
+
20
+ ActiveSupport::Notifications.subscribe 'enqueue.active_job' do |event|
21
+ Recorders::Jobs::EnqueueRecorder.new(event).execute
22
+ rescue StandardError => e
23
+ Rails.logger.error "ActiveJobSubscriber - Error on handle enqueue job event: #{e.message}"
24
+ end
25
+ end
26
+
27
+ def start
28
+ ActiveSupport::Notifications.subscribe 'perform_start.active_job' do |event|
29
+ Recorders::Jobs::StartRecorder.new(event).execute
30
+ rescue StandardError => e
31
+ Rails.logger.error "ActiveJobSubscriber - Error on handle perform_start job event: #{e.message}"
32
+ end
33
+ end
34
+
35
+ def perform
36
+ ActiveSupport::Notifications.subscribe 'perform.active_job' do |event|
37
+ Recorders::Jobs::PerformRecorder.new(event).execute
38
+ rescue StandardError => e
39
+ Rails.logger.error "ActiveJobSubscriber - Error on handle perform job event: #{e.message}"
40
+ end
41
+ end
42
+
43
+ def discard
44
+ ActiveSupport::Notifications.subscribe 'discard.active_job' do |event|
45
+ Recorders::Jobs::DiscardRecorder.new(event).execute
46
+ rescue StandardError => e
47
+ Rails.logger.error "ActiveJobSubscriber - Error on handle discard job event: #{e.message}"
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
@@ -1,3 +1,3 @@
1
1
  module RailsLiveDashboard
2
- VERSION = '0.1.2'
2
+ VERSION = '0.2.0'
3
3
  end
@@ -2,11 +2,17 @@ require 'rails_live_dashboard/version'
2
2
  require 'rails_live_dashboard/engine'
3
3
  require 'rails_live_dashboard/configuration'
4
4
  require 'rails_live_dashboard/context'
5
+ require 'rails_live_dashboard/exceptions/entry_not_found'
5
6
  require 'rails_live_dashboard/recorders/exception_recorder'
6
7
  require 'rails_live_dashboard/recorders/request_recorder'
7
8
  require 'rails_live_dashboard/recorders/query_recorder'
9
+ require 'rails_live_dashboard/recorders/jobs/discard_recorder'
10
+ require 'rails_live_dashboard/recorders/jobs/enqueue_recorder'
11
+ require 'rails_live_dashboard/recorders/jobs/perform_recorder'
12
+ require 'rails_live_dashboard/recorders/jobs/start_recorder'
8
13
  require 'rails_live_dashboard/subscribers/action_controller_subscriber'
9
14
  require 'rails_live_dashboard/subscribers/active_record_subscriber'
15
+ require 'rails_live_dashboard/subscribers/active_job_subscriber'
10
16
 
11
17
  module RailsLiveDashboard
12
18
  def self.configuration
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rails_live_dashboard
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.2
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Anderson Dalmina
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-02-28 00:00:00.000000000 Z
11
+ date: 2024-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: cssbundling-rails
@@ -169,6 +169,8 @@ files:
169
169
  - app/assets/javascripts/rails_live_dashboard/controllers/reveal_controller.js
170
170
  - app/assets/javascripts/rails_live_dashboard/controllers/tabs_controller.js
171
171
  - app/assets/stylesheets/rails_live_dashboard/application.css
172
+ - app/components/rails_live_dashboard/job_status_badge_component.html.erb
173
+ - app/components/rails_live_dashboard/job_status_badge_component.rb
172
174
  - app/components/rails_live_dashboard/query_duration_badge_component.html.erb
173
175
  - app/components/rails_live_dashboard/query_duration_badge_component.rb
174
176
  - app/components/rails_live_dashboard/request_duration_badge_component.html.erb
@@ -181,6 +183,7 @@ files:
181
183
  - app/controllers/rails_live_dashboard/clean_controller.rb
182
184
  - app/controllers/rails_live_dashboard/dashboard_controller.rb
183
185
  - app/controllers/rails_live_dashboard/exceptions_controller.rb
186
+ - app/controllers/rails_live_dashboard/jobs_controller.rb
184
187
  - app/controllers/rails_live_dashboard/queries_controller.rb
185
188
  - app/controllers/rails_live_dashboard/requests_controller.rb
186
189
  - app/controllers/rails_live_dashboard/widgets/slowest_queries_controller.rb
@@ -189,16 +192,20 @@ files:
189
192
  - app/helpers/rails_live_dashboard/clean_helper.rb
190
193
  - app/helpers/rails_live_dashboard/dashboard_helper.rb
191
194
  - app/helpers/rails_live_dashboard/exceptions_helper.rb
195
+ - app/helpers/rails_live_dashboard/jobs_helper.rb
192
196
  - app/helpers/rails_live_dashboard/requests_helper.rb
193
197
  - app/jobs/rails_live_dashboard/application_job.rb
194
198
  - app/mailers/rails_live_dashboard/application_mailer.rb
195
199
  - app/models/rails_live_dashboard/application_record.rb
200
+ - app/models/rails_live_dashboard/current.rb
196
201
  - app/models/rails_live_dashboard/entry.rb
197
202
  - app/models/rails_live_dashboard/exception.rb
203
+ - app/models/rails_live_dashboard/job.rb
198
204
  - app/models/rails_live_dashboard/query.rb
199
205
  - app/models/rails_live_dashboard/request.rb
200
206
  - app/models/rails_live_dashboard/types/content.rb
201
207
  - app/models/rails_live_dashboard/types/exception_content.rb
208
+ - app/models/rails_live_dashboard/types/job_content.rb
202
209
  - app/models/rails_live_dashboard/types/query_content.rb
203
210
  - app/models/rails_live_dashboard/types/request_content.rb
204
211
  - app/views/layouts/rails_live_dashboard/application.html.erb
@@ -206,6 +213,11 @@ files:
206
213
  - app/views/rails_live_dashboard/exceptions/_tabs.html.erb
207
214
  - app/views/rails_live_dashboard/exceptions/index.html.erb
208
215
  - app/views/rails_live_dashboard/exceptions/show.html.erb
216
+ - app/views/rails_live_dashboard/jobs/_queries.html.erb
217
+ - app/views/rails_live_dashboard/jobs/_relateds.html.erb
218
+ - app/views/rails_live_dashboard/jobs/_tabs.html.erb
219
+ - app/views/rails_live_dashboard/jobs/index.html.erb
220
+ - app/views/rails_live_dashboard/jobs/show.html.erb
209
221
  - app/views/rails_live_dashboard/queries/_list.html.erb
210
222
  - app/views/rails_live_dashboard/queries/_query.html.erb
211
223
  - app/views/rails_live_dashboard/queries/index.html.erb
@@ -230,10 +242,16 @@ files:
230
242
  - lib/rails_live_dashboard/configuration.rb
231
243
  - lib/rails_live_dashboard/context.rb
232
244
  - lib/rails_live_dashboard/engine.rb
245
+ - lib/rails_live_dashboard/exceptions/entry_not_found.rb
233
246
  - lib/rails_live_dashboard/recorders/exception_recorder.rb
247
+ - lib/rails_live_dashboard/recorders/jobs/discard_recorder.rb
248
+ - lib/rails_live_dashboard/recorders/jobs/enqueue_recorder.rb
249
+ - lib/rails_live_dashboard/recorders/jobs/perform_recorder.rb
250
+ - lib/rails_live_dashboard/recorders/jobs/start_recorder.rb
234
251
  - lib/rails_live_dashboard/recorders/query_recorder.rb
235
252
  - lib/rails_live_dashboard/recorders/request_recorder.rb
236
253
  - lib/rails_live_dashboard/subscribers/action_controller_subscriber.rb
254
+ - lib/rails_live_dashboard/subscribers/active_job_subscriber.rb
237
255
  - lib/rails_live_dashboard/subscribers/active_record_subscriber.rb
238
256
  - lib/rails_live_dashboard/version.rb
239
257
  - lib/tasks/rails_live_dashboard_tasks.rake
@@ -259,7 +277,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
259
277
  - !ruby/object:Gem::Version
260
278
  version: '0'
261
279
  requirements: []
262
- rubygems_version: 3.5.5
280
+ rubygems_version: 3.5.3
263
281
  signing_key:
264
282
  specification_version: 4
265
283
  summary: Realtime debug dashboard for Rails applications