11import { Api } from "coder/site/src/api/api"
2- import { Workspace , WorkspaceAgent } from "coder/site/src/api/typesGenerated"
2+ import { Workspace , WorkspaceAgent , WorkspaceApp } from "coder/site/src/api/typesGenerated"
33import { EventSource } from "eventsource"
44import * as path from "path"
55import * as vscode from "vscode"
@@ -146,9 +146,36 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
146146 }
147147 } )
148148
149- return resp . workspaces . map ( ( workspace ) => {
150- return new WorkspaceTreeItem ( workspace , this . getWorkspacesQuery === WorkspaceQuery . All , showMetadata )
151- } )
149+ // Create tree items for each workspace
150+ const workspaceTreeItems = await Promise . all (
151+ resp . workspaces . map ( async ( workspace ) => {
152+ const workspaceTreeItem = new WorkspaceTreeItem (
153+ workspace ,
154+ this . getWorkspacesQuery === WorkspaceQuery . All ,
155+ showMetadata ,
156+ )
157+
158+ // Get app status from the workspace agents
159+ const agents = extractAgents ( workspace )
160+ agents . forEach ( ( agent ) => {
161+ // Check if agent has apps property with status reporting
162+ if ( agent . apps && Array . isArray ( agent . apps ) ) {
163+ workspaceTreeItem . appStatus = agent . apps . map ( ( app : WorkspaceApp ) => ( {
164+ name : app . display_name ,
165+ url : app . url ,
166+ agent_id : agent . id ,
167+ agent_name : agent . name ,
168+ command : app . command ,
169+ workspace_name : workspace . name ,
170+ } ) )
171+ }
172+ } )
173+
174+ return workspaceTreeItem
175+ } ) ,
176+ )
177+
178+ return workspaceTreeItems
152179 }
153180
154181 /**
@@ -207,14 +234,58 @@ export class WorkspaceProvider implements vscode.TreeDataProvider<vscode.TreeIte
207234 const agentTreeItems = agents . map (
208235 ( agent ) => new AgentTreeItem ( agent , element . workspaceOwner , element . workspaceName , element . watchMetadata ) ,
209236 )
237+
210238 return Promise . resolve ( agentTreeItems )
211239 } else if ( element instanceof AgentTreeItem ) {
212240 const watcher = this . agentWatchers [ element . agent . id ]
213241 if ( watcher ?. error ) {
214242 return Promise . resolve ( [ new ErrorTreeItem ( watcher . error ) ] )
215243 }
244+
245+ const items : vscode . TreeItem [ ] = [ ]
246+
247+ // Add app status section with collapsible header
248+ if ( element . agent . apps && element . agent . apps . length > 0 ) {
249+ const appStatuses = [ ]
250+ for ( const app of element . agent . apps ) {
251+ if ( app . statuses && app . statuses . length > 0 ) {
252+ for ( const status of app . statuses ) {
253+ // Show all statuses, not just ones needing attention.
254+ // We need to do this for now because the reporting isn't super accurate
255+ // yet.
256+ appStatuses . push (
257+ new AppStatusTreeItem ( {
258+ name : status . message ,
259+ command : app . command ,
260+ workspace_name : element . workspaceName ,
261+ } ) ,
262+ )
263+ }
264+ }
265+ }
266+
267+ // Show the section if it has any items
268+ if ( appStatuses . length > 0 ) {
269+ const appStatusSection = new SectionTreeItem ( "App Statuses" , appStatuses . reverse ( ) )
270+ items . push ( appStatusSection )
271+ }
272+ }
273+
216274 const savedMetadata = watcher ?. metadata || [ ]
217- return Promise . resolve ( savedMetadata . map ( ( metadata ) => new AgentMetadataTreeItem ( metadata ) ) )
275+
276+ // Add agent metadata section with collapsible header
277+ if ( savedMetadata . length > 0 ) {
278+ const metadataSection = new SectionTreeItem (
279+ "Agent Metadata" ,
280+ savedMetadata . map ( ( metadata ) => new AgentMetadataTreeItem ( metadata ) ) ,
281+ )
282+ items . push ( metadataSection )
283+ }
284+
285+ return Promise . resolve ( items )
286+ } else if ( element instanceof SectionTreeItem ) {
287+ // Return the children of the section
288+ return Promise . resolve ( element . children )
218289 }
219290
220291 return Promise . resolve ( [ ] )
@@ -265,6 +336,19 @@ function monitorMetadata(agentId: WorkspaceAgent["id"], restClient: Api): AgentW
265336 return watcher
266337}
267338
339+ /**
340+ * A tree item that represents a collapsible section with child items
341+ */
342+ class SectionTreeItem extends vscode . TreeItem {
343+ constructor (
344+ label : string ,
345+ public readonly children : vscode . TreeItem [ ] ,
346+ ) {
347+ super ( label , vscode . TreeItemCollapsibleState . Collapsed )
348+ this . contextValue = "coderSectionHeader"
349+ }
350+ }
351+
268352class ErrorTreeItem extends vscode . TreeItem {
269353 constructor ( error : unknown ) {
270354 super ( "Failed to query metadata: " + errToStr ( error , "no error provided" ) , vscode . TreeItemCollapsibleState . None )
@@ -285,6 +369,28 @@ class AgentMetadataTreeItem extends vscode.TreeItem {
285369 }
286370}
287371
372+ class AppStatusTreeItem extends vscode . TreeItem {
373+ constructor (
374+ public readonly app : {
375+ name : string
376+ url ?: string
377+ command ?: string
378+ workspace_name ?: string
379+ } ,
380+ ) {
381+ super ( "" , vscode . TreeItemCollapsibleState . None )
382+ this . description = app . name
383+ this . contextValue = "coderAppStatus"
384+
385+ // Add command to handle clicking on the app
386+ this . command = {
387+ command : "coder.openAppStatus" ,
388+ title : "Open App Status" ,
389+ arguments : [ app ] ,
390+ }
391+ }
392+ }
393+
288394type CoderOpenableTreeItemType = "coderWorkspaceSingleAgent" | "coderWorkspaceMultipleAgents" | "coderAgent"
289395
290396export class OpenableTreeItem extends vscode . TreeItem {
@@ -335,6 +441,15 @@ class AgentTreeItem extends OpenableTreeItem {
335441}
336442
337443export class WorkspaceTreeItem extends OpenableTreeItem {
444+ public appStatus : {
445+ name : string
446+ url ?: string
447+ agent_id ?: string
448+ agent_name ?: string
449+ command ?: string
450+ workspace_name ?: string
451+ } [ ] = [ ]
452+
338453 constructor (
339454 public readonly workspace : Workspace ,
340455 public readonly showOwner : boolean ,
0 commit comments