connection testing

This commit is contained in:
Jan Prochazka 2022-12-25 12:48:10 +01:00
parent d66fc06403
commit 1eea117062
10 changed files with 103 additions and 16 deletions

View File

@ -17,6 +17,7 @@
"start:api:portal": "yarn workspace dbgate-api start:portal", "start:api:portal": "yarn workspace dbgate-api start:portal",
"start:api:singledb": "yarn workspace dbgate-api start:singledb", "start:api:singledb": "yarn workspace dbgate-api start:singledb",
"start:api:auth": "yarn workspace dbgate-api start:auth", "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:web": "yarn workspace dbgate-web dev",
"start:sqltree": "yarn workspace dbgate-sqltree start", "start:sqltree": "yarn workspace dbgate-sqltree start",
"start:tools": "yarn workspace dbgate-tools start", "start:tools": "yarn workspace dbgate-tools start",

11
packages/api/env/dblogin/.env vendored Normal file
View File

@ -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

View File

@ -60,6 +60,7 @@
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api", "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: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: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: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", "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", "ts": "tsc",

View File

@ -52,6 +52,7 @@ function getPortalCollections() {
server: process.env[`SERVER_${id}`], server: process.env[`SERVER_${id}`],
user: process.env[`USER_${id}`], user: process.env[`USER_${id}`],
password: process.env[`PASSWORD_${id}`], password: process.env[`PASSWORD_${id}`],
passwordMode: process.env[`PASSWORD_MODE_${id}`],
port: process.env[`PORT_${id}`], port: process.env[`PORT_${id}`],
databaseUrl: process.env[`URL_${id}`], databaseUrl: process.env[`URL_${id}`],
useDatabaseUrl: !!process.env[`URL_${id}`], useDatabaseUrl: !!process.env[`URL_${id}`],
@ -204,7 +205,7 @@ module.exports = {
}, },
saveVolatile_meta: true, saveVolatile_meta: true,
async saveVolatile({ conid, user, password }) { async saveVolatile({ conid, user, password, test }) {
const old = await this.getCore({ conid }); const old = await this.getCore({ conid });
const res = { const res = {
...old, ...old,
@ -217,8 +218,20 @@ module.exports = {
res.user = user; res.user = user;
} }
volatileConnections[res._id] = res; if (test) {
return res; 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, save_meta: true,

View File

@ -131,9 +131,9 @@ module.exports = {
}, },
ping_meta: true, ping_meta: true,
async ping({ connections }) { async ping({ conidArray }) {
await Promise.all( await Promise.all(
_.uniq(connections).map(async conid => { _.uniq(conidArray).map(async conid => {
const last = this.lastPinged[conid]; const last = this.lastPinged[conid];
if (last && new Date().getTime() - last < 30 * 1000) { if (last && new Date().getTime() - last < 30 * 1000) {
return Promise.resolve(); return Promise.resolve();

View File

@ -11,15 +11,20 @@
import { onDestroy, onMount } from 'svelte'; import { onDestroy, onMount } from 'svelte';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
import FormStyledButton from '../buttons/FormStyledButton.svelte'; import FormStyledButton from '../buttons/FormStyledButton.svelte';
import Link from '../elements/Link.svelte';
import FormPasswordField from '../forms/FormPasswordField.svelte'; import FormPasswordField from '../forms/FormPasswordField.svelte';
import FormProviderCore from '../forms/FormProviderCore.svelte'; import FormProviderCore from '../forms/FormProviderCore.svelte';
import FormSubmit from '../forms/FormSubmit.svelte'; import FormSubmit from '../forms/FormSubmit.svelte';
import FormTextField from '../forms/FormTextField.svelte'; import FormTextField from '../forms/FormTextField.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { apiCall, setVolatileConnectionRemapping } from '../utility/api'; import { apiCall, setVolatileConnectionRemapping } from '../utility/api';
import { dispatchCacheChange } from '../utility/cache';
import createRef from '../utility/createRef';
import { getConnectionInfo } from '../utility/metadataLoaders'; import { getConnectionInfo } from '../utility/metadataLoaders';
import ErrorMessageModal from './ErrorMessageModal.svelte';
import ModalBase from './ModalBase.svelte'; import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools'; import { closeCurrentModal, showModal } from './modalTools';
export let conid; export let conid;
export let passwordMode; export let passwordMode;
@ -27,6 +32,10 @@
const values = writable({}); const values = writable({});
let connection; let connection;
let isTesting;
let sqlConnectResult;
const testIdRef = createRef(0);
currentModalConid = conid; currentModalConid = conid;
onMount(async () => { onMount(async () => {
@ -44,14 +53,31 @@
currentModalConid = null; currentModalConid = null;
}); });
function handleCancelTest() {
testIdRef.update(x => x + 1); // invalidate current test
isTesting = false;
}
async function handleSubmit(ev) { 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, conid,
user: ev.detail.user, user: ev.detail.user,
password: ev.detail.password, password: ev.detail.password,
test: true,
}); });
setVolatileConnectionRemapping(conid, con._id); if (testIdRef.get() != testid) return;
closeCurrentModal(); isTesting = false;
if (resp.msgtype == 'connected') {
setVolatileConnectionRemapping(conid, resp._id);
dispatchCacheChange(`database-list-changed-${conid}`);
dispatchCacheChange(`server-status-changed`);
closeCurrentModal();
} else {
sqlConnectResult = resp;
}
} }
</script> </script>
@ -74,9 +100,36 @@
focused={passwordMode == 'askPassword'} focused={passwordMode == 'askPassword'}
/> />
{#if isTesting}
<div>
<FontIcon icon="icon loading" /> Testing connection
</div>
{/if}
{#if !isTesting && sqlConnectResult && sqlConnectResult.msgtype == 'error'}
<div class="error-result">
Connect failed: <FontIcon icon="img error" />
{sqlConnectResult.error}
<Link
onClick={() =>
showModal(ErrorMessageModal, {
message: sqlConnectResult.detail,
showAsCode: true,
title: 'Database connection error',
})}
>
Show detail
</Link>
</div>
{/if}
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<FormSubmit value="Connect" on:click={handleSubmit} /> {#if isTesting}
<FormStyledButton value="Cancel" on:click={closeCurrentModal} /> <FormStyledButton value="Stop connecting" on:click={handleCancelTest} />
{:else}
<FormSubmit value="Connect" on:click={handleSubmit} />
{/if}
<FormStyledButton value="Close" on:click={closeCurrentModal} />
</svelte:fragment> </svelte:fragment>
</ModalBase> </ModalBase>
</FormProviderCore> </FormProviderCore>

View File

@ -29,6 +29,10 @@ export function setVolatileConnectionRemapping(existingConnectionId, volatileCon
volatileConnectionMap[existingConnectionId] = volatileConnectionId; volatileConnectionMap[existingConnectionId] = volatileConnectionId;
} }
export function getVolatileRemapping(conid) {
return volatileConnectionMap[conid] || conid;
}
function wantEventSource() { function wantEventSource() {
if (!eventSource) { if (!eventSource) {
eventSource = new EventSource(`${resolveApi()}/stream`); eventSource = new EventSource(`${resolveApi()}/stream`);
@ -61,7 +65,11 @@ function processApiResponse(route, args, resp) {
} }
function transformApiArgs(args) { 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) { export async function apiCall(route: string, args: {} = undefined) {

View File

@ -105,7 +105,7 @@ export async function unsubscribeCacheChange(reloadTrigger, cacheKey, reloadHand
} }
} }
function dispatchCacheChange(reloadTrigger) { export function dispatchCacheChange(reloadTrigger) {
// console.log('CHANGE', reloadTrigger); // console.log('CHANGE', reloadTrigger);
cacheClean(reloadTrigger); cacheClean(reloadTrigger);

View File

@ -10,7 +10,7 @@ import { getConnectionList } from './metadataLoaders';
// }; // };
const doServerPing = value => { const doServerPing = value => {
apiCall('server-connections/ping', { connections: value }); apiCall('server-connections/ping', { conidArray: value });
}; };
const doDatabasePing = value => { const doDatabasePing = value => {

View File

@ -21,7 +21,7 @@
import { useConnectionColorFactory } from '../utility/useConnectionColor'; import { useConnectionColorFactory } from '../utility/useConnectionColor';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.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 LargeButton from '../buttons/LargeButton.svelte';
import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons'; import { plusExpandIcon, chevronExpandIcon } from '../icons/expandIcons';
import { safeJsonParse } from 'dbgate-tools'; import { safeJsonParse } from 'dbgate-tools';
@ -33,7 +33,7 @@
$: connectionsWithStatus = $: connectionsWithStatus =
$connections && $serverStatus $connections && $serverStatus
? $connections.map(conn => ({ ...conn, status: $serverStatus[conn._id] })) ? $connections.map(conn => ({ ...conn, status: $serverStatus[getVolatileRemapping(conn._id)] }))
: $connections; : $connections;
$: connectionsWithStatusFiltered = connectionsWithStatus?.filter( $: connectionsWithStatusFiltered = connectionsWithStatus?.filter(