chrono_forge-dashboard 0.1.0 → 0.2.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 +28 -0
- data/README.md +43 -24
- data/app/assets/chrono_forge/dashboard/cytoscape-dagre.js +397 -0
- data/app/assets/chrono_forge/dashboard/cytoscape.min.js +32 -0
- data/app/assets/chrono_forge/dashboard/dagre.min.js +3809 -0
- data/app/assets/chrono_forge/dashboard/dashboard.css +1 -1
- data/app/assets/chrono_forge/dashboard/dashboard.js +37 -0
- data/app/assets/chrono_forge/dashboard/definition_graph.js +161 -0
- data/app/controllers/chrono_forge/dashboard/assets_controller.rb +8 -1
- data/app/controllers/chrono_forge/dashboard/definitions_controller.rb +35 -0
- data/app/controllers/chrono_forge/dashboard/workflows_controller.rb +6 -2
- data/app/helpers/chrono_forge/dashboard/dashboard_helper.rb +33 -0
- data/app/presenters/chrono_forge/dashboard/branch_presenter.rb +38 -1
- data/app/presenters/chrono_forge/dashboard/branches_presenter.rb +59 -4
- data/app/presenters/chrono_forge/dashboard/cytoscape_graph.rb +48 -0
- data/app/presenters/chrono_forge/dashboard/definition_overlay.rb +128 -0
- data/app/queries/chrono_forge/dashboard/workflows_query.rb +10 -1
- data/app/views/chrono_forge/dashboard/branch_children/show.html.erb +56 -11
- data/app/views/chrono_forge/dashboard/definitions/show.html.erb +42 -0
- data/app/views/chrono_forge/dashboard/workflows/_branches.html.erb +14 -4
- data/app/views/chrono_forge/dashboard/workflows/_filters.html.erb +12 -1
- data/app/views/chrono_forge/dashboard/workflows/_stats.html.erb +5 -9
- data/app/views/chrono_forge/dashboard/workflows/index.html.erb +3 -3
- data/app/views/chrono_forge/dashboard/workflows/show.html.erb +1 -0
- data/app/views/layouts/chrono_forge/dashboard/application.html.erb +18 -10
- data/config/routes.rb +6 -1
- data/lib/chrono_forge/dashboard/configuration.rb +2 -2
- data/lib/chrono_forge/dashboard/version.rb +1 -1
- metadata +10 -2
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/*! tailwindcss v4.3.1 | MIT License | https://tailwindcss.com */
|
|
2
|
-
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-700:oklch(50% .134 242.749);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-700:oklch(49.1% .27 292.581);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.cf-pill{align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-width:1px;border-radius:3.40282e38px;display:inline-flex}.cf-pill-idle{border-color:var(--color-zinc-200);background-color:var(--color-zinc-100);color:var(--color-zinc-600)}.cf-pill-running{border-color:var(--color-sky-200);background-color:var(--color-sky-50);color:var(--color-sky-700)}.cf-pill-completed{border-color:var(--color-emerald-200);background-color:var(--color-emerald-50);color:var(--color-emerald-700)}.cf-pill-failed{border-color:var(--color-rose-200);background-color:var(--color-rose-50);color:var(--color-rose-700)}.cf-pill-stalled{border-color:var(--color-amber-200);background-color:var(--color-amber-50);color:var(--color-amber-700)}.cf-pill-scheduled{border-color:var(--color-violet-200);background-color:var(--color-violet-50);color:var(--color-violet-700)}.cf-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.cf-dot-idle{background-color:var(--color-zinc-400)}.cf-dot-running{background-color:var(--color-sky-500)}.cf-dot-completed{background-color:var(--color-emerald-500)}.cf-dot-failed{background-color:var(--color-rose-500)}.cf-dot-stalled{background-color:var(--color-amber-500)}.cf-dot-scheduled{background-color:var(--color-violet-500)}.cf-dot-pending{background-color:var(--color-zinc-300)}.cf-btn{align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-zinc-800);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:inline-flex}@media (hover:hover){.cf-btn:hover{background-color:var(--color-zinc-50)}}.cf-btn:focus{--tw-outline-style:none;outline-style:none}.cf-btn:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#18181b26}@supports (color:color-mix(in lab, red, red)){.cf-btn:focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-zinc-900) 15%, transparent)}}.cf-btn-primary{border-color:var(--color-zinc-900);background-color:var(--color-zinc-900);color:var(--color-white)}@media (hover:hover){.cf-btn-primary:hover{background-color:var(--color-zinc-700)}}.cf-btn-danger{border-color:var(--color-rose-300);color:var(--color-rose-700)}@media (hover:hover){.cf-btn-danger:hover{background-color:var(--color-rose-50)}}.cf-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-200);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.cf-bar{height:100%}.cf-bar-0{width:0%}.cf-bar-5{width:5%}.cf-bar-10{width:10%}.cf-bar-15{width:15%}.cf-bar-20{width:20%}.cf-bar-25{width:25%}.cf-bar-30{width:30%}.cf-bar-35{width:35%}.cf-bar-40{width:40%}.cf-bar-45{width:45%}.cf-bar-50{width:50%}.cf-bar-55{width:55%}.cf-bar-60{width:60%}.cf-bar-65{width:65%}.cf-bar-70{width:70%}.cf-bar-75{width:75%}.cf-bar-80{width:80%}.cf-bar-85{width:85%}.cf-bar-90{width:90%}.cf-bar-95{width:95%}.cf-bar-100{width:100%}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-4{top:calc(var(--spacing) * 4)}.right-4{right:calc(var(--spacing) * 4)}.-left-\[5px\]{left:-5px}.z-50{z-index:50}.order-99{order:99}.order-1000{order:1000}.order-1001{order:1001}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.min-h-screen{min-height:100vh}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-16{width:calc(var(--spacing) * 16)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-64{width:calc(var(--spacing) * 64)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[16rem\]{max-width:16rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-\[36rem\]{min-width:36rem}.min-w-\[40rem\]{min-width:40rem}.min-w-\[44rem\]{min-width:44rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-amber-100>:not(:last-child)){border-color:var(--color-amber-100)}:where(.divide-zinc-100>:not(:last-child)){border-color:var(--color-zinc-100)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-amber-200{border-color:var(--color-amber-200)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-rose-200{border-color:var(--color-rose-200)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-900{border-color:var(--color-zinc-900)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-50\/40{background-color:#fffbeb66}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)}}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-white{background-color:var(--color-white)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-6{padding-left:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-900{color:var(--color-amber-900)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-800{color:var(--color-emerald-800)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-amber-200{--tw-ring-color:var(--color-amber-200)}.ring-white{--tw-ring-color:var(--color-white)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}.last\:border-l-transparent:last-child{border-left-color:#0000}.last\:pb-0:last-child{padding-bottom:0}@media (hover:hover){.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial;--tw-duration:initial;--tw-ease:initial}}}@layer theme{:root,:host{--font-sans:ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;--font-mono:ui-monospace, "SF Mono", SFMono-Regular, Menlo, Consolas, monospace;--color-amber-50:oklch(98.7% .022 95.277);--color-amber-100:oklch(96.2% .059 95.617);--color-amber-200:oklch(92.4% .12 95.746);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-amber-800:oklch(47.3% .137 46.201);--color-amber-900:oklch(41.4% .112 45.904);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-400:oklch(76.5% .177 163.223);--color-emerald-500:oklch(69.6% .17 162.48);--color-emerald-600:oklch(59.6% .145 163.225);--color-emerald-700:oklch(50.8% .118 165.612);--color-emerald-800:oklch(43.2% .095 166.913);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-200:oklch(90.1% .058 230.902);--color-sky-500:oklch(68.5% .169 237.323);--color-sky-700:oklch(50% .134 242.749);--color-violet-50:oklch(96.9% .016 293.756);--color-violet-200:oklch(89.4% .057 293.283);--color-violet-500:oklch(60.6% .25 292.717);--color-violet-700:oklch(49.1% .27 292.581);--color-rose-50:oklch(96.9% .015 12.422);--color-rose-200:oklch(89.2% .058 10.001);--color-rose-300:oklch(81% .117 11.638);--color-rose-400:oklch(71.2% .194 13.428);--color-rose-500:oklch(64.5% .246 16.439);--color-rose-600:oklch(58.6% .253 17.585);--color-rose-700:oklch(51.4% .222 16.935);--color-rose-800:oklch(45.5% .188 13.697);--color-zinc-50:oklch(98.5% 0 0);--color-zinc-100:oklch(96.7% .001 286.375);--color-zinc-200:oklch(92% .004 286.32);--color-zinc-300:oklch(87.1% .006 286.286);--color-zinc-400:oklch(70.5% .015 286.067);--color-zinc-500:oklch(55.2% .016 285.938);--color-zinc-600:oklch(44.2% .017 285.786);--color-zinc-700:oklch(37% .013 285.805);--color-zinc-800:oklch(27.4% .006 286.033);--color-zinc-900:oklch(21% .006 285.885);--color-white:#fff;--spacing:.25rem;--container-6xl:72rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-base:1rem;--text-base--line-height:calc(1.5 / 1);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-xl:1.25rem;--text-xl--line-height:calc(1.75 / 1.25);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-normal:400;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wide:.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--radius-xl:.75rem;--ease-in:cubic-bezier(.4, 0, 1, 1);--ease-out:cubic-bezier(0, 0, .2, 1);--ease-in-out:cubic-bezier(.4, 0, .2, 1);--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:var(--font-sans);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components{.cf-pill{align-items:center;gap:calc(var(--spacing) * 1.5);border-style:var(--tw-border-style);padding-inline:calc(var(--spacing) * 2);padding-block:calc(var(--spacing) * .5);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);border-width:1px;border-radius:3.40282e38px;display:inline-flex}.cf-pill-idle{border-color:var(--color-zinc-200);background-color:var(--color-zinc-100);color:var(--color-zinc-600)}.cf-pill-running{border-color:var(--color-sky-200);background-color:var(--color-sky-50);color:var(--color-sky-700)}.cf-pill-completed{border-color:var(--color-emerald-200);background-color:var(--color-emerald-50);color:var(--color-emerald-700)}.cf-pill-failed{border-color:var(--color-rose-200);background-color:var(--color-rose-50);color:var(--color-rose-700)}.cf-pill-stalled{border-color:var(--color-amber-200);background-color:var(--color-amber-50);color:var(--color-amber-700)}.cf-pill-scheduled{border-color:var(--color-violet-200);background-color:var(--color-violet-50);color:var(--color-violet-700)}.cf-dot{height:calc(var(--spacing) * 2);width:calc(var(--spacing) * 2);border-radius:3.40282e38px;display:inline-block}.cf-dot-idle{background-color:var(--color-zinc-400)}.cf-dot-running{background-color:var(--color-sky-500)}.cf-dot-completed{background-color:var(--color-emerald-500)}.cf-dot-failed{background-color:var(--color-rose-500)}.cf-dot-stalled{background-color:var(--color-amber-500)}.cf-dot-scheduled{background-color:var(--color-violet-500)}.cf-dot-pending{background-color:var(--color-zinc-300)}.cf-btn{align-items:center;gap:calc(var(--spacing) * 1.5);border-radius:var(--radius-md);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-300);background-color:var(--color-white);padding-inline:calc(var(--spacing) * 3);padding-block:calc(var(--spacing) * 1.5);font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height));--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium);color:var(--color-zinc-800);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration));display:inline-flex}@media (hover:hover){.cf-btn:hover{background-color:var(--color-zinc-50)}}.cf-btn:focus{--tw-outline-style:none;outline-style:none}.cf-btn:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow);--tw-ring-color:#18181b26}@supports (color:color-mix(in lab, red, red)){.cf-btn:focus-visible{--tw-ring-color:color-mix(in oklab, var(--color-zinc-900) 15%, transparent)}}.cf-btn-primary{border-color:var(--color-zinc-900);background-color:var(--color-zinc-900);color:var(--color-white)}@media (hover:hover){.cf-btn-primary:hover{background-color:var(--color-zinc-700)}}.cf-btn-danger{border-color:var(--color-rose-300);color:var(--color-rose-700)}@media (hover:hover){.cf-btn-danger:hover{background-color:var(--color-rose-50)}}.cf-card{border-radius:var(--radius-lg);border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-zinc-200);background-color:var(--color-white);--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.cf-bar{height:100%}.cf-bar-0{width:0%}.cf-bar-5{width:5%}.cf-bar-10{width:10%}.cf-bar-15{width:15%}.cf-bar-20{width:20%}.cf-bar-25{width:25%}.cf-bar-30{width:30%}.cf-bar-35{width:35%}.cf-bar-40{width:40%}.cf-bar-45{width:45%}.cf-bar-50{width:50%}.cf-bar-55{width:55%}.cf-bar-60{width:60%}.cf-bar-65{width:65%}.cf-bar-70{width:70%}.cf-bar-75{width:75%}.cf-bar-80{width:80%}.cf-bar-85{width:85%}.cf-bar-90{width:90%}.cf-bar-95{width:95%}.cf-bar-100{width:100%}}@layer utilities{.pointer-events-auto{pointer-events:auto}.pointer-events-none{pointer-events:none}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.static{position:static}.top-1\.5{top:calc(var(--spacing) * 1.5)}.top-4{top:calc(var(--spacing) * 4)}.right-4{right:calc(var(--spacing) * 4)}.-left-\[5px\]{left:-5px}.z-50{z-index:50}.order-99{order:99}.order-1000{order:1000}.order-1001{order:1001}.container{width:100%}@media (min-width:40rem){.container{max-width:40rem}}@media (min-width:48rem){.container{max-width:48rem}}@media (min-width:64rem){.container{max-width:64rem}}@media (min-width:80rem){.container{max-width:80rem}}@media (min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-1\.5{margin-top:calc(var(--spacing) * 1.5)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:var(--spacing)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-5{margin-bottom:calc(var(--spacing) * 5)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-2{margin-left:calc(var(--spacing) * -2)}.ml-1{margin-left:var(--spacing)}.ml-1\.5{margin-left:calc(var(--spacing) * 1.5)}.ml-2{margin-left:calc(var(--spacing) * 2)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline{display:inline}.inline-block{display:inline-block}.inline-flex{display:inline-flex}.table{display:table}.h-2\.5{height:calc(var(--spacing) * 2.5)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-\[72vh\]{height:72vh}.min-h-\[3rem\]{min-height:3rem}.min-h-screen{min-height:100vh}.w-2\.5{width:calc(var(--spacing) * 2.5)}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-16{width:calc(var(--spacing) * 16)}.w-24{width:calc(var(--spacing) * 24)}.w-32{width:calc(var(--spacing) * 32)}.w-64{width:calc(var(--spacing) * 64)}.w-80{width:calc(var(--spacing) * 80)}.w-full{width:100%}.max-w-6xl{max-width:var(--container-6xl)}.max-w-\[16rem\]{max-width:16rem}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-\[36rem\]{min-width:36rem}.min-w-\[40rem\]{min-width:40rem}.min-w-\[44rem\]{min-width:44rem}.flex-1{flex:1}.shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-help{cursor:help}.cursor-pointer{cursor:pointer}.resize{resize:both}.list-disc{list-style-type:disc}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.gap-0\.5{gap:calc(var(--spacing) * .5)}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}.gap-5{gap:calc(var(--spacing) * 5)}:where(.space-y-0\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * .5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * .5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-2{column-gap:calc(var(--spacing) * 2)}.gap-x-3{column-gap:calc(var(--spacing) * 3)}.gap-x-4{column-gap:calc(var(--spacing) * 4)}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-0\.5{row-gap:calc(var(--spacing) * .5)}.gap-y-1{row-gap:var(--spacing)}.gap-y-1\.5{row-gap:calc(var(--spacing) * 1.5)}.gap-y-2{row-gap:calc(var(--spacing) * 2)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-amber-100>:not(:last-child)){border-color:var(--color-amber-100)}:where(.divide-zinc-100>:not(:last-child)){border-color:var(--color-zinc-100)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded{border-radius:.25rem}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.rounded-xl{border-radius:var(--radius-xl)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-l{border-left-style:var(--tw-border-style);border-left-width:1px}.border-amber-200{border-color:var(--color-amber-200)}.border-amber-300{border-color:var(--color-amber-300)}.border-emerald-200{border-color:var(--color-emerald-200)}.border-rose-200{border-color:var(--color-rose-200)}.border-zinc-100{border-color:var(--color-zinc-100)}.border-zinc-200{border-color:var(--color-zinc-200)}.border-zinc-300{border-color:var(--color-zinc-300)}.border-zinc-900{border-color:var(--color-zinc-900)}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-50\/40{background-color:#fffbeb66}@supports (color:color-mix(in lab, red, red)){.bg-amber-50\/40{background-color:color-mix(in oklab, var(--color-amber-50) 40%, transparent)}}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-400{background-color:var(--color-emerald-400)}.bg-rose-50{background-color:var(--color-rose-50)}.bg-rose-300{background-color:var(--color-rose-300)}.bg-rose-400{background-color:var(--color-rose-400)}.bg-white{background-color:var(--color-white)}.bg-zinc-50{background-color:var(--color-zinc-50)}.bg-zinc-100{background-color:var(--color-zinc-100)}.bg-zinc-900{background-color:var(--color-zinc-900)}.p-0\.5{padding:calc(var(--spacing) * .5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-12{padding-block:calc(var(--spacing) * 12)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pr-4{padding-right:calc(var(--spacing) * 4)}.pb-5{padding-bottom:calc(var(--spacing) * 5)}.pl-4{padding-left:calc(var(--spacing) * 4)}.pl-6{padding-left:calc(var(--spacing) * 6)}.text-center{text-align:center}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:var(--font-sans)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-base{font-size:var(--text-base);line-height:var(--tw-leading,var(--text-base--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xl{font-size:var(--text-xl);line-height:var(--tw-leading,var(--text-xl--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[11px\]{font-size:11px}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-normal{--tw-font-weight:var(--font-weight-normal);font-weight:var(--font-weight-normal)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wide{--tw-tracking:var(--tracking-wide);letter-spacing:var(--tracking-wide)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.text-wrap{text-wrap:wrap}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-amber-800{color:var(--color-amber-800)}.text-amber-900{color:var(--color-amber-900)}.text-emerald-600{color:var(--color-emerald-600)}.text-emerald-800{color:var(--color-emerald-800)}.text-rose-500{color:var(--color-rose-500)}.text-rose-600{color:var(--color-rose-600)}.text-rose-700{color:var(--color-rose-700)}.text-rose-800{color:var(--color-rose-800)}.text-white{color:var(--color-white)}.text-zinc-300{color:var(--color-zinc-300)}.text-zinc-400{color:var(--color-zinc-400)}.text-zinc-500{color:var(--color-zinc-500)}.text-zinc-600{color:var(--color-zinc-600)}.text-zinc-700{color:var(--color-zinc-700)}.text-zinc-800{color:var(--color-zinc-800)}.text-zinc-900{color:var(--color-zinc-900)}.lowercase{text-transform:lowercase}.normal-case{text-transform:none}.uppercase{text-transform:uppercase}.italic{font-style:italic}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.line-through{text-decoration-line:line-through}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.antialiased{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.opacity-0{opacity:0}.shadow-md{--tw-shadow:0 4px 6px -1px var(--tw-shadow-color,#0000001a), 0 2px 4px -2px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.shadow-sm{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-1{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(1px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-4{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(4px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.ring-amber-200{--tw-ring-color:var(--color-amber-200)}.ring-white{--tw-ring-color:var(--color-white)}.invert{--tw-invert:invert(100%);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-opacity{transition-property:opacity;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.duration-300{--tw-duration:.3s;transition-duration:.3s}.ease-in{--tw-ease:var(--ease-in);transition-timing-function:var(--ease-in)}.ease-in-out{--tw-ease:var(--ease-in-out);transition-timing-function:var(--ease-in-out)}.ease-out{--tw-ease:var(--ease-out);transition-timing-function:var(--ease-out)}.placeholder\:text-zinc-400::placeholder{color:var(--color-zinc-400)}.last\:border-l-transparent:last-child{border-left-color:#0000}.last\:pb-0:last-child{padding-bottom:0}@media (hover:hover){.hover\:bg-rose-50:hover{background-color:var(--color-rose-50)}.hover\:bg-zinc-50:hover{background-color:var(--color-zinc-50)}.hover\:text-zinc-900:hover{color:var(--color-zinc-900)}.hover\:underline:hover{text-decoration-line:underline}}@media (min-width:40rem){.sm\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}@media (min-width:48rem){.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}@property --tw-duration{syntax:"*";inherits:false}@property --tw-ease{syntax:"*";inherits:false}
|
|
@@ -79,10 +79,47 @@
|
|
|
79
79
|
// so polling doesn't yank a table back while it's being scrolled.
|
|
80
80
|
var scrolls = Array.prototype.map.call(
|
|
81
81
|
region.querySelectorAll(".overflow-x-auto"), function (el) { return el.scrollLeft; });
|
|
82
|
+
|
|
83
|
+
// Preserve in-progress text in the filter boxes. The swap replaces the
|
|
84
|
+
// inputs with server-rendered ones reflecting only the LAST SUBMITTED
|
|
85
|
+
// query, so without this every poll tick wipes whatever is being typed
|
|
86
|
+
// and drops focus. Capture each named text field's value, plus the caret
|
|
87
|
+
// of the focused one, and reapply them after the swap.
|
|
88
|
+
var isTextEntry = function (el) {
|
|
89
|
+
if (!el) return false;
|
|
90
|
+
if (el.tagName === "TEXTAREA") return true;
|
|
91
|
+
if (el.tagName !== "INPUT") return false;
|
|
92
|
+
return /^(text|search|email|url|tel|number|password)$/i.test(el.type || "text");
|
|
93
|
+
};
|
|
94
|
+
var values = {};
|
|
95
|
+
region.querySelectorAll("input, textarea").forEach(function (el) {
|
|
96
|
+
if (el.name && isTextEntry(el)) values[el.name] = el.value;
|
|
97
|
+
});
|
|
98
|
+
var active = document.activeElement;
|
|
99
|
+
var activeName = (active && region.contains(active) && isTextEntry(active) && active.name) ? active.name : null;
|
|
100
|
+
var caretStart = null, caretEnd = null;
|
|
101
|
+
if (activeName) {
|
|
102
|
+
try { caretStart = active.selectionStart; caretEnd = active.selectionEnd; } catch (e) {}
|
|
103
|
+
}
|
|
104
|
+
|
|
82
105
|
region.innerHTML = fresh.innerHTML;
|
|
106
|
+
|
|
83
107
|
region.querySelectorAll(".overflow-x-auto").forEach(function (el, i) {
|
|
84
108
|
if (scrolls[i]) el.scrollLeft = scrolls[i];
|
|
85
109
|
});
|
|
110
|
+
// Reapply preserved field values, then restore focus + caret.
|
|
111
|
+
region.querySelectorAll("input, textarea").forEach(function (el) {
|
|
112
|
+
if (el.name && isTextEntry(el) && Object.prototype.hasOwnProperty.call(values, el.name)) {
|
|
113
|
+
el.value = values[el.name];
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
if (activeName) {
|
|
117
|
+
var refocus = region.querySelector("[name='" + activeName + "']");
|
|
118
|
+
if (refocus) {
|
|
119
|
+
refocus.focus();
|
|
120
|
+
try { refocus.setSelectionRange(caretStart, caretEnd); } catch (e) {}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
86
123
|
}).catch(function () {});
|
|
87
124
|
}, interval);
|
|
88
125
|
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// Renders the workflow definition DAG with Cytoscape + dagre. Reads the graph as
|
|
2
|
+
// JSON from #cf-graph[data-graph] (structured elements — no text grammar) and
|
|
3
|
+
// paints kind (node shape) + run status (fill/border). Interactive: pan/zoom,
|
|
4
|
+
// and tapping a node/edge shows its details and highlights its neighborhood.
|
|
5
|
+
(function () {
|
|
6
|
+
var el = document.getElementById("cf-graph");
|
|
7
|
+
if (!el || typeof cytoscape === "undefined") return;
|
|
8
|
+
if (window.cytoscapeDagre) {
|
|
9
|
+
try { cytoscape.use(window.cytoscapeDagre); } catch (e) { /* already registered */ }
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
var graph;
|
|
13
|
+
try { graph = JSON.parse(el.getAttribute("data-graph")); } catch (e) { return; }
|
|
14
|
+
|
|
15
|
+
// Escape before writing into innerHTML: labels, step names and guard text are
|
|
16
|
+
// author-controlled source strings (step-name validation only forbids "$"), and
|
|
17
|
+
// guards legitimately contain "<"/">", so they must not be treated as markup.
|
|
18
|
+
var ESC = {"&": "&", "<": "<", ">": ">", '"': """, "'": "'"};
|
|
19
|
+
function esc(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, function (c) { return ESC[c]; }); }
|
|
20
|
+
|
|
21
|
+
// Node shape per durable kind (a legend in the view maps these back to names).
|
|
22
|
+
var SHAPE = {
|
|
23
|
+
execute: "round-rectangle", wait: "round-rectangle", wait_until: "round-rectangle",
|
|
24
|
+
continue_if: "round-rectangle", branch: "round-rectangle", merge: "round-rectangle",
|
|
25
|
+
repeat: "round-rectangle", join: "ellipse", dynamic: "round-rectangle",
|
|
26
|
+
endpoint: "round-tag"
|
|
27
|
+
};
|
|
28
|
+
// [fill, border, text] per run status. not_reached is intentionally muted so the
|
|
29
|
+
// path a run HASN'T reached recedes behind the path it has.
|
|
30
|
+
var COLOR = {
|
|
31
|
+
done: ["#ecfdf5", "#10b981", "#065f46"],
|
|
32
|
+
active: ["#eff6ff", "#3b82f6", "#1e40af"],
|
|
33
|
+
pending: ["#fafafa", "#d4d4d8", "#52525b"],
|
|
34
|
+
not_reached: ["#ffffff", "#e4e4e7", "#a1a1aa"],
|
|
35
|
+
failed: ["#fef2f2", "#ef4444", "#991b1b"],
|
|
36
|
+
stalled: ["#fff7ed", "#f59e0b", "#9a3412"],
|
|
37
|
+
unmapped: ["#fafaf9", "#d6d3d1", "#78716c"]
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Lead with the step's own name; the DSL word (kind) is carried by the shape +
|
|
41
|
+
// the small kind line, so "durably_execute send_payment_reminder" reads as the
|
|
42
|
+
// bold "send_payment_reminder" over a muted "execute".
|
|
43
|
+
var KIND_WORD = {
|
|
44
|
+
execute: "execute", wait: "wait", wait_until: "wait until", continue_if: "continue if",
|
|
45
|
+
branch: "branch", merge: "merge", repeat: "repeat", dynamic: "dynamic", join: "join"
|
|
46
|
+
};
|
|
47
|
+
graph.nodes.forEach(function (n) {
|
|
48
|
+
var kind = (n.classes.match(/kind-(\w+)/) || [])[1] || "";
|
|
49
|
+
if (kind === "endpoint") { n.data.display = n.data.label; return; }
|
|
50
|
+
var lbl = n.data.label || "";
|
|
51
|
+
var name = lbl.indexOf(" ") > -1 ? lbl.slice(lbl.indexOf(" ") + 1) : lbl;
|
|
52
|
+
// A repeat carries how many times it has run so far; show it inline (×N).
|
|
53
|
+
if (n.data.repetitions) name += " ×" + n.data.repetitions;
|
|
54
|
+
n.data.display = (KIND_WORD[kind] ? KIND_WORD[kind].toUpperCase() + "\n" : "") + name;
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
var style = [
|
|
58
|
+
{
|
|
59
|
+
selector: "node",
|
|
60
|
+
style: {
|
|
61
|
+
"label": "data(display)", "text-wrap": "wrap", "text-max-width": "150px",
|
|
62
|
+
"text-valign": "center", "text-halign": "center", "text-justification": "center",
|
|
63
|
+
"font-family": "ui-sans-serif, system-ui, sans-serif", "font-size": "11px",
|
|
64
|
+
"line-height": 1.35, "width": "170px", "height": "label",
|
|
65
|
+
"padding": "12px", "shape": "round-rectangle", "corner-radius": "10px",
|
|
66
|
+
"border-width": 1.5, "background-color": "#fff", "border-color": "#e4e4e7",
|
|
67
|
+
"color": "#3f3f46", "text-outline-width": 0
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
{selector: "edge", style: {
|
|
71
|
+
// Guard sits near the TARGET (not mid-edge), so several edges leaving the
|
|
72
|
+
// same node don't stack their labels on top of each other near the source.
|
|
73
|
+
"target-label": "data(label)", "target-text-offset": "42px",
|
|
74
|
+
"font-size": "9px", "font-family": "ui-sans-serif, system-ui, sans-serif",
|
|
75
|
+
"color": "#64748b", "curve-style": "taxi", "taxi-direction": "downward",
|
|
76
|
+
"taxi-turn": "40%", "taxi-turn-min-distance": "8px",
|
|
77
|
+
"width": 1.5, "line-color": "#cbd5e1", "target-arrow-shape": "triangle",
|
|
78
|
+
"target-arrow-color": "#94a3b8", "arrow-scale": 0.9,
|
|
79
|
+
"text-background-color": "#fff", "text-background-opacity": 1,
|
|
80
|
+
"text-background-padding": "3px", "text-background-shape": "round-rectangle",
|
|
81
|
+
"text-border-color": "#e2e8f0", "text-border-width": 1, "text-border-opacity": 1,
|
|
82
|
+
"text-max-width": "150px", "text-wrap": "ellipsis", "text-events": "yes"
|
|
83
|
+
}},
|
|
84
|
+
{selector: "edge.kind-terminal", style: {
|
|
85
|
+
"line-style": "dashed", "line-color": "#f87171", "target-arrow-color": "#ef4444", "color": "#b91c1c"
|
|
86
|
+
}},
|
|
87
|
+
{selector: "node.dim", style: {"opacity": 0.2}},
|
|
88
|
+
{selector: "edge.dim", style: {"opacity": 0.12}},
|
|
89
|
+
{selector: "node.focus", style: {"border-width": 3}},
|
|
90
|
+
{selector: "node.hover", style: {"border-width": 2.5}}
|
|
91
|
+
];
|
|
92
|
+
Object.keys(SHAPE).forEach(function (k) {
|
|
93
|
+
style.push({selector: "node.kind-" + k, style: {"shape": SHAPE[k]}});
|
|
94
|
+
});
|
|
95
|
+
Object.keys(COLOR).forEach(function (s) {
|
|
96
|
+
style.push({selector: "node.status-" + s, style: {
|
|
97
|
+
"background-color": COLOR[s][0], "border-color": COLOR[s][1], "color": COLOR[s][2]
|
|
98
|
+
}});
|
|
99
|
+
});
|
|
100
|
+
style.push({selector: "node.kind-endpoint", style: {
|
|
101
|
+
"background-color": "#18181b", "border-color": "#18181b", "color": "#fff",
|
|
102
|
+
"width": "label", "font-size": "10px", "font-weight": "600", "padding": "8px"
|
|
103
|
+
}});
|
|
104
|
+
|
|
105
|
+
var cy = cytoscape({
|
|
106
|
+
container: el,
|
|
107
|
+
elements: graph,
|
|
108
|
+
style: style,
|
|
109
|
+
layout: {
|
|
110
|
+
name: "dagre", rankDir: "TB", nodeSep: 42, rankSep: 70, edgeSep: 18,
|
|
111
|
+
ranker: "network-simplex", padding: 30
|
|
112
|
+
},
|
|
113
|
+
minZoom: 0.25, maxZoom: 2.5, wheelSensitivity: 0.25, autoungrabify: true
|
|
114
|
+
});
|
|
115
|
+
cy.ready(function () { cy.fit(cy.elements(), 36); });
|
|
116
|
+
|
|
117
|
+
var detail = document.getElementById("cf-graph-detail");
|
|
118
|
+
function hint() {
|
|
119
|
+
if (detail) detail.innerHTML = '<span class="text-zinc-400">Tap a node or edge to inspect it. Scroll to zoom, drag to pan.</span>';
|
|
120
|
+
}
|
|
121
|
+
function clearFocus() { cy.elements().removeClass("dim focus"); hint(); }
|
|
122
|
+
|
|
123
|
+
cy.on("mouseover", "node", function (e) { e.target.addClass("hover"); });
|
|
124
|
+
cy.on("mouseout", "node", function (e) { e.target.removeClass("hover"); });
|
|
125
|
+
|
|
126
|
+
cy.on("tap", "node", function (evt) {
|
|
127
|
+
var n = evt.target, hood = n.closedNeighborhood();
|
|
128
|
+
cy.elements().addClass("dim");
|
|
129
|
+
hood.removeClass("dim");
|
|
130
|
+
cy.nodes().removeClass("focus");
|
|
131
|
+
n.addClass("focus");
|
|
132
|
+
if (detail) {
|
|
133
|
+
var d = n.data(), cls = n.classes() || [];
|
|
134
|
+
var kind = (cls.join(" ").match(/kind-(\w+)/) || [])[1] || "";
|
|
135
|
+
var status = (cls.join(" ").match(/status-(\w+)/) || [])[1] || "";
|
|
136
|
+
// Run aggregates: a repeat's execution count, and a fan-out's per-state
|
|
137
|
+
// child tally ("2 completed · 1 failed") — rendered only when present.
|
|
138
|
+
var extra = "";
|
|
139
|
+
if (d.repetitions) extra += '<div class="mt-0.5 text-zinc-500">' + esc(d.repetitions) + " repetition" + (d.repetitions === 1 ? "" : "s") + "</div>";
|
|
140
|
+
if (d.counts) {
|
|
141
|
+
var parts = Object.keys(d.counts).map(function (k) { return esc(d.counts[k]) + " " + esc(k); });
|
|
142
|
+
if (parts.length) extra += '<div class="mt-0.5 text-zinc-500">' + parts.join(" · ") + "</div>";
|
|
143
|
+
}
|
|
144
|
+
detail.innerHTML =
|
|
145
|
+
'<div class="font-medium text-zinc-800">' + esc(d.label || d.id) + "</div>" +
|
|
146
|
+
'<div class="text-zinc-500">' + esc(kind) + (status ? ' · <span class="font-medium">' + esc(status.replace("_", " ")) + "</span>" : "") + "</div>" +
|
|
147
|
+
(d.step_name ? '<div class="mt-0.5 font-mono text-[11px] text-zinc-500">' + esc(d.step_name) + "</div>" : "") +
|
|
148
|
+
extra;
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
cy.on("tap", "edge", function (evt) {
|
|
152
|
+
var g = evt.target.data("label");
|
|
153
|
+
if (detail) {
|
|
154
|
+
detail.innerHTML = g && g.length
|
|
155
|
+
? '<div class="text-zinc-500">guard</div><div class="font-mono text-[11px] text-zinc-700">' + esc(g) + "</div>"
|
|
156
|
+
: '<span class="text-zinc-400">unconditional edge</span>';
|
|
157
|
+
}
|
|
158
|
+
});
|
|
159
|
+
cy.on("tap", function (evt) { if (evt.target === cy) clearFocus(); });
|
|
160
|
+
hint();
|
|
161
|
+
})();
|
|
@@ -4,7 +4,14 @@ module ChronoForge
|
|
|
4
4
|
skip_before_action :authenticate!
|
|
5
5
|
skip_forgery_protection
|
|
6
6
|
|
|
7
|
-
TYPES = {
|
|
7
|
+
TYPES = {
|
|
8
|
+
"dashboard.css" => "text/css",
|
|
9
|
+
"dashboard.js" => "application/javascript",
|
|
10
|
+
"cytoscape.min.js" => "application/javascript",
|
|
11
|
+
"dagre.min.js" => "application/javascript",
|
|
12
|
+
"cytoscape-dagre.js" => "application/javascript",
|
|
13
|
+
"definition_graph.js" => "application/javascript"
|
|
14
|
+
}.freeze
|
|
8
15
|
ROOT = ChronoForge::Dashboard::Engine.root.join("app/assets/chrono_forge/dashboard")
|
|
9
16
|
|
|
10
17
|
def show
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module ChronoForge
|
|
2
|
+
module Dashboard
|
|
3
|
+
# Per-run view of a workflow's statically-analyzed definition graph, with the
|
|
4
|
+
# run's execution logs overlaid onto the nodes. Analysis is best-effort: an
|
|
5
|
+
# unknown/unloadable class or an unanalyzable perform yields a warning, not a
|
|
6
|
+
# 500, so the page always renders.
|
|
7
|
+
class DefinitionsController < BaseController
|
|
8
|
+
def show
|
|
9
|
+
# Opt out of the auto-refresh region swap: it replaces the page's HTML in
|
|
10
|
+
# place, which wipes the live Cytoscape canvas (injected <script>s don't
|
|
11
|
+
# re-run), leaving a blank graph. This page reloads fully instead.
|
|
12
|
+
@cf_disable_polling = true
|
|
13
|
+
@workflow = ChronoForge::Workflow.find(params[:id])
|
|
14
|
+
definition = analyze(@workflow)
|
|
15
|
+
overlay = DefinitionOverlay.new(definition, @workflow)
|
|
16
|
+
@nodes = overlay.nodes
|
|
17
|
+
@warnings = overlay.warnings
|
|
18
|
+
@graph = CytoscapeGraph.new(@nodes, definition.edges).to_h
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
# DefinitionAnalyzer.call always returns a Definition (an `unavailable` one on
|
|
24
|
+
# any analysis failure), so the only gap is a class that won't constantize.
|
|
25
|
+
def analyze(workflow)
|
|
26
|
+
ChronoForge::DefinitionAnalyzer.call(workflow.job_class.constantize)
|
|
27
|
+
rescue NameError
|
|
28
|
+
ChronoForge::Definition.new(
|
|
29
|
+
warnings: ["workflow class #{workflow.job_class} is not loadable and " \
|
|
30
|
+
"cannot be statically analyzed"]
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -2,10 +2,14 @@ module ChronoForge
|
|
|
2
2
|
module Dashboard
|
|
3
3
|
class WorkflowsController < BaseController
|
|
4
4
|
def index
|
|
5
|
-
@
|
|
5
|
+
@hide_branches = params[:hide_branches] != "0" # on by default
|
|
6
|
+
@query = WorkflowsQuery.new(**list_params, exclude_branched: @hide_branches)
|
|
6
7
|
@workflows = @query.records
|
|
7
8
|
@waits = WaitStatePresenter.active_map(@workflows)
|
|
8
|
-
|
|
9
|
+
# Stats track the toggle so the counts match the visible list — a large
|
|
10
|
+
# fan-out's children don't dominate the totals while hidden from the list.
|
|
11
|
+
stats_base = @hide_branches ? ChronoForge::Workflow.where(parent_execution_log_id: nil) : ChronoForge::Workflow.all
|
|
12
|
+
stats = StatsQuery.new(base: stats_base)
|
|
9
13
|
@stats = stats.counts
|
|
10
14
|
@stats_cap = stats.cap
|
|
11
15
|
end
|
|
@@ -32,6 +32,23 @@ module ChronoForge
|
|
|
32
32
|
tag.span(class: "cf-dot cf-dot-#{state}")
|
|
33
33
|
end
|
|
34
34
|
|
|
35
|
+
# A filter chip: optional colored state dot, a label, and an optional capped
|
|
36
|
+
# count, with active styling. Shared by the index stats header and the
|
|
37
|
+
# branch-children filter row. The caller computes +href+ (the two views build
|
|
38
|
+
# different URLs); +dot+ is a state name for the colored dot, or nil for none
|
|
39
|
+
# (e.g. the "all" chip, or a composite like "blocked").
|
|
40
|
+
def cf_filter_chip(href, label:, active:, count: nil, cap: nil, dot: nil)
|
|
41
|
+
classes = "cf-stat flex items-center gap-2 rounded-md border px-3 py-1.5 text-sm transition " +
|
|
42
|
+
(active ? "border-zinc-900 bg-zinc-50" : "border-zinc-200 bg-white hover:bg-zinc-50")
|
|
43
|
+
link_to href, class: classes do
|
|
44
|
+
safe_join([
|
|
45
|
+
dot ? cf_dot(dot) : "",
|
|
46
|
+
tag.span(label, class: "text-zinc-500"),
|
|
47
|
+
count ? tag.span(cf_capped(count, cap), class: "font-mono font-medium tabular-nums") : ""
|
|
48
|
+
])
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
35
52
|
def cf_time(t)
|
|
36
53
|
t&.iso8601 || "—"
|
|
37
54
|
end
|
|
@@ -61,6 +78,12 @@ module ChronoForge
|
|
|
61
78
|
(secs % 60 == 0) ? "#{secs / 60}m" : "#{secs}s"
|
|
62
79
|
end
|
|
63
80
|
|
|
81
|
+
# Whether the main region opts into auto-refresh. A page sets
|
|
82
|
+
# @cf_disable_polling to opt OUT (e.g. the definition graph, whose live
|
|
83
|
+
# Cytoscape canvas can't survive the poll's innerHTML region swap). Without a
|
|
84
|
+
# [data-poll-region] the JS never starts a poll timer for the page.
|
|
85
|
+
def cf_poll_region? = !@cf_disable_polling
|
|
86
|
+
|
|
64
87
|
# A timestamp shown relative ("3 minutes ago") or absolute (raw ISO8601)
|
|
65
88
|
# per the viewer's preference, with the other form available on hover.
|
|
66
89
|
def cf_ago(t)
|
|
@@ -71,6 +94,16 @@ module ChronoForge
|
|
|
71
94
|
tag.span(shown, title: hover, class: "cursor-help")
|
|
72
95
|
end
|
|
73
96
|
|
|
97
|
+
# Like cf_ago but direction-aware: future times read "in 3 minutes", past
|
|
98
|
+
# times "3 minutes ago" — for things like a poller's next scheduled check.
|
|
99
|
+
def cf_when(t)
|
|
100
|
+
return "—" unless t
|
|
101
|
+
rel = t.future? ? "in #{time_ago_in_words(t)}" : "#{time_ago_in_words(t)} ago"
|
|
102
|
+
abs = t.iso8601
|
|
103
|
+
shown, hover = cf_absolute_time? ? [abs, rel] : [rel, abs]
|
|
104
|
+
tag.span(shown, title: hover, class: "cursor-help")
|
|
105
|
+
end
|
|
106
|
+
|
|
74
107
|
# Human duration between two times (e.g. "1m 04s"); "—" if unfinished.
|
|
75
108
|
def cf_duration(from, to)
|
|
76
109
|
return "—" unless from && to
|
|
@@ -20,8 +20,10 @@ module ChronoForge
|
|
|
20
20
|
# The branch is "sealed" once its block closed (done dispatching children).
|
|
21
21
|
def sealed? = @log.completed?
|
|
22
22
|
|
|
23
|
-
def
|
|
23
|
+
def spawned = capped(children)
|
|
24
|
+
|
|
24
25
|
def pending = capped(children.where.not(state: ChronoForge::Workflow.states[:completed]))
|
|
26
|
+
|
|
25
27
|
def blocked = capped(children.where(state: BLOCKED_STATES))
|
|
26
28
|
|
|
27
29
|
def cap = CAP
|
|
@@ -35,8 +37,11 @@ module ChronoForge
|
|
|
35
37
|
# The BranchMergeJob stamps its poll state onto the branch log's metadata
|
|
36
38
|
# (it can't be queried from the backend; ActiveJob has no such API).
|
|
37
39
|
def polled? = poll.present?
|
|
40
|
+
|
|
38
41
|
def last_polled_at = parse_time(poll&.dig("last_polled_at"))
|
|
42
|
+
|
|
39
43
|
def next_poll_at = parse_time(poll&.dig("next_poll_at"))
|
|
44
|
+
|
|
40
45
|
def polls = poll&.dig("polls").to_i
|
|
41
46
|
|
|
42
47
|
# next_poll_at is nil once the merge completes, so a finished merge never
|
|
@@ -46,6 +51,38 @@ module ChronoForge
|
|
|
46
51
|
t.present? && t < Time.current - POLL_OVERDUE_GRACE
|
|
47
52
|
end
|
|
48
53
|
|
|
54
|
+
# Live throughput/ETA from the last poll (the poller measures the branch's
|
|
55
|
+
# completion rate each pass). The wake poll records rate 0, so a positive rate
|
|
56
|
+
# means the branch is still draining — a merged/idle branch shows no gauge.
|
|
57
|
+
def rate = poll&.dig("rate").to_f
|
|
58
|
+
|
|
59
|
+
def eta_seconds = poll&.dig("eta_seconds")
|
|
60
|
+
|
|
61
|
+
def throughput? = rate > 0
|
|
62
|
+
|
|
63
|
+
# Children dispatched but not yet started (idle, started_at nil) — how much of
|
|
64
|
+
# this branch hasn't been picked up yet. Capped/index-only like the other counts.
|
|
65
|
+
def never_started = capped(children.where(state: ChronoForge::Workflow.states[:idle], started_at: nil))
|
|
66
|
+
|
|
67
|
+
# The FULL (uncapped) pending / never-started counts the poller ALREADY records
|
|
68
|
+
# each pass — so the dashboard can show the real number instead of the capped
|
|
69
|
+
# "CAP+", with NO new query. nil until the branch has been polled; callers fall
|
|
70
|
+
# back to the capped count. (The poll's "never_started" is the never-started count.)
|
|
71
|
+
def exact_pending = poll&.dig("pending")
|
|
72
|
+
|
|
73
|
+
def exact_never_started = poll&.dig("never_started")
|
|
74
|
+
|
|
75
|
+
# Total spawned, counted once and cached by the poller when the branch sealed
|
|
76
|
+
# (see BranchProbe#spawned) — exact and free. nil until sealed+polled; the
|
|
77
|
+
# "Spawned" column then falls back to the capped live count.
|
|
78
|
+
def exact_spawned = poll&.dig("spawned")
|
|
79
|
+
|
|
80
|
+
# Dropped-child recovery: how many children the poller has rekicked, and when
|
|
81
|
+
# it last did (nil if never).
|
|
82
|
+
def rekicks = poll&.dig("rekick_total").to_i
|
|
83
|
+
|
|
84
|
+
def last_rekick_at = parse_time(poll&.dig("last_rekick_at"))
|
|
85
|
+
|
|
49
86
|
private
|
|
50
87
|
|
|
51
88
|
def children = @log.spawned_workflows
|
|
@@ -6,8 +6,19 @@ module ChronoForge
|
|
|
6
6
|
class BranchesPresenter
|
|
7
7
|
# One merge join (a merge$<names> log = a BranchMergeJob's durable target).
|
|
8
8
|
# state: :merging (pending — a poller is joining) | :merged (completed).
|
|
9
|
-
|
|
9
|
+
# The poll fields are the BranchMergeJob's observable cadence (it stamps them
|
|
10
|
+
# on the target branch logs each pass): when it last checked, when it's
|
|
11
|
+
# scheduled to check next, and how many times it has polled.
|
|
12
|
+
Merge = Struct.new(:names, :state, :started_at, :last_polled_at, :next_poll_at, :polls, :rate, :eta_seconds) do
|
|
10
13
|
def merging? = state == :merging
|
|
14
|
+
|
|
15
|
+
# A next check scheduled in the past while still merging means the poller
|
|
16
|
+
# was dropped (or is overdue) — the join is stuck until it's re-armed.
|
|
17
|
+
def poll_overdue? = merging? && next_poll_at && next_poll_at.past?
|
|
18
|
+
|
|
19
|
+
# Throughput is a live gauge — only meaningful while merging and actually
|
|
20
|
+
# draining (rate 0.0 means idle; a merged join has no live rate).
|
|
21
|
+
def throughput? = merging? && rate.to_f > 0
|
|
11
22
|
end
|
|
12
23
|
|
|
13
24
|
def initialize(workflow) = @workflow = workflow
|
|
@@ -23,13 +34,57 @@ module ChronoForge
|
|
|
23
34
|
# The merge joins on this workflow, in-progress first. A long-pending merge
|
|
24
35
|
# with no blocked children is the sign of a dropped BranchMergeJob poller.
|
|
25
36
|
def merges
|
|
26
|
-
@merges ||= merge_logs
|
|
27
|
-
|
|
28
|
-
|
|
37
|
+
@merges ||= merge_logs.map { |log|
|
|
38
|
+
names = StepNameParser.parse(log.step_name).name.split(",")
|
|
39
|
+
poll = merge_poll(names)
|
|
40
|
+
rate, eta = merge_throughput(names)
|
|
41
|
+
Merge.new(
|
|
42
|
+
names,
|
|
43
|
+
log.completed? ? :merged : :merging,
|
|
44
|
+
log.started_at,
|
|
45
|
+
parse_time(poll&.dig("last_polled_at")),
|
|
46
|
+
parse_time(poll&.dig("next_poll_at")),
|
|
47
|
+
poll&.dig("polls"),
|
|
48
|
+
rate,
|
|
49
|
+
eta
|
|
50
|
+
)
|
|
51
|
+
}.sort_by { |m| [m.merging? ? 0 : 1, m.started_at || Time.current] }
|
|
29
52
|
end
|
|
30
53
|
|
|
31
54
|
private
|
|
32
55
|
|
|
56
|
+
# Shared cadence timestamps for a merge. BranchMergeJob writes the SAME
|
|
57
|
+
# last_polled_at/next_poll_at/polls to every target branch each pass, so any
|
|
58
|
+
# one works; take the latest by last_polled_at to be safe. (rate/eta_seconds
|
|
59
|
+
# are per-branch, so they are aggregated separately — see #merge_throughput.)
|
|
60
|
+
def merge_poll(names)
|
|
61
|
+
names.filter_map { |nm| branch_poll_by_name[nm] }.max_by { |p| p["last_polled_at"].to_s }
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Live throughput/ETA for the whole merge. A merge may join several branches
|
|
65
|
+
# (merge_branches(*names)), and each branch records its OWN rate/pending, so
|
|
66
|
+
# the merge's rate is their SUM and its ETA is the combined remaining over the
|
|
67
|
+
# combined rate — not any single branch's figure. Returns [rate, eta_seconds];
|
|
68
|
+
# rate 0.0 / eta nil when nothing is draining.
|
|
69
|
+
def merge_throughput(names)
|
|
70
|
+
polls = names.filter_map { |nm| branch_poll_by_name[nm] }
|
|
71
|
+
rate = polls.sum { |p| p["rate"].to_f }
|
|
72
|
+
return [0.0, nil] unless rate > 0
|
|
73
|
+
pending = polls.sum { |p| p["pending"].to_i }
|
|
74
|
+
[rate, (pending / rate).round]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def branch_poll_by_name
|
|
78
|
+
@branch_poll_by_name ||= branch_logs.each_with_object({}) do |log, h|
|
|
79
|
+
h[StepNameParser.parse(log.step_name).name] = log.metadata&.dig("poll")
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def parse_time(value)
|
|
84
|
+
return nil if value.blank?
|
|
85
|
+
value.is_a?(Time) ? value : Time.zone.parse(value.to_s)
|
|
86
|
+
end
|
|
87
|
+
|
|
33
88
|
def coordination_logs
|
|
34
89
|
d = StepNameParser::DELIM
|
|
35
90
|
@coordination_logs ||= @workflow.execution_logs
|