mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 11:56:00 +00:00
Merge branch 'develop'
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
import { subscribeConnectionPingers } from './utility/connectionsPinger';
|
||||
import { subscribePermissionCompiler } from './utility/hasPermission';
|
||||
import { apiCall } from './utility/api';
|
||||
import { getUsedApps } from './utility/metadataLoaders';
|
||||
|
||||
let loadedApi = false;
|
||||
|
||||
@@ -30,7 +31,8 @@
|
||||
const settings = await apiCall('config/get-settings');
|
||||
const connections = await apiCall('connections/list');
|
||||
const config = await apiCall('config/get');
|
||||
loadedApi = settings && connections && config;
|
||||
const apps = await getUsedApps();
|
||||
loadedApi = settings && connections && config && apps;
|
||||
|
||||
if (loadedApi) {
|
||||
subscribeApiDependendStores();
|
||||
|
||||
@@ -26,6 +26,12 @@
|
||||
$: currentThemeType = $currentThemeDefinition?.themeType == 'dark' ? 'theme-type-dark' : 'theme-type-light';
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
{#if $currentThemeDefinition?.themeCss}
|
||||
{@html `<style id="themePlugin">${$currentThemeDefinition?.themeCss}</style>`}
|
||||
{/if}
|
||||
</svelte:head>
|
||||
|
||||
<div
|
||||
class={`${$currentTheme} ${currentThemeType} root dbgate-screen`}
|
||||
use:dragDropFileTarget
|
||||
|
||||
119
packages/web/src/appobj/AppFileAppObject.svelte
Normal file
119
packages/web/src/appobj/AppFileAppObject.svelte
Normal file
@@ -0,0 +1,119 @@
|
||||
<script lang="ts" context="module">
|
||||
async function openTextFile(fileName, fileType, folderName, tabComponent, icon) {
|
||||
const connProps: any = {};
|
||||
let tooltip = undefined;
|
||||
|
||||
const resp = await apiCall('files/load', {
|
||||
folder: 'app:' + folderName,
|
||||
file: fileName + '.' + fileType,
|
||||
format: 'text',
|
||||
});
|
||||
|
||||
openNewTab(
|
||||
{
|
||||
title: fileName,
|
||||
icon,
|
||||
tabComponent,
|
||||
tooltip,
|
||||
props: {
|
||||
savedFile:fileName + '.' + fileType,
|
||||
savedFolder: 'app:' + folderName,
|
||||
savedFormat: 'text',
|
||||
appFolder: folderName,
|
||||
...connProps,
|
||||
},
|
||||
},
|
||||
{ editor: resp }
|
||||
);
|
||||
}
|
||||
|
||||
export const extractKey = data => data.fileName;
|
||||
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
|
||||
const APP_ICONS = {
|
||||
'config.json': 'img json',
|
||||
'command.sql': 'img app-command',
|
||||
'query.sql': 'img app-query',
|
||||
};
|
||||
|
||||
function getAppIcon(data) {
|
||||
return APP_ICONS[data.fileType];
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { currentDatabase, currentDatabase } from '../stores';
|
||||
|
||||
export let data;
|
||||
|
||||
const handleRename = () => {
|
||||
showModal(InputTextModal, {
|
||||
value: data.fileName,
|
||||
label: 'New file name',
|
||||
header: 'Rename file',
|
||||
onConfirm: newFile => {
|
||||
apiCall('apps/rename-file', {
|
||||
file: data.fileName,
|
||||
folder: data.folderName,
|
||||
fileType: data.fileType,
|
||||
newFile,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = () => {
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really delete file ${data.fileName}?`,
|
||||
onConfirm: () => {
|
||||
apiCall('apps/delete-file', {
|
||||
file: data.fileName,
|
||||
folder: data.folderName,
|
||||
fileType: data.fileType,
|
||||
});
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleClick = () => {
|
||||
if (data.fileType.endsWith('.sql')) {
|
||||
handleOpenSqlFile();
|
||||
}
|
||||
if (data.fileType.endsWith('.json')) {
|
||||
handleOpenJsonFile();
|
||||
}
|
||||
};
|
||||
const handleOpenSqlFile = () => {
|
||||
openTextFile(data.fileName, data.fileType, data.folderName, 'QueryTab', 'img sql-file');
|
||||
};
|
||||
const handleOpenJsonFile = () => {
|
||||
openTextFile(data.fileName, data.fileType, data.folderName, 'JsonEditorTab', 'img json');
|
||||
};
|
||||
|
||||
function createMenu() {
|
||||
return [
|
||||
{ text: 'Delete', onClick: handleDelete },
|
||||
{ text: 'Rename', onClick: handleRename },
|
||||
data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile },
|
||||
data.fileType.endsWith('.json') && { text: 'Open JSON', onClick: handleOpenJsonFile },
|
||||
|
||||
// data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
{...$$restProps}
|
||||
{data}
|
||||
title={data.fileLabel}
|
||||
icon={getAppIcon(data)}
|
||||
menu={createMenu}
|
||||
on:click={handleClick}
|
||||
/>
|
||||
96
packages/web/src/appobj/AppFolderAppObject.svelte
Normal file
96
packages/web/src/appobj/AppFolderAppObject.svelte
Normal file
@@ -0,0 +1,96 @@
|
||||
<script lang="ts" context="module">
|
||||
export const extractKey = data => data.name;
|
||||
export const createMatcher = data => filter => filterName(filter, data.name);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _, { find } from 'lodash';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
|
||||
import { currentApplication, currentDatabase } from '../stores';
|
||||
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { useConnectionList } from '../utility/metadataLoaders';
|
||||
|
||||
export let data;
|
||||
|
||||
$: connections = useConnectionList();
|
||||
|
||||
const handleDelete = () => {
|
||||
showModal(ConfirmModal, {
|
||||
message: `Really delete application ${data.name}?`,
|
||||
onConfirm: () => {
|
||||
apiCall('apps/delete-folder', { folder: data.name });
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const handleRename = () => {
|
||||
const { name } = data;
|
||||
|
||||
showModal(InputTextModal, {
|
||||
value: name,
|
||||
label: 'New application name',
|
||||
header: 'Rename application',
|
||||
onConfirm: async newFolder => {
|
||||
await apiCall('apps/rename-folder', {
|
||||
folder: data.name,
|
||||
newFolder: newFolder,
|
||||
});
|
||||
if ($currentApplication == data.name) {
|
||||
$currentApplication = newFolder;
|
||||
}
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
function setOnCurrentDb(value) {
|
||||
apiCall('connections/update-database', {
|
||||
conid: $currentDatabase?.connection?._id,
|
||||
database: $currentDatabase?.name,
|
||||
values: {
|
||||
[`useApp:${data.name}`]: value,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createMenu() {
|
||||
return [
|
||||
{ text: 'Delete', onClick: handleDelete },
|
||||
{ text: 'Rename', onClick: handleRename },
|
||||
|
||||
$currentDatabase && [
|
||||
!isOnCurrentDb($currentDatabase, $connections) && {
|
||||
text: 'Enable on current database',
|
||||
onClick: () => setOnCurrentDb(true),
|
||||
},
|
||||
isOnCurrentDb($currentDatabase, $connections) && {
|
||||
text: 'Disable on current database',
|
||||
onClick: () => setOnCurrentDb(false),
|
||||
},
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
function isOnCurrentDb(currentDb, connections) {
|
||||
const conn = connections.find(x => x._id == currentDb?.connection?._id);
|
||||
const db = conn?.databases?.find(x => x.name == currentDb?.name);
|
||||
return db && db[`useApp:${data.name}`];
|
||||
}
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
{...$$restProps}
|
||||
{data}
|
||||
title={data.name}
|
||||
icon={'img app'}
|
||||
statusIcon={isOnCurrentDb($currentDatabase, $connections) ? 'icon check' : null}
|
||||
statusTitle={`Application ${data.name} is used for database ${$currentDatabase?.name}`}
|
||||
isBold={data.name == $currentApplication}
|
||||
on:click={() => ($currentApplication = data.name)}
|
||||
menu={createMenu}
|
||||
/>
|
||||
@@ -81,7 +81,7 @@
|
||||
markArchiveFileAsDataSheet,
|
||||
markArchiveFileAsReadonly,
|
||||
} from '../utility/archiveTools';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
export let data;
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getDatabaseList } from '../utility/metadataLoaders';
|
||||
import { getDatabaseList, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
value: 'newdb',
|
||||
label: 'Database name',
|
||||
onConfirm: name =>
|
||||
apiCall('server-connections/create-database', {
|
||||
apiCall('server-connections/create-database', {
|
||||
conid: data._id,
|
||||
name,
|
||||
}),
|
||||
@@ -153,7 +153,7 @@
|
||||
],
|
||||
data.singleDatabase && [
|
||||
{ divider: true },
|
||||
getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase),
|
||||
getDatabaseMenuItems(data, data.defaultDatabase, $extensions, $currentDatabase, $apps),
|
||||
],
|
||||
];
|
||||
};
|
||||
@@ -186,6 +186,8 @@
|
||||
statusTitle = null;
|
||||
}
|
||||
}
|
||||
|
||||
$: apps = useUsedApps();
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<script lang="ts" context="module">
|
||||
export const extractKey = props => props.name;
|
||||
|
||||
export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase) {
|
||||
export function getDatabaseMenuItems(connection, name, $extensions, $currentDatabase, $apps) {
|
||||
const apps = filterAppsForDatabase(connection, name, $apps);
|
||||
const handleNewQuery = () => {
|
||||
const tooltip = `${getConnectionLabel(connection)}\n${name}`;
|
||||
openNewTab({
|
||||
@@ -157,8 +158,20 @@
|
||||
openJsonDocument(db, name);
|
||||
};
|
||||
|
||||
async function handleConfirmSql(sql) {
|
||||
const resp = await apiCall('database-connections/run-script', { conid: connection._id, database: name, sql });
|
||||
const { errorMessage } = resp || {};
|
||||
if (errorMessage) {
|
||||
showModal(ErrorMessageModal, { title: 'Error when executing script', message: errorMessage });
|
||||
} else {
|
||||
showSnackbarSuccess('Saved to database');
|
||||
}
|
||||
}
|
||||
|
||||
const driver = findEngineDriver(connection, getExtensions());
|
||||
|
||||
const commands = _.flatten((apps || []).map(x => x.commands || []));
|
||||
|
||||
return [
|
||||
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
|
||||
!driver?.dialect?.nosql && { onClick: handleNewTable, text: 'New table' },
|
||||
@@ -180,6 +193,20 @@
|
||||
|
||||
_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
|
||||
_.get($currentDatabase, 'name') == name && { onClick: handleDisconnect, text: 'Disconnect' },
|
||||
|
||||
commands.length > 0 && [
|
||||
{ divider: true },
|
||||
commands.map((cmd: any) => ({
|
||||
text: cmd.name,
|
||||
onClick: () => {
|
||||
showModal(ConfirmSqlModal, {
|
||||
sql: cmd.sql,
|
||||
onConfirm: () => handleConfirmSql(cmd.sql),
|
||||
engine: driver.engine,
|
||||
});
|
||||
},
|
||||
})),
|
||||
],
|
||||
];
|
||||
}
|
||||
</script>
|
||||
@@ -207,18 +234,22 @@
|
||||
import { showSnackbarSuccess } from '../utility/snackbar';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { getDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { getDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import ConfirmSqlModal from '../modals/ConfirmSqlModal.svelte';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
function createMenu() {
|
||||
return getDatabaseMenuItems(data.connection, data.name, $extensions, $currentDatabase);
|
||||
return getDatabaseMenuItems(data.connection, data.name, $extensions, $currentDatabase, $apps);
|
||||
}
|
||||
|
||||
$: isPinned = !!$pinnedDatabases.find(x => x.name == data.name && x.connection?._id == data.connection?._id);
|
||||
$: apps = useUsedApps();
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
|
||||
@@ -30,7 +30,7 @@ import runCommand from './runCommand';
|
||||
function themeCommand(theme: ThemeDefinition) {
|
||||
return {
|
||||
text: theme.themeName,
|
||||
onClick: () => currentTheme.set(theme.className),
|
||||
onClick: () => currentTheme.set(theme.themeClassName),
|
||||
// onPreview: () => {
|
||||
// const old = get(currentTheme);
|
||||
// currentTheme.set(css);
|
||||
@@ -128,6 +128,23 @@ registerCommand({
|
||||
},
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'new.application',
|
||||
category: 'New',
|
||||
icon: 'img app',
|
||||
name: 'Application',
|
||||
onClick: () => {
|
||||
showModal(InputTextModal, {
|
||||
value: '',
|
||||
label: 'New application name',
|
||||
header: 'Create application',
|
||||
onConfirm: async folder => {
|
||||
apiCall('apps/create-folder', { folder });
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'new.table',
|
||||
category: 'New',
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
import { isTypeDateTime } from 'dbgate-tools';
|
||||
import { openDatabaseObjectDetail } from '../appobj/DatabaseObjectAppObject.svelte';
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import VirtualForeignKeyEditorModal from '../tableeditor/VirtualForeignKeyEditorModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
|
||||
export let column;
|
||||
export let conid = undefined;
|
||||
@@ -14,6 +16,7 @@
|
||||
export let setSort;
|
||||
export let grouping = undefined;
|
||||
export let order = undefined;
|
||||
export let allowDefineVirtualReferences = false;
|
||||
export let setGrouping;
|
||||
|
||||
const openReferencedTable = () => {
|
||||
@@ -26,6 +29,16 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleDefineVirtualForeignKey = () => {
|
||||
showModal(VirtualForeignKeyEditorModal, {
|
||||
schemaName: column.schemaName,
|
||||
pureName: column.pureName,
|
||||
conid,
|
||||
database,
|
||||
columnName: column.columnName,
|
||||
});
|
||||
};
|
||||
|
||||
function getMenu() {
|
||||
return [
|
||||
setSort && { onClick: () => setSort('ASC'), text: 'Sort ascending' },
|
||||
@@ -49,6 +62,11 @@
|
||||
{ onClick: () => setGrouping('GROUP:MONTH'), text: 'Group by MONTH' },
|
||||
{ onClick: () => setGrouping('GROUP:DAY'), text: 'Group by DAY' },
|
||||
],
|
||||
|
||||
allowDefineVirtualReferences && [
|
||||
{ divider: true },
|
||||
{ onClick: handleDefineVirtualForeignKey, text: 'Define virtual foreign key' },
|
||||
],
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -303,6 +303,7 @@
|
||||
export let errorMessage = undefined;
|
||||
export let pureName = undefined;
|
||||
export let schemaName = undefined;
|
||||
export let allowDefineVirtualReferences = false;
|
||||
|
||||
export let isLoadedAll;
|
||||
export let loadedTime;
|
||||
@@ -1425,6 +1426,7 @@
|
||||
}}
|
||||
setGrouping={display.groupable ? groupFunc => display.setGrouping(col.uniqueName, groupFunc) : null}
|
||||
grouping={display.getGrouping(col.uniqueName)}
|
||||
{allowDefineVirtualReferences}
|
||||
/>
|
||||
</td>
|
||||
{/each}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
TableGridDisplay,
|
||||
} from 'dbgate-datalib';
|
||||
import { getFilterValueExpression } from 'dbgate-filterparser';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
import { extendDatabaseInfoFromApps, findEngineDriver } from 'dbgate-tools';
|
||||
import _ from 'lodash';
|
||||
import { writable } from 'svelte/store';
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
@@ -16,9 +16,11 @@
|
||||
|
||||
import {
|
||||
useConnectionInfo,
|
||||
useConnectionList,
|
||||
useDatabaseInfo,
|
||||
useDatabaseServerVersion,
|
||||
useServerVersion,
|
||||
useUsedApps,
|
||||
} from '../utility/metadataLoaders';
|
||||
|
||||
import DataGrid from './DataGrid.svelte';
|
||||
@@ -46,6 +48,9 @@
|
||||
$: connection = useConnectionInfo({ conid });
|
||||
$: dbinfo = useDatabaseInfo({ conid, database });
|
||||
$: serverVersion = useDatabaseServerVersion({ conid, database });
|
||||
$: apps = useUsedApps();
|
||||
$: extendedDbInfo = extendDatabaseInfoFromApps($dbinfo, $apps);
|
||||
$: connections = useConnectionList();
|
||||
|
||||
// $: console.log('serverVersion', $serverVersion);
|
||||
|
||||
@@ -64,10 +69,10 @@
|
||||
setConfig,
|
||||
cache,
|
||||
setCache,
|
||||
$dbinfo,
|
||||
extendedDbInfo,
|
||||
{ showHintColumns: getBoolSettingsValue('dataGrid.showHintColumns', true) },
|
||||
$serverVersion,
|
||||
table => getDictionaryDescription(table, conid, database)
|
||||
table => getDictionaryDescription(table, conid, database, $apps, $connections)
|
||||
)
|
||||
: null;
|
||||
|
||||
@@ -80,10 +85,10 @@
|
||||
setConfig,
|
||||
cache,
|
||||
setCache,
|
||||
$dbinfo,
|
||||
extendedDbInfo,
|
||||
{ showHintColumns: getBoolSettingsValue('dataGrid.showHintColumns', true) },
|
||||
$serverVersion,
|
||||
table => getDictionaryDescription(table, conid, database)
|
||||
table => getDictionaryDescription(table, conid, database, $apps, $connections)
|
||||
)
|
||||
: null;
|
||||
|
||||
@@ -159,6 +164,7 @@
|
||||
macroCondition={macro => macro.type == 'transformValue'}
|
||||
onReferenceSourceChanged={reference ? handleReferenceSourceChanged : null}
|
||||
multipleGridsOnTab={multipleGridsOnTab || !!reference}
|
||||
allowDefineVirtualReferences
|
||||
onReferenceClick={value => {
|
||||
if (value && value.referenceId && reference && reference.referenceId == value.referenceId) {
|
||||
// reference not changed
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
import DesignerTable from './DesignerTable.svelte';
|
||||
import { isConnectedByReference } from './designerTools';
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import { getTableInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { getTableInfo, useDatabaseInfo, useUsedApps } from '../utility/metadataLoaders';
|
||||
import cleanupDesignColumns from './cleanupDesignColumns';
|
||||
import _ from 'lodash';
|
||||
import { writable } from 'svelte/store';
|
||||
@@ -46,6 +46,7 @@
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import ChooseColorModal from '../modals/ChooseColorModal.svelte';
|
||||
import { currentThemeDefinition } from '../stores';
|
||||
import { extendDatabaseInfoFromApps } from 'dbgate-tools';
|
||||
|
||||
export let value;
|
||||
export let onChange;
|
||||
@@ -67,10 +68,12 @@
|
||||
const targetDragColumn$ = writable(null);
|
||||
|
||||
const dbInfo = settings?.updateFromDbInfo ? useDatabaseInfo({ conid, database }) : null;
|
||||
$: dbInfoExtended = $dbInfo ? extendDatabaseInfoFromApps($dbInfo, $apps) : null;
|
||||
|
||||
$: tables = value?.tables as any[];
|
||||
$: references = value?.references as any[];
|
||||
$: zoomKoef = settings?.customizeStyle && value?.style?.zoomKoef ? value?.style?.zoomKoef : 1;
|
||||
$: apps = useUsedApps();
|
||||
|
||||
$: isMultipleTableSelection = tables.filter(x => x.isSelectedTable).length >= 2;
|
||||
|
||||
@@ -94,8 +97,8 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if (dbInfo) {
|
||||
updateFromDbInfo($dbInfo);
|
||||
if (dbInfoExtended) {
|
||||
updateFromDbInfo(dbInfoExtended as any);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,13 +107,13 @@
|
||||
}
|
||||
|
||||
$: {
|
||||
if (dbInfo && value?.autoLayout) {
|
||||
performAutoActions($dbInfo);
|
||||
if (dbInfoExtended && value?.autoLayout) {
|
||||
performAutoActions(dbInfoExtended);
|
||||
}
|
||||
}
|
||||
|
||||
function updateFromDbInfo(db = 'auto') {
|
||||
if (db == 'auto' && dbInfo) db = $dbInfo;
|
||||
if (db == 'auto' && dbInfo) db = dbInfoExtended as any;
|
||||
if (!settings?.updateFromDbInfo || !db) return;
|
||||
|
||||
onChange(current => {
|
||||
@@ -372,8 +375,8 @@
|
||||
};
|
||||
|
||||
const handleAddTableReferences = async table => {
|
||||
if (!dbInfo) return;
|
||||
const db = $dbInfo;
|
||||
if (!dbInfoExtended) return;
|
||||
const db = dbInfoExtended;
|
||||
if (!db) return;
|
||||
callChange(current => {
|
||||
return getTablesWithReferences(db, table, current);
|
||||
@@ -692,13 +695,17 @@
|
||||
if (css) css += '\n';
|
||||
css += cssItem;
|
||||
}
|
||||
if ($currentThemeDefinition?.themeCss) {
|
||||
if (css) css += '\n';
|
||||
css += $currentThemeDefinition?.themeCss;
|
||||
}
|
||||
saveFileToDisk(async filePath => {
|
||||
await apiCall('files/export-diagram', {
|
||||
filePath,
|
||||
html: domCanvas.outerHTML,
|
||||
css,
|
||||
themeType: $currentThemeDefinition?.themeType,
|
||||
themeClassName: $currentThemeDefinition?.className,
|
||||
themeClassName: $currentThemeDefinition?.themeClassName,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { currentThemeDefinition } from '../stores';
|
||||
import VirtualForeignKeyEditorModal from '../tableeditor/VirtualForeignKeyEditorModal.svelte';
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
import moveDrag from '../utility/moveDrag';
|
||||
import ColumnLine from './ColumnLine.svelte';
|
||||
@@ -166,6 +167,15 @@
|
||||
});
|
||||
};
|
||||
|
||||
const handleDefineVirtualForeignKey = table => {
|
||||
showModal(VirtualForeignKeyEditorModal, {
|
||||
schemaName: table.schemaName,
|
||||
pureName: table.pureName,
|
||||
conid,
|
||||
database,
|
||||
});
|
||||
};
|
||||
|
||||
function createMenu() {
|
||||
return [
|
||||
{ text: 'Remove', onClick: () => onRemoveTable({ designerId }) },
|
||||
@@ -185,6 +195,11 @@
|
||||
settings?.allowAddAllReferences &&
|
||||
!isMultipleTableSelection && { text: 'Add references', onClick: () => onAddAllReferences(table) },
|
||||
settings?.allowChangeColor && { text: 'Change color', onClick: () => onChangeTableColor(table) },
|
||||
settings?.allowDefineVirtualReferences &&
|
||||
!isMultipleTableSelection && {
|
||||
text: 'Define virtual foreign key',
|
||||
onClick: () => handleDefineVirtualForeignKey(table),
|
||||
},
|
||||
settings?.appendTableSystemMenu &&
|
||||
!isMultipleTableSelection && [{ divider: true }, createDatabaseObjectMenu({ ...table, conid, database })],
|
||||
];
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
allowChangeColor: true,
|
||||
appendTableSystemMenu: true,
|
||||
customizeStyle: true,
|
||||
allowDefineVirtualReferences: true,
|
||||
}}
|
||||
referenceComponent={DiagramDesignerReference}
|
||||
/>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
allowChangeColor: false,
|
||||
appendTableSystemMenu: false,
|
||||
customizeStyle: false,
|
||||
allowDefineVirtualReferences: false,
|
||||
}}
|
||||
referenceComponent={QueryDesignerReference}
|
||||
/>
|
||||
|
||||
@@ -9,11 +9,13 @@
|
||||
export let name;
|
||||
export let options;
|
||||
export let isClearable = false;
|
||||
export let selectFieldComponent = SelectField;
|
||||
|
||||
const { values, setFieldValue } = getFormContext();
|
||||
</script>
|
||||
|
||||
<SelectField
|
||||
<svelte:component
|
||||
this={selectFieldComponent}
|
||||
{...$$restProps}
|
||||
value={$values && $values[name]}
|
||||
options={_.compact(options)}
|
||||
|
||||
48
packages/web/src/forms/TargetApplicationSelect.svelte
Normal file
48
packages/web/src/forms/TargetApplicationSelect.svelte
Normal file
@@ -0,0 +1,48 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import { currentDatabase } from '../stores';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
import { useAppFolders, useUsedApps } from '../utility/metadataLoaders';
|
||||
|
||||
export let value = '#new';
|
||||
export let disableInitialize = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
$: appFolders = useAppFolders();
|
||||
$: usedApps = useUsedApps();
|
||||
|
||||
$: {
|
||||
if (!disableInitialize && value == '#new' && $currentDatabase) {
|
||||
const filtered = filterAppsForDatabase($currentDatabase.connection, $currentDatabase.name, $usedApps || []);
|
||||
const common = _.intersection(
|
||||
($appFolders || []).map(x => x.name),
|
||||
filtered.map(x => x.name)
|
||||
);
|
||||
if (common.length > 0) {
|
||||
value = common[0] as string;
|
||||
dispatch('change', value);
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<SelectField
|
||||
isNative
|
||||
{...$$restProps}
|
||||
{value}
|
||||
on:change={e => {
|
||||
value = e.detail;
|
||||
dispatch('change', value);
|
||||
}}
|
||||
options={[
|
||||
{ label: '(New application linked to current DB)', value: '#new' },
|
||||
...($appFolders || []).map(app => ({
|
||||
label: app.name,
|
||||
value: app.name,
|
||||
})),
|
||||
]}
|
||||
/>
|
||||
@@ -26,6 +26,7 @@
|
||||
'icon version': 'mdi mdi-ticket-confirmation',
|
||||
'icon pin': 'mdi mdi-pin',
|
||||
'icon arrange': 'mdi mdi-arrange-send-to-back',
|
||||
'icon app': 'mdi mdi-layers-triple',
|
||||
|
||||
'icon columns': 'mdi mdi-view-column',
|
||||
'icon columns-outline': 'mdi mdi-view-column-outline',
|
||||
@@ -126,6 +127,9 @@
|
||||
'img diagram': 'mdi mdi-graph color-icon-blue',
|
||||
'img yaml': 'mdi mdi-code-brackets color-icon-red',
|
||||
'img compare': 'mdi mdi-compare color-icon-red',
|
||||
'img app': 'mdi mdi-layers-triple color-icon-magenta',
|
||||
'img app-command': 'mdi mdi-flash color-icon-green',
|
||||
'img app-query': 'mdi mdi-view-comfy color-icon-magenta',
|
||||
|
||||
'img add': 'mdi mdi-plus-circle color-icon-green',
|
||||
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script lang="ts">
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import _ from 'lodash';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||
import ModalBase from './ModalBase.svelte';
|
||||
import { closeCurrentModal } from './modalTools';
|
||||
import { useTableInfo } from '../utility/metadataLoaders';
|
||||
import { useAppFolders, useConnectionList, useTableInfo, useUsedApps } from '../utility/metadataLoaders';
|
||||
import TableControl from '../elements/TableControl.svelte';
|
||||
import TextField from '../forms/TextField.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
@@ -19,6 +20,10 @@
|
||||
} from '../utility/dictionaryDescriptionTools';
|
||||
import { includes } from 'lodash';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import TargetApplicationSelect from '../forms/TargetApplicationSelect.svelte';
|
||||
import { currentDatabase } from '../stores';
|
||||
import { filterAppsForDatabase } from '../utility/appTools';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -28,18 +33,41 @@
|
||||
|
||||
$: tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
||||
|
||||
$: descriptionInfo = getDictionaryDescription($tableInfo, conid, database, true);
|
||||
$: apps = useUsedApps();
|
||||
$: appFolders = useAppFolders();
|
||||
$: connections = useConnectionList();
|
||||
|
||||
const values = writable({});
|
||||
$: descriptionInfo = getDictionaryDescription($tableInfo, conid, database, $apps, $connections, true);
|
||||
|
||||
const values = writable({ targetApplication: '#new' } as any);
|
||||
|
||||
function initValues(descriptionInfo) {
|
||||
$values = {
|
||||
targetApplication: $values.targetApplication,
|
||||
columns: descriptionInfo.expression,
|
||||
delimiter: descriptionInfo.delimiter,
|
||||
};
|
||||
}
|
||||
|
||||
$: if (descriptionInfo) initValues(descriptionInfo);
|
||||
$: {
|
||||
if (descriptionInfo) initValues(descriptionInfo);
|
||||
}
|
||||
|
||||
$: {
|
||||
if ($values.targetApplication == '#new' && $currentDatabase) {
|
||||
const filtered = filterAppsForDatabase($currentDatabase.connection, $currentDatabase.name, $apps || []);
|
||||
const common = _.intersection(
|
||||
($appFolders || []).map(x => x.name),
|
||||
filtered.map(x => x.name)
|
||||
);
|
||||
if (common.length > 0) {
|
||||
$values = {
|
||||
...$values,
|
||||
targetApplication: common[0],
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormProviderCore {values}>
|
||||
@@ -75,7 +103,14 @@
|
||||
|
||||
<FormTextField name="delimiter" label="Delimiter" />
|
||||
|
||||
<FormCheckboxField name="useForAllDatabases" label="Use for all databases" />
|
||||
<FormSelectField
|
||||
label="Target application"
|
||||
name="targetApplication"
|
||||
disableInitialize
|
||||
selectFieldComponent={TargetApplicationSelect}
|
||||
/>
|
||||
|
||||
<!-- <FormCheckboxField name="useForAllDatabases" label="Use for all databases" /> -->
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit
|
||||
@@ -89,7 +124,7 @@
|
||||
database,
|
||||
$values.columns,
|
||||
$values.delimiter,
|
||||
$values.useForAllDatabases
|
||||
$values.targetApplication
|
||||
);
|
||||
onConfirm();
|
||||
}}
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import DefineDictionaryDescriptionModal from './DefineDictionaryDescriptionModal.svelte';
|
||||
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
|
||||
import { getTableInfo } from '../utility/metadataLoaders';
|
||||
import { getTableInfo, useConnectionList, useUsedApps } from '../utility/metadataLoaders';
|
||||
import { getDictionaryDescription } from '../utility/dictionaryDescriptionTools';
|
||||
import { onMount } from 'svelte';
|
||||
import { dumpSqlSelect } from 'dbgate-sqltree';
|
||||
@@ -33,6 +33,9 @@
|
||||
|
||||
let checkedKeys = [];
|
||||
|
||||
$: apps = useUsedApps();
|
||||
$: connections = useConnectionList();
|
||||
|
||||
function defineDescription() {
|
||||
showModal(DefineDictionaryDescriptionModal, {
|
||||
conid,
|
||||
@@ -45,7 +48,7 @@
|
||||
|
||||
async function reload() {
|
||||
tableInfo = await getTableInfo({ conid, database, schemaName, pureName });
|
||||
description = getDictionaryDescription(tableInfo, conid, database);
|
||||
description = getDictionaryDescription(tableInfo, conid, database, $apps, $connections);
|
||||
|
||||
if (!tableInfo || !description) return;
|
||||
if (tableInfo?.primaryKey?.columns?.length != 1) return;
|
||||
@@ -112,6 +115,8 @@
|
||||
|
||||
$: {
|
||||
search;
|
||||
$apps;
|
||||
$connections;
|
||||
reload();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script context="module">
|
||||
export const className = 'theme-dark';
|
||||
export const themeClassName = 'theme-dark';
|
||||
export const themeName = 'Dark';
|
||||
export const themeType = 'dark';
|
||||
</script>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script context="module">
|
||||
export const className = 'theme-light';
|
||||
export const themeClassName = 'theme-light';
|
||||
export const themeName = 'Light';
|
||||
export const themeType = 'light';
|
||||
</script>
|
||||
|
||||
@@ -64,6 +64,7 @@ export const openedModals = writable([]);
|
||||
export const openedSnackbars = writable([]);
|
||||
export const nullStore = readable(null, () => {});
|
||||
export const currentArchive = writableWithStorage('default', 'currentArchive');
|
||||
export const currentApplication = writableWithStorage(null, 'currentApplication');
|
||||
export const isFileDragActive = writable(false);
|
||||
export const selectedCellsCallback = writable(null);
|
||||
export const loadingPluginStore = writable({
|
||||
@@ -72,7 +73,7 @@ export const loadingPluginStore = writable({
|
||||
});
|
||||
|
||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||
$extensions.themes.find(x => x.className == $currentTheme)
|
||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
||||
);
|
||||
|
||||
subscribeCssVariable(selectedWidget, x => (x ? 1 : 0), '--dim-visible-left-panel');
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
|
||||
isNative
|
||||
notSelected
|
||||
options={(dbInfo?.tables || []).map(tbl => ({
|
||||
options={_.sortBy(dbInfo?.tables || [], ['schemaName', 'pureName']).map(tbl => ({
|
||||
label: fullNameToLabel(tbl),
|
||||
value: fullNameToString(tbl),
|
||||
}))}
|
||||
|
||||
190
packages/web/src/tableeditor/VirtualForeignKeyEditorModal.svelte
Normal file
190
packages/web/src/tableeditor/VirtualForeignKeyEditorModal.svelte
Normal file
@@ -0,0 +1,190 @@
|
||||
<script lang="ts">
|
||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import ModalBase from '../modals/ModalBase.svelte';
|
||||
import { closeCurrentModal } from '../modals/modalTools';
|
||||
import { fullNameFromString, fullNameToLabel, fullNameToString } from 'dbgate-tools';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import _ from 'lodash';
|
||||
import { useDatabaseInfo, useTableInfo } from '../utility/metadataLoaders';
|
||||
import { onMount } from 'svelte';
|
||||
import TargetApplicationSelect from '../forms/TargetApplicationSelect.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { saveDbToApp } from '../utility/appTools';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
export let schemaName;
|
||||
export let pureName;
|
||||
export let columnName;
|
||||
|
||||
let dstApp;
|
||||
|
||||
const dbInfo = useDatabaseInfo({ conid, database });
|
||||
const tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
||||
|
||||
let columns = [];
|
||||
let refTableName = null;
|
||||
let refSchemaName = null;
|
||||
|
||||
$: refTableInfo = $dbInfo?.tables?.find(x => x.pureName == refTableName && x.schemaName == refSchemaName);
|
||||
// $dbInfo?.views?.find(x => x.pureName == refTableName && x.schemaName == refSchemaName);
|
||||
|
||||
onMount(() => {
|
||||
if (columnName) {
|
||||
columns = [
|
||||
...columns,
|
||||
{
|
||||
columnName,
|
||||
},
|
||||
];
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">Virtual foreign key</svelte:fragment>
|
||||
|
||||
<div class="largeFormMarker">
|
||||
<div class="row">
|
||||
<div class="label col-3">Referenced table</div>
|
||||
<div class="col-9">
|
||||
<SelectField
|
||||
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
|
||||
isNative
|
||||
notSelected
|
||||
options={[
|
||||
..._.sortBy($dbInfo?.tables || [], ['schemaName', 'pureName']),
|
||||
// ..._.sortBy($dbInfo?.views || [], ['schemaName', 'pureName']),
|
||||
].map(tbl => ({
|
||||
label: fullNameToLabel(tbl),
|
||||
value: fullNameToString(tbl),
|
||||
}))}
|
||||
on:change={e => {
|
||||
if (e.detail) {
|
||||
const name = fullNameFromString(e.detail);
|
||||
refTableName = name.pureName;
|
||||
refSchemaName = name.schemaName;
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-5 mr-1">
|
||||
Base column - {$tableInfo.pureName}
|
||||
</div>
|
||||
<div class="col-5 ml-1">
|
||||
Ref column - {refTableName || '(table not set)'}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{#each columns as column, index}
|
||||
<div class="row">
|
||||
<div class="col-5 mr-1">
|
||||
{#key column.columnName}
|
||||
<SelectField
|
||||
value={column.columnName}
|
||||
isNative
|
||||
notSelected
|
||||
options={$tableInfo.columns.map(col => ({
|
||||
label: col.columnName,
|
||||
value: col.columnName,
|
||||
}))}
|
||||
on:change={e => {
|
||||
if (e.detail) {
|
||||
columns = columns.map((col, i) => (i == index ? { ...col, columnName: e.detail } : col));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
<div class="col-5 ml-1">
|
||||
{#key column.refColumnName}
|
||||
<SelectField
|
||||
value={column.refColumnName}
|
||||
isNative
|
||||
notSelected
|
||||
options={(refTableInfo?.columns || []).map(col => ({
|
||||
label: col.columnName,
|
||||
value: col.columnName,
|
||||
}))}
|
||||
on:change={e => {
|
||||
if (e.detail) {
|
||||
columns = columns.map((col, i) => (i == index ? { ...col, refColumnName: e.detail } : col));
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{/key}
|
||||
</div>
|
||||
<div class="col-2 button">
|
||||
<FormStyledButton
|
||||
value="Delete"
|
||||
on:click={e => {
|
||||
const x = [...columns];
|
||||
x.splice(index, 1);
|
||||
columns = x;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="Add column"
|
||||
on:click={() => {
|
||||
columns = [...columns, {}];
|
||||
}}
|
||||
/>
|
||||
|
||||
<div class="row">
|
||||
<div class="label col-3">Target application</div>
|
||||
<div class="col-9">
|
||||
<TargetApplicationSelect bind:value={dstApp} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<FormSubmit
|
||||
value={'Save'}
|
||||
on:click={async () => {
|
||||
const appFolder = await saveDbToApp(conid, database, dstApp);
|
||||
await apiCall('apps/save-virtual-reference', {
|
||||
appFolder,
|
||||
schemaName,
|
||||
pureName,
|
||||
refSchemaName,
|
||||
refTableName,
|
||||
columns,
|
||||
});
|
||||
closeCurrentModal();
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
</svelte:fragment>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
|
||||
<style>
|
||||
.row {
|
||||
margin: var(--dim-large-form-margin);
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.row .label {
|
||||
white-space: nowrap;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.button {
|
||||
align-self: center;
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
83
packages/web/src/tabs/JsonEditorTab.svelte
Normal file
83
packages/web/src/tabs/JsonEditorTab.svelte
Normal file
@@ -0,0 +1,83 @@
|
||||
<script lang="ts" context="module">
|
||||
const getCurrentEditor = () => getActiveComponent('JsonEditorTab');
|
||||
|
||||
registerFileCommands({
|
||||
idPrefix: 'json',
|
||||
category: 'Json',
|
||||
getCurrentEditor,
|
||||
folder: 'yaml',
|
||||
format: 'text',
|
||||
fileExtension: 'json',
|
||||
|
||||
toggleComment: true,
|
||||
findReplace: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext } from 'svelte';
|
||||
import { registerFileCommands } from '../commands/stdCommands';
|
||||
|
||||
import AceEditor from '../query/AceEditor.svelte';
|
||||
import useEditorData from '../query/useEditorData';
|
||||
import invalidateCommands from '../commands/invalidateCommands';
|
||||
import createActivator, { getActiveComponent } from '../utility/createActivator';
|
||||
|
||||
export let tabid;
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
|
||||
export const activator = createActivator('JsonEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
|
||||
$: if ($tabVisible && domEditor) {
|
||||
domEditor?.getEditor()?.focus();
|
||||
}
|
||||
|
||||
export function getData() {
|
||||
return $editorState.value || '';
|
||||
}
|
||||
|
||||
export function toggleComment() {
|
||||
domEditor.getEditor().execCommand('togglecomment');
|
||||
}
|
||||
|
||||
export function find() {
|
||||
domEditor.getEditor().execCommand('find');
|
||||
}
|
||||
|
||||
export function replace() {
|
||||
domEditor.getEditor().execCommand('replace');
|
||||
}
|
||||
|
||||
export function getTabId() {
|
||||
return tabid;
|
||||
}
|
||||
|
||||
const { editorState, editorValue, setEditorData, saveToStorage } = useEditorData({ tabid });
|
||||
|
||||
function createMenu() {
|
||||
return [
|
||||
{ command: 'json.toggleComment' },
|
||||
{ divider: true },
|
||||
{ command: 'json.save' },
|
||||
{ command: 'json.saveAs' },
|
||||
{ divider: true },
|
||||
{ command: 'json.find' },
|
||||
{ command: 'json.replace' },
|
||||
];
|
||||
}
|
||||
</script>
|
||||
|
||||
<AceEditor
|
||||
value={$editorState.value || ''}
|
||||
menu={createMenu()}
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
mode="json"
|
||||
/>
|
||||
@@ -31,7 +31,7 @@
|
||||
|
||||
const tabVisible: any = getContext('tabVisible');
|
||||
|
||||
export const activator = createActivator('MarkdownEditorTab', false);
|
||||
export const activator = createActivator('YamlEditorTab', false);
|
||||
|
||||
let domEditor;
|
||||
|
||||
@@ -63,8 +63,6 @@
|
||||
|
||||
function createMenu() {
|
||||
return [
|
||||
{ command: 'yaml.preview' },
|
||||
{ divider: true },
|
||||
{ command: 'yaml.toggleComment' },
|
||||
{ divider: true },
|
||||
{ command: 'yaml.save' },
|
||||
|
||||
@@ -15,6 +15,7 @@ import * as FavoriteEditorTab from './FavoriteEditorTab.svelte';
|
||||
import * as QueryDesignTab from './QueryDesignTab.svelte';
|
||||
import * as CommandListTab from './CommandListTab.svelte';
|
||||
import * as YamlEditorTab from './YamlEditorTab.svelte';
|
||||
import * as JsonEditorTab from './JsonEditorTab.svelte';
|
||||
import * as CompareModelTab from './CompareModelTab.svelte';
|
||||
import * as JsonTab from './JsonTab.svelte';
|
||||
import * as ChangelogTab from './ChangelogTab.svelte';
|
||||
@@ -38,6 +39,7 @@ export default {
|
||||
QueryDesignTab,
|
||||
CommandListTab,
|
||||
YamlEditorTab,
|
||||
JsonEditorTab,
|
||||
CompareModelTab,
|
||||
JsonTab,
|
||||
ChangelogTab,
|
||||
|
||||
33
packages/web/src/utility/appTools.ts
Normal file
33
packages/web/src/utility/appTools.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { ApplicationDefinition, StoredConnection } from 'dbgate-types';
|
||||
import { apiCall } from '../utility/api';
|
||||
|
||||
export async function saveDbToApp(conid: string, database: string, app: string) {
|
||||
if (app == '#new') {
|
||||
const folder = await apiCall('apps/create-folder', { folder: database });
|
||||
|
||||
await apiCall('connections/update-database', {
|
||||
conid,
|
||||
database,
|
||||
values: {
|
||||
[`useApp:${folder}`]: true,
|
||||
},
|
||||
});
|
||||
|
||||
return folder;
|
||||
}
|
||||
|
||||
await apiCall('connections/update-database', {
|
||||
conid,
|
||||
database,
|
||||
values: {
|
||||
[`useApp:${app}`]: true,
|
||||
},
|
||||
});
|
||||
|
||||
return app;
|
||||
}
|
||||
|
||||
export function filterAppsForDatabase(connection, database: string, $apps): ApplicationDefinition[] {
|
||||
const db = (connection?.databases || []).find(x => x.name == database);
|
||||
return $apps.filter(app => db && db[`useApp:${app.name}`]);
|
||||
}
|
||||
@@ -1,7 +1,8 @@
|
||||
import { DictionaryDescription } from 'dbgate-datalib';
|
||||
import { TableInfo } from 'dbgate-types';
|
||||
import { ApplicationDefinition, TableInfo } from 'dbgate-types';
|
||||
import _ from 'lodash';
|
||||
import { getLocalStorage, setLocalStorage, removeLocalStorage } from './storageCache';
|
||||
import { apiCall } from './api';
|
||||
import { filterAppsForDatabase, saveDbToApp } from './appTools';
|
||||
|
||||
function checkDescriptionColumns(columns: string[], table: TableInfo) {
|
||||
if (!columns?.length) return false;
|
||||
@@ -14,17 +15,20 @@ export function getDictionaryDescription(
|
||||
table: TableInfo,
|
||||
conid: string,
|
||||
database: string,
|
||||
apps: ApplicationDefinition[],
|
||||
connections,
|
||||
skipCheckSaved: boolean = false
|
||||
): DictionaryDescription {
|
||||
const keySpecific = `dictionary_spec_${table.schemaName}||${table.pureName}||${conid}||${database}`;
|
||||
const keyCommon = `dictionary_spec_${table.schemaName}||${table.pureName}`;
|
||||
const conn = connections.find(x => x._id == conid);
|
||||
const dbApps = filterAppsForDatabase(conn, database, apps);
|
||||
|
||||
const cachedSpecific = getLocalStorage(keySpecific);
|
||||
const cachedCommon = getLocalStorage(keyCommon);
|
||||
const cached = _.flatten(dbApps.map(x => x.dictionaryDescriptions || [])).find(
|
||||
x => x.pureName == table.pureName && x.schemaName == table.schemaName
|
||||
);
|
||||
|
||||
if (cachedSpecific && (skipCheckSaved || checkDescriptionColumns(cachedSpecific.columns, table)))
|
||||
return cachedSpecific;
|
||||
if (cachedCommon && (skipCheckSaved || checkDescriptionColumns(cachedCommon.columns, table))) return cachedCommon;
|
||||
if (cached && (skipCheckSaved || checkDescriptionColumns(cached.columns, table))) {
|
||||
return cached;
|
||||
}
|
||||
|
||||
const descColumn = table.columns.find(x => x?.dataType?.toLowerCase()?.includes('char'));
|
||||
if (descColumn) {
|
||||
@@ -57,29 +61,22 @@ export function changeDelimitedColumnList(columns, columnName, isChecked) {
|
||||
return parsed.join(',');
|
||||
}
|
||||
|
||||
export function saveDictionaryDescription(
|
||||
export async function saveDictionaryDescription(
|
||||
table: TableInfo,
|
||||
conid: string,
|
||||
database: string,
|
||||
expression: string,
|
||||
delimiter: string,
|
||||
useForAllDatabases: boolean
|
||||
targetApplication: string
|
||||
) {
|
||||
const keySpecific = `dictionary_spec_${table.schemaName}||${table.pureName}||${conid}||${database}`;
|
||||
const keyCommon = `dictionary_spec_${table.schemaName}||${table.pureName}`;
|
||||
const appFolder = await saveDbToApp(conid, database, targetApplication);
|
||||
|
||||
removeLocalStorage(keySpecific);
|
||||
if (useForAllDatabases) removeLocalStorage(keyCommon);
|
||||
|
||||
const description = {
|
||||
await apiCall('apps/save-dictionary-description', {
|
||||
appFolder,
|
||||
schemaName: table.schemaName,
|
||||
pureName: table.pureName,
|
||||
columns: parseDelimitedColumnList(expression),
|
||||
expression,
|
||||
delimiter,
|
||||
};
|
||||
|
||||
if (useForAllDatabases) {
|
||||
setLocalStorage(keyCommon, description);
|
||||
} else {
|
||||
setLocalStorage(keySpecific, description);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -103,6 +103,30 @@ const archiveFilesLoader = ({ folder }) => ({
|
||||
reloadTrigger: `archive-files-changed-${folder}`,
|
||||
});
|
||||
|
||||
const appFoldersLoader = () => ({
|
||||
url: 'apps/folders',
|
||||
params: {},
|
||||
reloadTrigger: `app-folders-changed`,
|
||||
});
|
||||
|
||||
const appFilesLoader = ({ folder }) => ({
|
||||
url: 'apps/files',
|
||||
params: { folder },
|
||||
reloadTrigger: `app-files-changed-${folder}`,
|
||||
});
|
||||
|
||||
// const dbAppsLoader = ({ conid, database }) => ({
|
||||
// url: 'apps/get-apps-for-db',
|
||||
// params: { conid, database },
|
||||
// reloadTrigger: `db-apps-changed-${conid}-${database}`,
|
||||
// });
|
||||
|
||||
const usedAppsLoader = ({ conid, database }) => ({
|
||||
url: 'apps/get-used-apps',
|
||||
params: { },
|
||||
reloadTrigger: `used-apps-changed`,
|
||||
});
|
||||
|
||||
const serverStatusLoader = () => ({
|
||||
url: 'server-connections/server-status',
|
||||
params: {},
|
||||
@@ -401,6 +425,36 @@ export function useArchiveFolders(args = {}) {
|
||||
return useCore(archiveFoldersLoader, args);
|
||||
}
|
||||
|
||||
export function getAppFiles(args) {
|
||||
return getCore(appFilesLoader, args);
|
||||
}
|
||||
export function useAppFiles(args) {
|
||||
return useCore(appFilesLoader, args);
|
||||
}
|
||||
|
||||
export function getAppFolders(args = {}) {
|
||||
return getCore(appFoldersLoader, args);
|
||||
}
|
||||
export function useAppFolders(args = {}) {
|
||||
return useCore(appFoldersLoader, args);
|
||||
}
|
||||
|
||||
|
||||
|
||||
export function getUsedApps(args = {}) {
|
||||
return getCore(usedAppsLoader, args);
|
||||
}
|
||||
export function useUsedApps(args = {}) {
|
||||
return useCore(usedAppsLoader, args);
|
||||
}
|
||||
|
||||
// export function getDbApps(args = {}) {
|
||||
// return getCore(dbAppsLoader, args);
|
||||
// }
|
||||
// export function useDbApps(args = {}) {
|
||||
// return useCore(dbAppsLoader, args);
|
||||
// }
|
||||
|
||||
export function getInstalledPlugins(args = {}) {
|
||||
return getCore(installedPluginsLoader, args) || [];
|
||||
}
|
||||
|
||||
101
packages/web/src/widgets/AppFilesList.svelte
Normal file
101
packages/web/src/widgets/AppFilesList.svelte
Normal file
@@ -0,0 +1,101 @@
|
||||
<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 '../elements/CloseSearchButton.svelte';
|
||||
import DropDownButton from '../elements/DropDownButton.svelte';
|
||||
|
||||
import InlineButton from '../elements/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 { markArchiveFileAsDataSheet } from '../utility/archiveTools';
|
||||
import { useAppFiles, useArchiveFolders } from '../utility/metadataLoaders';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
|
||||
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,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function createAddMenu() {
|
||||
return [
|
||||
{
|
||||
text: 'New SQL command',
|
||||
onClick: () => handleNewSqlFile('command.sql', 'Create new SQL command', COMMAND_TEMPLATE),
|
||||
},
|
||||
// { 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>
|
||||
39
packages/web/src/widgets/AppFolderList.svelte
Normal file
39
packages/web/src/widgets/AppFolderList.svelte
Normal file
@@ -0,0 +1,39 @@
|
||||
<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 '../elements/CloseSearchButton.svelte';
|
||||
|
||||
import InlineButton from '../elements/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>
|
||||
19
packages/web/src/widgets/AppWidget.svelte
Normal file
19
packages/web/src/widgets/AppWidget.svelte
Normal file
@@ -0,0 +1,19 @@
|
||||
<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>
|
||||
@@ -6,6 +6,7 @@
|
||||
import PluginsWidget from './PluginsWidget.svelte';
|
||||
import CellDataWidget from './CellDataWidget.svelte';
|
||||
import HistoryWidget from './HistoryWidget.svelte';
|
||||
import AppWidget from './AppWidget.svelte';
|
||||
</script>
|
||||
|
||||
<DatabaseWidget hidden={$selectedWidget != 'database'} />
|
||||
@@ -25,3 +26,6 @@
|
||||
{#if $selectedWidget == 'cell-data'}
|
||||
<CellDataWidget />
|
||||
{/if}
|
||||
{#if $selectedWidget == 'app'}
|
||||
<AppWidget />
|
||||
{/if}
|
||||
|
||||
@@ -40,6 +40,11 @@
|
||||
name: 'cell-data',
|
||||
title: 'Selected cell data detail view',
|
||||
},
|
||||
{
|
||||
icon: 'icon app',
|
||||
name: 'app',
|
||||
title: 'Application layers',
|
||||
},
|
||||
// {
|
||||
// icon: 'icon settings',
|
||||
// name: 'settings',
|
||||
|
||||
Reference in New Issue
Block a user