Skip to content

Commit

Permalink
Add Debug Proxy Terminal (#31)
Browse files Browse the repository at this point in the history
* update tasks/launch

* update cloud api funcs to return unauthorized obj on 401 status

* reworked cloud data provider

* rework login/logout commands + add cloud refresh command

* rework update debug proxy

* initial debug proxy terminal work

* launchDashboard command

* move commands to folder

* cleanup utils

* * rename code lens provider and uri handler
* move source parser code into code lens file

* remove global CloudDataProvider

* remove global ProvenanceDatabase

* implement shutdownDebugProxy command

* move command names to index

* don't validate token in getStoredCloudCredentials

* rename utils -> utility

* finish rework

* renames

* finish rename

* change dashboard icon to match wf picker

* rename one of two 'launchDebugProxy' files

* * rework debug proxy terminal to fix relaunch issue
* shutdownProvenanceDbConnectionPool

* validateCredentials to trigger login flow

---------

Co-authored-by: Harry Pierson <harrypierson@outlook.com>
  • Loading branch information
devhawk and Harry Pierson authored May 3, 2024
1 parent 2f59861 commit e72818c
Show file tree
Hide file tree
Showing 37 changed files with 1,354 additions and 1,030 deletions.
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
"outFiles": [
"${workspaceFolder}/out/**/*.js"
],
"preLaunchTask": "${defaultBuildTask}"
"preLaunchTask": "tsc: watch"
}
]
}
5 changes: 4 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"out": true // set this to false to include "out" folder in search results
},
// Turn off tsc task auto detection since we have the necessary tasks as npm scripts
"typescript.tsc.autoDetect": "off"
"typescript.tsc.autoDetect": "off",
"cSpell.words": [
"ttdbg"
]
}
46 changes: 39 additions & 7 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
@@ -1,21 +1,53 @@
// See https://go.microsoft.com/fwlink/?LinkId=733558
// for the documentation about the tasks.json format
{
"version": "2.0.0",
"tasks": [
{
"label": "tsc: build",
"type": "typescript",
"group": {
"kind": "build",
"isDefault": true
},
"tsconfig": "tsconfig.json",
"problemMatcher": "$tsc",
"presentation": {
"reveal": "silent"
}
},
{
"label": "tsc: watch",
"type": "typescript",
"group": "build",
"tsconfig": "tsconfig.json",
"option": "watch",
"problemMatcher": "$tsc-watch",
"isBackground": true,
"presentation": {
"reveal": "silent"
}
},
{
"label": "npm: watch",
"type": "npm",
"group": "build",
"script": "watch",
"problemMatcher": "$esbuild-watch",
"isBackground": true,
"presentation": {
"reveal": "never",
"revealProblems": "onProblem"
},
"group": {
"kind": "build",
"isDefault": true
}
},
{
"label": "npm: compile",
"type": "npm",
"group": "build",
"script": "compile",
"problemMatcher": "$esbuild",
"presentation": {
"reveal": "never",
"revealProblems": "onProblem"
}
}
]
}
}
38 changes: 38 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
"category": "DBOS",
"icon": "$(log-in)"
},
{
"command": "dbos-ttdbg.refresh-domain",
"title": "Refresh DBOS Cloud Resources",
"category": "DBOS",
"icon": "$(refresh)"
},
{
"command": "dbos-ttdbg.delete-domain-credentials",
"title": "Delete Stored DBOS Cloud Credentials",
Expand All @@ -52,6 +58,23 @@
"command": "dbos-ttdbg.shutdown-debug-proxy",
"title": "Shutdown Debug Proxy",
"category": "DBOS"
},
{
"command": "dbos-ttdbg.update-debug-proxy",
"title": "Update Debug Proxy",
"category": "DBOS"
},
{
"command": "dbos-ttdbg.launch-debug-proxy",
"title": "Launch Debug Proxy",
"category": "DBOS",
"icon": "$(debug)"
},
{
"command": "dbos-ttdbg.launch-dashboard",
"title": "Launch DBOS Dashboard",
"category": "DBOS",
"icon": "$(server)"
}
],
"configuration": {
Expand Down Expand Up @@ -85,13 +108,28 @@
"when": "view == dbos-ttdbg.views.resources && viewItem == cloudDomain",
"group": "inline"
},
{
"command": "dbos-ttdbg.launch-dashboard",
"when": "view == dbos-ttdbg.views.resources && (viewItem == cloudDomain || viewItem == cloudApp)",
"group": "inline"
},
{
"command": "dbos-ttdbg.refresh-domain",
"when": "view == dbos-ttdbg.views.resources && viewItem == cloudDomain",
"group": "inline"
},
{
"command": "dbos-ttdbg.delete-domain-credentials",
"when": "view == dbos-ttdbg.views.resources && viewItem == cloudDomain"
},
{
"command": "dbos-ttdbg.delete-app-db-password",
"when": "view == dbos-ttdbg.views.resources && viewItem == cloudApp"
},
{
"command": "dbos-ttdbg.launch-debug-proxy",
"when": "view == dbos-ttdbg.views.resources && viewItem == cloudApp",
"group": "inline"
}
]
},
Expand Down
177 changes: 107 additions & 70 deletions src/CloudDataProvider.ts
Original file line number Diff line number Diff line change
@@ -1,83 +1,94 @@
import * as vscode from 'vscode';
import { DbosCloudApp, DbosCloudDatabase, DbosCloudCredentials, listApps, listDatabases, getCloudDomain, authenticate, isTokenExpired, DbosCloudDomain } from './dbosCloudApi';
import { DbosCloudApp, getCloudDomain, DbosCloudDbInstance, listApps, listDbInstances, isUnauthorized } from './dbosCloudApi';
import { config } from './extension';
import { validateCredentials } from './validateCredentials';

export interface CloudDomainNode {
kind: "cloudDomain";
domain: string;
credentials?: DbosCloudCredentials;
}

interface CloudServiceTypeNode {
kind: "cloudServiceType";
type: "Applications" | "Databases";
interface CloudResourceTypeNode {
kind: "cloudResourceType";
type: "apps" | "dbInstances";
domain: string;
credentials?: DbosCloudCredentials;
};

export interface CloudAppNode {
kind: "cloudApp";
domain: string;
app: DbosCloudApp;
credentials: DbosCloudCredentials;
};

export interface CloudDatabaseNode {
kind: "cloudDatabase";
database: DbosCloudDatabase;
credentials: DbosCloudCredentials;
export interface CloudDbInstanceNode {
kind: "cloudDbInstance";
domain: string;
dbInstance: DbosCloudDbInstance;
}

type CloudProviderNode = CloudDomainNode | CloudServiceTypeNode | CloudAppNode | CloudDatabaseNode;
type CloudProviderNode = CloudDomainNode | CloudResourceTypeNode | CloudAppNode | CloudDbInstanceNode;

export class CloudDataProvider implements vscode.TreeDataProvider<CloudProviderNode> {
private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter<CloudProviderNode | CloudProviderNode[] | undefined | null | void>();
readonly onDidChangeTreeData = this.onDidChangeTreeDataEmitter.event;

private readonly domains = new Set<string>();
private readonly domains: Array<CloudDomainNode>;
private readonly apps = new Map<string, CloudAppNode[]>();
private readonly dbInstances = new Map<string, CloudDbInstanceNode[]>();

constructor() {
const { cloudDomain } = getCloudDomain();
this.domains.add(cloudDomain);
this.domains = [{ kind: "cloudDomain", domain: cloudDomain }];
}

async #getCredentials(domain?: string | DbosCloudDomain): Promise<DbosCloudCredentials | undefined> {
const storedCredentials = await config.getStoredCloudCredentials(domain);
if (storedCredentials) {
return storedCredentials;
async refresh(domain: string) {
this.apps.delete(domain);
this.dbInstances.delete(domain);

const node = this.domains.find(d => d.domain === domain);
if (node) {
this.onDidChangeTreeDataEmitter.fire(node);
}
return await config.cloudLogin(domain);
}

async getChildren(element?: CloudProviderNode | undefined): Promise<CloudProviderNode[]> {
if (element === undefined) {
const children = new Array<CloudDomainNode>();
for (const domain of this.domains) {
const credentials = await config.getStoredCloudCredentials(domain);
children.push({ kind: 'cloudDomain', domain, credentials });
}
return children;
return this.domains;
}

if (element.kind === "cloudDomain") {
return [
<CloudServiceTypeNode>{ kind: 'cloudServiceType', domain: element.domain, credentials: element.credentials, type: "Applications" },
<CloudServiceTypeNode>{ kind: 'cloudServiceType', domain: element.domain, credentials: element.credentials, type: "Databases" },
];
if (!this.apps.has(element.domain) || !this.dbInstances.has(element.domain)) {
const credentials = await config.getCredentials(element.domain);
if (!validateCredentials(credentials)) { return []; }

const [apps, dbInstances] = await Promise.all([listApps(credentials), listDbInstances(credentials)]);
if (isUnauthorized(apps)) {
this.apps.delete(element.domain);
} else {
this.apps.set(element.domain, apps.map(a => ({ kind: "cloudApp", domain: element.domain, app: a })));
}
if (isUnauthorized(dbInstances)) {
this.dbInstances.delete(element.domain);
} else {
this.dbInstances.set(element.domain, dbInstances.map(dbi => ({ kind: "cloudDbInstance", domain: element.domain, dbInstance: dbi })));
}
return [
{ kind: "cloudResourceType", type: "apps", domain: element.domain },
{ kind: "cloudResourceType", type: "dbInstances", domain: element.domain },
];
}
}

if (element.kind === "cloudServiceType") {
const credentials = element.credentials ?? await this.#getCredentials(element.domain);
if (!credentials) { return []; }
if (element.kind === "cloudResourceType") {
switch (element.type) {
case "Applications": {
const apps = await listApps(credentials);
return apps.map(app => ({ kind: "cloudApp", app, credentials }));
case "apps": {
return this.apps.get(element.domain) ?? [];
}
case "Databases": {
const dbs = await listDatabases(credentials);
return dbs.map(database => ({ kind: "cloudDatabase", database, credentials }));
case "dbInstances": {
return this.dbInstances.get(element.domain) ?? [];
}
default:
const _: never = element.type;
throw new Error(`Unknown service type: ${element.type}`);
}
}
Expand All @@ -86,38 +97,64 @@ export class CloudDataProvider implements vscode.TreeDataProvider<CloudProviderN
}

getTreeItem(element: CloudProviderNode): vscode.TreeItem | Thenable<vscode.TreeItem> {
if (element.kind === 'cloudDomain') {
return {
label: element.domain,
collapsibleState: vscode.TreeItemCollapsibleState.Expanded,
contextValue: element.kind,
};
}

if (element.kind === 'cloudServiceType') {
return {
label: element.type,
collapsibleState: vscode.TreeItemCollapsibleState.Expanded,
contextValue: element.kind,
};
}

if (element.kind === 'cloudApp') {
return {
label: element.app.Name,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: element.kind,
};
}

if (element.kind === 'cloudDatabase') {
return {
label: element.database.PostgresInstanceName,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: element.kind,
};
const { kind } = element;
switch (kind) {
case "cloudDomain": {
return {
label: element.domain,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: element.kind,
};
}
case 'cloudResourceType': {
let label: string;
switch (element.type) {
case "apps": label = "Applications"; break;
case "dbInstances": label = "Database Instances"; break;
default:
const _: never = element.type;
throw new Error(`Unknown service type: ${element.type}`);
}
return {
label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: element.kind,
};
}
case 'cloudApp': {
const { app } = element;
const tooltip = `
Database Instance: ${app.PostgresInstanceName}\n
Database Name: ${app.ApplicationDatabaseName}\n
Status: ${app.Status}\n
Version: ${app.Version}\n
Application URL: ${app.AppURL}`;

return {
label: app.Name,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: element.kind,
tooltip: new vscode.MarkdownString(tooltip)
};
}
case 'cloudDbInstance': {
const { dbInstance: dbi } = element;
const tooltip = `
Host Name: ${dbi.HostName}\n
Port: ${dbi.Port}\n
Username: ${dbi.DatabaseUsername}\n
Status: ${dbi.Status}`;

return {
label: element.dbInstance.PostgresInstanceName,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: element.kind,
tooltip: new vscode.MarkdownString(tooltip)
};
}
default:
const _: never = kind;
throw new Error(`Unknown service type: ${kind}`);
}

throw new Error(`Unknown element type: ${JSON.stringify(element)}`);
}
}
Loading

0 comments on commit e72818c

Please sign in to comment.