solid_job_monitor 1.0.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 (147) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +106 -0
  4. data/Rakefile +8 -0
  5. data/app/assets/config/mission_control_jobs_manifest.js +4 -0
  6. data/app/assets/stylesheets/mission_control/jobs/application.css +16 -0
  7. data/app/assets/stylesheets/mission_control/jobs/bulma.min.css +21013 -0
  8. data/app/assets/stylesheets/mission_control/jobs/forms.css +8 -0
  9. data/app/assets/stylesheets/mission_control/jobs/jobs.css +68 -0
  10. data/app/controllers/concerns/mission_control/jobs/adapter_features.rb +24 -0
  11. data/app/controllers/concerns/mission_control/jobs/application_scoped.rb +38 -0
  12. data/app/controllers/concerns/mission_control/jobs/basic_authentication.rb +33 -0
  13. data/app/controllers/concerns/mission_control/jobs/failed_jobs_bulk_operations.rb +17 -0
  14. data/app/controllers/concerns/mission_control/jobs/job_filters.rb +41 -0
  15. data/app/controllers/concerns/mission_control/jobs/job_scoped.rb +16 -0
  16. data/app/controllers/concerns/mission_control/jobs/not_found_redirections.rb +36 -0
  17. data/app/controllers/concerns/mission_control/jobs/queue_scoped.rb +12 -0
  18. data/app/controllers/mission_control/jobs/application_controller.rb +32 -0
  19. data/app/controllers/mission_control/jobs/bulk_discards_controller.rb +20 -0
  20. data/app/controllers/mission_control/jobs/bulk_retries_controller.rb +10 -0
  21. data/app/controllers/mission_control/jobs/discards_controller.rb +18 -0
  22. data/app/controllers/mission_control/jobs/dispatches_controller.rb +18 -0
  23. data/app/controllers/mission_control/jobs/jobs_controller.rb +54 -0
  24. data/app/controllers/mission_control/jobs/progresses_controller.rb +27 -0
  25. data/app/controllers/mission_control/jobs/queues/pauses_controller.rb +15 -0
  26. data/app/controllers/mission_control/jobs/queues_controller.rb +16 -0
  27. data/app/controllers/mission_control/jobs/recurring_tasks_controller.rb +38 -0
  28. data/app/controllers/mission_control/jobs/retries_controller.rb +13 -0
  29. data/app/controllers/mission_control/jobs/stops_controller.rb +22 -0
  30. data/app/controllers/mission_control/jobs/workers_controller.rb +23 -0
  31. data/app/helpers/mission_control/jobs/application_helper.rb +9 -0
  32. data/app/helpers/mission_control/jobs/dates_helper.rb +19 -0
  33. data/app/helpers/mission_control/jobs/interface_helper.rb +23 -0
  34. data/app/helpers/mission_control/jobs/jobs_helper.rb +94 -0
  35. data/app/helpers/mission_control/jobs/navigation_helper.rb +60 -0
  36. data/app/javascript/mission_control/jobs/application.js +4 -0
  37. data/app/javascript/mission_control/jobs/controllers/application.js +9 -0
  38. data/app/javascript/mission_control/jobs/controllers/form_controller.js +21 -0
  39. data/app/javascript/mission_control/jobs/controllers/index.js +11 -0
  40. data/app/javascript/mission_control/jobs/controllers/progress_controller.js +55 -0
  41. data/app/javascript/mission_control/jobs/helpers/debounce_helpers.js +9 -0
  42. data/app/javascript/mission_control/jobs/helpers/index.js +1 -0
  43. data/app/models/mission_control/jobs/application_record.rb +7 -0
  44. data/app/models/mission_control/jobs/current.rb +3 -0
  45. data/app/models/mission_control/jobs/job_progress.rb +7 -0
  46. data/app/models/mission_control/jobs/page.rb +48 -0
  47. data/app/models/mission_control/jobs/recurring_task.rb +25 -0
  48. data/app/models/mission_control/jobs/worker.rb +17 -0
  49. data/app/views/layouts/mission_control/jobs/_application_selection.html.erb +14 -0
  50. data/app/views/layouts/mission_control/jobs/_flash.html.erb +9 -0
  51. data/app/views/layouts/mission_control/jobs/_navigation.html.erb +9 -0
  52. data/app/views/layouts/mission_control/jobs/application.html.erb +27 -0
  53. data/app/views/layouts/mission_control/jobs/application_selection/_applications.html.erb +13 -0
  54. data/app/views/layouts/mission_control/jobs/application_selection/_servers.html.erb +15 -0
  55. data/app/views/mission_control/jobs/jobs/_error_information.html.erb +22 -0
  56. data/app/views/mission_control/jobs/jobs/_filters.html.erb +55 -0
  57. data/app/views/mission_control/jobs/jobs/_general_information.html.erb +85 -0
  58. data/app/views/mission_control/jobs/jobs/_job.html.erb +13 -0
  59. data/app/views/mission_control/jobs/jobs/_jobs_page.html.erb +13 -0
  60. data/app/views/mission_control/jobs/jobs/_progress_bar.html.erb +16 -0
  61. data/app/views/mission_control/jobs/jobs/_raw_data.html.erb +4 -0
  62. data/app/views/mission_control/jobs/jobs/_title.html.erb +22 -0
  63. data/app/views/mission_control/jobs/jobs/_toolbar.html.erb +18 -0
  64. data/app/views/mission_control/jobs/jobs/blocked/_actions.html.erb +3 -0
  65. data/app/views/mission_control/jobs/jobs/blocked/_job.html.erb +8 -0
  66. data/app/views/mission_control/jobs/jobs/failed/_actions.html.erb +5 -0
  67. data/app/views/mission_control/jobs/jobs/failed/_backtrace_toggle.html.erb +14 -0
  68. data/app/views/mission_control/jobs/jobs/failed/_job.html.erb +7 -0
  69. data/app/views/mission_control/jobs/jobs/finished/_job.html.erb +2 -0
  70. data/app/views/mission_control/jobs/jobs/in_progress/_actions.html.erb +6 -0
  71. data/app/views/mission_control/jobs/jobs/in_progress/_job.html.erb +13 -0
  72. data/app/views/mission_control/jobs/jobs/index.html.erb +19 -0
  73. data/app/views/mission_control/jobs/jobs/scheduled/_actions.html.erb +5 -0
  74. data/app/views/mission_control/jobs/jobs/scheduled/_job.html.erb +13 -0
  75. data/app/views/mission_control/jobs/jobs/show.html.erb +6 -0
  76. data/app/views/mission_control/jobs/queues/_actions.html.erb +7 -0
  77. data/app/views/mission_control/jobs/queues/_job.html.erb +15 -0
  78. data/app/views/mission_control/jobs/queues/_queue.html.erb +16 -0
  79. data/app/views/mission_control/jobs/queues/_queue_title.html.erb +17 -0
  80. data/app/views/mission_control/jobs/queues/index.html.erb +16 -0
  81. data/app/views/mission_control/jobs/queues/show.html.erb +25 -0
  82. data/app/views/mission_control/jobs/recurring_tasks/_actions.html.erb +5 -0
  83. data/app/views/mission_control/jobs/recurring_tasks/_general_information.html.erb +37 -0
  84. data/app/views/mission_control/jobs/recurring_tasks/_recurring_task.html.erb +22 -0
  85. data/app/views/mission_control/jobs/recurring_tasks/_title.html.erb +10 -0
  86. data/app/views/mission_control/jobs/recurring_tasks/index.html.erb +18 -0
  87. data/app/views/mission_control/jobs/recurring_tasks/show.html.erb +14 -0
  88. data/app/views/mission_control/jobs/shared/_job.html.erb +27 -0
  89. data/app/views/mission_control/jobs/shared/_jobs.html.erb +14 -0
  90. data/app/views/mission_control/jobs/shared/_pagination_toolbar.html.erb +7 -0
  91. data/app/views/mission_control/jobs/workers/_configuration.html.erb +6 -0
  92. data/app/views/mission_control/jobs/workers/_raw_data.html.erb +6 -0
  93. data/app/views/mission_control/jobs/workers/_title.html.erb +11 -0
  94. data/app/views/mission_control/jobs/workers/_worker.html.erb +21 -0
  95. data/app/views/mission_control/jobs/workers/_workers_page.html.erb +15 -0
  96. data/app/views/mission_control/jobs/workers/index.html.erb +6 -0
  97. data/app/views/mission_control/jobs/workers/show.html.erb +14 -0
  98. data/config/importmap.rb +6 -0
  99. data/config/routes.rb +35 -0
  100. data/db/migrate/20260828105144_create_mission_control_job_progresses.rb +16 -0
  101. data/lib/active_job/errors/invalid_operation.rb +5 -0
  102. data/lib/active_job/errors/job_not_found_error.rb +14 -0
  103. data/lib/active_job/errors/query_error.rb +5 -0
  104. data/lib/active_job/executing.rb +40 -0
  105. data/lib/active_job/execution_error.rb +8 -0
  106. data/lib/active_job/failed.rb +7 -0
  107. data/lib/active_job/job_proxy.rb +38 -0
  108. data/lib/active_job/jobs_relation.rb +318 -0
  109. data/lib/active_job/progress_reporting.rb +21 -0
  110. data/lib/active_job/querying.rb +44 -0
  111. data/lib/active_job/queue.rb +62 -0
  112. data/lib/active_job/queue_adapters/async_ext.rb +49 -0
  113. data/lib/active_job/queue_adapters/resque_ext.rb +317 -0
  114. data/lib/active_job/queue_adapters/solid_queue_ext/recurring_tasks.rb +52 -0
  115. data/lib/active_job/queue_adapters/solid_queue_ext/workers.rb +41 -0
  116. data/lib/active_job/queue_adapters/solid_queue_ext.rb +313 -0
  117. data/lib/active_job/queues.rb +33 -0
  118. data/lib/mission_control/jobs/adapter.rb +156 -0
  119. data/lib/mission_control/jobs/application.rb +20 -0
  120. data/lib/mission_control/jobs/applications.rb +8 -0
  121. data/lib/mission_control/jobs/arguments_filter.rb +24 -0
  122. data/lib/mission_control/jobs/authentication.rb +67 -0
  123. data/lib/mission_control/jobs/console/connect_to.rb +15 -0
  124. data/lib/mission_control/jobs/console/context.rb +11 -0
  125. data/lib/mission_control/jobs/console/jobs_help.rb +23 -0
  126. data/lib/mission_control/jobs/engine.rb +118 -0
  127. data/lib/mission_control/jobs/errors/incompatible_adapter.rb +2 -0
  128. data/lib/mission_control/jobs/errors/resource_not_found.rb +2 -0
  129. data/lib/mission_control/jobs/i18n_config.rb +17 -0
  130. data/lib/mission_control/jobs/identified_by_name.rb +18 -0
  131. data/lib/mission_control/jobs/identified_elements.rb +23 -0
  132. data/lib/mission_control/jobs/job_stopped_error.rb +5 -0
  133. data/lib/mission_control/jobs/progress/null_store.rb +25 -0
  134. data/lib/mission_control/jobs/progress/reporter.rb +63 -0
  135. data/lib/mission_control/jobs/progress/store.rb +72 -0
  136. data/lib/mission_control/jobs/progress.rb +17 -0
  137. data/lib/mission_control/jobs/server/recurring_tasks.rb +15 -0
  138. data/lib/mission_control/jobs/server/serializable.rb +24 -0
  139. data/lib/mission_control/jobs/server/workers.rb +13 -0
  140. data/lib/mission_control/jobs/server.rb +27 -0
  141. data/lib/mission_control/jobs/tasks.rb +8 -0
  142. data/lib/mission_control/jobs/version.rb +5 -0
  143. data/lib/mission_control/jobs/workers_relation.rb +78 -0
  144. data/lib/mission_control/jobs.rb +43 -0
  145. data/lib/resque/thread_safe_redis.rb +34 -0
  146. data/lib/solid_job_monitor.rb +6 -0
  147. metadata +527 -0
@@ -0,0 +1,13 @@
1
+ <table class="jobs <%= jobs_status %> table queues is-hoverable is-fullwidth">
2
+ <thead>
3
+ <tr>
4
+ <th class="job-header">Job</th>
5
+ <% attribute_names_for_job_status(jobs_status).each do |attribute| %>
6
+ <th><%= attribute %></th>
7
+ <% end %>
8
+ </tr>
9
+ </thead>
10
+ <tbody>
11
+ <%= render partial: "mission_control/jobs/jobs/job", collection: jobs_page.records %>
12
+ </tbody>
13
+ </table>
@@ -0,0 +1,16 @@
1
+ <% progress = local_assigns[:progress]
2
+ job_id = local_assigns[:job]&.job_id || local_assigns[:job_id] %>
3
+ <% if progress %>
4
+ <div class="job-progress-wrap is-flex is-align-items-center"
5
+ data-controller="progress"
6
+ data-progress-url-value="<%= application_job_progress_path(@application, job_id) %>"
7
+ data-progress-percent-value="<%= progress.percent.to_i %>">
8
+ <div class="job-progress">
9
+ <div class="job-progress-bar" data-progress-target="bar"></div>
10
+ <span class="job-progress-label" data-progress-target="label"><%= progress.percent.to_i %>%</span>
11
+ </div>
12
+ <div class="is-warning tag ml-2<%= " is-hidden" unless progress.stop_requested? %>" data-progress-target="stopping">Stopping…</div>
13
+ </div>
14
+ <% else %>
15
+
16
+ <% end %>
@@ -0,0 +1,4 @@
1
+ <h2 class="subtitle">Raw data</h2>
2
+ <pre>
3
+ <%= JSON.pretty_generate(job.filtered_raw_data.without("backtrace")) %>
4
+ </pre>
@@ -0,0 +1,22 @@
1
+ <h1 class="title">
2
+ <div class="level">
3
+ <div class="level-left">
4
+ <%= job_title(job) %>
5
+ <div class="<%= modifier_for_status(job.status) %> tag ml-4"><%= job.status %></div>
6
+ </div>
7
+ <div class="level-right">
8
+ <% if job.failed? %>
9
+ <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
10
+ <% end %>
11
+ <% if job.blocked? %>
12
+ <%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
13
+ <% end %>
14
+ <% if job.scheduled? %>
15
+ <%= render "mission_control/jobs/jobs/scheduled/actions", job: job %>
16
+ <% end %>
17
+ <% if job.in_progress? %>
18
+ <%= render "mission_control/jobs/jobs/in_progress/actions", job: job %>
19
+ <% end %>
20
+ </div>
21
+ </div>
22
+ </h1>
@@ -0,0 +1,18 @@
1
+ <div class="buttons is-right level-right">
2
+ <% if active_filters? %>
3
+ <span class="mr-5">
4
+ <%= jobs_count %> jobs found
5
+ </span>
6
+ <% end %>
7
+
8
+ <% if jobs_status.failed? %>
9
+ <% target = active_filters? ? "selection" : "all" %>
10
+
11
+ <%= button_to "Discard #{target}", application_bulk_discards_path(@application, **jobs_filter_param),
12
+ method: :post, disabled: jobs_count == 0, class: "button is-danger is-light",
13
+ form: { data: { turbo_confirm: "This will delete #{jobs_count} jobs and can't be undone. Are you sure?" } } %>
14
+ <%= button_to "Retry #{target}", application_bulk_retries_path(@application, **jobs_filter_param),
15
+ method: :post, disabled: jobs_count == 0,
16
+ class: "button is-warning is-light mr-0" %>
17
+ <% end %>
18
+ </div>
@@ -0,0 +1,3 @@
1
+ <div class="buttons is-right">
2
+ <%= button_to "Run now", application_job_dispatch_path(@application, job.job_id), class: "button is-warning is-light mr-0" %>
3
+ </div>
@@ -0,0 +1,8 @@
1
+ <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
+ <td>
3
+ <div class="is-family-monospace is-size-7"><%= job.blocked_by %></div>
4
+ <div class="has-text-grey is-size-7"><%= blocked_job_expiration(job) %></div>
5
+ </td>
6
+ <td class="pr-0">
7
+ <%= render "mission_control/jobs/jobs/blocked/actions", job: job %>
8
+ </td>
@@ -0,0 +1,5 @@
1
+ <div class="buttons is-right">
2
+ <%= button_to "Discard", application_job_discard_path(@application, job.job_id, params: jobs_filter_param), class: "button is-danger is-light mr-0",
3
+ form: { data: { turbo_confirm: "This will delete the job and can't be undone. Are you sure?" } } %>
4
+ <%= button_to "Retry", application_job_retry_path(@application, job.job_id, params: jobs_filter_param), class: "button is-warning is-light mr-0" %>
5
+ </div>
@@ -0,0 +1,14 @@
1
+ <%# locals: (application:, job:) %>
2
+
3
+ <div class="is-flex is-justify-content-flex-end mb-2 mr-2 backtrace-toggle-selector">
4
+ <div class="tabs is-toggle is-toggle-rounded is-small">
5
+ <ul>
6
+ <li class="<%= class_names('backtrace-clean-link', 'is-active' => clean_backtrace?) %>">
7
+ <%= link_to "Clean", application_job_path(application, job.job_id, clean_backtrace: true) %>
8
+ </li>
9
+ <li class="<%= class_names('backtrace-full-link', 'is-active' => !clean_backtrace?) %>">
10
+ <%= link_to "Full", application_job_path(application, job.job_id, clean_backtrace: false) %>
11
+ </li>
12
+ </ul>
13
+ </div>
14
+ </div>
@@ -0,0 +1,7 @@
1
+ <td>
2
+ <%= link_to failed_job_error(job), application_job_path(@application, job.job_id, anchor: "error") %>
3
+ <div class="has-text-grey"><%= time_distance_in_words_with_title(job.failed_at) %> ago</div>
4
+ </td>
5
+ <td class="pr-0">
6
+ <%= render "mission_control/jobs/jobs/failed/actions", job: job %>
7
+ </td>
@@ -0,0 +1,2 @@
1
+ <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
+ <td><div class="has-text-grey"><%= time_distance_in_words_with_title(job.finished_at) %> ago</div></td>
@@ -0,0 +1,6 @@
1
+ <% if job_progress_enabled? %>
2
+ <div class="buttons is-right">
3
+ <%= button_to "Stop", application_job_stop_path(@application, job.job_id, params: jobs_filter_param), class: "button is-danger is-light mr-0",
4
+ form: { data: { turbo_confirm: "This requests the job to stop. It will stop the next time it reports progress or calls check_stopped!. Jobs that never do that will keep running. Are you sure?" } } %>
5
+ </div>
6
+ <% end %>
@@ -0,0 +1,13 @@
1
+ <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
+ <td>
3
+ <% if job.worker_id %>
4
+ <%= link_to "worker #{job.worker_id}", application_worker_path(@application, job.worker_id) %>
5
+ <% else %>
6
+
7
+ <% end %>
8
+ </td>
9
+ <td><div class="has-text-grey"><%= job.started_at ? time_distance_in_words_with_title(job.started_at) : "(Finished)" %></div></td>
10
+ <td><%= render "mission_control/jobs/jobs/progress_bar", progress: progress_for(job), job: job %></td>
11
+ <td class="pr-0">
12
+ <%= render "mission_control/jobs/jobs/in_progress/actions", job: job %>
13
+ </td>
@@ -0,0 +1,19 @@
1
+ <% navigation(title: "#{jobs_status.titleize} jobs", section: "#{jobs_status}_jobs".to_sym) %>
2
+
3
+ <% if @jobs_page.empty? && !active_filters? %>
4
+ <%= blank_status_notice "There are no #{jobs_status.dasherize} jobs #{blank_status_emoji(jobs_status)}" %>
5
+ <% else %>
6
+ <div class="level is-flex-wrap-wrap">
7
+ <%= render "mission_control/jobs/jobs/filters", job_class_names: @job_class_names, queue_names: @queue_names %>
8
+ <%= render "mission_control/jobs/jobs/toolbar", jobs_count: @jobs_count %>
9
+ </div>
10
+
11
+ <% if @jobs_page.empty? %>
12
+ <%= blank_status_notice "No #{jobs_status.dasherize} jobs found with the given filters" %>
13
+ <% else %>
14
+ <%= render "mission_control/jobs/jobs/jobs_page", jobs_page: @jobs_page %>
15
+
16
+ <%= render "mission_control/jobs/shared/pagination_toolbar", page: @jobs_page, filter_param: jobs_filter_param %>
17
+ <% end %>
18
+ <% end %>
19
+
@@ -0,0 +1,5 @@
1
+ <div class="buttons is-right">
2
+ <%= button_to "Run now", application_job_dispatch_path(@application, job.job_id), class: "button is-warning is-light mr-0" %>
3
+ <%= button_to "Discard", application_job_discard_path(@application, job.job_id), class: "button is-danger is-light mr-0",
4
+ form: { data: { turbo_confirm: "This will delete the job and can't be undone. Are you sure?" } } %>
5
+ </div>
@@ -0,0 +1,13 @@
1
+ <td><%= link_to job.queue_name, application_queue_path(@application, job.queue) %></td>
2
+ <td>
3
+ <%= bidirectional_time_distance_in_words_with_title(job.scheduled_at) %>
4
+ <% if job.executions > 0 %>
5
+ <div class="is-warning tag ml-4"><%= job.executions.ordinalize %> retry</div>
6
+ <% end %>
7
+ <% if job_delayed?(job) %>
8
+ <div class="is-danger tag ml-4">delayed</div>
9
+ <% end %>
10
+ </td>
11
+ <td class="pr-0">
12
+ <%= render "mission_control/jobs/jobs/scheduled/actions", job: job %>
13
+ </td>
@@ -0,0 +1,6 @@
1
+ <% navigation(title: "Job #{@job.job_id}", section: navigation_section_for_status(@job.status)) %>
2
+
3
+ <%= render "mission_control/jobs/jobs/title", job: @job %>
4
+ <%= render "mission_control/jobs/jobs/general_information", job: @job %>
5
+ <%= render "mission_control/jobs/jobs/error_information", job: @job %>
6
+ <%= render "mission_control/jobs/jobs/raw_data", job: @job %>
@@ -0,0 +1,7 @@
1
+ <div class="buttons is-right">
2
+ <% if queue.active? %>
3
+ <%= button_to "Pause", application_queue_pause_path(@application, queue.name), method: :post, class: "button is-success is-light mr-0" %>
4
+ <% else %>
5
+ <%= button_to "Resume", application_queue_pause_path(@application, queue.name), method: :delete, class: "button is-warning is-light mr-0" %>
6
+ <% end %>
7
+ </div>
@@ -0,0 +1,15 @@
1
+ <tr class="job">
2
+ <td>
3
+ <%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
4
+ <%= job_title(job) %>
5
+ <% end %>
6
+ <div class="has-text-grey">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
7
+ </td>
8
+ <td>
9
+ <% if job.serialized_arguments.present? %>
10
+ <span class="is-family-monospace">
11
+ <%= job_arguments(job) %>
12
+ </span>
13
+ <% end %>
14
+ </td>
15
+ </tr>
@@ -0,0 +1,16 @@
1
+ <tr class="queue">
2
+ <td>
3
+ <%= link_to queue.name, application_queue_path(@application, queue) %>
4
+ <% if queue.paused? %>
5
+ <span class="ml-4 tag is-warning">Paused</span>
6
+ <% end %>
7
+ </td>
8
+ <td>
9
+ <%= queue.size %>
10
+ </td>
11
+ <td class="pr-0">
12
+ <% if queue_pausing_supported? %>
13
+ <%= render "mission_control/jobs/queues/actions", queue: queue %>
14
+ <% end %>
15
+ </td>
16
+ </tr>
@@ -0,0 +1,17 @@
1
+ <h1 class="title">
2
+ <div class="level">
3
+ <div class="level-left">
4
+ <%= queue.name %>
5
+ <% if queue.paused? %>
6
+ <span class="ml-4 tag is-warning">Paused</span>
7
+ <% end %>
8
+ </div>
9
+ <% if queue_pausing_supported? %>
10
+ <div class="level-right">
11
+ <%= render "mission_control/jobs/queues/actions", queue: queue %>
12
+ </div>
13
+ <% end %>
14
+ </div>
15
+ </h1>
16
+
17
+ <h2 class="subtitle"><%= pluralize queue.size, "pending job" %></h2>
@@ -0,0 +1,16 @@
1
+ <% navigation(title: "Queues", section: :queues) %>
2
+
3
+ <table class="queues table is-hoverable is-fullwidth">
4
+ <tbody>
5
+ <thead>
6
+ <tr>
7
+ <th>Queue</th>
8
+ <th>Pending jobs</th>
9
+ <th></th>
10
+ </tr>
11
+ </thead>
12
+
13
+ <%= render partial: "mission_control/jobs/queues/queue", collection: @queues %>
14
+
15
+ </tbody>
16
+ </table>
@@ -0,0 +1,25 @@
1
+ <% navigation(title: "Queue #{@queue.name}", section: :queues) %>
2
+
3
+ <%= render "mission_control/jobs/queues/queue_title", queue: @queue %>
4
+
5
+ <% if @jobs_page.empty? %>
6
+ <%= blank_status_notice "The queue is empty" %>
7
+ <% else %>
8
+ <table class="jobs table queues is-hoverable is-fullwidth">
9
+ <tbody>
10
+ <thead>
11
+ <tr>
12
+ <th class="job-header">Job</th>
13
+ <th></th>
14
+ </tr>
15
+ </thead>
16
+
17
+ <%= render partial: "mission_control/jobs/queues/job", collection: @jobs_page.records %>
18
+
19
+ </tbody>
20
+ </table>
21
+
22
+ <%= render "mission_control/jobs/shared/pagination_toolbar", page: @jobs_page, filter_param: {} %>
23
+ <% end %>
24
+
25
+
@@ -0,0 +1,5 @@
1
+ <div class="buttons is-right">
2
+ <% if recurring_task.runnable? %>
3
+ <%= button_to "Run now", application_recurring_task_path(@application, recurring_task.id), class: "button is-warning is-light mr-0", method: :put %>
4
+ <% end %>
5
+ </div>
@@ -0,0 +1,37 @@
1
+ <table class="table">
2
+ <tbody>
3
+ <% if recurring_task.job_class_name.present? %>
4
+ <tr>
5
+ <th>Job class</th>
6
+ <td><%= recurring_task.job_class_name %></td>
7
+ </tr>
8
+
9
+ <tr>
10
+ <th>Arguments</th>
11
+ <td><div class="is-family-monospace"><%= recurring_task.arguments.join(",") %></div></td>
12
+ </tr>
13
+ <% elsif recurring_task.command.present? %>
14
+ <tr>
15
+ <th>Command</th>
16
+ <td><div class="is-family-monospace"><%= recurring_task.command %></div></td>
17
+ </tr>
18
+ <% end %>
19
+
20
+ <tr>
21
+ <th>Schedule</th>
22
+ <td><%= recurring_task.schedule %></td>
23
+ </tr>
24
+ <% if recurring_task.queue_name.present? %>
25
+ <tr>
26
+ <th>Queue</th>
27
+ <td><%= recurring_task.queue_name %></td>
28
+ </tr>
29
+ <% end %>
30
+ <% if recurring_task.priority.present? %>
31
+ <tr>
32
+ <th>Priority</th>
33
+ <td><%= recurring_task.priority %></td>
34
+ </tr>
35
+ <% end %>
36
+ </tbody>
37
+ </table>
@@ -0,0 +1,22 @@
1
+ <tr class="recurring_task">
2
+ <td>
3
+ <%= link_to recurring_task.id, application_recurring_task_path(@application, recurring_task.id) %>
4
+ </td>
5
+ <td>
6
+ <% if recurring_task.job_class_name.present? %>
7
+ <%= recurring_task.job_class_name %>
8
+
9
+ <% if recurring_task.arguments.present? %>
10
+ <div class="is-family-monospace"><%= recurring_task.arguments.join(",") %></div>
11
+ <% end %>
12
+ <% elsif recurring_task.command.present? %>
13
+ <div class="is-family-monospace"><%= recurring_task.command %></div>
14
+ <% end %>
15
+ </td>
16
+ <td> <%= recurring_task.schedule %> </td>
17
+ <td><div class="has-text-grey"><%= recurring_task.last_enqueued_at ? bidirectional_time_distance_in_words_with_title(recurring_task.last_enqueued_at) : "Never" %></div></td>
18
+ <td class="next_time"><div class="has-text-grey"><%= bidirectional_time_distance_in_words_with_title(recurring_task.next_time) %></div></td>
19
+ <td class="pr-0">
20
+ <%= render "mission_control/jobs/recurring_tasks/actions", recurring_task: recurring_task %>
21
+ </td>
22
+ </tr>
@@ -0,0 +1,10 @@
1
+ <h1 class="title">
2
+ <div class="level">
3
+ <div class="level-left">
4
+ <%= recurring_task.id %>
5
+ </div>
6
+ <div class="level-right">
7
+ <%= render "mission_control/jobs/recurring_tasks/actions", recurring_task: recurring_task %>
8
+ </div>
9
+ </div>
10
+ </h1>
@@ -0,0 +1,18 @@
1
+ <% navigation(title: "Recurring tasks", section: :recurring_tasks) %>
2
+
3
+ <table class="recurring-tasks table jobs is-hoverable is-fullwidth">
4
+ <tbody>
5
+ <thead>
6
+ <tr>
7
+ <th></th>
8
+ <th>Job</th>
9
+ <th>Schedule</th>
10
+ <th>Last enqueued</th>
11
+ <th>Next</th>
12
+ <th></th>
13
+ </tr>
14
+ </thead>
15
+
16
+ <%= render partial: "mission_control/jobs/recurring_tasks/recurring_task", collection: @recurring_tasks %>
17
+ </tbody>
18
+ </table>
@@ -0,0 +1,14 @@
1
+ <% navigation(title: @recurring_task.id, section: :recurring_tasks) %>
2
+
3
+ <%= render "mission_control/jobs/recurring_tasks/title", recurring_task: @recurring_task %>
4
+ <%= render "mission_control/jobs/recurring_tasks/general_information", recurring_task: @recurring_task %>
5
+
6
+ <% if @jobs_page.empty? %>
7
+ <%= blank_status_notice "No jobs found for this recurring task" %>
8
+ <% else %>
9
+ <h2 class="subtitle"><%= pluralize @recurring_task.jobs.count, "job" %></h2>
10
+
11
+ <%= render "mission_control/jobs/shared/jobs", jobs: @jobs_page.records %>
12
+
13
+ <%= render "mission_control/jobs/shared/pagination_toolbar", page: @jobs_page, filter_param: jobs_filter_param %>
14
+ <% end %>
@@ -0,0 +1,27 @@
1
+ <tr class="job">
2
+ <td>
3
+ <%= link_to application_job_path(@application, job.job_id, filter: { queue_name: job.queue }) do %>
4
+ <%= job_title(job) %>
5
+ <% end %>
6
+ <div class="has-text-grey">Enqueued <%= time_distance_in_words_with_title(job.enqueued_at.to_datetime) %> ago</div>
7
+ </td>
8
+ <td>
9
+ <% if job.serialized_arguments.present? %>
10
+ <span class="is-family-monospace">
11
+ <%= job_arguments(job) %>
12
+ </span>
13
+ <% end %>
14
+ </td>
15
+
16
+ <td>
17
+ <div class="has-text-grey">
18
+ <% if job.started_at %>
19
+ Running for <%= time_distance_in_words_with_title(job.started_at) %>
20
+ <% elsif job.finished_at %>
21
+ Finished <%= time_distance_in_words_with_title(job.finished_at) %> ago
22
+ <% else %>
23
+ Pending
24
+ <% end %>
25
+ </div>
26
+ </td>
27
+ </tr>
@@ -0,0 +1,14 @@
1
+ <table class="jobs table is-hoverable is-fullwidth">
2
+ <tbody>
3
+ <thead>
4
+ <tr>
5
+ <th class="job-header">Job</th>
6
+ <th></th>
7
+ <th class="duration-header"></th>
8
+ </tr>
9
+ </thead>
10
+
11
+ <%= render partial: "mission_control/jobs/shared/job", collection: jobs %>
12
+
13
+ </tbody>
14
+ </table>
@@ -0,0 +1,7 @@
1
+ <nav class="buttons is-right" role="navigation" aria-label="pagination">
2
+ <span class="mr-3"><%= page.index %> / <%= page.pages_count || "..." %></span>
3
+ <%= link_to "↞", url_for(page: 1, **filter_param), class: "pagination-previous", disabled: page.first? %>
4
+ <%= link_to "Previous page", url_for(page: page.previous_index, **filter_param), class: "pagination-previous", disabled: page.first? %>
5
+ <%= link_to "Next page", url_for(page: page.next_index, **filter_param), class: "pagination-next", disabled: page.last? %>
6
+ <%= link_to "↠", url_for(page: page.pages_count, **filter_param), class: "pagination-next", disabled: page.last? %>
7
+ </nav>
@@ -0,0 +1,6 @@
1
+ <h2 class="subtitle mt-2">Configuration</h2>
2
+ <pre>
3
+ <%= JSON.pretty_generate(worker.configuration) %>
4
+ </pre>
5
+
6
+ <hr />
@@ -0,0 +1,6 @@
1
+ <hr />
2
+
3
+ <h2 class="subtitle">Raw data</h2>
4
+ <pre>
5
+ <%= JSON.pretty_generate(worker.raw_data) %>
6
+ </pre>
@@ -0,0 +1,11 @@
1
+ <h1 class="title">
2
+ <div class="level">
3
+ <div class="level-left">
4
+ <%= "Worker #{worker.id} — #{worker.name}" %>
5
+ </div>
6
+
7
+ <div class="level-right is-family-monospace">
8
+ <%= worker.hostname %>
9
+ </div>
10
+ </div>
11
+ </h1>
@@ -0,0 +1,21 @@
1
+ <tr class="worker">
2
+ <td>
3
+ <%= link_to "worker #{worker.id}", application_worker_path(@application, worker.id) %>
4
+ <br />
5
+ <%= worker.name %>
6
+ </td>
7
+ <td><%= worker.hostname %></td>
8
+ <td>
9
+ <% worker.jobs.each do |job| %>
10
+ <div>
11
+ <%= link_to job_title(job), application_job_path(@application, job.job_id) %>
12
+
13
+ <% if job.serialized_arguments.present? %>
14
+ <div class="is-family-monospace is-size-7"><%= job_arguments(job) %></div>
15
+ <% end %>
16
+ </div>
17
+ <% end %>
18
+ </td>
19
+
20
+ <td><div class="has-text-grey"><%= time_distance_in_words_with_title(worker.last_heartbeat_at) %> ago</div></td>
21
+ </tr>
@@ -0,0 +1,15 @@
1
+ <table class="workers table jobs is-hoverable is-fullwidth">
2
+ <tbody>
3
+ <thead>
4
+ <tr>
5
+ <th>Worker</th>
6
+ <th>Hostname</th>
7
+ <th class="job-header">Jobs</th>
8
+ <th>Last heartbeat</th>
9
+ </tr>
10
+ </thead>
11
+
12
+ <%= render partial: "mission_control/jobs/workers/worker", collection: workers_page.records %>
13
+
14
+ </tbody>
15
+ </table>
@@ -0,0 +1,6 @@
1
+ <% navigation(title: "Workers", section: :workers) %>
2
+
3
+ <%= render "mission_control/jobs/workers/workers_page", workers_page: @workers_page %>
4
+
5
+ <%= render "mission_control/jobs/shared/pagination_toolbar", page: @workers_page, filter_param: {} %>
6
+
@@ -0,0 +1,14 @@
1
+ <% navigation(title: "Worker #{@worker.id}", section: :workers) %>
2
+
3
+ <%= render "mission_control/jobs/workers/title", worker: @worker %>
4
+ <%= render "mission_control/jobs/workers/configuration", worker: @worker %>
5
+
6
+ <% if @worker.jobs.empty? %>
7
+ <%= blank_status_notice "This worker is idle" %>
8
+ <% else %>
9
+ <h2 class="subtitle">Running <%= pluralize @worker.jobs.size, "job" %></h2>
10
+
11
+ <%= render "mission_control/jobs/shared/jobs", jobs: @worker.jobs %>
12
+ <% end %>
13
+
14
+ <%= render "mission_control/jobs/workers/raw_data", worker: @worker %>
@@ -0,0 +1,6 @@
1
+ pin "application", to: "mission_control/jobs/application.js", preload: true
2
+ pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
3
+ pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
4
+ pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true
5
+ pin_all_from MissionControl::Jobs::Engine.root.join("app/javascript/mission_control/jobs/controllers"), under: "controllers", to: "mission_control/jobs/controllers"
6
+ pin_all_from MissionControl::Jobs::Engine.root.join("app/javascript/mission_control/jobs/helpers"), under: "helpers", to: "mission_control/jobs/helpers"
data/config/routes.rb ADDED
@@ -0,0 +1,35 @@
1
+ MissionControl::Jobs::Engine.routes.draw do
2
+ resources :applications, only: [] do
3
+ resources :queues, only: [ :index, :show ] do
4
+ scope module: :queues do
5
+ resource :pause, only: [ :create, :destroy ]
6
+ end
7
+ end
8
+
9
+ resources :jobs, only: :show do
10
+ resource :retry, only: :create
11
+ resource :discard, only: :create
12
+ resource :dispatch, only: :create
13
+ resource :stop, only: :create
14
+ resource :progress, only: :show
15
+
16
+ collection do
17
+ resource :bulk_retries, only: :create
18
+ resource :bulk_discards, only: :create
19
+ end
20
+ end
21
+
22
+ resources :jobs, only: :index, path: ":status/jobs"
23
+
24
+ resources :workers, only: [ :index, :show ]
25
+ resources :recurring_tasks, only: [ :index, :show, :update ]
26
+ end
27
+
28
+ # Allow referencing urls without providing an application_id. It will default to the first one.
29
+ resources :queues, only: [ :index, :show ]
30
+
31
+ resources :jobs, only: :show
32
+ resources :jobs, only: :index, path: ":status/jobs"
33
+
34
+ root to: "queues#index"
35
+ end
@@ -0,0 +1,16 @@
1
+ class CreateMissionControlJobProgresses < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :mission_control_job_progresses do |t|
4
+ t.string :job_id, null: false
5
+ t.integer :current, default: 0, null: false
6
+ t.integer :total
7
+ t.integer :percent
8
+ t.string :message
9
+ t.boolean :stop_requested, default: false, null: false
10
+
11
+ t.timestamps
12
+ end
13
+
14
+ add_index :mission_control_job_progresses, :job_id, unique: true
15
+ end
16
+ end