save new connection on cloud

This commit is contained in:
SPRINX0\prochazka
2025-05-26 14:41:41 +02:00
parent b3497c7306
commit 88f937f73e
10 changed files with 173 additions and 36 deletions

View File

@@ -10,7 +10,7 @@ const {
} = require('../utility/cloudIntf'); } = require('../utility/cloudIntf');
const connections = require('./connections'); const connections = require('./connections');
const socket = require('../utility/socket'); const socket = require('../utility/socket');
const { recryptConnection, getInternalEncryptor } = require('../utility/crypting'); const { recryptConnection, getInternalEncryptor, encryptConnection } = require('../utility/crypting');
const { getConnectionLabel, getLogger, extractErrorLogData } = require('dbgate-tools'); const { getConnectionLabel, getLogger, extractErrorLogData } = require('dbgate-tools');
const logger = getLogger('cloud'); const logger = getLogger('cloud');
const _ = require('lodash'); const _ = require('lodash');
@@ -113,7 +113,7 @@ module.exports = {
moveConnectionCloud_meta: true, moveConnectionCloud_meta: true,
async moveConnectionCloud({ conid, folid }) { async moveConnectionCloud({ conid, folid }) {
const conn = await connections.getCore({ conid }); const conn = await connections.getCore({ conid });
const folderEncryptor = getCloudFolderEncryptor(folid); const folderEncryptor = await getCloudFolderEncryptor(folid);
const recryptedConn = recryptConnection(conn, getInternalEncryptor(), folderEncryptor); const recryptedConn = recryptConnection(conn, getInternalEncryptor(), folderEncryptor);
const connToSend = _.omit(recryptedConn, ['_id']); const connToSend = _.omit(recryptedConn, ['_id']);
const resp = await putCloudContent( const resp = await putCloudContent(
@@ -125,4 +125,24 @@ module.exports = {
); );
return resp; return resp;
}, },
saveConnection_meta: true,
async saveConnection({ folid, connection }) {
const folderEncryptor = await getCloudFolderEncryptor(folid);
const recryptedConn = encryptConnection(connection, folderEncryptor);
const resp = await putCloudContent(
folid,
undefined,
JSON.stringify(recryptedConn),
getConnectionLabel(recryptedConn),
'connection'
);
const { cntid } = resp;
socket.emitChanged('cloud-content-changed');
return {
...recryptedConn,
_id: `cloud://${folid}/${cntid}`,
};
},
}; };

View File

@@ -81,11 +81,11 @@ function decryptPasswordString(password) {
return password; return password;
} }
function encryptObjectPasswordField(obj, field) { function encryptObjectPasswordField(obj, field, encryptor = null) {
if (obj && obj[field] && !obj[field].startsWith('crypt:')) { if (obj && obj[field] && !obj[field].startsWith('crypt:')) {
return { return {
...obj, ...obj,
[field]: 'crypt:' + getInternalEncryptor().encrypt(obj[field]), [field]: 'crypt:' + (encryptor || getInternalEncryptor()).encrypt(obj[field]),
}; };
} }
return obj; return obj;
@@ -101,11 +101,11 @@ function decryptObjectPasswordField(obj, field) {
return obj; return obj;
} }
function encryptConnection(connection) { function encryptConnection(connection, encryptor = null) {
if (connection.passwordMode != 'saveRaw') { if (connection.passwordMode != 'saveRaw') {
connection = encryptObjectPasswordField(connection, 'password'); connection = encryptObjectPasswordField(connection, 'password', encryptor);
connection = encryptObjectPasswordField(connection, 'sshPassword'); connection = encryptObjectPasswordField(connection, 'sshPassword', encryptor);
connection = encryptObjectPasswordField(connection, 'sshKeyfilePassword'); connection = encryptObjectPasswordField(connection, 'sshKeyfilePassword', encryptor);
} }
return connection; return connection;
} }

View File

@@ -48,7 +48,7 @@
<AppObjectCore <AppObjectCore
{...$$restProps} {...$$restProps}
{data} {data}
icon={'img connection'} icon={'img cloud-connection'}
title={data.name} title={data.name}
menu={createMenu} menu={createMenu}
on:click={handleOpenContent} on:click={handleOpenContent}

View File

@@ -337,12 +337,14 @@
$cloudSigninTokenHolder && $cloudSigninTokenHolder &&
passProps?.cloudContentList?.length > 0 && { passProps?.cloudContentList?.length > 0 && {
text: _t('connection.moveToCloudFolder', { defaultMessage: 'Move to cloud folder' }), text: _t('connection.moveToCloudFolder', { defaultMessage: 'Move to cloud folder' }),
submenu: passProps?.cloudContentList?.map(fld => ({ submenu: passProps?.cloudContentList
text: fld.name, ?.filter(x => x.role == 'write' || x.role == 'admin')
onClick: () => { ?.map(fld => ({
apiCall('cloud/move-connection-cloud', { conid: data._id, folid: fld.folid }); text: fld.name,
}, onClick: () => {
})), apiCall('cloud/move-connection-cloud', { conid: data._id, folid: fld.folid });
},
})),
}, },
], ],
{ divider: true }, { divider: true },

View File

@@ -124,6 +124,27 @@ registerCommand({
}, },
}); });
registerCommand({
id: 'new.connectionOnCloud',
toolbar: true,
icon: 'img cloud-connection',
toolbarName: 'Add connection on cloud',
category: 'New',
toolbarOrder: 1,
name: 'Connection on Cloud',
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
onClick: () => {
openNewTab({
title: 'New Connection on Cloud',
icon: 'img cloud-connection',
tabComponent: 'ConnectionTab',
props: {
saveOnCloud: true,
},
});
},
});
registerCommand({ registerCommand({
id: 'new.connection.folder', id: 'new.connection.folder',
toolbar: true, toolbar: true,

View File

@@ -0,0 +1,19 @@
<script lang="ts">
import { useCloudContentList } from '../utility/metadataLoaders';
import FormSelectField from './FormSelectField.svelte';
export let name;
export let requiredRoleVariants = ['read', 'write', 'admin'];
const cloudContentList = useCloudContentList();
$: folderOptions = ($cloudContentList || [])
.filter(folder => requiredRoleVariants.find(role => folder.role == role))
.map(folder => ({
value: folder.folid,
label: folder.name,
}));
</script>
<FormSelectField {...$$props} options={folderOptions} />

View File

@@ -270,6 +270,7 @@
'img role': 'mdi mdi-account-group color-icon-blue', 'img role': 'mdi mdi-account-group color-icon-blue',
'img admin': 'mdi mdi-security color-icon-blue', 'img admin': 'mdi mdi-security color-icon-blue',
'img auth': 'mdi mdi-account-key color-icon-blue', 'img auth': 'mdi mdi-account-key color-icon-blue',
'img cloud-connection': 'mdi mdi-cloud-lock color-icon-blue',
'img add': 'mdi mdi-plus-circle color-icon-green', 'img add': 'mdi mdi-plus-circle color-icon-green',
'img minus': 'mdi mdi-minus-circle color-icon-red', 'img minus': 'mdi mdi-minus-circle color-icon-red',

View File

@@ -0,0 +1,40 @@
<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import FormCloudFolderSelect from '../forms/FormCloudFolderSelect.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import FormSubmit from '../forms/FormSubmit.svelte';
import { useCloudContentList } from '../utility/metadataLoaders';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
export let message = '';
export let onConfirm;
export let requiredRoleVariants;
const cloudContentList = useCloudContentList();
</script>
{#if $cloudContentList}
<FormProvider initialValues={{ cloudFolder: $cloudContentList?.find(x => x.isPrivate)?.folid }}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Choose cloud folder</svelte:fragment>
<div>{message}</div>
<FormCloudFolderSelect label="Cloud folder" name="cloudFolder" isNative {requiredRoleVariants} />
<svelte:fragment slot="footer">
<FormSubmit
value="OK"
on:click={e => {
closeCurrentModal();
console.log('onConfirm', e.detail);
onConfirm(e.detail.cloudFolder);
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
{/if}

View File

@@ -36,6 +36,7 @@
import ConnectionAdvancedDriverFields from '../settings/ConnectionAdvancedDriverFields.svelte'; import ConnectionAdvancedDriverFields from '../settings/ConnectionAdvancedDriverFields.svelte';
import DatabaseLoginModal from '../modals/DatabaseLoginModal.svelte'; import DatabaseLoginModal from '../modals/DatabaseLoginModal.svelte';
import { _t } from '../translations'; import { _t } from '../translations';
import ChooseCloudFolderModal from '../modals/ChooseCloudFolderModal.svelte';
export let connection; export let connection;
export let tabid; export let tabid;
@@ -44,6 +45,7 @@
export let inlineTabs = false; export let inlineTabs = false;
export let onlyTestButton; export let onlyTestButton;
export let saveOnCloud = false;
let isTesting; let isTesting;
let sqlConnectResult; let sqlConnectResult;
@@ -157,26 +159,53 @@
$: currentConnection = getCurrentConnectionCore($values, driver); $: currentConnection = getCurrentConnectionCore($values, driver);
async function handleSave() { async function handleSave() {
let connection = getCurrentConnection(); if (saveOnCloud) {
connection = { showModal(ChooseCloudFolderModal, {
...connection, requiredRoleVariants: ['write', 'admin'],
unsaved: false, message: 'Choose cloud folder to saved connection',
}; onConfirm: async folid => {
const saved = await apiCall('connections/save', connection); let connection = getCurrentConnection();
$values = { const saved = await apiCall('cloud/save-connection', { folid, connection });
...$values, if (saved?._id) {
_id: saved._id, $values = {
unsaved: false, ...$values,
}; _id: saved._id,
changeTab(tabid, tab => ({ unsaved: false,
...tab, };
title: getConnectionLabel(saved), changeTab(tabid, tab => ({
props: { ...tab,
...tab.props, title: getConnectionLabel(saved),
conid: saved._id, props: {
}, ...tab.props,
})); conid: saved._id,
showSnackbarSuccess('Connection saved'); },
}));
showSnackbarSuccess('Connection saved');
}
},
});
} else {
let connection = getCurrentConnection();
connection = {
...connection,
unsaved: false,
};
const saved = await apiCall('connections/save', connection);
$values = {
...$values,
_id: saved._id,
unsaved: false,
};
changeTab(tabid, tab => ({
...tab,
title: getConnectionLabel(saved),
props: {
...tab.props,
conid: saved._id,
},
}));
showSnackbarSuccess('Connection saved');
}
} }
async function handleConnect() { async function handleConnect() {
@@ -287,7 +316,9 @@
{:else if isConnected} {:else if isConnected}
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid="ConnectionTab_buttonDisconnect" /> <FormButton value="Disconnect" on:click={handleDisconnect} data-testid="ConnectionTab_buttonDisconnect" />
{:else} {:else}
<FormButton value="Connect" on:click={handleConnect} data-testid="ConnectionTab_buttonConnect" /> {#if $values._id || !saveOnCloud}
<FormButton value="Connect" on:click={handleConnect} data-testid="ConnectionTab_buttonConnect" />
{/if}
{#if isTesting} {#if isTesting}
<FormButton value="Cancel test" on:click={handleCancelTest} /> <FormButton value="Cancel test" on:click={handleCancelTest} />
{:else} {:else}

View File

@@ -111,6 +111,9 @@
}); });
}, },
}, },
{
command: 'new.connectionOnCloud',
},
]; ];
} }