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

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

View File

@@ -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 @@
</script>
<div class="container" bind:clientHeight>
<div
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}
{#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;
}}
/>
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}
{#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
class={collapsed1 ? 'child1' : 'child2'}
style={collapsed2 ? 'display:none' : collapsed1 ? 'flex:1' : 'child2'}

View File

@@ -120,6 +120,7 @@
'icon structure': 'mdi mdi-tools',
'icon square': 'mdi mdi-square',
'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-connected': 'mdi mdi-account-check-outline',
@@ -351,6 +352,7 @@
'img settings': 'mdi mdi-cog color-icon-blue',
'img data-deploy': 'mdi mdi-database-settings 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>

View File

@@ -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 @@
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Save file</svelte:fragment>
<FormTextField label="File name" name="name" focused />
{#if $cloudSigninTokenHolder}
{#if $cloudSigninTokenHolder && !$values['saveToTeamFolder']}
<FormCloudFolderSelect
label="Choose cloud folder"
name="cloudFolder"
@@ -107,6 +124,9 @@
]}
/>
{/if}
{#if getCurrentConfig().storageDatabase}
<FormCheckboxField label="Save to team folder" name="saveToTeamFolder" />
{/if}
<svelte:fragment slot="footer">
<FormSubmit value={_t('common.save', { defaultMessage: 'Save' })} on:click={handleSubmit} />

View File

@@ -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 @@
</script>
<ToolStripContainer bind:this={domToolStrip}>
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}>
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue} hideFirst={hideEditor}>
<svelte:fragment slot="1">
{#if driver?.databaseEngineTypes?.includes('sql')}
<SqlEditor

View File

@@ -184,6 +184,12 @@ const cloudContentListLoader = () => ({
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);
}

View File

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

View File

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