@@ -9,13 +9,23 @@ import {
9
9
TooltipProvider ,
10
10
TooltipTrigger ,
11
11
} from "components/Tooltip/Tooltip" ;
12
- import { ExternalLinkIcon } from "lucide-react" ;
12
+ import {
13
+ HelpTooltip ,
14
+ HelpTooltipContent ,
15
+ HelpTooltipText ,
16
+ HelpTooltipTitle ,
17
+ HelpTooltipTrigger ,
18
+ } from "components/HelpTooltip/HelpTooltip" ;
19
+ import { ExternalLinkIcon , Loader2Icon } from "lucide-react" ;
13
20
import type { FC } from "react" ;
14
21
import { portForwardURL } from "utils/portForward" ;
15
22
import { AgentButton } from "./AgentButton" ;
16
23
import { AgentDevcontainerSSHButton } from "./SSHButton/SSHButton" ;
17
24
import { TerminalLink } from "./TerminalLink/TerminalLink" ;
18
25
import { VSCodeDevContainerButton } from "./VSCodeDevContainerButton/VSCodeDevContainerButton" ;
26
+ import { useState } from "react" ;
27
+ import { Button } from "components/Button/Button" ;
28
+ import { displayError } from "components/GlobalSnackbar/utils" ;
19
29
20
30
type AgentDevcontainerCardProps = {
21
31
agent :WorkspaceAgent ;
@@ -32,24 +42,94 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
32
42
} ) => {
33
43
const folderPath = container . labels [ "devcontainer.local_folder" ] ;
34
44
const containerFolder = container . volumes [ folderPath ] ;
45
+ const [ isRecreating , setIsRecreating ] = useState ( false ) ;
46
+
47
+ const handleRecreateDevcontainer = async ( ) => {
48
+ setIsRecreating ( true ) ;
49
+ let recreateSucceeded = false ;
50
+ try {
51
+ const response = await fetch (
52
+ `/api/v2/workspaceagents/${ agent . id } /containers/devcontainers/container/${ container . id } /recreate` ,
53
+ {
54
+ method :"POST" ,
55
+ } ,
56
+ ) ;
57
+ if ( ! response . ok ) {
58
+ const errorData = await response . json ( ) . catch ( ( ) => ( { } ) ) ;
59
+ throw new Error (
60
+ errorData . message || `Failed to recreate:${ response . statusText } ` ,
61
+ ) ;
62
+ }
63
+ // If the request was accepted (e.g. 202), we mark it as succeeded.
64
+ // Once complete, the component will unmount, so the spinner will
65
+ // disappear with it.
66
+ if ( response . status === 202 ) {
67
+ recreateSucceeded = true ;
68
+ }
69
+ } catch ( error ) {
70
+ const errorMessage =
71
+ error instanceof Error ?error . message :"An unknown error occurred." ;
72
+ displayError ( `Failed to recreate devcontainer:${ errorMessage } ` ) ;
73
+ console . error ( "Failed to recreate devcontainer:" , error ) ;
74
+ } finally {
75
+ if ( ! recreateSucceeded ) {
76
+ setIsRecreating ( false ) ;
77
+ }
78
+ }
79
+ } ;
35
80
36
81
return (
37
82
< section
38
83
className = "border border-border border-dashed rounded p-6 "
39
84
key = { container . id }
40
85
>
41
- < header className = "flex justify-between" >
42
- < h3 className = "m-0 text-xs font-medium text-content-secondary" >
43
- { container . name }
44
- </ h3 >
86
+ < header className = "flex justify-between items-center mb-4" >
87
+ < div className = "flex items-center gap-2" >
88
+ < h3 className = "m-0 text-xs font-medium text-content-secondary" >
89
+ { container . name }
90
+ </ h3 >
91
+ { container . devcontainer_dirty && (
92
+ < HelpTooltip >
93
+ < HelpTooltipTrigger className = "flex items-center text-xs text-warning-foreground ml-2" >
94
+ < span > Outdated</ span >
95
+ </ HelpTooltipTrigger >
96
+ < HelpTooltipContent >
97
+ < HelpTooltipTitle > Devcontainer Outdated</ HelpTooltipTitle >
98
+ < HelpTooltipText >
99
+ Devcontainer configuration has been modified and is outdated.
100
+ Recreate to get an up-to-date container.
101
+ </ HelpTooltipText >
102
+ </ HelpTooltipContent >
103
+ </ HelpTooltip >
104
+ ) }
105
+ </ div >
45
106
46
- < AgentDevcontainerSSHButton
47
- workspace = { workspace . name }
48
- container = { container . name }
49
- />
107
+ < div className = "flex items-center gap-2" >
108
+ < Button
109
+ variant = "outline"
110
+ size = "sm"
111
+ className = "text-xs font-medium"
112
+ onClick = { handleRecreateDevcontainer }
113
+ disabled = { isRecreating }
114
+ >
115
+ { isRecreating ?(
116
+ < >
117
+ < Loader2Icon className = "mr-2 h-4 w-4 animate-spin" />
118
+ Recreating...
119
+ </ >
120
+ ) :(
121
+ "Recreate"
122
+ ) }
123
+ </ Button >
124
+
125
+ < AgentDevcontainerSSHButton
126
+ workspace = { workspace . name }
127
+ container = { container . name }
128
+ />
129
+ </ div >
50
130
</ header >
51
131
52
- < h4 className = "m-0 text-xl font-semibold" > Forwarded ports</ h4 >
132
+ < h4 className = "m-0 text-xl font-semibold mb-2 " > Forwarded ports</ h4 >
53
133
54
134
< div className = "flex gap-4 flex-wrap mt-4" >
55
135
< VSCodeDevContainerButton