SYNC: Merge pull request #12 from dbgate/feature/team-files

This commit is contained in:
Jan Prochazka
2025-09-26 12:44:08 +02:00
committed by Diflow
parent 925e3a67da
commit 494b33bd7a
15 changed files with 510 additions and 104 deletions

View File

@@ -10,7 +10,13 @@ function getTokenSecret() {
return tokenSecret; return tokenSecret;
} }
function getStaticTokenSecret() {
// TODO static not fixed
return '14813c43-a91b-4ad1-9dcd-a81bd7dbb05f';
}
module.exports = { module.exports = {
getTokenLifetime, getTokenLifetime,
getTokenSecret, getTokenSecret,
getStaticTokenSecret,
}; };

View File

@@ -21,7 +21,7 @@ module.exports = {
const filePermissions = await loadFilePermissionsFromRequest(req); const filePermissions = await loadFilePermissionsFromRequest(req);
for (const file of await fs.readdir(dir)) { 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); const role = getFilePermissionRole('apps', file, filePermissions);
if (role == 'deny') continue; if (role == 'deny') continue;
} }

View File

@@ -8,11 +8,13 @@ const path = require('path');
const { handleProcessCommunication } = require('../utility/processComm'); const { handleProcessCommunication } = require('../utility/processComm');
const processArgs = require('../utility/processArgs'); const processArgs = require('../utility/processArgs');
const { appdir } = require('../utility/directories'); const { appdir } = require('../utility/directories');
const { getLogger, extractErrorLogData } = require('dbgate-tools'); const { getLogger, extractErrorLogData, removeSqlFrontMatter } = require('dbgate-tools');
const pipeForkLogs = require('../utility/pipeForkLogs'); const pipeForkLogs = require('../utility/pipeForkLogs');
const config = require('./config'); const config = require('./config');
const { sendToAuditLog } = require('../utility/auditlog'); const { sendToAuditLog } = require('../utility/auditlog');
const { testStandardPermission, testDatabaseRolePermission } = require('../utility/hasPermission'); const { testStandardPermission, testDatabaseRolePermission } = require('../utility/hasPermission');
const { getStaticTokenSecret } = require('../auth/authCommon');
const jwt = require('jsonwebtoken');
const logger = getLogger('sessions'); const logger = getLogger('sessions');
@@ -95,7 +97,7 @@ module.exports = {
socket.emit(`session-initialize-file-${jslid}`); socket.emit(`session-initialize-file-${jslid}`);
}, },
handle_ping() { }, handle_ping() {},
create_meta: true, create_meta: true,
async create({ conid, database }) { async create({ conid, database }) {
@@ -149,12 +151,23 @@ module.exports = {
executeQuery_meta: true, executeQuery_meta: true,
async executeQuery({ sesid, sql, autoCommit, autoDetectCharts, limitRows, frontMatter }, req) { 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); const session = this.opened.find(x => x.sesid == sesid);
if (!session) { if (!session) {
throw new Error('Invalid 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, { sendToAuditLog(req, {
category: 'dbop', category: 'dbop',

View File

@@ -29,6 +29,8 @@ const files = require('./controllers/files');
const scheduler = require('./controllers/scheduler'); const scheduler = require('./controllers/scheduler');
const queryHistory = require('./controllers/queryHistory'); const queryHistory = require('./controllers/queryHistory');
const cloud = require('./controllers/cloud'); const cloud = require('./controllers/cloud');
const teamFiles = require('./controllers/teamFiles');
const onFinished = require('on-finished'); const onFinished = require('on-finished');
const processArgs = require('./utility/processArgs'); const processArgs = require('./utility/processArgs');
@@ -264,6 +266,7 @@ function useAllControllers(app, electron) {
useController(app, electron, '/apps', apps); useController(app, electron, '/apps', apps);
useController(app, electron, '/auth', auth); useController(app, electron, '/auth', auth);
useController(app, electron, '/cloud', cloud); useController(app, electron, '/cloud', cloud);
useController(app, electron, '/team-files', teamFiles);
} }
function setElectronSender(electronSender) { function setElectronSender(electronSender) {

View File

@@ -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", "pureName": "role_connections",
"columns": [ "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", "pureName": "table_permission_roles",
"columns": [ "columns": [
@@ -1362,39 +1442,111 @@ module.exports = {
] ]
}, },
{ {
"pureName": "users", "pureName": "team_file_types",
"columns": [ "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", "columnName": "id",
"dataType": "int", "dataType": "int",
"autoIncrement": true, "autoIncrement": true,
"notNull": true "notNull": true
}, },
{ {
"pureName": "users", "pureName": "team_files",
"columnName": "login", "columnName": "file_name",
"dataType": "varchar(250)", "dataType": "varchar(250)",
"notNull": false "notNull": false
}, },
{ {
"pureName": "users", "pureName": "team_files",
"columnName": "password", "columnName": "file_content",
"dataType": "varchar(250)", "dataType": "text",
"notNull": false "notNull": false
}, },
{ {
"pureName": "users", "pureName": "team_files",
"columnName": "email", "columnName": "file_type_id",
"dataType": "varchar(250)", "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 "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": { "primaryKey": {
"pureName": "users", "pureName": "team_files",
"constraintType": "primaryKey", "constraintType": "primaryKey",
"constraintName": "PK_users", "constraintName": "PK_team_files",
"columns": [ "columns": [
{ {
"columnName": "id" "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": [], "collections": [],

View File

@@ -327,7 +327,7 @@ async function testStandardPermission(permission, req, loadedPermissions) {
loadedPermissions = await loadPermissionsFromRequest(req); loadedPermissions = await loadPermissionsFromRequest(req);
} }
if (!hasPermission(permission, loadedPermissions)) { 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 requiredIndex = getDatabaseRoleLevelIndex(requiredRole);
const roleIndex = getDatabaseRoleLevelIndex(role); const roleIndex = getDatabaseRoleLevelIndex(role);
if (roleIndex < requiredIndex) { if (roleIndex < requiredIndex) {
throw new Error('DBGM-00266 Permission not granted'); throw new Error(`DBGM-00266 Permission ${requiredRole} not granted`);
} }
} }

View File

@@ -107,11 +107,27 @@ export function testSubPermission(
export function getPredefinedPermissions(predefinedRoleName: string) { export function getPredefinedPermissions(predefinedRoleName: string) {
switch (predefinedRoleName) { switch (predefinedRoleName) {
case 'superadmin': case 'superadmin':
return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections']; return ['*', '~widgets/*', 'widgets/admin', 'widgets/database', '~all-connections', '~all-team-files/*'];
case 'logged-user': 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': 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: default:
return null; return null;
} }

View File

@@ -192,6 +192,7 @@
import { isProApp } from '../utility/proTools'; import { isProApp } from '../utility/proTools';
import { saveFileToDisk } from '../utility/exportFileTools'; import { saveFileToDisk } from '../utility/exportFileTools';
import { getConnectionInfo } from '../utility/metadataLoaders'; import { getConnectionInfo } from '../utility/metadataLoaders';
import { showSnackbarError } from '../utility/snackbar';
export let data; export let data;
@@ -214,11 +215,20 @@
function createMenu() { function createMenu() {
return [ return [
handler?.tabComponent && { text: 'Open', onClick: openTab }, 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 }, !data.teamFileId && hasPermission(`files/${data.folder}/write`) && { text: 'Rename', onClick: handleRename },
hasPermission(`files/${data.folder}/write`) && { text: 'Delete', onClick: handleDelete }, !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 }, 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, { showModal(ConfirmModal, {
message: `Really delete file ${data.file}?`, message: `Really delete file ${data.file}?`,
onConfirm: () => { 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', { apiCall('cloud/delete-content', {
folid: data.folid, folid: data.folid,
cntid: data.cntid, cntid: data.cntid,
@@ -244,7 +256,9 @@
label: 'New file name', label: 'New file name',
header: 'Rename file', header: 'Rename file',
onConfirm: newFile => { 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', { apiCall('cloud/rename-content', {
folid: data.folid, folid: data.folid,
cntid: data.cntid, cntid: data.cntid,
@@ -263,7 +277,9 @@
label: 'New file name', label: 'New file name',
header: 'Copy file', header: 'Copy file',
onConfirm: newFile => { 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', { apiCall('cloud/copy-file', {
folid: data.folid, folid: data.folid,
cntid: data.cntid, cntid: data.cntid,
@@ -279,7 +295,12 @@
const handleDownload = () => { const handleDownload = () => {
saveFileToDisk( saveFileToDisk(
async filePath => { 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', { await apiCall('cloud/export-file', {
folid: data.folid, folid: data.folid,
cntid: data.cntid, cntid: data.cntid,
@@ -299,7 +320,23 @@
async function openTab() { async function openTab() {
let dataContent; 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', { const resp = await apiCall('cloud/get-content', {
folid: data.folid, folid: data.folid,
cntid: data.cntid, cntid: data.cntid,
@@ -324,6 +361,11 @@
tooltip = `${getConnectionLabel(connection)}\n${database}`; tooltip = `${getConnectionLabel(connection)}\n${database}`;
} }
if (data?.metadata?.connectionId) {
connProps.conid = data.metadata.connectionId;
connProps.database = data.metadata.databaseName;
}
openNewTab( openNewTab(
{ {
title: data.file, title: data.file,
@@ -336,6 +378,8 @@
savedFormat: handler.format, savedFormat: handler.format,
savedCloudFolderId: data.folid, savedCloudFolderId: data.folid,
savedCloudContentId: data.cntid, savedCloudContentId: data.cntid,
savedTeamFileId: data.teamFileId,
hideEditor: data.teamFileId && data?.metadata?.autoExecute && !data.allowRead,
...connProps, ...connProps,
}, },
}, },

View File

@@ -7,6 +7,7 @@
export let isSplitter = true; export let isSplitter = true;
export let initialValue = undefined; export let initialValue = undefined;
export let hideFirst = false;
export let allowCollapseChild1 = false; export let allowCollapseChild1 = false;
export let allowCollapseChild2 = false; export let allowCollapseChild2 = false;
@@ -22,28 +23,32 @@
</script> </script>
<div class="container" bind:clientHeight> <div class="container" bind:clientHeight>
<div {#if !hideFirst}
class="child1"
style={isSplitter
? collapsed1
? 'display:none'
: collapsed2
? 'flex:1'
: `height:${size}px; min-height:${size}px; max-height:${size}px}`
: `flex:1`}
>
<slot name="1" />
</div>
{#if isSplitter}
<div <div
class={'vertical-split-handle'} class="child1"
style={collapsed1 || collapsed2 ? 'display:none' : ''} style={isSplitter
use:splitterDrag={'clientY'} ? collapsed1
on:resizeSplitter={e => { ? 'display:none'
size += e.detail; : collapsed2
if (clientHeight > 0) customRatio = size / clientHeight; ? 'flex:1'
}} : `height:${size}px; min-height:${size}px; max-height:${size}px}`
/> : `flex:1`}
>
<slot name="1" />
</div>
{/if}
{#if isSplitter}
{#if !hideFirst}
<div
class={'vertical-split-handle'}
style={collapsed1 || collapsed2 ? 'display:none' : ''}
use:splitterDrag={'clientY'}
on:resizeSplitter={e => {
size += e.detail;
if (clientHeight > 0) customRatio = size / clientHeight;
}}
/>
{/if}
<div <div
class={collapsed1 ? 'child1' : 'child2'} class={collapsed1 ? 'child1' : 'child2'}
style={collapsed2 ? 'display:none' : collapsed1 ? 'flex:1' : 'child2'} style={collapsed2 ? 'display:none' : collapsed1 ? 'flex:1' : 'child2'}

View File

@@ -120,6 +120,7 @@
'icon structure': 'mdi mdi-tools', 'icon structure': 'mdi mdi-tools',
'icon square': 'mdi mdi-square', 'icon square': 'mdi mdi-square',
'icon data-deploy': 'mdi mdi-database-settings', 'icon data-deploy': 'mdi mdi-database-settings',
'icon team-file': 'mdi mdi-account-file',
'icon cloud-account': 'mdi mdi-account-remove-outline', 'icon cloud-account': 'mdi mdi-account-remove-outline',
'icon cloud-account-connected': 'mdi mdi-account-check-outline', 'icon cloud-account-connected': 'mdi mdi-account-check-outline',
@@ -351,6 +352,7 @@
'img settings': 'mdi mdi-cog color-icon-blue', 'img settings': 'mdi mdi-cog color-icon-blue',
'img data-deploy': 'mdi mdi-database-settings color-icon-green', 'img data-deploy': 'mdi mdi-database-settings color-icon-green',
'img arrow-start-here': 'mdi mdi-arrow-down-bold-circle color-icon-green', 'img arrow-start-here': 'mdi mdi-arrow-down-bold-circle color-icon-green',
'img team-file': 'mdi mdi-account-file color-icon-red',
}; };
</script> </script>

View File

@@ -4,7 +4,7 @@
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 { cloudSigninTokenHolder } from '../stores'; import { cloudSigninTokenHolder, getCurrentConfig } from '../stores';
import { _t } from '../translations'; import { _t } from '../translations';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import { writable } from 'svelte/store'; import { writable } from 'svelte/store';
@@ -13,6 +13,7 @@
import ModalBase from './ModalBase.svelte'; import ModalBase from './ModalBase.svelte';
import { closeCurrentModal, showModal } from './modalTools'; import { closeCurrentModal, showModal } from './modalTools';
import FormCloudFolderSelect from '../forms/FormCloudFolderSelect.svelte'; import FormCloudFolderSelect from '../forms/FormCloudFolderSelect.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
export let data; export let data;
export let name; export let name;
@@ -31,7 +32,20 @@
const handleSubmit = async e => { const handleSubmit = async e => {
const { name, cloudFolder } = e.detail; 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 }); await apiCall('files/save', { folder, file: name, data, format });
closeCurrentModal(); closeCurrentModal();
if (onSave) { if (onSave) {
@@ -41,6 +55,7 @@
savedFilePath: null, savedFilePath: null,
savedCloudFolderId: null, savedCloudFolderId: null,
savedCloudContentId: null, savedCloudContentId: null,
savedTeamFileId: null,
}); });
} }
} else { } else {
@@ -61,6 +76,7 @@
savedFilePath: null, savedFilePath: null,
savedCloudFolderId: cloudFolder, savedCloudFolderId: cloudFolder,
savedCloudContentId: resp.cntid, savedCloudContentId: resp.cntid,
savedTeamFileId: null,
}); });
} }
} }
@@ -82,6 +98,7 @@
savedFilePath: filePath, savedFilePath: filePath,
savedCloudFolderId: null, savedCloudFolderId: null,
savedCloudContentId: null, savedCloudContentId: null,
savedTeamFileId: null,
}); });
} }
}; };
@@ -91,7 +108,7 @@
<ModalBase {...$$restProps}> <ModalBase {...$$restProps}>
<svelte:fragment slot="header">Save file</svelte:fragment> <svelte:fragment slot="header">Save file</svelte:fragment>
<FormTextField label="File name" name="name" focused /> <FormTextField label="File name" name="name" focused />
{#if $cloudSigninTokenHolder} {#if $cloudSigninTokenHolder && !$values['saveToTeamFolder']}
<FormCloudFolderSelect <FormCloudFolderSelect
label="Choose cloud folder" label="Choose cloud folder"
name="cloudFolder" name="cloudFolder"
@@ -107,6 +124,9 @@
]} ]}
/> />
{/if} {/if}
{#if getCurrentConfig().storageDatabase}
<FormCheckboxField label="Save to team folder" name="saveToTeamFolder" />
{/if}
<svelte:fragment slot="footer"> <svelte:fragment slot="footer">
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} /> <FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />

View File

@@ -160,6 +160,7 @@
export let conid; export let conid;
export let database; export let database;
export let initialArgs; export let initialArgs;
export let hideEditor;
export const activator = createActivator('QueryTab', false); export const activator = createActivator('QueryTab', false);
@@ -653,7 +654,7 @@
</script> </script>
<ToolStripContainer bind:this={domToolStrip}> <ToolStripContainer bind:this={domToolStrip}>
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}> <VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue} hideFirst={hideEditor}>
<svelte:fragment slot="1"> <svelte:fragment slot="1">
{#if driver?.databaseEngineTypes?.includes('sql')} {#if driver?.databaseEngineTypes?.includes('sql')}
<SqlEditor <SqlEditor

View File

@@ -184,6 +184,12 @@ const cloudContentListLoader = () => ({
reloadTrigger: { key: `cloud-content-changed` }, reloadTrigger: { key: `cloud-content-changed` },
}); });
const teamFilesLoader = () => ({
url: 'team-files/list',
params: {},
reloadTrigger: { key: `team-files-changed` },
});
async function getCore(loader, args) { async function getCore(loader, args) {
const { url, params, reloadTrigger, transform, onLoaded, errorValue } = loader(args); const { url, params, reloadTrigger, transform, onLoaded, errorValue } = loader(args);
const key = stableStringify({ url, ...params }); const key = stableStringify({ url, ...params });
@@ -523,3 +529,10 @@ export function getCloudContentList(args) {
export function useCloudContentList(args = {}) { export function useCloudContentList(args = {}) {
return useCore(cloudContentListLoader, args); return useCore(cloudContentListLoader, args);
} }
export function getTeamFiles(args) {
return getCore(teamFilesLoader, args);
}
export function useTeamFiles(args) {
return useCore(teamFilesLoader, args);
}

View File

@@ -15,11 +15,14 @@ export default async function saveTabFile(editor, saveMode, folder, format, file
const tabs = get(openedTabs); const tabs = get(openedTabs);
const tabid = editor.activator.tabid; const tabid = editor.activator.tabid;
const data = editor.getData(); const data = editor.getData();
const { savedFile, savedFilePath, savedFolder, savedCloudFolderId, savedCloudContentId } = const { savedFile, savedFilePath, savedFolder, savedCloudFolderId, savedCloudContentId, savedTeamFileId } =
tabs.find(x => x.tabid == tabid).props || {}; tabs.find(x => x.tabid == tabid).props || {};
const handleSave = async () => { 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', { const resp = await apiCall('cloud/save-file', {
folid: savedCloudFolderId, folid: savedCloudFolderId,
fileName: savedFile, fileName: savedFile,

View File

@@ -8,7 +8,7 @@
import SearchInput from '../elements/SearchInput.svelte'; import SearchInput from '../elements/SearchInput.svelte';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import { apiCall } from '../utility/api'; import { apiCall } from '../utility/api';
import { useFiles } from '../utility/metadataLoaders'; import { useFiles, useTeamFiles } from '../utility/metadataLoaders';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte'; import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
import { isProApp } from '../utility/proTools'; import { isProApp } from '../utility/proTools';
import InlineUploadButton from '../buttons/InlineUploadButton.svelte'; import InlineUploadButton from '../buttons/InlineUploadButton.svelte';
@@ -29,6 +29,7 @@
const perspectiveFiles = useFiles({ folder: 'perspectives' }); const perspectiveFiles = useFiles({ folder: 'perspectives' });
const modelTransformFiles = useFiles({ folder: 'modtrans' }); const modelTransformFiles = useFiles({ folder: 'modtrans' });
const appFiles = useFiles({ folder: 'apps' }); const appFiles = useFiles({ folder: 'apps' });
const teamFiles = useTeamFiles({});
$: files = [ $: files = [
...($sqlFiles || []), ...($sqlFiles || []),
@@ -44,6 +45,7 @@
...((isProApp() && $dataDeployJobFiles) || []), ...((isProApp() && $dataDeployJobFiles) || []),
...((isProApp() && $dbCompareJobFiles) || []), ...((isProApp() && $dbCompareJobFiles) || []),
...((isProApp() && $appFiles) || []), ...((isProApp() && $appFiles) || []),
...($teamFiles || []),
]; ];
function handleRefreshFiles() { function handleRefreshFiles() {
@@ -81,5 +83,10 @@
</SearchBoxWrapper> </SearchBoxWrapper>
<WidgetsInnerContainer> <WidgetsInnerContainer>
<AppObjectList list={files} module={savedFileAppObject} groupFunc={data => dataFolderTitle(data.folder)} {filter} /> <AppObjectList
list={files}
module={savedFileAppObject}
groupFunc={data => (data.teamFileId ? 'Team files' : dataFolderTitle(data.folder))}
{filter}
/>
</WidgetsInnerContainer> </WidgetsInnerContainer>