SYNC: Merge pull request #9 from dbgate/feature/apps

This commit is contained in:
Jan Prochazka
2025-09-11 13:10:36 +02:00
committed by Diflow
parent ef15f299d2
commit 11a4f0ef32
40 changed files with 1770 additions and 754 deletions

View File

@@ -1,120 +0,0 @@
<script lang="ts" context="module">
const APP_LABELS = {
'command.sql': 'SQL commands',
'query.sql': 'SQL queries',
};
const COMMAND_TEMPLATE = `-- Write SQL command here
-- After save, you can execute it from database context menu, for all databases, which use this application
`;
const QUERY_TEMPLATE = `-- Write SQL query here
-- After save, you can view it in tables list, for all databases, which use this application
`;
</script>
<script lang="ts">
import { createFreeTableModel } from 'dbgate-datalib';
import _ from 'lodash';
import AppObjectList from '../appobj/AppObjectList.svelte';
import * as appFileAppObject from '../appobj/AppFileAppObject.svelte';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import DropDownButton from '../buttons/DropDownButton.svelte';
import InlineButton from '../buttons/InlineButton.svelte';
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import SearchInput from '../elements/SearchInput.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import InputTextModal from '../modals/InputTextModal.svelte';
import { showModal } from '../modals/modalTools';
import newQuery from '../query/newQuery';
import { currentApplication } from '../stores';
import { apiCall } from '../utility/api';
import { useAppFiles, useArchiveFolders } from '../utility/metadataLoaders';
import openNewTab from '../utility/openNewTab';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
import { showSnackbarError } from '../utility/snackbar';
let filter = '';
$: folder = $currentApplication;
$: files = useAppFiles({ folder });
const handleRefreshFiles = () => {
apiCall('apps/refresh-files', { folder });
};
function handleNewSqlFile(fileType, header, initialData) {
showModal(InputTextModal, {
value: '',
label: 'New file name',
header,
onConfirm: async file => {
newQuery({
title: file,
initialData,
// @ts-ignore
savedFile: file + '.' + fileType,
savedFolder: 'app:' + $currentApplication,
savedFormat: 'text',
appFolder: $currentApplication,
});
},
});
}
async function handleNewConfigFile(fileName, content) {
if (!(await apiCall('apps/create-config-file', { fileName, content, appFolder: $currentApplication }))) {
showSnackbarError('File not created, probably already exists');
}
}
function createAddMenu() {
return [
{
text: 'New SQL command',
onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command', COMMAND_TEMPLATE),
},
{
text: 'New SQL query',
onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE),
},
{
text: 'New virtual references file',
onClick: () => handleNewConfigFile('virtual-references.config.json', []),
},
{
text: 'New dictionary descriptions file',
onClick: () => handleNewConfigFile('dictionary-descriptions.config.json', []),
},
// { text: 'New query view', onClick: () => handleNewSqlFile('query.sql', 'Create new SQL query', QUERY_TEMPLATE) },
];
}
</script>
<SearchBoxWrapper>
<SearchInput placeholder="Search application files" bind:value={filter} />
<CloseSearchButton bind:filter />
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
<InlineButton on:click={handleRefreshFiles} title="Refresh files of selected application">
<FontIcon icon="icon refresh" />
</InlineButton>
</SearchBoxWrapper>
<WidgetsInnerContainer>
<AppObjectList
list={($files || []).map(file => ({
fileName: file.name,
folderName: folder,
fileType: file.type,
fileLabel: file.label,
}))}
groupFunc={data => APP_LABELS[data.fileType] || 'App config'}
module={appFileAppObject}
{filter}
/>
</WidgetsInnerContainer>

View File

@@ -1,39 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import AppObjectList from '../appobj/AppObjectList.svelte';
import * as appFolderAppObject from '../appobj/AppFolderAppObject.svelte';
import runCommand from '../commands/runCommand';
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import InlineButton from '../buttons/InlineButton.svelte';
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import SearchInput from '../elements/SearchInput.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { apiCall } from '../utility/api';
import { useAppFolders } from '../utility/metadataLoaders';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
let filter = '';
$: folders = useAppFolders();
const handleRefreshFolders = () => {
apiCall('apps/refresh-folders');
};
</script>
<SearchBoxWrapper>
<SearchInput placeholder="Search applications" bind:value={filter} />
<CloseSearchButton bind:filter />
<InlineButton on:click={() => runCommand('new.application')} title="Create new application">
<FontIcon icon="icon plus-thick" />
</InlineButton>
<InlineButton on:click={handleRefreshFolders} title="Refresh application list">
<FontIcon icon="icon refresh" />
</InlineButton>
</SearchBoxWrapper>
<WidgetsInnerContainer>
<AppObjectList list={_.sortBy($folders, 'name')} module={appFolderAppObject} {filter} />
</WidgetsInnerContainer>

View File

@@ -1,19 +0,0 @@
<script lang="ts">
import AppFilesList from './AppFilesList.svelte';
import WidgetColumnBar from './WidgetColumnBar.svelte';
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
import { useFavorites } from '../utility/metadataLoaders';
import AppFolderList from './AppFolderList.svelte';
</script>
<WidgetColumnBar>
<WidgetColumnBarItem title="Applications" name="apps" height="30%" storageName="appsWidget">
<AppFolderList />
</WidgetColumnBarItem>
<WidgetColumnBarItem title="Application files" name="files" storageName="appFilesWidget">
<AppFilesList />
</WidgetColumnBarItem>
</WidgetColumnBar>

View File

@@ -12,6 +12,7 @@
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
import { isProApp } from '../utility/proTools';
import InlineUploadButton from '../buttons/InlineUploadButton.svelte';
import { DATA_FOLDER_NAMES } from 'dbgate-tools';
let filter = '';
@@ -27,6 +28,7 @@
const dbCompareJobFiles = useFiles({ folder: 'dbcompare' });
const perspectiveFiles = useFiles({ folder: 'perspectives' });
const modelTransformFiles = useFiles({ folder: 'modtrans' });
const appFiles = useFiles({ folder: 'apps' });
$: files = [
...($sqlFiles || []),
@@ -41,32 +43,18 @@
...($modelTransformFiles || []),
...((isProApp() && $dataDeployJobFiles) || []),
...((isProApp() && $dbCompareJobFiles) || []),
...((isProApp() && $appFiles) || []),
];
function handleRefreshFiles() {
apiCall('files/refresh', {
folders: [
'sql',
'shell',
'markdown',
'charts',
'query',
'sqlite',
'diagrams',
'perspectives',
'impexp',
'modtrans',
'datadeploy',
'dbcompare',
],
folders: DATA_FOLDER_NAMES.map(folder => folder.name),
});
}
function dataFolderTitle(folder) {
if (folder == 'modtrans') return 'Model transforms';
if (folder == 'datadeploy') return 'Data deploy jobs';
if (folder == 'dbcompare') return 'Database compare jobs';
return _.startCase(folder);
const foundFolder = DATA_FOLDER_NAMES.find(f => f.name === folder);
return foundFolder ? foundFolder.label : _.startCase(folder);
}
async function handleUploadedFile(filePath, fileName) {

View File

@@ -17,11 +17,11 @@
import SearchInput from '../elements/SearchInput.svelte';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
import {
useAllApps,
useConnectionInfo,
useDatabaseInfo,
useDatabaseStatus,
useSchemaList,
useUsedApps,
} from '../utility/metadataLoaders';
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import AppObjectList from '../appobj/AppObjectList.svelte';
@@ -73,9 +73,8 @@
$: connection = useConnectionInfo({ conid });
$: driver = findEngineDriver($connection, $extensions);
$: apps = useUsedApps();
$: dbApps = filterAppsForDatabase($currentDatabase?.connection, $currentDatabase?.name, $apps || []);
$: apps = useAllApps();
$: appsForDb = filterAppsForDatabase($connection, database, $apps || [], $objects);
// $: console.log('OBJECTS', $objects);
@@ -87,13 +86,14 @@
['schemaName', 'pureName']
)
),
...dbApps.map(app =>
app.queries.map(query => ({
objectTypeField: 'queries',
pureName: query.name,
schemaName: app.name,
sql: query.sql,
}))
...appsForDb.map(app =>
Object.values(app.files || {})
.filter(x => x.type == 'query')
.map(query => ({
objectTypeField: 'queries',
pureName: query.label,
sql: query.sql,
}))
),
]);
@@ -281,7 +281,7 @@
>
<AppObjectList
list={objectList
.filter(x => ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
.filter(x => x.schemaName == null || ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
.map(x => ({ ...x, conid, database }))}
module={databaseObjectAppObject}
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}

View File

@@ -6,7 +6,6 @@
import PluginsWidget from './PluginsWidget.svelte';
import CellDataWidget from './CellDataWidget.svelte';
import HistoryWidget from './HistoryWidget.svelte';
import AppWidget from './AppWidget.svelte';
import AdminMenuWidget from './AdminMenuWidget.svelte';
import AdminPremiumPromoWidget from './AdminPremiumPromoWidget.svelte';
import PublicCloudWidget from './PublicCloudWidget.svelte';
@@ -14,8 +13,9 @@
import hasPermission from '../utility/hasPermission';
</script>
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
{#if hasPermission('widgets/database')}
<DatabaseWidget hidden={$visibleSelectedWidget != 'database'} />
{/if}
{#if $visibleSelectedWidget == 'file' && hasPermission('widgets/file')}
<FilesWidget />
{/if}
@@ -31,9 +31,6 @@
{#if $visibleSelectedWidget == 'cell-data' && hasPermission('widgets/cell-data')}
<CellDataWidget />
{/if}
{#if $visibleSelectedWidget == 'app' && hasPermission('widgets/app')}
<AppWidget />
{/if}
{#if $visibleSelectedWidget == 'admin' && hasPermission('widgets/admin')}
<AdminMenuWidget />
{/if}

View File

@@ -110,13 +110,6 @@
hasPermission('settings/change') && { command: 'settings.show' },
{ command: 'theme.changeTheme' },
hasPermission('settings/change') && { command: 'settings.commands' },
hasPermission('widgets/app') && {
text: 'View applications',
onClick: () => {
$selectedWidget = 'app';
$visibleWidgetSideBar = true;
},
},
hasPermission('widgets/plugins') && {
text: 'Manage plugins',
onClick: () => {