Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit5384e66

Browse files
feat(site): support deleting dev containers
1 parent6eeae3c commit5384e66

File tree

4 files changed

+202
-14
lines changed

4 files changed

+202
-14
lines changed

‎site/src/api/api.ts‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2607,6 +2607,31 @@ class ApiMethods {
26072607
}
26082608
};
26092609

2610+
deleteDevContainer=async({
2611+
parentAgentId,
2612+
devcontainerId,
2613+
}:{
2614+
parentAgentId:string;
2615+
devcontainerId:string;
2616+
})=>{
2617+
awaitthis.axios.delete(
2618+
`/api/v2/workspaceagents/${parentAgentId}/containers/devcontainers/${devcontainerId}`,
2619+
);
2620+
};
2621+
2622+
recreateDevContainer=async({
2623+
parentAgentId,
2624+
devcontainerId,
2625+
}:{
2626+
parentAgentId:string;
2627+
devcontainerId:string;
2628+
})=>{
2629+
constresponse=awaitthis.axios.post<TypesGen.Response>(
2630+
`/api/v2/workspaceagents/${parentAgentId}/containers/devcontainers/${devcontainerId}/recreate`,
2631+
);
2632+
returnresponse.data;
2633+
};
2634+
26102635
getAgentContainers=async(agentId:string,labels?:string[])=>{
26112636
constparams=newURLSearchParams(
26122637
labels?.map((label)=>["label",label]),

‎site/src/modules/resources/AgentDevcontainerCard.stories.tsx‎

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,17 @@ export const Recreating: Story = {
9191
},
9292
};
9393

94+
exportconstStopping:Story={
95+
args:{
96+
devcontainer:{
97+
...MockWorkspaceAgentDevcontainer,
98+
dirty:true,
99+
status:"stopping",
100+
},
101+
subAgents:[],
102+
},
103+
};
104+
94105
exportconstNoContainerOrSubAgent:Story={
95106
args:{
96107
devcontainer:{

‎site/src/modules/resources/AgentDevcontainerCard.tsx‎

Lines changed: 30 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,9 @@ import { AgentSSHButton } from "./SSHButton/SSHButton";
3434
import{SubAgentOutdatedTooltip}from"./SubAgentOutdatedTooltip";
3535
import{TerminalLink}from"./TerminalLink/TerminalLink";
3636
import{VSCodeDevContainerButton}from"./VSCodeDevContainerButton/VSCodeDevContainerButton";
37+
import{API}from"api/api";
38+
import{DropdownMenu}from"components/DropdownMenu/DropdownMenu";
39+
import{AgentDevcontainerMoreActions}from"./AgentDevcontainerMoreActions";
3740

3841
typeAgentDevcontainerCardProps={
3942
parentAgent:WorkspaceAgent;
@@ -80,17 +83,10 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
8083

8184
constrebuildDevcontainerMutation=useMutation({
8285
mutationFn:async()=>{
83-
constresponse=awaitfetch(
84-
`/api/v2/workspaceagents/${parentAgent.id}/containers/devcontainers/${devcontainer.id}/recreate`,
85-
{method:"POST"},
86-
);
87-
if(!response.ok){
88-
consterrorData=awaitresponse.json().catch(()=>({}));
89-
thrownewError(
90-
errorData.message||`Failed to rebuild:${response.statusText}`,
91-
);
92-
}
93-
returnresponse;
86+
awaitAPI.recreateDevContainer({
87+
parentAgentId:parentAgent.id,
88+
devcontainerId:devcontainer.id,
89+
});
9490
},
9591
onMutate:async()=>{
9692
awaitqueryClient.cancelQueries({
@@ -168,6 +164,7 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
168164

169165
constshowDevcontainerControls=subAgent&&devcontainer.container;
170166
constshowSubAgentApps=
167+
devcontainer.status!=="stopping"&&
171168
devcontainer.status!=="starting"&&
172169
subAgent?.status==="connected"&&
173170
hasAppsToDisplay;
@@ -250,11 +247,23 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
250247
variant="outline"
251248
size="sm"
252249
onClick={handleRebuildDevcontainer}
253-
disabled={devcontainer.status==="starting"}
250+
disabled={
251+
devcontainer.status==="starting"||
252+
devcontainer.status==="stopping"
253+
}
254254
>
255-
<Spinnerloading={devcontainer.status==="starting"}/>
255+
<Spinner
256+
loading={
257+
devcontainer.status==="starting"||
258+
devcontainer.status==="stopping"
259+
}
260+
/>
256261

257-
{devcontainer.container===undefined ?"Start" :"Rebuild"}
262+
{devcontainer.status==="stopping"
263+
?"Stop"
264+
:devcontainer.container===undefined
265+
?"Start"
266+
:"Rebuild"}
258267
</Button>
259268

260269
{showDevcontainerControls&&displayApps.includes("ssh_helper")&&(
@@ -274,6 +283,13 @@ export const AgentDevcontainerCard: FC<AgentDevcontainerCardProps> = ({
274283
template={template}
275284
/>
276285
)}
286+
287+
{showDevcontainerControls&&(
288+
<AgentDevcontainerMoreActions
289+
devcontainer={devcontainer}
290+
parentAgent={parentAgent}
291+
/>
292+
)}
277293
</div>
278294
</header>
279295

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
import{API}from"api/api";
2+
import{
3+
WorkspaceAgent,
4+
WorkspaceAgentDevcontainer,
5+
WorkspaceAgentListContainersResponse,
6+
}from"api/typesGenerated";
7+
import{Button}from"components/Button/Button";
8+
import{ConfirmDialog}from"components/Dialogs/ConfirmDialog/ConfirmDialog";
9+
import{
10+
DropdownMenu,
11+
DropdownMenuContent,
12+
DropdownMenuItem,
13+
DropdownMenuTrigger,
14+
}from"components/DropdownMenu/DropdownMenu";
15+
import{EllipsisVertical}from"lucide-react";
16+
import{FC,useId,useState}from"react";
17+
import{useMutation,useQueryClient}from"react-query";
18+
19+
typeAgentDevcontainerMoreActionsProps={
20+
parentAgent:WorkspaceAgent;
21+
devcontainer:WorkspaceAgentDevcontainer;
22+
};
23+
24+
exportconstAgentDevcontainerMoreActions:FC<
25+
AgentDevcontainerMoreActionsProps
26+
>=({ parentAgent, devcontainer})=>{
27+
constqueryClient=useQueryClient();
28+
const[isConfirmingDelete,setIsConfirmingDelete]=useState(false);
29+
const[open,setOpen]=useState(false);
30+
constmenuContentId=useId();
31+
32+
constdeleteDevContainerMutation=useMutation({
33+
mutationFn:async()=>{
34+
awaitAPI.deleteDevContainer({
35+
parentAgentId:parentAgent.id,
36+
devcontainerId:devcontainer.id,
37+
});
38+
},
39+
onMutate:async()=>{
40+
awaitqueryClient.cancelQueries({
41+
queryKey:["agents",parentAgent.id,"containers"],
42+
});
43+
44+
// Snapshot the previous data for rollback in case of error.
45+
constpreviousData=queryClient.getQueryData([
46+
"agents",
47+
parentAgent.id,
48+
"containers",
49+
]);
50+
51+
// Optimistically update the devcontainer status to
52+
// "stopping" and zero the agent and container to mimic what
53+
// the API does.
54+
queryClient.setQueryData(
55+
["agents",parentAgent.id,"containers"],
56+
(oldData?:WorkspaceAgentListContainersResponse)=>{
57+
if(!oldData?.devcontainers)returnoldData;
58+
return{
59+
...oldData,
60+
devcontainers:oldData.devcontainers.map((dc)=>{
61+
if(dc.id===devcontainer.id){
62+
return{
63+
...dc,
64+
status:"stopping",
65+
container:undefined,
66+
};
67+
}
68+
returndc;
69+
}),
70+
};
71+
},
72+
);
73+
74+
return{ previousData};
75+
},
76+
});
77+
78+
return(
79+
<DropdownMenuopen={open}onOpenChange={setOpen}>
80+
<DropdownMenuTriggerasChild>
81+
<Buttonsize="icon-lg"variant="subtle"aria-controls={menuContentId}>
82+
<EllipsisVerticalaria-hidden="true"/>
83+
<spanclassName="sr-only">Dev Container actions</span>
84+
</Button>
85+
</DropdownMenuTrigger>
86+
87+
<DropdownMenuContentid={menuContentId}align="end">
88+
<DropdownMenuItem
89+
className="text-content-destructive focus:text-content-destructive"
90+
onClick={()=>{
91+
setIsConfirmingDelete(true);
92+
}}
93+
>
94+
Delete&hellip;
95+
</DropdownMenuItem>
96+
</DropdownMenuContent>
97+
98+
<DevcontainerDeleteDialog
99+
isOpen={isConfirmingDelete}
100+
onCancel={()=>setIsConfirmingDelete(false)}
101+
onConfirm={()=>{
102+
deleteDevContainerMutation.mutate();
103+
setIsConfirmingDelete(false);
104+
}}
105+
/>
106+
</DropdownMenu>
107+
);
108+
};
109+
110+
typeDevcontainerDeleteDialogProps={
111+
isOpen:boolean;
112+
onCancel:()=>void;
113+
onConfirm:()=>void;
114+
};
115+
116+
constDevcontainerDeleteDialog:FC<DevcontainerDeleteDialogProps>=({
117+
isOpen,
118+
onCancel,
119+
onConfirm,
120+
})=>{
121+
return(
122+
<ConfirmDialog
123+
type="delete"
124+
open={isOpen}
125+
title="Delete Dev Container"
126+
onConfirm={onConfirm}
127+
onClose={onCancel}
128+
description={
129+
<p>
130+
Are you sure you want to delete this Dev Container? Any unsaved work
131+
will be lost.
132+
</p>
133+
}
134+
/>
135+
);
136+
};

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp