diff --git a/CHANGELOG.md b/CHANGELOG.md index b1b1cdc..844e7a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Added +- Inline notifications — bulk and single-job actions now surface a flash notice; Turbo Stream discard responses prepend the message to the page without a full reload; bulk discard/retry counts are shown ("3 jobs discarded") - Dark mode — toggle button in the header switches between light and dark palettes; preference persisted in `localStorage`; respects `prefers-color-scheme` on first visit; driven entirely by CSS custom properties on `[data-theme="dark"]` ## [0.6.0] - 2026-05-26 diff --git a/README.md b/README.md index 0fa2769..0530615 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ The dashboard will be available at `/solid_stack` (or whatever path you choose). - **Auto-refresh** — dashboard, jobs, processes, and history views poll automatically; pauses when the tab is hidden or a checkbox is checked; intervals configurable via `dashboard_refresh_interval` and `default_refresh_interval` - **Turbo Stream** job discard — removes the row inline without a full page reload - **Dark mode** — toggle button in the header switches between light and dark palettes; preference persisted in `localStorage`; respects `prefers-color-scheme` on first visit +- **Inline notifications** — bulk and single-job actions surface a flash notice; Turbo Stream discard responses inject the message inline without a full page reload; bulk actions report the affected count ("3 jobs discarded") ### Configuration diff --git a/ROADMAP.md b/ROADMAP.md index bf32600..0991d69 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,8 +11,7 @@ The path to v1.0.0 is staged: first achieve feature parity with `solid_queue_das > _Make the interface feel fast and operational, not just functional._ ### Added -o- **Empty-state improvements** — contextual empty states per section with actionable next steps -- **Inline notifications** — flash-style Turbo Stream feedback on bulk actions +- **Empty-state improvements** — contextual empty states per section with actionable next steps - **Responsive layout** — stats cards and tables adapt to narrow viewports - **CSS audit** — review all inline styles, consolidate utility classes, remove duplication, and enforce consistent use of CSS custom properties across all stylesheets diff --git a/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb b/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb index 9d8e8a3..15b86fd 100644 --- a/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb +++ b/app/controllers/solid_stack_web/failed_jobs/selections_controller.rb @@ -4,16 +4,17 @@ class SelectionsController < ApplicationController before_action :set_ids def create + count = @ids.size SolidQueue::FailedExecution.where(id: @ids).each(&:retry) - redirect_to failed_jobs_path + redirect_to failed_jobs_path, notice: "#{count} #{count == 1 ? "job" : "jobs"} retried." rescue => e redirect_to failed_jobs_path, alert: "Could not retry jobs: #{e.message}" end def destroy job_ids = SolidQueue::FailedExecution.where(id: @ids).pluck(:job_id) - SolidQueue::Job.where(id: job_ids).destroy_all - redirect_to failed_jobs_path + count = SolidQueue::Job.where(id: job_ids).destroy_all.size + redirect_to failed_jobs_path, notice: "#{count} #{count == 1 ? "job" : "jobs"} discarded." rescue => e redirect_to failed_jobs_path, alert: "Could not discard jobs: #{e.message}" end diff --git a/app/controllers/solid_stack_web/failed_jobs_controller.rb b/app/controllers/solid_stack_web/failed_jobs_controller.rb index 36d7015..966c51e 100644 --- a/app/controllers/solid_stack_web/failed_jobs_controller.rb +++ b/app/controllers/solid_stack_web/failed_jobs_controller.rb @@ -25,6 +25,7 @@ def destroy @execution = ::SolidQueue::FailedExecution.find(params[:id]) @execution.job.destroy! @executions_remain = ::SolidQueue::FailedExecution.exists? + @notice = "Job discarded." respond_to do |format| format.html { redirect_to failed_jobs_path } @@ -35,7 +36,7 @@ def destroy def retry execution = ::SolidQueue::FailedExecution.find(params[:id]) execution.retry - redirect_to failed_jobs_path + redirect_to failed_jobs_path, notice: "Job retried." end private diff --git a/app/controllers/solid_stack_web/jobs/selections_controller.rb b/app/controllers/solid_stack_web/jobs/selections_controller.rb index e31dc35..ec98d35 100644 --- a/app/controllers/solid_stack_web/jobs/selections_controller.rb +++ b/app/controllers/solid_stack_web/jobs/selections_controller.rb @@ -7,7 +7,7 @@ def destroy ids = Array(params[:job_ids]).map(&:to_i).reject(&:zero?) job_ids = Job::EXECUTION_MODELS[status].where(id: ids).pluck(:job_id) - SolidQueue::Job.where(id: job_ids).destroy_all + count = SolidQueue::Job.where(id: job_ids).destroy_all.size redirect_to jobs_path( status: status, @@ -15,7 +15,7 @@ def destroy queue: params[:queue].presence, period: params[:period].presence_in(PERIOD_DURATIONS.keys), priority: params[:priority].presence - ) + ), notice: "#{count} #{count == 1 ? "job" : "jobs"} discarded." rescue ArgumentError => e redirect_to jobs_path(status: params[:status]), alert: e.message end diff --git a/app/controllers/solid_stack_web/jobs_controller.rb b/app/controllers/solid_stack_web/jobs_controller.rb index 718124c..510f25d 100644 --- a/app/controllers/solid_stack_web/jobs_controller.rb +++ b/app/controllers/solid_stack_web/jobs_controller.rb @@ -30,6 +30,7 @@ def destroy @execution = Job::EXECUTION_MODELS[@status].find(params[:id]) @execution.job.destroy! @executions_remain = Job::EXECUTION_MODELS[@status].exists? + @notice = "Job discarded." respond_to do |format| format.html { redirect_to jobs_path(status: @status, q: @search, queue: @queue, period: @period, priority: @priority) } @@ -37,8 +38,9 @@ def destroy end else job_ids = filtered_scope.pluck(:job_id) - SolidQueue::Job.where(id: job_ids).destroy_all - redirect_to jobs_path(status: @status, q: @search, queue: @queue, period: @period, priority: @priority) + count = SolidQueue::Job.where(id: job_ids).destroy_all.size + redirect_to jobs_path(status: @status, q: @search, queue: @queue, period: @period, priority: @priority), + notice: "#{count} #{count == 1 ? "job" : "jobs"} discarded." end end diff --git a/app/views/layouts/solid_stack_web/application.html.erb b/app/views/layouts/solid_stack_web/application.html.erb index 6c72e06..49525ac 100644 --- a/app/views/layouts/solid_stack_web/application.html.erb +++ b/app/views/layouts/solid_stack_web/application.html.erb @@ -69,9 +69,11 @@ <% end %>
- <% flash.each do |type, message| %> -
<%= message %>
- <% end %> +
+ <% flash.each do |type, message| %> +
<%= message %>
+ <% end %> +
<%= yield %>
diff --git a/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb b/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb index 0efe4b3..3461b90 100644 --- a/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb +++ b/app/views/solid_stack_web/failed_jobs/destroy.turbo_stream.erb @@ -1,3 +1,6 @@ +<%= turbo_stream.prepend "sqw-flash-container", + partial: "solid_stack_web/shared/flash", + locals: { type: "notice", message: @notice } %> <% if @executions_remain %> <%= turbo_stream.remove "execution_#{@execution.id}" %> <% else %> diff --git a/app/views/solid_stack_web/jobs/destroy.turbo_stream.erb b/app/views/solid_stack_web/jobs/destroy.turbo_stream.erb index 927b463..73eb336 100644 --- a/app/views/solid_stack_web/jobs/destroy.turbo_stream.erb +++ b/app/views/solid_stack_web/jobs/destroy.turbo_stream.erb @@ -1,3 +1,6 @@ +<%= turbo_stream.prepend "sqw-flash-container", + partial: "solid_stack_web/shared/flash", + locals: { type: "notice", message: @notice } %> <% if @executions_remain %> <%= turbo_stream.remove "execution_#{@execution.id}" %> <% else %> diff --git a/app/views/solid_stack_web/shared/_flash.html.erb b/app/views/solid_stack_web/shared/_flash.html.erb new file mode 100644 index 0000000..d4256aa --- /dev/null +++ b/app/views/solid_stack_web/shared/_flash.html.erb @@ -0,0 +1 @@ +
<%= message %>
\ No newline at end of file