diff --git a/CHANGELOG.md b/CHANGELOG.md
index f60a817..11344c5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,6 @@
+## 2.0.0 (16-Apr-2021)
+* Add tree view interface.
+
## 1.0.5 (02-Feb-2021)
* Fix publication problem (#69).
diff --git a/README.md b/README.md
index c47b44e..4a8a273 100644
--- a/README.md
+++ b/README.md
@@ -1,23 +1,75 @@
# InterSystems Server Manager
-This is a VS Code helper extension for defining connections to [InterSystems®](https://www.intersystems.com/) servers. These connection definitions can used by other VS Code extensions when they make connections. One example is the [ObjectScript extension](https://github.com/intersystems-community/vscode-objectscript).
+
+InterSystems Server Manager is a Visual Studio Code extension for defining connections to [InterSystems](https://www.intersystems.com/) servers. These definitions can used by other VS Code extensions when they make connections. One example is the [ObjectScript extension](https://github.com/intersystems-community/vscode-objectscript) for code editing.
+
+# New in 2.0 - April 2021
+
+> We are pleased to publish version 2.0 of this extension, adding a tree-style user interface. This significant new release is competing in the April 2021 InterSystems Programming Contest for Developer Tools. If you like the new 2.0 features please visit the [contest page](https://openexchange.intersystems.com/contest/13) no later than April 25 and vote for us.
+
+> Thanks to [George James Software](https://georgejames.com) for backing this development effort.
+
+## The Server Tree
+
+Server Manager displays connection definitions as a tree on an InterSystems Tools view:
+
+![Server Manager tree](images/README/tree.png)
+
+In this tree you can:
+
+- Launch the InterSystems Management Portal, either in a VS Code tab or in your default browser.
+- List namespaces.
+- Add namespaces to your VS Code workspace for viewing or editing code on the server with the [ObjectScript extension](https://github.com/intersystems-community/vscode-objectscript).
+- Tag favorite servers.
+- Set icon colors.
+- Focus on recently used connections.
+- Manage stored passwords.
+- Add new servers, and edit existing ones.
In common with the rest of VS Code, Server Manager stores your connection settings in JSON files. VS Code settings are arranged in a hierarchy that you can learn more about [here](https://code.visualstudio.com/docs/getstarted/settings).
-Using Server Manager you can store connection passwords in the native keystore of your workstation's operating system instead of as plaintext in JSON files.
+Server Manager can store connection passwords in the native keystore of your workstation's operating system. This is a more secure alternative to you putting them as plaintext in your JSON files.
+
+On Windows, Server Manager can create connection entries for all connections you previously defined with the original Windows app called InterSystems Server Manager. This action is available from the '`...`' menu at the top right corner of Server Manager's tree.
+
+## Defining a New Server
+
+1. Click the '`+`' button on Server Manager's title bar.
+2. Complete the sequence of prompts.
+3. Expand `All Servers` to see your new entry in the tree.
+
+The server definition is added to your [user-level](https://code.visualstudio.com/docs/getstarted/settings) `settings.json` file.
+
+Optionally use its context menu to store the password for the username you entered when defining the server. You can also set the color of the server icon.
-On Windows you can run `Import Servers from Registry` from [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) to create connection entries for all connections you previously defined with the InterSystems Server Manager.
+The 'star' button that appears when you hover over the row lets you add the server to the `Favorites` list at the top of the tree.
-## Defining a new connection
+## Launching Management Portal
-1. Open the VS Code [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) by typing Ctrl+Shift+P (Cmd+Shift+P on macOS) or F1.
-2. Start typing "Server Manager" to locate `InterSystems Server Manager: Store Password in Keychain`.
-3. Type the '+' character into the quickpick input field. Alternatively click the '+' button on the top right of the quickpick.
-4. Complete the prompts. By the time you reach the password prompt your connection definition has already been saved in **user-level** JSON. If you prefer to enter your password whenever a VS Code extension connects via Server Manager for the first time in a session, press Esc here.
+When you hover over a server entry in the tree, two command buttons let you launch InterSystems Management Portal.
-## Amending and removing definitions
+The first button uses VS Code's Simple Browser feature, which creates a tab alongside any documents you may have open. The second button opens Portal in your workstation's default web browser.
-To manage your definitions, [edit the relevant JSON file](https://code.visualstudio.com/docs/getstarted/settings). VS Code offers several routes to these files. One way is to type "json" into the Command Palette.
+### Notes About Simple Browser
+- There is only ever a single Simple Browser tab. Launching another server's Management Portal in it will replace the previous one.
+- If the server version is InterSystems IRIS 2021.1.1 or later you will need to change a setting on the suite of web applications that implement Management Portal. This is a consequence of change [SGM031 - Support SameSite for CSP session and user cookies](https://docs.intersystems.com/iris20201/csp/docbook/relnotes/index.html#SGM031). Simple Browser will not be permitted to store Portal's session management cookies, so Portal must be willing to fall back to using the CSPCHD query parameter mechanism.
+ - Locate the five web applications whose path begins with `/csp/sys`
+ ![Portal web app list](images/README/portalWebApps.png)
+
+ - Alter the `Use Cookie for Session` setting on each of them so it is `Autodetect` instead of `Always`.
+ ![Portal web app detail](images/README/portalWebAppSetting.png)
+ Remember to save the change. The change is not thought to have any adverse effects on the usage of Portal from ordinary browsers, which will continue to use session cookies.
+- When a 2020.1.1+ Portal has resorted to using CSPCHD (see above) a few inter-page links fail because they don't add the CSPCHD queryparam. One specific case is the breadcrumb links. Pending the arrival of an InterSystems correction (JIRA DP-404817) these links will take you to the login page. Either enter your credentials to proceed, or launch Simple Browser again from the Server Manager tree.
+
+## Amending and Removing Servers
+
+To manage your server definitions, including changing the username it connects with, [edit the relevant JSON file](https://code.visualstudio.com/docs/getstarted/settings).
+
+1. From a server's context menu, or from Server Manager's top-right '`...`' menu, choose `Edit Settings`. This opens VS Code's Settings Editor and filters its contents.
+
+![Edit Settings](images/README/editSettings.png)
+
+2. Click the `Edit in settings.json` link.
In this example two connections have been defined:
@@ -48,25 +100,37 @@ In this example two connections have been defined:
The JSON editor offers the usual [IntelliSense](https://code.visualstudio.com/docs/editor/intellisense) as you work in this structure.
-Notice how you can add a `description` property to each connection. This will be shown alongside its entry in the server quickpick.
+Notice how you can add a `description` property to each connection. This will be shown in the hover in Server Manager's tree, and alongside the entry if a server quickpick is used.
Servers are displayed in the quickpick in the order they are defined in the JSON file. The exception is that if a server name is set as the value of the `/default` property (see example above) it will be shown first in the list.
-A set of embedded servers with names beginning `default~` will appear at the end of the quickpick unless you add the property `"/hideEmbeddedEntries": true` to your `intersystems.server` object (see above).
+A set of embedded servers with names beginning `default~` will appear at the end of the lists unless you add the property `"/hideEmbeddedEntries": true` to your `intersystems.server` object to hide them (see above).
+
+## Removing a Stored Password
+
+Use the server's context menu. Alternatively, run `InterSystems Server Manager: Clear Password from Keychain` from Command Palette.
+
+---
+
+## Technical Notes
+
+### Colors, Favorites and Recents
+
+These features use VS Code's extension-private global state storage. Data is not present in your `settings.json` file.
+
+### The 'All Servers' Folder
-## Removing a stored password
+The `All Servers` tree respects the optional `/default` and `/hideEmbeddedEntries` settings in the `intersystems.servers` JSON.
-The command `InterSystems Server Manager: Clear Password from Keychain` removes a stored password.
+If a server has been named in `/default` it is promoted to the top of the list, which is otherwise presented in alphabetical order.
-**Usage:**
+Embedded entries (built-in default ones) are demoted to the end of the list, or omitted completely if `/hideEmbeddedEntries` is true.
-1. Bring up the [Command Palette](https://code.visualstudio.com/docs/getstarted/userinterface#_command-palette) by typing Ctrl+Shift+P (Cmd+Shift+P on Mac) or F1.
-2. Start typing "Server Manager" to locate the `InterSystems Server Manager: Clear Password from Keychain` command.
-3. Pick a server.
+---
-## For VS Code Extension Developers: Use By Other Extensions
+## Information for VS Code Extension Developers - How To Leverage Server Manager
-An extension XYZ needing to connect to InterSystems servers can define this extension as a dependency in its `package.json`
+An extension XYZ needing to connect to InterSystems servers can define Server Manager as a dependency in its `package.json` like this:
```json
"extensionDependencies": [
@@ -74,7 +138,7 @@ An extension XYZ needing to connect to InterSystems servers can define this exte
],
```
-Alternatively the `activate` method of XYZ can detect if the extension is already available, then offer to install it if necessary:
+Alternatively the `activate` method of XYZ can detect whether the extension is already available, then offer to install it if necessary:
```ts
const extId = "intersystems-community.servermanager";
diff --git a/images/README/editSettings.png b/images/README/editSettings.png
new file mode 100644
index 0000000..79cff41
Binary files /dev/null and b/images/README/editSettings.png differ
diff --git a/images/README/portalWebAppSetting.png b/images/README/portalWebAppSetting.png
new file mode 100644
index 0000000..bfcf4f8
Binary files /dev/null and b/images/README/portalWebAppSetting.png differ
diff --git a/images/README/portalWebApps.png b/images/README/portalWebApps.png
new file mode 100644
index 0000000..153af91
Binary files /dev/null and b/images/README/portalWebApps.png differ
diff --git a/images/README/tree.png b/images/README/tree.png
new file mode 100644
index 0000000..49255eb
Binary files /dev/null and b/images/README/tree.png differ
diff --git a/package.json b/package.json
index d537777..510efb3 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
"name": "servermanager",
"displayName": "InterSystems Server Manager",
- "version": "2.0.0-SNAPSHOT.4",
+ "version": "2.0.0-SNAPSHOT",
"publisher": "intersystems-community",
- "description": "Helper extension for defining connections to InterSystems servers.",
+ "description": "Define connections to InterSystems servers. Browse and manage those servers.",
"repository": {
"type": "git",
"url": "https://github.com/intersystems-community/intersystems-servermanager"
@@ -89,6 +89,13 @@
}
]
},
+ "viewsWelcome": [
+ {
+ "view": "explorer",
+ "contents": "Use the 'InterSystems Tools: Servers' view to work with your servers.\n[Manage Servers $(tools)](command:workbench.view.extension.intersystems-community_servermanager)"
+ }
+ ]
+ ,
"configuration": {
"title": "InterSystems Server Manager",
"properties": {
@@ -392,6 +399,11 @@
}
],
"view/item/context": [
+ {
+ "command": "intersystems-community.servermanager.addServer",
+ "when": "view == intersystems-community_servermanager && viewItem == sorted",
+ "group": "inline@10"
+ },
{
"command": "intersystems-community.servermanager.addToStarred",
"when": "view == intersystems-community_servermanager && viewItem =~ /\\.server\\.$/",
diff --git a/src/api/addServer.ts b/src/api/addServer.ts
index 11f5852..735fad8 100644
--- a/src/api/addServer.ts
+++ b/src/api/addServer.ts
@@ -27,69 +27,78 @@ export async function addServer(
.then(
async (name): Promise => {
if (name) {
- const host = await vscode.window.showInputBox({
- placeHolder: "Hostname or IP address of web server",
- validateInput: (value) => {
- return value.trim().length ? undefined : "Required";
- },
+ const description = await vscode.window.showInputBox({
+ placeHolder: "Optional description",
ignoreFocusOut: true
});
- if (host) {
- spec.webServer.host = host.trim();
- const portString = await vscode.window.showInputBox({
- placeHolder: "Port of web server",
+ if (typeof description !== 'undefined') {
+ if (description) {
+ spec.description = description.trim();
+ }
+ const host = await vscode.window.showInputBox({
+ placeHolder: "Hostname or IP address of web server",
validateInput: (value) => {
- const port = +value;
- return value.match(/\d+/) &&
- port.toString() === value &&
- port > 0 &&
- port < 65536
- ? undefined
- : "Required, 1-65535";
+ return value.trim().length ? undefined : "Required";
},
ignoreFocusOut: true
});
- if (portString) {
- spec.webServer.port = +portString;
- const username = await vscode.window.showInputBox({
- placeHolder:
- "Username",
- prompt:
- "Leave empty to be prompted when connecting.",
+ if (host) {
+ spec.webServer.host = host.trim();
+ const portString = await vscode.window.showInputBox({
+ placeHolder: "Port of web server",
+ validateInput: (value) => {
+ const port = +value;
+ return value.match(/\d+/) &&
+ port.toString() === value &&
+ port > 0 &&
+ port < 65536
+ ? undefined
+ : "Required, 1-65535";
+ },
ignoreFocusOut: true
- });
- if (typeof username !== 'undefined') {
- const usernameTrimmed = username.trim();
- if (usernameTrimmed !== "") {
- spec.username = usernameTrimmed;
- }
- const scheme = await vscode.window.showQuickPick(
- ["http", "https"],
- {
- placeHolder:
- "Confirm connection type, then the definition will be stored in your User Settings. 'Escape' to cancel.",
- ignoreFocusOut: true
+ });
+ if (portString) {
+ spec.webServer.port = +portString;
+ const username = await vscode.window.showInputBox({
+ placeHolder:
+ "Username",
+ prompt:
+ "Leave empty to be prompted when connecting.",
+ ignoreFocusOut: true
+ });
+ if (typeof username !== 'undefined') {
+ const usernameTrimmed = username.trim();
+ if (usernameTrimmed !== "") {
+ spec.username = usernameTrimmed;
+ }
+ const scheme = await vscode.window.showQuickPick(
+ ["http", "https"],
+ {
+ placeHolder:
+ "Confirm connection type, then the definition will be stored in your User Settings. 'Escape' to cancel.",
+ ignoreFocusOut: true
+ }
+ );
+ if (scheme) {
+ spec.webServer.scheme = scheme;
+ try {
+ const config = vscode.workspace.getConfiguration(
+ "intersystems",
+ scope
+ );
+ // For simplicity we always add to the user-level (aka Global) settings
+ const servers: any =
+ config.inspect("servers")?.globalValue || {};
+ servers[name] = spec;
+ await config.update("servers", servers, true);
+ vscode.window.showInformationMessage(`Server '${name}' stored in user-level settings.`);
+ return name;
+ } catch (error) {
+ vscode.window.showErrorMessage(
+ "Failed to store server '${name}' definition."
+ );
+ return undefined;
}
- );
- if (scheme) {
- spec.webServer.scheme = scheme;
- try {
- const config = vscode.workspace.getConfiguration(
- "intersystems",
- scope
- );
- // For simplicity we always add to the user-level (aka Global) settings
- const servers: any =
- config.inspect("servers")?.globalValue || {};
- servers[name] = spec;
- await config.update("servers", servers, true);
- vscode.window.showInformationMessage(`Server '${name}' stored in user-level settings.`);
- return name;
- } catch (error) {
- vscode.window.showErrorMessage(
- "Failed to store server '${name}' definition."
- );
- return undefined;
}
}
}
diff --git a/src/ui/serverManagerView.ts b/src/ui/serverManagerView.ts
index 45e8ca3..943e2e5 100644
--- a/src/ui/serverManagerView.ts
+++ b/src/ui/serverManagerView.ts
@@ -128,26 +128,26 @@ class SMNodeProvider implements vscode.TreeDataProvider {
let firstRevealId = favoritesMap.size > 0 ? 'starred' : recentsArray.length > 0 ? 'recent' : 'sorted';
if (vscode.workspace.workspaceFolders?.length || 0 > 0) {
- children.push(new SMTreeItem({parent: element, label: 'Current', id: 'current', tooltip: 'Servers referenced by current workspace', codiconName: 'home', getChildren: currentServers}));
+ children.push(new SMTreeItem({parent: element, label: 'Current', id: 'current', contextValue: 'current', tooltip: 'Servers referenced by current workspace', codiconName: 'home', getChildren: currentServers}));
firstRevealId = 'current';
this._firstRevealItem = children[children.length - 1];
}
if (favoritesMap.size > 0) {
- children.push(new SMTreeItem({parent: element, label: 'Starred', id: 'starred', tooltip: 'Favorite servers', codiconName: 'star-full', getChildren: favoriteServers}));
+ children.push(new SMTreeItem({parent: element, label: 'Favorites', id: 'starred', contextValue: 'starred', tooltip: 'Favorite servers', codiconName: 'star-full', getChildren: favoriteServers}));
if (firstRevealId === 'starred') {
this._firstRevealItem = children[children.length - 1];
}
}
- children.push(new SMTreeItem({parent: element, label: 'Recent', id: 'recent', tooltip: 'Recently used servers', codiconName: 'history', getChildren: recentServers}));
+ children.push(new SMTreeItem({parent: element, label: 'Recent', id: 'recent', contextValue: 'recent', tooltip: 'Recently used servers', codiconName: 'history', getChildren: recentServers}));
if (firstRevealId === 'recent') {
this._firstRevealItem = children[children.length - 1];
}
// TODO - use this when we can implement resequencing in the UI
- // children.push(new SMTreeItem({parent: element, label: 'Ordered', id: 'ordered', tooltip: 'All servers in settings.json order', codiconName: 'list-ordered', getChildren: allServers, params: {sorted: false}}));
+ // children.push(new SMTreeItem({parent: element, label: 'Ordered', id: 'ordered', contextValue: 'ordered', tooltip: 'All servers in settings.json order', codiconName: 'list-ordered', getChildren: allServers, params: {sorted: false}}));
- children.push(new SMTreeItem({parent: element, label: 'All Servers', id: 'sorted', tooltip: 'All servers in alphabetical order', codiconName: 'server-environment', getChildren: allServers, params: {sorted: true}}));
+ children.push(new SMTreeItem({parent: element, label: 'All Servers', id: 'sorted', contextValue: 'sorted', tooltip: 'All servers in alphabetical order', codiconName: 'server-environment', getChildren: allServers, params: {sorted: true}}));
if (firstRevealId === 'sorted') {
this._firstRevealItem = children[children.length - 1];
}
@@ -286,20 +286,30 @@ export class ServerTreeItem extends SMTreeItem {
serverSummary: ServerName
) {
const parentFolderId = element.parent?.id || "";
+ // Convert linebreaks etc, escape Markdown characters, truncate
+ const escapedDescription = serverSummary.description
+ .replace(/[\n\t]/g, ' ')
+ .replace(/[\r\f\b]/g, '')
+ .replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
+ .substr(0,90)
+ .trim();
// Wrap detail (a uri string) as a null link to prevent it from being linkified
+ const wrappedDetail = `[${serverSummary.detail}]()`;
super({
parent: element.parent,
label: serverSummary.name,
id: parentFolderId + ':' + serverSummary.name,
- tooltip: new vscode.MarkdownString(`[${serverSummary.detail}]()`).appendMarkdown(serverSummary.description ? `\n\n*${serverSummary.description}*` : ''),
+ tooltip: new vscode.MarkdownString(wrappedDetail).appendMarkdown(escapedDescription ? `\n\n*${escapedDescription}*` : ''),
getChildren: serverFeatures,
params: { serverSummary }
});
this.name = serverSummary.name;
- //this.command = {command: 'intersystems-community.servermanager.openPortalTab', title: 'Open Management Portal in Simple Browser Tab', arguments: [this]};
this.contextValue = `${parentFolderId}.server.${favoritesMap.has(this.name) ? 'starred' : ''}`;
const color = colorsMap.get(this.name);
this.iconPath = new vscode.ThemeIcon('server-environment', color ? new vscode.ThemeColor('charts.' + color) : undefined);
+
+ // TODO If single click on server item should open Portal tab
+ // this.command = {command: 'intersystems-community.servermanager.openPortalTab', title: 'Open Management Portal in Simple Browser Tab', arguments: [this]};
}
}