diff --git a/package.json b/package.json index d3746f15..f6e4d37e 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ "start:api:portal": "yarn workspace dbgate-api start:portal", "start:api:singledb": "yarn workspace dbgate-api start:singledb", "start:api:auth": "yarn workspace dbgate-api start:auth", + "start:api:dblogin": "yarn workspace dbgate-api start:dblogin", "start:web": "yarn workspace dbgate-web dev", "start:sqltree": "yarn workspace dbgate-sqltree start", "start:tools": "yarn workspace dbgate-tools start", diff --git a/packages/api/env/dblogin/.env b/packages/api/env/dblogin/.env new file mode 100644 index 00000000..db6b9bb0 --- /dev/null +++ b/packages/api/env/dblogin/.env @@ -0,0 +1,11 @@ +DEVMODE=1 + +CONNECTIONS=mysql + +LABEL_mysql=MySql localhost +SERVER_mysql=localhost +USER_mysql=root +PORT_mysql=3306 +# PASSWORD_mysql=Pwd2020Db +ENGINE_mysql=mysql@dbgate-plugin-mysql +PASSWORD_MODE_mysql=askPassword diff --git a/packages/api/package.json b/packages/api/package.json index 27e7ba1c..64caf1d1 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -60,6 +60,7 @@ "start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api", "start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api", "start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api", + "start:dblogin": "env-cmd -f env/dblogin/.env node src/index.js --listen-api", "start:filedb": "env-cmd node src/index.js /home/jena/test/chinook/Chinook.db --listen-api", "start:singleconn": "env-cmd node src/index.js --server localhost --user root --port 3307 --engine mysql@dbgate-plugin-mysql --password test --listen-api", "ts": "tsc", diff --git a/packages/api/src/controllers/connections.js b/packages/api/src/controllers/connections.js index bf967bd0..8757deec 100644 --- a/packages/api/src/controllers/connections.js +++ b/packages/api/src/controllers/connections.js @@ -52,6 +52,7 @@ function getPortalCollections() { server: process.env[`SERVER_${id}`], user: process.env[`USER_${id}`], password: process.env[`PASSWORD_${id}`], + passwordMode: process.env[`PASSWORD_MODE_${id}`], port: process.env[`PORT_${id}`], databaseUrl: process.env[`URL_${id}`], useDatabaseUrl: !!process.env[`URL_${id}`], @@ -204,7 +205,7 @@ module.exports = { }, saveVolatile_meta: true, - async saveVolatile({ conid, user, password }) { + async saveVolatile({ conid, user, password, test }) { const old = await this.getCore({ conid }); const res = { ...old, @@ -217,8 +218,20 @@ module.exports = { res.user = user; } - volatileConnections[res._id] = res; - return res; + if (test) { + const testRes = await this.test(res); + if (testRes.msgtype == 'connected') { + volatileConnections[res._id] = res; + return { + ...res, + msgtype: 'connected', + }; + } + return testRes; + } else { + volatileConnections[res._id] = res; + return res; + } }, save_meta: true, diff --git a/packages/api/src/controllers/serverConnections.js b/packages/api/src/controllers/serverConnections.js index 2ed0189a..831dda44 100644 --- a/packages/api/src/controllers/serverConnections.js +++ b/packages/api/src/controllers/serverConnections.js @@ -131,9 +131,9 @@ module.exports = { }, ping_meta: true, - async ping({ connections }) { + async ping({ conidArray }) { await Promise.all( - _.uniq(connections).map(async conid => { + _.uniq(conidArray).map(async conid => { const last = this.lastPinged[conid]; if (last && new Date().getTime() - last < 30 * 1000) { return Promise.resolve(); diff --git a/packages/web/src/modals/DatabaseLoginModal.svelte b/packages/web/src/modals/DatabaseLoginModal.svelte index e5c14ddc..d4991aea 100644 --- a/packages/web/src/modals/DatabaseLoginModal.svelte +++ b/packages/web/src/modals/DatabaseLoginModal.svelte @@ -11,15 +11,20 @@ import { onDestroy, onMount } from 'svelte'; import { writable } from 'svelte/store'; import FormStyledButton from '../buttons/FormStyledButton.svelte'; + import Link from '../elements/Link.svelte'; import FormPasswordField from '../forms/FormPasswordField.svelte'; import FormProviderCore from '../forms/FormProviderCore.svelte'; import FormSubmit from '../forms/FormSubmit.svelte'; import FormTextField from '../forms/FormTextField.svelte'; + import FontIcon from '../icons/FontIcon.svelte'; import { apiCall, setVolatileConnectionRemapping } from '../utility/api'; + import { dispatchCacheChange } from '../utility/cache'; + import createRef from '../utility/createRef'; import { getConnectionInfo } from '../utility/metadataLoaders'; + import ErrorMessageModal from './ErrorMessageModal.svelte'; import ModalBase from './ModalBase.svelte'; - import { closeCurrentModal } from './modalTools'; + import { closeCurrentModal, showModal } from './modalTools'; export let conid; export let passwordMode; @@ -27,6 +32,10 @@ const values = writable({}); let connection; + let isTesting; + let sqlConnectResult; + const testIdRef = createRef(0); + currentModalConid = conid; onMount(async () => { @@ -44,14 +53,31 @@ currentModalConid = null; }); + function handleCancelTest() { + testIdRef.update(x => x + 1); // invalidate current test + isTesting = false; + } + async function handleSubmit(ev) { - const con = await apiCall('connections/save-volatile', { + isTesting = true; + testIdRef.update(x => x + 1); + const testid = testIdRef.get(); + const resp = await apiCall('connections/save-volatile', { conid, user: ev.detail.user, password: ev.detail.password, + test: true, }); - setVolatileConnectionRemapping(conid, con._id); - closeCurrentModal(); + if (testIdRef.get() != testid) return; + isTesting = false; + if (resp.msgtype == 'connected') { + setVolatileConnectionRemapping(conid, resp._id); + dispatchCacheChange(`database-list-changed-${conid}`); + dispatchCacheChange(`server-status-changed`); + closeCurrentModal(); + } else { + sqlConnectResult = resp; + } } @@ -74,9 +100,36 @@ focused={passwordMode == 'askPassword'} /> + {#if isTesting} +
+ Testing connection +
+ {/if} + + {#if !isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error'} +
+ Connect failed: + {sqlConnectResult.error} + + showModal(ErrorMessageModal, { + message: sqlConnectResult.detail, + showAsCode: true, + title: 'Database connection error', + })} + > + Show detail + +
+ {/if} + - - + {#if isTesting} + + {:else} + + {/if} + diff --git a/packages/web/src/utility/api.ts b/packages/web/src/utility/api.ts index aa331267..45f3165f 100644 --- a/packages/web/src/utility/api.ts +++ b/packages/web/src/utility/api.ts @@ -29,6 +29,10 @@ export function setVolatileConnectionRemapping(existingConnectionId, volatileCon volatileConnectionMap[existingConnectionId] = volatileConnectionId; } +export function getVolatileRemapping(conid) { + return volatileConnectionMap[conid] || conid; +} + function wantEventSource() { if (!eventSource) { eventSource = new EventSource(`${resolveApi()}/stream`); @@ -61,7 +65,11 @@ function processApiResponse(route, args, resp) { } function transformApiArgs(args) { - return _.mapValues(args, (v, k) => (k == 'conid' && v && volatileConnectionMap[v] ? volatileConnectionMap[v] : v)); + return _.mapValues(args, (v, k) => { + if (k == 'conid' && v && volatileConnectionMap[v]) return volatileConnectionMap[v]; + if (k == 'conidArray' && _.isArray(v)) return v.map(x => volatileConnectionMap[x] || x); + return v; + }); } export async function apiCall(route: string, args: {} = undefined) { diff --git a/packages/web/src/utility/cache.ts b/packages/web/src/utility/cache.ts index d6dcc670..4de67e25 100644 --- a/packages/web/src/utility/cache.ts +++ b/packages/web/src/utility/cache.ts @@ -105,7 +105,7 @@ export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHand } } -function dispatchCacheChange(reloadTrigger) { +export function dispatchCacheChange(reloadTrigger) { // console.log('CHANGE', reloadTrigger); cacheClean(reloadTrigger); diff --git a/packages/web/src/utility/connectionsPinger.js b/packages/web/src/utility/connectionsPinger.js index c37b5c8d..426a6a9f 100644 --- a/packages/web/src/utility/connectionsPinger.js +++ b/packages/web/src/utility/connectionsPinger.js @@ -10,7 +10,7 @@ import { getConnectionList } from './metadataLoaders'; // }; const doServerPing = value => { - apiCall('server-connections/ping', { connections: value }); + apiCall('server-connections/ping', { conidArray: value }); }; const doDatabasePing = value => { diff --git a/packages/web/src/widgets/ConnectionList.svelte b/packages/web/src/widgets/ConnectionList.svelte index f475ba7f..77b85ee8 100644 --- a/packages/web/src/widgets/ConnectionList.svelte +++ b/packages/web/src/widgets/ConnectionList.svelte @@ -21,7 +21,7 @@ import { useConnectionColorFactory } from '../utility/useConnectionColor'; import FontIcon from '../icons/FontIcon.svelte'; import CloseSearchButton from '../buttons/CloseSearchButton.svelte'; - import { apiCall } from '../utility/api'; + import { apiCall, getVolatileRemapping } from '../utility/api'; import LargeButton from '../buttons/LargeButton.svelte'; import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons'; import { safeJsonParse } from 'dbgate-tools'; @@ -33,7 +33,7 @@ $: connectionsWithStatus = $connections && $serverStatus - ? $connections.map(conn => ({ ...conn, status: $serverStatus[conn._id] })) + ? $connections.map(conn => ({ ...conn, status: $serverStatus[getVolatileRemapping(conn._id)] })) : $connections; $: connectionsWithStatusFiltered = connectionsWithStatus?.filter(