diff --git a/packages/api/src/controllers/cloud.js b/packages/api/src/controllers/cloud.js index 7541ef023..9561ca71e 100644 --- a/packages/api/src/controllers/cloud.js +++ b/packages/api/src/controllers/cloud.js @@ -4,8 +4,13 @@ const { refreshPublicFiles, callCloudApiGet, callCloudApiPost, + getCloudFolderEncryptor, } = require('../utility/cloudIntf'); +const connections = require('./connections'); const socket = require('../utility/socket'); +const { decryptConnection, recryptConnection, getInternalEncryptor } = require('../utility/crypting'); +const { getConnectionLabel, getLogger, extractErrorLogData } = require('dbgate-tools'); +const logger = getLogger('cloud'); module.exports = { publicFiles_meta: true, @@ -30,9 +35,14 @@ module.exports = { contentList_meta: true, async contentList() { - const resp = callCloudApiGet('content-list'); - console.log('contentList', resp); - return resp; + try { + const resp = await callCloudApiGet('content-list'); + return resp; + } catch (err) { + logger.error(extractErrorLogData(err), 'Error getting cloud content list'); + + return []; + } }, getContent_meta: true, @@ -79,4 +89,21 @@ module.exports = { status: 'ok', }; }, + + moveConnectionCloud_meta: true, + async moveConnectionCloud({ conid, folid }) { + const conn = await connections.getCore({ conid }); + const folderEncryptor = getCloudFolderEncryptor(folid); + const recryptedConn = recryptConnection(conn, getInternalEncryptor(), folderEncryptor); + await this.putContent({ + folid, + cntid: conid, + content: JSON.stringify(recryptedConn), + name: getConnectionLabel(conn), + type: 'connection', + }); + return { + status: 'ok', + }; + }, }; diff --git a/packages/api/src/utility/cloudIntf.js b/packages/api/src/utility/cloudIntf.js index 4398c20c5..a1ad374a6 100644 --- a/packages/api/src/utility/cloudIntf.js +++ b/packages/api/src/utility/cloudIntf.js @@ -10,6 +10,7 @@ const connections = require('../controllers/connections'); const { isProApp } = require('./checkLicense'); const socket = require('./socket'); const config = require('../controllers/config'); +const simpleEncryptor = require('simple-encryptor'); const logger = getLogger('cloudIntf'); @@ -239,6 +240,14 @@ async function callCloudApiPost(endpoint, body) { return resp.data; } +async function getCloudFolderEncryptor(folid) { + const { encryptionKey } = await callCloudApiGet(`folder-key/${folid}`); + if (!encryptionKey) { + throw new Error('No encryption key'); + } + return simpleEncryptor.createEncryptor(encryptionKey); +} + module.exports = { createDbGateIdentitySession, startCloudTokenChecking, @@ -248,4 +257,5 @@ module.exports = { refreshPublicFiles, callCloudApiGet, callCloudApiPost, + getCloudFolderEncryptor, }; diff --git a/packages/web/src/appobj/CloudContentAppObject.svelte b/packages/web/src/appobj/CloudContentAppObject.svelte new file mode 100644 index 000000000..9a9b75a62 --- /dev/null +++ b/packages/web/src/appobj/CloudContentAppObject.svelte @@ -0,0 +1,30 @@ + + + + + + + diff --git a/packages/web/src/appobj/ConnectionAppObject.svelte b/packages/web/src/appobj/ConnectionAppObject.svelte index 43a9a4663..d6fedd9ab 100644 --- a/packages/web/src/appobj/ConnectionAppObject.svelte +++ b/packages/web/src/appobj/ConnectionAppObject.svelte @@ -108,6 +108,7 @@ import _ from 'lodash'; import AppObjectCore from './AppObjectCore.svelte'; import { + cloudSigninToken, currentDatabase, DEFAULT_CONNECTION_SEARCH_SETTINGS, expandedConnections, @@ -332,6 +333,17 @@ text: _t('connection.duplicate', { defaultMessage: 'Duplicate' }), onClick: handleDuplicate, }, + !$openedConnections.includes(data._id) && + $cloudSigninToken && + passProps?.cloudContentList?.length > 0 && { + text: _t('connection.moveToCloudFolder', { defaultMessage: 'Move to cloud folder' }), + submenu: passProps?.cloudContentList?.map(fld => ({ + text: fld.name, + onClick: () => { + apiCall('cloud/move-connection-cloud', { conid: data._id, folid: fld.folid }); + }, + })), + }, ], { divider: true }, !data.singleDatabase && [ diff --git a/packages/web/src/appobj/CloudFileAppObject.svelte b/packages/web/src/appobj/PublicCloudFileAppObject.svelte similarity index 100% rename from packages/web/src/appobj/CloudFileAppObject.svelte rename to packages/web/src/appobj/PublicCloudFileAppObject.svelte diff --git a/packages/web/src/stores.ts b/packages/web/src/stores.ts index c0857d36e..3e351ee19 100644 --- a/packages/web/src/stores.ts +++ b/packages/web/src/stores.ts @@ -183,7 +183,6 @@ export const focusedConnectionOrDatabase = writable<{ conid: string; database?: export const focusedTreeDbKey = writable<{ key: string; root: string; type: string; text: string }>(null); export const cloudSigninToken = writableSettingsValue(null, 'cloudSigninToken'); -export const cloudEncryptKeysByFolder = writableSettingsValue({}, 'cloudEncryptKeysByFolder'); // export const cloudSigninToken = getElectron() // ? writableSettingsValue(null, 'cloudSigninToken') diff --git a/packages/web/src/widgets/CloudItemsWidget.svelte b/packages/web/src/widgets/CloudItemsWidget.svelte index 439a0376c..4e7ef3b36 100644 --- a/packages/web/src/widgets/CloudItemsWidget.svelte +++ b/packages/web/src/widgets/CloudItemsWidget.svelte @@ -5,7 +5,8 @@ import WidgetColumnBarItem from './WidgetColumnBarItem.svelte'; import AppObjectList from '../appobj/AppObjectList.svelte'; - import * as cloudFileAppObject from '../appobj/CloudFileAppObject.svelte'; + import * as publicCloudFileAppObject from '../appobj/PublicCloudFileAppObject.svelte'; + import * as cloudContentAppObject from '../appobj/CloudContentAppObject.svelte'; import { useCloudContentList, usePublicCloudFiles } from '../utility/metadataLoaders'; import { _t } from '../translations'; @@ -26,7 +27,7 @@ $: cloudContentList = useCloudContentList(); $: emptyCloudContent = ($cloudContentList || []).filter(x => !x.items?.length).map(x => x.folid); - $: cloudContentFlat = ($cloudContentList || []).flatMap(fld => fld.items ?? []).map(x => x.folid); + $: cloudContentFlat = ($cloudContentList || []).flatMap(fld => fld.items ?? []); $: contentGroupTitleMap = _.fromPairs(($cloudContentList || []).map(x => [x.folid, x.name])); async function handleRefreshPublic() { @@ -61,7 +62,7 @@ data.folid} mapGroupTitle={folid => contentGroupTitleMap[folid]} @@ -86,7 +87,7 @@ data.folder || undefined} filter={publicFilter} /> diff --git a/packages/web/src/widgets/ConnectionList.svelte b/packages/web/src/widgets/ConnectionList.svelte index 463e46e23..0e9227335 100644 --- a/packages/web/src/widgets/ConnectionList.svelte +++ b/packages/web/src/widgets/ConnectionList.svelte @@ -351,6 +351,7 @@ isExpandable={data => $openedConnections.includes(data._id) && !data.singleDatabase} {filter} passProps={{ + ...passProps, connectionColorFactory: $connectionColorFactory, showPinnedInsteadOfUnpin: true, searchSettings: $connectionAppObjectSearchSettings, diff --git a/packages/web/src/widgets/DatabaseWidget.svelte b/packages/web/src/widgets/DatabaseWidget.svelte index 7e525c090..cce04923d 100644 --- a/packages/web/src/widgets/DatabaseWidget.svelte +++ b/packages/web/src/widgets/DatabaseWidget.svelte @@ -1,7 +1,7 @@ @@ -45,7 +46,12 @@ height="35%" storageName="connectionsWidget" > - domSqlObjectList.focus() }} /> + domSqlObjectList.focus(), + cloudContentList: $cloudContentList, + }} + /> {/if}