rails_live_dashboard 0.1.2 → 0.2.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.
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