diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..3b614348 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26375083..f86c8230 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -40,6 +40,7 @@ Coder Remote periodically reads the `network-info-dir + "/" + matchingSSHPID` fi ```bash # Inside https://github.com/coder/coder + # on Mac replace /tmp/coder with "$TMPDIR" $ go build -o /tmp/coder ./cmd/coder ``` diff --git a/package.json b/package.json index a3e3a4e3..8825dfd2 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "coder": [ { "id": "coderRemote", - "name": "", + "name": "Workspaces", "visibility": "visible", "icon": "media/logo.svg", "contextualTitle": "Coder Remote" diff --git a/src/commands.ts b/src/commands.ts index 1ea8d5c3..fe8867e5 100644 --- a/src/commands.ts +++ b/src/commands.ts @@ -4,9 +4,10 @@ import { Workspace } from "coder/site/src/api/typesGenerated" import * as vscode from "vscode" import { Remote } from "./remote" import { Storage } from "./storage" +import { WorkspacesProvider } from "./workspaces" export class Commands { - public constructor(private readonly storage: Storage) {} + public constructor(private readonly storage: Storage, private readonly treeDataProvider: WorkspacesProvider) {} public async login(...args: string[]): Promise { let url: string | undefined = args.length >= 1 ? args[0] : undefined @@ -64,25 +65,17 @@ export class Commands { await this.storage.setSessionToken(token) const user = await getUser() await vscode.commands.executeCommand("setContext", "coder.authenticated", true) - vscode.window - .showInformationMessage( - `Welcome to Coder, ${user.username}!`, - { - detail: "You can now use the Coder extension to manage your Coder instance.", - }, - "Open Workspace", - ) - .then((action) => { - if (action === "Open Workspace") { - vscode.commands.executeCommand("coder.open") - } - }) + this.treeDataProvider.refresh() + vscode.window.showInformationMessage(`Welcome to Coder, ${user.username}!`, { + detail: "You can now use the Coder extension to manage your Coder instance.", + }) } public async logout(): Promise { await this.storage.setURL(undefined) await this.storage.setSessionToken(undefined) await vscode.commands.executeCommand("setContext", "coder.authenticated", false) + this.treeDataProvider.refresh() vscode.window.showInformationMessage("You've been logged out of Coder!", "Login").then((action) => { if (action === "Login") { vscode.commands.executeCommand("coder.login") diff --git a/src/extension.ts b/src/extension.ts index 64ab01d2..65ce8d86 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -6,12 +6,16 @@ import * as vscode from "vscode" import { Commands } from "./commands" import { Remote } from "./remote" import { Storage } from "./storage" +import { WorkspacesProvider } from "./workspaces" export async function activate(ctx: vscode.ExtensionContext): Promise { const output = vscode.window.createOutputChannel("Coder") + const workspacesProvider = new WorkspacesProvider() const storage = new Storage(output, ctx.globalState, ctx.secrets, ctx.globalStorageUri, ctx.logUri) await storage.init() + vscode.window.registerTreeDataProvider("coderRemote", workspacesProvider) + getUser() .then(() => { vscode.commands.executeCommand("setContext", "coder.authenticated", true) @@ -50,7 +54,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { }, }) - const commands = new Commands(storage) + const commands = new Commands(storage, workspacesProvider) vscode.commands.registerCommand("coder.login", commands.login.bind(commands)) vscode.commands.registerCommand("coder.logout", commands.logout.bind(commands)) diff --git a/src/workspaces.ts b/src/workspaces.ts new file mode 100644 index 00000000..19508825 --- /dev/null +++ b/src/workspaces.ts @@ -0,0 +1,65 @@ +import { getWorkspaces } from "coder/site/src/api/api" +import * as vscode from "vscode" + +export class WorkspacesProvider implements vscode.TreeDataProvider { + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter< + Workspace | undefined | void + >() + readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event + constructor() { + // intentional blank link for ESLint + } + + refresh(): void { + this._onDidChangeTreeData.fire() + } + + getTreeItem(element: Workspace): vscode.TreeItem { + return element + } + + async getChildren(): Promise { + const workspaces = await getWorkspaces({ + q: "owner:me", + }).catch(() => { + // TODO: we should probably warn or error here + return + }) + + if (workspaces) { + const items: Workspace[] = workspaces.workspaces.map((workspace) => { + return new Workspace( + `${workspace.name}`, + vscode.TreeItemCollapsibleState.None, + `${workspace.latest_build.status !== "running" ? "circle-outline" : "circle-filled"}`, + { + command: "coder.open", + title: "", + arguments: [workspace.owner_name, workspace.name], + }, + ) + }) + + return Promise.resolve(items) + } else { + // TODO: should we issue a warning new workspaces found? + // Or return a link to create a new Workspace from the dashboard? + return Promise.resolve([]) + } + } +} + +export class Workspace extends vscode.TreeItem { + constructor( + public readonly label: string, + public readonly collapsibleState: vscode.TreeItemCollapsibleState, + public readonly iconId: vscode.ThemeIcon["id"], + public readonly command?: vscode.Command, + ) { + super(label, collapsibleState) + + this.tooltip = `${this.label}` + } + + iconPath = new vscode.ThemeIcon(this.iconId) +}