Class: Wurk::Web::Config
- Inherits:
-
Object
- Object
- Wurk::Web::Config
- Extended by:
- Forwardable
- Defined in:
- lib/wurk/web/config.rb
Overview
Web UI configuration. Holds the authorization callback documented in
docs/target/sidekiq-ent.md §9.2 — a Rack-level hook called with
(env, method, path) per request. Truthy return proceeds; falsey
short-circuits to 403.
Wurk ships the Ent feature in the free gem. No license check; the block runs unconditionally when present.
Example:
Wurk::Web.configure do |c|
c. do |env, method, _path|
user = env['warden']&.user
method == 'GET' ? user&.support? || user&.admin? : user&.admin?
end
end
When no block is registered, every request is authorized (matches Sidekiq's default — no auth until the user opts in).
Constant Summary collapse
- FALSEY_STRINGS =
String forms that mean "off" — so
config.web.read_only = ENV[...]doesn't flip on when the env var is "0"/"false"/empty. ['', '0', 'false', 'no', 'off'].freeze
- PROFILE_VIEW_URL =
Firefox-profiler endpoints for the Profiles pane (spec §25.2). The dashboard uploads a stored profile to
profile_store_urland redirects the operator toprofile_view_url % <returned-hash>. Overridable for self-hosted profiler instances. 'https://profiler.firefox.com/public/%s'- PROFILE_STORE_URL =
'https://api.profiler.firefox.com/compressed-store'- OPTIONS =
Hash-style settings, same surface as Sidekiq::Web::Config (#204): gems and apps write
Sidekiq::Web.configure { |c| c[:csrf] = false }. Seeded like upstream's OPTIONS; unknown keys are stored verbatim so a setting wurk doesn't consume still round-trips (e.g. :csrf — wurk's extension POST guard is the Sec-Fetch-Site check in ExtensionsController, spec §25.1, not a token). { profile_view_url: PROFILE_VIEW_URL, profile_store_url: PROFILE_STORE_URL }.freeze
- DEFAULT_TABS =
Sidekiq's built-in dashboard tabs (spec §25.3). The
tabshash starts as a copy of this; extensions add to it viaregister_extensionor by mutatingtabsdirectly — the same surface third-party gems use. { 'Dashboard' => '', 'Busy' => 'busy', 'Queues' => 'queues', 'Retries' => 'retries', 'Scheduled' => 'scheduled', 'Dead' => 'morgue', 'Metrics' => 'metrics', 'Profiles' => 'profiles' }.freeze
- NATIVE_TAB_PATHS =
Tab paths the SPA already renders natively (Sidekiq DEFAULT_TABS plus wurk's Pro/Ent extras). A gem re-registering one of these — e.g. sidekiq-cron's "cron" — must not produce a duplicate nav item, so
custom_tabsfilters them out. %w[ busy queues retries scheduled dead morgue metrics profiles batches limiters cron search ].freeze
- BUNDLED_LOCALES =
Locale tags the shipped SPA bundle carries copy for, in menu order. Mirrors
availableLocalesin frontend/src/i18n/index.ts — the pair is pinned by WebConfigTest, since a bundle added on one side only would make the server negotiate a locale the SPA can't render (or refuse one it can). %w[en es fr de pt-BR ja zh-CN ar].freeze
- THEMES =
Preferences the SPA's theme runtime understands (frontend/src/theme.ts). 'system' follows the visitor's OS; the palettes themselves are the two
data-themevalues. %w[light dark system].freeze
Instance Attribute Summary collapse
-
#app_url ⇒ Object
Returns the value of attribute app_url.
-
#assets_path ⇒ Object
Returns the value of attribute assets_path.
-
#custom_job_info_rows ⇒ Object
Returns the value of attribute custom_job_info_rows.
-
#default_locale ⇒ Object
Dashboard language.
-
#default_theme ⇒ Object
Dashboard theme hint.
-
#extensions ⇒ Object
readonly
Web-extension surface (spec §25.2).
-
#locales ⇒ Object
readonly
Web-extension surface (spec §25.2).
-
#middlewares ⇒ Object
readonly
Host-app Rack middleware stacked in front of the dashboard, newest last.
-
#offered_locales ⇒ Object
Dashboard language.
-
#read_only_message ⇒ Object
Optional banner copy shown by the dashboard in read-only mode.
-
#tabs ⇒ Object
readonly
Web-extension surface (spec §25.2).
-
#translations ⇒ Object
Host copy overrides for the dashboard, keyed by locale tag and deep-merged over the shipped bundle by the SPA:.
Instance Method Summary collapse
-
#authorization(&block) ⇒ Object
Registers a
(env, method, path) -> truthy/falseyblock. -
#authorized?(env, method, path) ⇒ Boolean
Returns true when no block is registered, otherwise the block's truthiness.
-
#custom_tabs ⇒ Object
Tabs the SPA should render in the nav: everything registered beyond the Sidekiq defaults and wurk's own native pages, as
{ name:, path:, ext_name: }. -
#extension_for_index(path) ⇒ Object
The registered extension whose
indexcoverspath("locks/" and "locks" are the same index). -
#initialize ⇒ Config
constructor
A new instance of Config.
-
#job_info_pairs(job) ⇒ Object
Evaluate the registered
custom_job_info_rowsagainst a job (spec §25.2), returning[[label, value], …]for the SPA's job-detail modal. - #profile_store_url ⇒ Object
- #profile_store_url=(value) ⇒ Object
-
#profile_view_url ⇒ Object
Firefox-profiler URLs — named views over the same @options keys the bracket surface exposes, so
c[:profile_view_url] = …andc.profile_view_url = …can't drift apart. - #profile_view_url=(value) ⇒ Object
-
#rack_app(inner) ⇒ Object
Builds the host-middleware chain wrapping
inner, memoized against[inner, middlewares]—middlewaresis the live array (upstream surface), so direct mutation after the first request triggers a rebuild instead of silently serving the stale chain. -
#read_only=(value) ⇒ Object
Read-only mode.
- #read_only? ⇒ Boolean
-
#register_extension(extension, name:, tab:, index:, root_dir: nil, cache_for: 86_400, asset_paths: nil) {|_self| ... } ⇒ Object
(also: #register)
Matches Sidekiq::Web::Config#register_extension (aliased
register, spec §25.2):tabis the label(s),indexthe path(s),namethe asset namespace. - #reset! ⇒ Object
-
#use(middleware, *args, &block) ⇒ Object
Sidekiq-compatible (
Sidekiq::Web.use).
Constructor Details
#initialize ⇒ Config
Returns a new instance of Config.
89 90 91 |
# File 'lib/wurk/web/config.rb', line 89 def initialize reset! end |
Instance Attribute Details
#app_url ⇒ Object
Returns the value of attribute app_url.
171 172 173 |
# File 'lib/wurk/web/config.rb', line 171 def app_url @app_url end |
#assets_path ⇒ Object
Returns the value of attribute assets_path.
171 172 173 |
# File 'lib/wurk/web/config.rb', line 171 def assets_path @assets_path end |
#custom_job_info_rows ⇒ Object
Returns the value of attribute custom_job_info_rows.
171 172 173 |
# File 'lib/wurk/web/config.rb', line 171 def custom_job_info_rows @custom_job_info_rows end |
#default_locale ⇒ Object
Dashboard language. offered_locales is the set the server negotiates
a request's Accept-Language against before emitting the SPA's
data-locale hint (see Wurk::Web::LocaleNegotiator): it starts as the
locales the bundle ships and may be narrowed, or widened with one the
host supplies copy for through translations. default_locale is the
hint used when the header asks for nothing offered — nil emits no hint
at all, leaving the browser's own preference list to decide.
Not to be confused with locales above: that is the extension
renderer's locale-directory list, a separate i18n system for
server-rendered third-party tabs.
126 127 128 |
# File 'lib/wurk/web/config.rb', line 126 def default_locale @default_locale end |
#default_theme ⇒ Object
Dashboard theme hint. The SPA resolves 'light', 'dark' or 'system' (the default) out of localStorage; this is what it falls back to for a visitor who has never picked one. Injected ahead of the shell's pre-paint script so a host default paints with everything else instead of flashing the other palette. nil emits nothing.
133 134 135 |
# File 'lib/wurk/web/config.rb', line 133 def default_theme @default_theme end |
#extensions ⇒ Object (readonly)
Web-extension surface (spec §25.2). Third-party gems (sidekiq-cron,
sidekiq-unique-jobs, sidekiq-status, …) register dashboard tabs at load
time. tabs is a mutable name→path hash seeded from DEFAULT_TABS;
custom_job_info_rows collects callables that add rows to the job
detail view; app_url / assets_path mirror Sidekiq's accessors.
locales is the mutable locale-directory list extensions append to
(Sidekiq::Web.configure.locales << dir) — the Extension renderer's
t() reads en.yml from every listed dir. Wurk's own SPA i18n is
separate, so it starts empty.
170 171 172 |
# File 'lib/wurk/web/config.rb', line 170 def extensions @extensions end |
#locales ⇒ Object (readonly)
Web-extension surface (spec §25.2). Third-party gems (sidekiq-cron,
sidekiq-unique-jobs, sidekiq-status, …) register dashboard tabs at load
time. tabs is a mutable name→path hash seeded from DEFAULT_TABS;
custom_job_info_rows collects callables that add rows to the job
detail view; app_url / assets_path mirror Sidekiq's accessors.
locales is the mutable locale-directory list extensions append to
(Sidekiq::Web.configure.locales << dir) — the Extension renderer's
t() reads en.yml from every listed dir. Wurk's own SPA i18n is
separate, so it starts empty.
170 171 172 |
# File 'lib/wurk/web/config.rb', line 170 def locales @locales end |
#middlewares ⇒ Object (readonly)
Host-app Rack middleware stacked in front of the dashboard, newest
last. Each entry is [middleware, args, block]. The LIVE array, like
Sidekiq::Web::Config#middlewares — callers mutate it directly
(sidekiq-cron's tests do c.middlewares.clear), so #rack_app
detects drift instead of this returning a frozen copy.
87 88 89 |
# File 'lib/wurk/web/config.rb', line 87 def middlewares @middlewares end |
#offered_locales ⇒ Object
Dashboard language. offered_locales is the set the server negotiates
a request's Accept-Language against before emitting the SPA's
data-locale hint (see Wurk::Web::LocaleNegotiator): it starts as the
locales the bundle ships and may be narrowed, or widened with one the
host supplies copy for through translations. default_locale is the
hint used when the header asks for nothing offered — nil emits no hint
at all, leaving the browser's own preference list to decide.
Not to be confused with locales above: that is the extension
renderer's locale-directory list, a separate i18n system for
server-rendered third-party tabs.
126 127 128 |
# File 'lib/wurk/web/config.rb', line 126 def offered_locales @offered_locales end |
#read_only_message ⇒ Object
Optional banner copy shown by the dashboard in read-only mode. Nil → the SPA falls back to its localized default ("Read-only mode"). Lets a host explain why it's read-only — e.g. the public demo sets "This is a public demo — actions are disabled."
113 114 115 |
# File 'lib/wurk/web/config.rb', line 113 def @read_only_message end |
#tabs ⇒ Object (readonly)
Web-extension surface (spec §25.2). Third-party gems (sidekiq-cron,
sidekiq-unique-jobs, sidekiq-status, …) register dashboard tabs at load
time. tabs is a mutable name→path hash seeded from DEFAULT_TABS;
custom_job_info_rows collects callables that add rows to the job
detail view; app_url / assets_path mirror Sidekiq's accessors.
locales is the mutable locale-directory list extensions append to
(Sidekiq::Web.configure.locales << dir) — the Extension renderer's
t() reads en.yml from every listed dir. Wurk's own SPA i18n is
separate, so it starts empty.
170 171 172 |
# File 'lib/wurk/web/config.rb', line 170 def tabs @tabs end |
#translations ⇒ Object
Host copy overrides for the dashboard, keyed by locale tag and deep-merged over the shipped bundle by the SPA:
c.translations = { 'en' => { 'nav' => { 'queues' => 'Pipelines' } } }
Keyed rather than flat because the negotiated hint is not the last
word — a ?locale= link or a stored pick outranks it — so the entry
has to be chosen in the browser, against the locale actually rendered.
152 153 154 |
# File 'lib/wurk/web/config.rb', line 152 def translations @translations end |
Instance Method Details
#authorization(&block) ⇒ Object
Registers a (env, method, path) -> truthy/falsey block. Re-calling
overwrites; the spec exposes a single hook, not a chain.
156 157 158 159 |
# File 'lib/wurk/web/config.rb', line 156 def (&block) @authorization = block if block @authorization end |
#authorized?(env, method, path) ⇒ Boolean
Returns true when no block is registered, otherwise the block's
truthiness. The path argument is the engine-relative path so a
consumer mounting under /wurk sees /api/stats, not the host's
absolute path — that matches Sidekiq's contract.
292 293 294 295 296 |
# File 'lib/wurk/web/config.rb', line 292 def (env, method, path) return true if @authorization.nil? !!@authorization.call(env, method, path) end |
#custom_tabs ⇒ Object
Tabs the SPA should render in the nav: everything registered beyond the
Sidekiq defaults and wurk's own native pages, as { name:, path:, ext_name: }. ext_name ties the tab back to a registered extension so
the Extension page can fetch its server-rendered view from
ext/:ext_name/*; nil for tabs added by bare tabs[]= mutation (the
SPA falls back to the iframe embed for those).
203 204 205 206 207 208 209 210 211 |
# File 'lib/wurk/web/config.rb', line 203 def custom_tabs @tabs.filter_map do |name, path| # Same trailing-slash normalization as extension_for_index, so a # native path registered as "cron/" doesn't duplicate the native tab. next if DEFAULT_TABS.key?(name) || NATIVE_TAB_PATHS.include?(path.to_s.delete_suffix('/')) { name: name, path: path.to_s, ext_name: extension_for_index(path)&.dig(:name)&.to_s } end end |
#extension_for_index(path) ⇒ Object
The registered extension whose index covers path ("locks/" and
"locks" are the same index).
215 216 217 218 |
# File 'lib/wurk/web/config.rb', line 215 def extension_for_index(path) norm = path.to_s.delete_suffix('/') @extensions.find { |e| Array(e[:index]).any? { |i| i.to_s.delete_suffix('/') == norm } } end |
#job_info_pairs(job) ⇒ Object
Evaluate the registered custom_job_info_rows against a job (spec
§25.2), returning [[label, value], …] for the SPA's job-detail modal.
Each row is a callable (call(job)) or a Sidekiq-style add_pair(job)
object; a row that raises or returns a non-pair is skipped so one bad
extension can't break the job views.
225 226 227 228 229 |
# File 'lib/wurk/web/config.rb', line 225 def job_info_pairs(job) return [] if @custom_job_info_rows.empty? @custom_job_info_rows.filter_map { |row| job_info_pair(row, job) } end |
#profile_store_url ⇒ Object
99 |
# File 'lib/wurk/web/config.rb', line 99 def profile_store_url = @options[:profile_store_url] |
#profile_store_url=(value) ⇒ Object
105 106 107 |
# File 'lib/wurk/web/config.rb', line 105 def profile_store_url=(value) @options[:profile_store_url] = value end |
#profile_view_url ⇒ Object
Firefox-profiler URLs — named views over the same @options keys the
bracket surface exposes, so c[:profile_view_url] = … and
c.profile_view_url = … can't drift apart.
98 |
# File 'lib/wurk/web/config.rb', line 98 def profile_view_url = @options[:profile_view_url] |
#profile_view_url=(value) ⇒ Object
101 102 103 |
# File 'lib/wurk/web/config.rb', line 101 def profile_view_url=(value) @options[:profile_view_url] = value end |
#rack_app(inner) ⇒ Object
Builds the host-middleware chain wrapping inner, memoized against
[inner, middlewares] — middlewares is the live array (upstream
surface), so direct mutation after the first request triggers a
rebuild instead of silently serving the stale chain. Keying on
inner too matters for the same reason: memoizing on the
middleware list alone pins whichever inner app the first caller
passed, forever — a second mount (e.g. per-test Rack app, or a
second engine mount) would silently get served the first one's app.
Production builds exactly once at boot; the per-request comparison
is an == over a handful of entries plus one object identity check.
252 253 254 255 256 257 258 259 260 261 |
# File 'lib/wurk/web/config.rb', line 252 def rack_app(inner) return @rack_app if @rack_app && @rack_app_key == [inner, @middlewares] @rack_app_key = [inner, @middlewares.dup] stack = @middlewares @rack_app = ::Rack::Builder.new do stack.each { |middleware, args, block| use(middleware, *args, &block) } run inner end.to_app end |
#read_only=(value) ⇒ Object
Read-only mode. When on, the Authorization middleware blocks every non-GET request (retry/kill/requeue/delete/pause/resume/clear) with 403, and the SPA hides destructive actions via the /api/meta flag. Defaults from WURK_WEB_READ_ONLY=1 so a viewer-only deploy (e.g. the public demo) needs no Ruby config.
268 269 270 |
# File 'lib/wurk/web/config.rb', line 268 def read_only=(value) @read_only = value.is_a?(String) ? !FALSEY_STRINGS.include?(value.strip.downcase) : !!value end |
#read_only? ⇒ Boolean
272 273 274 |
# File 'lib/wurk/web/config.rb', line 272 def read_only? @read_only end |
#register_extension(extension, name:, tab:, index:, root_dir: nil, cache_for: 86_400, asset_paths: nil) {|_self| ... } ⇒ Object Also known as: register
Matches Sidekiq::Web::Config#register_extension (aliased register,
spec §25.2): tab is the label(s), index the path(s), name the
asset namespace. tab/index are zipped into the tabs hash
(label => path) so the tab surfaces in the SPA nav via /api/meta, and
the extension's registered(app) routes/ERB views are served by
Wurk::Web::Extension::Renderer under ext/:name/* (#187) — the SPA's
Extension page embeds the rendered HTML. Yields self to an optional
block for further config, and returns self. The wide keyword signature
matches Sidekiq::Web::Config#register_extension (spec §25.2).
182 183 184 185 186 187 188 189 190 191 192 193 194 |
# File 'lib/wurk/web/config.rb', line 182 def register_extension(extension, name:, tab:, index:, root_dir: nil, cache_for: 86_400, asset_paths: nil) Array(tab).zip(Array(index)).each { |label, path| @tabs[label] = path if label } # Upstream registers root_dir/locales automatically; extensions # without a root_dir append their dir to `locales` themselves. @locales << ::File.join(root_dir, 'locales') if root_dir @extensions << { extension: extension, name: name, tab: tab, index: index, root_dir: root_dir, cache_for: cache_for, asset_paths: asset_paths } yield self if block_given? self end |
#reset! ⇒ Object
276 277 278 279 280 281 282 283 284 285 286 |
# File 'lib/wurk/web/config.rb', line 276 def reset! @options = OPTIONS.dup @authorization = nil @read_only = env_read_only? @read_only_message = nil @default_theme = nil @middlewares = [] @rack_app = nil init_extensions! init_locales! end |
#use(middleware, *args, &block) ⇒ Object
Sidekiq-compatible (Sidekiq::Web.use). Registers a Rack middleware
that wraps the dashboard, in front of the authorization hook, so a
host app can gate the UI with Devise/Warden/Sorcery/Rack::Auth::Basic
without writing its own middleware. args and an optional block pass
straight through to the middleware's new. Call before the first
request (i.e. from an initializer) — the chain is built once.
237 238 239 240 |
# File 'lib/wurk/web/config.rb', line 237 def use(middleware, *args, &block) @middlewares << [middleware, args, block] @rack_app = nil end |