@@ -12,6 +12,7 @@ import (
12
12
"golang.org/x/mod/semver"
13
13
14
14
"github.com/coder/coder/v2/coderd/database/dbtime"
15
+ "github.com/coder/coder/v2/coderd/util/slice"
15
16
"github.com/coder/coder/v2/codersdk"
16
17
"github.com/coder/pretty"
17
18
)
@@ -29,6 +30,7 @@ type WorkspaceResourcesOptions struct {
29
30
ServerVersion string
30
31
ListeningPorts map [uuid.UUID ]codersdk.WorkspaceAgentListeningPortsResponse
31
32
Devcontainers map [uuid.UUID ]codersdk.WorkspaceAgentListContainersResponse
33
+ ShowDetails bool
32
34
}
33
35
34
36
// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -69,7 +71,11 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
69
71
70
72
totalAgents := 0
71
73
for _ , resource := range resources {
72
- totalAgents += len (resource .Agents )
74
+ for _ , agent := range resource .Agents {
75
+ if ! agent .ParentID .Valid {
76
+ totalAgents ++
77
+ }
78
+ }
73
79
}
74
80
75
81
for _ , resource := range resources {
@@ -94,12 +100,15 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
94
100
"" ,
95
101
})
96
102
// Display all agents associated with the resource.
97
- for index , agent := range resource .Agents {
103
+ agents := slice .Filter (resource .Agents , func (agent codersdk.WorkspaceAgent ) bool {
104
+ return ! agent .ParentID .Valid
105
+ })
106
+ for index , agent := range agents {
98
107
tableWriter .AppendRow (renderAgentRow (agent , index , totalAgents , options ))
99
108
for _ , row := range renderListeningPorts (options , agent .ID , index , totalAgents ) {
100
109
tableWriter .AppendRow (row )
101
110
}
102
- for _ , row := range renderDevcontainers (options , agent .ID , index , totalAgents ) {
111
+ for _ , row := range renderDevcontainers (resources , options , agent .ID , index , totalAgents ) {
103
112
tableWriter .AppendRow (row )
104
113
}
105
114
}
@@ -125,7 +134,7 @@ func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, optio
125
134
}
126
135
if ! options .HideAccess {
127
136
sshCommand := "coder ssh " + options .WorkspaceName
128
- if totalAgents > 1 {
137
+ if totalAgents > 1 || len ( options . Devcontainers ) > 0 {
129
138
sshCommand += "." + agent .Name
130
139
}
131
140
sshCommand = pretty .Sprint (DefaultStyles .Code , sshCommand )
@@ -164,45 +173,129 @@ func renderPortRow(port codersdk.WorkspaceAgentListeningPort, idx, total int) ta
164
173
return table.Row {sb .String ()}
165
174
}
166
175
167
- func renderDevcontainers (wro WorkspaceResourcesOptions , agentID uuid.UUID , index , totalAgents int ) []table.Row {
176
+ func renderDevcontainers (resources []codersdk. WorkspaceResource , wro WorkspaceResourcesOptions , agentID uuid.UUID , index , totalAgents int ) []table.Row {
168
177
var rows []table.Row
169
178
if wro .Devcontainers == nil {
170
179
return []table.Row {}
171
180
}
172
181
dc , ok := wro .Devcontainers [agentID ]
173
- if ! ok || len (dc .Containers ) == 0 {
182
+ if ! ok || len (dc .Devcontainers ) == 0 {
174
183
return []table.Row {}
175
184
}
176
185
rows = append (rows , table.Row {
177
186
fmt .Sprintf (" %s─ %s" , renderPipe (index , totalAgents ), "Devcontainers" ),
178
187
})
179
- for idx , container := range dc .Containers {
180
- rows = append (rows , renderDevcontainerRow (container , idx , len (dc .Containers )) )
188
+ for idx , devcontainer := range dc .Devcontainers {
189
+ rows = append (rows , renderDevcontainerRow (resources , devcontainer , idx , len (dc .Devcontainers ), wro ) ... )
181
190
}
182
191
return rows
183
192
}
184
193
185
- func renderDevcontainerRow (container codersdk.WorkspaceAgentContainer , index , total int ) table.Row {
186
- var row table.Row
187
- var sb strings.Builder
188
- _ , _ = sb .WriteString (" " )
189
- _ , _ = sb .WriteString (renderPipe (index , total ))
190
- _ , _ = sb .WriteString ("─ " )
191
- _ , _ = sb .WriteString (pretty .Sprintf (DefaultStyles .Code , "%s" , container .FriendlyName ))
192
- row = append (row , sb .String ())
193
- sb .Reset ()
194
- if container .Running {
195
- _ , _ = sb .WriteString (pretty .Sprintf (DefaultStyles .Keyword , "(%s)" , container .Status ))
196
- } else {
197
- _ , _ = sb .WriteString (pretty .Sprintf (DefaultStyles .Error , "(%s)" , container .Status ))
194
+ func renderDevcontainerRow (resources []codersdk.WorkspaceResource , devcontainer codersdk.WorkspaceAgentDevcontainer , index , total int , wro WorkspaceResourcesOptions ) []table.Row {
195
+ var rows []table.Row
196
+
197
+ // If the devcontainer is running and has an associated agent, we want to
198
+ // display the agent's details. Otherwise, we just display the devcontainer
199
+ // name and status.
200
+ var subAgent * codersdk.WorkspaceAgent
201
+ displayName := devcontainer .Name
202
+ if devcontainer .Agent != nil && devcontainer .Status == codersdk .WorkspaceAgentDevcontainerStatusRunning {
203
+ for _ , resource := range resources {
204
+ if agent , found := slice .Find (resource .Agents , func (agent codersdk.WorkspaceAgent ) bool {
205
+ return agent .ID == devcontainer .Agent .ID
206
+ }); found {
207
+ subAgent = & agent
208
+ break
209
+ }
210
+ }
211
+ if subAgent != nil {
212
+ displayName = subAgent .Name
213
+ displayName += fmt .Sprintf (" (%s, %s)" , subAgent .OperatingSystem , subAgent .Architecture )
214
+ }
215
+ }
216
+
217
+ if devcontainer .Container != nil {
218
+ displayName += " " + pretty .Sprint (DefaultStyles .Keyword , "[" + devcontainer .Container .FriendlyName + "]" )
219
+ }
220
+
221
+ // Build the main row.
222
+ row := table.Row {
223
+ fmt .Sprintf (" %s─ %s" , renderPipe (index , total ), displayName ),
224
+ }
225
+
226
+ // Add status, health, and version columns.
227
+ if ! wro .HideAgentState {
228
+ if subAgent != nil {
229
+ row = append (row , renderAgentStatus (* subAgent ))
230
+ row = append (row , renderAgentHealth (* subAgent ))
231
+ row = append (row , renderAgentVersion (subAgent .Version , wro .ServerVersion ))
232
+ } else {
233
+ row = append (row , renderDevcontainerStatus (devcontainer .Status ))
234
+ row = append (row , "" ) // No health for devcontainer without agent.
235
+ row = append (row , "" ) // No version for devcontainer without agent.
236
+ }
237
+ }
238
+
239
+ // Add access column.
240
+ if ! wro .HideAccess {
241
+ if subAgent != nil {
242
+ accessString := fmt .Sprintf ("coder ssh %s.%s" , wro .WorkspaceName , subAgent .Name )
243
+ row = append (row , pretty .Sprint (DefaultStyles .Code , accessString ))
244
+ } else {
245
+ row = append (row , "" ) // No access for devcontainers without agent.
246
+ }
247
+ }
248
+
249
+ rows = append (rows , row )
250
+
251
+ // Add error message if present.
252
+ if errorMessage := devcontainer .Error ; errorMessage != "" {
253
+ // Cap error message length for display.
254
+ if ! wro .ShowDetails && len (errorMessage ) > 80 {
255
+ errorMessage = errorMessage [:79 ] + "…"
256
+ }
257
+ errorRow := table.Row {
258
+ " × " + pretty .Sprint (DefaultStyles .Error , errorMessage ),
259
+ "" ,
260
+ "" ,
261
+ "" ,
262
+ }
263
+ if ! wro .HideAccess {
264
+ errorRow = append (errorRow , "" )
265
+ }
266
+ rows = append (rows , errorRow )
267
+ }
268
+
269
+ // Add listening ports for the devcontainer agent.
270
+ if subAgent != nil {
271
+ portRows := renderListeningPorts (wro , subAgent .ID , index , total )
272
+ for _ , portRow := range portRows {
273
+ // Adjust indentation for ports under devcontainer agent.
274
+ if len (portRow ) > 0 {
275
+ if str , ok := portRow [0 ].(string ); ok {
276
+ portRow [0 ] = " " + str // Add extra indentation.
277
+ }
278
+ }
279
+ rows = append (rows , portRow )
280
+ }
281
+ }
282
+
283
+ return rows
284
+ }
285
+
286
+ func renderDevcontainerStatus (status codersdk.WorkspaceAgentDevcontainerStatus ) string {
287
+ switch status {
288
+ case codersdk .WorkspaceAgentDevcontainerStatusRunning :
289
+ return pretty .Sprint (DefaultStyles .Keyword , "▶ running" )
290
+ case codersdk .WorkspaceAgentDevcontainerStatusStopped :
291
+ return pretty .Sprint (DefaultStyles .Placeholder , "⏹ stopped" )
292
+ case codersdk .WorkspaceAgentDevcontainerStatusStarting :
293
+ return pretty .Sprint (DefaultStyles .Warn , "⧗ starting" )
294
+ case codersdk .WorkspaceAgentDevcontainerStatusError :
295
+ return pretty .Sprint (DefaultStyles .Error , "✘ error" )
296
+ default :
297
+ return pretty .Sprint (DefaultStyles .Placeholder , "○ " + string (status ))
198
298
}
199
- row = append (row , sb .String ())
200
- sb .Reset ()
201
- // "health" is not applicable here.
202
- row = append (row , sb .String ())
203
- _ , _ = sb .WriteString (container .Image )
204
- row = append (row , sb .String ())
205
- return row
206
299
}
207
300
208
301
func renderAgentStatus (agent codersdk.WorkspaceAgent ) string {
0 commit comments