chrono_forge-dashboard 0.2.0 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +36 -15
- data/app/assets/chrono_forge/dashboard/dashboard.css +1 -1
- data/app/assets/chrono_forge/dashboard/dashboard.js +86 -80
- data/app/assets/chrono_forge/dashboard/turbo.min.js +34 -0
- data/app/controllers/chrono_forge/dashboard/actions_controller.rb +41 -21
- data/app/controllers/chrono_forge/dashboard/assets_controller.rb +1 -0
- data/app/controllers/chrono_forge/dashboard/definitions_controller.rb +3 -3
- data/app/controllers/chrono_forge/dashboard/overview_controller.rb +45 -0
- data/app/controllers/chrono_forge/dashboard/stranded_controller.rb +29 -0
- data/app/helpers/chrono_forge/dashboard/dashboard_helper.rb +135 -1
- data/app/jobs/chrono_forge/dashboard/bulk_reap_job.rb +11 -0
- data/app/jobs/chrono_forge/dashboard/bulk_retry_job.rb +24 -0
- data/app/queries/chrono_forge/dashboard/overview_query.rb +62 -0
- data/app/queries/chrono_forge/dashboard/workflows_query.rb +6 -1
- data/app/views/chrono_forge/dashboard/analytics/index.html.erb +26 -7
- data/app/views/chrono_forge/dashboard/branch_children/show.html.erb +8 -3
- data/app/views/chrono_forge/dashboard/overview/_card_skeleton.html.erb +5 -0
- data/app/views/chrono_forge/dashboard/overview/_stat_card.html.erb +5 -0
- data/app/views/chrono_forge/dashboard/overview/blocked.html.erb +5 -0
- data/app/views/chrono_forge/dashboard/overview/classes.html.erb +65 -0
- data/app/views/chrono_forge/dashboard/overview/in_flight.html.erb +4 -0
- data/app/views/chrono_forge/dashboard/overview/index.html.erb +36 -0
- data/app/views/chrono_forge/dashboard/overview/processed.html.erb +4 -0
- data/app/views/chrono_forge/dashboard/repetitions/index.html.erb +30 -8
- data/app/views/chrono_forge/dashboard/stranded/index.html.erb +73 -0
- data/app/views/chrono_forge/dashboard/wait_states/index.html.erb +32 -8
- data/app/views/chrono_forge/dashboard/workflows/_error_card.html.erb +1 -1
- data/app/views/chrono_forge/dashboard/workflows/_filters.html.erb +9 -3
- data/app/views/chrono_forge/dashboard/workflows/_timeline.html.erb +66 -18
- data/app/views/chrono_forge/dashboard/workflows/_workflow_row.html.erb +18 -1
- data/app/views/chrono_forge/dashboard/workflows/index.html.erb +10 -3
- data/app/views/chrono_forge/dashboard/workflows/show.html.erb +28 -1
- data/app/views/layouts/chrono_forge/dashboard/application.html.erb +12 -5
- data/config/routes.rb +15 -1
- data/lib/chrono_forge/dashboard/version.rb +1 -1
- metadata +16 -2
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minified by jsDelivr using Terser v5.39.0.
|
|
3
|
+
* Original file: /npm/@hotwired/turbo@8.0.23/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.23
|
|
9
|
+
Copyright © 2026 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";const t={eager:"eager",lazy:"lazy"};class r extends HTMLElement{static delegateConstructor=void 0;loaded=Promise.resolve();static get observedAttributes(){return["disabled","loading","src"]}constructor(){super(),this.delegate=new r.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 t.lazy;return t.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 s={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 i(e){if("false"==e.getAttribute("data-turbo-eval"))return e;{const t=document.createElement("script"),r=E();return r&&(t.nonce=r),t.textContent=e.textContent,t.async=!1,function(e,t){for(const{name:r,value:s}of t.attributes)e.setAttribute(r,s)}(t,e),t}}function n(e,{target:t,cancelable:r,detail:s}={}){const i=new CustomEvent(e,{cancelable:r,bubbles:!0,composed:!0,detail:s});return t&&t.isConnected?t.dispatchEvent(i):document.documentElement.dispatchEvent(i),i}function o(e){e.preventDefault(),e.stopImmediatePropagation()}function a(){return"hidden"===document.visibilityState?l():c()}function c(){return new Promise((e=>requestAnimationFrame((()=>e()))))}function l(){return new Promise((e=>setTimeout((()=>e()),0)))}function h(e=""){return(new DOMParser).parseFromString(e,"text/html")}function d(e,...t){const r=function(e,t){return e.reduce(((e,r,s)=>e+r+(null==t[s]?"":t[s])),"")}(e,t).replace(/^\n/,"").split("\n"),s=r[0].match(/^\s+/),i=s?s[0].length:0;return r.map((e=>e.slice(i))).join("\n")}function u(){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(16*Math.random()).toString(16))).join("")}function m(e,...t){for(const r of t.map((t=>t?.getAttribute(e))))if("string"==typeof r)return r;return null}function p(...e){for(const t of e)"turbo-frame"==t.localName&&t.setAttribute("busy",""),t.setAttribute("aria-busy","true")}function f(...e){for(const t of e)"turbo-frame"==t.localName&&t.removeAttribute("busy"),t.removeAttribute("aria-busy")}function g(e,t=2e3){return new Promise((r=>{const s=()=>{e.removeEventListener("error",s),e.removeEventListener("load",s),r()};e.addEventListener("load",s,{once:!0}),e.addEventListener("error",s,{once:!0}),setTimeout(r,t)}))}function b(e){switch(e){case"replace":return history.replaceState;case"advance":case"restore":return history.pushState}}function v(...e){const t=m("data-turbo-action",...e);return function(e){return"advance"==e||"replace"==e||"restore"==e}(t)?t:null}function w(e){return document.querySelector(`meta[name="${e}"]`)}function S(e){const t=w(e);return t&&t.content}function E(){const e=w("csp-nonce");if(e){const{nonce:t,content:r}=e;return""==t?r:t}}function y(e,t){if(e instanceof Element)return e.closest(t)||y(e.assignedSlot||e.getRootNode()?.host,t)}function R(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(R)}function T(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 L(e){const t=y(e,"a[href], a[xlink\\:href]");if(!t)return null;if(t.href.startsWith("#"))return null;if(t.hasAttribute("download"))return null;const r=t.getAttribute("target");return r&&"_self"!==r?null:t}const C={"aria-disabled":{beforeSubmit:e=>{e.setAttribute("aria-disabled","true"),e.addEventListener("click",o)},afterSubmit:e=>{e.removeAttribute("aria-disabled"),e.removeEventListener("click",o)}},disabled:{beforeSubmit:e=>e.disabled=!0,afterSubmit:e=>e.disabled=!1}};const P=new class{#e=null;constructor(e){Object.assign(this,e)}get submitter(){return this.#e}set submitter(e){this.#e=C[e]||e}}({mode:"on",submitter:"disabled"}),k={drive:s,forms:P};function M(e){return new URL(e.toString(),document.baseURI)}function F(e){let t;return e.hash?e.hash.slice(1):(t=e.href.match(/#(.*)$/))?t[1]:void 0}function I(e,t){return M(t?.getAttribute("formaction")||e.getAttribute("action")||e.action)}function q(e){return(function(e){return function(e){return e.pathname.split("/").slice(1)}(e).slice(-1)[0]}(e).match(/\.[^.]*$/)||[])[0]||""}function H(e,t){return function(e,t){const r=x(t.origin+t.pathname);return x(e.href)===r||e.href.startsWith(r)}(e,t)&&!k.drive.unvisitableExtensions.has(q(e))}function B(e){return M(e.getAttribute("href")||"")}function N(e){return function(e){const t=F(e);return null!=t?e.href.slice(0,-(t.length+1)):e.href}(e)}function O(e,t){return M(e).href==M(t).href}function x(e){return e.endsWith("/")?e:e+"/"}class D{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 M(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 V 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 W=new V(20);function U(e,t={}){const r=new Headers(t.headers||{}),s=u();return W.add(s),r.append("X-Turbo-Request-Id",s),window.fetch(e,{...t,headers:r})}function j(e){switch(e.toLowerCase()){case"get":return z.get;case"post":return z.post;case"put":return z.put;case"patch":return z.patch;case"delete":return z.delete}}const z={get:"get",post:"post",put:"put",patch:"patch",delete:"delete"};function _(e){switch(e.toLowerCase()){case $.multipart:return $.multipart;case $.plain:return $.plain;default:return $.urlEncoded}}const $={urlEncoded:"application/x-www-form-urlencoded",multipart:"multipart/form-data",plain:"text/plain"};class K{abortController=new AbortController;#t=e=>{};constructor(e,t,r,s=new URLSearchParams,i=null,n=$.urlEncoded){const[o,a]=Y(M(r),t,s,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,r=j(e)||z.get;this.url.search="";const[s,i]=Y(this.url,r,t,this.enctype);this.url=s,this.fetchOptions.body=i,this.fetchOptions.method=r.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.#r(e);try{this.delegate.requestStarted(this),t.detail.fetchRequest?this.response=t.detail.fetchRequest.response:this.response=U(this.url.href,e);const r=await this.response;return await this.receive(r)}catch(e){if("AbortError"!==e.name)throw this.#s(e)&&this.delegate.requestErrored(this,e),e}finally{this.delegate.requestFinished(this)}}async receive(e){const t=new D(e);return n("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 X(this.method)}get abortSignal(){return this.abortController.signal}acceptResponseType(e){this.headers.Accept=[e,this.headers.Accept].join(", ")}async#r(e){const t=new Promise((e=>this.#t=e)),r=n("turbo:before-fetch-request",{cancelable:!0,detail:{fetchOptions:e,url:this.url,resume:this.#t},target:this.target});return this.url=r.detail.url,r.defaultPrevented&&await t,r}#s(e){return!n("turbo:fetch-request-error",{target:this.target,cancelable:!0,detail:{request:this,error:e}}).defaultPrevented}}function X(e){return j(e)==z.get}function Y(e,t,r,s){const i=Array.from(r).length>0?new URLSearchParams(Q(r)):e.searchParams;return X(t)?[G(e,i),null]:s==$.urlEncoded?[e,i]:[e,r]}function Q(e){const t=[];for(const[r,s]of e)s instanceof File||t.push([r,s]);return t}function G(e,t){const r=new URLSearchParams(Q(t));return e.search=r.toString(),e}class J{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 Z{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(i(t));t.replaceWith(e)}return e}(e)}}const ee=e=>e;class te{keys=[];entries={};#i;constructor(e,t=ee){this.size=e,this.#i=t}has(e){return this.#i(e)in this.entries}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(){for(const e of Object.keys(this.entries))this.evict(e)}read(e){return this.entries[this.#i(e)]}write(e,t){this.entries[this.#i(e)]=t}touch(e){e=this.#i(e);const t=this.keys.indexOf(e);t>-1&&this.keys.splice(t,1),this.keys.unshift(e),this.trim()}trim(){for(const e of this.keys.splice(this.size))this.evict(e)}evict(e){delete this.entries[e]}}const re=1e4,se=new class extends te{#n=null;#o={};constructor(e=1,t=100){super(e,N),this.prefetchDelay=t}putLater(e,t,r){this.#n=setTimeout((()=>{t.perform(),this.put(e,t,r),this.#n=null}),this.prefetchDelay)}put(e,t,r=re){super.put(e,t),this.#o[N(e)]=new Date((new Date).getTime()+r)}clear(){super.clear(),this.#n&&clearTimeout(this.#n)}evict(e){super.evict(e),delete this.#o[e]}has(e){if(super.has(e)){const t=this.#o[N(e)];return t&&t>Date.now()}return!1}},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,r,s=!1){const i=function(e,t){const r=t?.getAttribute("formmethod")||e.getAttribute("method")||"";return j(r.toLowerCase())||z.get}(t,r),n=function(e,t){const r=M(e);X(t)&&(r.search="");return r}(function(e,t){const r="string"==typeof e.action?e.action:null;return t?.hasAttribute("formaction")?t.getAttribute("formaction")||"":e.getAttribute("action")||r||""}(t,r),i),o=function(e,t){const r=new FormData(e),s=t?.getAttribute("name"),i=t?.getAttribute("value");s&&r.append(s,i||"");return r}(t,r),a=function(e,t){return _(t?.getAttribute("formenctype")||e.enctype)}(t,r);this.delegate=e,this.formElement=t,this.submitter=r,this.fetchRequest=new K(this,i,n,o,t,a),this.mustRedirect=s}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=M(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,r=m("data-turbo-confirm",this.submitter,this.formElement);if("string"==typeof r){const e="function"==typeof k.forms.confirm?k.forms.confirm:ne.confirmMethod;if(!await e(r,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}}}(S("csrf-param"))||S("csrf-token");t&&(e.headers["X-CSRF-Token"]=t)}this.requestAcceptsTurboStreamResponse(e)&&e.acceptResponseType(Z.contentType)}requestStarted(e){this.state=ie.waiting,this.submitter&&k.forms.submitter.beforeSubmit(this.submitter),this.setSubmitsWith(),p(this.formElement),n("turbo:submit-start",{target:this.formElement,detail:{formSubmission:this}}),this.delegate.formSubmissionStarted(this)}requestPreventedHandlingResponse(e,t){se.clear(),this.result={success:t.succeeded,fetchResponse:t}}requestSucceededWithResponse(e,t){if(t.clientError||t.serverError)this.delegate.formSubmissionFailedWithResponse(this,t);else if(se.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&&k.forms.submitter.afterSubmit(this.submitter),this.resetSubmitterText(),f(this.formElement),n("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 ce(this.element)}getPermanentElementById(e){return ae(this.element,e)}getPermanentElementMapForSnapshot(e){const t={};for(const r of this.permanentElements){const{id:s}=r,i=e.getPermanentElementById(s);i&&(t[s]=[r,i])}return t}}function ae(e,t){return e.querySelector(`#${t}[data-turbo-permanent]`)}function ce(e){return e.querySelectorAll("[id][data-turbo-permanent]")}class le{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,r=e.submitter||void 0;t&&function(e,t){const r=t?.getAttribute("formmethod")||e.getAttribute("method");return"dialog"!=r}(t,r)&&function(e,t){const r=t?.getAttribute("formtarget")||e.getAttribute("target");return T(r)}(t,r)&&this.delegate.willSubmitForm(t,r)&&(e.preventDefault(),e.stopImmediatePropagation(),this.delegate.formSubmitted(t,r))}}}class he{#a=e=>{};#c=e=>{};constructor(e,t){this.delegate=e,this.element=t}scrollToAnchor(e){const t=this.snapshot.getElementForAnchor(e);t?(this.focusElement(t),this.scrollToElement(t)):this.scrollToPosition({x:0,y:0})}scrollToAnchorFromLocation(e){this.scrollToAnchor(F(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:r,willRender:s,newSnapshot:i}=e,n=s;if(r)try{this.renderPromise=new Promise((e=>this.#a=e)),this.renderer=e,await this.prepareToRenderSnapshot(e);const r=new Promise((e=>this.#c=e)),s={resume:this.#c,render:this.renderer.renderElement,renderMethod:this.renderer.renderMethod};this.delegate.allowsImmediateRender(i,s)||await r,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.#a(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,r=L(t)||t;return r instanceof Element&&r.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=L(e.composedPath&&e.composedPath()[0]||e.target);if(t&&T(t.target)){const r=B(t);this.delegate.willFollowLinkToLocation(t,r,e)&&(e.preventDefault(),this.delegate.followedLinkToLocation(t,r))}}};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,r){return this.delegate.willSubmitFormLinkToLocation(e,t,r)&&(e.hasAttribute("data-turbo-method")||e.hasAttribute("data-turbo-stream"))}followedLinkToLocation(e,t){const r=document.createElement("form");for(const[e,s]of t.searchParams)r.append(Object.assign(document.createElement("input"),{type:"hidden",name:e,value:s}));const s=Object.assign(t,{search:""});r.setAttribute("data-turbo","true"),r.setAttribute("action",s.href),r.setAttribute("hidden","");const i=e.getAttribute("data-turbo-method");i&&r.setAttribute("method",i);const n=e.getAttribute("data-turbo-frame");n&&r.setAttribute("data-turbo-frame",n);const o=v(e);o&&r.setAttribute("data-turbo-action",o);const a=e.getAttribute("data-turbo-confirm");a&&r.setAttribute("data-turbo-confirm",a);e.hasAttribute("data-turbo-stream")&&r.setAttribute("data-turbo-stream",""),this.delegate.submittedFormLinkToLocation(e,t,r),document.body.appendChild(r),r.addEventListener("turbo:submit-end",(()=>r.remove()),{once:!0}),requestAnimationFrame((()=>r.requestSubmit()))}}class fe{static async preservingPermanentElements(e,t,r){const s=new this(e,t);s.enter(),await r(),s.leave()}constructor(e,t){this.delegate=e,this.permanentElementMap=t}enter(){for(const e in this.permanentElementMap){const[t,r]=this.permanentElementMap[e];this.delegate.enteringBardo(t,r),this.replaceNewPermanentElementWithPlaceholder(r)}}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,r,s=!0){this.currentSnapshot=e,this.newSnapshot=t,this.isPreview=r,this.willRender=s,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 r=document.createRange();r.selectNodeContents(e),r.deleteContents();const s=t,i=s.ownerDocument?.createRange();i&&(i.selectNodeContents(s),e.appendChild(i.extractContents()))}constructor(e,t,r,s,i,n=!0){super(t,r,s,i,n),this.delegate=e}get shouldRender(){return!0}async render(){await a(),this.preservingPermanentElements((()=>{this.loadFrameElement()})),this.scrollFrameIntoView(),await a(),this.focusFirstAutofocusableElement(),await a(),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 r=this.currentElement.firstElementChild,s=(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(r)return r.scrollIntoView({block:s,behavior:i}),!0}var e,t;return!1}activateScriptElements(){for(const e of this.newScriptElements){const t=i(e);e.replaceWith(t)}}get newScriptElements(){return this.currentElement.querySelectorAll("script")}}var ve=function(){const e=()=>{},t={morphStyle:"outerHTML",callbacks:{beforeNodeAdded:e,afterNodeAdded:e,beforeNodeMorphed:e,afterNodeMorphed:e,beforeNodeRemoved:e,afterNodeRemoved:e,beforeAttributeUpdated:e},head:{style:"merge",shouldPreserve:e=>"true"===e.getAttribute("im-preserve"),shouldReAppend:e=>"true"===e.getAttribute("im-re-append"),shouldRemove:e,afterHeadMorphed:e},restoreFocus:!0};const r=function(){function e(e,t,r,i){if(!1===i.callbacks.beforeNodeAdded(t))return null;if(i.idMap.has(t)){const n=document.createElement(t.tagName);return e.insertBefore(n,r),s(n,t,i),i.callbacks.afterNodeAdded(n),n}{const s=document.importNode(t,!0);return e.insertBefore(s,r),i.callbacks.afterNodeAdded(s),s}}const t=function(){function e(e,t,r){let s=e.idMap.get(t),i=e.idMap.get(r);if(!i||!s)return!1;for(const e of s)if(i.has(e))return!0;return!1}function t(e,t){const r=e,s=t;return r.nodeType===s.nodeType&&r.tagName===s.tagName&&(!r.getAttribute?.("id")||r.getAttribute?.("id")===s.getAttribute?.("id"))}return function(r,s,i,n){let o=null,a=s.nextSibling,c=0,l=i;for(;l&&l!=n;){if(t(l,s)){if(e(r,l,s))return l;null===o&&(r.idMap.has(l)||(o=l))}if(null===o&&a&&t(l,a)&&(c++,a=a.nextSibling,c>=2&&(o=void 0)),r.activeElementAndParents.includes(l))break;l=l.nextSibling}return o||null}}();function r(e,t){if(e.idMap.has(t))o(e.pantry,t,null);else{if(!1===e.callbacks.beforeNodeRemoved(t))return;t.parentNode?.removeChild(t),e.callbacks.afterNodeRemoved(t)}}function i(e,t,s){let i=t;for(;i&&i!==s;){let t=i;i=i.nextSibling,r(e,t)}return i}function n(e,t,r,s){const i=s.target.getAttribute?.("id")===t&&s.target||s.target.querySelector(`[id="${t}"]`)||s.pantry.querySelector(`[id="${t}"]`);return function(e,t){const r=e.getAttribute("id");for(;e=e.parentNode;){let s=t.idMap.get(e);s&&(s.delete(r),s.size||t.idMap.delete(e))}}(i,s),o(e,i,r),i}function o(e,t,r){if(e.moveBefore)try{e.moveBefore(t,r)}catch(s){e.insertBefore(t,r)}else e.insertBefore(t,r)}return function(o,a,c,l=null,h=null){a instanceof HTMLTemplateElement&&c instanceof HTMLTemplateElement&&(a=a.content,c=c.content),l||=a.firstChild;for(const r of c.childNodes){if(l&&l!=h){const e=t(o,r,l,h);if(e){e!==l&&i(o,l,e),s(e,r,o),l=e.nextSibling;continue}}if(r instanceof Element){const e=r.getAttribute("id");if(o.persistentIds.has(e)){const t=n(a,e,l,o);s(t,r,o),l=t.nextSibling;continue}}const c=e(a,r,l,o);c&&(l=c.nextSibling)}for(;l&&l!=h;){const e=l;l=l.nextSibling,r(o,e)}}}(),s=function(){function e(e,r,s,i){const n=r[s];if(n!==e[s]){const o=t(s,e,"update",i);o||(e[s]=r[s]),n?o||e.setAttribute(s,""):t(s,e,"remove",i)||e.removeAttribute(s)}}function t(e,t,r,s){return!("value"!==e||!s.ignoreActiveValue||t!==document.activeElement)||!1===s.callbacks.beforeAttributeUpdated(e,t,r)}function s(e,t){return!!t.ignoreActiveValue&&e===document.activeElement&&e!==document.body}return function(n,o,a){return a.ignoreActive&&n===document.activeElement?null:(!1===a.callbacks.beforeNodeMorphed(n,o)||(n instanceof HTMLHeadElement&&a.head.ignore||(n instanceof HTMLHeadElement&&"morph"!==a.head.style?i(n,o,a):(!function(r,i,n){let o=i.nodeType;if(1===o){const o=r,a=i,c=o.attributes,l=a.attributes;for(const e of l)t(e.name,o,"update",n)||o.getAttribute(e.name)!==e.value&&o.setAttribute(e.name,e.value);for(let e=c.length-1;0<=e;e--){const r=c[e];if(r&&!a.hasAttribute(r.name)){if(t(r.name,o,"remove",n))continue;o.removeAttribute(r.name)}}s(o,n)||function(r,s,i){if(r instanceof HTMLInputElement&&s instanceof HTMLInputElement&&"file"!==s.type){let n=s.value,o=r.value;e(r,s,"checked",i),e(r,s,"disabled",i),s.hasAttribute("value")?o!==n&&(t("value",r,"update",i)||(r.setAttribute("value",n),r.value=n)):t("value",r,"remove",i)||(r.value="",r.removeAttribute("value"))}else if(r instanceof HTMLOptionElement&&s instanceof HTMLOptionElement)e(r,s,"selected",i);else if(r instanceof HTMLTextAreaElement&&s instanceof HTMLTextAreaElement){let e=s.value,n=r.value;if(t("value",r,"update",i))return;e!==n&&(r.value=e),r.firstChild&&r.firstChild.nodeValue!==e&&(r.firstChild.nodeValue=e)}}(o,a,n)}8!==o&&3!==o||r.nodeValue!==i.nodeValue&&(r.nodeValue=i.nodeValue)}(n,o,a),s(n,a)||r(a,n,o))),a.callbacks.afterNodeMorphed(n,o)),n)}}();function i(e,t,r){let s=[],i=[],n=[],o=[],a=new Map;for(const e of t.children)a.set(e.outerHTML,e);for(const t of e.children){let e=a.has(t.outerHTML),s=r.head.shouldReAppend(t),c=r.head.shouldPreserve(t);e||c?s?i.push(t):(a.delete(t.outerHTML),n.push(t)):"append"===r.head.style?s&&(i.push(t),o.push(t)):!1!==r.head.shouldRemove(t)&&i.push(t)}o.push(...a.values());let c=[];for(const t of o){let i=document.createRange().createContextualFragment(t.outerHTML).firstChild;if(!1!==r.callbacks.beforeNodeAdded(i)){if("href"in i&&i.href||"src"in i&&i.src){let e,t=new Promise((function(t){e=t}));i.addEventListener("load",(function(){e()})),c.push(t)}e.appendChild(i),r.callbacks.afterNodeAdded(i),s.push(i)}}for(const t of i)!1!==r.callbacks.beforeNodeRemoved(t)&&(e.removeChild(t),r.callbacks.afterNodeRemoved(t));return r.head.afterHeadMorphed(e,{added:s,kept:n,removed:i}),c}const n=function(){function e(){const e=document.createElement("div");return e.hidden=!0,document.body.insertAdjacentElement("afterend",e),e}function r(e){let t=[],r=document.activeElement;if("BODY"!==r?.tagName&&e.contains(r))for(;r&&(t.push(r),r!==e);)r=r.parentElement;return t}function s(e){let t=Array.from(e.querySelectorAll("[id]"));return e.getAttribute?.("id")&&t.push(e),t}function i(e,t,r,s){for(const i of s){const s=i.getAttribute("id");if(t.has(s)){let t=i;for(;t;){let i=e.get(t);if(null==i&&(i=new Set,e.set(t,i)),i.add(s),t===r)break;t=t.parentElement}}}}return function(n,o,a){const{persistentIds:c,idMap:l}=function(e,t){const r=s(e),n=s(t),o=function(e,t){let r=new Set,s=new Map;for(const{id:t,tagName:i}of e)s.has(t)?r.add(t):s.set(t,i);let i=new Set;for(const{id:e,tagName:n}of t)i.has(e)?r.add(e):s.get(e)===n&&i.add(e);for(const e of r)i.delete(e);return i}(r,n);let a=new Map;i(a,o,e,r);const c=t.__idiomorphRoot||t;return i(a,o,c,n),{persistentIds:o,idMap:a}}(n,o),h=function(e){let r=Object.assign({},t);return Object.assign(r,e),r.callbacks=Object.assign({},t.callbacks,e.callbacks),r.head=Object.assign({},t.head,e.head),r}(a),d=h.morphStyle||"outerHTML";if(!["innerHTML","outerHTML"].includes(d))throw`Do not understand how to morph style ${d}`;return{target:n,newContent:o,config:h,morphStyle:d,ignoreActive:h.ignoreActive,ignoreActiveValue:h.ignoreActiveValue,restoreFocus:h.restoreFocus,idMap:l,persistentIds:c,pantry:e(),activeElementAndParents:r(n),callbacks:h.callbacks,head:h.head}}}(),{normalizeElement:o,normalizeParent:a}=function(){const e=new WeakSet;class t{constructor(e){this.originalNode=e,this.realParentNode=e.parentNode,this.previousSibling=e.previousSibling,this.nextSibling=e.nextSibling}get childNodes(){const e=[];let t=this.previousSibling?this.previousSibling.nextSibling:this.realParentNode.firstChild;for(;t&&t!=this.nextSibling;)e.push(t),t=t.nextSibling;return e}querySelectorAll(e){return this.childNodes.reduce(((t,r)=>{if(r instanceof Element){r.matches(e)&&t.push(r);const s=r.querySelectorAll(e);for(let e=0;e<s.length;e++)t.push(s[e])}return t}),[])}insertBefore(e,t){return this.realParentNode.insertBefore(e,t)}moveBefore(e,t){return this.realParentNode.moveBefore(e,t)}get __idiomorphRoot(){return this.originalNode}}return{normalizeElement:function(e){return e instanceof Document?e.documentElement:e},normalizeParent:function r(s){if(null==s)return document.createElement("div");if("string"==typeof s)return r(function(t){let r=new DOMParser,s=t.replace(/<svg(\s[^>]*>|>)([\s\S]*?)<\/svg>/gim,"");if(s.match(/<\/html>/)||s.match(/<\/head>/)||s.match(/<\/body>/)){let i=r.parseFromString(t,"text/html");if(s.match(/<\/html>/))return e.add(i),i;{let t=i.firstChild;return t&&e.add(t),t}}{let s=r.parseFromString("<body><template>"+t+"</template></body>","text/html").body.querySelector("template").content;return e.add(s),s}}(s));if(e.has(s))return s;if(s instanceof Node){if(s.parentNode)return new t(s);{const e=document.createElement("div");return e.append(s),e}}{const e=document.createElement("div");for(const t of[...s])e.append(t);return e}}}}();return{morph:function(e,t,s={}){e=o(e);const c=a(t),l=n(e,c,s),h=function(e,t){if(!e.config.restoreFocus)return t();let r=document.activeElement;if(!(r instanceof HTMLInputElement||r instanceof HTMLTextAreaElement))return t();const{id:s,selectionStart:i,selectionEnd:n}=r,o=t();s&&s!==document.activeElement?.getAttribute("id")&&(r=e.target.querySelector(`[id="${s}"]`),r?.focus());r&&!r.selectionEnd&&n&&r.setSelectionRange(i,n);return o}(l,(()=>function(e,t,r,s){if(e.head.block){const n=t.querySelector("head"),o=r.querySelector("head");if(n&&o){const t=i(n,o,e);return Promise.all(t).then((()=>{const t=Object.assign(e,{head:{block:!1,ignore:!0}});return s(t)}))}}return s(e)}(l,e,c,(t=>"innerHTML"===t.morphStyle?(r(t,e,c),Array.from(e.childNodes)):function(e,t,s){const i=a(t);return r(e,i,s,t,t.nextSibling),Array.from(i.childNodes)}(t,e,c)))));return l.pantry.remove(),h},defaults:t}}();function we(e,t,{callbacks:r,...s}={}){ve.morph(e,t,{...s,callbacks:new Re(r)})}function Se(e,t,r={}){we(e,t.childNodes,{...r,morphStyle:"innerHTML"})}function Ee(e,t){return e instanceof r&&e.shouldReloadWithMorph&&(!t||function(e,t){return t instanceof Element&&"TURBO-FRAME"===t.nodeName&&e.id===t.id&&(!t.getAttribute("src")||O(e.src,t.getAttribute("src")))}(e,t))&&!e.closest("[data-turbo-permanent]")}function ye(e){return e.parentElement.closest("turbo-frame[src][refresh=morph]")}class Re{#h;constructor({beforeNodeMorphed:e}={}){this.#h=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.#h(e,t)){return!n("turbo:before-morph-element",{cancelable:!0,target:e,detail:{currentElement:e,newElement:t}}).defaultPrevented}return!1}};beforeAttributeUpdated=(e,t,r)=>!n("turbo:before-morph-attribute",{cancelable:!0,target:t,detail:{attributeName:e,mutationType:r}}).defaultPrevented;beforeNodeRemoved=e=>this.beforeNodeMorphed(e);afterNodeMorphed=(e,t)=>{e instanceof Element&&n("turbo:morph-element",{target:e,detail:{currentElement:e,newElement:t}})}}class Ae extends be{static renderElement(e,t){n("turbo:before-frame-morph",{target:e,detail:{currentElement:e,newElement:t}}),Se(e,t,{callbacks:{beforeNodeMorphed:(t,r)=>!Ee(t,r)||ye(t)!==e||(t.reload(),!1)}})}async preservingPermanentElements(e){return await e()}}class Te{static animationDuration=300;static get defaultCSS(){return d`
|
|
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 ${Te.animationDuration}ms ease-out,
|
|
22
|
+
opacity ${Te.animationDuration/2}ms ${Te.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*Te.animationDuration)}uninstallProgressElement(){this.progressElement.parentNode&&document.documentElement.removeChild(this.progressElement)}startTrickling(){this.trickleInterval||(this.trickleInterval=window.setInterval(this.trickle,Te.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=Te.defaultCSS;const t=E();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:r}=t,s=r in e?e[r]:{type:Ce(t),tracked:Pe(t),elements:[]};return{...e,[r]:{...s,elements:[...s.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:r,tracked:s,elements:i}=this.detailsByOuterHTML[t];return null!=r||s?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,r)=>{const{elements:[s]}=this.detailsByOuterHTML[r];return function(e,t){const r=e.localName;return"meta"==r&&e.getAttribute("name")==t}(s,e)?s:t}),0)}}function Ce(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 Pe(e){return"reload"==e.getAttribute("data-turbo-track")}class ke extends oe{static fromHTMLString(e=""){return this.fromDocument(h(e))}static fromElement(e){return this.fromDocument(e.ownerDocument)}static fromDocument({documentElement:e,body:t,head:r}){return new this(e,t,new Le(r))}constructor(e,t,r){super(t),this.documentElement=e,this.headSnapshot=r}clone(){const e=this.element.cloneNode(!0),t=this.element.querySelectorAll("select"),r=e.querySelectorAll("select");for(const[e,s]of t.entries()){const t=r[e];for(const e of t.selectedOptions)e.selected=!1;for(const e of s.selectedOptions)t.options[e.index].selected=!0}for(const t of e.querySelectorAll('input[type="password"]'))t.value="";for(const t of e.querySelectorAll("noscript"))t.remove();return new ke(this.documentElement,e,this.headSnapshot)}get lang(){return this.documentElement.getAttribute("lang")}get dir(){return this.documentElement.getAttribute("dir")}get headElement(){return this.headSnapshot.element}get rootLocation(){return M(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("true"===this.getSetting("view-transition")||"same-origin"===this.headSnapshot.getMetaValue("view-transition"))&&!window.matchMedia("(prefers-reduced-motion: reduce)").matches}get refreshMethod(){return this.getSetting("refresh-method")}get refreshScroll(){return this.getSetting("refresh-scroll")}getSetting(e){return this.headSnapshot.getMetaValue(`turbo-${e}`)}}class Me{#d=!1;#u=Promise.resolve();renderChange(e,t){return e&&this.viewTransitionsAvailable&&!this.#d?(this.#d=!0,this.#u=this.#u.then((async()=>{await document.startViewTransition(t).finished}))):this.#u=this.#u.then(t),this.#u}get viewTransitionsAvailable(){return document.startViewTransition}}const Fe={action:"advance",historyChanged:!1,visitCachedSnapshot:()=>{},willRender:!0,updateHistory:!0,shouldCacheSnapshot:!0,acceptsStreamResponse:!1,refresh:{}},Ie="visitStart",qe="requestStart",He="requestEnd",Be="visitEnd",Ne="initialized",Oe="started",xe="canceled",De="failed",Ve="completed",We=0,Ue=-1,je=-2,ze={advance:"forward",restore:"back",replace:"none"};class _e{identifier=u();timingMetrics={};followedRedirect=!1;historyChanged=!1;scrolled=!1;shouldCacheSnapshot=!0;acceptsStreamResponse=!1;snapshotCached=!1;state=Ne;viewTransitioner=new Me;constructor(e,t,r,s={}){this.delegate=e,this.location=t,this.restorationIdentifier=r||u();const{action:i,historyChanged:n,referrer:o,snapshot:a,snapshotHTML:c,response:l,visitCachedSnapshot:h,willRender:d,updateHistory:m,shouldCacheSnapshot:p,acceptsStreamResponse:f,direction:g,refresh:b}={...Fe,...s};this.action=i,this.historyChanged=n,this.referrer=o,this.snapshot=a,this.snapshotHTML=c,this.response=l,this.isPageRefresh=this.view.isPageRefresh(this),this.visitCachedSnapshot=h,this.willRender=d,this.updateHistory=m,this.scrolled=!d,this.shouldCacheSnapshot=p,this.acceptsStreamResponse=f,this.direction=g||ze[i],this.refresh=b}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)}start(){this.state==Ne&&(this.recordTimingMetric(Ie),this.state=Oe,this.adapter.visitStarted(this),this.delegate.visitStarted(this))}cancel(){this.state==Oe&&(this.request&&this.request.cancel(),this.cancelRender(),this.state=xe)}complete(){this.state==Oe&&(this.recordTimingMetric(Be),this.adapter.visitCompleted(this),this.state=Ve,this.followRedirect(),this.followedRedirect||this.delegate.visitCompleted(this))}fail(){this.state==Oe&&(this.state=De,this.adapter.visitFailed(this),this.delegate.visitCompleted(this))}changeHistory(){if(!this.historyChanged&&this.updateHistory){const e=b(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 K(this,z.get,this.location),this.request.perform())}simulateRequest(){this.response&&(this.startRequest(),this.recordResponse(),this.finishRequest())}startRequest(){this.recordTimingMetric(qe),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(He),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=ke.fromHTMLString(t);await this.renderPageSnapshot(e,!1),this.adapter.visitRendered(this),this.complete()}else await this.view.renderError(ke.fromHTMLString(t),this),this.adapter.visitRendered(this),this.fail()}))}}getCachedSnapshot(){const e=this.view.getCachedSnapshotForLocation(this.location)||this.getPreloadedSnapshot();if(e&&(!F(this.location)||e.hasAnchor(F(this.location)))&&("restore"==this.action||e.isPreviewable))return e}getPreloadedSnapshot(){if(this.snapshotHTML)return ke.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.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)}prepareRequest(e){this.acceptsStreamResponse&&e.acceptResponseType(Z.contentType)}requestStarted(){this.startRequest()}requestPreventedHandlingResponse(e,t){}async requestSucceededWithResponse(e,t){const r=await t.responseHTML,{redirected:s,statusCode:i}=t;null==r?this.recordResponse({statusCode:je,redirected:s}):(this.redirectedToLocation=t.redirected?t.location:void 0,this.recordResponse({statusCode:i,responseHTML:r,redirected:s}))}async requestFailedWithResponse(e,t){const r=await t.responseHTML,{redirected:s,statusCode:i}=t;null==r?this.recordResponse({statusCode:je,redirected:s}):this.recordResponse({statusCode:i,responseHTML:r,redirected:s})}requestErrored(e,t){this.recordResponse({statusCode:We,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.scrolled=!0)}scrollToRestoredPosition(){const{scrollPosition:e}=this.restorationData;if(e)return this.view.scrollToPosition(e),!0}scrollToAnchor(){const e=F(this.location);if(null!=e)return this.view.scrollToAnchor(e),!0}recordTimingMetric(e){this.timingMetrics[e]=(new Date).getTime()}getTimingMetrics(){return{...this.timingMetrics}}hasPreloadedResponse(){return"object"==typeof this.response}shouldIssueRequest(){return"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 Ke{progressBar=new Te;constructor(e){this.session=e}visitProposedToLocation(e,t){H(e,this.navigator.rootLocation)?this.navigator.startVisit(e,t?.restorationIdentifier||u(),t):window.location.href=e.toString()}visitStarted(e){this.location=e.location,this.redirectedToLocation=null,e.loadCachedSnapshot(),e.issueRequest()}visitRequestStarted(e){this.progressBar.setValue(0),e.hasCachedSnapshot()||"restore"!=e.action?this.showVisitProgressBarAfterDelay():this.showProgressBar()}visitRequestCompleted(e){e.loadResponse(),e.response.redirected&&(this.redirectedToLocation=e.redirectedToLocation)}visitRequestFailedWithStatusCode(e,t){switch(t){case We:case Ue:case je: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){}linkPrefetchingIsEnabledForLocation(e){return!0}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){n("turbo:reload",{detail:e}),window.location.href=(this.redirectedToLocation||this.location)?.toString()||window.location.href}get navigator(){return this.session.navigator}}class Xe{selector="[data-turbo-temporary]";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)]}}class Ye{constructor(e,t){this.session=e,this.element=t,this.linkInterceptor=new ue(this,t),this.formSubmitObserver=new le(this,t)}start(){this.linkInterceptor.start(),this.formSubmitObserver.start()}stop(){this.linkInterceptor.stop(),this.formSubmitObserver.stop()}shouldInterceptLinkClick(e,t,r){return this.#m(e)}linkClickIntercepted(e,t,r){const s=this.#p(e);s&&s.delegate.linkClickIntercepted(e,t,r)}willSubmitForm(e,t){return null==e.closest("turbo-frame")&&this.#f(e,t)&&this.#m(e,t)}formSubmitted(e,t){const r=this.#p(e,t);r&&r.delegate.formSubmitted(e,t)}#f(e,t){const r=I(e,t),s=this.element.ownerDocument.querySelector('meta[name="turbo-root"]'),i=M(s?.content??"/");return this.#m(e,t)&&H(r,i)}#m(e,t){if(e instanceof HTMLFormElement?this.session.submissionIsNavigatable(e,t):this.session.elementIsNavigatable(e)){const r=this.#p(e,t);return!!r&&r!=e.closest("turbo-frame")}return!1}#p(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 r)return e}}}class Qe{location;restorationIdentifier=u();restorationData={};started=!1;currentIndex=0;constructor(e){this.delegate=e}start(){this.started||(addEventListener("popstate",this.onPopState,!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),this.started=!1)}push(e,t){this.update(history.pushState,e,t)}replace(e,t){this.update(history.replaceState,e,t)}update(e,t,r=u()){e===history.pushState&&++this.currentIndex;const s={turbo:{restorationIdentifier:r,restorationIndex:this.currentIndex}};e.call(history,s,"",t.href),this.location=t,this.restorationIdentifier=r}getRestorationDataForIdentifier(e){return this.restorationData[e]||{}}updateRestorationData(e){const{restorationIdentifier:t}=this,r=this.restorationData[t];this.restorationData[t]={...r,...e}}assumeControlOfScrollRestoration(){this.previousScrollRestoration||(this.previousScrollRestoration=history.scrollRestoration??"auto",history.scrollRestoration="manual")}relinquishControlOfScrollRestoration(){this.previousScrollRestoration&&(history.scrollRestoration=this.previousScrollRestoration,delete this.previousScrollRestoration)}onPopState=e=>{const{turbo:t}=e.state||{};if(this.location=new URL(window.location.href),t){const{restorationIdentifier:e,restorationIndex:r}=t;this.restorationIdentifier=e;const s=r>this.currentIndex?"forward":"back";this.delegate.historyPoppedToLocationWithRestorationIdentifierAndDirection(this.location,e,s),this.currentIndex=r}else this.currentIndex++,this.delegate.historyPoppedWithEmptyState(this.location)}}class Ge{started=!1;#g=null;constructor(e,t){this.delegate=e,this.eventTarget=t}start(){this.started||("loading"===this.eventTarget.readyState?this.eventTarget.addEventListener("DOMContentLoaded",this.#b,{once:!0}):this.#b())}stop(){this.started&&(this.eventTarget.removeEventListener("mouseenter",this.#v,{capture:!0,passive:!0}),this.eventTarget.removeEventListener("mouseleave",this.#w,{capture:!0,passive:!0}),this.eventTarget.removeEventListener("turbo:before-fetch-request",this.#S,!0),this.started=!1)}#b=()=>{this.eventTarget.addEventListener("mouseenter",this.#v,{capture:!0,passive:!0}),this.eventTarget.addEventListener("mouseleave",this.#w,{capture:!0,passive:!0}),this.eventTarget.addEventListener("turbo:before-fetch-request",this.#S,!0),this.started=!0};#v=e=>{if("false"===S("turbo-prefetch"))return;const t=e.target;if(t.matches&&t.matches("a[href]:not([target^=_]):not([download])")&&this.#E(t)){const e=t,r=B(e);if(this.delegate.canPrefetchRequestToLocation(e,r)){this.#g=e;const s=new K(this,z.get,r,new URLSearchParams,t);s.fetchOptions.priority="low",se.putLater(r,s,this.#y)}}};#w=e=>{e.target===this.#g&&this.#R()};#R=()=>{se.clear(),this.#g=null};#S=e=>{if("FORM"!==e.target.tagName&&"GET"===e.detail.fetchOptions.method){const t=se.get(e.detail.url);t&&(e.detail.fetchRequest=t),se.clear()}};prepareRequest(e){const t=e.target;e.headers["X-Sec-Purpose"]="prefetch";const r=t.closest("turbo-frame"),s=t.getAttribute("data-turbo-frame")||r?.getAttribute("target")||r?.id;s&&"_top"!==s&&(e.headers["Turbo-Frame"]=s)}requestSucceededWithResponse(){}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}get#y(){return Number(S("turbo-prefetch-cache-time"))||re}#E(e){return!!e.getAttribute("href")&&(!Je(e)&&(!Ze(e)&&(!et(e)&&(!tt(e)&&!st(e)))))}}const Je=e=>e.origin!==document.location.origin||!["http:","https:"].includes(e.protocol)||e.hasAttribute("target"),Ze=e=>e.pathname+e.search===document.location.pathname+document.location.search||e.href.startsWith("#"),et=e=>{if("false"===e.getAttribute("data-turbo-prefetch"))return!0;if("false"===e.getAttribute("data-turbo"))return!0;const t=y(e,"[data-turbo-prefetch]");return!(!t||"false"!==t.getAttribute("data-turbo-prefetch"))},tt=e=>{const t=e.getAttribute("data-turbo-method");return!(!t||"get"===t.toLowerCase())||(!!rt(e)||(!!e.hasAttribute("data-turbo-confirm")||!!e.hasAttribute("data-turbo-stream")))},rt=e=>e.hasAttribute("data-remote")||e.hasAttribute("data-behavior")||e.hasAttribute("data-confirm")||e.hasAttribute("data-method"),st=e=>n("turbo:before-prefetch",{target:e,cancelable:!0}).defaultPrevented;class it{constructor(e){this.delegate=e}proposeVisit(e,t={}){this.delegate.allowsVisitingLocationWithAction(e,t.action)&&this.delegate.visitProposedToLocation(e,t)}startVisit(e,t,r={}){this.stop(),this.currentVisit=new _e(this,M(e),t,{referrer:this.location,...r}),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 r=await t.responseHTML;if(r){const s=e.isSafe;s||this.view.clearSnapshotCache();const{statusCode:i,redirected:n}=t,o={action:this.#A(e,t),shouldCacheSnapshot:s,response:{statusCode:i,responseHTML:r,redirected:n}};this.proposeVisit(t.location,o)}}}async formSubmissionFailedWithResponse(e,t){const r=await t.responseHTML;if(r){const e=ke.fromHTMLString(r);t.serverError?await this.view.renderError(e,this.currentVisit):await this.view.renderPage(e,!1,!0,this.currentVisit),"preserve"!==e.refreshScroll&&this.view.scrollToTop(),this.view.clearSnapshotCache()}}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished(e){"function"==typeof this.adapter.formSubmissionFinished&&this.adapter.formSubmissionFinished(e)}linkPrefetchingIsEnabledForLocation(e){return"function"!=typeof this.adapter.linkPrefetchingIsEnabledForLocation||this.adapter.linkPrefetchingIsEnabledForLocation(e)}visitStarted(e){this.delegate.visitStarted(e)}visitCompleted(e){this.delegate.visitCompleted(e),delete this.currentVisit}locationWithActionIsSamePage(e,t){return!1}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}#A(e,t){const{submitter:r,formElement:s}=e;return v(r,s)||this.#T(t)}#T(e){return e.redirected&&e.location.href===this.location?.href?"replace":"advance"}}const nt=0,ot=1,at=2,ct=3;class lt{stage=nt;started=!1;constructor(e){this.delegate=e}start(){this.started||(this.stage==nt&&(this.stage=ot),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==ot&&(this.stage=at,this.delegate.pageBecameInteractive())}pageIsComplete(){this.pageIsInteractive(),this.stage==at&&(this.stage=ct,this.delegate.pageLoaded())}pageWillUnload=()=>{this.delegate.pageWillUnload()};get readyState(){return document.readyState}}class ht{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 dt{render({fragment:e}){fe.preservingPermanentElements(this,function(e){const t=ce(document.documentElement),r={};for(const s of t){const{id:t}=s;for(const i of e.querySelectorAll("turbo-stream")){const e=ae(i.templateElement.content,t);e&&(r[t]=[s,e])}}return r}(e),(()=>{!async function(e,t){const r=`turbo-stream-autofocus-${u()}`,s=e.querySelectorAll("turbo-stream"),i=function(e){for(const t of e){const e=A(t.templateElement.content);if(e)return e}return null}(s);let n=null;i&&(n=i.id?i.id:r,i.id=n);t(),await a();if((null==document.activeElement||document.activeElement==document.body)&&n){const e=document.getElementById(n);R(e)&&e.focus(),e&&e.id==r&&e.removeAttribute("id")}}(e,(()=>{!async function(e){const[t,r]=await async function(e,t){const r=t();return e(),await c(),[r,t()]}(e,(()=>document.activeElement)),s=t&&t.id;if(s){const e=document.getElementById(s);R(e)&&e!=r&&e.focus()}}((()=>{document.documentElement.appendChild(e)}))}))}))}enteringBardo(e,t){t.replaceWith(e.cloneNode(!0))}leavingBardo(){}}class ut{sources=new Set;#L=!1;constructor(e){this.delegate=e}start(){this.#L||(this.#L=!0,addEventListener("turbo:before-fetch-response",this.inspectFetchResponse,!1))}stop(){this.#L&&(this.#L=!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 D)return t}(e);t&&function(e){const t=e.contentType??"";return t.startsWith(Z.contentType)}(t)&&(e.preventDefault(),this.receiveMessageResponse(t))};receiveMessageEvent=e=>{this.#L&&"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(Z.wrap(e))}}class mt extends ge{static renderElement(e,t){const{documentElement:r,body:s}=document;r.replaceChild(t,s)}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 r=i(e);t.replaceChild(r,e)}}}get newHead(){return this.newSnapshot.headSnapshot.element}get scriptElements(){return document.documentElement.querySelectorAll("script")}}class pt 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.#C(),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}#C(){const{documentElement:e}=this.currentSnapshot,{dir:t,lang:r}=this.newSnapshot;r?e.setAttribute("lang",r):e.removeAttribute("lang"),t?e.setAttribute("dir",t):e.removeAttribute("dir")}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(g(t)),document.head.appendChild(t);await Promise.all(e)}copyNewHeadScriptElements(){for(const e of this.newHeadScriptElements)document.head.appendChild(i(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[r,s]of t.entries()){if("TITLE"==e.tagName){if("TITLE"!=s.tagName)continue;if(e.innerHTML==s.innerHTML)return t.splice(r,1),!0}if(s.isEqualNode(e))return t.splice(r,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.removeNoscriptElements(),this.activateNewBodyScriptElements()}removeNoscriptElements(){for(const e of this.newElement.querySelectorAll("noscript"))e.remove()}activateNewBodyScriptElements(){for(const e of this.newBodyScriptElements){const t=i(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 ft extends pt{static renderElement(e,t){we(e,t,{callbacks:{beforeNodeMorphed:(e,t)=>!(Ee(e,t)&&!ye(e))||(e.reload(),!1)}}),n("turbo:morph",{detail:{currentElement:e,newElement:t}})}async preservingPermanentElements(e){return await e()}get renderMethod(){return"morph"}get shouldAutofocus(){return!1}}class gt extends te{constructor(e){super(e,N)}get snapshots(){return this.entries}}class bt extends he{snapshotCache=new gt(10);lastRenderedLocation=new URL(location.href);forceReloaded=!1;shouldTransitionTo(e){return this.snapshot.prefersViewTransitions&&e.prefersViewTransitions}renderPage(e,t=!1,r=!0,s){const i=new(this.isPageRefresh(s)&&"morph"===(s?.refresh?.method||this.snapshot.refreshMethod)?ft:pt)(this.snapshot,e,t,r);return i.shouldRender?s?.changeHistory():this.forceReloaded=!0,this.render(i)}renderError(e,t){t?.changeHistory();const r=new mt(this.snapshot,e,!1);return this.render(r)}clearSnapshotCache(){this.snapshotCache.clear()}async cacheSnapshot(e=this.snapshot){if(e.isCacheable){this.delegate.viewWillCacheSnapshot();const{lastRenderedLocation:t}=this;await l();const r=e.clone();return this.snapshotCache.put(t,r),r}}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)&&"preserve"===(e?.refresh?.scroll||this.snapshot.refreshScroll)}get snapshot(){return ke.fromElement(this.element)}}class vt{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 r=new K(this,z.get,t,new URLSearchParams,e);await r.perform()}prepareRequest(e){e.headers["X-Sec-Purpose"]="prefetch"}async requestSucceededWithResponse(e,t){try{const r=await t.responseHTML,s=ke.fromHTMLString(r);this.snapshotCache.put(e.url,s)}catch(e){}}requestStarted(e){}requestErrored(e){}requestFinished(e){}requestPreventedHandlingResponse(e,t){}requestFailedWithResponse(e,t){}#P=()=>{this.preloadOnLoadLinksForView(document.body)}}class wt{constructor(e){this.session=e}clear(){this.session.clearCache()}resetCacheControl(){this.#k("")}exemptPageFromCache(){this.#k("no-cache")}exemptPageFromPreview(){this.#k("no-preview")}#k(e){!function(e,t){let r=w(e);r||(r=document.createElement("meta"),r.setAttribute("name",e),document.head.appendChild(r)),r.setAttribute("content",t)}("turbo-cache-control",e)}}function St(e){Object.defineProperties(e,Et)}const Et={absoluteURL:{get(){return this.toString()}}},yt=new class{navigator=new it(this);history=new Qe(this);view=new bt(this,document.documentElement);adapter=new Ke(this);pageObserver=new lt(this);cacheObserver=new Xe;linkPrefetchObserver=new Ge(this,document);linkClickObserver=new me(this,window);formSubmitObserver=new le(this,document);scrollObserver=new ht(this);streamObserver=new ut(this);formLinkClickObserver=new pe(this,document.documentElement);frameRedirector=new Ye(this,document.documentElement);streamMessageRenderer=new dt;cache=new wt(this);enabled=!0;started=!1;#M=150;constructor(e){this.recentRequests=e,this.preloader=new vt(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 r){const r=t.action||v(s);s.delegate.proposeVisitIfNavigatedWithAction(s,r),s.src=e.toString()}else this.navigator.proposeVisit(M(e),t)}refresh(e,t={}){t="string"==typeof t?{requestId:t}:t;const{method:r,requestId:s,scroll:i}=t,n=s&&this.recentRequests.has(s),o=e===document.baseURI;n||this.navigator.currentVisit||!o||this.visit(e,{action:"replace",shouldCacheSnapshot:!1,refresh:{method:r,scroll:i}})}connectStreamSource(e){this.streamObserver.connectStreamSource(e)}disconnectStreamSource(e){this.streamObserver.disconnectStreamSource(e)}renderStreamMessage(e){this.streamMessageRenderer.render(Z.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){k.drive.progressBarDelay=e}get progressBarDelay(){return k.drive.progressBarDelay}set drive(e){k.drive.enabled=e}get drive(){return k.drive.enabled}set formMode(e){k.forms.mode=e}get formMode(){return k.forms.mode}get location(){return this.history.location}get restorationIdentifier(){return this.history.restorationIdentifier}get pageRefreshDebouncePeriod(){return this.#M}set pageRefreshDebouncePeriod(e){this.refresh=function(e,t){let r=null;return(...s)=>{clearTimeout(r),r=setTimeout((()=>e.apply(this,s)),t)}}(this.debouncedRefresh.bind(this),e),this.#M=e}shouldPreloadLink(e){const t=e.hasAttribute("data-turbo-method"),s=e.hasAttribute("data-turbo-stream"),i=e.getAttribute("data-turbo-frame"),n="_top"==i?null:document.getElementById(i)||y(e,"turbo-frame:not([disabled])");if(t||s||n instanceof r)return!1;{const t=new URL(e.href);return this.elementIsNavigatable(e)&&H(t,this.snapshot.rootLocation)}}historyPoppedToLocationWithRestorationIdentifierAndDirection(e,t,r){this.enabled?this.navigator.startVisit(e,t,{action:"restore",historyChanged:!0,direction:r}):this.adapter.pageInvalidated({reason:"turbo_disabled"})}historyPoppedWithEmptyState(e){this.history.replace(e),this.view.lastRenderedLocation=e,this.view.cacheSnapshot()}scrollPositionChanged(e){this.history.updateRestorationData({scrollPosition:e})}willSubmitFormLinkToLocation(e,t){return this.elementIsNavigatable(e)&&H(t,this.snapshot.rootLocation)}submittedFormLinkToLocation(){}canPrefetchRequestToLocation(e,t){return this.elementIsNavigatable(e)&&H(t,this.snapshot.rootLocation)&&this.navigator.linkPrefetchingIsEnabledForLocation(t)}willFollowLinkToLocation(e,t,r){return this.elementIsNavigatable(e)&&H(t,this.snapshot.rootLocation)&&this.applicationAllowsFollowingLinkToLocation(e,t,r)}followedLinkToLocation(e,t){const r=this.getActionForLink(e),s=e.hasAttribute("data-turbo-stream");this.visit(t.href,{action:r,acceptsStreamResponse:s})}allowsVisitingLocationWithAction(e,t){return this.applicationAllowsVisitingLocation(e)}visitProposedToLocation(e,t){St(e),this.adapter.visitProposedToLocation(e,t)}visitStarted(e){e.acceptsStreamResponse||(p(document.documentElement),this.view.markVisitDirection(e.direction)),St(e.location),this.notifyApplicationAfterVisitingLocation(e.location,e.action)}visitCompleted(e){this.view.unmarkVisitDirection(),f(document.documentElement),this.notifyApplicationAfterPageLoad(e.getTimingMetrics())}willSubmitForm(e,t){const r=I(e,t);return this.submissionIsNavigatable(e,t)&&H(M(r),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.notifyApplicationBeforeCachingSnapshot()}allowsImmediateRender({element:e},t){const r=this.notifyApplicationBeforeRender(e,t),{defaultPrevented:s,detail:{render:i}}=r;return this.view.renderer&&i&&(this.view.renderer.renderElement=i),!s}viewRenderedSnapshot(e,t,r){this.view.lastRenderedLocation=this.history.location,this.notifyApplicationAfterRender(r)}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,r){return!this.notifyApplicationAfterClickingLinkToLocation(e,t,r).defaultPrevented}applicationAllowsVisitingLocation(e){return!this.notifyApplicationBeforeVisitingLocation(e).defaultPrevented}notifyApplicationAfterClickingLinkToLocation(e,t,r){return n("turbo:click",{target:e,detail:{url:t.href,originalEvent:r},cancelable:!0})}notifyApplicationBeforeVisitingLocation(e){return n("turbo:before-visit",{detail:{url:e.href},cancelable:!0})}notifyApplicationAfterVisitingLocation(e,t){return n("turbo:visit",{detail:{url:e.href,action:t}})}notifyApplicationBeforeCachingSnapshot(){return n("turbo:before-cache")}notifyApplicationBeforeRender(e,t){return n("turbo:before-render",{detail:{newBody:e,...t},cancelable:!0})}notifyApplicationAfterRender(e){return n("turbo:render",{detail:{renderMethod:e}})}notifyApplicationAfterPageLoad(e={}){return n("turbo:load",{detail:{url:this.location.href,timing:e}})}notifyApplicationAfterFrameLoad(e){return n("turbo:frame-load",{target:e})}notifyApplicationAfterFrameRender(e,t){return n("turbo:frame-render",{detail:{fetchResponse:e},target:t,cancelable:!0})}submissionIsNavigatable(e,t){if("off"==k.forms.mode)return!1;{const r=!t||this.elementIsNavigatable(t);return"optin"==k.forms.mode?r&&null!=e.closest('[data-turbo="true"]'):r&&this.elementIsNavigatable(e)}}elementIsNavigatable(e){const t=y(e,"[data-turbo]"),r=y(e,"turbo-frame");return k.drive.enabled||r?!t||"false"!=t.getAttribute("data-turbo"):!!t&&"true"==t.getAttribute("data-turbo")}getActionForLink(e){return v(e)||"advance"}get snapshot(){return this.view.snapshot}}(W),{cache:Rt,navigator:At}=yt;function Tt(){yt.start()}function Lt(e){yt.registerAdapter(e)}function Ct(e,t){yt.visit(e,t)}function Pt(e){yt.connectStreamSource(e)}function kt(e){yt.disconnectStreamSource(e)}function Mt(e){yt.renderStreamMessage(e)}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.`"),k.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.`"),k.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.`"),k.forms.mode=e}function Ht(e,t){ft.renderElement(e,t)}function Bt(e,t){Ae.renderElement(e,t)}var Nt=Object.freeze({__proto__:null,PageRenderer:pt,PageSnapshot:ke,FrameRenderer:be,fetch:U,config:k,session:yt,cache:Rt,navigator:At,start:Tt,registerAdapter:Lt,visit:Ct,connectStreamSource:Pt,disconnectStreamSource:kt,renderStreamMessage:Mt,setProgressBarDelay:Ft,setConfirmMethod:It,setFormMode:qt,morphBodyElements:Ht,morphTurboFrameElements:Bt,morphChildren:Se,morphElements:we});class Ot extends Error{}function xt(e,t){if(e){const s=e.getAttribute("src");if(null!=s&&null!=t&&O(s,t))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 r)return e.connectedCallback(),e.disconnectedCallback(),e}}const Dt={after(){this.removeDuplicateTargetSiblings(),this.targetElements.forEach((e=>e.parentElement?.insertBefore(this.templateContent,e.nextSibling)))},append(){this.removeDuplicateTargetChildren(),this.targetElements.forEach((e=>e.append(this.templateContent)))},before(){this.removeDuplicateTargetSiblings(),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?we(t,this.templateContent):t.replaceWith(this.templateContent)}))},update(){const e=this.getAttribute("method");this.targetElements.forEach((t=>{"morph"===e?Se(t,this.templateContent):(t.innerHTML="",t.append(this.templateContent))}))},refresh(){const e=this.getAttribute("method"),t=this.requestId,r=this.getAttribute("scroll");yt.refresh(this.baseURI,{method:e,requestId:t,scroll:r})}};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 a(),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.getAttribute("id"))),t=[...this.templateContent?.children||[]].filter((e=>!!e.getAttribute("id"))).map((e=>e.getAttribute("id")));return e.filter((e=>t.includes(e.getAttribute("id"))))}removeDuplicateTargetSiblings(){this.duplicateSiblings.forEach((e=>e.remove()))}get duplicateSiblings(){const e=this.targetElements.flatMap((e=>[...e.parentElement.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=Dt[this.action];if(e)return e;this.#F("unknown action")}this.#F("action attribute is missing")}get targetElements(){return this.target?this.targetElementsById:this.targets?this.targetElementsByQuery:void this.#F("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.#F("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")}#F(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 Wt 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(),kt(this.streamSource))}get src(){return this.getAttribute("src")||""}}r.delegateConstructor=class{fetchResponseLoaded=e=>Promise.resolve();#I=null;#q=()=>{};#H=!1;#B=!1;#N=new Set;#O=!1;action=null;constructor(e){this.element=e,this.view=new de(this,this.element),this.appearanceObserver=new J(this,this.element),this.formLinkClickObserver=new pe(this,this.element),this.linkInterceptor=new ue(this,this.element),this.restorationIdentifier=u(),this.formSubmitObserver=new le(this,this.element)}connect(){this.#H||(this.#H=!0,this.loadingStyle==t.lazy?this.appearanceObserver.start():this.#x(),this.formLinkClickObserver.start(),this.linkInterceptor.start(),this.formSubmitObserver.start())}disconnect(){this.#H&&(this.#H=!1,this.appearanceObserver.stop(),this.formLinkClickObserver.stop(),this.linkInterceptor.stop(),this.formSubmitObserver.stop(),this.element.hasAttribute("recurse")||this.#I?.cancel())}disabledChanged(){this.disabled?this.#I?.cancel():this.loadingStyle==t.eager&&this.#x()}sourceURLChanged(){this.#D("src")||(this.sourceURL||this.#I?.cancel(),this.element.isConnected&&(this.complete=!1),(this.loadingStyle==t.eager||this.#B)&&this.#x())}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==t.lazy?this.appearanceObserver.start():(this.appearanceObserver.stop(),this.#x())}async#x(){this.enabled&&this.isActive&&!this.complete&&this.sourceURL&&(this.element.loaded=this.#V(M(this.sourceURL)),this.appearanceObserver.stop(),await this.element.loaded,this.#B=!0)}async loadResponse(e){(e.redirected||e.succeeded&&e.isHTML)&&(this.sourceURL=e.response.url);try{const t=await e.responseHTML;if(t){const r=h(t);ke.fromDocument(r).isVisitable?await this.#W(e,r):await this.#U(e)}}finally{this.#O=!1,this.fetchResponseLoaded=()=>Promise.resolve()}}elementAppearedInViewport(e){this.proposeVisitIfNavigatedWithAction(e,v(e)),this.#x()}willSubmitFormLinkToLocation(e){return this.#j(e)}submittedFormLinkToLocation(e,t,r){const s=this.#p(e);s&&r.setAttribute("data-turbo-frame",s.id)}shouldInterceptLinkClick(e,t,r){return this.#j(e)}linkClickIntercepted(e,t){this.#z(e,t)}willSubmitForm(e,t){return e.closest("turbo-frame")==this.element&&this.#j(e,t)}formSubmitted(e,t){this.formSubmission&&this.formSubmission.stop(),this.formSubmission=new ne(this,e,t);const{fetchRequest:r}=this.formSubmission,s=this.#p(e,t);this.prepareRequest(r,s),this.formSubmission.start()}prepareRequest(e,t=this){e.headers["Turbo-Frame"]=t.id,this.currentNavigationElement?.hasAttribute("data-turbo-stream")&&e.acceptResponseType(Z.contentType)}requestStarted(e){p(this.element)}requestPreventedHandlingResponse(e,t){this.#q()}async requestSucceededWithResponse(e,t){await this.loadResponse(t),this.#q()}async requestFailedWithResponse(e,t){await this.loadResponse(t),this.#q()}requestErrored(e,t){console.error(t),this.#q()}requestFinished(e){f(this.element)}formSubmissionStarted({formElement:e}){p(e,this.#p(e))}formSubmissionSucceededWithResponse(e,t){const r=this.#p(e.formElement,e.submitter);r.delegate.proposeVisitIfNavigatedWithAction(r,v(e.submitter,e.formElement,r)),r.delegate.loadResponse(t),e.isSafe||yt.clearCache()}formSubmissionFailedWithResponse(e,t){this.element.delegate.loadResponse(t),yt.clearCache()}formSubmissionErrored(e,t){console.error(t)}formSubmissionFinished({formElement:e}){f(e,this.#p(e))}allowsImmediateRender({element:e},t){const r=n("turbo:before-frame-render",{target:this.element,detail:{newFrame:e,...t},cancelable:!0}),{defaultPrevented:s,detail:{render:i}}=r;return this.view.renderer&&i&&(this.view.renderer.renderElement=i),!s}viewRenderedSnapshot(e,t,r){}preloadOnLoadLinksForView(e){yt.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#W(e,t){const r=await this.extractForeignFrameElement(t.body),s=this.#O?Ae:be;if(r){const t=new oe(r),i=new s(this,this.view.snapshot,t,!1,!1);this.view.renderPromise&&await this.view.renderPromise,this.changeHistory(),await this.view.render(i),this.complete=!0,yt.frameRendered(e,this.element),yt.frameLoaded(this.element),await this.fetchResponseLoaded(e)}else this.#_(e)&&this.#$(e)}async#V(e){const t=new K(this,z.get,e,new URLSearchParams,this.element);return this.#I?.cancel(),this.#I=t,new Promise((e=>{this.#q=()=>{this.#q=()=>{},this.#I=null,e()},t.perform()}))}#z(e,t,r){const s=this.#p(e,r);s.delegate.proposeVisitIfNavigatedWithAction(s,v(r,e,s)),this.#K(e,(()=>{s.src=t}))}proposeVisitIfNavigatedWithAction(e,t=null){if(this.action=t,this.action){const t=ke.fromElement(e).clone(),{visitCachedSnapshot:r}=e.delegate;e.delegate.fetchResponseLoaded=async s=>{if(e.src){const{statusCode:i,redirected:n}=s,o={response:{statusCode:i,redirected:n,responseHTML:await s.responseHTML},visitCachedSnapshot:r,willRender:!1,updateHistory:!1,restorationIdentifier:this.restorationIdentifier,snapshot:t};this.action&&(o.action=this.action),yt.visit(e.src,o)}}}}changeHistory(){if(this.action){const e=b(this.action);yt.history.update(e,M(this.element.src||""),this.restorationIdentifier)}}async#U(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!n("turbo:frame-missing",{target:this.element,detail:{response:t,visit:async(e,t)=>{e instanceof Response?this.#X(e):yt.visit(e,t)}},cancelable:!0}).defaultPrevented}#$(e){this.view.missing(),this.#Y(e)}#Y(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 Ot(t)}async#X(e){const t=new D(e),r=await t.responseHTML,{location:s,redirected:i,statusCode:n}=t;return yt.visit(s,{response:{redirected:i,statusCode:n,responseHTML:r}})}#p(e,t){const s=m("data-turbo-frame",t,e)||this.element.getAttribute("target"),i=this.#Q(s);return i instanceof r?i:this.element}async extractForeignFrameElement(e){let t;const s=CSS.escape(this.id);try{if(t=xt(e.querySelector(`turbo-frame#${s}`),this.sourceURL),t)return t;if(t=xt(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 r}return null}#G(e,t){return H(M(I(e,t)),this.rootLocation)}#j(e,t){const r=m("data-turbo-frame",t,e)||this.element.getAttribute("target");if(e instanceof HTMLFormElement&&!this.#G(e,t))return!1;if(!this.enabled||"_top"==r)return!1;if(r){const e=this.#Q(r);if(e)return!e.disabled;if("_parent"==r)return!1}return!!yt.elementIsNavigatable(e)&&!(t&&!yt.elementIsNavigatable(t))}get id(){return this.element.id}get disabled(){return this.element.disabled}get enabled(){return!this.disabled}get sourceURL(){if(this.element.src)return this.element.src}set sourceURL(e){this.#J("src",(()=>{this.element.src=e??null}))}get loadingStyle(){return this.element.loading}get isLoading(){return void 0!==this.formSubmission||void 0!==this.#q()}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.#H}get rootLocation(){const e=this.element.ownerDocument.querySelector('meta[name="turbo-root"]');return M(e?.content??"/")}#D(e){return this.#N.has(e)}#J(e,t){this.#N.add(e),t(),this.#N.delete(e)}#K(e,t){this.currentNavigationElement=e,t(),delete this.currentNavigationElement}#Q(e){if(null!=e){const t="_parent"===e?this.element.parentElement.closest("turbo-frame"):document.getElementById(e);if(t instanceof r)return t}}},void 0===customElements.get("turbo-frame")&&customElements.define("turbo-frame",r),void 0===customElements.get("turbo-stream")&&customElements.define("turbo-stream",Vt),void 0===customElements.get("turbo-stream-source")&&customElements.define("turbo-stream-source",Wt),(()=>{const e=document.currentScript;if(!e)return;if(e.hasAttribute("data-turbo-suppress-warning"))return;let t=e.parentElement;for(;t;){if(t==document.body)return console.warn(d`
|
|
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);t=t.parentElement}})(),window.Turbo={...Nt,StreamActions:Dt},Tt(),e.FetchEnctype=$,e.FetchMethod=z,e.FetchRequest=K,e.FetchResponse=D,e.FrameElement=r,e.FrameLoadingStyle=t,e.FrameRenderer=be,e.PageRenderer=pt,e.PageSnapshot=ke,e.StreamActions=Dt,e.StreamElement=Vt,e.StreamSourceElement=Wt,e.cache=Rt,e.config=k,e.connectStreamSource=Pt,e.disconnectStreamSource=kt,e.fetch=U,e.fetchEnctypeFromString=_,e.fetchMethodFromString=j,e.isSafe=X,e.morphBodyElements=Ht,e.morphChildren=Se,e.morphElements=we,e.morphTurboFrameElements=Bt,e.navigator=At,e.registerAdapter=Lt,e.renderStreamMessage=Mt,e.session=yt,e.setConfirmMethod=It,e.setFormMode=qt,e.setProgressBarDelay=Ft,e.start=Tt,e.visit=Ct,Object.defineProperty(e,"__esModule",{value:!0})}));
|
|
@@ -2,17 +2,30 @@ module ChronoForge
|
|
|
2
2
|
module Dashboard
|
|
3
3
|
class ActionsController < BaseController
|
|
4
4
|
rescue_from ChronoForge::Executor::WorkflowNotRetryableError do |e|
|
|
5
|
-
redirect_to workflow_path(params[:id]), alert: e.message
|
|
5
|
+
redirect_to workflow_path(params[:id]), alert: e.message, status: :see_other
|
|
6
6
|
end
|
|
7
7
|
|
|
8
8
|
def retry
|
|
9
9
|
workflow.retry_later
|
|
10
|
-
redirect_to workflow_path(workflow), notice: "Re-enqueued #{workflow.key}."
|
|
10
|
+
redirect_to workflow_path(workflow), notice: "Re-enqueued #{workflow.key}.", status: :see_other
|
|
11
11
|
end
|
|
12
12
|
|
|
13
13
|
def unlock
|
|
14
14
|
workflow.update!(locked_at: nil, locked_by: nil, state: :idle)
|
|
15
|
-
redirect_to workflow_path(workflow), notice: "Unlocked #{workflow.key}."
|
|
15
|
+
redirect_to workflow_path(workflow), notice: "Unlocked #{workflow.key}.", status: :see_other
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Recover a workflow stranded in :running — the single-workflow form of the
|
|
19
|
+
# stalled-workflow reaper (Workflow.reap_stalled). Re-enqueues it so the
|
|
20
|
+
# executor's acquire_lock steals the stale lock and replays completed steps
|
|
21
|
+
# as no-ops. This is the right fix for an overdue/stuck run: unlike Force
|
|
22
|
+
# unlock (which idles the row and leaves nothing to wake it), reap actually
|
|
23
|
+
# re-drives the workflow. Safe and idempotent — a duplicate just loses the
|
|
24
|
+
# acquire_lock race and no-ops.
|
|
25
|
+
def reap
|
|
26
|
+
return redirect_to(workflow_path(workflow), alert: "Only running workflows can be reaped.", status: :see_other) unless workflow.running?
|
|
27
|
+
workflow.job_klass.perform_later(workflow.key, **workflow.kwargs.symbolize_keys)
|
|
28
|
+
redirect_to workflow_path(workflow), notice: "Reaped #{workflow.key} — re-enqueued to steal its stale lock and replay.", status: :see_other
|
|
16
29
|
end
|
|
17
30
|
|
|
18
31
|
# Re-enqueue an idle (parked) workflow so the executor picks it up again.
|
|
@@ -20,34 +33,41 @@ module ChronoForge
|
|
|
20
33
|
# poller job was lost, or a continue_if whose event has since arrived: the
|
|
21
34
|
# replay re-checks the condition and re-arms the poll if still unmet.
|
|
22
35
|
def resume
|
|
23
|
-
return redirect_to(workflow_path(workflow), alert: "Only idle workflows can be resumed.") unless workflow.idle?
|
|
36
|
+
return redirect_to(workflow_path(workflow), alert: "Only idle workflows can be resumed.", status: :see_other) unless workflow.idle?
|
|
24
37
|
workflow.job_klass.perform_later(workflow.key)
|
|
25
|
-
redirect_to workflow_path(workflow), notice: "Re-enqueued #{workflow.key}."
|
|
38
|
+
redirect_to workflow_path(workflow), notice: "Re-enqueued #{workflow.key}.", status: :see_other
|
|
26
39
|
end
|
|
27
40
|
|
|
28
|
-
#
|
|
29
|
-
#
|
|
30
|
-
|
|
41
|
+
# Reap every stranded workflow (running with a stale lock) in the
|
|
42
|
+
# background — the bulk form of #reap, delegating to Workflow.reap_stalled.
|
|
43
|
+
# The count is taken up front for the flash; the job does the sweep.
|
|
44
|
+
def bulk_reap
|
|
45
|
+
cutoff = ChronoForge.config.reap_stale_after.ago
|
|
46
|
+
n = ChronoForge::Workflow.where(state: ChronoForge::Workflow.states[:running]).where(locked_at: ...cutoff).count
|
|
47
|
+
return redirect_to(stranded_index_path, notice: "No stranded workflows to reap.", status: :see_other) if n.zero?
|
|
48
|
+
BulkReapJob.perform_later
|
|
49
|
+
redirect_to stranded_index_path, notice: "Reaping #{n} stranded workflow(s) in the background.", status: :see_other
|
|
50
|
+
end
|
|
31
51
|
|
|
52
|
+
# Retry every blocked (failed/stalled) workflow. The fan-out runs in a
|
|
53
|
+
# background job so the request returns fast even with a huge backlog; the
|
|
54
|
+
# count is taken up front for the flash (BulkRetryJob does the enqueueing).
|
|
32
55
|
def bulk_retry
|
|
33
|
-
n =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
end
|
|
38
|
-
redirect_to workflows_path, notice: "Re-enqueued #{n} workflow(s)."
|
|
56
|
+
n = BulkRetryJob.retryable.count
|
|
57
|
+
return redirect_to(workflows_path, notice: "No blocked workflows to retry.", status: :see_other) if n.zero?
|
|
58
|
+
BulkRetryJob.perform_later
|
|
59
|
+
redirect_to workflows_path, notice: "Retrying #{n} blocked workflow(s) in the background.", status: :see_other
|
|
39
60
|
end
|
|
40
61
|
|
|
41
|
-
# Retry every blocked (failed/stalled) child of one branch.
|
|
62
|
+
# Retry every blocked (failed/stalled) child of one branch, in the background.
|
|
42
63
|
def bulk_retry_branch
|
|
43
64
|
parent = ChronoForge::Workflow.find(params[:workflow_id])
|
|
44
65
|
branch_log = parent.execution_logs.find(params[:id])
|
|
45
|
-
n =
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
redirect_to workflow_branch_path(parent, branch_log), notice: "Re-enqueued #{n} child workflow(s)."
|
|
66
|
+
n = BulkRetryJob.retryable(branch_log).count
|
|
67
|
+
redirect = ->(msg) { redirect_to workflow_branch_path(parent, branch_log), notice: msg, status: :see_other }
|
|
68
|
+
return redirect.call("No blocked child workflows to retry.") if n.zero?
|
|
69
|
+
BulkRetryJob.perform_later(branch_log.id)
|
|
70
|
+
redirect.call("Retrying #{n} child workflow(s) in the background.")
|
|
51
71
|
end
|
|
52
72
|
|
|
53
73
|
private
|
|
@@ -7,6 +7,7 @@ module ChronoForge
|
|
|
7
7
|
TYPES = {
|
|
8
8
|
"dashboard.css" => "text/css",
|
|
9
9
|
"dashboard.js" => "application/javascript",
|
|
10
|
+
"turbo.min.js" => "application/javascript",
|
|
10
11
|
"cytoscape.min.js" => "application/javascript",
|
|
11
12
|
"dagre.min.js" => "application/javascript",
|
|
12
13
|
"cytoscape-dagre.js" => "application/javascript",
|
|
@@ -6,9 +6,9 @@ module ChronoForge
|
|
|
6
6
|
# 500, so the page always renders.
|
|
7
7
|
class DefinitionsController < BaseController
|
|
8
8
|
def show
|
|
9
|
-
# Opt out of the auto-refresh region
|
|
10
|
-
#
|
|
11
|
-
#
|
|
9
|
+
# Opt out of the auto-refresh region morph: reconciling the region's HTML
|
|
10
|
+
# wipes the live Cytoscape canvas (injected <script>s don't re-run),
|
|
11
|
+
# leaving a blank graph. This page reloads fully instead.
|
|
12
12
|
@cf_disable_polling = true
|
|
13
13
|
@workflow = ChronoForge::Workflow.find(params[:id])
|
|
14
14
|
definition = analyze(@workflow)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
module ChronoForge
|
|
2
|
+
module Dashboard
|
|
3
|
+
# Fleet summary. The index is a lightweight shell of turbo-frames; each
|
|
4
|
+
# card and the per-class table loads from its own action below, so the shell
|
|
5
|
+
# paints immediately and the heavy per-class aggregation never blocks the
|
|
6
|
+
# cheap card counts (or the rest of the page). Frame responses render without
|
|
7
|
+
# the layout — Turbo swaps them into the matching frame in the shell.
|
|
8
|
+
class OverviewController < BaseController
|
|
9
|
+
# Frame responses are live aggregates but tolerate a few seconds of
|
|
10
|
+
# staleness. A short *private* cache lets Turbo's fetch reuse a frame across
|
|
11
|
+
# a back/forward or quick re-visit without re-running the query — private so
|
|
12
|
+
# a shared proxy never serves one viewer's counts to another. It's per-frame
|
|
13
|
+
# (4 independent requests), and does nothing within a single page load; the
|
|
14
|
+
# win is only the repeat visit. A host wanting cross-user dedup can layer
|
|
15
|
+
# Rails.cache fragment caching on top.
|
|
16
|
+
FRAME_TTL = 5.seconds
|
|
17
|
+
before_action(only: %i[processed in_flight blocked classes]) { expires_in FRAME_TTL, public: false }
|
|
18
|
+
|
|
19
|
+
# The shell opts out of the polling morph: each frame owns its own fetch.
|
|
20
|
+
def index
|
|
21
|
+
@cf_disable_polling = true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def processed
|
|
25
|
+
@count = OverviewQuery.processed_total
|
|
26
|
+
render layout: false
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def in_flight
|
|
30
|
+
@count = OverviewQuery.in_flight_total
|
|
31
|
+
render layout: false
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def blocked
|
|
35
|
+
@count = OverviewQuery.blocked_total
|
|
36
|
+
render layout: false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def classes
|
|
40
|
+
@overview = OverviewQuery.new
|
|
41
|
+
render layout: false
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module ChronoForge
|
|
2
|
+
module Dashboard
|
|
3
|
+
# Workflows stranded in :running — still locked, but their lock hasn't been
|
|
4
|
+
# refreshed within reap_stale_after, so no worker is driving them (the worker
|
|
5
|
+
# was hard-killed mid-pass). This is exactly the set ChronoForge::Workflow
|
|
6
|
+
# .reap_stalled re-enqueues; the page reads the reaper's own criterion so the
|
|
7
|
+
# two never disagree. Top-level and branch children alike, like the reaper.
|
|
8
|
+
class StrandedController < BaseController
|
|
9
|
+
# Bound the scan: at scale there can be a large backlog, so we examine at
|
|
10
|
+
# most CAP (oldest lock first — most stranded) and let the bulk reap sweep
|
|
11
|
+
# the rest server-side.
|
|
12
|
+
CAP = 500
|
|
13
|
+
|
|
14
|
+
def index
|
|
15
|
+
cutoff = ChronoForge.config.reap_stale_after.ago
|
|
16
|
+
scope = ChronoForge::Workflow
|
|
17
|
+
.where(state: ChronoForge::Workflow.states[:running])
|
|
18
|
+
.where(locked_at: ...cutoff)
|
|
19
|
+
.order(locked_at: :asc)
|
|
20
|
+
.limit(CAP + 1)
|
|
21
|
+
.to_a
|
|
22
|
+
@capped = scope.size > CAP
|
|
23
|
+
@stranded = scope.first(CAP)
|
|
24
|
+
@cap = CAP
|
|
25
|
+
@stale_after = ChronoForge.config.reap_stale_after
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -80,7 +80,7 @@ module ChronoForge
|
|
|
80
80
|
|
|
81
81
|
# Whether the main region opts into auto-refresh. A page sets
|
|
82
82
|
# @cf_disable_polling to opt OUT (e.g. the definition graph, whose live
|
|
83
|
-
# Cytoscape canvas can't survive the poll's
|
|
83
|
+
# Cytoscape canvas can't survive the poll's morph region refresh). Without a
|
|
84
84
|
# [data-poll-region] the JS never starts a poll timer for the page.
|
|
85
85
|
def cf_poll_region? = !@cf_disable_polling
|
|
86
86
|
|
|
@@ -181,6 +181,140 @@ module ChronoForge
|
|
|
181
181
|
else "text-zinc-500"
|
|
182
182
|
end
|
|
183
183
|
end
|
|
184
|
+
|
|
185
|
+
# Age past which a running workflow's lock is considered stale (its worker
|
|
186
|
+
# gone) — the reaper's own threshold, so the dashboard flags exactly what
|
|
187
|
+
# ChronoForge::Workflow.reap_stalled would re-enqueue.
|
|
188
|
+
def cf_reap_stale_after = ChronoForge.config.reap_stale_after
|
|
189
|
+
|
|
190
|
+
# A workflow stranded in :running by a hard-killed worker: still running,
|
|
191
|
+
# but its lock hasn't been refreshed within reap_stale_after, so nothing is
|
|
192
|
+
# driving it. This is the *only* "stuck" signal that holds — a healthy
|
|
193
|
+
# workflow may legitimately run for years, so elapsed runtime says nothing.
|
|
194
|
+
def cf_stranded?(workflow)
|
|
195
|
+
workflow.running? && workflow.locked_at.present? &&
|
|
196
|
+
workflow.locked_at < cf_reap_stale_after.ago
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
# How long a workflow's lock has been stale (nil if never locked).
|
|
200
|
+
def cf_lock_age(workflow)
|
|
201
|
+
workflow.locked_at ? (Time.current - workflow.locked_at).to_i : nil
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
# The attempts count made legible — nil when there's nothing worth saying.
|
|
205
|
+
# The number means different things per step kind, so it's labelled per kind:
|
|
206
|
+
# an execution retried, a wait/gate polled. Repeat coordination shows its
|
|
207
|
+
# iteration count elsewhere, so it opts out here.
|
|
208
|
+
POLLING_KINDS = %i[wait continue sleep].freeze
|
|
209
|
+
|
|
210
|
+
def cf_attempts_note(kind, attempts, status)
|
|
211
|
+
return nil if attempts.to_i <= 1
|
|
212
|
+
return nil if kind == :repeat_coordination
|
|
213
|
+
if POLLING_KINDS.include?(kind)
|
|
214
|
+
{text: "checked #{attempts}×", tone: :muted,
|
|
215
|
+
title: "Polled #{attempts} times before this step resolved"}
|
|
216
|
+
else
|
|
217
|
+
retries = attempts.to_i - 1
|
|
218
|
+
{text: "#{attempts} attempts", tone: ((status.to_s == "failed") ? :crit : :warn),
|
|
219
|
+
title: "Ran #{attempts} times — #{pluralize(retries, "retry")} after the first attempt"}
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Width class (reusing the CSP-safe cf-bar-{0..100} steps) for a duration
|
|
224
|
+
# meter, on a sqrt scale so a 2-second step stays visible next to a
|
|
225
|
+
# 7-minute one instead of collapsing to nothing.
|
|
226
|
+
def cf_duration_bar(seconds, max)
|
|
227
|
+
# A non-positive duration (clock skew, or a step whose completed_at
|
|
228
|
+
# predates started_at) has no bar — and guards Math.sqrt against a
|
|
229
|
+
# negative argument, which would otherwise raise a domain error.
|
|
230
|
+
return "cf-bar-0" if seconds.nil? || seconds.to_f <= 0 || max.to_f <= 0
|
|
231
|
+
pct = Math.sqrt(seconds.to_f / max) * 100
|
|
232
|
+
"cf-bar-#{[(pct / 5).round * 5, 100].min}"
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# A step slow enough to emphasize (a minute or more) vs. ordinary sub-minute
|
|
236
|
+
# work — drives the amber meter/label in the timeline.
|
|
237
|
+
def cf_slow_step?(seconds) = seconds.to_i >= 60
|
|
238
|
+
|
|
239
|
+
# Hover text for a duration meter: what the visible number can't say —
|
|
240
|
+
# this step's share of the run's longest step, and the wall-clock span it
|
|
241
|
+
# covered. The bar is relative, so the percentage is what it's encoding.
|
|
242
|
+
def cf_meter_title(seconds, max, from, to)
|
|
243
|
+
share = (max.to_f > 0) ? (seconds.to_f / max * 100).round : 0
|
|
244
|
+
"#{cf_secs(seconds)} · #{share}% of the longest step · #{from.iso8601} → #{to.iso8601}"
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# A CSP-safe proportional meter (the timeline's bar, reusable): a fixed
|
|
248
|
+
# track with a filled inner bar whose width is a pre-generated cf-bar-{n}
|
|
249
|
+
# class. `width_class` is that class; `color_class` tints the fill.
|
|
250
|
+
def cf_meter(width_class, color_class, track: "w-16", title: nil)
|
|
251
|
+
content_tag(:span, class: "inline-block h-1.5 #{track} overflow-hidden rounded-full bg-zinc-200 align-middle", title: title) do
|
|
252
|
+
content_tag(:span, "", class: "cf-bar #{width_class} block rounded-full #{color_class}")
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
|
|
256
|
+
# --- Analytics day health ------------------------------------------------
|
|
257
|
+
# A day's failure rate high enough to flag the row. Ten percent of terminal
|
|
258
|
+
# workflows failing in a day is a real signal, not catch-up churn.
|
|
259
|
+
DAY_FAILURE_FLAG = 0.1
|
|
260
|
+
|
|
261
|
+
# A day's completion rate (0.0–1.0), or nil when nothing terminated.
|
|
262
|
+
def cf_day_rate(bucket)
|
|
263
|
+
bucket.terminal.zero? ? nil : bucket.completed.to_f / bucket.terminal
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
def cf_day_flagged?(bucket)
|
|
267
|
+
bucket.terminal > 0 && (bucket.failed.to_f / bucket.terminal) >= DAY_FAILURE_FLAG
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def cf_trend_arrow(points)
|
|
271
|
+
return "—" if points.zero?
|
|
272
|
+
(points > 0) ? "▲" : "▼"
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def cf_trend_points(points)
|
|
276
|
+
return "flat" if points.zero?
|
|
277
|
+
"#{(points > 0) ? "+" : "−"}#{points.abs.round(1)}"
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# Trend across the window: the newer half's completion/failure rate minus
|
|
281
|
+
# the older half's, in percentage points — so a stat card can say whether
|
|
282
|
+
# things are getting better or worse. Derived from the buckets already
|
|
283
|
+
# loaded (no query); nil when there's too little to compare.
|
|
284
|
+
def cf_window_trend(buckets)
|
|
285
|
+
return nil if buckets.size < 2
|
|
286
|
+
mid = buckets.size / 2
|
|
287
|
+
rate = lambda do |bs|
|
|
288
|
+
t = bs.sum(&:terminal)
|
|
289
|
+
t.zero? ? nil : bs.sum(&:completed).to_f / t
|
|
290
|
+
end
|
|
291
|
+
older = rate.call(buckets[0...mid])
|
|
292
|
+
newer = rate.call(buckets[mid..])
|
|
293
|
+
return nil if older.nil? || newer.nil?
|
|
294
|
+
pts = ((newer - older) * 100).round(1)
|
|
295
|
+
{completion: pts, failure: -pts}
|
|
296
|
+
end
|
|
297
|
+
|
|
298
|
+
# Run length in seconds for a workflow row: elapsed for a live run, final
|
|
299
|
+
# span for a run that ran and stopped (completed / failed / stalled), nil
|
|
300
|
+
# for a parked (idle/scheduled) one whose "duration" would just be how long
|
|
301
|
+
# it's been waiting on a condition, not how long it actually ran.
|
|
302
|
+
def cf_row_duration_secs(workflow)
|
|
303
|
+
return nil unless workflow.started_at
|
|
304
|
+
ending =
|
|
305
|
+
if workflow.completed_at
|
|
306
|
+
workflow.completed_at
|
|
307
|
+
elsif workflow.running?
|
|
308
|
+
Time.current
|
|
309
|
+
elsif workflow.failed? || workflow.stalled?
|
|
310
|
+
workflow.updated_at
|
|
311
|
+
end
|
|
312
|
+
return nil unless ending
|
|
313
|
+
secs = (ending - workflow.started_at).to_i
|
|
314
|
+
# A negative elapsed time (ending before start — clock skew or bad data)
|
|
315
|
+
# is meaningless; report it as unknown rather than a fake 0s or a crash.
|
|
316
|
+
secs.negative? ? nil : secs
|
|
317
|
+
end
|
|
184
318
|
end
|
|
185
319
|
end
|
|
186
320
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module ChronoForge
|
|
2
|
+
module Dashboard
|
|
3
|
+
# Reap every stranded workflow in the background so the request returns fast
|
|
4
|
+
# even with a large backlog. Delegates to the gem's own sweep, which finds
|
|
5
|
+
# running workflows with a stale lock (top-level and branch children) and
|
|
6
|
+
# re-enqueues each — the same operation a periodic reaper would run.
|
|
7
|
+
class BulkReapJob < ActiveJob::Base
|
|
8
|
+
def perform = ChronoForge::Workflow.reap_stalled
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|