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

Commit7f72067

Browse files
authored
feat(site): add support for external agents in the UI and extend CodeExample (#19288)
This pull request introduces support for external workspace management, allowing users to register and manage workspaces that are provisioned and managed outside of the Coder.* Added a new component AgentExternal which shows instructions for connecting external agents.* Added redacted fields to CodeExample so you can now hide specific parts of the code instead of the full line* Hides workspace actions if workspace is using external agent.<img width="1719" height="646" alt="image" src="https://github.com/user-attachments/assets/45b7bfae-7006-461f-a96d-e61f97084819" />
1 parent7b1dcd9 commit7f72067

File tree

8 files changed

+221
-10
lines changed

8 files changed

+221
-10
lines changed

‎site/src/api/api.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2022,6 +2022,16 @@ class ApiMethods {
20222022
returnresponse.data;
20232023
};
20242024

2025+
getWorkspaceAgentCredentials=async(
2026+
workspaceID:string,
2027+
agentName:string,
2028+
):Promise<TypesGen.ExternalAgentCredentials>=>{
2029+
constresponse=awaitthis.axios.get(
2030+
`/api/v2/workspaces/${workspaceID}/external-agent/${agentName}/credentials`,
2031+
);
2032+
returnresponse.data;
2033+
};
2034+
20252035
upsertWorkspaceAgentSharedPort=async(
20262036
workspaceID:string,
20272037
req:TypesGen.UpsertWorkspaceAgentPortShareRequest,

‎site/src/api/queries/workspaces.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,3 +430,13 @@ export const updateWorkspaceACL = (workspaceId: string) => {
430430
},
431431
};
432432
};
433+
434+
exportconstworkspaceAgentCredentials=(
435+
workspaceId:string,
436+
agentName:string,
437+
)=>{
438+
return{
439+
queryKey:["workspaces",workspaceId,"agents",agentName,"credentials"],
440+
queryFn:()=>API.getWorkspaceAgentCredentials(workspaceId,agentName),
441+
};
442+
};

‎site/src/components/CodeExample/CodeExample.stories.tsx‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,3 +31,12 @@ export const LongCode: Story = {
3131
code:"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAICnKzATuWwmmt5+CKTPuRGN0R1PBemA+6/SStpLiyX+L",
3232
},
3333
};
34+
35+
exportconstRedact:Story={
36+
args:{
37+
secret:false,
38+
redactPattern:/CODER_AGENT_TOKEN="([^"]+)"/g,
39+
redactReplacement:`CODER_AGENT_TOKEN="********"`,
40+
showRevealButton:true,
41+
},
42+
};

‎site/src/components/CodeExample/CodeExample.tsx‎

Lines changed: 58 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,26 @@
11
importtype{Interpolation,Theme}from"@emotion/react";
2-
importtype{FC}from"react";
2+
import{Button}from"components/Button/Button";
3+
import{
4+
Tooltip,
5+
TooltipContent,
6+
TooltipProvider,
7+
TooltipTrigger,
8+
}from"components/Tooltip/Tooltip";
9+
import{EyeIcon,EyeOffIcon}from"lucide-react";
10+
import{typeFC,useState}from"react";
311
import{MONOSPACE_FONT_FAMILY}from"theme/constants";
412
import{CopyButton}from"../CopyButton/CopyButton";
513

614
interfaceCodeExampleProps{
715
code:string;
16+
/** Defaulting to true to be on the safe side; you should have to opt out of the secure option, not remember to opt in */
817
secret?:boolean;
18+
/** Redact parts of the code if the user doesn't want to obfuscate the whole code */
19+
redactPattern?:RegExp;
20+
/** Replacement text for redacted content */
21+
redactReplacement?:string;
22+
/** Show a button to reveal the redacted parts of the code */
23+
showRevealButton?:boolean;
924
className?:string;
1025
}
1126

@@ -15,11 +30,28 @@ interface CodeExampleProps {
1530
exportconstCodeExample:FC<CodeExampleProps>=({
1631
code,
1732
className,
18-
19-
// Defaulting to true to be on the safe side; you should have to opt out of
20-
// the secure option, not remember to opt in
2133
secret=true,
34+
redactPattern,
35+
redactReplacement="********",
36+
showRevealButton,
2237
})=>{
38+
const[showFullValue,setShowFullValue]=useState(false);
39+
40+
constdisplayValue=secret
41+
?obfuscateText(code)
42+
:redactPattern&&!showFullValue
43+
?code.replace(redactPattern,redactReplacement)
44+
:code;
45+
46+
constshowButtonLabel=showFullValue
47+
?"Hide sensitive data"
48+
:"Show sensitive data";
49+
consticon=showFullValue ?(
50+
<EyeOffIconclassName="h-4 w-4"/>
51+
) :(
52+
<EyeIconclassName="h-4 w-4"/>
53+
);
54+
2355
return(
2456
<divcss={styles.container}className={className}>
2557
<codecss={[styles.code,secret&&styles.secret]}>
@@ -33,17 +65,36 @@ export const CodeExample: FC<CodeExampleProps> = ({
3365
* 2. Even with it turned on and supported, the plaintext is still
3466
* readily available in the HTML itself
3567
*/}
36-
<spanaria-hidden>{obfuscateText(code)}</span>
68+
<spanaria-hidden>{displayValue}</span>
3769
<spanclassName="sr-only">
3870
Encrypted text. Please access via the copy button.
3971
</span>
4072
</>
4173
) :(
42-
code
74+
displayValue
4375
)}
4476
</code>
4577

46-
<CopyButtontext={code}label="Copy code"/>
78+
<divclassName="flex items-center gap-1">
79+
{showRevealButton&&redactPattern&&!secret&&(
80+
<TooltipProvider>
81+
<Tooltip>
82+
<TooltipTriggerasChild>
83+
<Button
84+
size="icon"
85+
variant="subtle"
86+
onClick={()=>setShowFullValue(!showFullValue)}
87+
>
88+
{icon}
89+
<spanclassName="sr-only">{showButtonLabel}</span>
90+
</Button>
91+
</TooltipTrigger>
92+
<TooltipContent>{showButtonLabel}</TooltipContent>
93+
</Tooltip>
94+
</TooltipProvider>
95+
)}
96+
<CopyButtontext={code}label="Copy code"/>
97+
</div>
4798
</div>
4899
);
49100
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import{chromatic}from"testHelpers/chromatic";
2+
import{MockWorkspace,MockWorkspaceAgent}from"testHelpers/entities";
3+
import{withDashboardProvider}from"testHelpers/storybook";
4+
importtype{Meta,StoryObj}from"@storybook/react-vite";
5+
import{AgentExternal}from"./AgentExternal";
6+
7+
constmeta:Meta<typeofAgentExternal>={
8+
title:"modules/resources/AgentExternal",
9+
component:AgentExternal,
10+
args:{
11+
agent:{
12+
...MockWorkspaceAgent,
13+
status:"connecting",
14+
operating_system:"linux",
15+
architecture:"amd64",
16+
},
17+
workspace:MockWorkspace,
18+
},
19+
decorators:[withDashboardProvider],
20+
parameters:{
21+
chromatic,
22+
},
23+
};
24+
25+
exportdefaultmeta;
26+
typeStory=StoryObj<typeofAgentExternal>;
27+
28+
exportconstConnecting:Story={
29+
args:{
30+
agent:{
31+
...MockWorkspaceAgent,
32+
status:"connecting",
33+
operating_system:"linux",
34+
architecture:"amd64",
35+
},
36+
},
37+
};
38+
39+
exportconstTimeout:Story={
40+
args:{
41+
agent:{
42+
...MockWorkspaceAgent,
43+
status:"timeout",
44+
operating_system:"linux",
45+
architecture:"amd64",
46+
},
47+
},
48+
};
49+
50+
exportconstDifferentOS:Story={
51+
args:{
52+
agent:{
53+
...MockWorkspaceAgent,
54+
status:"connecting",
55+
operating_system:"darwin",
56+
architecture:"arm64",
57+
},
58+
},
59+
};
60+
61+
exportconstNotExternalAgent:Story={
62+
args:{
63+
agent:{
64+
...MockWorkspaceAgent,
65+
status:"connecting",
66+
operating_system:"linux",
67+
architecture:"amd64",
68+
},
69+
},
70+
};
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import{workspaceAgentCredentials}from"api/queries/workspaces";
2+
importtype{Workspace,WorkspaceAgent}from"api/typesGenerated";
3+
import{ErrorAlert}from"components/Alert/ErrorAlert";
4+
import{CodeExample}from"components/CodeExample/CodeExample";
5+
import{Loader}from"components/Loader/Loader";
6+
importtype{FC}from"react";
7+
import{useQuery}from"react-query";
8+
9+
interfaceAgentExternalProps{
10+
agent:WorkspaceAgent;
11+
workspace:Workspace;
12+
}
13+
14+
exportconstAgentExternal:FC<AgentExternalProps>=({ agent, workspace})=>{
15+
const{
16+
data:credentials,
17+
error,
18+
isLoading,
19+
isError,
20+
}=useQuery(workspaceAgentCredentials(workspace.id,agent.name));
21+
22+
if(isLoading){
23+
return<Loader/>;
24+
}
25+
26+
if(isError){
27+
return<ErrorAlerterror={error}/>;
28+
}
29+
30+
return(
31+
<sectionclassName="text-base text-muted-foreground pb-2 leading-relaxed">
32+
<p>
33+
Please run the following command to attach an agent to the{" "}
34+
{workspace.name} workspace:
35+
</p>
36+
<CodeExample
37+
code={credentials?.command??""}
38+
secret={false}
39+
redactPattern={/CODER_AGENT_TOKEN="([^"]+)"/g}
40+
redactReplacement={`CODER_AGENT_TOKEN="********"`}
41+
showRevealButton={true}
42+
/>
43+
</section>
44+
);
45+
};

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import AutoSizer from "react-virtualized-auto-sizer";
2727
importtype{FixedSizeListasList,ListOnScrollProps}from"react-window";
2828
import{AgentApps,organizeAgentApps}from"./AgentApps/AgentApps";
2929
import{AgentDevcontainerCard}from"./AgentDevcontainerCard";
30+
import{AgentExternal}from"./AgentExternal";
3031
import{AgentLatency}from"./AgentLatency";
3132
import{AGENT_LOG_LINE_HEIGHT}from"./AgentLogs/AgentLogLine";
3233
import{AgentLogs}from"./AgentLogs/AgentLogs";
@@ -58,13 +59,14 @@ export const AgentRow: FC<AgentRowProps> = ({
5859
onUpdateAgent,
5960
initialMetadata,
6061
})=>{
61-
const{ browser_only}=useFeatureVisibility();
62+
const{ browser_only, workspace_external_agent}=useFeatureVisibility();
6263
constappSections=organizeAgentApps(agent.apps);
6364
consthasAppsToDisplay=
6465
!browser_only||appSections.some((it)=>it.apps.length>0);
66+
constisExternalAgent=workspace.latest_build.has_external_agent;
6567
constshouldDisplayAgentApps=
6668
(agent.status==="connected"&&hasAppsToDisplay)||
67-
agent.status==="connecting";
69+
(agent.status==="connecting"&&!isExternalAgent);
6870
consthasVSCodeApp=
6971
agent.display_apps.includes("vscode")||
7072
agent.display_apps.includes("vscode_insiders");
@@ -258,7 +260,7 @@ export const AgentRow: FC<AgentRowProps> = ({
258260
</section>
259261
)}
260262

261-
{agent.status==="connecting"&&(
263+
{agent.status==="connecting"&&!isExternalAgent&&(
262264
<sectioncss={styles.apps}>
263265
<Skeleton
264266
width={80}
@@ -293,6 +295,12 @@ export const AgentRow: FC<AgentRowProps> = ({
293295
</section>
294296
)}
295297

298+
{isExternalAgent&&
299+
(agent.status==="timeout"||agent.status==="connecting")&&
300+
workspace_external_agent&&(
301+
<AgentExternalagent={agent}workspace={workspace}/>
302+
)}
303+
296304
<AgentMetadatainitialMetadata={initialMetadata}agent={agent}/>
297305
</div>
298306

‎site/src/modules/workspaces/actions.ts‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,14 @@ export const abilitiesByWorkspaceStatus = (
6363
};
6464
}
6565

66+
if(workspace.latest_build.has_external_agent){
67+
return{
68+
actions:[],
69+
canCancel:false,
70+
canAcceptJobs:true,
71+
};
72+
}
73+
6674
conststatus=workspace.latest_build.status;
6775

6876
switch(status){

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp