From 494b33bd7a3f6d45d63006fb487c6e185250f9aa Mon Sep 17 00:00:00 2001 From: Jan Prochazka Date: Fri, 26 Sep 2025 12:44:08 +0200 Subject: [PATCH] SYNC: Merge pull request #12 from dbgate/feature/team-files --- packages/api/src/auth/authCommon.js | 6 + packages/api/src/controllers/apps.js | 2 +- packages/api/src/controllers/sessions.js | 21 +- packages/api/src/main.js | 3 + packages/api/src/storageModel.js | 385 +++++++++++++++--- packages/api/src/utility/hasPermission.js | 4 +- packages/tools/src/testPermission.ts | 22 +- .../web/src/appobj/SavedFileAppObject.svelte | 62 ++- .../web/src/elements/VerticalSplitter.svelte | 47 ++- packages/web/src/icons/FontIcon.svelte | 2 + packages/web/src/modals/SaveFileModal.svelte | 26 +- packages/web/src/tabs/QueryTab.svelte | 3 +- packages/web/src/utility/metadataLoaders.ts | 13 + packages/web/src/utility/saveTabFile.ts | 7 +- .../web/src/widgets/SavedFilesList.svelte | 11 +- 15 files changed, 510 insertions(+), 104 deletions(-) diff --git a/packages/api/src/auth/authCommon.js b/packages/api/src/auth/authCommon.js index 824b9baa8..9863dfefe 100644 --- a/packages/api/src/auth/authCommon.js +++ b/packages/api/src/auth/authCommon.js @@ -10,7 +10,13 @@ function getTokenSecret() { return tokenSecret; } +function getStaticTokenSecret() { + // TODO static not fixed + return '14813c43-a91b-4ad1-9dcd-a81bd7dbb05f'; +} + module.exports = { getTokenLifetime, getTokenSecret, + getStaticTokenSecret, }; diff --git a/packages/api/src/controllers/apps.js b/packages/api/src/controllers/apps.js index ac2f3aafb..9425013e4 100644 --- a/packages/api/src/controllers/apps.js +++ b/packages/api/src/controllers/apps.js @@ -21,7 +21,7 @@ module.exports = { const filePermissions = await loadFilePermissionsFromRequest(req); for (const file of await fs.readdir(dir)) { - if (!hasPermission(`all-files`, loadedPermissions)) { + if (!hasPermission(`all-disk-files`, loadedPermissions)) { const role = getFilePermissionRole('apps', file, filePermissions); if (role == 'deny') continue; } diff --git a/packages/api/src/controllers/sessions.js b/packages/api/src/controllers/sessions.js index 5d5f9bc81..14c8a7bed 100644 --- a/packages/api/src/controllers/sessions.js +++ b/packages/api/src/controllers/sessions.js @@ -8,11 +8,13 @@ const path = require('path'); const { handleProcessCommunication } = require('../utility/processComm'); const processArgs = require('../utility/processArgs'); const { appdir } = require('../utility/directories'); -const { getLogger, extractErrorLogData } = require('dbgate-tools'); +const { getLogger, extractErrorLogData, removeSqlFrontMatter } = require('dbgate-tools'); const pipeForkLogs = require('../utility/pipeForkLogs'); const config = require('./config'); const { sendToAuditLog } = require('../utility/auditlog'); const { testStandardPermission, testDatabaseRolePermission } = require('../utility/hasPermission'); +const { getStaticTokenSecret } = require('../auth/authCommon'); +const jwt = require('jsonwebtoken'); const logger = getLogger('sessions'); @@ -95,7 +97,7 @@ module.exports = { socket.emit(`session-initialize-file-${jslid}`); }, - handle_ping() { }, + handle_ping() {}, create_meta: true, async create({ conid, database }) { @@ -149,12 +151,23 @@ module.exports = { executeQuery_meta: true, async executeQuery({ sesid, sql, autoCommit, autoDetectCharts, limitRows, frontMatter }, req) { - await testStandardPermission('dbops/query', req); + let useTokenIsOk = false; + if (frontMatter?.useToken) { + const decoded = jwt.verify(frontMatter.useToken, getStaticTokenSecret()); + if (decoded?.['contentHash'] == crypto.createHash('md5').update(removeSqlFrontMatter(sql)).digest('hex')) { + useTokenIsOk = true; + } + } + if (!useTokenIsOk) { + await testStandardPermission('dbops/query', req); + } const session = this.opened.find(x => x.sesid == sesid); if (!session) { throw new Error('Invalid session'); } - await testDatabaseRolePermission(session.conid, session.database, 'run_script', req); + if (!useTokenIsOk) { + await testDatabaseRolePermission(session.conid, session.database, 'run_script', req); + } sendToAuditLog(req, { category: 'dbop', diff --git a/packages/api/src/main.js b/packages/api/src/main.js index d59c8b3df..314c5c899 100644 --- a/packages/api/src/main.js +++ b/packages/api/src/main.js @@ -29,6 +29,8 @@ const files = require('./controllers/files'); const scheduler = require('./controllers/scheduler'); const queryHistory = require('./controllers/queryHistory'); const cloud = require('./controllers/cloud'); +const teamFiles = require('./controllers/teamFiles'); + const onFinished = require('on-finished'); const processArgs = require('./utility/processArgs'); @@ -264,6 +266,7 @@ function useAllControllers(app, electron) { useController(app, electron, '/apps', apps); useController(app, electron, '/auth', auth); useController(app, electron, '/cloud', cloud); + useController(app, electron, '/team-files', teamFiles); } function setElectronSender(electronSender) { diff --git a/packages/api/src/storageModel.js b/packages/api/src/storageModel.js index 3c8ee800f..54a83a188 100644 --- a/packages/api/src/storageModel.js +++ b/packages/api/src/storageModel.js @@ -784,49 +784,6 @@ module.exports = { } ] }, - { - "pureName": "roles", - "columns": [ - { - "pureName": "roles", - "columnName": "id", - "dataType": "int", - "autoIncrement": true, - "notNull": true - }, - { - "pureName": "roles", - "columnName": "name", - "dataType": "varchar(250)", - "notNull": false - } - ], - "foreignKeys": [], - "primaryKey": { - "pureName": "roles", - "constraintType": "primaryKey", - "constraintName": "PK_roles", - "columns": [ - { - "columnName": "id" - } - ] - }, - "preloadedRows": [ - { - "id": -1, - "name": "anonymous-user" - }, - { - "id": -2, - "name": "logged-user" - }, - { - "id": -3, - "name": "superadmin" - } - ] - }, { "pureName": "role_connections", "columns": [ @@ -1243,6 +1200,129 @@ module.exports = { ] } }, + { + "pureName": "role_team_files", + "columns": [ + { + "pureName": "role_team_files", + "columnName": "id", + "dataType": "int", + "autoIncrement": true, + "notNull": true + }, + { + "pureName": "role_team_files", + "columnName": "role_id", + "dataType": "int", + "notNull": true + }, + { + "pureName": "role_team_files", + "columnName": "team_file_id", + "dataType": "int", + "notNull": true + }, + { + "pureName": "role_team_files", + "columnName": "allow_read", + "dataType": "int", + "notNull": false + }, + { + "pureName": "role_team_files", + "columnName": "allow_write", + "dataType": "int", + "notNull": false + }, + { + "pureName": "role_team_files", + "columnName": "allow_use", + "dataType": "int", + "notNull": false + } + ], + "foreignKeys": [ + { + "constraintType": "foreignKey", + "constraintName": "FK_role_team_files_role_id", + "pureName": "role_team_files", + "refTableName": "roles", + "deleteAction": "CASCADE", + "columns": [ + { + "columnName": "role_id", + "refColumnName": "id" + } + ] + }, + { + "constraintType": "foreignKey", + "constraintName": "FK_role_team_files_team_file_id", + "pureName": "role_team_files", + "refTableName": "team_files", + "deleteAction": "CASCADE", + "columns": [ + { + "columnName": "team_file_id", + "refColumnName": "id" + } + ] + } + ], + "primaryKey": { + "pureName": "role_team_files", + "constraintType": "primaryKey", + "constraintName": "PK_role_team_files", + "columns": [ + { + "columnName": "id" + } + ] + } + }, + { + "pureName": "roles", + "columns": [ + { + "pureName": "roles", + "columnName": "id", + "dataType": "int", + "autoIncrement": true, + "notNull": true + }, + { + "pureName": "roles", + "columnName": "name", + "dataType": "varchar(250)", + "notNull": false + } + ], + "foreignKeys": [], + "primaryKey": { + "pureName": "roles", + "constraintType": "primaryKey", + "constraintName": "PK_roles", + "columns": [ + { + "columnName": "id" + } + ] + }, + "preloadedRows": [ + { + "id": -1, + "name": "anonymous-user" + }, + { + "id": -2, + "name": "logged-user" + }, + { + "id": -3, + "name": "superadmin" + } + ] + }, { "pureName": "table_permission_roles", "columns": [ @@ -1362,39 +1442,111 @@ module.exports = { ] }, { - "pureName": "users", + "pureName": "team_file_types", "columns": [ { - "pureName": "users", + "pureName": "team_file_types", + "columnName": "id", + "dataType": "int", + "notNull": true + }, + { + "pureName": "team_file_types", + "columnName": "name", + "dataType": "varchar(250)", + "notNull": true + } + ], + "foreignKeys": [], + "primaryKey": { + "pureName": "team_file_types", + "constraintType": "primaryKey", + "constraintName": "PK_team_file_types", + "columns": [ + { + "columnName": "id" + } + ] + }, + "preloadedRows": [ + { + "id": -1, + "name": "sql" + } + ] + }, + { + "pureName": "team_files", + "columns": [ + { + "pureName": "team_files", "columnName": "id", "dataType": "int", "autoIncrement": true, "notNull": true }, { - "pureName": "users", - "columnName": "login", + "pureName": "team_files", + "columnName": "file_name", "dataType": "varchar(250)", "notNull": false }, { - "pureName": "users", - "columnName": "password", - "dataType": "varchar(250)", + "pureName": "team_files", + "columnName": "file_content", + "dataType": "text", "notNull": false }, { - "pureName": "users", - "columnName": "email", - "dataType": "varchar(250)", + "pureName": "team_files", + "columnName": "file_type_id", + "dataType": "int", + "notNull": true + }, + { + "pureName": "team_files", + "columnName": "owner_user_id", + "dataType": "int", + "notNull": false + }, + { + "pureName": "team_files", + "columnName": "metadata", + "dataType": "varchar(1000)", "notNull": false } ], - "foreignKeys": [], + "foreignKeys": [ + { + "constraintType": "foreignKey", + "constraintName": "FK_team_files_file_type_id", + "pureName": "team_files", + "refTableName": "team_file_types", + "columns": [ + { + "columnName": "file_type_id", + "refColumnName": "id" + } + ] + }, + { + "constraintType": "foreignKey", + "constraintName": "FK_team_files_owner_user_id", + "pureName": "team_files", + "refTableName": "users", + "deleteAction": "CASCADE", + "columns": [ + { + "columnName": "owner_user_id", + "refColumnName": "id" + } + ] + } + ], "primaryKey": { - "pureName": "users", + "pureName": "team_files", "constraintType": "primaryKey", - "constraintName": "PK_users", + "constraintName": "PK_team_files", "columns": [ { "columnName": "id" @@ -1879,6 +2031,127 @@ module.exports = { } ] } + }, + { + "pureName": "user_team_files", + "columns": [ + { + "pureName": "user_team_files", + "columnName": "id", + "dataType": "int", + "autoIncrement": true, + "notNull": true + }, + { + "pureName": "user_team_files", + "columnName": "user_id", + "dataType": "int", + "notNull": true + }, + { + "pureName": "user_team_files", + "columnName": "team_file_id", + "dataType": "int", + "notNull": true + }, + { + "pureName": "user_team_files", + "columnName": "allow_read", + "dataType": "int", + "notNull": false + }, + { + "pureName": "user_team_files", + "columnName": "allow_write", + "dataType": "int", + "notNull": false + }, + { + "pureName": "user_team_files", + "columnName": "allow_use", + "dataType": "int", + "notNull": false + } + ], + "foreignKeys": [ + { + "constraintType": "foreignKey", + "constraintName": "FK_user_team_files_user_id", + "pureName": "user_team_files", + "refTableName": "users", + "deleteAction": "CASCADE", + "columns": [ + { + "columnName": "user_id", + "refColumnName": "id" + } + ] + }, + { + "constraintType": "foreignKey", + "constraintName": "FK_user_team_files_team_file_id", + "pureName": "user_team_files", + "refTableName": "team_files", + "deleteAction": "CASCADE", + "columns": [ + { + "columnName": "team_file_id", + "refColumnName": "id" + } + ] + } + ], + "primaryKey": { + "pureName": "user_team_files", + "constraintType": "primaryKey", + "constraintName": "PK_user_team_files", + "columns": [ + { + "columnName": "id" + } + ] + } + }, + { + "pureName": "users", + "columns": [ + { + "pureName": "users", + "columnName": "id", + "dataType": "int", + "autoIncrement": true, + "notNull": true + }, + { + "pureName": "users", + "columnName": "login", + "dataType": "varchar(250)", + "notNull": false + }, + { + "pureName": "users", + "columnName": "password", + "dataType": "varchar(250)", + "notNull": false + }, + { + "pureName": "users", + "columnName": "email", + "dataType": "varchar(250)", + "notNull": false + } + ], + "foreignKeys": [], + "primaryKey": { + "pureName": "users", + "constraintType": "primaryKey", + "constraintName": "PK_users", + "columns": [ + { + "columnName": "id" + } + ] + } } ], "collections": [], diff --git a/packages/api/src/utility/hasPermission.js b/packages/api/src/utility/hasPermission.js index 2e9902c2f..f964593b1 100644 --- a/packages/api/src/utility/hasPermission.js +++ b/packages/api/src/utility/hasPermission.js @@ -327,7 +327,7 @@ async function testStandardPermission(permission, req, loadedPermissions) { loadedPermissions = await loadPermissionsFromRequest(req); } if (!hasPermission(permission, loadedPermissions)) { - throw new Error('DBGM-00265 Permission not granted'); + throw new Error(`DBGM-00265 Permission ${permission} not granted`); } } @@ -344,7 +344,7 @@ async function testDatabaseRolePermission(conid, database, requiredRole, req) { const requiredIndex = getDatabaseRoleLevelIndex(requiredRole); const roleIndex = getDatabaseRoleLevelIndex(role); if (roleIndex < requiredIndex) { - throw new Error('DBGM-00266 Permission not granted'); + throw new Error(`DBGM-00266 Permission ${requiredRole} not granted`); } } diff --git a/packages/tools/src/testPermission.ts b/packages/tools/src/testPermission.ts index 1998a947a..9bc57cd53 100644 --- a/packages/tools/src/testPermission.ts +++ b/packages/tools/src/testPermission.ts @@ -107,11 +107,27 @@ export function testSubPermission( export function getPredefinedPermissions(predefinedRoleName: string) { switch (predefinedRoleName) { case 'superadmin': - return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections']; + return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections', '~all-team-files/*']; case 'logged-user': - return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections', '~run-shell-script']; + return [ + '*', + '~widgets/admin', + '~admin/*', + '~internal-storage', + '~all-connections', + '~run-shell-script', + '~all-team-files/*', + ]; case 'anonymous-user': - return ['*', '~widgets/admin', '~admin/*', '~internal-storage', '~all-connections', '~run-shell-script']; + return [ + '*', + '~widgets/admin', + '~admin/*', + '~internal-storage', + '~all-connections', + '~run-shell-script', + '~all-team-files/*', + ]; default: return null; } diff --git a/packages/web/src/appobj/SavedFileAppObject.svelte b/packages/web/src/appobj/SavedFileAppObject.svelte index 01060e41a..89776c195 100644 --- a/packages/web/src/appobj/SavedFileAppObject.svelte +++ b/packages/web/src/appobj/SavedFileAppObject.svelte @@ -192,6 +192,7 @@ import { isProApp } from '../utility/proTools'; import { saveFileToDisk } from '../utility/exportFileTools'; import { getConnectionInfo } from '../utility/metadataLoaders'; + import { showSnackbarError } from '../utility/snackbar'; export let data; @@ -214,11 +215,20 @@ function createMenu() { return [ handler?.tabComponent && { text: 'Open', onClick: openTab }, - hasPermission(`files/${data.folder}/write`) && { text: 'Rename', onClick: handleRename }, - hasPermission(`files/${data.folder}/write`) && { text: 'Create copy', onClick: handleCopy }, - hasPermission(`files/${data.folder}/write`) && { text: 'Delete', onClick: handleDelete }, + + !data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Rename', onClick: handleRename }, + !data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Create copy', onClick: handleCopy }, + !data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Delete', onClick: handleDelete }, + + data.teamFileId && data.allowWrite && { text: 'Rename', onClick: handleRename }, + data.teamFileId && + data.allowRead && + hasPermission('all-team-files/create') && { text: 'Create copy', onClick: handleCopy }, + data.teamFileId && data.allowWrite && { text: 'Delete', onClick: handleDelete }, + folder == 'markdown' && { text: 'Show page', onClick: showMarkdownPage }, - { text: 'Download', onClick: handleDownload }, + !data.teamFileId && { text: 'Download', onClick: handleDownload }, + data.teamFileId && data.allowRead && { text: 'Download', onClick: handleDownload }, ]; } @@ -226,7 +236,9 @@ showModal(ConfirmModal, { message: `Really delete file ${data.file}?`, onConfirm: () => { - if (data.folid && data.cntid) { + if (data.teamFileId) { + apiCall('team-files/delete', { teamFileId: data.teamFileId }); + } else if (data.folid && data.cntid) { apiCall('cloud/delete-content', { folid: data.folid, cntid: data.cntid, @@ -244,7 +256,9 @@ label: 'New file name', header: 'Rename file', onConfirm: newFile => { - if (data.folid && data.cntid) { + if (data.teamFileId) { + apiCall('team-files/update', { teamFileId: data.teamFileId, name: newFile }); + } else if (data.folid && data.cntid) { apiCall('cloud/rename-content', { folid: data.folid, cntid: data.cntid, @@ -263,7 +277,9 @@ label: 'New file name', header: 'Copy file', onConfirm: newFile => { - if (data.folid && data.cntid) { + if (data.teamFileId) { + apiCall('team-files/copy', { teamFileId: data.teamFileId, newName: newFile }); + } else if (data.folid && data.cntid) { apiCall('cloud/copy-file', { folid: data.folid, cntid: data.cntid, @@ -279,7 +295,12 @@ const handleDownload = () => { saveFileToDisk( async filePath => { - if (data.folid && data.cntid) { + if (data.teamFileId) { + await apiCall('team-files/export-file', { + teamFileId: data.teamFileId, + filePath, + }); + } else if (data.folid && data.cntid) { await apiCall('cloud/export-file', { folid: data.folid, cntid: data.cntid, @@ -299,7 +320,23 @@ async function openTab() { let dataContent; - if (data.folid && data.cntid) { + if (data.teamFileId) { + if (data?.metadata?.autoExecute) { + if (!data.allowUse) { + showSnackbarError('You do not have permission to use this team file'); + return; + } + } else { + if (!data.allowRead) { + showSnackbarError('You do not have permission to read this team file'); + return; + } + } + const resp = await apiCall('team-files/get-content', { + teamFileId: data.teamFileId, + }); + dataContent = resp.content; + } else if (data.folid && data.cntid) { const resp = await apiCall('cloud/get-content', { folid: data.folid, cntid: data.cntid, @@ -324,6 +361,11 @@ tooltip = `${getConnectionLabel(connection)}\n${database}`; } + if (data?.metadata?.connectionId) { + connProps.conid = data.metadata.connectionId; + connProps.database = data.metadata.databaseName; + } + openNewTab( { title: data.file, @@ -336,6 +378,8 @@ savedFormat: handler.format, savedCloudFolderId: data.folid, savedCloudContentId: data.cntid, + savedTeamFileId: data.teamFileId, + hideEditor: data.teamFileId && data?.metadata?.autoExecute && !data.allowRead, ...connProps, }, }, diff --git a/packages/web/src/elements/VerticalSplitter.svelte b/packages/web/src/elements/VerticalSplitter.svelte index 74d81302c..faeea13c0 100644 --- a/packages/web/src/elements/VerticalSplitter.svelte +++ b/packages/web/src/elements/VerticalSplitter.svelte @@ -7,6 +7,7 @@ export let isSplitter = true; export let initialValue = undefined; + export let hideFirst = false; export let allowCollapseChild1 = false; export let allowCollapseChild2 = false; @@ -22,28 +23,32 @@
-
- -
- {#if isSplitter} + {#if !hideFirst}
{ - size += e.detail; - if (clientHeight > 0) customRatio = size / clientHeight; - }} - /> + class="child1" + style={isSplitter + ? collapsed1 + ? 'display:none' + : collapsed2 + ? 'flex:1' + : `height:${size}px; min-height:${size}px; max-height:${size}px}` + : `flex:1`} + > + +
+ {/if} + {#if isSplitter} + {#if !hideFirst} +
{ + size += e.detail; + if (clientHeight > 0) customRatio = size / clientHeight; + }} + /> + {/if}
diff --git a/packages/web/src/modals/SaveFileModal.svelte b/packages/web/src/modals/SaveFileModal.svelte index bc9bf0d77..062f63298 100644 --- a/packages/web/src/modals/SaveFileModal.svelte +++ b/packages/web/src/modals/SaveFileModal.svelte @@ -4,7 +4,7 @@ import FormProviderCore from '../forms/FormProviderCore.svelte'; import FormSubmit from '../forms/FormSubmit.svelte'; import FormTextField from '../forms/FormTextField.svelte'; - import { cloudSigninTokenHolder } from '../stores'; + import { cloudSigninTokenHolder, getCurrentConfig } from '../stores'; import { _t } from '../translations'; import { apiCall } from '../utility/api'; import { writable } from 'svelte/store'; @@ -13,6 +13,7 @@ import ModalBase from './ModalBase.svelte'; import { closeCurrentModal, showModal } from './modalTools'; import FormCloudFolderSelect from '../forms/FormCloudFolderSelect.svelte'; + import FormCheckboxField from '../forms/FormCheckboxField.svelte'; export let data; export let name; @@ -31,7 +32,20 @@ const handleSubmit = async e => { const { name, cloudFolder } = e.detail; - if (cloudFolder === '__local') { + if ($values['saveToTeamFolder']) { + const { teamFileId } = await apiCall('team-files/create-new', { fileType: folder, file: name, data }); + closeCurrentModal(); + if (onSave) { + onSave(name, { + savedFile: name, + savedFolder: folder, + savedFilePath: null, + savedCloudFolderId: null, + savedCloudContentId: null, + savedTeamFileId: teamFileId, + }); + } + } else if (cloudFolder === '__local') { await apiCall('files/save', { folder, file: name, data, format }); closeCurrentModal(); if (onSave) { @@ -41,6 +55,7 @@ savedFilePath: null, savedCloudFolderId: null, savedCloudContentId: null, + savedTeamFileId: null, }); } } else { @@ -61,6 +76,7 @@ savedFilePath: null, savedCloudFolderId: cloudFolder, savedCloudContentId: resp.cntid, + savedTeamFileId: null, }); } } @@ -82,6 +98,7 @@ savedFilePath: filePath, savedCloudFolderId: null, savedCloudContentId: null, + savedTeamFileId: null, }); } }; @@ -91,7 +108,7 @@ Save file - {#if $cloudSigninTokenHolder} + {#if $cloudSigninTokenHolder && !$values['saveToTeamFolder']} {/if} + {#if getCurrentConfig().storageDatabase} + + {/if} diff --git a/packages/web/src/tabs/QueryTab.svelte b/packages/web/src/tabs/QueryTab.svelte index e040a0e5d..995d505de 100644 --- a/packages/web/src/tabs/QueryTab.svelte +++ b/packages/web/src/tabs/QueryTab.svelte @@ -160,6 +160,7 @@ export let conid; export let database; export let initialArgs; + export let hideEditor; export const activator = createActivator('QueryTab', false); @@ -653,7 +654,7 @@ - + {#if driver?.databaseEngineTypes?.includes('sql')} ({ reloadTrigger: { key: `cloud-content-changed` }, }); +const teamFilesLoader = () => ({ + url: 'team-files/list', + params: {}, + reloadTrigger: { key: `team-files-changed` }, +}); + async function getCore(loader, args) { const { url, params, reloadTrigger, transform, onLoaded, errorValue } = loader(args); const key = stableStringify({ url, ...params }); @@ -523,3 +529,10 @@ export function getCloudContentList(args) { export function useCloudContentList(args = {}) { return useCore(cloudContentListLoader, args); } + +export function getTeamFiles(args) { + return getCore(teamFilesLoader, args); +} +export function useTeamFiles(args) { + return useCore(teamFilesLoader, args); +} diff --git a/packages/web/src/utility/saveTabFile.ts b/packages/web/src/utility/saveTabFile.ts index b4519fc9d..c47c66173 100644 --- a/packages/web/src/utility/saveTabFile.ts +++ b/packages/web/src/utility/saveTabFile.ts @@ -15,11 +15,14 @@ export default async function saveTabFile(editor, saveMode, folder, format, file const tabs = get(openedTabs); const tabid = editor.activator.tabid; const data = editor.getData(); - const { savedFile, savedFilePath, savedFolder, savedCloudFolderId, savedCloudContentId } = + const { savedFile, savedFilePath, savedFolder, savedCloudFolderId, savedCloudContentId, savedTeamFileId } = tabs.find(x => x.tabid == tabid).props || {}; const handleSave = async () => { - if (savedCloudFolderId && savedCloudContentId) { + if (savedTeamFileId) { + const resp = await apiCall('team-files/update', { teamFileId: savedTeamFileId, data }); + markTabSaved(tabid); + } else if (savedCloudFolderId && savedCloudContentId) { const resp = await apiCall('cloud/save-file', { folid: savedCloudFolderId, fileName: savedFile, diff --git a/packages/web/src/widgets/SavedFilesList.svelte b/packages/web/src/widgets/SavedFilesList.svelte index 4dd592638..095794588 100644 --- a/packages/web/src/widgets/SavedFilesList.svelte +++ b/packages/web/src/widgets/SavedFilesList.svelte @@ -8,7 +8,7 @@ import SearchInput from '../elements/SearchInput.svelte'; import FontIcon from '../icons/FontIcon.svelte'; import { apiCall } from '../utility/api'; - import { useFiles } from '../utility/metadataLoaders'; + import { useFiles, useTeamFiles } from '../utility/metadataLoaders'; import WidgetsInnerContainer from './WidgetsInnerContainer.svelte'; import { isProApp } from '../utility/proTools'; import InlineUploadButton from '../buttons/InlineUploadButton.svelte'; @@ -29,6 +29,7 @@ const perspectiveFiles = useFiles({ folder: 'perspectives' }); const modelTransformFiles = useFiles({ folder: 'modtrans' }); const appFiles = useFiles({ folder: 'apps' }); + const teamFiles = useTeamFiles({}); $: files = [ ...($sqlFiles || []), @@ -44,6 +45,7 @@ ...((isProApp() && $dataDeployJobFiles) || []), ...((isProApp() && $dbCompareJobFiles) || []), ...((isProApp() && $appFiles) || []), + ...($teamFiles || []), ]; function handleRefreshFiles() { @@ -81,5 +83,10 @@ - dataFolderTitle(data.folder)} {filter} /> + (data.teamFileId ? 'Team files' : dataFolderTitle(data.folder))} + {filter} + />