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

Commite3c9e7f

Browse files
committed
Add workspace state and agent state machines
1 parent878e2ae commite3c9e7f

File tree

7 files changed

+565
-555
lines changed

7 files changed

+565
-555
lines changed

‎src/api/workspace.ts‎

Lines changed: 40 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import{typeApi}from"coder/site/src/api/api";
22
import{
33
typeWorkspaceAgentLog,
4+
typeProvisionerJobLog,
45
typeWorkspace,
56
typeWorkspaceAgent,
67
}from"coder/site/src/api/typesGenerated";
@@ -85,92 +86,72 @@ export async function startWorkspaceIfStoppedOrFailed(
8586
}
8687

8788
/**
88-
* Wait for the latest build to finish while streaming logs to the emitter.
89-
*
90-
* Once completed, fetch the workspace again and return it.
89+
* Streams build logs to the emitter in real-time.
90+
* Returns the websocket for lifecycle management.
9191
*/
92-
exportasyncfunctionwaitForBuild(
92+
exportasyncfunctionstreamBuildLogs(
9393
client:CoderApi,
9494
writeEmitter:vscode.EventEmitter<string>,
9595
workspace:Workspace,
96-
):Promise<Workspace>{
96+
):Promise<OneWayWebSocket<ProvisionerJobLog>>{
9797
constsocket=awaitclient.watchBuildLogsByBuildId(
9898
workspace.latest_build.id,
9999
[],
100100
);
101101

102-
awaitnewPromise<void>((resolve,reject)=>{
103-
socket.addEventListener("message",(data)=>{
104-
if(data.parseError){
105-
writeEmitter.fire(
106-
errToStr(data.parseError,"Failed to parse message")+"\r\n",
107-
);
108-
}else{
109-
writeEmitter.fire(data.parsedMessage.output+"\r\n");
110-
}
111-
});
112-
113-
socket.addEventListener("error",(error)=>{
114-
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
115-
returnreject(
116-
newError(
117-
`Failed to watch workspace build on${baseUrlRaw}:${errToStr(error,"no further details")}`,
118-
),
102+
socket.addEventListener("message",(data)=>{
103+
if(data.parseError){
104+
writeEmitter.fire(
105+
errToStr(data.parseError,"Failed to parse message")+"\r\n",
119106
);
120-
});
107+
}else{
108+
writeEmitter.fire(data.parsedMessage.output+"\r\n");
109+
}
110+
});
121111

122-
socket.addEventListener("close",()=>resolve());
112+
socket.addEventListener("error",(error)=>{
113+
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
114+
writeEmitter.fire(
115+
`Error watching workspace build logs on${baseUrlRaw}:${errToStr(error,"no further details")}\r\n`,
116+
);
123117
});
124118

125-
writeEmitter.fire("Build complete\r\n");
126-
constupdatedWorkspace=awaitclient.getWorkspace(workspace.id);
127-
writeEmitter.fire(
128-
`Workspace is now${updatedWorkspace.latest_build.status}\r\n`,
129-
);
130-
returnupdatedWorkspace;
119+
socket.addEventListener("close",()=>{
120+
writeEmitter.fire("Build complete\r\n");
121+
});
122+
123+
returnsocket;
131124
}
132125

133126
/**
134127
* Streams agent logs to the emitter in real-time.
135-
* Returns the websocketand a completion promise that rejects on error.
128+
* Returns the websocketfor lifecycle management.
136129
*/
137130
exportasyncfunctionstreamAgentLogs(
138131
client:CoderApi,
139132
writeEmitter:vscode.EventEmitter<string>,
140133
agent:WorkspaceAgent,
141-
):Promise<{
142-
socket:OneWayWebSocket<WorkspaceAgentLog[]>;
143-
completion:Promise<void>;
144-
}>{
134+
):Promise<OneWayWebSocket<WorkspaceAgentLog[]>>{
145135
constsocket=awaitclient.watchWorkspaceAgentLogs(agent.id,[]);
146136

147-
constcompletion=newPromise<void>((resolve,reject)=>{
148-
socket.addEventListener("message",(data)=>{
149-
if(data.parseError){
150-
writeEmitter.fire(
151-
errToStr(data.parseError,"Failed to parse message")+"\r\n",
152-
);
153-
}else{
154-
for(constlogofdata.parsedMessage){
155-
writeEmitter.fire(log.output+"\r\n");
156-
}
157-
}
158-
});
159-
160-
socket.addEventListener("error",(error)=>{
161-
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
137+
socket.addEventListener("message",(data)=>{
138+
if(data.parseError){
162139
writeEmitter.fire(
163-
`Error watching agent logs on${baseUrlRaw}:${errToStr(error,"no further details")}\r\n`,
140+
errToStr(data.parseError,"Failed to parse message")+"\r\n",
164141
);
165-
returnreject(
166-
newError(
167-
`Failed to watch agent logs on${baseUrlRaw}:${errToStr(error,"no further details")}`,
168-
),
169-
);
170-
});
142+
}else{
143+
for(constlogofdata.parsedMessage){
144+
writeEmitter.fire(log.output+"\r\n");
145+
}
146+
}
147+
});
171148

172-
socket.addEventListener("close",()=>resolve());
149+
socket.addEventListener("error",(error)=>{
150+
constbaseUrlRaw=client.getAxiosInstance().defaults.baseURL;
151+
writeEmitter.fire(
152+
`Error watching agent logs on${baseUrlRaw}:${errToStr(error,"no further details")}\r\n`,
153+
);
173154
});
174155

175-
return{socket, completion};
156+
returnsocket;
176157
}

‎src/commands.ts‎

Lines changed: 4 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import { type SecretsManager } from "./core/secretsManager";
1919
import{CertificateError}from"./error";
2020
import{getGlobalFlags}from"./globalFlags";
2121
import{typeLogger}from"./logging/logger";
22+
import{maybeAskAgent,maybeAskUrl}from"./promptUtils";
2223
import{escapeCommandArg,toRemoteAuthority,toSafeHost}from"./util";
2324
import{
2425
AgentTreeItem,
@@ -58,129 +59,6 @@ export class Commands {
5859
this.contextManager=serviceContainer.getContextManager();
5960
}
6061

61-
/**
62-
* Find the requested agent if specified, otherwise return the agent if there
63-
* is only one or ask the user to pick if there are multiple. Return
64-
* undefined if the user cancels.
65-
*/
66-
publicasyncmaybeAskAgent(
67-
agents:WorkspaceAgent[],
68-
filter?:string,
69-
):Promise<WorkspaceAgent|undefined>{
70-
constfilteredAgents=filter
71-
?agents.filter((agent)=>agent.name===filter)
72-
:agents;
73-
if(filteredAgents.length===0){
74-
thrownewError("Workspace has no matching agents");
75-
}elseif(filteredAgents.length===1){
76-
returnfilteredAgents[0];
77-
}else{
78-
constquickPick=vscode.window.createQuickPick();
79-
quickPick.title="Select an agent";
80-
quickPick.busy=true;
81-
constagentItems:vscode.QuickPickItem[]=filteredAgents.map((agent)=>{
82-
leticon="$(debug-start)";
83-
if(agent.status!=="connected"){
84-
icon="$(debug-stop)";
85-
}
86-
return{
87-
alwaysShow:true,
88-
label:`${icon}${agent.name}`,
89-
detail:`${agent.name} • Status:${agent.status}`,
90-
};
91-
});
92-
quickPick.items=agentItems;
93-
quickPick.busy=false;
94-
quickPick.show();
95-
96-
constselected=awaitnewPromise<WorkspaceAgent|undefined>(
97-
(resolve)=>{
98-
quickPick.onDidHide(()=>resolve(undefined));
99-
quickPick.onDidChangeSelection((selected)=>{
100-
if(selected.length<1){
101-
returnresolve(undefined);
102-
}
103-
constagent=filteredAgents[quickPick.items.indexOf(selected[0])];
104-
resolve(agent);
105-
});
106-
},
107-
);
108-
quickPick.dispose();
109-
returnselected;
110-
}
111-
}
112-
113-
/**
114-
* Ask the user for the URL, letting them choose from a list of recent URLs or
115-
* CODER_URL or enter a new one. Undefined means the user aborted.
116-
*/
117-
privateasyncaskURL(selection?:string):Promise<string|undefined>{
118-
constdefaultURL=vscode.workspace
119-
.getConfiguration()
120-
.get<string>("coder.defaultUrl")
121-
?.trim();
122-
constquickPick=vscode.window.createQuickPick();
123-
quickPick.value=
124-
selection||defaultURL||process.env.CODER_URL?.trim()||"";
125-
quickPick.placeholder="https://example.coder.com";
126-
quickPick.title="Enter the URL of your Coder deployment.";
127-
128-
// Initial items.
129-
quickPick.items=this.mementoManager
130-
.withUrlHistory(defaultURL,process.env.CODER_URL)
131-
.map((url)=>({
132-
alwaysShow:true,
133-
label:url,
134-
}));
135-
136-
// Quick picks do not allow arbitrary values, so we add the value itself as
137-
// an option in case the user wants to connect to something that is not in
138-
// the list.
139-
quickPick.onDidChangeValue((value)=>{
140-
quickPick.items=this.mementoManager
141-
.withUrlHistory(defaultURL,process.env.CODER_URL,value)
142-
.map((url)=>({
143-
alwaysShow:true,
144-
label:url,
145-
}));
146-
});
147-
148-
quickPick.show();
149-
150-
constselected=awaitnewPromise<string|undefined>((resolve)=>{
151-
quickPick.onDidHide(()=>resolve(undefined));
152-
quickPick.onDidChangeSelection((selected)=>resolve(selected[0]?.label));
153-
});
154-
quickPick.dispose();
155-
returnselected;
156-
}
157-
158-
/**
159-
* Ask the user for the URL if it was not provided, letting them choose from a
160-
* list of recent URLs or the default URL or CODER_URL or enter a new one, and
161-
* normalizes the returned URL. Undefined means the user aborted.
162-
*/
163-
publicasyncmaybeAskUrl(
164-
providedUrl:string|undefined|null,
165-
lastUsedUrl?:string,
166-
):Promise<string|undefined>{
167-
leturl=providedUrl||(awaitthis.askURL(lastUsedUrl));
168-
if(!url){
169-
// User aborted.
170-
returnundefined;
171-
}
172-
173-
// Normalize URL.
174-
if(!url.startsWith("http://")&&!url.startsWith("https://")){
175-
// Default to HTTPS if not provided so URLs can be typed more easily.
176-
url="https://"+url;
177-
}
178-
while(url.endsWith("/")){
179-
url=url.substring(0,url.length-1);
180-
}
181-
returnurl;
182-
}
183-
18462
/**
18563
* Log into the provided deployment. If the deployment URL is not specified,
18664
* ask for it first with a menu showing recent URLs along with the default URL
@@ -197,7 +75,7 @@ export class Commands {
19775
}
19876
this.logger.info("Logging in");
19977

200-
consturl=awaitthis.maybeAskUrl(args?.url);
78+
consturl=awaitmaybeAskUrl(this.mementoManager,args?.url);
20179
if(!url){
20280
return;// The user aborted.
20381
}
@@ -488,7 +366,7 @@ export class Commands {
488366
);
489367
}elseif(iteminstanceofWorkspaceTreeItem){
490368
constagents=awaitthis.extractAgentsWithFallback(item.workspace);
491-
constagent=awaitthis.maybeAskAgent(agents);
369+
constagent=awaitmaybeAskAgent(agents);
492370
if(!agent){
493371
// User declined to pick an agent.
494372
return;
@@ -611,7 +489,7 @@ export class Commands {
611489
}
612490

613491
constagents=awaitthis.extractAgentsWithFallback(workspace);
614-
constagent=awaitthis.maybeAskAgent(agents,agentName);
492+
constagent=awaitmaybeAskAgent(agents,agentName);
615493
if(!agent){
616494
// User declined to pick an agent.
617495
return;

‎src/extension.ts‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { Commands } from "./commands";
1212
import{ServiceContainer}from"./core/container";
1313
import{AuthAction}from"./core/secretsManager";
1414
import{CertificateError,getErrorDetail}from"./error";
15+
import{maybeAskUrl}from"./promptUtils";
1516
import{Remote}from"./remote/remote";
1617
import{toSafeHost}from"./util";
1718
import{
@@ -147,7 +148,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
147148
// queries will default to localhost) so ask for it if missing.
148149
// Pre-populate in case we do have the right URL so the user can just
149150
// hit enter and move on.
150-
consturl=awaitcommands.maybeAskUrl(
151+
consturl=awaitmaybeAskUrl(
152+
mementoManager,
151153
params.get("url"),
152154
mementoManager.getUrl(),
153155
);
@@ -230,7 +232,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
230232
// queries will default to localhost) so ask for it if missing.
231233
// Pre-populate in case we do have the right URL so the user can just
232234
// hit enter and move on.
233-
consturl=awaitcommands.maybeAskUrl(
235+
consturl=awaitmaybeAskUrl(
236+
mementoManager,
234237
params.get("url"),
235238
mementoManager.getUrl(),
236239
);

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp