hammerkit 2.0.0-next.4 → 2.0.0-next.5
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.
- package/dist/dashboard/assets/index-D51NA6-h.js +14 -0
- package/dist/dashboard/assets/index-mmvC6u9O.css +2 -0
- package/dist/dashboard/index.html +3 -3
- package/dist/dashboard/logo.png +0 -0
- package/dist/remote/remote-work-commands.js +11 -2
- package/dist/server/dashboard.js +25 -7
- package/dist/server/endpoints.js +10 -1
- package/dist/server/oauth.js +16 -1
- package/dist/server/overview.js +41 -5
- package/dist/server/runs.js +25 -2
- package/dist/server/server.js +36 -4
- package/dist/server/service-runs.js +8 -1
- package/dist/server/timings.js +23 -3
- package/dist/utils/web-links.js +34 -0
- package/package.json +1 -1
- package/dist/dashboard/assets/index-CjnKm5-3.css +0 -2
- package/dist/dashboard/assets/index-zPXa4klZ.js +0 -13
- package/dist/dashboard/favicon.svg +0 -1
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
/*! tailwindcss v4.3.5 | MIT License | https://tailwindcss.com */
|
|
2
|
+
@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:block))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-translate-x:0;--tw-translate-y:0;--tw-translate-z: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-leading:initial}}}@layer theme{:root,:host{--font-sans:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-50:oklch(97.1% .013 17.38);--color-red-300:oklch(80.8% .114 19.571);--color-red-400:oklch(70.4% .191 22.216);--color-red-600:oklch(57.7% .245 27.325);--color-red-700:oklch(50.5% .213 27.518);--color-red-900:oklch(39.6% .141 25.723);--color-red-950:oklch(25.8% .092 26.042);--color-amber-50:oklch(98.7% .022 95.277);--color-amber-300:oklch(87.9% .169 91.605);--color-amber-400:oklch(82.8% .189 84.429);--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-950:oklch(27.9% .077 45.635);--color-emerald-50:oklch(97.9% .021 166.113);--color-emerald-200:oklch(90.5% .093 164.15);--color-emerald-300:oklch(84.5% .143 164.978);--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-emerald-950:oklch(26.2% .051 172.552);--color-sky-50:oklch(97.7% .013 236.62);--color-sky-300:oklch(82.8% .111 230.318);--color-sky-400:oklch(74.6% .16 232.661);--color-sky-600:oklch(58.8% .158 241.966);--color-sky-700:oklch(50% .134 242.749);--color-sky-950:oklch(29.3% .066 243.157);--color-stone-50:oklch(98.5% .001 106.423);--color-stone-100:oklch(97% .001 106.424);--color-stone-200:oklch(92.3% .003 48.717);--color-stone-300:oklch(86.9% .005 56.366);--color-stone-400:oklch(70.9% .01 56.259);--color-stone-500:oklch(55.3% .013 58.071);--color-stone-600:oklch(44.4% .011 73.639);--color-stone-700:oklch(37.4% .01 67.558);--color-stone-800:oklch(26.8% .007 34.298);--color-stone-900:oklch(21.6% .006 56.043);--color-stone-950:oklch(14.7% .004 49.25);--color-white:#fff;--spacing:.25rem;--container-xs:20rem;--container-sm:24rem;--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-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;--leading-relaxed:1.625;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--animate-spin:spin 1s linear infinite;--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,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, 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;-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:where(:not(iframe)){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}html{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;scrollbar-gutter:stable}@media (prefers-color-scheme:dark){html{--lightningcss-light: ;--lightningcss-dark:initial}}body{background-color:var(--color-stone-50);color:var(--color-stone-900);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}@media (prefers-color-scheme:dark){body{background-color:var(--color-stone-950);color:var(--color-stone-100)}}}@layer components{.link{color:var(--color-emerald-700);text-underline-offset:2px}@media (hover:hover){.link:hover{text-decoration-line:underline}}@media (prefers-color-scheme:dark){.link{color:var(--color-emerald-400)}}.log{border-radius:var(--radius-lg);background-color:var(--color-stone-950);padding:calc(var(--spacing) * 3);font-family:var(--font-mono);font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height));--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed);white-space:pre;color:var(--color-stone-200);overflow-x:auto}@media (prefers-color-scheme:dark){.log{border-style:var(--tw-border-style);border-width:1px;border-color:var(--color-stone-800)}}}@layer utilities{.pointer-events-none{pointer-events:none}.invisible{visibility:hidden}.visible{visibility:visible}.absolute{position:absolute}.fixed{position:fixed}.relative{position:relative}.inset-y-0{inset-block:0}.right-0{right:0}.left-0{left:0}.z-20{z-index:20}.z-50{z-index:50}.order-last{order:9999}.col-span-2{grid-column:span 2/span 2}.container{width:100%}@media (width>=40rem){.container{max-width:40rem}}@media (width>=48rem){.container{max-width:48rem}}@media (width>=64rem){.container{max-width:64rem}}@media (width>=80rem){.container{max-width:80rem}}@media (width>=96rem){.container{max-width:96rem}}.mx-3{margin-inline:calc(var(--spacing) * 3)}.mx-auto{margin-inline:auto}.my-1{margin-block:var(--spacing)}.my-2{margin-block:calc(var(--spacing) * 2)}.mt-1{margin-top:var(--spacing)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mr-1{margin-right:var(--spacing)}.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-6{margin-bottom:calc(var(--spacing) * 6)}.mb-8{margin-bottom:calc(var(--spacing) * 8)}.-ml-1{margin-left:calc(var(--spacing) * -1)}.ml-1{margin-left:var(--spacing)}.ml-auto{margin-left:auto}.contents{display:contents}.flex{display:flex}.grid{display:grid}.inline{display:inline}.inline-flex{display:inline-flex}.table{display:table}.h-2{height:calc(var(--spacing) * 2)}.h-3\.5{height:calc(var(--spacing) * 3.5)}.h-4{height:calc(var(--spacing) * 4)}.h-5{height:calc(var(--spacing) * 5)}.h-6{height:calc(var(--spacing) * 6)}.h-7{height:calc(var(--spacing) * 7)}.max-h-80{max-height:calc(var(--spacing) * 80)}.min-h-screen{min-height:100vh}.w-3\.5{width:calc(var(--spacing) * 3.5)}.w-4{width:calc(var(--spacing) * 4)}.w-5{width:calc(var(--spacing) * 5)}.w-6{width:calc(var(--spacing) * 6)}.w-7{width:calc(var(--spacing) * 7)}.w-10{width:calc(var(--spacing) * 10)}.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-72{max-width:calc(var(--spacing) * 72)}.max-w-80{max-width:calc(var(--spacing) * 80)}.max-w-\[calc\(100vw-2rem\)\]{max-width:calc(100vw - 2rem)}.max-w-full{max-width:100%}.min-w-0{min-width:0}.min-w-48{min-width:calc(var(--spacing) * 48)}.flex-1{flex:1}.shrink-0{flex-shrink:0}.origin-center{transform-origin:50%}.-translate-x-full{--tw-translate-x:-100%;translate:var(--tw-translate-x) var(--tw-translate-y)}.rotate-90{rotate:90deg}.animate-spin{animation:var(--animate-spin)}.cursor-default{cursor:default}.cursor-pointer{cursor:pointer}.list-disc{list-style-type:disc}.grid-cols-1{grid-template-columns:repeat(1,minmax(0,1fr))}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[9rem_1fr\]{grid-template-columns:9rem 1fr}.grid-cols-\[auto_1fr\]{grid-template-columns:auto 1fr}.grid-cols-\[auto_auto\]{grid-template-columns:auto auto}.grid-cols-\[max-content_1fr\]{grid-template-columns:max-content 1fr}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.items-baseline{align-items:baseline}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.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)}.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)}: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-stone-100>:not(:last-child)){border-color:var(--color-stone-100)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.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-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-amber-500\/50{border-color:#f99c0080}@supports (color:color-mix(in lab, red, red)){.border-amber-500\/50{border-color:color-mix(in oklab, var(--color-amber-500) 50%, transparent)}}.border-amber-600\/40{border-color:#dd740066}@supports (color:color-mix(in lab, red, red)){.border-amber-600\/40{border-color:color-mix(in oklab, var(--color-amber-600) 40%, transparent)}}.border-emerald-600{border-color:var(--color-emerald-600)}.border-emerald-600\/40{border-color:#00976766}@supports (color:color-mix(in lab, red, red)){.border-emerald-600\/40{border-color:color-mix(in oklab, var(--color-emerald-600) 40%, transparent)}}.border-emerald-600\/50{border-color:#00976780}@supports (color:color-mix(in lab, red, red)){.border-emerald-600\/50{border-color:color-mix(in oklab, var(--color-emerald-600) 50%, transparent)}}.border-emerald-700{border-color:var(--color-emerald-700)}.border-red-300{border-color:var(--color-red-300)}.border-red-600\/40{border-color:#e4001466}@supports (color:color-mix(in lab, red, red)){.border-red-600\/40{border-color:color-mix(in oklab, var(--color-red-600) 40%, transparent)}}.border-sky-600\/40{border-color:#0084cc66}@supports (color:color-mix(in lab, red, red)){.border-sky-600\/40{border-color:color-mix(in oklab, var(--color-sky-600) 40%, transparent)}}.border-stone-200{border-color:var(--color-stone-200)}.border-stone-300{border-color:var(--color-stone-300)}.border-transparent{border-color:#0000}.bg-amber-50{background-color:var(--color-amber-50)}.bg-amber-500{background-color:var(--color-amber-500)}.bg-amber-700{background-color:var(--color-amber-700)}.bg-emerald-50{background-color:var(--color-emerald-50)}.bg-emerald-600{background-color:var(--color-emerald-600)}.bg-emerald-600\/30{background-color:#0097674d}@supports (color:color-mix(in lab, red, red)){.bg-emerald-600\/30{background-color:color-mix(in oklab, var(--color-emerald-600) 30%, transparent)}}.bg-red-50{background-color:var(--color-red-50)}.bg-sky-50{background-color:var(--color-sky-50)}.bg-stone-50{background-color:var(--color-stone-50)}.bg-stone-50\/60{background-color:#fafaf999}@supports (color:color-mix(in lab, red, red)){.bg-stone-50\/60{background-color:color-mix(in oklab, var(--color-stone-50) 60%, transparent)}}.bg-stone-200{background-color:var(--color-stone-200)}.bg-white{background-color:var(--color-white)}.fill-current{fill:currentColor}.fill-none{fill:none}.stroke-current{stroke:currentColor}.stroke-none{stroke:none}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.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-3{padding-block:calc(var(--spacing) * 3)}.py-6{padding-block:calc(var(--spacing) * 6)}.pt-1{padding-top:var(--spacing)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pb-0\.5{padding-bottom:calc(var(--spacing) * .5)}.pb-3{padding-bottom:calc(var(--spacing) * 3)}.pl-5{padding-left:calc(var(--spacing) * 5)}.pl-9{padding-left:calc(var(--spacing) * 9)}.text-left{text-align:left}.text-right{text-align:right}.align-middle{vertical-align:middle}.align-top{vertical-align:top}.font-mono{font-family:var(--font-mono)}.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-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[0\.7rem\]{font-size:.7rem}.text-\[0\.85em\]{font-size:.85em}.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)}.break-normal{overflow-wrap:normal;word-break:normal}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-normal{white-space:normal}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-emerald-400{color:var(--color-emerald-400)}.text-emerald-700{color:var(--color-emerald-700)}.text-emerald-800{color:var(--color-emerald-800)}.text-red-300{color:var(--color-red-300)}.text-red-400{color:var(--color-red-400)}.text-red-600{color:var(--color-red-600)}.text-red-700{color:var(--color-red-700)}.text-sky-400{color:var(--color-sky-400)}.text-sky-700{color:var(--color-sky-700)}.text-stone-400{color:var(--color-stone-400)}.text-stone-500{color:var(--color-stone-500)}.text-stone-600{color:var(--color-stone-600)}.text-stone-700{color:var(--color-stone-700)}.text-stone-900{color:var(--color-stone-900)}.text-white{color:var(--color-white)}.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}.decoration-stone-400\/40{text-decoration-color:#a6a09b66}@supports (color:color-mix(in lab, red, red)){.decoration-stone-400\/40{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-400) 40%, transparent)}}.decoration-stone-400\/70{text-decoration-color:#a6a09bb3}@supports (color:color-mix(in lab, red, red)){.decoration-stone-400\/70{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-400) 70%, transparent)}}.decoration-dotted{text-decoration-style:dotted}.underline-offset-2{text-underline-offset:2px}.underline-offset-4{text-underline-offset:4px}.accent-emerald-600{accent-color:var(--color-emerald-600)}.opacity-60{opacity:.6}.shadow-lg{--tw-shadow:0 10px 15px -3px var(--tw-shadow-color,#0000001a), 0 4px 6px -4px 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-xs{--tw-shadow:0 1px 2px 0 var(--tw-shadow-color,#0000000d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-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-transform{transition-property:transform,translate,scale,rotate;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}@media (hover:hover){.hover\:border-stone-400:hover{border-color:var(--color-stone-400)}.hover\:bg-amber-600:hover{background-color:var(--color-amber-600)}.hover\:bg-emerald-50:hover{background-color:var(--color-emerald-50)}.hover\:bg-emerald-700:hover{background-color:var(--color-emerald-700)}.hover\:bg-red-50:hover{background-color:var(--color-red-50)}.hover\:bg-stone-50:hover{background-color:var(--color-stone-50)}.hover\:bg-stone-100:hover{background-color:var(--color-stone-100)}.hover\:text-stone-900:hover{color:var(--color-stone-900)}.hover\:underline:hover{text-decoration-line:underline}.hover\:decoration-current:hover{text-decoration-color:currentColor}}.focus-visible\:rounded-sm:focus-visible{border-radius:var(--radius-sm)}.focus-visible\:ring-2: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)}.focus-visible\:ring-emerald-500:focus-visible{--tw-ring-color:var(--color-emerald-500)}.disabled\:opacity-50:disabled{opacity:.5}@media (width>=40rem){.sm\:ml-auto{margin-left:auto}.sm\:max-w-sm{max-width:var(--container-sm)}.sm\:max-w-xs{max-width:var(--container-xs)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}}@media (width>=48rem){.md\:order-none{order:0}.md\:w-auto{width:auto}.md\:flex-1{flex:1}}@media (width>=64rem){.lg\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.lg\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.lg\:grid-cols-\[max-content_1fr\]{grid-template-columns:max-content 1fr}}@media (prefers-color-scheme:dark){:where(.dark\:divide-stone-800>:not(:last-child)){border-color:var(--color-stone-800)}.dark\:border-emerald-500{border-color:var(--color-emerald-500)}.dark\:border-emerald-700{border-color:var(--color-emerald-700)}.dark\:border-red-900{border-color:var(--color-red-900)}.dark\:border-stone-700{border-color:var(--color-stone-700)}.dark\:border-stone-800{border-color:var(--color-stone-800)}.dark\:bg-amber-950{background-color:var(--color-amber-950)}.dark\:bg-amber-950\/30{background-color:#4619014d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-amber-950\/30{background-color:color-mix(in oklab, var(--color-amber-950) 30%, transparent)}}.dark\:bg-emerald-400\/30{background-color:#00d2944d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-emerald-400\/30{background-color:color-mix(in oklab, var(--color-emerald-400) 30%, transparent)}}.dark\:bg-emerald-500{background-color:var(--color-emerald-500)}.dark\:bg-emerald-950{background-color:var(--color-emerald-950)}.dark\:bg-red-950{background-color:var(--color-red-950)}.dark\:bg-sky-950{background-color:var(--color-sky-950)}.dark\:bg-stone-800{background-color:var(--color-stone-800)}.dark\:bg-stone-900{background-color:var(--color-stone-900)}.dark\:bg-stone-950\/30{background-color:#0c0a094d}@supports (color:color-mix(in lab, red, red)){.dark\:bg-stone-950\/30{background-color:color-mix(in oklab, var(--color-stone-950) 30%, transparent)}}.dark\:bg-stone-950\/40{background-color:#0c0a0966}@supports (color:color-mix(in lab, red, red)){.dark\:bg-stone-950\/40{background-color:color-mix(in oklab, var(--color-stone-950) 40%, transparent)}}.dark\:text-amber-300{color:var(--color-amber-300)}.dark\:text-amber-400{color:var(--color-amber-400)}.dark\:text-emerald-200{color:var(--color-emerald-200)}.dark\:text-emerald-300{color:var(--color-emerald-300)}.dark\:text-emerald-400{color:var(--color-emerald-400)}.dark\:text-red-300{color:var(--color-red-300)}.dark\:text-red-400{color:var(--color-red-400)}.dark\:text-sky-300{color:var(--color-sky-300)}.dark\:text-stone-200{color:var(--color-stone-200)}.dark\:text-stone-300{color:var(--color-stone-300)}.dark\:text-stone-400{color:var(--color-stone-400)}.dark\:text-stone-500{color:var(--color-stone-500)}.dark\:text-stone-950{color:var(--color-stone-950)}.dark\:text-white{color:var(--color-white)}.dark\:decoration-stone-500\/50{text-decoration-color:#79716b80}@supports (color:color-mix(in lab, red, red)){.dark\:decoration-stone-500\/50{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-500) 50%, transparent)}}.dark\:decoration-stone-500\/70{text-decoration-color:#79716bb3}@supports (color:color-mix(in lab, red, red)){.dark\:decoration-stone-500\/70{-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent);-webkit-text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent);text-decoration-color:color-mix(in oklab, var(--color-stone-500) 70%, transparent)}}@media (hover:hover){.dark\:hover\:border-stone-500:hover{border-color:var(--color-stone-500)}.dark\:hover\:bg-emerald-400:hover{background-color:var(--color-emerald-400)}.dark\:hover\:bg-emerald-950:hover{background-color:var(--color-emerald-950)}.dark\:hover\:bg-red-950:hover{background-color:var(--color-red-950)}.dark\:hover\:bg-stone-700:hover{background-color:var(--color-stone-700)}.dark\:hover\:bg-stone-800:hover{background-color:var(--color-stone-800)}.dark\:hover\:text-white:hover{color:var(--color-white)}}}}@property --tw-translate-x{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-y{syntax:"*";inherits:false;initial-value:0}@property --tw-translate-z{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-leading{syntax:"*";inherits:false}@keyframes spin{to{transform:rotate(360deg)}}
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
<meta charset="utf-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
6
6
|
<meta name="robots" content="noindex" />
|
|
7
|
-
<link rel="icon" href="/
|
|
7
|
+
<link rel="icon" href="/logo.png" type="image/png" />
|
|
8
8
|
<title>hammerkit</title>
|
|
9
|
-
<script type="module" crossorigin src="/assets/index-
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
9
|
+
<script type="module" crossorigin src="/assets/index-D51NA6-h.js"></script>
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/index-mmvC6u9O.css">
|
|
11
11
|
</head>
|
|
12
12
|
<body>
|
|
13
13
|
<div id="root"></div>
|
|
Binary file
|
|
@@ -519,16 +519,25 @@ function registerWorkCommands(remote, context) {
|
|
|
519
519
|
.command('tasks [server]')
|
|
520
520
|
.description('show what the past runs of each task took, over the runs the server keeps: how often it ran, failed and came from the cache, the median and 90th percentile of its time, and how long its runs waited for a place')
|
|
521
521
|
.addOption(new commander_1.Option('--mine', 'only your runs'))
|
|
522
|
+
.addOption(new commander_1.Option('--since <when>', 'only the runs created since then: back from now, such as 24h or 7d, or a time'))
|
|
523
|
+
.addOption(new commander_1.Option('--until <when>', 'only the runs created until then: back from now, or a time'))
|
|
522
524
|
.addOption(new commander_1.Option('--json', 'print it as the server answers it'))
|
|
523
525
|
.action(guarded('Remote tasks was not successful', async (name, options) => {
|
|
524
526
|
const { name: serverName, server, token } = await connect(name);
|
|
525
|
-
const
|
|
527
|
+
const query = new URLSearchParams({ scope: options.mine ? 'mine' : 'visible' });
|
|
528
|
+
if (options.since) {
|
|
529
|
+
query.set('since', options.since);
|
|
530
|
+
}
|
|
531
|
+
if (options.until) {
|
|
532
|
+
query.set('until', options.until);
|
|
533
|
+
}
|
|
534
|
+
const { tasks } = (await (0, server_client_1.callServer)(server, token, 'GET', `/v1/tasks?${query}`)).body;
|
|
526
535
|
if (options.json) {
|
|
527
536
|
environment.stdout.write(`${JSON.stringify(tasks, null, 2)}\n`);
|
|
528
537
|
return;
|
|
529
538
|
}
|
|
530
539
|
if (tasks.length === 0) {
|
|
531
|
-
environment.stdout.write(`${serverName}: no task ran in the runs it keeps\n`);
|
|
540
|
+
environment.stdout.write(`${serverName}: no task ran in the runs it keeps${options.since || options.until ? ' of that period' : ''}\n`);
|
|
532
541
|
return;
|
|
533
542
|
}
|
|
534
543
|
for (const line of (0, remote_status_1.describeTaskStats)(tasks)) {
|
package/dist/server/dashboard.js
CHANGED
|
@@ -5,12 +5,14 @@ const crypto_1 = require("crypto");
|
|
|
5
5
|
const promises_1 = require("fs/promises");
|
|
6
6
|
const path_1 = require("path");
|
|
7
7
|
const http_1 = require("./http");
|
|
8
|
+
const oauth_1 = require("./oauth");
|
|
8
9
|
const policy_1 = require("./policy");
|
|
9
10
|
const overview_1 = require("./overview");
|
|
10
11
|
// Approving and publishing take a sign-in of the last minutes: the groups of a session are
|
|
11
12
|
// those of its sign-in, and somebody taken out of a group must not decide for hours.
|
|
12
13
|
exports.DECIDE_WITHIN_MS = 15 * 60000;
|
|
13
14
|
const RUN = 'run_[A-Za-z0-9_-]{16}';
|
|
15
|
+
const SANDBOX = 'sbx_[A-Za-z0-9_-]{16}';
|
|
14
16
|
// a hash of a build file version, checked by the API
|
|
15
17
|
const HASH = '[A-Za-z0-9:_-]{1,200}';
|
|
16
18
|
const path = (pattern) => new RegExp(`^/v1/${pattern}$`);
|
|
@@ -28,9 +30,19 @@ const READS = [
|
|
|
28
30
|
'proposals',
|
|
29
31
|
`proposals/${HASH}`,
|
|
30
32
|
`proposals/${HASH}/files`,
|
|
33
|
+
// the build files, and the page of the version a task or run ran
|
|
34
|
+
'definitions',
|
|
35
|
+
`definitions/${HASH}`,
|
|
36
|
+
`definitions/${HASH}/files`,
|
|
37
|
+
// the person's sandboxes and what changed in them; not their files, nothing that writes
|
|
38
|
+
'sandboxes',
|
|
39
|
+
`sandboxes/${SANDBOX}`,
|
|
40
|
+
`sandboxes/${SANDBOX}/changes`,
|
|
31
41
|
].map(path);
|
|
32
42
|
// what the buttons do
|
|
33
|
-
const ACTIONS = [`runs/${RUN}/(cancel|extend)`].map(path);
|
|
43
|
+
const ACTIONS = [`runs/${RUN}/(cancel|extend|wake)`].map(path);
|
|
44
|
+
// what the delete buttons remove: a sandbox of the person's
|
|
45
|
+
const REMOVALS = [`sandboxes/${SANDBOX}`].map(path);
|
|
34
46
|
// what needs a recent sign-in
|
|
35
47
|
const DECISIONS = [`approvals/${RUN}/(approve|reject)`, `proposals/${HASH}/(approve|reject)`].map(path);
|
|
36
48
|
exports.HEADERS = {
|
|
@@ -78,9 +90,11 @@ async function api(context, principal, viewer, now, req, res, url) {
|
|
|
78
90
|
if (method !== 'GET') {
|
|
79
91
|
return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow: 'GET' });
|
|
80
92
|
}
|
|
93
|
+
const name = (0, oauth_1.displayName)(principal);
|
|
81
94
|
const session = {
|
|
82
95
|
iss: principal.iss,
|
|
83
96
|
sub: principal.sub,
|
|
97
|
+
...(name ? { name } : {}),
|
|
84
98
|
observe: grant.observe,
|
|
85
99
|
seesOthers: grant.observe || (0, overview_1.seesActivity)(viewer),
|
|
86
100
|
publishes: (0, policy_1.mayPublish)(grant),
|
|
@@ -103,15 +117,19 @@ async function api(context, principal, viewer, now, req, res, url) {
|
|
|
103
117
|
const read = READS.some((pattern) => pattern.test(target));
|
|
104
118
|
const action = ACTIONS.some((pattern) => pattern.test(target));
|
|
105
119
|
const decision = DECISIONS.some((pattern) => pattern.test(target));
|
|
106
|
-
|
|
120
|
+
const removal = REMOVALS.some((pattern) => pattern.test(target));
|
|
121
|
+
if (!read && !action && !decision && !removal) {
|
|
107
122
|
return (0, http_1.send)(res, 404, { error: 'not found' });
|
|
108
123
|
}
|
|
124
|
+
const allow = [read ? 'GET' : '', action || decision ? 'POST' : '', removal ? 'DELETE' : '']
|
|
125
|
+
.filter(Boolean)
|
|
126
|
+
.join(', ');
|
|
109
127
|
if (method === 'GET' || method === 'HEAD') {
|
|
110
128
|
if (!read) {
|
|
111
|
-
return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow
|
|
129
|
+
return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow });
|
|
112
130
|
}
|
|
113
131
|
}
|
|
114
|
-
else if (method === 'POST' && (action || decision)) {
|
|
132
|
+
else if ((method === 'POST' && (action || decision)) || (method === 'DELETE' && removal)) {
|
|
115
133
|
// a page of another site can not set the header, nor read the token
|
|
116
134
|
const given = Buffer.from(String(req.headers['x-hammerkit-csrf'] ?? ''));
|
|
117
135
|
const expected = Buffer.from(csrf);
|
|
@@ -128,7 +146,7 @@ async function api(context, principal, viewer, now, req, res, url) {
|
|
|
128
146
|
context.log(`dashboard ${method} ${target} iss=${principal.iss} sub=${principal.sub}`);
|
|
129
147
|
}
|
|
130
148
|
else {
|
|
131
|
-
return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow
|
|
149
|
+
return (0, http_1.send)(res, 405, { error: 'method not allowed' }, { allow });
|
|
132
150
|
}
|
|
133
151
|
const forwarded = new URL(`${target}${url.search}`, 'http://localhost');
|
|
134
152
|
return context.api(principal, req, res, forwarded);
|
|
@@ -165,8 +183,8 @@ async function asset(context, req, res, pathname) {
|
|
|
165
183
|
const root = context.assets ?? (0, path_1.join)(__dirname, '..', 'dashboard');
|
|
166
184
|
const file = /^\/assets\/[A-Za-z0-9_-][A-Za-z0-9._-]{0,200}$/.test(pathname)
|
|
167
185
|
? pathname.slice(1)
|
|
168
|
-
: pathname === '/
|
|
169
|
-
? '
|
|
186
|
+
: pathname === '/logo.png'
|
|
187
|
+
? 'logo.png'
|
|
170
188
|
: pathname.startsWith('/assets/') || pathname.startsWith('/.')
|
|
171
189
|
? undefined
|
|
172
190
|
: 'index.html';
|
package/dist/server/endpoints.js
CHANGED
|
@@ -285,7 +285,15 @@ class EndpointGateway {
|
|
|
285
285
|
return null;
|
|
286
286
|
}
|
|
287
287
|
const session = this.readSession(label, req.headers.cookie);
|
|
288
|
-
return session
|
|
288
|
+
return session
|
|
289
|
+
? {
|
|
290
|
+
iss: session.iss,
|
|
291
|
+
sub: session.sub,
|
|
292
|
+
groups: session.groups,
|
|
293
|
+
claims: session.claims,
|
|
294
|
+
...(session.name ? { name: session.name } : {}),
|
|
295
|
+
}
|
|
296
|
+
: null;
|
|
289
297
|
}
|
|
290
298
|
// Whether the browser sends the request for another page than the endpoint's own: an
|
|
291
299
|
// upgrade from another origin, or a request a page of another site makes (a frame too),
|
|
@@ -652,6 +660,7 @@ ${run.state === 'up'
|
|
|
652
660
|
host: label,
|
|
653
661
|
iss: issued.principal.iss,
|
|
654
662
|
sub: issued.principal.sub,
|
|
663
|
+
...((0, oauth_1.displayName)(issued.principal) ? { name: (0, oauth_1.displayName)(issued.principal) } : {}),
|
|
655
664
|
groups: issued.principal.groups,
|
|
656
665
|
claims: this.matchedClaims(issued.principal),
|
|
657
666
|
exp,
|
package/dist/server/oauth.js
CHANGED
|
@@ -1,7 +1,22 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.TokenVerifier = exports.quote = exports.TokenError = void 0;
|
|
3
|
+
exports.TokenVerifier = exports.quote = exports.TokenError = exports.displayName = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
|
+
/**
|
|
6
|
+
* How pages name a person: the `name` of their token, else `preferred_username`, else `email`
|
|
7
|
+
* (with the scopes `profile email`); none with `openid` alone, which carries the subject only.
|
|
8
|
+
*/
|
|
9
|
+
function displayName(principal) {
|
|
10
|
+
const claims = principal.claims ?? {};
|
|
11
|
+
for (const value of [principal.name, claims.name, claims.preferred_username, claims.email]) {
|
|
12
|
+
const trimmed = value?.trim();
|
|
13
|
+
if (trimmed) {
|
|
14
|
+
return trimmed.slice(0, 100);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
exports.displayName = displayName;
|
|
5
20
|
// The reason is for the operator's log, never for the caller.
|
|
6
21
|
class TokenError extends Error {
|
|
7
22
|
}
|
package/dist/server/overview.js
CHANGED
|
@@ -5,6 +5,7 @@ const policy_1 = require("./policy");
|
|
|
5
5
|
const runs_1 = require("./runs");
|
|
6
6
|
const service_runs_1 = require("./service-runs");
|
|
7
7
|
const repo_1 = require("./repo");
|
|
8
|
+
const units_1 = require("../utils/units");
|
|
8
9
|
var wait_reason_1 = require("./wait-reason");
|
|
9
10
|
Object.defineProperty(exports, "describeWait", { enumerable: true, get: function () { return wait_reason_1.describeWait; } });
|
|
10
11
|
function getViewer(config, principal) {
|
|
@@ -21,9 +22,14 @@ function runActions(viewer, run) {
|
|
|
21
22
|
}
|
|
22
23
|
const mine = owns(viewer, run);
|
|
23
24
|
const may = {
|
|
24
|
-
...(mine || decides(viewer, run)
|
|
25
|
+
...(mine || decides(viewer, run) || (run.up && (0, service_runs_1.mayPublishEndpoints)(viewer.grant, run))
|
|
26
|
+
? { stop: true }
|
|
27
|
+
: {}),
|
|
25
28
|
...(run.up && run.upAt && (mine || (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run)) ? { extend: true } : {}),
|
|
26
29
|
...(mayDecide(viewer, run) ? { decide: true } : {}),
|
|
30
|
+
...(run.up && run.state === 'asleep' && (mine || (0, service_runs_1.mayOpenEndpoints)(viewer.grant, run))
|
|
31
|
+
? { wake: true }
|
|
32
|
+
: {}),
|
|
27
33
|
};
|
|
28
34
|
return Object.keys(may).length > 0 ? may : undefined;
|
|
29
35
|
}
|
|
@@ -102,7 +108,9 @@ function toListedRun(viewer, run, queue) {
|
|
|
102
108
|
]));
|
|
103
109
|
return {
|
|
104
110
|
...rest,
|
|
105
|
-
...(mine
|
|
111
|
+
...(mine
|
|
112
|
+
? { plan, options }
|
|
113
|
+
: { owner: { iss: owner.iss, sub: owner.sub, ...(owner.name ? { name: owner.name } : {}) } }),
|
|
106
114
|
...(up
|
|
107
115
|
? {
|
|
108
116
|
up: {
|
|
@@ -209,7 +217,7 @@ const STATES = [
|
|
|
209
217
|
'rejected',
|
|
210
218
|
];
|
|
211
219
|
// `scope`, `state` (a comma separated list) and `kind` of GET /v1/runs.
|
|
212
|
-
function parseRunFilter(params, grant) {
|
|
220
|
+
function parseRunFilter(params, grant, now = Date.now()) {
|
|
213
221
|
const scope = params.get('scope') ?? 'mine';
|
|
214
222
|
if (scope !== 'mine' && scope !== 'visible' && scope !== 'all') {
|
|
215
223
|
throw new Error('scope: mine, visible or all');
|
|
@@ -234,9 +242,35 @@ function parseRunFilter(params, grant) {
|
|
|
234
242
|
if (id !== undefined && !/^run_[A-Za-z0-9_-]{16}$/.test(id)) {
|
|
235
243
|
throw new Error('id: the id of a run, run_...');
|
|
236
244
|
}
|
|
237
|
-
|
|
245
|
+
const since = parseMoment(params.get('since'), now, 'since');
|
|
246
|
+
const until = parseMoment(params.get('until'), now, 'until');
|
|
247
|
+
if (since !== undefined && until !== undefined && until < since) {
|
|
248
|
+
throw new Error('until: after since');
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
scope,
|
|
252
|
+
...(states ? { states } : {}),
|
|
253
|
+
...(kind ? { kind } : {}),
|
|
254
|
+
...(id ? { id } : {}),
|
|
255
|
+
...(since !== undefined ? { since } : {}),
|
|
256
|
+
...(until !== undefined ? { until } : {}),
|
|
257
|
+
};
|
|
238
258
|
}
|
|
239
259
|
exports.parseRunFilter = parseRunFilter;
|
|
260
|
+
// `7d` back from now, or a time such as `2026-11-01T08:00:00Z`, in ms
|
|
261
|
+
function parseMoment(value, now, name) {
|
|
262
|
+
if (value === null) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
if (/^(\d+(\.\d+)?(ms|s|m|h|d))+$/.test(value)) {
|
|
266
|
+
return now - (0, units_1.parseDuration)(value);
|
|
267
|
+
}
|
|
268
|
+
const at = /^\d{4}-\d{2}-\d{2}/.test(value) ? Date.parse(value) : NaN;
|
|
269
|
+
if (!Number.isFinite(at)) {
|
|
270
|
+
throw new Error(`${name}: a duration back from now, such as 1h, 24h or 7d, or a time, such as 2026-11-01T08:00:00Z`);
|
|
271
|
+
}
|
|
272
|
+
return at;
|
|
273
|
+
}
|
|
240
274
|
class FilterRefused extends Error {
|
|
241
275
|
}
|
|
242
276
|
exports.FilterRefused = FilterRefused;
|
|
@@ -244,7 +278,9 @@ function filterRuns(runs, viewer, filter) {
|
|
|
244
278
|
return runs.filter((run) => (filter.scope === 'mine' ? owns(viewer, run) : maySee(viewer, run)) &&
|
|
245
279
|
(!filter.id || run.id === filter.id) &&
|
|
246
280
|
(!filter.states || filter.states.includes(run.state)) &&
|
|
247
|
-
(!filter.kind || (filter.kind === 'services') === !!run.up)
|
|
281
|
+
(!filter.kind || (filter.kind === 'services') === !!run.up) &&
|
|
282
|
+
(filter.since === undefined || Date.parse(run.createdAt) >= filter.since) &&
|
|
283
|
+
(filter.until === undefined || Date.parse(run.createdAt) <= filter.until));
|
|
248
284
|
}
|
|
249
285
|
exports.filterRuns = filterRuns;
|
|
250
286
|
// The machines and what the runs take, with whose the namespaces are where the caller may
|
package/dist/server/runs.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.unavailableExecutor = exports.RunRegistry = exports.MAX_TASK_ERROR = exports.MAX_CAUSES = exports.SANDBOX_ID_PATTERN = exports.RUN_ID_PATTERN = exports.QuotaError = exports.RunError = exports.isFinal = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
|
+
const oauth_1 = require("./oauth");
|
|
5
6
|
const run_logs_1 = require("./run-logs");
|
|
6
7
|
const units_1 = require("../utils/units");
|
|
7
8
|
const FINAL_STATES = ['succeeded', 'failed', 'cancelled', 'rejected'];
|
|
@@ -192,12 +193,14 @@ class RunRegistry {
|
|
|
192
193
|
this.checkUpLimits(owner, spec.up.name);
|
|
193
194
|
this.checkHosts(owner, spec.up);
|
|
194
195
|
}
|
|
196
|
+
const name = (0, oauth_1.displayName)(owner);
|
|
195
197
|
const run = {
|
|
196
198
|
...spec,
|
|
197
199
|
id: `run_${(0, crypto_1.randomBytes)(12).toString('base64url')}`,
|
|
198
200
|
owner: {
|
|
199
201
|
iss: owner.iss,
|
|
200
202
|
sub: owner.sub,
|
|
203
|
+
...(name ? { name } : {}),
|
|
201
204
|
...(spec.up?.sleepTtl ? { groups: [...(owner.groups ?? [])], claims: { ...(owner.claims ?? {}) } } : {}),
|
|
202
205
|
},
|
|
203
206
|
state: waiting ? 'awaiting-approval' : 'queued',
|
|
@@ -306,8 +309,10 @@ class RunRegistry {
|
|
|
306
309
|
}
|
|
307
310
|
// A queued run is cancelled at once; a running one when its executor stops. A service run
|
|
308
311
|
// that is up stops its services, and says who stopped it.
|
|
309
|
-
|
|
310
|
-
|
|
312
|
+
// Stops a run for its owner, who decides about it, and, a service run, whoever `mayStop` says.
|
|
313
|
+
cancel(owner, id, canUse, mayStop) {
|
|
314
|
+
const other = this.entries.get(id);
|
|
315
|
+
const entry = this.find(owner, id, canUse) ?? (other?.run.up && mayStop?.(this.copy(other.run)) ? other : undefined);
|
|
311
316
|
if (!entry) {
|
|
312
317
|
return undefined;
|
|
313
318
|
}
|
|
@@ -336,6 +341,24 @@ class RunRegistry {
|
|
|
336
341
|
this.expireLater(entry);
|
|
337
342
|
return { status: 'ok', run: this.copy(entry.run) };
|
|
338
343
|
}
|
|
344
|
+
/**
|
|
345
|
+
* Wakes a service run that sleeps, for its owner or who `mayOpen` it, as a request to its
|
|
346
|
+
* endpoints would (visit): with the same check of its owner (`mayWake`) and the same places.
|
|
347
|
+
* Answers why it did not wake when it could not.
|
|
348
|
+
*/
|
|
349
|
+
wake(caller, id, mayOpen, mayWake) {
|
|
350
|
+
const entry = this.entries.get(id);
|
|
351
|
+
if (!entry?.run.up || !(this.owns(caller, entry) || mayOpen?.(this.copy(entry.run)))) {
|
|
352
|
+
return { status: 'not-found' };
|
|
353
|
+
}
|
|
354
|
+
if (entry.run.state !== 'asleep') {
|
|
355
|
+
return { status: 'not-asleep', state: entry.run.state };
|
|
356
|
+
}
|
|
357
|
+
const refused = this.visit(id, mayWake);
|
|
358
|
+
return refused
|
|
359
|
+
? { status: 'refused', reason: refused, run: this.copy(entry.run) }
|
|
360
|
+
: { status: 'ok', run: this.copy(entry.run) };
|
|
361
|
+
}
|
|
339
362
|
/**
|
|
340
363
|
* The service run an endpoint host belongs to: the newest that is up, so one that replaces
|
|
341
364
|
* a run of the same name takes its hosts over as soon as it is up; else the newest that
|
package/dist/server/server.js
CHANGED
|
@@ -109,7 +109,7 @@ function streamEvents(registry, config, principal, id, req, res, url) {
|
|
|
109
109
|
});
|
|
110
110
|
}
|
|
111
111
|
async function handleRuns(config, registry, definitions, sources, sandboxes, planning, repos, endpointUrl, log, principal, req, res, url) {
|
|
112
|
-
const match = /^\/v1\/runs(?:\/([^/]+)(?:\/(cancel|events|logs|extend))?)?$/.exec(url.pathname);
|
|
112
|
+
const match = /^\/v1\/runs(?:\/([^/]+)(?:\/(cancel|events|logs|extend|wake))?)?$/.exec(url.pathname);
|
|
113
113
|
if (!match) {
|
|
114
114
|
return false;
|
|
115
115
|
}
|
|
@@ -148,7 +148,8 @@ async function handleRuns(config, registry, definitions, sources, sandboxes, pla
|
|
|
148
148
|
(0, http_2.send)(res, 405, { error: 'method not allowed' }, { allow: 'POST' });
|
|
149
149
|
return true;
|
|
150
150
|
}
|
|
151
|
-
const
|
|
151
|
+
const grant = (0, policy_1.getGrant)(config.entitlements, principal);
|
|
152
|
+
const run = registry.cancel(principal, id, accountsOf(config, principal), (up) => (0, service_runs_1.mayPublishEndpoints)(grant, up));
|
|
152
153
|
log(`cancel iss=${principal.iss} sub=${principal.sub} run=${id} found=${Boolean(run)}`);
|
|
153
154
|
run ? (0, http_2.send)(res, 200, toRunResponse(run)) : (0, http_2.send)(res, 404, { error: 'not found' });
|
|
154
155
|
}
|
|
@@ -159,6 +160,28 @@ async function handleRuns(config, registry, definitions, sources, sandboxes, pla
|
|
|
159
160
|
}
|
|
160
161
|
await extendRun(config, registry, log, principal, id, req, res);
|
|
161
162
|
}
|
|
163
|
+
else if (action === 'wake') {
|
|
164
|
+
if (method !== 'POST') {
|
|
165
|
+
(0, http_2.send)(res, 405, { error: 'method not allowed' }, { allow: 'POST' });
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
// as a request to one of its endpoints would, with the same check of its owner
|
|
169
|
+
const grant = (0, policy_1.getGrant)(config.entitlements, principal);
|
|
170
|
+
const result = registry.wake(principal, id, (run) => (0, service_runs_1.mayOpenEndpoints)(grant, run), (run) => refuseWake(config, repos, run));
|
|
171
|
+
log(`wake iss=${principal.iss} sub=${principal.sub} run=${id} result=${result.status}`);
|
|
172
|
+
if (result.status === 'not-found') {
|
|
173
|
+
(0, http_2.send)(res, 404, { error: 'not found' });
|
|
174
|
+
}
|
|
175
|
+
else if (result.status === 'not-asleep') {
|
|
176
|
+
(0, http_2.send)(res, 409, { error: `the run is ${result.state}, only a service run that sleeps wakes` });
|
|
177
|
+
}
|
|
178
|
+
else if (result.status === 'refused') {
|
|
179
|
+
(0, http_2.send)(res, 409, { error: result.reason });
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
(0, http_2.send)(res, 200, toRunResponse(result.run));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
162
185
|
else if (method !== 'GET') {
|
|
163
186
|
(0, http_2.send)(res, 405, { error: 'method not allowed' }, { allow: 'GET' });
|
|
164
187
|
}
|
|
@@ -303,7 +326,7 @@ function toRunResponse(run, queue, named = false) {
|
|
|
303
326
|
const { owner, ...visible } = run;
|
|
304
327
|
return {
|
|
305
328
|
...visible,
|
|
306
|
-
...(named ? { owner: { iss: owner.iss, sub: owner.sub } } : {}),
|
|
329
|
+
...(named ? { owner: { iss: owner.iss, sub: owner.sub, ...(owner.name ? { name: owner.name } : {}) } } : {}),
|
|
307
330
|
results: run.results ?? {},
|
|
308
331
|
...(queue ? { queue } : {}),
|
|
309
332
|
};
|
|
@@ -518,8 +541,11 @@ async function submitRun(config, registry, definitions, sources, sandboxes, plan
|
|
|
518
541
|
: undefined;
|
|
519
542
|
// a run that may sleep keeps what its owner is entitled by, for the check of a wake
|
|
520
543
|
const claimNames = new Set(config.entitlements.flatMap((entitlement) => Object.keys(entitlement.claims?.match ?? {})));
|
|
544
|
+
const name = (0, oauth_1.displayName)(principal);
|
|
521
545
|
const owner = {
|
|
522
546
|
...principal,
|
|
547
|
+
// how the pages name them, which the claims kept no longer tell
|
|
548
|
+
...(name ? { name } : {}),
|
|
523
549
|
claims: Object.fromEntries(Object.entries(principal.claims).filter(([name]) => claimNames.has(name))),
|
|
524
550
|
};
|
|
525
551
|
// A service run of the caller of this name that runs the same build file, services and
|
|
@@ -702,7 +728,13 @@ async function handleApi(api, principal, req, res, url) {
|
|
|
702
728
|
return;
|
|
703
729
|
}
|
|
704
730
|
const runs = (0, overview_1.filterRuns)(registry.all(), viewer, filter).map((run) => (0, overview_1.toListedRun)(viewer, run));
|
|
705
|
-
|
|
731
|
+
const oldest = (0, timings_1.getOldestCounted)(runs);
|
|
732
|
+
// where in the repository the build file of the last run is, as its version says
|
|
733
|
+
const tasks = (0, timings_1.getTaskStats)(runs, Date.now()).map((stats) => {
|
|
734
|
+
const entry = stats.last.definition ? api.definitions.find(stats.last.definition)?.entry : undefined;
|
|
735
|
+
return entry ? { ...stats, last: { ...stats.last, entry } } : stats;
|
|
736
|
+
});
|
|
737
|
+
(0, http_2.send)(res, 200, { tasks, ...(oldest ? { oldest } : {}) });
|
|
706
738
|
return;
|
|
707
739
|
}
|
|
708
740
|
if (await (0, approvals_api_1.handleApprovals)(config, registry, log, principal, req, res, url)) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.mayOpenEndpoints = exports.getRunRepo = exports.toRunEndpoints = exports.refuseEndpoints = exports.getEndpointHosts = exports.getServiceRunName = void 0;
|
|
3
|
+
exports.mayPublishEndpoints = exports.mayOpenEndpoints = exports.getRunRepo = exports.toRunEndpoints = exports.refuseEndpoints = exports.getEndpointHosts = exports.getServiceRunName = void 0;
|
|
4
4
|
const crypto_1 = require("crypto");
|
|
5
5
|
const plan_1 = require("./plan");
|
|
6
6
|
const policy_1 = require("./policy");
|
|
@@ -79,4 +79,11 @@ function mayOpenEndpoints(grant, run) {
|
|
|
79
79
|
return repo !== null && (0, policy_1.endpointLevel)(grant, repo) !== null;
|
|
80
80
|
}
|
|
81
81
|
exports.mayOpenEndpoints = mayOpenEndpoints;
|
|
82
|
+
// Who may publish endpoints of the run's repository: they could bring up a run of their own
|
|
83
|
+
// there, so they may stop somebody else's (a preview left running), besides its owner.
|
|
84
|
+
function mayPublishEndpoints(grant, run) {
|
|
85
|
+
const repo = getRunRepo(run);
|
|
86
|
+
return repo !== null && (0, policy_1.endpointLevel)(grant, repo) === 'publish';
|
|
87
|
+
}
|
|
88
|
+
exports.mayPublishEndpoints = mayPublishEndpoints;
|
|
82
89
|
//# sourceMappingURL=service-runs.js.map
|
package/dist/server/timings.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getTaskStats = exports.roughly = exports.ranMs = exports.waitedMs = void 0;
|
|
3
|
+
exports.getTaskStats = exports.getOldestCounted = exports.roughly = exports.ranMs = exports.waitedMs = void 0;
|
|
4
4
|
const at = (iso) => (iso ? Date.parse(iso) : NaN);
|
|
5
5
|
// How long a run waited in the queue for a place: from when it was queued (after its
|
|
6
6
|
// approval, or when it woke) until it started, or until now while it waits.
|
|
@@ -64,9 +64,27 @@ function quantile(sorted, q) {
|
|
|
64
64
|
}
|
|
65
65
|
return sorted[Math.min(sorted.length - 1, Math.max(0, Math.ceil(q * sorted.length) - 1))];
|
|
66
66
|
}
|
|
67
|
+
// a task that ran to its end or came from the cache; one that runs, was skipped or stopped says
|
|
68
|
+
// nothing about what it takes
|
|
69
|
+
const counts = (timing) => timing.status !== 'running' && timing.status !== 'skipped' && timing.status !== 'cancelled';
|
|
70
|
+
/**
|
|
71
|
+
* When the oldest run that getTaskStats counts was created: the start of what its numbers
|
|
72
|
+
* cover. Undefined when it counts none.
|
|
73
|
+
*/
|
|
74
|
+
function getOldestCounted(runs) {
|
|
75
|
+
let oldest;
|
|
76
|
+
for (const run of runs) {
|
|
77
|
+
if (Object.values(run.timings ?? {}).some(counts) && (!oldest || Date.parse(run.createdAt) < Date.parse(oldest))) {
|
|
78
|
+
oldest = run.createdAt;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return oldest;
|
|
82
|
+
}
|
|
83
|
+
exports.getOldestCounted = getOldestCounted;
|
|
67
84
|
/**
|
|
68
85
|
* Per task and repository, newest first: how often it ran and how, what it took the last
|
|
69
|
-
* time,
|
|
86
|
+
* time, the median and 90th percentile of the times it executed, and those times added up.
|
|
87
|
+
* `runs` is newest first.
|
|
70
88
|
*/
|
|
71
89
|
function getTaskStats(runs, now) {
|
|
72
90
|
const groups = new Map();
|
|
@@ -74,7 +92,7 @@ function getTaskStats(runs, now) {
|
|
|
74
92
|
const repo = run.source.repo ?? 'an upload';
|
|
75
93
|
const waited = waitedMs(run, now);
|
|
76
94
|
for (const [task, timing] of Object.entries(run.timings ?? {})) {
|
|
77
|
-
if (timing
|
|
95
|
+
if (!counts(timing)) {
|
|
78
96
|
continue;
|
|
79
97
|
}
|
|
80
98
|
const key = `${repo}\0${task}`;
|
|
@@ -92,6 +110,7 @@ function getTaskStats(runs, now) {
|
|
|
92
110
|
status: timing.status,
|
|
93
111
|
at: timing.finishedAt ?? run.finishedAt ?? run.createdAt,
|
|
94
112
|
...(timing.durationMs !== undefined ? { durationMs: timing.durationMs } : {}),
|
|
113
|
+
...(run.definition ? { definition: run.definition } : {}),
|
|
95
114
|
},
|
|
96
115
|
},
|
|
97
116
|
durations: [],
|
|
@@ -124,6 +143,7 @@ function getTaskStats(runs, now) {
|
|
|
124
143
|
...stats,
|
|
125
144
|
...(median !== undefined ? { medianMs: median } : {}),
|
|
126
145
|
...(p90 !== undefined ? { p90Ms: p90 } : {}),
|
|
146
|
+
...(durations.length > 0 ? { totalMs: durations.reduce((sum, ms) => sum + ms, 0) } : {}),
|
|
127
147
|
...(wait !== undefined ? { medianWaitMs: wait } : {}),
|
|
128
148
|
};
|
|
129
149
|
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.webLinks = void 0;
|
|
4
|
+
const REPO = /^[A-Za-z0-9.-]+(:[0-9]{1,5})?(\/[A-Za-z0-9._~-]+)+$/;
|
|
5
|
+
const COMMIT = /^[0-9a-f]{7,64}$/;
|
|
6
|
+
// a branch keeps its slashes, as the forges read them; the rest of each part is encoded
|
|
7
|
+
const refPath = (ref) => ref.split('/').map(encodeURIComponent).join('/');
|
|
8
|
+
function webLinks(repo) {
|
|
9
|
+
if (!REPO.test(repo)) {
|
|
10
|
+
return undefined;
|
|
11
|
+
}
|
|
12
|
+
const base = `https://${repo}`;
|
|
13
|
+
const host = repo
|
|
14
|
+
.split('/')[0]
|
|
15
|
+
.replace(/:[0-9]+$/, '')
|
|
16
|
+
.toLowerCase();
|
|
17
|
+
const forge = (tree, commits) => ({
|
|
18
|
+
repo: base,
|
|
19
|
+
ref: (ref) => `${base}/${tree}/${refPath(ref)}`,
|
|
20
|
+
commit: (commit) => (COMMIT.test(commit) ? `${base}/${commits}/${commit}` : base),
|
|
21
|
+
});
|
|
22
|
+
if (host === 'github.com' || host.startsWith('github.')) {
|
|
23
|
+
return forge('tree', 'commit');
|
|
24
|
+
}
|
|
25
|
+
if (host.split('.').some((label) => label.includes('gitlab'))) {
|
|
26
|
+
return forge('-/tree', '-/commit');
|
|
27
|
+
}
|
|
28
|
+
if (host === 'bitbucket.org') {
|
|
29
|
+
return forge('src', 'commits');
|
|
30
|
+
}
|
|
31
|
+
return { repo: base };
|
|
32
|
+
}
|
|
33
|
+
exports.webLinks = webLinks;
|
|
34
|
+
//# sourceMappingURL=web-links.js.map
|
package/package.json
CHANGED