@@ -3,6 +3,7 @@ import Star from "@mui/icons-material/Star";
3
3
import Checkbox from "@mui/material/Checkbox" ;
4
4
import Skeleton from "@mui/material/Skeleton" ;
5
5
import { templateVersion } from "api/queries/templates" ;
6
+ import { apiKey } from "api/queries/users" ;
6
7
import {
7
8
cancelBuild ,
8
9
deleteWorkspace ,
@@ -19,6 +20,8 @@ import { Avatar } from "components/Avatar/Avatar";
19
20
import { AvatarData } from "components/Avatar/AvatarData" ;
20
21
import { AvatarDataSkeleton } from "components/Avatar/AvatarDataSkeleton" ;
21
22
import { Button } from "components/Button/Button" ;
23
+ import { VSCodeIcon } from "components/Icons/VSCodeIcon" ;
24
+ import { VSCodeInsidersIcon } from "components/Icons/VSCodeInsidersIcon" ;
22
25
import { InfoTooltip } from "components/InfoTooltip/InfoTooltip" ;
23
26
import { Spinner } from "components/Spinner/Spinner" ;
24
27
import { Stack } from "components/Stack/Stack" ;
@@ -49,7 +52,17 @@ import dayjs from "dayjs";
49
52
import relativeTime from "dayjs/plugin/relativeTime" ;
50
53
import { useAuthenticated } from "hooks" ;
51
54
import { useClickableTableRow } from "hooks/useClickableTableRow" ;
52
- import { BanIcon , PlayIcon , RefreshCcwIcon , SquareIcon } from "lucide-react" ;
55
+ import {
56
+ BanIcon ,
57
+ PlayIcon ,
58
+ RefreshCcwIcon ,
59
+ SquareTerminalIcon ,
60
+ } from "lucide-react" ;
61
+ import {
62
+ getTerminalHref ,
63
+ getVSCodeHref ,
64
+ openAppInNewWindow ,
65
+ } from "modules/apps/apps" ;
53
66
import { useDashboard } from "modules/dashboard/useDashboard" ;
54
67
import { WorkspaceAppStatus } from "modules/workspaces/WorkspaceAppStatus/WorkspaceAppStatus" ;
55
68
import { WorkspaceDormantBadge } from "modules/workspaces/WorkspaceDormantBadge/WorkspaceDormantBadge" ;
@@ -59,6 +72,7 @@ import {
59
72
useWorkspaceUpdate ,
60
73
} from "modules/workspaces/WorkspaceUpdateDialogs" ;
61
74
import { abilitiesByWorkspaceStatus } from "modules/workspaces/actions" ;
75
+ import type React from "react" ;
62
76
import {
63
77
type FC ,
64
78
type PropsWithChildren ,
@@ -534,6 +548,10 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
534
548
return (
535
549
< TableCell >
536
550
< div className = "flex gap-1 justify-end" >
551
+ { workspace . latest_build . status === "running" && (
552
+ < WorkspaceApps workspace = { workspace } />
553
+ ) }
554
+
537
555
{ abilities . actions . includes ( "start" ) && (
538
556
< PrimaryAction
539
557
onClick = { ( ) => startWorkspaceMutation . mutate ( { } ) }
@@ -557,18 +575,6 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
557
575
</ >
558
576
) }
559
577
560
- { abilities . actions . includes ( "stop" ) && (
561
- < PrimaryAction
562
- onClick = { ( ) => {
563
- stopWorkspaceMutation . mutate ( { } ) ;
564
- } }
565
- isLoading = { stopWorkspaceMutation . isLoading }
566
- label = "Stop workspace"
567
- >
568
- < SquareIcon />
569
- </ PrimaryAction >
570
- ) }
571
-
572
578
{ abilities . canCancel && (
573
579
< PrimaryAction
574
580
onClick = { cancelBuildMutation . mutate }
@@ -594,9 +600,9 @@ const WorkspaceActionsCell: FC<WorkspaceActionsCellProps> = ({
594
600
} ;
595
601
596
602
type PrimaryActionProps = PropsWithChildren < {
597
- onClick : ( ) => void ;
598
- isLoading : boolean ;
599
603
label : string ;
604
+ isLoading ?: boolean ;
605
+ onClick : ( ) => void ;
600
606
} > ;
601
607
602
608
const PrimaryAction : FC < PrimaryActionProps > = ( {
@@ -626,3 +632,120 @@ const PrimaryAction: FC<PrimaryActionProps> = ({
626
632
</ TooltipProvider >
627
633
) ;
628
634
} ;
635
+
636
+ type WorkspaceAppsProps = {
637
+ workspace : Workspace ;
638
+ } ;
639
+
640
+ const WorkspaceApps : FC < WorkspaceAppsProps > = ( { workspace } ) => {
641
+ const buttons : ReactNode [ ] = [ ] ;
642
+ const { data : apiKeyRes } = useQuery ( apiKey ( ) ) ;
643
+ const token = apiKeyRes ?. key ;
644
+
645
+ const resource = workspace . latest_build . resources
646
+ . filter ( ( r ) => ! r . hide )
647
+ . at ( 0 ) ;
648
+ if ( ! resource ) {
649
+ return null ;
650
+ }
651
+
652
+ const agent = resource . agents ?. at ( 0 ) ;
653
+ if ( ! agent ) {
654
+ return null ;
655
+ }
656
+
657
+ if ( agent . display_apps . includes ( "vscode" ) ) {
658
+ buttons . push (
659
+ < AppLink
660
+ isLoading = { ! token }
661
+ label = "Open VSCode"
662
+ href = { getVSCodeHref ( "vscode" , {
663
+ owner : workspace . owner_name ,
664
+ workspace : workspace . name ,
665
+ agent : agent . name ,
666
+ token : apiKeyRes ?. key ?? "" ,
667
+ folder : agent . expanded_directory ,
668
+ } ) }
669
+ >
670
+ < VSCodeIcon />
671
+ </ AppLink > ,
672
+ ) ;
673
+ }
674
+
675
+ if ( agent . display_apps . includes ( "vscode_insiders" ) ) {
676
+ buttons . push (
677
+ < AppLink
678
+ label = "Open VSCode Insiders"
679
+ isLoading = { ! token }
680
+ href = { getVSCodeHref ( "vscode-insiders" , {
681
+ owner : workspace . owner_name ,
682
+ workspace : workspace . name ,
683
+ agent : agent . name ,
684
+ token : apiKeyRes ?. key ?? "" ,
685
+ folder : agent . expanded_directory ,
686
+ } ) }
687
+ >
688
+ < VSCodeInsidersIcon />
689
+ </ AppLink > ,
690
+ ) ;
691
+ }
692
+
693
+ if ( agent . display_apps . includes ( "web_terminal" ) ) {
694
+ const href = getTerminalHref ( {
695
+ username : workspace . owner_name ,
696
+ workspace : workspace . name ,
697
+ agent : agent . name ,
698
+ } ) ;
699
+ buttons . push (
700
+ < AppLink
701
+ href = { href }
702
+ onClick = { ( e ) => {
703
+ e . preventDefault ( ) ;
704
+ openAppInNewWindow ( "Terminal" , href ) ;
705
+ } }
706
+ label = "Open Terminal"
707
+ >
708
+ < SquareTerminalIcon />
709
+ </ AppLink > ,
710
+ ) ;
711
+ }
712
+
713
+ return buttons ;
714
+ } ;
715
+
716
+ type AppLinkProps = PropsWithChildren < {
717
+ label : string ;
718
+ href : string ;
719
+ isLoading ?: boolean ;
720
+ onClick ?: ( e : React . MouseEvent < HTMLAnchorElement > ) => void ;
721
+ } > ;
722
+
723
+ const AppLink : FC < AppLinkProps > = ( {
724
+ href,
725
+ isLoading,
726
+ label,
727
+ children,
728
+ onClick,
729
+ } ) => {
730
+ return (
731
+ < TooltipProvider >
732
+ < Tooltip >
733
+ < TooltipTrigger asChild >
734
+ < Button variant = "outline" size = "icon-lg" asChild >
735
+ < a
736
+ href = { href }
737
+ onClick = { ( e ) => {
738
+ e . stopPropagation ( ) ;
739
+ onClick ?.( e ) ;
740
+ } }
741
+ >
742
+ < Spinner loading = { isLoading } > { children } </ Spinner >
743
+ < span className = "sr-only" > { label } </ span >
744
+ </ a >
745
+ </ Button >
746
+ </ TooltipTrigger >
747
+ < TooltipContent > { label } </ TooltipContent >
748
+ </ Tooltip >
749
+ </ TooltipProvider >
750
+ ) ;
751
+ } ;
0 commit comments