import BaseCommand from "../common/baseCommand" ; import * as vscode from 'vscode' ; import { PostgreSQLTreeDataProvider } from "../tree/treeProvider" ; import { IConnection } from "../common/IConnection" ; import { Constants } from "../common/constants" ; import { v1 as uuidv1 } from 'uuid' ; import { Global } from "../common/global" ; import { MultiStepInput , InputFlowAction } from "../common/multiStepInput" ; import { Database } from "../common/database" ; import { PgClient } from '../common/connection' ; 'use strict' ; interface SSLQuickPickItem extends vscode . QuickPickItem { ssl : boolean ; } interface DatabaseQuickPickItem extends vscode . QuickPickItem { dbname ? : string ; } const sslOptions = [ { label : 'Use Secure Connection' , ssl : true } , { label : 'Standard Connection' , ssl : false } ] ; export class addConnectionCommand extends BaseCommand { readonly TITLE : string = 'Add Database Connection' ; readonly TotalSteps : number = 7 ; async run ( ) { const state = { port : 5432 } as Partial < ConnectionState > ; if ( ! ( await MultiStepInput . run ( input => this . setHostName ( input , state ) ) ) ) { return ; } const tree = PostgreSQLTreeDataProvider . getInstance ( ) ; let connections = tree . context . globalState . get < { [ key ] : IConnection ; } > ( Constants . GlobalStateKey ) ; if ( ! connections ) connections = { } ; const id = uuidv1 ( ) ; connections [ id ] = { label : state . label , host : state . host , user : state . user , port : state . port , ssl : state . secure , database : state . database } ; connections [ id ] . hasPassword = ! ! state . password ; if ( connections [ id ] . hasPassword ) { await Global . context . secrets . store ( id , state . password ) ; } await tree . context . globalState . update ( Constants . GlobalStateKey , connections ) ; tree . refresh ( ) ; } async setHostName ( input , state ) { state . host = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : 'The hostname of the database' , placeholder : 'ex. 127.0.0.1' , ignoreFocusOut : true , value : ( typeof state . host === 'string' ) ? state . host : '' , validate : async ( value ) => ( ! value || ! value . trim ( ) ) ? 'Hostname is required' : '' } ) ; state . label = state . host ; return ( input ) => this . setUsername ( input , state ) ; } async setUsername ( input , state ) { state . user = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : 'The PostgreSQL user to authenticate as' , placeholder : 'ex. root' , ignoreFocusOut : true , value : ( typeof state . user === 'string' ) ? state . user : '' , validate : async ( value ) => ( ! value || ! value . trim ( ) ) ? 'Username is required' : '' } ) ; return ( input ) => this . setPassword ( input , state ) ; } async setPassword ( input , state ) { state . password = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : 'The password of the PostgreSQL user' , placeholder : '' , ignoreFocusOut : true , password : true , value : ( typeof state . password === 'string' ) ? state . password : '' , validate : async ( value ) => '' } ) ; return ( input ) => this . setPort ( input , state ) ; } async setPort ( input , state ) { state . port = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : 'The port number to connect to' , placeholder : 'ex. 5432' , ignoreFocusOut : true , value : ( typeof state . port === 'number' ) ? state . port . toString ( ) : '' , validate : async ( value ) => { if ( ! value || ! value . trim ( ) ) return 'Port number is required' ; return Number . isNaN ( Number . parseInt ( value ) ) ? 'The port number specified was not a number' : '' ; } , convert : async ( value ) => Number . parseInt ( value ) } ) ; return ( input ) => this . setSSL ( input , state ) ; } async setSSL ( input , state ) { let active = sslOptions . find ( s => s . ssl === ! ! state . secure ) ; state . secure = await input . showQuickPick ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , placeholder : 'Use an ssl connection?' , ignoreFocusOut : true , items : sslOptions , activeItem : active || undefined , convert : async ( value ) => value . ssl } ) ; if ( typeof state . secure === 'undefined' ) state . secure = false ; return ( input ) => this . setDatabase ( input , state ) ; } async setDatabase ( input , state ) { let connection = null ; let databases = [ ] ; let connectionError = null ; try { connection = await Database . createConnection ( { label : '' , host : state . host , user : state . user , password : state . password , port : state . port , ssl : state . secure } , 'postgres' ) ; const res = await connection . query ( 'SELECT datname FROM pg_database WHERE datistemplate = false;' ) ; databases = res . rows . map < DatabaseQuickPickItem > ( database => ( { label : database . datname , dbname : database . datname } ) ) ; } catch ( err ) { if ( err . message === `permission denied for database "postgres"` ) { } else { } } finally { if ( connection ) { await connection . end ( ) ; connection = null ; } } if ( databases . length < 1 ) { let connectionOK = false ; do { state . database = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : '[Optional] The database to connect to. Leave empty to enumerate databases on the server' , placeholder : '' , ignoreFocusOut : true , value : ( typeof state . database === 'string' ) ? state . database : '' , validate : async ( value ) => '' } ) ; try { let databaseToTry = state . database && state . database . trim ( ) ? state . database : 'postgres' ; connection = await Database . createConnection ( { label : '' , host : state . host , user : state . user , password : state . password , port : state . port , ssl : state . secure } , databaseToTry ) ; connectionOK = true ; } catch ( err ) { connectionError = err ; vscode . window . showErrorMessage ( err . message ) ; } finally { if ( connection ) { await connection . end ( ) ; connection = null ; } } } while ( ! connectionOK ) ; return ( input ) => this . setDisplayName ( input , state ) ; } if ( connectionError ) { input . redoLastStep ( ) ; } databases . unshift ( { label : 'Show All Databases' } ) ; let active = databases . find ( d => d . dbname && d . dbname === state . database ) ; let selected = await input . showQuickPick ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , placeholder : '' , ignoreFocusOut : true , items : databases , activeItem : active || undefined , convert : async ( value ) => value . dbname } ) ; state . database = selected || '' ; return ( input ) => this . setDisplayName ( input , state ) ; } async setDisplayName ( input , state ) { state . label = await input . showInputBox ( { title : this . TITLE , step : input . CurrentStepNumber , totalSteps : this . TotalSteps , prompt : 'The display name of the database connection' , placeholder : 'ex. My Local DB (optional)' , ignoreFocusOut : true , value : ( typeof state . label === 'string' ) ? state . label : '' , validate : async ( value ) => '' } ) ; } } interface ConnectionState { label : string ; host : string ; user : string ; password : string ; port : number ; database : string ; secure : boolean ; }