diff --git a/src/api/getServerSpec.ts b/src/api/getServerSpec.ts index e532075..bf5d48a 100644 --- a/src/api/getServerSpec.ts +++ b/src/api/getServerSpec.ts @@ -1,5 +1,6 @@ import * as vscode from "vscode"; import { IServerSpec } from "@intersystems-community/intersystems-servermanager"; +import { OBJECTSCRIPT_EXTENSIONID } from "../extension"; /** * Get a server specification. @@ -17,7 +18,42 @@ export async function getServerSpec( // Unknown server if (!server) { - return undefined; + const folder = vscode.workspace.workspaceFolders?.find(f => f.name === name); + if (!folder) { + return undefined; + } + + // It is the name of a workspace root folder + // Get the server details from the ObjectScript extension if available + const objectScriptExtension = vscode.extensions.getExtension(OBJECTSCRIPT_EXTENSIONID); + if (!objectScriptExtension) { + return undefined; + } + if (!objectScriptExtension.isActive) { + // Activating it here would cause a deadlock because the activate method of the ObjectScript extension itself calls our getServerSpec API + return undefined; + } + let serverForUri: any; + if (objectScriptExtension.exports.asyncServerForUri) { + serverForUri = await objectScriptExtension.exports.asyncServerForUri(folder.uri); + } else { + serverForUri = objectScriptExtension.exports.serverForUri(folder.uri); + } + if (!serverForUri) { + return undefined; + } + return { + name: serverForUri.serverName, + webServer: { + scheme: serverForUri.scheme, + host: serverForUri.host, + port: serverForUri.port, + pathPrefix: serverForUri.pathPrefix + }, + username: serverForUri.username, + password: serverForUri.password ? serverForUri.password : undefined, + description: `Server for workspace folder "${name}"`, + }; } server.name = name; diff --git a/src/extension.ts b/src/extension.ts index 26c2a17..8a0990d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -14,6 +14,8 @@ import { logout, serverSessions } from "./makeRESTRequest"; import { NamespaceTreeItem, ProjectTreeItem, ServerManagerView, ServerTreeItem, SMTreeItem, WebAppTreeItem } from "./ui/serverManagerView"; export const extensionId = "intersystems-community.servermanager"; +export const OBJECTSCRIPT_EXTENSIONID = "intersystems-community.vscode-objectscript"; + export let globalState: vscode.Memento; export function getAccountFromParts(serverName: string, userName?: string): vscode.AuthenticationSessionAccountInformation | undefined { @@ -262,18 +264,17 @@ export function activate(context: vscode.ExtensionContext) { const namespace = pathParts[3]; const serverSpec = await getServerSpec(serverName); if (serverSpec) { - const ISFS_ID = "intersystems-community.vscode-objectscript"; - const isfsExtension = vscode.extensions.getExtension(ISFS_ID); + const isfsExtension = vscode.extensions.getExtension(OBJECTSCRIPT_EXTENSIONID); if (isfsExtension) { if (!isfsExtension.isActive) { await isfsExtension.activate(); if (!isfsExtension.isActive) { - vscode.window.showErrorMessage(`${ISFS_ID} could not be activated.`, "Close"); + vscode.window.showErrorMessage(`${OBJECTSCRIPT_EXTENSIONID} could not be activated.`, "Close"); return; } } } else { - vscode.window.showErrorMessage(`${ISFS_ID} is not installed.`, "Close"); + vscode.window.showErrorMessage(`${OBJECTSCRIPT_EXTENSIONID} is not installed.`, "Close"); return; } diff --git a/src/ui/serverManagerView.ts b/src/ui/serverManagerView.ts index f1730ab..e29c7d0 100644 --- a/src/ui/serverManagerView.ts +++ b/src/ui/serverManagerView.ts @@ -2,8 +2,9 @@ import * as vscode from "vscode"; import { getServerNames } from "../api/getServerNames"; import { getServerSpec } from "../api/getServerSpec"; import { getServerSummary } from "../api/getServerSummary"; -import { IServerName } from "@intersystems-community/intersystems-servermanager"; +import { IServerName, IServerSpec } from "@intersystems-community/intersystems-servermanager"; import { makeRESTRequest } from "../makeRESTRequest"; +import { OBJECTSCRIPT_EXTENSIONID } from "../extension"; const SETTINGS_VERSION = "v1"; @@ -271,10 +272,12 @@ function allServers(treeItem: SMTreeItem, params?: any): ServerTreeItem[] { return children; } -function currentServers(element: SMTreeItem, params?: any): ServerTreeItem[] { +async function currentServers(element: SMTreeItem, params?: any): Promise { const children = new Map(); + const dockerLocalPorts = new Map(); - vscode.workspace.workspaceFolders?.map((folder) => { + const workspaceFolders = vscode.workspace.workspaceFolders || []; + await Promise.all(workspaceFolders.map(async (folder) => { const serverName = folder.uri.authority.split(":")[0]; if (["isfs", "isfs-readonly"].includes(folder.uri.scheme)) { const serverSummary = getServerSummary(serverName); @@ -284,10 +287,25 @@ function currentServers(element: SMTreeItem, params?: any): ServerTreeItem[] { new ServerTreeItem({ parent: element, label: serverName, id: serverName }, serverSummary), ); } + return; } const conn = vscode.workspace.getConfiguration("objectscript.conn", folder); const connServer = conn.get("server"); - if (connServer) { + if (conn.get("docker-compose")) { + const objectScriptExtension = vscode.extensions.getExtension(OBJECTSCRIPT_EXTENSIONID); + if (objectScriptExtension) { + if (!objectScriptExtension.isActive) { + await objectScriptExtension.activate(); + } + if (objectScriptExtension.isActive && objectScriptExtension.exports?.asyncServerForUri) { + const server = await objectScriptExtension.exports.asyncServerForUri(folder.uri); + if (server && server.host === "localhost" && server.port > 0 && !dockerLocalPorts.has(server.port)) { + dockerLocalPorts.set(server.port, folder.name); + } + } + } + } + else if (connServer) { const serverSummary = getServerSummary(connServer); if (serverSummary) { children.set( @@ -296,7 +314,18 @@ function currentServers(element: SMTreeItem, params?: any): ServerTreeItem[] { ); } } - }); + + })); + + dockerLocalPorts.forEach((name, port) => { + if (!children.has(name)) { + const serverSummary: IServerName = { name, description: `Docker service bound to local port ${port} for folder '${name}'`, detail: `http://localhost:${port}/` }; + children.set( + name, + new ServerTreeItem({ parent: element, label: `docker:${port}`, id: name }, serverSummary), + ); + } + }) return Array.from(children.values()).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0); } @@ -347,7 +376,7 @@ export class ServerTreeItem extends SMTreeItem { super({ getChildren: serverFeatures, id: parentFolderId + ":" + serverSummary.name, - label: serverSummary.name, + label: element.label ? element.label : serverSummary.name, params: { serverSummary }, parent: element.parent, tooltip: new vscode.MarkdownString(wrappedDetail).appendMarkdown(escapedDescription ? `\n\n*${escapedDescription}*` : ""), @@ -377,12 +406,12 @@ async function serverFeatures(element: ServerTreeItem, params?: any): Promise { + const { name, description, detail } = serverSummary; + const dockerDetail = detail.match(/^http:\/\/localhost:(\d+)\/$/); + if (dockerDetail) { + return { name, description, webServer: { scheme: "http", host: "127.0.0.1", port: parseInt(dockerDetail[1], 10), pathPrefix: "" } }; + } + return getServerSpec(name); +} + // tslint:disable-next-line: max-classes-per-file export class FeatureTreeItem extends SMTreeItem { } @@ -428,6 +466,7 @@ export class NamespacesTreeItem extends FeatureTreeItem { constructor( element: ISMItem, serverName: string, + serverSpec: IServerSpec, username: string ) { const parentFolderId = element.parent?.id || ""; @@ -435,7 +474,7 @@ export class NamespacesTreeItem extends FeatureTreeItem { getChildren: serverNamespaces, id: parentFolderId + ":namespaces", label: "Namespaces", - params: { serverName }, + params: { serverName, serverSpec }, parent: element.parent, tooltip: `Namespaces '${username}' can access`, }); @@ -457,7 +496,7 @@ async function serverNamespaces(element: ServerTreeItem, params?: any): Promise< if (params?.serverName) { const name: string = params.serverName; - const serverSpec = await getServerSpec(name); + const serverSpec: IServerSpec | undefined = params.serverSpec; if (!serverSpec) { return undefined; } @@ -468,7 +507,7 @@ async function serverNamespaces(element: ServerTreeItem, params?: any): Promise< } else { const serverApiVersion = response.data.result.content.api; response.data.result.content.namespaces.map((namespace: string) => { - children.push(new NamespaceTreeItem({ parent: element, label: name, id: name }, namespace, name, serverApiVersion)); + children.push(new NamespaceTreeItem({ parent: element, label: name, id: name }, namespace, name, serverSpec, serverApiVersion)); }); } } @@ -483,6 +522,7 @@ export class NamespaceTreeItem extends SMTreeItem { element: ISMItem, name: string, serverName: string, + serverSpec: IServerSpec, serverApiVersion: number ) { const parentFolderId = element.parent?.id || ""; @@ -493,7 +533,7 @@ export class NamespaceTreeItem extends SMTreeItem { parent: element.parent, tooltip: `${name} on ${serverName}`, getChildren: namespaceFeatures, - params: { serverName, serverApiVersion } + params: { serverName, serverSpec, serverApiVersion } }); this.name = name; this.contextValue = `${serverApiVersion.toString()}/${name === "%SYS" ? "sysnamespace" : "namespace"}`; @@ -510,8 +550,8 @@ export class NamespaceTreeItem extends SMTreeItem { */ async function namespaceFeatures(element: NamespaceTreeItem, params?: any): Promise { return [ - new ProjectsTreeItem({ parent: element, id: element.name, label: element.name }, params.serverName, params.serverApiVersion), - new WebAppsTreeItem({ parent: element, id: element.name, label: element.name }, params.serverName, params.serverApiVersion) + new ProjectsTreeItem({ parent: element, id: element.name, label: element.name }, params.serverName, params.serverSpec, params.serverApiVersion), + new WebAppsTreeItem({ parent: element, id: element.name, label: element.name }, params.serverName, params.serverSpec, params.serverApiVersion) ]; } @@ -520,6 +560,7 @@ export class ProjectsTreeItem extends FeatureTreeItem { constructor( element: ISMItem, serverName: string, + serverSpec: IServerSpec, serverApiVersion: number ) { const parentFolderId = element.parent?.id || ''; @@ -529,7 +570,7 @@ export class ProjectsTreeItem extends FeatureTreeItem { id: parentFolderId + ':projects', tooltip: `Projects in this namespace`, getChildren: namespaceProjects, - params: { serverName, serverApiVersion, ns: element.label } + params: { serverName, serverSpec, serverApiVersion, ns: element.label } }); this.name = 'Projects'; this.contextValue = serverApiVersion.toString() + '/projects'; @@ -549,7 +590,7 @@ async function namespaceProjects(element: ProjectsTreeItem, params?: any): Promi if (params?.serverName && params.ns) { const name: string = params.serverName; - const serverSpec = await getServerSpec(name) + const serverSpec: IServerSpec | undefined = params.serverSpec; if (!serverSpec) { return undefined } @@ -607,6 +648,7 @@ export class WebAppsTreeItem extends FeatureTreeItem { constructor( element: ISMItem, serverName: string, + serverSpec: IServerSpec, serverApiVersion: number ) { const parentFolderId = element.parent?.id || ''; @@ -616,7 +658,7 @@ export class WebAppsTreeItem extends FeatureTreeItem { id: parentFolderId + ':webapps', tooltip: `Web Applications in this namespace`, getChildren: namespaceWebApps, - params: { serverName, serverApiVersion, ns: element.label } + params: { serverName, serverSpec, serverApiVersion, ns: element.label } }); this.name = 'Web Applications'; this.contextValue = serverApiVersion.toString() + '/webapps'; @@ -636,7 +678,7 @@ async function namespaceWebApps(element: ProjectsTreeItem, params?: any): Promis if (params?.serverName && params.ns) { const name: string = params.serverName; - const serverSpec = await getServerSpec(name) + const serverSpec: IServerSpec | undefined = params.serverSpec; if (!serverSpec) { return undefined }