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. a bare
tabs['Cron'] = 'cron'— must not produce a duplicate nav item, socustom_tabsfilters them out. One an extension answers stays: sidekiq-cron's tab shows that gem's jobs, which the native page knows nothing about. %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).
-
#max_streams ⇒ Object
Cap on concurrent live-update (SSE) streams per web process.
-
#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:.
-
#views ⇒ Object
readonly
Upstream's ERB view-directory list.
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, cached perinner(by identity) against a snapshot ofmiddlewares. -
#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.
90 91 92 |
# File 'lib/wurk/web/config.rb', line 90 def initialize reset! end |
Instance Attribute Details
#app_url ⇒ Object
Returns the value of attribute app_url.
201 202 203 |
# File 'lib/wurk/web/config.rb', line 201 def app_url @app_url end |
#assets_path ⇒ Object
Returns the value of attribute assets_path.
201 202 203 |
# File 'lib/wurk/web/config.rb', line 201 def assets_path @assets_path end |
#custom_job_info_rows ⇒ Object
Returns the value of attribute custom_job_info_rows.
201 202 203 |
# File 'lib/wurk/web/config.rb', line 201 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.
127 128 129 |
# File 'lib/wurk/web/config.rb', line 127 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. Initialization and reset! sample WURK_WEB_DEFAULT_THEME through the validating writer; later explicit assignments (including nil) override that seed.
136 137 138 |
# File 'lib/wurk/web/config.rb', line 136 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.
196 197 198 |
# File 'lib/wurk/web/config.rb', line 196 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.
196 197 198 |
# File 'lib/wurk/web/config.rb', line 196 def locales @locales end |
#max_streams ⇒ Object
Cap on concurrent live-update (SSE) streams per web process. Each open stream pins a server thread, so nil (the default) derives the cap from the server's thread count, leaving the rest for ordinary requests; 0 turns streaming off, and the SPA falls back to polling.
151 152 153 |
# File 'lib/wurk/web/config.rb', line 151 def max_streams @max_streams 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.
88 89 90 |
# File 'lib/wurk/web/config.rb', line 88 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.
127 128 129 |
# File 'lib/wurk/web/config.rb', line 127 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."
114 115 116 |
# File 'lib/wurk/web/config.rb', line 114 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.
196 197 198 |
# File 'lib/wurk/web/config.rb', line 196 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.
169 170 171 |
# File 'lib/wurk/web/config.rb', line 169 def translations @translations end |
#views ⇒ Object (readonly)
Upstream's ERB view-directory list. Wurk's dashboard is the SPA, and
extension views resolve from their own root_dir, so appending here is
accepted and ignored — it exists so gems doing views << dir boot.
200 201 202 |
# File 'lib/wurk/web/config.rb', line 200 def views @views 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.
182 183 184 185 |
# File 'lib/wurk/web/config.rb', line 182 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.
328 329 330 331 332 |
# File 'lib/wurk/web/config.rb', line 328 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).
233 234 235 236 237 238 239 240 241 242 243 244 |
# File 'lib/wurk/web/config.rb', line 233 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) owner = extension_for_index(path) next if owner.nil? && NATIVE_TAB_PATHS.include?(path.to_s.delete_suffix('/')) { name: name, path: path.to_s, ext_name: owner&.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).
248 249 250 251 |
# File 'lib/wurk/web/config.rb', line 248 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.
258 259 260 261 262 |
# File 'lib/wurk/web/config.rb', line 258 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
100 |
# File 'lib/wurk/web/config.rb', line 100 def profile_store_url = @options[:profile_store_url] |
#profile_store_url=(value) ⇒ Object
106 107 108 |
# File 'lib/wurk/web/config.rb', line 106 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.
99 |
# File 'lib/wurk/web/config.rb', line 99 def profile_view_url = @options[:profile_view_url] |
#profile_view_url=(value) ⇒ Object
102 103 104 |
# File 'lib/wurk/web/config.rb', line 102 def profile_view_url=(value) @options[:profile_view_url] = value end |
#rack_app(inner) ⇒ Object
Builds the host-middleware chain wrapping inner, cached per inner
(by identity) against a snapshot of middlewares. Per inner because
two callers live side by side — the engine's MiddlewareStack and the
standalone Sidekiq::Web.call — and a single slot made each request
on one evict the other's chain. The snapshot matters because
middlewares is the live array (upstream surface): direct mutation
after the first request triggers a rebuild instead of silently serving
the stale chain. Copy triples and positional-argument lists so edits
cannot mutate the snapshot too; argument objects and blocks retain
their identities (their internal mutations are not tracked). Steady
state is one Hash lookup plus an == over a handful of entries.
285 286 287 288 289 290 291 292 293 294 295 296 |
# File 'lib/wurk/web/config.rb', line 285 def rack_app(inner) snapshot, app = @rack_apps[inner] return app if app && snapshot == @middlewares stack = middleware_snapshot app = ::Rack::Builder.new do stack.each { |middleware, args, block| use(middleware, *args, &block) } run inner end.to_app @rack_apps[inner] = [stack, app] app end |
#read_only=(value) ⇒ Object
Read-only mode. When on, the Authorization middleware blocks every non-GET request (retry/kill/add_to_queue/delete/pause/unpause/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.
303 304 305 |
# File 'lib/wurk/web/config.rb', line 303 def read_only=(value) @read_only = value.is_a?(String) ? !FALSEY_STRINGS.include?(value.strip.downcase) : !!value end |
#read_only? ⇒ Boolean
307 308 309 |
# File 'lib/wurk/web/config.rb', line 307 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).
212 213 214 215 216 217 218 219 220 221 222 223 224 |
# File 'lib/wurk/web/config.rb', line 212 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
311 312 313 314 315 316 317 318 319 320 321 322 |
# File 'lib/wurk/web/config.rb', line 311 def reset! @options = OPTIONS.dup @authorization = nil @read_only = env_read_only? @read_only_message = nil self.default_theme = ENV.fetch('WURK_WEB_DEFAULT_THEME', nil) @max_streams = nil @middlewares = [] @rack_apps = {}.compare_by_identity 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. Each inner app's chain is
cached until the middleware configuration changes.
270 271 272 |
# File 'lib/wurk/web/config.rb', line 270 def use(middleware, *args, &block) @middlewares << [middleware, args, block] end |