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

Commit861c4b1

Browse files
feat: add devcontainer in the UI (#16800)
![image](https://github.com/user-attachments/assets/361f9e69-dec8-47c8-b075-7c13ce84c7e8)Related to#16422---------Co-authored-by: Cian Johnston <cian@coder.com>
1 parent73057eb commit861c4b1

File tree

6 files changed

+186
-15
lines changed

6 files changed

+186
-15
lines changed

‎site/src/api/api.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2374,6 +2374,18 @@ class ApiMethods {
23742374
);
23752375
}
23762376
};
2377+
2378+
getAgentContainers=async(agentId:string,labels?:string[])=>{
2379+
constparams=newURLSearchParams(
2380+
labels?.map((label)=>["label",label]),
2381+
);
2382+
2383+
constres=
2384+
awaitthis.axios.get<TypesGen.WorkspaceAgentListContainersResponse>(
2385+
`/api/v2/workspaceagents/${agentId}/containers?${params.toString()}`,
2386+
);
2387+
returnres.data;
2388+
};
23772389
}
23782390

23792391
// This is a hard coded CSRF token/cookie pair for local development. In prod,
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
importLinkfrom"@mui/material/Link";
2+
importtype{Workspace,WorkspaceAgentDevcontainer}from"api/typesGenerated";
3+
import{ExternalLinkIcon}from"lucide-react";
4+
importtype{FC}from"react";
5+
import{portForwardURL}from"utils/portForward";
6+
import{AgentButton}from"./AgentButton";
7+
import{AgentDevcontainerSSHButton}from"./SSHButton/SSHButton";
8+
import{TerminalLink}from"./TerminalLink/TerminalLink";
9+
10+
typeAgentDevcontainerCardProps={
11+
container:WorkspaceAgentDevcontainer;
12+
workspace:Workspace;
13+
wildcardHostname:string;
14+
agentName:string;
15+
};
16+
17+
exportconstAgentDevcontainerCard:FC<AgentDevcontainerCardProps>=({
18+
container,
19+
workspace,
20+
agentName,
21+
wildcardHostname,
22+
})=>{
23+
return(
24+
<section
25+
className="border border-border border-dashed rounded p-6 "
26+
key={container.id}
27+
>
28+
<headerclassName="flex justify-between">
29+
<h3className="m-0 text-xs font-medium text-content-secondary">
30+
{container.name}
31+
</h3>
32+
33+
<AgentDevcontainerSSHButton
34+
workspace={workspace.name}
35+
container={container.name}
36+
/>
37+
</header>
38+
39+
<h4className="m-0 text-xl font-semibold">Forwarded ports</h4>
40+
41+
<divclassName="flex gap-4 flex-wrap mt-4">
42+
<TerminalLink
43+
workspaceName={workspace.name}
44+
agentName={agentName}
45+
containerName={container.name}
46+
userName={workspace.owner_name}
47+
/>
48+
{wildcardHostname!==""&&
49+
container.ports.map((port)=>{
50+
return(
51+
<Link
52+
key={port.port}
53+
color="inherit"
54+
component={AgentButton}
55+
underline="none"
56+
startIcon={<ExternalLinkIconclassName="size-icon-sm"/>}
57+
href={portForwardURL(
58+
wildcardHostname,
59+
port.port,
60+
agentName,
61+
workspace.name,
62+
workspace.owner_name,
63+
location.protocol==="https" ?"https" :"http",
64+
)}
65+
>
66+
{port.process_name||
67+
`${port.port}/${port.network.toUpperCase()}`}
68+
</Link>
69+
);
70+
})}
71+
</div>
72+
</section>
73+
);
74+
};

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

Lines changed: 33 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import Button from "@mui/material/Button";
33
importCollapsefrom"@mui/material/Collapse";
44
importDividerfrom"@mui/material/Divider";
55
importSkeletonfrom"@mui/material/Skeleton";
6+
import{API}from"api/api";
67
import{xrayScan}from"api/queries/integrations";
78
importtype{
89
Template,
@@ -25,6 +26,7 @@ import {
2526
import{useQuery}from"react-query";
2627
importAutoSizerfrom"react-virtualized-auto-sizer";
2728
importtype{FixedSizeListasList,ListOnScrollProps}from"react-window";
29+
import{AgentDevcontainerCard}from"./AgentDevcontainerCard";
2830
import{AgentLatency}from"./AgentLatency";
2931
import{AGENT_LOG_LINE_HEIGHT}from"./AgentLogs/AgentLogLine";
3032
import{AgentLogs}from"./AgentLogs/AgentLogs";
@@ -35,7 +37,7 @@ import { AgentVersion } from "./AgentVersion";
3537
import{AppLink}from"./AppLink/AppLink";
3638
import{DownloadAgentLogsButton}from"./DownloadAgentLogsButton";
3739
import{PortForwardButton}from"./PortForwardButton";
38-
import{SSHButton}from"./SSHButton/SSHButton";
40+
import{AgentSSHButton}from"./SSHButton/SSHButton";
3941
import{TerminalLink}from"./TerminalLink/TerminalLink";
4042
import{VSCodeDesktopButton}from"./VSCodeDesktopButton/VSCodeDesktopButton";
4143
import{XRayScanAlert}from"./XRayScanAlert";
@@ -152,6 +154,18 @@ export const AgentRow: FC<AgentRowProps> = ({
152154
setBottomOfLogs(distanceFromBottom<AGENT_LOG_LINE_HEIGHT);
153155
},[]);
154156

157+
const{data:containers}=useQuery({
158+
queryKey:["agents",agent.id,"containers"],
159+
queryFn:()=>
160+
// Only return devcontainers
161+
API.getAgentContainers(agent.id,[
162+
"devcontainer.config_file=",
163+
"devcontainer.local_folder=",
164+
]),
165+
enabled:agent.status==="connected",
166+
select:(res)=>res.containers.filter((c)=>c.status==="running"),
167+
});
168+
155169
return(
156170
<Stack
157171
key={agent.id}
@@ -191,14 +205,13 @@ export const AgentRow: FC<AgentRowProps> = ({
191205
{showBuiltinApps&&(
192206
<divcss={{display:"flex"}}>
193207
{!hideSSHButton&&agent.display_apps.includes("ssh_helper")&&(
194-
<SSHButton
208+
<AgentSSHButton
195209
workspaceName={workspace.name}
196210
agentName={agent.name}
197211
sshPrefix={sshPrefix}
198212
/>
199213
)}
200-
{proxy.preferredWildcardHostname&&
201-
proxy.preferredWildcardHostname!==""&&
214+
{proxy.preferredWildcardHostname!==""&&
202215
agent.display_apps.includes("port_forwarding_helper")&&(
203216
<PortForwardButton
204217
host={proxy.preferredWildcardHostname}
@@ -267,6 +280,22 @@ export const AgentRow: FC<AgentRowProps> = ({
267280
</section>
268281
)}
269282

283+
{containers&&containers.length>0&&(
284+
<sectionclassName="flex flex-col gap-4">
285+
{containers.map((container)=>{
286+
return(
287+
<AgentDevcontainerCard
288+
key={container.id}
289+
container={container}
290+
workspace={workspace}
291+
wildcardHostname={proxy.preferredWildcardHostname}
292+
agentName={agent.name}
293+
/>
294+
);
295+
})}
296+
</section>
297+
)}
298+
270299
<AgentMetadata
271300
storybookMetadata={storybookAgentMetadata}
272301
agent={agent}

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import type { Meta, StoryObj } from "@storybook/react";
22
import{userEvent,within}from"@storybook/test";
33
import{MockWorkspace,MockWorkspaceAgent}from"testHelpers/entities";
44
import{withDesktopViewport}from"testHelpers/storybook";
5-
import{SSHButton}from"./SSHButton";
5+
import{AgentSSHButton}from"./SSHButton";
66

7-
constmeta:Meta<typeofSSHButton>={
8-
title:"modules/resources/SSHButton",
9-
component:SSHButton,
7+
constmeta:Meta<typeofAgentSSHButton>={
8+
title:"modules/resources/AgentSSHButton",
9+
component:AgentSSHButton,
1010
};
1111

1212
exportdefaultmeta;
13-
typeStory=StoryObj<typeofSSHButton>;
13+
typeStory=StoryObj<typeofAgentSSHButton>;
1414

1515
exportconstClosed:Story={
1616
args:{

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

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,13 @@ import { type ClassName, useClassName } from "hooks/useClassName";
1717
importtype{FC}from"react";
1818
import{docs}from"utils/docs";
1919

20-
exportinterfaceSSHButtonProps{
20+
exportinterfaceAgentSSHButtonProps{
2121
workspaceName:string;
2222
agentName:string;
2323
sshPrefix?:string;
2424
}
2525

26-
exportconstSSHButton:FC<SSHButtonProps>=({
26+
exportconstAgentSSHButton:FC<AgentSSHButtonProps>=({
2727
workspaceName,
2828
agentName,
2929
sshPrefix,
@@ -82,6 +82,56 @@ export const SSHButton: FC<SSHButtonProps> = ({
8282
);
8383
};
8484

85+
exportinterfaceAgentDevcontainerSSHButtonProps{
86+
workspace:string;
87+
container:string;
88+
}
89+
90+
exportconstAgentDevcontainerSSHButton:FC<
91+
AgentDevcontainerSSHButtonProps
92+
>=({ workspace, container})=>{
93+
constpaper=useClassName(classNames.paper,[]);
94+
95+
return(
96+
<Popover>
97+
<PopoverTrigger>
98+
<Button
99+
size="small"
100+
variant="text"
101+
endIcon={<KeyboardArrowDown/>}
102+
css={{fontSize:13,padding:"8px 12px"}}
103+
>
104+
Connect via SSH
105+
</Button>
106+
</PopoverTrigger>
107+
108+
<PopoverContenthorizontal="right"classes={{ paper}}>
109+
<HelpTooltipText>
110+
Run the following commands to connect with SSH:
111+
</HelpTooltipText>
112+
113+
<olstyle={{margin:0,padding:0}}>
114+
<Stackspacing={0.5}css={styles.codeExamples}>
115+
<SSHStep
116+
helpText="Connect to the container:"
117+
codeExample={`coder ssh${workspace} -c${container}`}
118+
/>
119+
</Stack>
120+
</ol>
121+
122+
<HelpTooltipLinksGroup>
123+
<HelpTooltipLinkhref={docs("/install")}>
124+
Install Coder CLI
125+
</HelpTooltipLink>
126+
<HelpTooltipLinkhref={docs("/user-guides/workspace-access#ssh")}>
127+
SSH configuration
128+
</HelpTooltipLink>
129+
</HelpTooltipLinksGroup>
130+
</PopoverContent>
131+
</Popover>
132+
);
133+
};
134+
85135
interfaceSSHStepProps{
86136
helpText:string;
87137
codeExample:string;

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ export const Language = {
1111
};
1212

1313
exportinterfaceTerminalLinkProps{
14-
agentName?:TypesGen.WorkspaceAgent["name"];
15-
userName?:TypesGen.User["username"];
16-
workspaceName:TypesGen.Workspace["name"];
14+
workspaceName:string;
15+
agentName?:string;
16+
userName?:string;
17+
containerName?:string;
1718
}
1819

1920
/**
@@ -27,11 +28,16 @@ export const TerminalLink: FC<TerminalLinkProps> = ({
2728
agentName,
2829
userName="me",
2930
workspaceName,
31+
containerName,
3032
})=>{
33+
constparams=newURLSearchParams();
34+
if(containerName){
35+
params.append("container",containerName);
36+
}
3137
// Always use the primary for the terminal link. This is a relative link.
3238
consthref=`/@${userName}/${workspaceName}${
3339
agentName ?`.${agentName}` :""
34-
}/terminal`;
40+
}/terminal?${params.toString()}`;
3541

3642
return(
3743
<Link

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp