mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-30 13:53:59 +00:00
define virtual fk
This commit is contained in:
@@ -18,9 +18,10 @@ module.exports = {
|
|||||||
|
|
||||||
createFolder_meta: true,
|
createFolder_meta: true,
|
||||||
async createFolder({ folder }) {
|
async createFolder({ folder }) {
|
||||||
await fs.mkdir(path.join(appdir(), folder));
|
const name = await this.getNewAppFolder({ name: folder });
|
||||||
|
await fs.mkdir(path.join(appdir(), name));
|
||||||
socket.emitChanged('app-folders-changed');
|
socket.emitChanged('app-folders-changed');
|
||||||
return true;
|
return name;
|
||||||
},
|
},
|
||||||
|
|
||||||
files_meta: true,
|
files_meta: true,
|
||||||
@@ -45,7 +46,7 @@ module.exports = {
|
|||||||
.map(name => ({
|
.map(name => ({
|
||||||
name: 'virtual-references.json',
|
name: 'virtual-references.json',
|
||||||
label: 'virtual-references.json',
|
label: 'virtual-references.json',
|
||||||
type: 'vfk',
|
type: 'vfk.json',
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,4 +187,46 @@ module.exports = {
|
|||||||
|
|
||||||
return res;
|
return res;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
saveVfk_meta: true,
|
||||||
|
async saveVfk({ appFolder, schemaName, pureName, refSchemaName, refTableName, columns }) {
|
||||||
|
const file = path.join(appdir(), appFolder, 'virtual-references.json');
|
||||||
|
|
||||||
|
let json;
|
||||||
|
try {
|
||||||
|
json = JSON.parse(await fs.readFile(file, { encoding: 'utf-8' }));
|
||||||
|
} catch (err) {
|
||||||
|
json = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (columns.length == 1) {
|
||||||
|
json = json.filter(
|
||||||
|
x =>
|
||||||
|
!(
|
||||||
|
x.schemaName == schemaName &&
|
||||||
|
x.pureName == pureName &&
|
||||||
|
x.columns.length == 1 &&
|
||||||
|
x.columns[0].columnName == columns[0].columnName
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
json = [
|
||||||
|
...json,
|
||||||
|
{
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
refSchemaName,
|
||||||
|
refTableName,
|
||||||
|
columns,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
await fs.writeFile(file, JSON.stringify(json, undefined, 2));
|
||||||
|
|
||||||
|
socket.emitChanged(`app-files-changed-${appFolder}`);
|
||||||
|
socket.emitChanged('used-apps-changed');
|
||||||
|
|
||||||
|
return true;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -3,9 +3,11 @@
|
|||||||
const connProps: any = {};
|
const connProps: any = {};
|
||||||
let tooltip = undefined;
|
let tooltip = undefined;
|
||||||
|
|
||||||
|
const savedFile = fileType == 'vfk.json' ? fileName : fileName + '.' + fileType;
|
||||||
|
|
||||||
const resp = await apiCall('files/load', {
|
const resp = await apiCall('files/load', {
|
||||||
folder: 'app:' + folderName,
|
folder: 'app:' + folderName,
|
||||||
file: fileName + '.' + fileType,
|
file: savedFile,
|
||||||
format: 'text',
|
format: 'text',
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -16,7 +18,7 @@
|
|||||||
tabComponent,
|
tabComponent,
|
||||||
tooltip,
|
tooltip,
|
||||||
props: {
|
props: {
|
||||||
savedFile: fileName + '.' + fileType,
|
savedFile,
|
||||||
savedFolder: 'app:' + folderName,
|
savedFolder: 'app:' + folderName,
|
||||||
savedFormat: 'text',
|
savedFormat: 'text',
|
||||||
appFolder: folderName,
|
appFolder: folderName,
|
||||||
@@ -30,6 +32,7 @@
|
|||||||
export const extractKey = data => data.fileName;
|
export const extractKey = data => data.fileName;
|
||||||
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
|
export const createMatcher = ({ fileName }) => filter => filterName(filter, fileName);
|
||||||
const APP_ICONS = {
|
const APP_ICONS = {
|
||||||
|
'vfk.json': 'img json',
|
||||||
'command.sql': 'img app-command',
|
'command.sql': 'img app-command',
|
||||||
'query.sql': 'img app-query',
|
'query.sql': 'img app-query',
|
||||||
};
|
};
|
||||||
@@ -85,12 +88,15 @@
|
|||||||
if (data.fileType.endsWith('.sql')) {
|
if (data.fileType.endsWith('.sql')) {
|
||||||
handleOpenSqlFile();
|
handleOpenSqlFile();
|
||||||
}
|
}
|
||||||
|
if (data.fileType.endsWith('.json')) {
|
||||||
|
handleOpenJsonFile();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
const handleOpenSqlFile = () => {
|
const handleOpenSqlFile = () => {
|
||||||
openTextFile(data.fileName, data.fileType, data.folderName, 'QueryTab', 'img sql-file');
|
openTextFile(data.fileName, data.fileType, data.folderName, 'QueryTab', 'img sql-file');
|
||||||
};
|
};
|
||||||
const handleOpenYamlFile = () => {
|
const handleOpenJsonFile = () => {
|
||||||
openTextFile(data.fileName, data.fileType, data.folderName, 'YamlEditorTab', 'img yaml');
|
openTextFile(data.fileName, data.fileType, data.folderName, 'JsonEditorTab', 'img json');
|
||||||
};
|
};
|
||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
@@ -98,6 +104,7 @@
|
|||||||
{ text: 'Delete', onClick: handleDelete },
|
{ text: 'Delete', onClick: handleDelete },
|
||||||
{ text: 'Rename', onClick: handleRename },
|
{ text: 'Rename', onClick: handleRename },
|
||||||
data.fileType.endsWith('.sql') && { text: 'Open SQL', onClick: handleOpenSqlFile },
|
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 },
|
// data.fileType.endsWith('.yaml') && { text: 'Open YAML', onClick: handleOpenYamlFile },
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -1,18 +1,69 @@
|
|||||||
|
<script lang="ts" context="module">
|
||||||
|
export async function saveDbToApp(conid, database, app) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { map } from 'lodash';
|
import _ from 'lodash';
|
||||||
|
import { filterAppsForDatabase } from '../appobj/DatabaseAppObject.svelte';
|
||||||
|
|
||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import { useAppFolders } from '../utility/metadataLoaders';
|
import { currentDatabase } from '../stores';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
import { useAppFolders, useUsedApps } from '../utility/metadataLoaders';
|
||||||
|
|
||||||
$: apps = useAppFolders();
|
export let value = '#new';
|
||||||
|
|
||||||
|
$: appFolders = useAppFolders();
|
||||||
|
$: usedApps = useUsedApps();
|
||||||
|
|
||||||
|
$: {
|
||||||
|
if (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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<SelectField
|
<SelectField
|
||||||
isNative
|
isNative
|
||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
|
{value}
|
||||||
|
on:change={e => {
|
||||||
|
value = e.detail;
|
||||||
|
}}
|
||||||
options={[
|
options={[
|
||||||
{ label: '(New application linked to current DB)', value: 'new' },
|
{ label: '(New application linked to current DB)', value: '#new' },
|
||||||
...($apps || []).map(app => ({
|
...($appFolders || []).map(app => ({
|
||||||
label: app.name,
|
label: app.name,
|
||||||
value: app.name,
|
value: app.name,
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -81,7 +81,7 @@
|
|||||||
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
|
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
|
||||||
isNative
|
isNative
|
||||||
notSelected
|
notSelected
|
||||||
options={(dbInfo?.tables || []).map(tbl => ({
|
options={_.sortBy(dbInfo?.tables || [], ['schemaName', 'pureName']).map(tbl => ({
|
||||||
label: fullNameToLabel(tbl),
|
label: fullNameToLabel(tbl),
|
||||||
value: fullNameToString(tbl),
|
value: fullNameToString(tbl),
|
||||||
}))}
|
}))}
|
||||||
|
|||||||
@@ -1,32 +1,17 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||||
import uuidv1 from 'uuid/v1';
|
|
||||||
|
|
||||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
|
||||||
import FormTextField from '../forms/FormTextField.svelte';
|
|
||||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
|
||||||
|
|
||||||
import FormProvider from '../forms/FormProvider.svelte';
|
import FormProvider from '../forms/FormProvider.svelte';
|
||||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||||
import ModalBase from '../modals/ModalBase.svelte';
|
import ModalBase from '../modals/ModalBase.svelte';
|
||||||
import { closeCurrentModal } from '../modals/modalTools';
|
import { closeCurrentModal } from '../modals/modalTools';
|
||||||
import ElectronFilesInput from '../impexp/ElectronFilesInput.svelte';
|
import { fullNameFromString, fullNameToLabel, fullNameToString } from 'dbgate-tools';
|
||||||
import DropDownButton from '../elements/DropDownButton.svelte';
|
|
||||||
import DataTypeEditor from './DataTypeEditor.svelte';
|
|
||||||
import {
|
|
||||||
editorAddConstraint,
|
|
||||||
editorDeleteConstraint,
|
|
||||||
editorModifyConstraint,
|
|
||||||
fullNameFromString,
|
|
||||||
fullNameToLabel,
|
|
||||||
fullNameToString,
|
|
||||||
} from 'dbgate-tools';
|
|
||||||
import TextField from '../forms/TextField.svelte';
|
|
||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { useDatabaseInfo, useTableInfo } from '../utility/metadataLoaders';
|
import { useDatabaseInfo, useTableInfo } from '../utility/metadataLoaders';
|
||||||
import { onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import TargetApplicationSelect from '../elements/TargetApplicationSelect.svelte';
|
import TargetApplicationSelect, { saveDbToApp } from '../elements/TargetApplicationSelect.svelte';
|
||||||
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
export let conid;
|
export let conid;
|
||||||
export let database;
|
export let database;
|
||||||
@@ -34,6 +19,8 @@
|
|||||||
export let pureName;
|
export let pureName;
|
||||||
export let columnName;
|
export let columnName;
|
||||||
|
|
||||||
|
let dstApp;
|
||||||
|
|
||||||
const dbInfo = useDatabaseInfo({ conid, database });
|
const dbInfo = useDatabaseInfo({ conid, database });
|
||||||
const tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
const tableInfo = useTableInfo({ conid, database, schemaName, pureName });
|
||||||
|
|
||||||
@@ -69,7 +56,10 @@
|
|||||||
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
|
value={fullNameToString({ pureName: refTableName, schemaName: refSchemaName })}
|
||||||
isNative
|
isNative
|
||||||
notSelected
|
notSelected
|
||||||
options={[...($dbInfo?.tables || []), ...($dbInfo?.views || [])].map(tbl => ({
|
options={[
|
||||||
|
..._.sortBy($dbInfo?.tables || [], ['schemaName', 'pureName']),
|
||||||
|
..._.sortBy($dbInfo?.views || [], ['schemaName', 'pureName']),
|
||||||
|
].map(tbl => ({
|
||||||
label: fullNameToLabel(tbl),
|
label: fullNameToLabel(tbl),
|
||||||
value: fullNameToString(tbl),
|
value: fullNameToString(tbl),
|
||||||
}))}
|
}))}
|
||||||
@@ -155,7 +145,7 @@
|
|||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="label col-3">Target application</div>
|
<div class="label col-3">Target application</div>
|
||||||
<div class="col-9">
|
<div class="col-9">
|
||||||
<TargetApplicationSelect />
|
<TargetApplicationSelect bind:value={dstApp} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -163,7 +153,16 @@
|
|||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<FormSubmit
|
<FormSubmit
|
||||||
value={'Save'}
|
value={'Save'}
|
||||||
on:click={() => {
|
on:click={async () => {
|
||||||
|
const appFolder = await saveDbToApp(conid, database, dstApp);
|
||||||
|
await apiCall('apps/save-vfk', {
|
||||||
|
appFolder,
|
||||||
|
schemaName,
|
||||||
|
pureName,
|
||||||
|
refSchemaName,
|
||||||
|
refTableName,
|
||||||
|
columns,
|
||||||
|
});
|
||||||
closeCurrentModal();
|
closeCurrentModal();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|||||||
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');
|
const tabVisible: any = getContext('tabVisible');
|
||||||
|
|
||||||
export const activator = createActivator('MarkdownEditorTab', false);
|
export const activator = createActivator('YamlEditorTab', false);
|
||||||
|
|
||||||
let domEditor;
|
let domEditor;
|
||||||
|
|
||||||
@@ -63,8 +63,6 @@
|
|||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
return [
|
return [
|
||||||
{ command: 'yaml.preview' },
|
|
||||||
{ divider: true },
|
|
||||||
{ command: 'yaml.toggleComment' },
|
{ command: 'yaml.toggleComment' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ command: 'yaml.save' },
|
{ command: 'yaml.save' },
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import * as FavoriteEditorTab from './FavoriteEditorTab.svelte';
|
|||||||
import * as QueryDesignTab from './QueryDesignTab.svelte';
|
import * as QueryDesignTab from './QueryDesignTab.svelte';
|
||||||
import * as CommandListTab from './CommandListTab.svelte';
|
import * as CommandListTab from './CommandListTab.svelte';
|
||||||
import * as YamlEditorTab from './YamlEditorTab.svelte';
|
import * as YamlEditorTab from './YamlEditorTab.svelte';
|
||||||
|
import * as JsonEditorTab from './JsonEditorTab.svelte';
|
||||||
import * as CompareModelTab from './CompareModelTab.svelte';
|
import * as CompareModelTab from './CompareModelTab.svelte';
|
||||||
import * as JsonTab from './JsonTab.svelte';
|
import * as JsonTab from './JsonTab.svelte';
|
||||||
import * as ChangelogTab from './ChangelogTab.svelte';
|
import * as ChangelogTab from './ChangelogTab.svelte';
|
||||||
@@ -38,6 +39,7 @@ export default {
|
|||||||
QueryDesignTab,
|
QueryDesignTab,
|
||||||
CommandListTab,
|
CommandListTab,
|
||||||
YamlEditorTab,
|
YamlEditorTab,
|
||||||
|
JsonEditorTab,
|
||||||
CompareModelTab,
|
CompareModelTab,
|
||||||
JsonTab,
|
JsonTab,
|
||||||
ChangelogTab,
|
ChangelogTab,
|
||||||
|
|||||||
Reference in New Issue
Block a user