roundhouse_ui 0.1.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 (60) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +166 -0
  4. data/Rakefile +3 -0
  5. data/app/assets/javascripts/roundhouse_ui/turbo.min.js +35 -0
  6. data/app/assets/stylesheets/roundhouse_ui/application.css +15 -0
  7. data/app/controllers/concerns/roundhouse_ui/job_set_browsing.rb +41 -0
  8. data/app/controllers/roundhouse_ui/application_controller.rb +75 -0
  9. data/app/controllers/roundhouse_ui/assets_controller.rb +16 -0
  10. data/app/controllers/roundhouse_ui/audit_controller.rb +7 -0
  11. data/app/controllers/roundhouse_ui/busy_controller.rb +29 -0
  12. data/app/controllers/roundhouse_ui/capsules_controller.rb +27 -0
  13. data/app/controllers/roundhouse_ui/dashboard_controller.rb +26 -0
  14. data/app/controllers/roundhouse_ui/dead_controller.rb +46 -0
  15. data/app/controllers/roundhouse_ui/errors_controller.rb +50 -0
  16. data/app/controllers/roundhouse_ui/jobs_controller.rb +94 -0
  17. data/app/controllers/roundhouse_ui/metrics_controller.rb +8 -0
  18. data/app/controllers/roundhouse_ui/queues_controller.rb +40 -0
  19. data/app/controllers/roundhouse_ui/redis_controller.rb +21 -0
  20. data/app/controllers/roundhouse_ui/retries_controller.rb +34 -0
  21. data/app/controllers/roundhouse_ui/scheduled_controller.rb +34 -0
  22. data/app/controllers/roundhouse_ui/snapshots_controller.rb +26 -0
  23. data/app/controllers/roundhouse_ui/workers_controller.rb +33 -0
  24. data/app/helpers/roundhouse_ui/application_helper.rb +4 -0
  25. data/app/helpers/roundhouse_ui/nav_helper.rb +24 -0
  26. data/app/helpers/roundhouse_ui/observability_helper.rb +13 -0
  27. data/app/views/layouts/roundhouse_ui/application.html.erb +365 -0
  28. data/app/views/roundhouse_ui/audit/index.html.erb +21 -0
  29. data/app/views/roundhouse_ui/busy/index.html.erb +23 -0
  30. data/app/views/roundhouse_ui/capsules/index.html.erb +22 -0
  31. data/app/views/roundhouse_ui/dashboard/show.html.erb +68 -0
  32. data/app/views/roundhouse_ui/dead/index.html.erb +46 -0
  33. data/app/views/roundhouse_ui/errors/index.html.erb +28 -0
  34. data/app/views/roundhouse_ui/jobs/_form.html.erb +24 -0
  35. data/app/views/roundhouse_ui/jobs/edit.html.erb +2 -0
  36. data/app/views/roundhouse_ui/jobs/new.html.erb +2 -0
  37. data/app/views/roundhouse_ui/jobs/show.html.erb +33 -0
  38. data/app/views/roundhouse_ui/metrics/show.html.erb +49 -0
  39. data/app/views/roundhouse_ui/queues/index.html.erb +45 -0
  40. data/app/views/roundhouse_ui/redis/show.html.erb +59 -0
  41. data/app/views/roundhouse_ui/retries/index.html.erb +33 -0
  42. data/app/views/roundhouse_ui/scheduled/index.html.erb +29 -0
  43. data/app/views/roundhouse_ui/shared/_pager.html.erb +15 -0
  44. data/app/views/roundhouse_ui/snapshots/index.html.erb +25 -0
  45. data/app/views/roundhouse_ui/workers/index.html.erb +39 -0
  46. data/config/routes.rb +54 -0
  47. data/lib/roundhouse_ui/audit.rb +25 -0
  48. data/lib/roundhouse_ui/cancel_middleware.rb +19 -0
  49. data/lib/roundhouse_ui/cancellation.rb +37 -0
  50. data/lib/roundhouse_ui/engine.rb +5 -0
  51. data/lib/roundhouse_ui/fetch.rb +36 -0
  52. data/lib/roundhouse_ui/metrics.rb +51 -0
  53. data/lib/roundhouse_ui/observability.rb +46 -0
  54. data/lib/roundhouse_ui/pause.rb +59 -0
  55. data/lib/roundhouse_ui/redaction.rb +33 -0
  56. data/lib/roundhouse_ui/snapshots.rb +90 -0
  57. data/lib/roundhouse_ui/version.rb +3 -0
  58. data/lib/roundhouse_ui.rb +73 -0
  59. data/lib/tasks/roundhouse_ui_tasks.rake +4 -0
  60. metadata +131 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 1dd0ae0f36eaaafd7bce6b40d6c420060fd7c35d632bee5da737821d081e8771
4
+ data.tar.gz: 4281ee8bf890c9819c73d4cf8a756e7dd7baa3f329456b1dcfe8750b9eb83147
5
+ SHA512:
6
+ metadata.gz: 4a19cfd3a5c7ad395f2ac271eaac50a8b3f6965ee9a1d7b68a3cf0096d1acba7a2e7afffd29816968556cf54f3f3d3889ceb766f8c2d1a5b5d6c1bc7987195c8
7
+ data.tar.gz: d85573bfc800cbcd853c6187c2863862d7e5d812eaaedaee71523bc663c06d6d001b0cd5c2e3effe12cc40007655137ac88116e1548237e8a65e7ee6738ddf9b
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright R.J. Robinson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,166 @@
1
+ <img width="3532" height="1956" alt="CleanShot 2026-06-27 at 21 07 23@2x" src="https://github.com/user-attachments/assets/7c27c997-d8d1-4e8e-b982-94c624ebfa2a" />
2
+ # Roundhouse
3
+
4
+ **A modern, real-time web UI for Sidekiq.**
5
+
6
+ [![CI](https://github.com/rjrobinson/roundhouse_ui/actions/workflows/ci.yml/badge.svg)](https://github.com/rjrobinson/roundhouse_ui/actions/workflows/ci.yml)
7
+ [![Gem Version](https://img.shields.io/gem/v/roundhouse_ui)](https://rubygems.org/gems/roundhouse_ui)
8
+ [![Ruby](https://img.shields.io/badge/ruby-%3E%3D%203.1-CC342D?logo=ruby&logoColor=white)](https://www.ruby-lang.org)
9
+ [![Rails](https://img.shields.io/badge/rails-%3E%3D%207.0-D30001?logo=rubyonrails&logoColor=white)](https://rubyonrails.org)
10
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](MIT-LICENSE)
11
+
12
+ Roundhouse is a mountable Rails engine that replaces the stock Sidekiq Web UI with a
13
+ control plane built for the way you actually operate background jobs: live stats,
14
+ searchable sets, grouped errors, safe queue management, job inspection/editing, and
15
+ Redis health — all server-rendered with Turbo (no build step), and **no Sidekiq Pro
16
+ required**.
17
+
18
+ > Gem name is `roundhouse_ui`; the brand and mount path are **Roundhouse**.
19
+
20
+ ## Features
21
+
22
+ - **Live dashboard** — throughput chart + stat cards refresh in place (no reload); polling pauses when the tab is hidden.
23
+ - **Search** — find a job across the dead/retry/scheduled sets by class, JID, error, or argument value.
24
+ - **Bulk actions** — select many dead jobs and retry/delete them at once (not 25-at-a-time).
25
+ - **Grouped errors** — failures fingerprinted by `class + error`, so one bad deploy is a single issue with a count.
26
+ - **Queue management** — pause/resume (OSS, see below), purge with an impact count, and **snapshot → restore**.
27
+ - **Job inspection & editing** — view a job's full args (with redaction), error, and backtrace; edit & re-enqueue, or enqueue a brand-new job (opt-in).
28
+ - **Workers** — process fleet with quiet/stop, threads, queues, and heartbeat.
29
+ - **Redis pressure** — memory, ops/sec, and the eviction-policy check that flags silent job loss.
30
+ - **Audit log** — every state-changing action recorded and attributable.
31
+ - **⌘K command palette**, light/dark themes, read-only mode, and a strict self-contained CSP.
32
+
33
+ Everything reads through Sidekiq's public API — **no database**.
34
+
35
+ ## Requirements
36
+
37
+ - Ruby >= 3.1 · Rails >= 7.0 · Sidekiq >= 7.0
38
+
39
+ ## Installation
40
+
41
+ ```ruby
42
+ # Gemfile
43
+ gem "roundhouse_ui"
44
+ ```
45
+
46
+ ## Mounting
47
+
48
+ Roundhouse is auth-agnostic — wrap the mount in whatever your app already uses.
49
+
50
+ ```ruby
51
+ # config/routes.rb
52
+ authenticate :user, ->(u) { u.admin? } do # Devise example
53
+ mount RoundhouseUi::Engine => "/roundhouse"
54
+ end
55
+ ```
56
+
57
+ It ships **no authentication** — always mount it behind yours; it exposes operational
58
+ controls over your job system.
59
+
60
+ ## Configuration
61
+
62
+ ```ruby
63
+ # config/initializers/roundhouse.rb
64
+ RoundhouseUi.configure do |c|
65
+ # Disable every destructive action (purge/retry/delete/edit) server-side.
66
+ c.read_only = !Rails.env.development?
67
+
68
+ # Enqueue new jobs and edit/re-enqueue existing ones from the UI (sharp tool — off by default).
69
+ c.allow_job_editing = Rails.env.development?
70
+
71
+ # Mask sensitive argument keys (case-insensitive substring) wherever args are displayed.
72
+ c.redact_args = %w[password token secret api_key authorization]
73
+
74
+ # Attribute audit entries to the signed-in user instead of "anonymous".
75
+ c.actor_resolver = ->(controller) { controller.current_user&.email }
76
+
77
+ # Deep-link jobs out to your APM (see Observability).
78
+ c.observability = RoundhouseUi::Observability::DatadogAdapter.new(service: "my-app")
79
+
80
+ # Where queue snapshots are stored (default: Redis). Swap for a file/S3 store.
81
+ # c.snapshot_store = MyS3SnapshotStore.new
82
+ end
83
+ ```
84
+
85
+ ## Pausing queues
86
+
87
+ Pause/resume is pure OSS. To make a pause actually stop a queue from being worked,
88
+ install Roundhouse's fetch strategy in your Sidekiq **server** config:
89
+
90
+ ```ruby
91
+ # config/initializers/sidekiq.rb
92
+ Sidekiq.configure_server do |config|
93
+ config[:fetch_class] = RoundhouseUi::Fetch
94
+ end
95
+ ```
96
+
97
+ `RoundhouseUi::Fetch` subclasses `Sidekiq::BasicFetch` and skips paused queues, inheriting
98
+ all of Sidekiq's weighting/ordering. Until it's installed, the Queues page records pauses
99
+ but **warns that they aren't enforced** (worker and web are separate processes, so
100
+ Roundhouse detects whether a fetcher has reported in).
101
+
102
+ ## Cancelling jobs
103
+
104
+ Cancellation is cooperative — Ruby can't safely kill a running thread. Install the
105
+ middleware so a cancelled job is dropped before it runs:
106
+
107
+ ```ruby
108
+ # config/initializers/sidekiq.rb
109
+ Sidekiq.configure_server do |config|
110
+ config.server_middleware { |chain| chain.add RoundhouseUi::CancelMiddleware }
111
+ end
112
+ ```
113
+
114
+ The **Busy** page's Cancel button flags a job's JID. A queued/scheduled/retrying job
115
+ is then skipped when it would next run; a *currently running* job stops only if it
116
+ checks in — e.g. a long loop can `break if RoundhouseUi.cancelled?(jid)`.
117
+
118
+ ## Observability deep-links
119
+
120
+ The core depends on nothing — it asks the configured adapter for a URL and renders a link
121
+ only if one comes back. A Datadog adapter ships in the box; write your own by duck-typing
122
+ `job_url` / `queue_url` / `label`:
123
+
124
+ ```ruby
125
+ RoundhouseUi.observability = RoundhouseUi::Observability::DatadogAdapter.new(site: "datadoghq.com", service: "my-app")
126
+ ```
127
+
128
+ ## Snapshots
129
+
130
+ Back up a queue before purging it (the safety net for clearing a stuck queue), then restore.
131
+ Storage is pluggable via `RoundhouseUi.snapshot_store` (default: Redis). For large/stuck
132
+ queues use a file or S3 store so the backup doesn't sit in the Redis you're trying to relieve.
133
+
134
+ ## Security
135
+
136
+ - All destructive actions are CSRF-protected `POST`s — never GET — and gated by `read_only`.
137
+ - Roundhouse sets its own strict, self-contained Content-Security-Policy on its responses
138
+ (nonce'd inline script, same-origin only), so it's safe even if the host sets no policy.
139
+ - Configure `redact_args` to keep tokens/PII out of the UI; the audit log records who did what.
140
+
141
+ ## Keyboard
142
+
143
+ `⌘K` (or `Ctrl+K`) opens the command palette — jump to any view or action.
144
+
145
+ ## Development
146
+
147
+ ```bash
148
+ bin/rails test # full suite, ~1s, no Redis required (Sidekiq's API is stubbed)
149
+ bundle exec rubocop # lint
150
+ ```
151
+
152
+ The dummy app under `test/dummy` mounts the engine at `/roundhouse`; point it at a local
153
+ Redis and run `bin/rails server` to click around.
154
+
155
+ ## Roadmap
156
+
157
+ - Multi-Redis / multi-cluster view (one pane across shards).
158
+ - Capsules and cron/periodic views.
159
+
160
+ ## Contributing
161
+
162
+ Bug reports and pull requests welcome at https://github.com/rjrobinson/roundhouse_ui.
163
+
164
+ ## License
165
+
166
+ Available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,3 @@
1
+ require "bundler/setup"
2
+
3
+ require "bundler/gem_tasks"
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Minified by jsDelivr using Terser v5.37.0.
3
+ * Original file: /npm/@hotwired/turbo@8.0.12/dist/turbo.es2017-umd.js
4
+ *
5
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
6
+ */
7
+ /*!
8
+ Turbo 8.0.12
9
+ Copyright © 2024 37signals LLC
10
+ */
11
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).Turbo={})}(this,(function(e){"use strict";!function(e){function t(e,t,s){throw new e("Failed to execute 'requestSubmit' on 'HTMLFormElement': "+t+".",s)}"function"!=typeof e.requestSubmit&&(e.requestSubmit=function(e){e?(!function(e,s){e instanceof HTMLElement||t(TypeError,"parameter 1 is not of type 'HTMLElement'"),"submit"==e.type||t(TypeError,"The specified element is not a submit button"),e.form==s||t(DOMException,"The specified element is not owned by this form element","NotFoundError")}(e,this),e.click()):((e=document.createElement("input")).type="submit",e.hidden=!0,this.appendChild(e),e.click(),this.removeChild(e))})}(HTMLFormElement.prototype);const t=new WeakMap;function s(e){const s=function(e){const t=e instanceof Element?e:e instanceof Node?e.parentElement:null,s=t?t.closest("input, button"):null;return"submit"==s?.type?s:null}(e.target);s&&s.form&&t.set(s.form,s)}!function(){if("submitter"in Event.prototype)return;let e=window.Event.prototype;if("SubmitEvent"in window){const t=window.SubmitEvent.prototype;if(!/Apple Computer/.test(navigator.vendor)||"submitter"in t)return;e=t}addEventListener("click",s,!0),Object.defineProperty(e,"submitter",{get(){if("submit"==this.type&&this.target instanceof HTMLFormElement)return t.get(this.target)}})}();const r={eager:"eager",lazy:"lazy"};class i extends HTMLElement{static delegateConstructor=void 0;loaded=Promise.resolve();static get observedAttributes(){return["disabled","loading","src"]}constructor(){super(),this.delegate=new i.delegateConstructor(this)}connectedCallback(){this.delegate.connect()}disconnectedCallback(){this.delegate.disconnect()}reload(){return this.delegate.sourceURLReloaded()}attributeChangedCallback(e){"loading"==e?this.delegate.loadingStyleChanged():"src"==e?this.delegate.sourceURLChanged():"disabled"==e&&this.delegate.disabledChanged()}get src(){return this.getAttribute("src")}set src(e){e?this.setAttribute("src",e):this.removeAttribute("src")}get refresh(){return this.getAttribute("refresh")}set refresh(e){e?this.setAttribute("refresh",e):this.removeAttribute("refresh")}get shouldReloadWithMorph(){return this.src&&"morph"===this.refresh}get loading(){return function(e){if("lazy"===e.toLowerCase())return r.lazy;return r.eager}(this.getAttribute("loading")||"")}set loading(e){e?this.setAttribute("loading",e):this.removeAttribute("loading")}get disabled(){return this.hasAttribute("disabled")}set disabled(e){e?this.setAttribute("disabled",""):this.removeAttribute("disabled")}get autoscroll(){return this.hasAttribute("autoscroll")}set autoscroll(e){e?this.setAttribute("autoscroll",""):this.removeAttribute("autoscroll")}get complete(){return!this.delegate.isLoading}get isActive(){return this.ownerDocument===document&&!this.isPreview}get isPreview(){return this.ownerDocument?.documentElement?.hasAttribute("data-turbo-preview")}}const n={enabled:!0,progressBarDelay:500,unvisitableExtensions:new Set([".7z",".aac",".apk",".avi",".bmp",".bz2",".css",".csv",".deb",".dmg",".doc",".docx",".exe",".gif",".gz",".heic",".heif",".ico",".iso",".jpeg",".jpg",".js",".json",".m4a",".mkv",".mov",".mp3",".mp4",".mpeg",".mpg",".msi",".ogg",".ogv",".pdf",".pkg",".png",".ppt",".pptx",".rar",".rtf",".svg",".tar",".tif",".tiff",".txt",".wav",".webm",".webp",".wma",".wmv",".xls",".xlsx",".xml",".zip"])};function o(e){if("false"==e.getAttribute("data-turbo-eval"))return e;{const t=document.createElement("script"),s=R();return s&&(t.nonce=s),t.textContent=e.textContent,t.async=!1,function(e,t){for(const{name:s,value:r}of t.attributes)e.setAttribute(s,r)}(t,e),t}}function a(e,{target:t,cancelable:s,detail:r}={}){const i=new CustomEvent(e,{cancelable:s,bubbles:!0,composed:!0,detail:r});return t&&t.isConnected?t.dispatchEvent(i):document.documentElement.dispatchEvent(i),i}function l(e){e.preventDefault(),e.stopImmediatePropagation()}function c(){return"hidden"===document.visibilityState?d():h()}function h(){return new Promise((e=>requestAnimationFrame((()=>e()))))}function d(){return new Promise((e=>setTimeout((()=>e()),0)))}function u(e=""){return(new DOMParser).parseFromString(e,"text/html")}function m(e,...t){const s=function(e,t){return e.reduce(((e,s,r)=>e+s+(null==t[r]?"":t[r])),"")}(e,t).replace(/^\n/,"").split("\n"),r=s[0].match(/^\s+/),i=r?r[0].length:0;return s.map((e=>e.slice(i))).join("\n")}function p(){return Array.from({length:36}).map(((e,t)=>8==t||13==t||18==t||23==t?"-":14==t?"4":19==t?(Math.floor(4*Math.random())+8).toString(16):Math.floor(15*Math.random()).toString(16))).join("")}function f(e,...t){for(const s of t.map((t=>t?.getAttribute(e))))if("string"==typeof s)return s;return null}function g(...e){for(const t of e)"turbo-frame"==t.localName&&t.setAttribute("busy",""),t.setAttribute("aria-busy","true")}function b(...e){for(const t of e)"turbo-frame"==t.localName&&t.removeAttribute("busy"),t.removeAttribute("aria-busy")}function v(e,t=2e3){return new Promise((s=>{const r=()=>{e.removeEventListener("error",r),e.removeEventListener("load",r),s()};e.addEventListener("load",r,{once:!0}),e.addEventListener("error",r,{once:!0}),setTimeout(s,t)}))}function S(e){switch(e){case"replace":return history.replaceState;case"advance":case"restore":return history.pushState}}function w(...e){const t=f("data-turbo-action",...e);return function(e){return"advance"==e||"replace"==e||"restore"==e}(t)?t:null}function E(e){return document.querySelector(`meta[name="${e}"]`)}function y(e){const t=E(e);return t&&t.content}function R(){const e=E("csp-nonce");if(e){const{nonce:t,content:s}=e;return""==t?s:t}}function L(e,t){if(e instanceof Element)return e.closest(t)||L(e.assignedSlot||e.getRootNode()?.host,t)}function T(e){return!!e&&null==e.closest("[inert], :disabled, [hidden], details:not([open]), dialog:not([open])")&&"function"==typeof e.focus}function A(e){return Array.from(e.querySelectorAll("[autofocus]")).find(T)}function P(e){if("_blank"===e)return!1;if(e){for(const t of document.getElementsByName(e))if(t instanceof HTMLIFrameElement)return!1;return!0}return!0}function C(e){return L(e,"a[href]:not([target^=_]):not([download])")}function k(e){return q(e.getAttribute("href")||"")}const M={"aria-disabled":{beforeSubmit:e=>{e.setAttribute("aria-disabled","true"),e.addEventListener("click",l)},afterSubmit:e=>{e.removeAttribute("aria-disabled"),e.removeEventListener("click",l)}},disabled:{beforeSubmit:e=>e.disabled=!0,afterSubmit:e=>e.disabled=!1}};const F=new class{#e=null;constructor(e){Object.assign(this,e)}get submitter(){return this.#e}set submitter(e){this.#e=M[e]||e}}({mode:"on",submitter:"disabled"}),I={drive:n,forms:F};function q(e){return new URL(e.toString(),document.baseURI)}function H(e){let t;return e.hash?e.hash.slice(1):(t=e.href.match(/#(.*)$/))?t[1]:void 0}function B(e,t){return q(t?.getAttribute("formaction")||e.getAttribute("action")||e.action)}function O(e){return(function(e){return function(e){return e.pathname.split("/").slice(1)}(e).slice(-1)[0]}(e).match(/\.[^.]*$/)||[])[0]||""}function N(e,t){const s=function(e){return t=e.origin+e.pathname,t.endsWith("/")?t:t+"/";var t}(t);return e.href===q(s).href||e.href.startsWith(s)}function x(e,t){return N(e,t)&&!I.drive.unvisitableExtensions.has(O(e))}function V(e){const t=H(e);return null!=t?e.href.slice(0,-(t.length+1)):e.href}function D(e){return V(e)}class W{constructor(e){this.response=e}get succeeded(){return this.response.ok}get failed(){return!this.succeeded}get clientError(){return this.statusCode>=400&&this.statusCode<=499}get serverError(){return this.statusCode>=500&&this.statusCode<=599}get redirected(){return this.response.redirected}get location(){return q(this.response.url)}get isHTML(){return this.contentType&&this.contentType.match(/^(?:text\/([^\s;,]+\b)?html|application\/xhtml\+xml)\b/)}get statusCode(){return this.response.status}get contentType(){return this.header("Content-Type")}get responseText(){return this.response.clone().text()}get responseHTML(){return this.isHTML?this.response.clone().text():Promise.resolve(void 0)}header(e){return this.response.headers.get(e)}}class U extends Set{constructor(e){super(),this.maxSize=e}add(e){if(this.size>=this.maxSize){const e=this.values().next().value;this.delete(e)}super.add(e)}}const j=new U(20),$=window.fetch;function z(e,t={}){const s=new Headers(t.headers||{}),r=p();return j.add(r),s.append("X-Turbo-Request-Id",r),$(e,{...t,headers:s})}function _(e){switch(e.toLowerCase()){case"get":return X.get;case"post":return X.post;case"put":return X.put;case"patch":return X.patch;case"delete":return X.delete}}const X={get:"get",post:"post",put:"put",patch:"patch",delete:"delete"};function K(e){switch(e.toLowerCase()){case Q.multipart:return Q.multipart;case Q.plain:return Q.plain;default:return Q.urlEncoded}}const Q={urlEncoded:"application/x-www-form-urlencoded",multipart:"multipart/form-data",plain:"text/plain"};class Y{abortController=new AbortController;#t=e=>{};constructor(e,t,s,r=new URLSearchParams,i=null,n=Q.urlEncoded){const[o,a]=J(q(s),t,r,n);this.delegate=e,this.url=o,this.target=i,this.fetchOptions={credentials:"same-origin",redirect:"follow",method:t.toUpperCase(),headers:{...this.defaultHeaders},body:a,signal:this.abortSignal,referrer:this.delegate.referrer?.href},this.enctype=n}get method(){return this.fetchOptions.method}set method(e){const t=this.isSafe?this.url.searchParams:this.fetchOptions.body||new FormData,s=_(e)||X.get;this.url.search="";const[r,i]=J(this.url,s,t,this.enctype);this.url=r,this.fetchOptions.body=i,this.fetchOptions.method=s.toUpperCase()}get headers(){return this.fetchOptions.headers}set headers(e){this.fetchOptions.headers=e}get body(){return this.isSafe?this.url.searchParams:this.fetchOptions.body}set body(e){this.fetchOptions.body=e}get location(){return this.url}get params(){return this.url.searchParams}get entries(){return this.body?Array.from(this.body.entries()):[]}cancel(){this.abortController.abort()}async perform(){const{fetchOptions:e}=this;this.delegate.prepareRequest(this);const t=await this.#s(e);try{this.delegate.requestStarted(this),t.detail.fetchRequest?this.response=t.detail.fetchRequest.response:this.response=z(this.url.href,e);const s=await this.response;return await this.receive(s)}catch(e){if("AbortError"!==e.name)throw this.#r(e)&&this.delegate.requestErrored(this,e),e}finally{this.delegate.requestFinished(this)}}async receive(e){const t=new W(e);return a("turbo:before-fetch-response",{cancelable:!0,detail:{fetchResponse:t},target:this.target}).defaultPrevented?this.delegate.requestPreventedHandlingResponse(this,t):t.succeeded?this.delegate.requestSucceededWithResponse(this,t):this.delegate.requestFailedWithResponse(this,t),t}get defaultHeaders(){return{Accept:"text/html, application/xhtml+xml"}}get isSafe(){return G(this.method)}get abortSignal(){return this.abortController.signal}acceptResponseType(e){this.headers.Accept=[e,this.headers.Accept].join(", ")}async#s(e){const t=new Promise((e=>this.#t=e)),s=a("turbo:before-fetch-request",{cancelable:!0,detail:{fetchOptions:e,url:this.url,resume:this.#t},target:this.target});return this.url=s.detail.url,s.defaultPrevented&&await t,s}#r(e){return!a("turbo:fetch-request-error",{target:this.target,cancelable:!0,detail:{request:this,error:e}}).defaultPrevented}}function G(e){return _(e)==X.get}function J(e,t,s,r){const i=Array.from(s).length>0?new URLSearchParams(Z(s)):e.searchParams;return G(t)?[ee(e,i),null]:r==Q.urlEncoded?[e,i]:[e,s]}function Z(e){const t=[];for(const[s,r]of e)r instanceof File||t.push([s,r]);return t}function ee(e,t){const s=new URLSearchParams(Z(t));return e.search=s.toString(),e}class te{started=!1;constructor(e,t){this.delegate=e,this.element=t,this.intersectionObserver=new IntersectionObserver(this.intersect)}start(){this.started||(this.started=!0,this.intersectionObserver.observe(this.element))}stop(){this.started&&(this.started=!1,this.intersectionObserver.unobserve(this.element))}intersect=e=>{const t=e.slice(-1)[0];t?.isIntersecting&&this.delegate.elementAppearedInViewport(this.element)}}class se{static contentType="text/vnd.turbo-stream.html";static wrap(e){return"string"==typeof e?new this(function(e){const t=document.createElement("template");return t.innerHTML=e,t.content}(e)):e}constructor(e){this.fragment=function(e){for(const t of e.querySelectorAll("turbo-stream")){const e=document.importNode(t,!0);for(const t of e.templateElement.content.querySelectorAll("script"))t.replaceWith(o(t));t.replaceWith(e)}return e}(e)}}const re=new class{#i=null;#n=null;get(e){if(this.#n&&this.#n.url===e&&this.#n.expire>Date.now())return this.#n.request}setLater(e,t,s){this.clear(),this.#i=setTimeout((()=>{t.perform(),this.set(e,t,s),this.#i=null}),100)}set(e,t,s){this.#n={url:e,request:t,expire:new Date((new Date).getTime()+s)}}clear(){this.#i&&clearTimeout(this.#i),this.#n=null}},ie={initialized:"initialized",requesting:"requesting",waiting:"waiting",receiving:"receiving",stopping:"stopping",stopped:"stopped"};class ne{state=ie.initialized;static confirmMethod(e){return Promise.resolve(confirm(e))}constructor(e,t,s,r=!1){const i=function(e,t){const s=t?.getAttribute("formmethod")||e.getAttribute("method")||"";return _(s.toLowerCase())||X.get}(t,s),n=function(e,t){const s=q(e);G(t)&&(s.search="");return s}(function(e,t){const s="string"==typeof e.action?e.action:null;return t?.hasAttribute("formaction")?t.getAttribute("formaction")||"":e.getAttribute("action")||s||""}(t,s),i),o=function(e,t){const s=new FormData(e),r=t?.getAttribute("name"),i=t?.getAttribute("value");r&&s.append(r,i||"");return s}(t,s),a=function(e,t){return K(t?.getAttribute("formenctype")||e.enctype)}(t,s);this.delegate=e,this.formElement=t,this.submitter=s,this.fetchRequest=new Y(this,i,n,o,t,a),this.mustRedirect=r}get method(){return this.fetchRequest.method}set method(e){this.fetchRequest.method=e}get action(){return this.fetchRequest.url.toString()}set action(e){this.fetchRequest.url=q(e)}get body(){return this.fetchRequest.body}get enctype(){return this.fetchRequest.enctype}get isSafe(){return this.fetchRequest.isSafe}get location(){return this.fetchRequest.url}async start(){const{initialized:e,requesting:t}=ie,s=f("data-turbo-confirm",this.submitter,this.formElement);if("string"==typeof s){const e="function"==typeof I.forms.confirm?I.forms.confirm:ne.confirmMethod;if(!await e(s,this.formElement,this.submitter))return}if(this.state==e)return this.state=t,this.fetchRequest.perform()}stop(){const{stopping:e,stopped:t}=ie;if(this.state!=e&&this.state!=t)return this.state=e,this.fetchRequest.cancel(),!0}prepareRequest(e){if(!e.isSafe){const t=function(e){if(null!=e){const t=(document.cookie?document.cookie.split("; "):[]).find((t=>t.startsWith(e)));if(t){const e=t.split("=").slice(1).join("=");return e?decodeURIComponent(e):void 0}}}(y("csrf-param"))||y("csrf-token");t&&(e.headers["X-CSRF-Token"]=t)}this.requestAcceptsTurboStreamResponse(e)&&e.acceptResponseType(se.contentType)}requestStarted(e){this.state=ie.waiting,this.submitter&&I.forms.submitter.beforeSubmit(this.submitter),this.setSubmitsWith(),g(this.formElement),a("turbo:submit-start",{target:this.formElement,detail:{formSubmission:this}}),this.delegate.formSubmissionStarted(this)}requestPreventedHandlingResponse(e,t){re.clear(),this.result={success:t.succeeded,fetchResponse:t}}requestSucceededWithResponse(e,t){if(t.clientError||t.serverError)this.delegate.formSubmissionFailedWithResponse(this,t);else if(re.clear(),this.requestMustRedirect(e)&&function(e){return 200==e.statusCode&&!e.redirected}(t)){const e=new Error("Form responses must redirect to another location");this.delegate.formSubmissionErrored(this,e)}else this.state=ie.receiving,this.result={success:!0,fetchResponse:t},this.delegate.formSubmissionSucceededWithResponse(this,t)}requestFailedWithResponse(e,t){this.result={success:!1,fetchResponse:t},this.delegate.formSubmissionFailedWithResponse(this,t)}requestErrored(e,t){this.result={success:!1,error:t},this.delegate.formSubmissionErrored(this,t)}requestFinished(e){this.state=ie.stopped,this.submitter&&I.forms.submitter.afterSubmit(this.submitter),this.resetSubmitterText(),b(this.formElement),a("turbo:submit-end",{target:this.formElement,detail:{formSubmission:this,...this.result}}),this.delegate.formSubmissionFinished(this)}setSubmitsWith(){if(this.submitter&&this.submitsWith)if(this.submitter.matches("button"))this.originalSubmitText=this.submitter.innerHTML,this.submitter.innerHTML=this.submitsWith;else if(this.submitter.matches("input")){const e=this.submitter;this.originalSubmitText=e.value,e.value=this.submitsWith}}resetSubmitterText(){if(this.submitter&&this.originalSubmitText)if(this.submitter.matches("button"))this.submitter.innerHTML=this.originalSubmitText;else if(this.submitter.matches("input")){this.submitter.value=this.originalSubmitText}}requestMustRedirect(e){return!e.isSafe&&this.mustRedirect}requestAcceptsTurboStreamResponse(e){return!e.isSafe||function(e,...t){return t.some((t=>t&&t.hasAttribute(e)))}("data-turbo-stream",this.submitter,this.formElement)}get submitsWith(){return this.submitter?.getAttribute("data-turbo-submits-with")}}class oe{constructor(e){this.element=e}get activeElement(){return this.element.ownerDocument.activeElement}get children(){return[...this.element.children]}hasAnchor(e){return null!=this.getElementForAnchor(e)}getElementForAnchor(e){return e?this.element.querySelector(`[id='${e}'], a[name='${e}']`):null}get isConnected(){return this.element.isConnected}get firstAutofocusableElement(){return A(this.element)}get permanentElements(){return le(this.element)}getPermanentElementById(e){return ae(this.element,e)}getPermanentElementMapForSnapshot(e){const t={};for(const s of this.permanentElements){const{id:r}=s,i=e.getPermanentElementById(r);i&&(t[r]=[s,i])}return t}}function ae(e,t){return e.querySelector(`#${t}[data-turbo-permanent]`)}function le(e){return e.querySelectorAll("[id][data-turbo-permanent]")}class ce{started=!1;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||(this.eventTarget.addEventListener("submit",this.submitCaptured,!0),this.started=!0)}stop(){this.started&&(this.eventTarget.removeEventListener("submit",this.submitCaptured,!0),this.started=!1)}submitCaptured=()=>{this.eventTarget.removeEventListener("submit",this.submitBubbled,!1),this.eventTarget.addEventListener("submit",this.submitBubbled,!1)};submitBubbled=e=>{if(!e.defaultPrevented){const t=e.target instanceof HTMLFormElement?e.target:void 0,s=e.submitter||void 0;t&&function(e,t){const s=t?.getAttribute("formmethod")||e.getAttribute("method");return"dialog"!=s}(t,s)&&function(e,t){const s=t?.getAttribute("formtarget")||e.getAttribute("target");return P(s)}(t,s)&&this.delegate.willSubmitForm(t,s)&&(e.preventDefault(),e.stopImmediatePropagation(),this.delegate.formSubmitted(t,s))}}}class he{#o=e=>{};#a=e=>{};constructor(e,t){this.delegate=e,this.element=t}scrollToAnchor(e){const t=this.snapshot.getElementForAnchor(e);t?(this.scrollToElement(t),this.focusElement(t)):this.scrollToPosition({x:0,y:0})}scrollToAnchorFromLocation(e){this.scrollToAnchor(H(e))}scrollToElement(e){e.scrollIntoView()}focusElement(e){e instanceof HTMLElement&&(e.hasAttribute("tabindex")?e.focus():(e.setAttribute("tabindex","-1"),e.focus(),e.removeAttribute("tabindex")))}scrollToPosition({x:e,y:t}){this.scrollRoot.scrollTo(e,t)}scrollToTop(){this.scrollToPosition({x:0,y:0})}get scrollRoot(){return window}async render(e){const{isPreview:t,shouldRender:s,willRender:r,newSnapshot:i}=e,n=r;if(s)try{this.renderPromise=new Promise((e=>this.#o=e)),this.renderer=e,await this.prepareToRenderSnapshot(e);const s=new Promise((e=>this.#a=e)),r={resume:this.#a,render:this.renderer.renderElement,renderMethod:this.renderer.renderMethod};this.delegate.allowsImmediateRender(i,r)||await s,await this.renderSnapshot(e),this.delegate.viewRenderedSnapshot(i,t,this.renderer.renderMethod),this.delegate.preloadOnLoadLinksForView(this.element),this.finishRenderingSnapshot(e)}finally{delete this.renderer,this.#o(void 0),delete this.renderPromise}else n&&this.invalidate(e.reloadReason)}invalidate(e){this.delegate.viewInvalidated(e)}async prepareToRenderSnapshot(e){this.markAsPreview(e.isPreview),await e.prepareToRender()}markAsPreview(e){e?this.element.setAttribute("data-turbo-preview",""):this.element.removeAttribute("data-turbo-preview")}markVisitDirection(e){this.element.setAttribute("data-turbo-visit-direction",e)}unmarkVisitDirection(){this.element.removeAttribute("data-turbo-visit-direction")}async renderSnapshot(e){await e.render()}finishRenderingSnapshot(e){e.finishRendering()}}class de extends he{missing(){this.element.innerHTML='<strong class="turbo-frame-error">Content missing</strong>'}get snapshot(){return new oe(this.element)}}class ue{constructor(e,t){this.delegate=e,this.element=t}start(){this.element.addEventListener("click",this.clickBubbled),document.addEventListener("turbo:click",this.linkClicked),document.addEventListener("turbo:before-visit",this.willVisit)}stop(){this.element.removeEventListener("click",this.clickBubbled),document.removeEventListener("turbo:click",this.linkClicked),document.removeEventListener("turbo:before-visit",this.willVisit)}clickBubbled=e=>{this.clickEventIsSignificant(e)?this.clickEvent=e:delete this.clickEvent};linkClicked=e=>{this.clickEvent&&this.clickEventIsSignificant(e)&&this.delegate.shouldInterceptLinkClick(e.target,e.detail.url,e.detail.originalEvent)&&(this.clickEvent.preventDefault(),e.preventDefault(),this.delegate.linkClickIntercepted(e.target,e.detail.url,e.detail.originalEvent)),delete this.clickEvent};willVisit=e=>{delete this.clickEvent};clickEventIsSignificant(e){const t=e.composed?e.target?.parentElement:e.target,s=C(t)||t;return s instanceof Element&&s.closest("turbo-frame, html")==this.element}}class me{started=!1;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||(this.eventTarget.addEventListener("click",this.clickCaptured,!0),this.started=!0)}stop(){this.started&&(this.eventTarget.removeEventListener("click",this.clickCaptured,!0),this.started=!1)}clickCaptured=()=>{this.eventTarget.removeEventListener("click",this.clickBubbled,!1),this.eventTarget.addEventListener("click",this.clickBubbled,!1)};clickBubbled=e=>{if(e instanceof MouseEvent&&this.clickEventIsSignificant(e)){const t=C(e.composedPath&&e.composedPath()[0]||e.target);if(t&&P(t.target)){const s=k(t);this.delegate.willFollowLinkToLocation(t,s,e)&&(e.preventDefault(),this.delegate.followedLinkToLocation(t,s))}}};clickEventIsSignificant(e){return!(e.target&&e.target.isContentEditable||e.defaultPrevented||e.which>1||e.altKey||e.ctrlKey||e.metaKey||e.shiftKey)}}class pe{constructor(e,t){this.delegate=e,this.linkInterceptor=new me(this,t)}start(){this.linkInterceptor.start()}stop(){this.linkInterceptor.stop()}canPrefetchRequestToLocation(e,t){return!1}prefetchAndCacheRequestToLocation(e,t){}willFollowLinkToLocation(e,t,s){return this.delegate.willSubmitFormLinkToLocation(e,t,s)&&(e.hasAttribute("data-turbo-method")||e.hasAttribute("data-turbo-stream"))}followedLinkToLocation(e,t){const s=document.createElement("form");for(const[e,r]of t.searchParams)s.append(Object.assign(document.createElement("input"),{type:"hidden",name:e,value:r}));const r=Object.assign(t,{search:""});s.setAttribute("data-turbo","true"),s.setAttribute("action",r.href),s.setAttribute("hidden","");const i=e.getAttribute("data-turbo-method");i&&s.setAttribute("method",i);const n=e.getAttribute("data-turbo-frame");n&&s.setAttribute("data-turbo-frame",n);const o=w(e);o&&s.setAttribute("data-turbo-action",o);const a=e.getAttribute("data-turbo-confirm");a&&s.setAttribute("data-turbo-confirm",a);e.hasAttribute("data-turbo-stream")&&s.setAttribute("data-turbo-stream",""),this.delegate.submittedFormLinkToLocation(e,t,s),document.body.appendChild(s),s.addEventListener("turbo:submit-end",(()=>s.remove()),{once:!0}),requestAnimationFrame((()=>s.requestSubmit()))}}class fe{static async preservingPermanentElements(e,t,s){const r=new this(e,t);r.enter(),await s(),r.leave()}constructor(e,t){this.delegate=e,this.permanentElementMap=t}enter(){for(const e in this.permanentElementMap){const[t,s]=this.permanentElementMap[e];this.delegate.enteringBardo(t,s),this.replaceNewPermanentElementWithPlaceholder(s)}}leave(){for(const e in this.permanentElementMap){const[t]=this.permanentElementMap[e];this.replaceCurrentPermanentElementWithClone(t),this.replacePlaceholderWithPermanentElement(t),this.delegate.leavingBardo(t)}}replaceNewPermanentElementWithPlaceholder(e){const t=function(e){const t=document.createElement("meta");return t.setAttribute("name","turbo-permanent-placeholder"),t.setAttribute("content",e.id),t}(e);e.replaceWith(t)}replaceCurrentPermanentElementWithClone(e){const t=e.cloneNode(!0);e.replaceWith(t)}replacePlaceholderWithPermanentElement(e){const t=this.getPlaceholderById(e.id);t?.replaceWith(e)}getPlaceholderById(e){return this.placeholders.find((t=>t.content==e))}get placeholders(){return[...document.querySelectorAll("meta[name=turbo-permanent-placeholder][content]")]}}class ge{#l=null;static renderElement(e,t){}constructor(e,t,s,r=!0){this.currentSnapshot=e,this.newSnapshot=t,this.isPreview=s,this.willRender=r,this.renderElement=this.constructor.renderElement,this.promise=new Promise(((e,t)=>this.resolvingFunctions={resolve:e,reject:t}))}get shouldRender(){return!0}get shouldAutofocus(){return!0}get reloadReason(){}prepareToRender(){}render(){}finishRendering(){this.resolvingFunctions&&(this.resolvingFunctions.resolve(),delete this.resolvingFunctions)}async preservingPermanentElements(e){await fe.preservingPermanentElements(this,this.permanentElementMap,e)}focusFirstAutofocusableElement(){if(this.shouldAutofocus){const e=this.connectedSnapshot.firstAutofocusableElement;e&&e.focus()}}enteringBardo(e){this.#l||e.contains(this.currentSnapshot.activeElement)&&(this.#l=this.currentSnapshot.activeElement)}leavingBardo(e){e.contains(this.#l)&&this.#l instanceof HTMLElement&&(this.#l.focus(),this.#l=null)}get connectedSnapshot(){return this.newSnapshot.isConnected?this.newSnapshot:this.currentSnapshot}get currentElement(){return this.currentSnapshot.element}get newElement(){return this.newSnapshot.element}get permanentElementMap(){return this.currentSnapshot.getPermanentElementMapForSnapshot(this.newSnapshot)}get renderMethod(){return"replace"}}class be extends ge{static renderElement(e,t){const s=document.createRange();s.selectNodeContents(e),s.deleteContents();const r=t,i=r.ownerDocument?.createRange();i&&(i.selectNodeContents(r),e.appendChild(i.extractContents()))}constructor(e,t,s,r,i,n=!0){super(t,s,r,i,n),this.delegate=e}get shouldRender(){return!0}async render(){await c(),this.preservingPermanentElements((()=>{this.loadFrameElement()})),this.scrollFrameIntoView(),await c(),this.focusFirstAutofocusableElement(),await c(),this.activateScriptElements()}loadFrameElement(){this.delegate.willRenderFrame(this.currentElement,this.newElement),this.renderElement(this.currentElement,this.newElement)}scrollFrameIntoView(){if(this.currentElement.autoscroll||this.newElement.autoscroll){const s=this.currentElement.firstElementChild,r=(e=this.currentElement.getAttribute("data-autoscroll-block"),t="end","end"==e||"start"==e||"center"==e||"nearest"==e?e:t),i=function(e,t){return"auto"==e||"smooth"==e?e:t}(this.currentElement.getAttribute("data-autoscroll-behavior"),"auto");if(s)return s.scrollIntoView({block:r,behavior:i}),!0}var e,t;return!1}activateScriptElements(){for(const e of this.newScriptElements){const t=o(e);e.replaceWith(t)}}get newScriptElements(){return this.currentElement.querySelectorAll("script")}}var ve=function(){let e=new Set,t={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:c,afterNodeAdded:c,beforeNodeMorphed:c,afterNodeMorphed:c,beforeNodeRemoved:c,afterNodeRemoved:c,beforeAttributeUpdated:c},head:{style:"merge",shouldPreserve:function(e){return"true"===e.getAttribute("im-preserve")},shouldReAppend:function(e){return"true"===e.getAttribute("im-re-append")},shouldRemove:c,afterHeadMorphed:c}};function s(e,t,r){if(r.head.block){let i=e.querySelector("head"),n=t.querySelector("head");if(i&&n){let o=l(n,i,r);return void Promise.all(o).then((function(){s(e,t,Object.assign(r,{head:{block:!1,ignore:!0}}))}))}}if("innerHTML"===r.morphStyle)return n(t,e,r),e.children;if("outerHTML"===r.morphStyle||null==r.morphStyle){let s=function(e,t,s){let r;r=e.firstChild;let i=r,n=0;for(;r;){let e=f(r,t,s);e>n&&(i=r,n=e),r=r.nextSibling}return i}(t,e,r),n=s?.previousSibling,o=s?.nextSibling,a=i(e,s,r);return s?function(e,t,s){let r=[],i=[];for(;null!=e;)r.push(e),e=e.previousSibling;for(;r.length>0;){let e=r.pop();i.push(e),t.parentElement.insertBefore(e,t)}i.push(t);for(;null!=s;)r.push(s),i.push(s),s=s.nextSibling;for(;r.length>0;)t.parentElement.insertBefore(r.pop(),t.nextSibling);return i}(n,a,o):[]}throw"Do not understand how to morph style "+r.morphStyle}function r(e,t){return t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}function i(e,t,s){if(!s.ignoreActive||e!==document.activeElement)return null==t?!1===s.callbacks.beforeNodeRemoved(e)?e:(e.remove(),s.callbacks.afterNodeRemoved(e),null):d(e,t)?(!1===s.callbacks.beforeNodeMorphed(e,t)||(e instanceof HTMLHeadElement&&s.head.ignore||(e instanceof HTMLHeadElement&&"morph"!==s.head.style?l(t,e,s):(!function(e,t,s){let i=e.nodeType;if(1===i){const r=e.attributes,i=t.attributes;for(const e of r)o(e.name,t,"update",s)||t.getAttribute(e.name)!==e.value&&t.setAttribute(e.name,e.value);for(let r=i.length-1;0<=r;r--){const n=i[r];o(n.name,t,"remove",s)||(e.hasAttribute(n.name)||t.removeAttribute(n.name))}}8!==i&&3!==i||t.nodeValue!==e.nodeValue&&(t.nodeValue=e.nodeValue);r(t,s)||function(e,t,s){if(e instanceof HTMLInputElement&&t instanceof HTMLInputElement&&"file"!==e.type){let r=e.value,i=t.value;a(e,t,"checked",s),a(e,t,"disabled",s),e.hasAttribute("value")?r!==i&&(o("value",t,"update",s)||(t.setAttribute("value",r),t.value=r)):o("value",t,"remove",s)||(t.value="",t.removeAttribute("value"))}else if(e instanceof HTMLOptionElement)a(e,t,"selected",s);else if(e instanceof HTMLTextAreaElement&&t instanceof HTMLTextAreaElement){let r=e.value,i=t.value;if(o("value",t,"update",s))return;r!==i&&(t.value=r),t.firstChild&&t.firstChild.nodeValue!==r&&(t.firstChild.nodeValue=r)}}(e,t,s)}(t,e,s),r(e,s)||n(t,e,s))),s.callbacks.afterNodeMorphed(e,t)),e):!1===s.callbacks.beforeNodeRemoved(e)||!1===s.callbacks.beforeNodeAdded(t)?e:(e.parentElement.replaceChild(t,e),s.callbacks.afterNodeAdded(t),s.callbacks.afterNodeRemoved(e),t)}function n(e,t,s){let r,n=e.firstChild,o=t.firstChild;for(;n;){if(r=n,n=r.nextSibling,null==o){if(!1===s.callbacks.beforeNodeAdded(r))return;t.appendChild(r),s.callbacks.afterNodeAdded(r),S(s,r);continue}if(h(r,o,s)){i(o,r,s),o=o.nextSibling,S(s,r);continue}let a=m(e,t,r,o,s);if(a){o=u(o,a,s),i(a,r,s),S(s,r);continue}let l=p(e,t,r,o,s);if(l)o=u(o,l,s),i(l,r,s),S(s,r);else{if(!1===s.callbacks.beforeNodeAdded(r))return;t.insertBefore(r,o),s.callbacks.afterNodeAdded(r),S(s,r)}}for(;null!==o;){let e=o;o=o.nextSibling,g(e,s)}}function o(e,t,s,r){return!("value"!==e||!r.ignoreActiveValue||t!==document.activeElement)||!1===r.callbacks.beforeAttributeUpdated(e,t,s)}function a(e,t,s,r){if(e[s]!==t[s]){let i=o(s,t,"update",r);i||(t[s]=e[s]),e[s]?i||t.setAttribute(s,e[s]):o(s,t,"remove",r)||t.removeAttribute(s)}}function l(e,t,s){let r=[],i=[],n=[],o=[],a=s.head.style,l=new Map;for(const t of e.children)l.set(t.outerHTML,t);for(const e of t.children){let t=l.has(e.outerHTML),r=s.head.shouldReAppend(e),c=s.head.shouldPreserve(e);t||c?r?i.push(e):(l.delete(e.outerHTML),n.push(e)):"append"===a?r&&(i.push(e),o.push(e)):!1!==s.head.shouldRemove(e)&&i.push(e)}o.push(...l.values());let c=[];for(const e of o){let i=document.createRange().createContextualFragment(e.outerHTML).firstChild;if(!1!==s.callbacks.beforeNodeAdded(i)){if(i.href||i.src){let e=null,t=new Promise((function(t){e=t}));i.addEventListener("load",(function(){e()})),c.push(t)}t.appendChild(i),s.callbacks.afterNodeAdded(i),r.push(i)}}for(const e of i)!1!==s.callbacks.beforeNodeRemoved(e)&&(t.removeChild(e),s.callbacks.afterNodeRemoved(e));return s.head.afterHeadMorphed(t,{added:r,kept:n,removed:i}),c}function c(){}function h(e,t,s){return null!=e&&null!=t&&(e.nodeType===t.nodeType&&e.tagName===t.tagName&&(""!==e.id&&e.id===t.id||w(s,e,t)>0))}function d(e,t){return null!=e&&null!=t&&(e.nodeType===t.nodeType&&e.tagName===t.tagName)}function u(e,t,s){for(;e!==t;){let t=e;e=e.nextSibling,g(t,s)}return S(s,t),t.nextSibling}function m(e,t,s,r,i){let n=w(i,s,t);if(n>0){let t=r,o=0;for(;null!=t;){if(h(s,t,i))return t;if(o+=w(i,t,e),o>n)return null;t=t.nextSibling}}return null}function p(e,t,s,r,i){let n=r,o=s.nextSibling,a=0;for(;null!=n;){if(w(i,n,e)>0)return null;if(d(s,n))return n;if(d(o,n)&&(a++,o=o.nextSibling,a>=2))return null;n=n.nextSibling}return n}function f(e,t,s){return d(e,t)?.5+w(s,e,t):0}function g(e,t){S(t,e),!1!==t.callbacks.beforeNodeRemoved(e)&&(e.remove(),t.callbacks.afterNodeRemoved(e))}function b(e,t){return!e.deadIds.has(t)}function v(t,s,r){return(t.idMap.get(r)||e).has(s)}function S(t,s){let r=t.idMap.get(s)||e;for(const e of r)t.deadIds.add(e)}function w(t,s,r){let i=t.idMap.get(s)||e,n=0;for(const e of i)b(t,e)&&v(t,e,r)&&++n;return n}function E(e,t){let s=e.parentElement,r=e.querySelectorAll("[id]");for(const e of r){let r=e;for(;r!==s&&null!=r;){let s=t.get(r);null==s&&(s=new Set,t.set(r,s)),s.add(e.id),r=r.parentElement}}}function y(e,t){let s=new Map;return E(e,s),E(t,s),s}return{morph:function(e,r,i={}){e instanceof Document&&(e=e.documentElement),"string"==typeof r&&(r=function(e){let t=new DOMParser,s=e.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(s.match(/<\/html>/)||s.match(/<\/head>/)||s.match(/<\/body>/)){let r=t.parseFromString(e,"text/html");if(s.match(/<\/html>/))return r.generatedByIdiomorph=!0,r;{let e=r.firstChild;return e?(e.generatedByIdiomorph=!0,e):null}}{let s=t.parseFromString("<body><template>"+e+"</template></body>","text/html").body.querySelector("template").content;return s.generatedByIdiomorph=!0,s}}(r));let n=function(e){if(null==e){return document.createElement("div")}if(e.generatedByIdiomorph)return e;if(e instanceof Node){const t=document.createElement("div");return t.append(e),t}{const t=document.createElement("div");for(const s of[...e])t.append(s);return t}}(r),o=function(e,s,r){return r=function(e){let s={};return Object.assign(s,t),Object.assign(s,e),s.callbacks={},Object.assign(s.callbacks,t.callbacks),Object.assign(s.callbacks,e.callbacks),s.head={},Object.assign(s.head,t.head),Object.assign(s.head,e.head),s}(r),{target:e,newContent:s,config:r,morphStyle:r.morphStyle,ignoreActive:r.ignoreActive,ignoreActiveValue:r.ignoreActiveValue,idMap:y(e,s),deadIds:new Set,callbacks:r.callbacks,head:r.head}}(e,n,i);return s(e,n,o)},defaults:t}}();function Se(e,t,{callbacks:s,...r}={}){ve.morph(e,t,{...r,callbacks:new Ee(s)})}function we(e,t){Se(e,t.children,{morphStyle:"innerHTML"})}class Ee{#c;constructor({beforeNodeMorphed:e}={}){this.#c=e||(()=>!0)}beforeNodeAdded=e=>!(e.id&&e.hasAttribute("data-turbo-permanent")&&document.getElementById(e.id));beforeNodeMorphed=(e,t)=>{if(e instanceof Element){if(!e.hasAttribute("data-turbo-permanent")&&this.#c(e,t)){return!a("turbo:before-morph-element",{cancelable:!0,target:e,detail:{currentElement:e,newElement:t}}).defaultPrevented}return!1}};beforeAttributeUpdated=(e,t,s)=>!a("turbo:before-morph-attribute",{cancelable:!0,target:t,detail:{attributeName:e,mutationType:s}}).defaultPrevented;beforeNodeRemoved=e=>this.beforeNodeMorphed(e);afterNodeMorphed=(e,t)=>{e instanceof Element&&a("turbo:morph-element",{target:e,detail:{currentElement:e,newElement:t}})}}class ye extends be{static renderElement(e,t){a("turbo:before-frame-morph",{target:e,detail:{currentElement:e,newElement:t}}),we(e,t)}async preservingPermanentElements(e){return await e()}}class Re{static animationDuration=300;static get defaultCSS(){return m`
12
+ .turbo-progress-bar {
13
+ position: fixed;
14
+ display: block;
15
+ top: 0;
16
+ left: 0;
17
+ height: 3px;
18
+ background: #0076ff;
19
+ z-index: 2147483647;
20
+ transition:
21
+ width ${Re.animationDuration}ms ease-out,
22
+ opacity ${Re.animationDuration/2}ms ${Re.animationDuration/2}ms ease-in;
23
+ transform: translate3d(0, 0, 0);
24
+ }
25
+ `}hiding=!1;value=0;visible=!1;constructor(){this.stylesheetElement=this.createStylesheetElement(),this.progressElement=this.createProgressElement(),this.installStylesheetElement(),this.setValue(0)}show(){this.visible||(this.visible=!0,this.installProgressElement(),this.startTrickling())}hide(){this.visible&&!this.hiding&&(this.hiding=!0,this.fadeProgressElement((()=>{this.uninstallProgressElement(),this.stopTrickling(),this.visible=!1,this.hiding=!1})))}setValue(e){this.value=e,this.refresh()}installStylesheetElement(){document.head.insertBefore(this.stylesheetElement,document.head.firstChild)}installProgressElement(){this.progressElement.style.width="0",this.progressElement.style.opacity="1",document.documentElement.insertBefore(this.progressElement,document.body),this.refresh()}fadeProgressElement(e){this.progressElement.style.opacity="0",setTimeout(e,1.5*Re.animationDuration)}uninstallProgressElement(){this.progressElement.parentNode&&document.documentElement.removeChild(this.progressElement)}startTrickling(){this.trickleInterval||(this.trickleInterval=window.setInterval(this.trickle,Re.animationDuration))}stopTrickling(){window.clearInterval(this.trickleInterval),delete this.trickleInterval}trickle=()=>{this.setValue(this.value+Math.random()/100)};refresh(){requestAnimationFrame((()=>{this.progressElement.style.width=10+90*this.value+"%"}))}createStylesheetElement(){const e=document.createElement("style");e.type="text/css",e.textContent=Re.defaultCSS;const t=R();return t&&(e.nonce=t),e}createProgressElement(){const e=document.createElement("div");return e.className="turbo-progress-bar",e}}class Le extends oe{detailsByOuterHTML=this.children.filter((e=>!function(e){const t=e.localName;return"noscript"==t}(e))).map((e=>function(e){e.hasAttribute("nonce")&&e.setAttribute("nonce","");return e}(e))).reduce(((e,t)=>{const{outerHTML:s}=t,r=s in e?e[s]:{type:Te(t),tracked:Ae(t),elements:[]};return{...e,[s]:{...r,elements:[...r.elements,t]}}}),{});get trackedElementSignature(){return Object.keys(this.detailsByOuterHTML).filter((e=>this.detailsByOuterHTML[e].tracked)).join("")}getScriptElementsNotInSnapshot(e){return this.getElementsMatchingTypeNotInSnapshot("script",e)}getStylesheetElementsNotInSnapshot(e){return this.getElementsMatchingTypeNotInSnapshot("stylesheet",e)}getElementsMatchingTypeNotInSnapshot(e,t){return Object.keys(this.detailsByOuterHTML).filter((e=>!(e in t.detailsByOuterHTML))).map((e=>this.detailsByOuterHTML[e])).filter((({type:t})=>t==e)).map((({elements:[e]})=>e))}get provisionalElements(){return Object.keys(this.detailsByOuterHTML).reduce(((e,t)=>{const{type:s,tracked:r,elements:i}=this.detailsByOuterHTML[t];return null!=s||r?i.length>1?[...e,...i.slice(1)]:e:[...e,...i]}),[])}getMetaValue(e){const t=this.findMetaElementByName(e);return t?t.getAttribute("content"):null}findMetaElementByName(e){return Object.keys(this.detailsByOuterHTML).reduce(((t,s)=>{const{elements:[r]}=this.detailsByOuterHTML[s];return function(e,t){const s=e.localName;return"meta"==s&&e.getAttribute("name")==t}(r,e)?r:t}),0)}}function Te(e){return function(e){const t=e.localName;return"script"==t}(e)?"script":function(e){const t=e.localName;return"style"==t||"link"==t&&"stylesheet"==e.getAttribute("rel")}(e)?"stylesheet":void 0}function Ae(e){return"reload"==e.getAttribute("data-turbo-track")}class Pe extends oe{static fromHTMLString(e=""){return this.fromDocument(u(e))}static fromElement(e){return this.fromDocument(e.ownerDocument)}static fromDocument({documentElement:e,body:t,head:s}){return new this(e,t,new Le(s))}constructor(e,t,s){super(t),this.documentElement=e,this.headSnapshot=s}clone(){const e=this.element.cloneNode(!0),t=this.element.querySelectorAll("select"),s=e.querySelectorAll("select");for(const[e,r]of t.entries()){const t=s[e];for(const e of t.selectedOptions)e.selected=!1;for(const e of r.selectedOptions)t.options[e.index].selected=!0}for(const t of e.querySelectorAll('input[type="password"]'))t.value="";return new Pe(this.documentElement,e,this.headSnapshot)}get lang(){return this.documentElement.getAttribute("lang")}get headElement(){return this.headSnapshot.element}get rootLocation(){return q(this.getSetting("root")??"/")}get cacheControlValue(){return this.getSetting("cache-control")}get isPreviewable(){return"no-preview"!=this.cacheControlValue}get isCacheable(){return"no-cache"!=this.cacheControlValue}get isVisitable(){return"reload"!=this.getSetting("visit-control")}get prefersViewTransitions(){return"same-origin"===this.headSnapshot.getMetaValue("view-transition")}get shouldMorphPage(){return"morph"===this.getSetting("refresh-method")}get shouldPreserveScrollPosition(){return"preserve"===this.getSetting("refresh-scroll")}getSetting(e){return this.headSnapshot.getMetaValue(`turbo-${e}`)}}class Ce{#h=!1;#d=Promise.resolve();renderChange(e,t){return e&&this.viewTransitionsAvailable&&!this.#h?(this.#h=!0,this.#d=this.#d.then((async()=>{await document.startViewTransition(t).finished}))):this.#d=this.#d.then(t),this.#d}get viewTransitionsAvailable(){return document.startViewTransition}}const ke={action:"advance",historyChanged:!1,visitCachedSnapshot:()=>{},willRender:!0,updateHistory:!0,shouldCacheSnapshot:!0,acceptsStreamResponse:!1},Me="visitStart",Fe="requestStart",Ie="requestEnd",qe="visitEnd",He="initialized",Be="started",Oe="canceled",Ne="failed",xe="completed",Ve=0,De=-1,We=-2,Ue={advance:"forward",restore:"back",replace:"none"};class je{identifier=p();timingMetrics={};followedRedirect=!1;historyChanged=!1;scrolled=!1;shouldCacheSnapshot=!0;acceptsStreamResponse=!1;snapshotCached=!1;state=He;viewTransitioner=new Ce;constructor(e,t,s,r={}){this.delegate=e,this.location=t,this.restorationIdentifier=s||p();const{action:i,historyChanged:n,referrer:o,snapshot:a,snapshotHTML:l,response:c,visitCachedSnapshot:h,willRender:d,updateHistory:u,shouldCacheSnapshot:m,acceptsStreamResponse:f,direction:g}={...ke,...r};this.action=i,this.historyChanged=n,this.referrer=o,this.snapshot=a,this.snapshotHTML=l,this.response=c,this.isSamePage=this.delegate.locationWithActionIsSamePage(this.location,this.action),this.isPageRefresh=this.view.isPageRefresh(this),this.visitCachedSnapshot=h,this.willRender=d,this.updateHistory=u,this.scrolled=!d,this.shouldCacheSnapshot=m,this.acceptsStreamResponse=f,this.direction=g||Ue[i]}get adapter(){return this.delegate.adapter}get view(){return this.delegate.view}get history(){return this.delegate.history}get restorationData(){return this.history.getRestorationDataForIdentifier(this.restorationIdentifier)}get silent(){return this.isSamePage}start(){this.state==He&&(this.recordTimingMetric(Me),this.state=Be,this.adapter.visitStarted(this),this.delegate.visitStarted(this))}cancel(){this.state==Be&&(this.request&&this.request.cancel(),this.cancelRender(),this.state=Oe)}complete(){this.state==Be&&(this.recordTimingMetric(qe),this.adapter.visitCompleted(this),this.state=xe,this.followRedirect(),this.followedRedirect||this.delegate.visitCompleted(this))}fail(){this.state==Be&&(this.state=Ne,this.adapter.visitFailed(this),this.delegate.visitCompleted(this))}changeHistory(){if(!this.historyChanged&&this.updateHistory){const e=S(this.location.href===this.referrer?.href?"replace":this.action);this.history.update(e,this.location,this.restorationIdentifier),this.historyChanged=!0}}issueRequest(){this.hasPreloadedResponse()?this.simulateRequest():this.shouldIssueRequest()&&!this.request&&(this.request=new Y(this,X.get,this.location),this.request.perform())}simulateRequest(){this.response&&(this.startRequest(),this.recordResponse(),this.finishRequest())}startRequest(){this.recordTimingMetric(Fe),this.adapter.visitRequestStarted(this)}recordResponse(e=this.response){if(this.response=e,e){const{statusCode:t}=e;$e(t)?this.adapter.visitRequestCompleted(this):this.adapter.visitRequestFailedWithStatusCode(this,t)}}finishRequest(){this.recordTimingMetric(Ie),this.adapter.visitRequestFinished(this)}loadResponse(){if(this.response){const{statusCode:e,responseHTML:t}=this.response;this.render((async()=>{if(this.shouldCacheSnapshot&&this.cacheSnapshot(),this.view.renderPromise&&await this.view.renderPromise,$e(e)&&null!=t){const e=Pe.fromHTMLString(t);await this.renderPageSnapshot(e,!1),this.adapter.visitRendered(this),this.complete()}else await this.view.renderError(Pe.fromHTMLString(t),this),this.adapter.visitRendered(this),this.fail()}))}}getCachedSnapshot(){const e=this.view.getCachedSnapshotForLocation(this.location)||this.getPreloadedSnapshot();if(e&&(!H(this.location)||e.hasAnchor(H(this.location)))&&("restore"==this.action||e.isPreviewable))return e}getPreloadedSnapshot(){if(this.snapshotHTML)return Pe.fromHTMLString(this.snapshotHTML)}hasCachedSnapshot(){return null!=this.getCachedSnapshot()}loadCachedSnapshot(){const e=this.getCachedSnapshot();if(e){const t=this.shouldIssueRequest();this.render((async()=>{this.cacheSnapshot(),this.isSamePage||this.isPageRefresh?this.adapter.visitRendered(this):(this.view.renderPromise&&await this.view.renderPromise,await this.renderPageSnapshot(e,t),this.adapter.visitRendered(this),t||this.complete())}))}}followRedirect(){this.redirectedToLocation&&!this.followedRedirect&&this.response?.redirected&&(this.adapter.visitProposedToLocation(this.redirectedToLocation,{action:"replace",response:this.response,shouldCacheSnapshot:!1,willRender:!1}),this.followedRedirect=!0)}goToSamePageAnchor(){this.isSamePage&&this.render((async()=>{this.cacheSnapshot(),this.performScroll(),this.changeHistory(),this.adapter.visitRendered(this)}))}prepareRequest(e){this.acceptsStreamResponse&&e.acceptResponseType(se.contentType)}requestStarted(){this.startRequest()}requestPreventedHandlingResponse(e,t){}async requestSucceededWithResponse(e,t){const s=await t.responseHTML,{redirected:r,statusCode:i}=t;null==s?this.recordResponse({statusCode:We,redirected:r}):(this.redirectedToLocation=t.redirected?t.location:void 0,this.recordResponse({statusCode:i,responseHTML:s,redirected:r}))}async requestFailedWithResponse(e,t){const s=await t.responseHTML,{redirected:r,statusCode:i}=t;null==s?this.recordResponse({statusCode:We,redirected:r}):this.recordResponse({statusCode:i,responseHTML:s,redirected:r})}requestErrored(e,t){this.recordResponse({statusCode:Ve,redirected:!1})}requestFinished(){this.finishRequest()}performScroll(){this.scrolled||this.view.forceReloaded||this.view.shouldPreserveScrollPosition(this)||("restore"==this.action?this.scrollToRestoredPosition()||this.scrollToAnchor()||this.view.scrollToTop():this.scrollToAnchor()||this.view.scrollToTop(),this.isSamePage&&this.delegate.visitScrolledToSamePageLocation(this.view.lastRenderedLocation,this.location),this.scrolled=!0)}scrollToRestoredPosition(){const{scrollPosition:e}=this.restorationData;if(e)return this.view.scrollToPosition(e),!0}scrollToAnchor(){const e=H(this.location);if(null!=e)return this.view.scrollToAnchor(e),!0}recordTimingMetric(e){this.timingMetrics[e]=(new Date).getTime()}getTimingMetrics(){return{...this.timingMetrics}}getHistoryMethodForAction(e){switch(e){case"replace":return history.replaceState;case"advance":case"restore":return history.pushState}}hasPreloadedResponse(){return"object"==typeof this.response}shouldIssueRequest(){return!this.isSamePage&&("restore"==this.action?!this.hasCachedSnapshot():this.willRender)}cacheSnapshot(){this.snapshotCached||(this.view.cacheSnapshot(this.snapshot).then((e=>e&&this.visitCachedSnapshot(e))),this.snapshotCached=!0)}async render(e){this.cancelRender(),await new Promise((e=>{this.frame="hidden"===document.visibilityState?setTimeout((()=>e()),0):requestAnimationFrame((()=>e()))})),await e(),delete this.frame}async renderPageSnapshot(e,t){await this.viewTransitioner.renderChange(this.view.shouldTransitionTo(e),(async()=>{await this.view.renderPage(e,t,this.willRender,this),this.performScroll()}))}cancelRender(){this.frame&&(cancelAnimationFrame(this.frame),delete this.frame)}}function $e(e){return e>=200&&e<300}class ze{progressBar=new Re;constructor(e){this.session=e}visitProposedToLocation(e,t){x(e,this.navigator.rootLocation)?this.navigator.startVisit(e,t?.restorationIdentifier||p(),t):window.location.href=e.toString()}visitStarted(e){this.location=e.location,e.loadCachedSnapshot(),e.issueRequest(),e.goToSamePageAnchor()}visitRequestStarted(e){this.progressBar.setValue(0),e.hasCachedSnapshot()||"restore"!=e.action?this.showVisitProgressBarAfterDelay():this.showProgressBar()}visitRequestCompleted(e){e.loadResponse()}visitRequestFailedWithStatusCode(e,t){switch(t){case Ve:case De:case We:return this.reload({reason:"request_failed",context:{statusCode:t}});default:return e.loadResponse()}}visitRequestFinished(e){}visitCompleted(e){this.progressBar.setValue(1),this.hideVisitProgressBar()}pageInvalidated(e){this.reload(e)}visitFailed(e){this.progressBar.setValue(1),this.hideVisitProgressBar()}visitRendered(e){}formSubmissionStarted(e){this.progressBar.setValue(0),this.showFormProgressBarAfterDelay()}formSubmissionFinished(e){this.progressBar.setValue(1),this.hideFormProgressBar()}showVisitProgressBarAfterDelay(){this.visitProgressBarTimeout=window.setTimeout(this.showProgressBar,this.session.progressBarDelay)}hideVisitProgressBar(){this.progressBar.hide(),null!=this.visitProgressBarTimeout&&(window.clearTimeout(this.visitProgressBarTimeout),delete this.visitProgressBarTimeout)}showFormProgressBarAfterDelay(){null==this.formProgressBarTimeout&&(this.formProgressBarTimeout=window.setTimeout(this.showProgressBar,this.session.progressBarDelay))}hideFormProgressBar(){this.progressBar.hide(),null!=this.formProgressBarTimeout&&(window.clearTimeout(this.formProgressBarTimeout),delete this.formProgressBarTimeout)}showProgressBar=()=>{this.progressBar.show()};reload(e){a("turbo:reload",{detail:e}),window.location.href=this.location?.toString()||window.location.href}get navigator(){return this.session.navigator}}class _e{selector="[data-turbo-temporary]";deprecatedSelector="[data-turbo-cache=false]";started=!1;start(){this.started||(this.started=!0,addEventListener("turbo:before-cache",this.removeTemporaryElements,!1))}stop(){this.started&&(this.started=!1,removeEventListener("turbo:before-cache",this.removeTemporaryElements,!1))}removeTemporaryElements=e=>{for(const e of this.temporaryElements)e.remove()};get temporaryElements(){return[...document.querySelectorAll(this.selector),...this.temporaryElementsWithDeprecation]}get temporaryElementsWithDeprecation(){const e=document.querySelectorAll(this.deprecatedSelector);return e.length&&console.warn(`The ${this.deprecatedSelector} selector is deprecated and will be removed in a future version. Use ${this.selector} instead.`),[...e]}}class Xe{constructor(e,t){this.session=e,this.element=t,this.linkInterceptor=new ue(this,t),this.formSubmitObserver=new ce(this,t)}start(){this.linkInterceptor.start(),this.formSubmitObserver.start()}stop(){this.linkInterceptor.stop(),this.formSubmitObserver.stop()}shouldInterceptLinkClick(e,t,s){return this.#u(e)}linkClickIntercepted(e,t,s){const r=this.#m(e);r&&r.delegate.linkClickIntercepted(e,t,s)}willSubmitForm(e,t){return null==e.closest("turbo-frame")&&this.#p(e,t)&&this.#u(e,t)}formSubmitted(e,t){const s=this.#m(e,t);s&&s.delegate.formSubmitted(e,t)}#p(e,t){const s=B(e,t),r=this.element.ownerDocument.querySelector('meta[name="turbo-root"]'),i=q(r?.content??"/");return this.#u(e,t)&&x(s,i)}#u(e,t){if(e instanceof HTMLFormElement?this.session.submissionIsNavigatable(e,t):this.session.elementIsNavigatable(e)){const s=this.#m(e,t);return!!s&&s!=e.closest("turbo-frame")}return!1}#m(e,t){const s=t?.getAttribute("data-turbo-frame")||e.getAttribute("data-turbo-frame");if(s&&"_top"!=s){const e=this.element.querySelector(`#${s}:not([disabled])`);if(e instanceof i)return e}}}class Ke{location;restorationIdentifier=p();restorationData={};started=!1;pageLoaded=!1;currentIndex=0;constructor(e){this.delegate=e}start(){this.started||(addEventListener("popstate",this.onPopState,!1),addEventListener("load",this.onPageLoad,!1),this.currentIndex=history.state?.turbo?.restorationIndex||0,this.started=!0,this.replace(new URL(window.location.href)))}stop(){this.started&&(removeEventListener("popstate",this.onPopState,!1),removeEventListener("load",this.onPageLoad,!1),this.started=!1)}push(e,t){this.update(history.pushState,e,t)}replace(e,t){this.update(history.replaceState,e,t)}update(e,t,s=p()){e===history.pushState&&++this.currentIndex;const r={turbo:{restorationIdentifier:s,restorationIndex:this.currentIndex}};e.call(history,r,"",t.href),this.location=t,this.restorationIdentifier=s}getRestorationDataForIdentifier(e){return this.restorationData[e]||{}}updateRestorationData(e){const{restorationIdentifier:t}=this,s=this.restorationData[t];this.restorationData[t]={...s,...e}}assumeControlOfScrollRestoration(){this.previousScrollRestoration||(this.previousScrollRestoration=history.scrollRestoration??"auto",history.scrollRestoration="manual")}relinquishControlOfScrollRestoration(){this.previousScrollRestoration&&(history.scrollRestoration=this.previousScrollRestoration,delete this.previousScrollRestoration)}onPopState=e=>{if(this.shouldHandlePopState()){const{turbo:t}=e.state||{};if(t){this.location=new URL(window.location.href);const{restorationIdentifier:e,restorationIndex:s}=t;this.restorationIdentifier=e;const r=s>this.currentIndex?"forward":"back";this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location,e,r),this.currentIndex=s}}};onPageLoad=async e=>{await Promise.resolve(),this.pageLoaded=!0};shouldHandlePopState(){return this.pageIsLoaded()}pageIsLoaded(){return this.pageLoaded||"complete"==document.readyState}}class Qe{started=!1;#f=null;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||("loading"===this.eventTarget.readyState?this.eventTarget.addEventListener("DOMContentLoaded",this.#g,{once:!0}):this.#g())}stop(){this.started&&(this.eventTarget.removeEventListener("mouseenter",this.#b,{capture:!0,passive:!0}),this.eventTarget.removeEventListener("mouseleave",this.#v,{capture:!0,passive:!0}),this.eventTarget.removeEventListener("turbo:before-fetch-request",this.#S,!0),this.started=!1)}#g=()=>{this.eventTarget.addEventListener("mouseenter",this.#b,{capture:!0,passive:!0}),this.eventTarget.addEventListener("mouseleave",this.#v,{capture:!0,passive:!0}),this.eventTarget.addEventListener("turbo:before-fetch-request",this.#S,!0),this.started=!0};#b=e=>{if("false"===y("turbo-prefetch"))return;const t=e.target;if(t.matches&&t.matches("a[href]:not([target^=_]):not([download])")&&this.#w(t)){const e=t,s=k(e);if(this.delegate.canPrefetchRequestToLocation(e,s)){this.#f=e;const r=new Y(this,X.get,s,new URLSearchParams,t);re.setLater(s.toString(),r,this.#E)}}};#v=e=>{e.target===this.#f&&this.#y()};#y=()=>{re.clear(),this.#f=null};#S=e=>{if("FORM"!==e.target.tagName&&"GET"===e.detail.fetchOptions.method){const t=re.get(e.detail.url.toString());t&&(e.detail.fetchRequest=t),re.clear()}};prepareRequest(e){const t=e.target;e.headers["X-Sec-Purpose"]="prefetch";const s=t.closest("turbo-frame"),r=t.getAttribute("data-turbo-frame")||s?.getAttribute("target")||s?.id;r&&"_top"!==r&&(e.headers["Turbo-Frame"]=r)}requestSucceededWithResponse(){}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}get#E(){return Number(y("turbo-prefetch-cache-time"))||1e4}#w(e){return!!e.getAttribute("href")&&(!Ye(e)&&(!Ge(e)&&(!Je(e)&&(!Ze(e)&&!tt(e)))))}}const Ye=e=>e.origin!==document.location.origin||!["http:","https:"].includes(e.protocol)||e.hasAttribute("target"),Ge=e=>e.pathname+e.search===document.location.pathname+document.location.search||e.href.startsWith("#"),Je=e=>{if("false"===e.getAttribute("data-turbo-prefetch"))return!0;if("false"===e.getAttribute("data-turbo"))return!0;const t=L(e,"[data-turbo-prefetch]");return!(!t||"false"!==t.getAttribute("data-turbo-prefetch"))},Ze=e=>{const t=e.getAttribute("data-turbo-method");return!(!t||"get"===t.toLowerCase())||(!!et(e)||(!!e.hasAttribute("data-turbo-confirm")||!!e.hasAttribute("data-turbo-stream")))},et=e=>e.hasAttribute("data-remote")||e.hasAttribute("data-behavior")||e.hasAttribute("data-confirm")||e.hasAttribute("data-method"),tt=e=>a("turbo:before-prefetch",{target:e,cancelable:!0}).defaultPrevented;class st{constructor(e){this.delegate=e}proposeVisit(e,t={}){this.delegate.allowsVisitingLocationWithAction(e,t.action)&&this.delegate.visitProposedToLocation(e,t)}startVisit(e,t,s={}){this.stop(),this.currentVisit=new je(this,q(e),t,{referrer:this.location,...s}),this.currentVisit.start()}submitForm(e,t){this.stop(),this.formSubmission=new ne(this,e,t,!0),this.formSubmission.start()}stop(){this.formSubmission&&(this.formSubmission.stop(),delete this.formSubmission),this.currentVisit&&(this.currentVisit.cancel(),delete this.currentVisit)}get adapter(){return this.delegate.adapter}get view(){return this.delegate.view}get rootLocation(){return this.view.snapshot.rootLocation}get history(){return this.delegate.history}formSubmissionStarted(e){"function"==typeof this.adapter.formSubmissionStarted&&this.adapter.formSubmissionStarted(e)}async formSubmissionSucceededWithResponse(e,t){if(e==this.formSubmission){const s=await t.responseHTML;if(s){const r=e.isSafe;r||this.view.clearSnapshotCache();const{statusCode:i,redirected:n}=t,o={action:this.#R(e,t),shouldCacheSnapshot:r,response:{statusCode:i,responseHTML:s,redirected:n}};this.proposeVisit(t.location,o)}}}async formSubmissionFailedWithResponse(e,t){const s=await t.responseHTML;if(s){const e=Pe.fromHTMLString(s);t.serverError?await this.view.renderError(e,this.currentVisit):await this.view.renderPage(e,!1,!0,this.currentVisit),e.shouldPreserveScrollPosition||this.view.scrollToTop(),this.view.clearSnapshotCache()}}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished(e){"function"==typeof this.adapter.formSubmissionFinished&&this.adapter.formSubmissionFinished(e)}visitStarted(e){this.delegate.visitStarted(e)}visitCompleted(e){this.delegate.visitCompleted(e),delete this.currentVisit}locationWithActionIsSamePage(e,t){const s=H(e),r=H(this.view.lastRenderedLocation),i="restore"===t&&void 0===s;return"replace"!==t&&V(e)===V(this.view.lastRenderedLocation)&&(i||null!=s&&s!==r)}visitScrolledToSamePageLocation(e,t){this.delegate.visitScrolledToSamePageLocation(e,t)}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}#R(e,t){const{submitter:s,formElement:r}=e;return w(s,r)||this.#L(t)}#L(e){return e.redirected&&e.location.href===this.location?.href?"replace":"advance"}}const rt=0,it=1,nt=2,ot=3;class at{stage=rt;started=!1;constructor(e){this.delegate=e}start(){this.started||(this.stage==rt&&(this.stage=it),document.addEventListener("readystatechange",this.interpretReadyState,!1),addEventListener("pagehide",this.pageWillUnload,!1),this.started=!0)}stop(){this.started&&(document.removeEventListener("readystatechange",this.interpretReadyState,!1),removeEventListener("pagehide",this.pageWillUnload,!1),this.started=!1)}interpretReadyState=()=>{const{readyState:e}=this;"interactive"==e?this.pageIsInteractive():"complete"==e&&this.pageIsComplete()};pageIsInteractive(){this.stage==it&&(this.stage=nt,this.delegate.pageBecameInteractive())}pageIsComplete(){this.pageIsInteractive(),this.stage==nt&&(this.stage=ot,this.delegate.pageLoaded())}pageWillUnload=()=>{this.delegate.pageWillUnload()};get readyState(){return document.readyState}}class lt{started=!1;constructor(e){this.delegate=e}start(){this.started||(addEventListener("scroll",this.onScroll,!1),this.onScroll(),this.started=!0)}stop(){this.started&&(removeEventListener("scroll",this.onScroll,!1),this.started=!1)}onScroll=()=>{this.updatePosition({x:window.pageXOffset,y:window.pageYOffset})};updatePosition(e){this.delegate.scrollPositionChanged(e)}}class ct{render({fragment:e}){fe.preservingPermanentElements(this,function(e){const t=le(document.documentElement),s={};for(const r of t){const{id:t}=r;for(const i of e.querySelectorAll("turbo-stream")){const e=ae(i.templateElement.content,t);e&&(s[t]=[r,e])}}return s}(e),(()=>{!async function(e,t){const s=`turbo-stream-autofocus-${p()}`,r=e.querySelectorAll("turbo-stream"),i=function(e){for(const t of e){const e=A(t.templateElement.content);if(e)return e}return null}(r);let n=null;i&&(n=i.id?i.id:s,i.id=n);t(),await c();if((null==document.activeElement||document.activeElement==document.body)&&n){const e=document.getElementById(n);T(e)&&e.focus(),e&&e.id==s&&e.removeAttribute("id")}}(e,(()=>{!async function(e){const[t,s]=await async function(e,t){const s=t();return e(),await h(),[s,t()]}(e,(()=>document.activeElement)),r=t&&t.id;if(r){const e=document.getElementById(r);T(e)&&e!=s&&e.focus()}}((()=>{document.documentElement.appendChild(e)}))}))}))}enteringBardo(e,t){t.replaceWith(e.cloneNode(!0))}leavingBardo(){}}class ht{sources=new Set;#T=!1;constructor(e){this.delegate=e}start(){this.#T||(this.#T=!0,addEventListener("turbo:before-fetch-response",this.inspectFetchResponse,!1))}stop(){this.#T&&(this.#T=!1,removeEventListener("turbo:before-fetch-response",this.inspectFetchResponse,!1))}connectStreamSource(e){this.streamSourceIsConnected(e)||(this.sources.add(e),e.addEventListener("message",this.receiveMessageEvent,!1))}disconnectStreamSource(e){this.streamSourceIsConnected(e)&&(this.sources.delete(e),e.removeEventListener("message",this.receiveMessageEvent,!1))}streamSourceIsConnected(e){return this.sources.has(e)}inspectFetchResponse=e=>{const t=function(e){const t=e.detail?.fetchResponse;if(t instanceof W)return t}(e);t&&function(e){const t=e.contentType??"";return t.startsWith(se.contentType)}(t)&&(e.preventDefault(),this.receiveMessageResponse(t))};receiveMessageEvent=e=>{this.#T&&"string"==typeof e.data&&this.receiveMessageHTML(e.data)};async receiveMessageResponse(e){const t=await e.responseHTML;t&&this.receiveMessageHTML(t)}receiveMessageHTML(e){this.delegate.receivedMessageFromStream(se.wrap(e))}}class dt extends ge{static renderElement(e,t){const{documentElement:s,body:r}=document;s.replaceChild(t,r)}async render(){this.replaceHeadAndBody(),this.activateScriptElements()}replaceHeadAndBody(){const{documentElement:e,head:t}=document;e.replaceChild(this.newHead,t),this.renderElement(this.currentElement,this.newElement)}activateScriptElements(){for(const e of this.scriptElements){const t=e.parentNode;if(t){const s=o(e);t.replaceChild(s,e)}}}get newHead(){return this.newSnapshot.headSnapshot.element}get scriptElements(){return document.documentElement.querySelectorAll("script")}}class ut extends ge{static renderElement(e,t){document.body&&t instanceof HTMLBodyElement?document.body.replaceWith(t):document.documentElement.appendChild(t)}get shouldRender(){return this.newSnapshot.isVisitable&&this.trackedElementsAreIdentical}get reloadReason(){return this.newSnapshot.isVisitable?this.trackedElementsAreIdentical?void 0:{reason:"tracked_element_mismatch"}:{reason:"turbo_visit_control_is_reload"}}async prepareToRender(){this.#A(),await this.mergeHead()}async render(){this.willRender&&await this.replaceBody()}finishRendering(){super.finishRendering(),this.isPreview||this.focusFirstAutofocusableElement()}get currentHeadSnapshot(){return this.currentSnapshot.headSnapshot}get newHeadSnapshot(){return this.newSnapshot.headSnapshot}get newElement(){return this.newSnapshot.element}#A(){const{documentElement:e}=this.currentSnapshot,{lang:t}=this.newSnapshot;t?e.setAttribute("lang",t):e.removeAttribute("lang")}async mergeHead(){const e=this.mergeProvisionalElements(),t=this.copyNewHeadStylesheetElements();this.copyNewHeadScriptElements(),await e,await t,this.willRender&&this.removeUnusedDynamicStylesheetElements()}async replaceBody(){await this.preservingPermanentElements((async()=>{this.activateNewBody(),await this.assignNewBody()}))}get trackedElementsAreIdentical(){return this.currentHeadSnapshot.trackedElementSignature==this.newHeadSnapshot.trackedElementSignature}async copyNewHeadStylesheetElements(){const e=[];for(const t of this.newHeadStylesheetElements)e.push(v(t)),document.head.appendChild(t);await Promise.all(e)}copyNewHeadScriptElements(){for(const e of this.newHeadScriptElements)document.head.appendChild(o(e))}removeUnusedDynamicStylesheetElements(){for(const e of this.unusedDynamicStylesheetElements)document.head.removeChild(e)}async mergeProvisionalElements(){const e=[...this.newHeadProvisionalElements];for(const t of this.currentHeadProvisionalElements)this.isCurrentElementInElementList(t,e)||document.head.removeChild(t);for(const t of e)document.head.appendChild(t)}isCurrentElementInElementList(e,t){for(const[s,r]of t.entries()){if("TITLE"==e.tagName){if("TITLE"!=r.tagName)continue;if(e.innerHTML==r.innerHTML)return t.splice(s,1),!0}if(r.isEqualNode(e))return t.splice(s,1),!0}return!1}removeCurrentHeadProvisionalElements(){for(const e of this.currentHeadProvisionalElements)document.head.removeChild(e)}copyNewHeadProvisionalElements(){for(const e of this.newHeadProvisionalElements)document.head.appendChild(e)}activateNewBody(){document.adoptNode(this.newElement),this.activateNewBodyScriptElements()}activateNewBodyScriptElements(){for(const e of this.newBodyScriptElements){const t=o(e);e.replaceWith(t)}}async assignNewBody(){await this.renderElement(this.currentElement,this.newElement)}get unusedDynamicStylesheetElements(){return this.oldHeadStylesheetElements.filter((e=>"dynamic"===e.getAttribute("data-turbo-track")))}get oldHeadStylesheetElements(){return this.currentHeadSnapshot.getStylesheetElementsNotInSnapshot(this.newHeadSnapshot)}get newHeadStylesheetElements(){return this.newHeadSnapshot.getStylesheetElementsNotInSnapshot(this.currentHeadSnapshot)}get newHeadScriptElements(){return this.newHeadSnapshot.getScriptElementsNotInSnapshot(this.currentHeadSnapshot)}get currentHeadProvisionalElements(){return this.currentHeadSnapshot.provisionalElements}get newHeadProvisionalElements(){return this.newHeadSnapshot.provisionalElements}get newBodyScriptElements(){return this.newElement.querySelectorAll("script")}}class mt extends ut{static renderElement(e,t){Se(e,t,{callbacks:{beforeNodeMorphed:e=>!pt(e)}});for(const t of e.querySelectorAll("turbo-frame"))pt(t)&&t.reload();a("turbo:morph",{detail:{currentElement:e,newElement:t}})}async preservingPermanentElements(e){return await e()}get renderMethod(){return"morph"}get shouldAutofocus(){return!1}}function pt(e){return e instanceof i&&e.src&&"morph"===e.refresh&&!e.closest("[data-turbo-permanent]")}class ft{keys=[];snapshots={};constructor(e){this.size=e}has(e){return D(e)in this.snapshots}get(e){if(this.has(e)){const t=this.read(e);return this.touch(e),t}}put(e,t){return this.write(e,t),this.touch(e),t}clear(){this.snapshots={}}read(e){return this.snapshots[D(e)]}write(e,t){this.snapshots[D(e)]=t}touch(e){const t=D(e),s=this.keys.indexOf(t);s>-1&&this.keys.splice(s,1),this.keys.unshift(t),this.trim()}trim(){for(const e of this.keys.splice(this.size))delete this.snapshots[e]}}class gt extends he{snapshotCache=new ft(10);lastRenderedLocation=new URL(location.href);forceReloaded=!1;shouldTransitionTo(e){return this.snapshot.prefersViewTransitions&&e.prefersViewTransitions}renderPage(e,t=!1,s=!0,r){const i=new(this.isPageRefresh(r)&&this.snapshot.shouldMorphPage?mt:ut)(this.snapshot,e,t,s);return i.shouldRender?r?.changeHistory():this.forceReloaded=!0,this.render(i)}renderError(e,t){t?.changeHistory();const s=new dt(this.snapshot,e,!1);return this.render(s)}clearSnapshotCache(){this.snapshotCache.clear()}async cacheSnapshot(e=this.snapshot){if(e.isCacheable){this.delegate.viewWillCacheSnapshot();const{lastRenderedLocation:t}=this;await d();const s=e.clone();return this.snapshotCache.put(t,s),s}}getCachedSnapshotForLocation(e){return this.snapshotCache.get(e)}isPageRefresh(e){return!e||this.lastRenderedLocation.pathname===e.location.pathname&&"replace"===e.action}shouldPreserveScrollPosition(e){return this.isPageRefresh(e)&&this.snapshot.shouldPreserveScrollPosition}get snapshot(){return Pe.fromElement(this.element)}}class bt{selector="a[data-turbo-preload]";constructor(e,t){this.delegate=e,this.snapshotCache=t}start(){"loading"===document.readyState?document.addEventListener("DOMContentLoaded",this.#P):this.preloadOnLoadLinksForView(document.body)}stop(){document.removeEventListener("DOMContentLoaded",this.#P)}preloadOnLoadLinksForView(e){for(const t of e.querySelectorAll(this.selector))this.delegate.shouldPreloadLink(t)&&this.preloadURL(t)}async preloadURL(e){const t=new URL(e.href);if(this.snapshotCache.has(t))return;const s=new Y(this,X.get,t,new URLSearchParams,e);await s.perform()}prepareRequest(e){e.headers["X-Sec-Purpose"]="prefetch"}async requestSucceededWithResponse(e,t){try{const s=await t.responseHTML,r=Pe.fromHTMLString(s);this.snapshotCache.put(e.url,r)}catch(e){}}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}#P=()=>{this.preloadOnLoadLinksForView(document.body)}}class vt{constructor(e){this.session=e}clear(){this.session.clearCache()}resetCacheControl(){this.#C("")}exemptPageFromCache(){this.#C("no-cache")}exemptPageFromPreview(){this.#C("no-preview")}#C(e){!function(e,t){let s=E(e);s||(s=document.createElement("meta"),s.setAttribute("name",e),document.head.appendChild(s)),s.setAttribute("content",t)}("turbo-cache-control",e)}}function St(e){Object.defineProperties(e,wt)}const wt={absoluteURL:{get(){return this.toString()}}},Et=new class{navigator=new st(this);history=new Ke(this);view=new gt(this,document.documentElement);adapter=new ze(this);pageObserver=new at(this);cacheObserver=new _e;linkPrefetchObserver=new Qe(this,document);linkClickObserver=new me(this,window);formSubmitObserver=new ce(this,document);scrollObserver=new lt(this);streamObserver=new ht(this);formLinkClickObserver=new pe(this,document.documentElement);frameRedirector=new Xe(this,document.documentElement);streamMessageRenderer=new ct;cache=new vt(this);enabled=!0;started=!1;#k=150;constructor(e){this.recentRequests=e,this.preloader=new bt(this,this.view.snapshotCache),this.debouncedRefresh=this.refresh,this.pageRefreshDebouncePeriod=this.pageRefreshDebouncePeriod}start(){this.started||(this.pageObserver.start(),this.cacheObserver.start(),this.linkPrefetchObserver.start(),this.formLinkClickObserver.start(),this.linkClickObserver.start(),this.formSubmitObserver.start(),this.scrollObserver.start(),this.streamObserver.start(),this.frameRedirector.start(),this.history.start(),this.preloader.start(),this.started=!0,this.enabled=!0)}disable(){this.enabled=!1}stop(){this.started&&(this.pageObserver.stop(),this.cacheObserver.stop(),this.linkPrefetchObserver.stop(),this.formLinkClickObserver.stop(),this.linkClickObserver.stop(),this.formSubmitObserver.stop(),this.scrollObserver.stop(),this.streamObserver.stop(),this.frameRedirector.stop(),this.history.stop(),this.preloader.stop(),this.started=!1)}registerAdapter(e){this.adapter=e}visit(e,t={}){const s=t.frame?document.getElementById(t.frame):null;if(s instanceof i){const r=t.action||w(s);s.delegate.proposeVisitIfNavigatedWithAction(s,r),s.src=e.toString()}else this.navigator.proposeVisit(q(e),t)}refresh(e,t){t&&this.recentRequests.has(t)||this.navigator.currentVisit||this.visit(e,{action:"replace",shouldCacheSnapshot:!1})}connectStreamSource(e){this.streamObserver.connectStreamSource(e)}disconnectStreamSource(e){this.streamObserver.disconnectStreamSource(e)}renderStreamMessage(e){this.streamMessageRenderer.render(se.wrap(e))}clearCache(){this.view.clearSnapshotCache()}setProgressBarDelay(e){console.warn("Please replace `session.setProgressBarDelay(delay)` with `session.progressBarDelay = delay`. The function is deprecated and will be removed in a future version of Turbo.`"),this.progressBarDelay=e}set progressBarDelay(e){I.drive.progressBarDelay=e}get progressBarDelay(){return I.drive.progressBarDelay}set drive(e){I.drive.enabled=e}get drive(){return I.drive.enabled}set formMode(e){I.forms.mode=e}get formMode(){return I.forms.mode}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}get pageRefreshDebouncePeriod(){return this.#k}set pageRefreshDebouncePeriod(e){this.refresh=function(e,t){let s=null;return(...r)=>{clearTimeout(s),s=setTimeout((()=>e.apply(this,r)),t)}}(this.debouncedRefresh.bind(this),e),this.#k=e}shouldPreloadLink(e){const t=e.hasAttribute("data-turbo-method"),s=e.hasAttribute("data-turbo-stream"),r=e.getAttribute("data-turbo-frame"),n="_top"==r?null:document.getElementById(r)||L(e,"turbo-frame:not([disabled])");if(t||s||n instanceof i)return!1;{const t=new URL(e.href);return this.elementIsNavigatable(e)&&x(t,this.snapshot.rootLocation)}}historyPoppedToLocationWithRestorationIdentifierAndDirection(e,t,s){this.enabled?this.navigator.startVisit(e,t,{action:"restore",historyChanged:!0,direction:s}):this.adapter.pageInvalidated({reason:"turbo_disabled"})}scrollPositionChanged(e){this.history.updateRestorationData({scrollPosition:e})}willSubmitFormLinkToLocation(e,t){return this.elementIsNavigatable(e)&&x(t,this.snapshot.rootLocation)}submittedFormLinkToLocation(){}canPrefetchRequestToLocation(e,t){return this.elementIsNavigatable(e)&&x(t,this.snapshot.rootLocation)}willFollowLinkToLocation(e,t,s){return this.elementIsNavigatable(e)&&x(t,this.snapshot.rootLocation)&&this.applicationAllowsFollowingLinkToLocation(e,t,s)}followedLinkToLocation(e,t){const s=this.getActionForLink(e),r=e.hasAttribute("data-turbo-stream");this.visit(t.href,{action:s,acceptsStreamResponse:r})}allowsVisitingLocationWithAction(e,t){return this.locationWithActionIsSamePage(e,t)||this.applicationAllowsVisitingLocation(e)}visitProposedToLocation(e,t){St(e),this.adapter.visitProposedToLocation(e,t)}visitStarted(e){e.acceptsStreamResponse||(g(document.documentElement),this.view.markVisitDirection(e.direction)),St(e.location),e.silent||this.notifyApplicationAfterVisitingLocation(e.location,e.action)}visitCompleted(e){this.view.unmarkVisitDirection(),b(document.documentElement),this.notifyApplicationAfterPageLoad(e.getTimingMetrics())}locationWithActionIsSamePage(e,t){return this.navigator.locationWithActionIsSamePage(e,t)}visitScrolledToSamePageLocation(e,t){this.notifyApplicationAfterVisitingSamePageLocation(e,t)}willSubmitForm(e,t){const s=B(e,t);return this.submissionIsNavigatable(e,t)&&x(q(s),this.snapshot.rootLocation)}formSubmitted(e,t){this.navigator.submitForm(e,t)}pageBecameInteractive(){this.view.lastRenderedLocation=this.location,this.notifyApplicationAfterPageLoad()}pageLoaded(){this.history.assumeControlOfScrollRestoration()}pageWillUnload(){this.history.relinquishControlOfScrollRestoration()}receivedMessageFromStream(e){this.renderStreamMessage(e)}viewWillCacheSnapshot(){this.navigator.currentVisit?.silent||this.notifyApplicationBeforeCachingSnapshot()}allowsImmediateRender({element:e},t){const s=this.notifyApplicationBeforeRender(e,t),{defaultPrevented:r,detail:{render:i}}=s;return this.view.renderer&&i&&(this.view.renderer.renderElement=i),!r}viewRenderedSnapshot(e,t,s){this.view.lastRenderedLocation=this.history.location,this.notifyApplicationAfterRender(s)}preloadOnLoadLinksForView(e){this.preloader.preloadOnLoadLinksForView(e)}viewInvalidated(e){this.adapter.pageInvalidated(e)}frameLoaded(e){this.notifyApplicationAfterFrameLoad(e)}frameRendered(e,t){this.notifyApplicationAfterFrameRender(e,t)}applicationAllowsFollowingLinkToLocation(e,t,s){return!this.notifyApplicationAfterClickingLinkToLocation(e,t,s).defaultPrevented}applicationAllowsVisitingLocation(e){return!this.notifyApplicationBeforeVisitingLocation(e).defaultPrevented}notifyApplicationAfterClickingLinkToLocation(e,t,s){return a("turbo:click",{target:e,detail:{url:t.href,originalEvent:s},cancelable:!0})}notifyApplicationBeforeVisitingLocation(e){return a("turbo:before-visit",{detail:{url:e.href},cancelable:!0})}notifyApplicationAfterVisitingLocation(e,t){return a("turbo:visit",{detail:{url:e.href,action:t}})}notifyApplicationBeforeCachingSnapshot(){return a("turbo:before-cache")}notifyApplicationBeforeRender(e,t){return a("turbo:before-render",{detail:{newBody:e,...t},cancelable:!0})}notifyApplicationAfterRender(e){return a("turbo:render",{detail:{renderMethod:e}})}notifyApplicationAfterPageLoad(e={}){return a("turbo:load",{detail:{url:this.location.href,timing:e}})}notifyApplicationAfterVisitingSamePageLocation(e,t){dispatchEvent(new HashChangeEvent("hashchange",{oldURL:e.toString(),newURL:t.toString()}))}notifyApplicationAfterFrameLoad(e){return a("turbo:frame-load",{target:e})}notifyApplicationAfterFrameRender(e,t){return a("turbo:frame-render",{detail:{fetchResponse:e},target:t,cancelable:!0})}submissionIsNavigatable(e,t){if("off"==I.forms.mode)return!1;{const s=!t||this.elementIsNavigatable(t);return"optin"==I.forms.mode?s&&null!=e.closest('[data-turbo="true"]'):s&&this.elementIsNavigatable(e)}}elementIsNavigatable(e){const t=L(e,"[data-turbo]"),s=L(e,"turbo-frame");return I.drive.enabled||s?!t||"false"!=t.getAttribute("data-turbo"):!!t&&"true"==t.getAttribute("data-turbo")}getActionForLink(e){return w(e)||"advance"}get snapshot(){return this.view.snapshot}}(j),{cache:yt,navigator:Rt}=Et;function Lt(){Et.start()}function Tt(e){Et.registerAdapter(e)}function At(e,t){Et.visit(e,t)}function Pt(e){Et.connectStreamSource(e)}function Ct(e){Et.disconnectStreamSource(e)}function kt(e){Et.renderStreamMessage(e)}function Mt(){console.warn("Please replace `Turbo.clearCache()` with `Turbo.cache.clear()`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),Et.clearCache()}function Ft(e){console.warn("Please replace `Turbo.setProgressBarDelay(delay)` with `Turbo.config.drive.progressBarDelay = delay`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),I.drive.progressBarDelay=e}function It(e){console.warn("Please replace `Turbo.setConfirmMethod(confirmMethod)` with `Turbo.config.forms.confirm = confirmMethod`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),I.forms.confirm=e}function qt(e){console.warn("Please replace `Turbo.setFormMode(mode)` with `Turbo.config.forms.mode = mode`. The top-level function is deprecated and will be removed in a future version of Turbo.`"),I.forms.mode=e}var Ht=Object.freeze({__proto__:null,navigator:Rt,session:Et,cache:yt,PageRenderer:ut,PageSnapshot:Pe,FrameRenderer:be,fetch:z,config:I,start:Lt,registerAdapter:Tt,visit:At,connectStreamSource:Pt,disconnectStreamSource:Ct,renderStreamMessage:kt,clearCache:Mt,setProgressBarDelay:Ft,setConfirmMethod:It,setFormMode:qt});class Bt extends Error{}function Ot(e){if(null!=e){const t=document.getElementById(e);if(t instanceof i)return t}}function Nt(e,t){if(e){const r=e.getAttribute("src");if(null!=r&&null!=t&&(s=t,q(r).href==q(s).href))throw new Error(`Matching <turbo-frame id="${e.id}"> element has a source URL which references itself`);if(e.ownerDocument!==document&&(e=document.importNode(e,!0)),e instanceof i)return e.connectedCallback(),e.disconnectedCallback(),e}var s}const xt={after(){this.targetElements.forEach((e=>e.parentElement?.insertBefore(this.templateContent,e.nextSibling)))},append(){this.removeDuplicateTargetChildren(),this.targetElements.forEach((e=>e.append(this.templateContent)))},before(){this.targetElements.forEach((e=>e.parentElement?.insertBefore(this.templateContent,e)))},prepend(){this.removeDuplicateTargetChildren(),this.targetElements.forEach((e=>e.prepend(this.templateContent)))},remove(){this.targetElements.forEach((e=>e.remove()))},replace(){const e=this.getAttribute("method");this.targetElements.forEach((t=>{"morph"===e?Se(t,this.templateContent):t.replaceWith(this.templateContent)}))},update(){const e=this.getAttribute("method");this.targetElements.forEach((t=>{"morph"===e?we(t,this.templateContent):(t.innerHTML="",t.append(this.templateContent))}))},refresh(){Et.refresh(this.baseURI,this.requestId)}};class Vt extends HTMLElement{static async renderElement(e){await e.performAction()}async connectedCallback(){try{await this.render()}catch(e){console.error(e)}finally{this.disconnect()}}async render(){return this.renderPromise??=(async()=>{const e=this.beforeRenderEvent;this.dispatchEvent(e)&&(await c(),await e.detail.render(this))})()}disconnect(){try{this.remove()}catch{}}removeDuplicateTargetChildren(){this.duplicateChildren.forEach((e=>e.remove()))}get duplicateChildren(){const e=this.targetElements.flatMap((e=>[...e.children])).filter((e=>!!e.id)),t=[...this.templateContent?.children||[]].filter((e=>!!e.id)).map((e=>e.id));return e.filter((e=>t.includes(e.id)))}get performAction(){if(this.action){const e=xt[this.action];if(e)return e;this.#M("unknown action")}this.#M("action attribute is missing")}get targetElements(){return this.target?this.targetElementsById:this.targets?this.targetElementsByQuery:void this.#M("target or targets attribute is missing")}get templateContent(){return this.templateElement.content.cloneNode(!0)}get templateElement(){if(null===this.firstElementChild){const e=this.ownerDocument.createElement("template");return this.appendChild(e),e}if(this.firstElementChild instanceof HTMLTemplateElement)return this.firstElementChild;this.#M("first child element must be a <template> element")}get action(){return this.getAttribute("action")}get target(){return this.getAttribute("target")}get targets(){return this.getAttribute("targets")}get requestId(){return this.getAttribute("request-id")}#M(e){throw new Error(`${this.description}: ${e}`)}get description(){return(this.outerHTML.match(/<[^>]+>/)??[])[0]??"<turbo-stream>"}get beforeRenderEvent(){return new CustomEvent("turbo:before-stream-render",{bubbles:!0,cancelable:!0,detail:{newStream:this,render:Vt.renderElement}})}get targetElementsById(){const e=this.ownerDocument?.getElementById(this.target);return null!==e?[e]:[]}get targetElementsByQuery(){const e=this.ownerDocument?.querySelectorAll(this.targets);return 0!==e.length?Array.prototype.slice.call(e):[]}}class Dt extends HTMLElement{streamSource=null;connectedCallback(){this.streamSource=this.src.match(/^ws{1,2}:/)?new WebSocket(this.src):new EventSource(this.src),Pt(this.streamSource)}disconnectedCallback(){this.streamSource&&(this.streamSource.close(),Ct(this.streamSource))}get src(){return this.getAttribute("src")||""}}i.delegateConstructor=class{fetchResponseLoaded=e=>Promise.resolve();#F=null;#I=()=>{};#q=!1;#H=!1;#B=new Set;#O=!1;action=null;constructor(e){this.element=e,this.view=new de(this,this.element),this.appearanceObserver=new te(this,this.element),this.formLinkClickObserver=new pe(this,this.element),this.linkInterceptor=new ue(this,this.element),this.restorationIdentifier=p(),this.formSubmitObserver=new ce(this,this.element)}connect(){this.#q||(this.#q=!0,this.loadingStyle==r.lazy?this.appearanceObserver.start():this.#N(),this.formLinkClickObserver.start(),this.linkInterceptor.start(),this.formSubmitObserver.start())}disconnect(){this.#q&&(this.#q=!1,this.appearanceObserver.stop(),this.formLinkClickObserver.stop(),this.linkInterceptor.stop(),this.formSubmitObserver.stop())}disabledChanged(){this.loadingStyle==r.eager&&this.#N()}sourceURLChanged(){this.#x("src")||(this.element.isConnected&&(this.complete=!1),(this.loadingStyle==r.eager||this.#H)&&this.#N())}sourceURLReloaded(){const{refresh:e,src:t}=this.element;return this.#O=t&&"morph"===e,this.element.removeAttribute("complete"),this.element.src=null,this.element.src=t,this.element.loaded}loadingStyleChanged(){this.loadingStyle==r.lazy?this.appearanceObserver.start():(this.appearanceObserver.stop(),this.#N())}async#N(){this.enabled&&this.isActive&&!this.complete&&this.sourceURL&&(this.element.loaded=this.#V(q(this.sourceURL)),this.appearanceObserver.stop(),await this.element.loaded,this.#H=!0)}async loadResponse(e){(e.redirected||e.succeeded&&e.isHTML)&&(this.sourceURL=e.response.url);try{const t=await e.responseHTML;if(t){const s=u(t);Pe.fromDocument(s).isVisitable?await this.#D(e,s):await this.#W(e)}}finally{this.#O=!1,this.fetchResponseLoaded=()=>Promise.resolve()}}elementAppearedInViewport(e){this.proposeVisitIfNavigatedWithAction(e,w(e)),this.#N()}willSubmitFormLinkToLocation(e){return this.#U(e)}submittedFormLinkToLocation(e,t,s){const r=this.#m(e);r&&s.setAttribute("data-turbo-frame",r.id)}shouldInterceptLinkClick(e,t,s){return this.#U(e)}linkClickIntercepted(e,t){this.#j(e,t)}willSubmitForm(e,t){return e.closest("turbo-frame")==this.element&&this.#U(e,t)}formSubmitted(e,t){this.formSubmission&&this.formSubmission.stop(),this.formSubmission=new ne(this,e,t);const{fetchRequest:s}=this.formSubmission;this.prepareRequest(s),this.formSubmission.start()}prepareRequest(e){e.headers["Turbo-Frame"]=this.id,this.currentNavigationElement?.hasAttribute("data-turbo-stream")&&e.acceptResponseType(se.contentType)}requestStarted(e){g(this.element)}requestPreventedHandlingResponse(e,t){this.#I()}async requestSucceededWithResponse(e,t){await this.loadResponse(t),this.#I()}async requestFailedWithResponse(e,t){await this.loadResponse(t),this.#I()}requestErrored(e,t){console.error(t),this.#I()}requestFinished(e){b(this.element)}formSubmissionStarted({formElement:e}){g(e,this.#m(e))}formSubmissionSucceededWithResponse(e,t){const s=this.#m(e.formElement,e.submitter);s.delegate.proposeVisitIfNavigatedWithAction(s,w(e.submitter,e.formElement,s)),s.delegate.loadResponse(t),e.isSafe||Et.clearCache()}formSubmissionFailedWithResponse(e,t){this.element.delegate.loadResponse(t),Et.clearCache()}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished({formElement:e}){b(e,this.#m(e))}allowsImmediateRender({element:e},t){const s=a("turbo:before-frame-render",{target:this.element,detail:{newFrame:e,...t},cancelable:!0}),{defaultPrevented:r,detail:{render:i}}=s;return this.view.renderer&&i&&(this.view.renderer.renderElement=i),!r}viewRenderedSnapshot(e,t,s){}preloadOnLoadLinksForView(e){Et.preloadOnLoadLinksForView(e)}viewInvalidated(){}willRenderFrame(e,t){this.previousFrameElement=e.cloneNode(!0)}visitCachedSnapshot=({element:e})=>{const t=e.querySelector("#"+this.element.id);t&&this.previousFrameElement&&t.replaceChildren(...this.previousFrameElement.children),delete this.previousFrameElement};async#D(e,t){const s=await this.extractForeignFrameElement(t.body),r=this.#O?ye:be;if(s){const t=new oe(s),i=new r(this,this.view.snapshot,t,!1,!1);this.view.renderPromise&&await this.view.renderPromise,this.changeHistory(),await this.view.render(i),this.complete=!0,Et.frameRendered(e,this.element),Et.frameLoaded(this.element),await this.fetchResponseLoaded(e)}else this.#$(e)&&this.#z(e)}async#V(e){const t=new Y(this,X.get,e,new URLSearchParams,this.element);return this.#F?.cancel(),this.#F=t,new Promise((e=>{this.#I=()=>{this.#I=()=>{},this.#F=null,e()},t.perform()}))}#j(e,t,s){const r=this.#m(e,s);r.delegate.proposeVisitIfNavigatedWithAction(r,w(s,e,r)),this.#_(e,(()=>{r.src=t}))}proposeVisitIfNavigatedWithAction(e,t=null){if(this.action=t,this.action){const t=Pe.fromElement(e).clone(),{visitCachedSnapshot:s}=e.delegate;e.delegate.fetchResponseLoaded=async r=>{if(e.src){const{statusCode:i,redirected:n}=r,o={response:{statusCode:i,redirected:n,responseHTML:await r.responseHTML},visitCachedSnapshot:s,willRender:!1,updateHistory:!1,restorationIdentifier:this.restorationIdentifier,snapshot:t};this.action&&(o.action=this.action),Et.visit(e.src,o)}}}}changeHistory(){if(this.action){const e=S(this.action);Et.history.update(e,q(this.element.src||""),this.restorationIdentifier)}}async#W(e){console.warn(`The response (${e.statusCode}) from <turbo-frame id="${this.element.id}"> is performing a full page visit due to turbo-visit-control.`),await this.#X(e.response)}#$(e){this.element.setAttribute("complete","");const t=e.response;return!a("turbo:frame-missing",{target:this.element,detail:{response:t,visit:async(e,t)=>{e instanceof Response?this.#X(e):Et.visit(e,t)}},cancelable:!0}).defaultPrevented}#z(e){this.view.missing(),this.#K(e)}#K(e){const t=`The response (${e.statusCode}) did not contain the expected <turbo-frame id="${this.element.id}"> and will be ignored. To perform a full page visit instead, set turbo-visit-control to reload.`;throw new Bt(t)}async#X(e){const t=new W(e),s=await t.responseHTML,{location:r,redirected:i,statusCode:n}=t;return Et.visit(r,{response:{redirected:i,statusCode:n,responseHTML:s}})}#m(e,t){return Ot(f("data-turbo-frame",t,e)||this.element.getAttribute("target"))??this.element}async extractForeignFrameElement(e){let t;const s=CSS.escape(this.id);try{if(t=Nt(e.querySelector(`turbo-frame#${s}`),this.sourceURL),t)return t;if(t=Nt(e.querySelector(`turbo-frame[src][recurse~=${s}]`),this.sourceURL),t)return await t.loaded,await this.extractForeignFrameElement(t)}catch(e){return console.error(e),new i}return null}#Q(e,t){return x(q(B(e,t)),this.rootLocation)}#U(e,t){const s=f("data-turbo-frame",t,e)||this.element.getAttribute("target");if(e instanceof HTMLFormElement&&!this.#Q(e,t))return!1;if(!this.enabled||"_top"==s)return!1;if(s){const e=Ot(s);if(e)return!e.disabled}return!!Et.elementIsNavigatable(e)&&!(t&&!Et.elementIsNavigatable(t))}get id(){return this.element.id}get enabled(){return!this.element.disabled}get sourceURL(){if(this.element.src)return this.element.src}set sourceURL(e){this.#Y("src",(()=>{this.element.src=e??null}))}get loadingStyle(){return this.element.loading}get isLoading(){return void 0!==this.formSubmission||void 0!==this.#I()}get complete(){return this.element.hasAttribute("complete")}set complete(e){e?this.element.setAttribute("complete",""):this.element.removeAttribute("complete")}get isActive(){return this.element.isActive&&this.#q}get rootLocation(){const e=this.element.ownerDocument.querySelector('meta[name="turbo-root"]');return q(e?.content??"/")}#x(e){return this.#B.has(e)}#Y(e,t){this.#B.add(e),t(),this.#B.delete(e)}#_(e,t){this.currentNavigationElement=e,t(),delete this.currentNavigationElement}},void 0===customElements.get("turbo-frame")&&customElements.define("turbo-frame",i),void 0===customElements.get("turbo-stream")&&customElements.define("turbo-stream",Vt),void 0===customElements.get("turbo-stream-source")&&customElements.define("turbo-stream-source",Dt),(()=>{let e=document.currentScript;if(e&&!e.hasAttribute("data-turbo-suppress-warning"))for(e=e.parentElement;e;){if(e==document.body)return console.warn(m`
26
+ You are loading Turbo from a <script> element inside the <body> element. This is probably not what you meant to do!
27
+
28
+ Load your application’s JavaScript bundle inside the <head> element instead. <script> elements in <body> are evaluated with each page change.
29
+
30
+ For more information, see: https://turbo.hotwired.dev/handbook/building#working-with-script-elements
31
+
32
+ ——
33
+ Suppress this warning by adding a "data-turbo-suppress-warning" attribute to: %s
34
+ `,e.outerHTML);e=e.parentElement}})(),window.Turbo={...Ht,StreamActions:xt},Lt(),e.FetchEnctype=Q,e.FetchMethod=X,e.FetchRequest=Y,e.FetchResponse=W,e.FrameElement=i,e.FrameLoadingStyle=r,e.FrameRenderer=be,e.PageRenderer=ut,e.PageSnapshot=Pe,e.StreamActions=xt,e.StreamElement=Vt,e.StreamSourceElement=Dt,e.cache=yt,e.clearCache=Mt,e.config=I,e.connectStreamSource=Pt,e.disconnectStreamSource=Ct,e.fetch=z,e.fetchEnctypeFromString=K,e.fetchMethodFromString=_,e.isSafe=G,e.navigator=Rt,e.registerAdapter=Tt,e.renderStreamMessage=kt,e.session=Et,e.setConfirmMethod=It,e.setFormMode=qt,e.setProgressBarDelay=Ft,e.start=Lt,e.visit=At,Object.defineProperty(e,"__esModule",{value:!0})}));
35
+ //# sourceMappingURL=/sm/e9536071dc67aba3f6050d0b7db4efdac3a1924e3883a69fc696c07cce63be26.map
@@ -0,0 +1,15 @@
1
+ /*
2
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
3
+ * listed below.
4
+ *
5
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
6
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
7
+ *
8
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
9
+ * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS
10
+ * files in this directory. Styles in this file should be added after the last require_* statement.
11
+ * It is generally better to create a new file per style scope.
12
+ *
13
+ *= require_tree .
14
+ *= require_self
15
+ */
@@ -0,0 +1,41 @@
1
+ module RoundhouseUi
2
+ # Shared search + pagination over a Sidekiq job set (dead, retry, scheduled).
3
+ # Keeps the controllers from duplicating the scan/filter/window logic.
4
+ module JobSetBrowsing
5
+ extend ActiveSupport::Concern
6
+
7
+ PER_PAGE = 25
8
+
9
+ # Returns [entries_for_page, has_next?]. Scans only far enough to fill the
10
+ # requested page plus one (to know if a next page exists) — never loads the
11
+ # whole set, so a 50k dead set stays cheap to page through.
12
+ def browse(set, query, page, per = PER_PAGE)
13
+ start = (page - 1) * per
14
+ jobs = []
15
+ has_next = false
16
+ matched = 0
17
+
18
+ set.each do |entry|
19
+ next if query.present? && !entry_matches?(entry, query)
20
+
21
+ if matched < start
22
+ matched += 1
23
+ elsif jobs.size < per
24
+ jobs << entry
25
+ matched += 1
26
+ else
27
+ has_next = true
28
+ break
29
+ end
30
+ end
31
+
32
+ [ jobs, has_next ]
33
+ end
34
+
35
+ def entry_matches?(entry, query)
36
+ needle = query.downcase
37
+ [ entry.klass, entry.jid, entry.item["error_class"], entry.item["error_message"], entry.args.to_s ]
38
+ .any? { |hay| hay.to_s.downcase.include?(needle) }
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,75 @@
1
+ require "securerandom"
2
+
3
+ module RoundhouseUi
4
+ class ApplicationController < ActionController::Base
5
+ # Isolated engines don't auto-include the host's helpers; include ours.
6
+ helper ObservabilityHelper
7
+ helper NavHelper
8
+ helper_method :content_nonce
9
+
10
+ # Self-contained CSP, set per-request on our own responses so Roundhouse is
11
+ # safe to mount even when the host sets no policy — and never weakens one it
12
+ # does (this header only applies to engine responses). Strict default; we
13
+ # enumerate exactly what our pages use (same-origin only, nonce'd inline JS).
14
+ after_action :set_content_security_policy
15
+
16
+ # Record every state-changing (POST) action. Actions halted by a
17
+ # before_action (e.g. read-only mode) never reach here, so we only log what
18
+ # actually ran.
19
+ AUDIT_VERBS = {
20
+ "purge" => "purged queue", "pause" => "paused queue", "resume" => "resumed queue",
21
+ "snapshot" => "snapshotted queue", "requeue" => "retried", "destroy" => "deleted",
22
+ "bulk" => "bulk action", "enqueue" => "enqueued now", "restore" => "restored snapshot",
23
+ "quiet" => "quieted process", "stop" => "stopped process",
24
+ "create" => "enqueued job", "update" => "edited & re-enqueued",
25
+ "cancel" => "requested cancel"
26
+ }.freeze
27
+
28
+ after_action :record_audit_event, if: -> { request.post? }
29
+
30
+ # Use 303 See Other after POSTs so Turbo treats form submissions as redirects
31
+ # (and visits the target in place) instead of re-issuing the POST.
32
+ def redirect_to(options = {}, response_options = {})
33
+ response_options[:status] ||= :see_other if request.post?
34
+ super
35
+ end
36
+
37
+ private
38
+
39
+ def record_audit_event
40
+ target = params[:name] || params[:jid] || params[:id] || params[:job_class] ||
41
+ (params[:jids].presence && "#{Array(params[:jids]).size} jobs") || params[:op]
42
+ RoundhouseUi::Audit.record(
43
+ actor: current_actor,
44
+ action: AUDIT_VERBS[action_name] || "#{controller_name}##{action_name}",
45
+ target: target
46
+ )
47
+ rescue => e
48
+ Rails.logger.warn("[roundhouse] audit failed: #{e.message}")
49
+ end
50
+
51
+ def current_actor
52
+ resolver = RoundhouseUi.actor_resolver
53
+ (resolver && resolver.call(self)) || "anonymous"
54
+ rescue
55
+ "anonymous"
56
+ end
57
+
58
+ # Memoized so the value rendered into the <script> tag matches the header.
59
+ def content_nonce
60
+ @content_nonce ||= SecureRandom.base64(16)
61
+ end
62
+
63
+ def set_content_security_policy
64
+ response.headers["Content-Security-Policy"] = [
65
+ "default-src 'none'",
66
+ "script-src 'self' 'nonce-#{content_nonce}'",
67
+ "style-src 'self' 'unsafe-inline'",
68
+ "connect-src 'self'",
69
+ "img-src 'self' data:",
70
+ "form-action 'self'",
71
+ "base-uri 'self'"
72
+ ].join("; ")
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,16 @@
1
+ module RoundhouseUi
2
+ # Serves the vendored Turbo build same-origin so it passes the engine's CSP
3
+ # (script-src 'self') without a build step or asset pipeline.
4
+ class AssetsController < ApplicationController
5
+ # It's a public static asset — Rails' cross-origin-JS forgery guard would
6
+ # otherwise 422 the <script src> request.
7
+ skip_forgery_protection
8
+
9
+ TURBO_PATH = RoundhouseUi::Engine.root.join("app/assets/javascripts/roundhouse_ui/turbo.min.js").freeze
10
+
11
+ def turbo
12
+ response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
13
+ send_file TURBO_PATH, type: "text/javascript", disposition: "inline"
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,7 @@
1
+ module RoundhouseUi
2
+ class AuditController < ApplicationController
3
+ def index
4
+ @entries = RoundhouseUi::Audit.recent
5
+ end
6
+ end
7
+ end