diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c284d8..6edfa01 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Added + +- Solid Cache entry browser — `GET /cache/entries` lists all `SolidCache::Entry` records in a paginated, sortable table (key, byte size, created-at); sortable by any column; key-substring search filters the list; per-row **Delete** removes a single entry; **Flush All** header button deletes every entry with a confirmation prompt; Cache subnav (Overview / Entries) added to the layout + ## [0.4.0] - 2026-05-26 ### Added diff --git a/README.md b/README.md index c2c6784..60e5a22 100644 --- a/README.md +++ b/README.md @@ -94,7 +94,13 @@ Filters are preserved when switching between status tabs (Ready / Scheduled / Ru ## Solid Cache -_Deep cache monitoring coming in v0.5.0. Currently shows entry count and total byte size on the overview dashboard._ +### Features + +- **Overview dashboard card** — live entry count and total byte size +- **Entry browser** — `GET /cache/entries` lists all `SolidCache::Entry` records in a paginated, sortable table; columns: key, byte size, created-at; sortable by any column +- **Key search** — filter entries by key substring +- **Delete entry** — per-row delete button removes a single cache entry +- **Flush All** — header button deletes every cache entry with a confirmation prompt --- diff --git a/ROADMAP.md b/ROADMAP.md index 12ca397..f4a0867 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -11,8 +11,12 @@ The path to v1.0.0 is staged: first achieve feature parity with `solid_queue_das > _Move beyond a single count; give operators visibility into what's in the cache._ ### Added -- **Entry browser** — paginated table of `SolidCache::Entry` records with key, byte size, created_at, and last accessed_at; sorted by size or recency -- **Key-pattern search** — filter entries by key prefix or substring +- **Delete entry** — remove a single cache entry from the browser +- **Flush actions** — "Flush expired" (entries past their TTL), "Flush all" (with confirmation prompt) +- **Size distribution stats** — top-N entries by byte size; histogram bucketed by size range +- **Entry detail** — view the serialised value of a single cache entry (with a configurable `allow_value_preview` toggle for sensitive data) +- **Cache timeline** — 24-hour chart of entry count and total byte size growth +- **Stats dashboard card** — expand the overview card to include hit/miss rates if `SolidCache` exposes them, plus oldest-entry age - **Size distribution stats** — top-N entries by byte size; histogram bucketed by size range - **Entry detail** — view the serialised value of a single cache entry (with a configurable `allow_value_preview` toggle for sensitive data) - **Delete entry** — remove a single cache entry from the browser diff --git a/app/controllers/solid_stack_web/application_controller.rb b/app/controllers/solid_stack_web/application_controller.rb index 5110276..965974d 100644 --- a/app/controllers/solid_stack_web/application_controller.rb +++ b/app/controllers/solid_stack_web/application_controller.rb @@ -16,7 +16,7 @@ class ApplicationController < ActionController::Base def current_section case controller_name when "jobs", "failed_jobs", "queues", "processes", "history", "scheduled_jobs", "recurring_tasks" then :queue - when "cache" then :cache + when "cache", "cache_entries" then :cache when "cable" then :cable else :overview end diff --git a/app/controllers/solid_stack_web/cache/flushes_controller.rb b/app/controllers/solid_stack_web/cache/flushes_controller.rb new file mode 100644 index 0000000..b53967f --- /dev/null +++ b/app/controllers/solid_stack_web/cache/flushes_controller.rb @@ -0,0 +1,8 @@ +module SolidStackWeb + class Cache::FlushesController < ApplicationController + def destroy + ::SolidCache::Entry.delete_all + redirect_to cache_entries_path, notice: "All cache entries flushed." + end + end +end diff --git a/app/controllers/solid_stack_web/cache_entries_controller.rb b/app/controllers/solid_stack_web/cache_entries_controller.rb new file mode 100644 index 0000000..f15ecde --- /dev/null +++ b/app/controllers/solid_stack_web/cache_entries_controller.rb @@ -0,0 +1,28 @@ +module SolidStackWeb + class CacheEntriesController < ApplicationController + def index + @search = params[:q].presence + @sort = resolve_sort + + scope = ::SolidCache::Entry.all + scope = scope.where("key LIKE ?", "%#{::ActiveRecord::Base.sanitize_sql_like(@search)}%") if @search + scope = scope.order(@sort["column"] => @sort["direction"]) + + @pagy, @entries = pagy(scope) + end + + def destroy + ::SolidCache::Entry.find(params[:id]).destroy + redirect_to cache_entries_path(q: params[:q], column: params[:column], direction: params[:direction]), + notice: "Cache entry deleted." + end + + private + + def resolve_sort + column = %w[byte_size created_at key].include?(params[:column]) ? params[:column] : "byte_size" + direction = %w[asc desc].include?(params[:direction]) ? params[:direction] : "desc" + { "column" => column, "direction" => direction } + end + end +end diff --git a/app/javascript/solid_stack_web/application.js b/app/javascript/solid_stack_web/application.js index 050a9d5..3c5b90b 100644 --- a/app/javascript/solid_stack_web/application.js +++ b/app/javascript/solid_stack_web/application.js @@ -1,10 +1,12 @@ import "@hotwired/turbo" import { Application } from "@hotwired/stimulus" import RefreshController from "solid_stack_web/refresh_controller" +import SearchController from "solid_stack_web/search_controller" import SelectionController from "solid_stack_web/selection_controller" import SparklineTooltipController from "solid_stack_web/sparkline_tooltip_controller" const application = Application.start() application.register("refresh", RefreshController) +application.register("search", SearchController) application.register("selection", SelectionController) application.register("sparkline-tooltip", SparklineTooltipController) \ No newline at end of file diff --git a/app/javascript/solid_stack_web/search_controller.js b/app/javascript/solid_stack_web/search_controller.js new file mode 100644 index 0000000..59de917 --- /dev/null +++ b/app/javascript/solid_stack_web/search_controller.js @@ -0,0 +1,16 @@ +import { Controller } from "@hotwired/stimulus" + +export default class extends Controller { + filter({ target }) { + clearTimeout(this._timer) + const len = target.value.length + if (len >= 4 || len === 0) { + this._timer = setTimeout(() => target.form.requestSubmit(), 300) + } + } + + select({ target }) { + clearTimeout(this._timer) + target.form.requestSubmit() + } +} \ No newline at end of file diff --git a/app/views/layouts/solid_stack_web/application.html.erb b/app/views/layouts/solid_stack_web/application.html.erb index 1df5f2e..462f69a 100644 --- a/app/views/layouts/solid_stack_web/application.html.erb +++ b/app/views/layouts/solid_stack_web/application.html.erb @@ -24,6 +24,17 @@ + <% if current_section == :cache %> + + <% end %> + <% if current_section == :queue %>