@@ -79,6 +79,14 @@ type RunsTableProps = {
7979 showTopBorder ?: boolean ;
8080 stickyHeader ?: boolean ;
8181 childrenStatusesBasePath ?: string ;
82+ /**
83+ * Display-only write:runs flags from the caller's loader. Default true so
84+ * callers that don't pass them (and OSS, where the ability is permissive)
85+ * keep the controls enabled. The cancel/replay action routes enforce
86+ * write:runs regardless.
87+ */
88+ canCancelRuns ?: boolean ;
89+ canReplayRuns ?: boolean ;
8290} ;
8391
8492export function TaskRunsTable ( {
@@ -95,6 +103,8 @@ export function TaskRunsTable({
95103 showTopBorder = true ,
96104 stickyHeader = false ,
97105 childrenStatusesBasePath,
106+ canCancelRuns = true ,
107+ canReplayRuns = true ,
98108} : RunsTableProps ) {
99109 const regions = useRegions ( ) ;
100110 const regionByMasterQueue = new Map ( regions . map ( ( r ) => [ r . masterQueue , r ] as const ) ) ;
@@ -512,7 +522,12 @@ export function TaskRunsTable({
512522 { run . tags . map ( ( tag ) => < RunTag key = { tag } tag = { tag } /> ) || "–" }
513523 </ div >
514524 </ TableCell >
515- < RunActionsCell run = { run } path = { path } />
525+ < RunActionsCell
526+ run = { run }
527+ path = { path }
528+ canCancelRuns = { canCancelRuns }
529+ canReplayRuns = { canReplayRuns }
530+ />
516531 </ TableRow >
517532 ) ;
518533 } )
@@ -530,7 +545,17 @@ export function TaskRunsTable({
530545 ) ;
531546}
532547
533- function RunActionsCell ( { run, path } : { run : NextRunListItem ; path : string } ) {
548+ function RunActionsCell ( {
549+ run,
550+ path,
551+ canCancelRuns,
552+ canReplayRuns,
553+ } : {
554+ run : NextRunListItem ;
555+ path : string ;
556+ canCancelRuns : boolean ;
557+ canReplayRuns : boolean ;
558+ } ) {
534559 const location = useLocation ( ) ;
535560
536561 if ( ! run . isCancellable && ! run . isReplayable ) return < TableCell to = { path } > { "" } </ TableCell > ;
@@ -546,57 +571,85 @@ function RunActionsCell({ run, path }: { run: NextRunListItem; path: string }) {
546571 leadingIconClassName = "text-blue-500"
547572 title = "View run"
548573 />
549- { run . isCancellable && (
550- < Dialog >
551- < DialogTrigger
552- asChild
553- className = "size-6 rounded-sm p-1 text-text-dimmed transition hover:bg-charcoal-700 hover:text-text-bright"
554- >
555- < Button
556- variant = "small-menu-item"
557- LeadingIcon = { NoSymbolIcon }
558- leadingIconClassName = "text-error"
559- fullWidth
560- textAlignLeft
561- className = "w-full px-1.5 py-[0.9rem]"
574+ { run . isCancellable &&
575+ ( canCancelRuns ? (
576+ < Dialog >
577+ < DialogTrigger
578+ asChild
579+ className = "size-6 rounded-sm p-1 text-text-dimmed transition hover:bg-charcoal-700 hover:text-text-bright"
562580 >
563- Cancel run
564- </ Button >
565- </ DialogTrigger >
566- < CancelRunDialog
567- runFriendlyId = { run . friendlyId }
568- redirectPath = { `${ location . pathname } ${ location . search } ` }
569- />
570- </ Dialog >
571- ) }
572- { run . isReplayable && (
573- < Dialog >
574- < DialogTrigger
575- asChild
576- className = "h-6 w-6 rounded-sm p-1 text-text-dimmed transition hover:bg-charcoal-700 hover:text-text-bright"
581+ < Button
582+ variant = "small-menu-item"
583+ LeadingIcon = { NoSymbolIcon }
584+ leadingIconClassName = "text-error"
585+ fullWidth
586+ textAlignLeft
587+ className = "w-full px-1.5 py-[0.9rem]"
588+ >
589+ Cancel run
590+ </ Button >
591+ </ DialogTrigger >
592+ < CancelRunDialog
593+ runFriendlyId = { run . friendlyId }
594+ redirectPath = { `${ location . pathname } ${ location . search } ` }
595+ />
596+ </ Dialog >
597+ ) : (
598+ < Button
599+ variant = "small-menu-item"
600+ LeadingIcon = { NoSymbolIcon }
601+ leadingIconClassName = "text-error"
602+ fullWidth
603+ textAlignLeft
604+ className = "w-full px-1.5 py-[0.9rem]"
605+ disabled
606+ tooltip = "You don't have permission to cancel runs"
577607 >
578- < Button
579- variant = "small-menu-item"
580- LeadingIcon = { ArrowPathIcon }
581- leadingIconClassName = "text-success"
582- fullWidth
583- textAlignLeft
584- className = "w-full px-1.5 py-[0.9rem]"
608+ Cancel run
609+ </ Button >
610+ ) ) }
611+ { run . isReplayable &&
612+ ( canReplayRuns ? (
613+ < Dialog >
614+ < DialogTrigger
615+ asChild
616+ className = "h-6 w-6 rounded-sm p-1 text-text-dimmed transition hover:bg-charcoal-700 hover:text-text-bright"
585617 >
586- Replay run…
587- </ Button >
588- </ DialogTrigger >
589- < ReplayRunDialog
590- runFriendlyId = { run . friendlyId }
591- failedRedirect = { `${ location . pathname } ${ location . search } ` }
592- />
593- </ Dialog >
594- ) }
618+ < Button
619+ variant = "small-menu-item"
620+ LeadingIcon = { ArrowPathIcon }
621+ leadingIconClassName = "text-success"
622+ fullWidth
623+ textAlignLeft
624+ className = "w-full px-1.5 py-[0.9rem]"
625+ >
626+ Replay run…
627+ </ Button >
628+ </ DialogTrigger >
629+ < ReplayRunDialog
630+ runFriendlyId = { run . friendlyId }
631+ failedRedirect = { `${ location . pathname } ${ location . search } ` }
632+ />
633+ </ Dialog >
634+ ) : (
635+ < Button
636+ variant = "small-menu-item"
637+ LeadingIcon = { ArrowPathIcon }
638+ leadingIconClassName = "text-success"
639+ fullWidth
640+ textAlignLeft
641+ className = "w-full px-1.5 py-[0.9rem]"
642+ disabled
643+ tooltip = "You don't have permission to replay runs"
644+ >
645+ Replay run…
646+ </ Button >
647+ ) ) }
595648 </ >
596649 }
597650 hiddenButtons = {
598651 < >
599- { run . isCancellable && (
652+ { run . isCancellable && canCancelRuns && (
600653 < SimpleTooltip
601654 button = {
602655 < Dialog >
@@ -617,10 +670,10 @@ function RunActionsCell({ run, path }: { run: NextRunListItem; path: string }) {
617670 disableHoverableContent
618671 />
619672 ) }
620- { run . isCancellable && run . isReplayable && (
673+ { run . isCancellable && canCancelRuns && run . isReplayable && canReplayRuns && (
621674 < div className = "mx-0.5 h-6 w-px bg-grid-dimmed" />
622675 ) }
623- { run . isReplayable && (
676+ { run . isReplayable && canReplayRuns && (
624677 < SimpleTooltip
625678 button = {
626679 < Dialog >
0 commit comments