@@ -12,6 +12,7 @@ import (
1212"golang.org/x/mod/semver"
1313
1414"github.com/coder/coder/v2/coderd/database/dbtime"
15+ "github.com/coder/coder/v2/coderd/util/slice"
1516"github.com/coder/coder/v2/codersdk"
1617"github.com/coder/pretty"
1718)
@@ -29,6 +30,7 @@ type WorkspaceResourcesOptions struct {
2930ServerVersion string
3031ListeningPorts map [uuid.UUID ]codersdk.WorkspaceAgentListeningPortsResponse
3132Devcontainers map [uuid.UUID ]codersdk.WorkspaceAgentListContainersResponse
33+ ShowDetails bool
3234}
3335
3436// WorkspaceResources displays the connection status and tree-view of provided resources.
@@ -69,7 +71,11 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
6971
7072totalAgents := 0
7173for _ ,resource := range resources {
72- totalAgents += len (resource .Agents )
74+ for _ ,agent := range resource .Agents {
75+ if ! agent .ParentID .Valid {
76+ totalAgents ++
77+ }
78+ }
7379}
7480
7581for _ ,resource := range resources {
@@ -94,12 +100,15 @@ func WorkspaceResources(writer io.Writer, resources []codersdk.WorkspaceResource
94100"" ,
95101})
96102// 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 {
98107tableWriter .AppendRow (renderAgentRow (agent ,index ,totalAgents ,options ))
99108for _ ,row := range renderListeningPorts (options ,agent .ID ,index ,totalAgents ) {
100109tableWriter .AppendRow (row )
101110}
102- for _ ,row := range renderDevcontainers (options ,agent .ID ,index ,totalAgents ) {
111+ for _ ,row := range renderDevcontainers (resources , options ,agent .ID ,index ,totalAgents ) {
103112tableWriter .AppendRow (row )
104113}
105114}
@@ -125,7 +134,7 @@ func renderAgentRow(agent codersdk.WorkspaceAgent, index, totalAgents int, optio
125134}
126135if ! options .HideAccess {
127136sshCommand := "coder ssh " + options .WorkspaceName
128- if totalAgents > 1 {
137+ if totalAgents > 1 || len ( options . Devcontainers ) > 0 {
129138sshCommand += "." + agent .Name
130139}
131140sshCommand = pretty .Sprint (DefaultStyles .Code ,sshCommand )
@@ -164,45 +173,129 @@ func renderPortRow(port codersdk.WorkspaceAgentListeningPort, idx, total int) ta
164173return table.Row {sb .String ()}
165174}
166175
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 {
168177var rows []table.Row
169178if wro .Devcontainers == nil {
170179return []table.Row {}
171180}
172181dc ,ok := wro .Devcontainers [agentID ]
173- if ! ok || len (dc .Containers )== 0 {
182+ if ! ok || len (dc .Devcontainers )== 0 {
174183return []table.Row {}
175184}
176185rows = append (rows , table.Row {
177186fmt .Sprintf (" %s─ %s" ,renderPipe (index ,totalAgents ),"Devcontainers" ),
178187})
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 ) ... )
181190}
182191return rows
183192}
184193
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 ))
198298}
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
206299}
207300
208301func renderAgentStatus (agent codersdk.WorkspaceAgent )string {