@@ -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,135 @@ 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
+ for _ , agent := range resource .Agents {
205
+ if agent .ID == devcontainer .Agent .ID {
206
+ subAgent = & agent
207
+ break
208
+ }
209
+ }
210
+ if subAgent != nil {
211
+ break
212
+ }
213
+ }
214
+
215
+ displayName = devcontainer .Agent .Name
216
+ if subAgent != nil {
217
+ displayName += fmt .Sprintf (" (%s, %s)" , subAgent .OperatingSystem , subAgent .Architecture )
218
+ } else {
219
+ displayName += " (linux, amd64)"
220
+ }
221
+ }
222
+
223
+ if devcontainer .Container != nil {
224
+ displayName += " " + pretty .Sprint (DefaultStyles .Keyword , "[" + devcontainer .Container .FriendlyName + "]" )
225
+ }
226
+
227
+ // Build the main row.
228
+ row := table.Row {
229
+ fmt .Sprintf (" %s─ %s" , renderPipe (index , total ), displayName ),
230
+ }
231
+
232
+ // Add status, health, and version columns.
233
+ if ! wro .HideAgentState {
234
+ if subAgent != nil {
235
+ row = append (row , renderAgentStatus (* subAgent ))
236
+ row = append (row , renderAgentHealth (* subAgent ))
237
+ row = append (row , renderAgentVersion (subAgent .Version , wro .ServerVersion ))
238
+ } else {
239
+ row = append (row , renderDevcontainerStatus (devcontainer .Status ))
240
+ row = append (row , "" ) // No health for devcontainer without agent.
241
+ row = append (row , "" ) // No version for devcontainer without agent.
242
+ }
243
+ }
244
+
245
+ // Add access column.
246
+ if ! wro .HideAccess {
247
+ if subAgent != nil {
248
+ accessString := fmt .Sprintf ("coder ssh %s.%s" , wro .WorkspaceName , subAgent .Name )
249
+ row = append (row , pretty .Sprint (DefaultStyles .Code , accessString ))
250
+ } else {
251
+ row = append (row , "" ) // No access for devcontainers without agent.
252
+ }
253
+ }
254
+
255
+ rows = append (rows , row )
256
+
257
+ // Add error message if present.
258
+ if errorMessage := devcontainer .Error ; errorMessage != "" {
259
+ // Cap error message length for display.
260
+ if ! wro .ShowDetails && len (errorMessage ) > 80 {
261
+ errorMessage = errorMessage [:77 ] + "..."
262
+ }
263
+ errorRow := table.Row {
264
+ " × " + pretty .Sprint (DefaultStyles .Error , errorMessage ),
265
+ "" ,
266
+ "" ,
267
+ "" ,
268
+ }
269
+ if ! wro .HideAccess {
270
+ errorRow = append (errorRow , "" )
271
+ }
272
+ rows = append (rows , errorRow )
273
+ }
274
+
275
+ // Add listening ports for the devcontainer agent.
276
+ if subAgent != nil {
277
+ portRows := renderListeningPorts (wro , subAgent .ID , index , total )
278
+ for _ , portRow := range portRows {
279
+ // Adjust indentation for ports under devcontainer agent.
280
+ if len (portRow ) > 0 {
281
+ if str , ok := portRow [0 ].(string ); ok {
282
+ portRow [0 ] = " " + str // Add extra indentation.
283
+ }
284
+ }
285
+ rows = append (rows , portRow )
286
+ }
287
+ }
288
+
289
+ return rows
290
+ }
291
+
292
+ func renderDevcontainerStatus (status codersdk.WorkspaceAgentDevcontainerStatus ) string {
293
+ switch status {
294
+ case codersdk .WorkspaceAgentDevcontainerStatusRunning :
295
+ return pretty .Sprint (DefaultStyles .Keyword , "▶ running" )
296
+ case codersdk .WorkspaceAgentDevcontainerStatusStopped :
297
+ return pretty .Sprint (DefaultStyles .Placeholder , "⏹ stopped" )
298
+ case codersdk .WorkspaceAgentDevcontainerStatusStarting :
299
+ return pretty .Sprint (DefaultStyles .Warn , "⧗ starting" )
300
+ case codersdk .WorkspaceAgentDevcontainerStatusError :
301
+ return pretty .Sprint (DefaultStyles .Error , "✘ error" )
302
+ default :
303
+ return pretty .Sprint (DefaultStyles .Placeholder , "○ " + string (status ))
198
304
}
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
305
}
207
306
208
307
func renderAgentStatus (agent codersdk.WorkspaceAgent ) string {
0 commit comments