mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-30 09:13:58 +00:00
Merge branch 'master' into develop
This commit is contained in:
@@ -66,7 +66,7 @@
|
||||
"env-cmd": "^10.1.0",
|
||||
"node-loader": "^1.0.2",
|
||||
"nodemon": "^2.0.2",
|
||||
"typescript": "^3.7.4",
|
||||
"typescript": "^4.4.3",
|
||||
"webpack": "^4.42.0",
|
||||
"webpack-cli": "^3.3.11"
|
||||
},
|
||||
|
||||
@@ -14,6 +14,7 @@ let analysedStructure = null;
|
||||
let lastPing = null;
|
||||
let lastStatus = null;
|
||||
let analysedTime = 0;
|
||||
let serverVersion;
|
||||
|
||||
async function checkedAsyncCall(promise) {
|
||||
try {
|
||||
@@ -36,7 +37,7 @@ async function handleFullRefresh() {
|
||||
loadingModel = true;
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('loadStructure');
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection));
|
||||
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection, serverVersion));
|
||||
analysedTime = new Date().getTime();
|
||||
process.send({ msgtype: 'structure', structure: analysedStructure });
|
||||
process.send({ msgtype: 'structureTime', analysedTime });
|
||||
@@ -48,7 +49,7 @@ async function handleIncrementalRefresh(forceSend) {
|
||||
loadingModel = true;
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
setStatusName('checkStructure');
|
||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure));
|
||||
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure, serverVersion));
|
||||
analysedTime = new Date().getTime();
|
||||
if (newStructure != null) {
|
||||
analysedStructure = newStructure;
|
||||
@@ -84,6 +85,7 @@ async function readVersion() {
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
const version = await driver.getVersion(systemConnection);
|
||||
process.send({ msgtype: 'version', version });
|
||||
serverVersion = version;
|
||||
}
|
||||
|
||||
async function handleConnect({ connection, structure, globalSettings }) {
|
||||
@@ -93,7 +95,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
|
||||
if (!structure) setStatusName('pending');
|
||||
const driver = requireEngineDriver(storedConnection);
|
||||
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
|
||||
readVersion();
|
||||
await checkedAsyncCall(readVersion());
|
||||
if (structure) {
|
||||
analysedStructure = structure;
|
||||
handleIncrementalRefresh(true);
|
||||
|
||||
@@ -70,14 +70,14 @@ function decryptPasswordField(connection, field) {
|
||||
function encryptConnection(connection) {
|
||||
connection = encryptPasswordField(connection, 'password');
|
||||
connection = encryptPasswordField(connection, 'sshPassword');
|
||||
connection = encryptPasswordField(connection, 'sshKeyFilePassword');
|
||||
connection = encryptPasswordField(connection, 'sshKeyfilePassword');
|
||||
return connection;
|
||||
}
|
||||
|
||||
function decryptConnection(connection) {
|
||||
connection = decryptPasswordField(connection, 'password');
|
||||
connection = decryptPasswordField(connection, 'sshPassword');
|
||||
connection = decryptPasswordField(connection, 'sshKeyFilePassword');
|
||||
connection = decryptPasswordField(connection, 'sshKeyfilePassword');
|
||||
return connection;
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ const platformInfo = {
|
||||
environment: process.env.NODE_ENV,
|
||||
platform,
|
||||
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL,
|
||||
defaultKeyFile: path.join(os.homedir(), '.ssh/id_rsa'),
|
||||
defaultKeyfile: path.join(os.homedir(), '.ssh/id_rsa'),
|
||||
};
|
||||
|
||||
module.exports = platformInfo;
|
||||
|
||||
@@ -16,9 +16,9 @@ const CONNECTION_FIELDS = [
|
||||
'sshLogin',
|
||||
'sshPassword',
|
||||
'sshMode',
|
||||
'sshKeyFile',
|
||||
'sshKeyfile',
|
||||
'sshBastionHost',
|
||||
'sshKeyFilePassword',
|
||||
'sshKeyfilePassword',
|
||||
];
|
||||
const TUNNEL_FIELDS = [...CONNECTION_FIELDS, 'server', 'port'];
|
||||
|
||||
@@ -31,7 +31,7 @@ async function getSshConnection(connection) {
|
||||
endPort: connection.sshPort || 22,
|
||||
bastionHost: connection.sshBastionHost || '',
|
||||
agentForward: connection.sshMode == 'agent',
|
||||
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyFilePassword : undefined,
|
||||
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
|
||||
username: connection.sshLogin,
|
||||
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
|
||||
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,
|
||||
|
||||
@@ -17,6 +17,6 @@
|
||||
"devDependencies": {
|
||||
"dbgate-types": "^4.1.1",
|
||||
"@types/node": "^13.7.0",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
||||
@@ -118,7 +118,11 @@ export function setChangeSetValue(
|
||||
};
|
||||
}
|
||||
|
||||
export function setChangeSetRowData(changeSet: ChangeSet, definition: ChangeSetRowDefinition, document: any): ChangeSet {
|
||||
export function setChangeSetRowData(
|
||||
changeSet: ChangeSet,
|
||||
definition: ChangeSetRowDefinition,
|
||||
document: any
|
||||
): ChangeSet {
|
||||
if (!changeSet || !definition) return changeSet;
|
||||
let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition);
|
||||
if (fieldName == 'deletes') {
|
||||
@@ -213,7 +217,7 @@ function extractFields(item: ChangeSetItem, allowNulls = true): UpdateField[] {
|
||||
}));
|
||||
}
|
||||
|
||||
function insertToSql(
|
||||
function changeSetInsertToSql(
|
||||
item: ChangeSetItem,
|
||||
dbinfo: DatabaseInfo = null
|
||||
): [AllowIdentityInsert, Insert, AllowIdentityInsert] {
|
||||
@@ -257,7 +261,7 @@ function insertToSql(
|
||||
];
|
||||
}
|
||||
|
||||
function extractCondition(item: ChangeSetItem): Condition {
|
||||
export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): Condition {
|
||||
return {
|
||||
conditionType: 'and',
|
||||
conditions: _.keys(item.condition).map(columnName => ({
|
||||
@@ -271,6 +275,7 @@ function extractCondition(item: ChangeSetItem): Condition {
|
||||
pureName: item.pureName,
|
||||
schemaName: item.schemaName,
|
||||
},
|
||||
alias,
|
||||
},
|
||||
},
|
||||
right: {
|
||||
@@ -281,7 +286,7 @@ function extractCondition(item: ChangeSetItem): Condition {
|
||||
};
|
||||
}
|
||||
|
||||
function updateToSql(item: ChangeSetItem): Update {
|
||||
function changeSetUpdateToSql(item: ChangeSetItem): Update {
|
||||
return {
|
||||
from: {
|
||||
name: {
|
||||
@@ -291,11 +296,11 @@ function updateToSql(item: ChangeSetItem): Update {
|
||||
},
|
||||
commandType: 'update',
|
||||
fields: extractFields(item),
|
||||
where: extractCondition(item),
|
||||
where: extractChangeSetCondition(item),
|
||||
};
|
||||
}
|
||||
|
||||
function deleteToSql(item: ChangeSetItem): Delete {
|
||||
function changeSetDeleteToSql(item: ChangeSetItem): Delete {
|
||||
return {
|
||||
from: {
|
||||
name: {
|
||||
@@ -304,16 +309,16 @@ function deleteToSql(item: ChangeSetItem): Delete {
|
||||
},
|
||||
},
|
||||
commandType: 'delete',
|
||||
where: extractCondition(item),
|
||||
where: extractChangeSetCondition(item),
|
||||
};
|
||||
}
|
||||
|
||||
export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Command[] {
|
||||
return _.compact(
|
||||
_.flatten([
|
||||
...(changeSet.inserts.map(item => insertToSql(item, dbinfo)) as any),
|
||||
...changeSet.updates.map(updateToSql),
|
||||
...changeSet.deletes.map(deleteToSql),
|
||||
...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo)) as any),
|
||||
...changeSet.updates.map(changeSetUpdateToSql),
|
||||
...changeSet.deletes.map(changeSetDeleteToSql),
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
136
packages/datalib/src/deleteCascade.ts
Normal file
136
packages/datalib/src/deleteCascade.ts
Normal file
@@ -0,0 +1,136 @@
|
||||
import _ from 'lodash';
|
||||
import { Command, Insert, Update, Delete, UpdateField, Condition, AllowIdentityInsert } from 'dbgate-sqltree';
|
||||
import { NamedObjectInfo, DatabaseInfo, ForeignKeyInfo, TableInfo } from 'dbgate-types';
|
||||
import { ChangeSet, ChangeSetItem, extractChangeSetCondition } from './ChangeSet';
|
||||
|
||||
export interface ChangeSetDeleteCascade {
|
||||
title: string;
|
||||
commands: Command[];
|
||||
}
|
||||
|
||||
// function getDeleteScript()
|
||||
|
||||
function processDependencies(
|
||||
changeSet: ChangeSet,
|
||||
result: ChangeSetDeleteCascade[],
|
||||
allForeignKeys: ForeignKeyInfo[],
|
||||
fkPath: ForeignKeyInfo[],
|
||||
table: TableInfo,
|
||||
baseCmd: ChangeSetItem,
|
||||
dbinfo: DatabaseInfo
|
||||
) {
|
||||
if (result.find(x => x.title == table.pureName)) return;
|
||||
|
||||
const dependencies = allForeignKeys.filter(
|
||||
x => x.refSchemaName == table.schemaName && x.refTableName == table.pureName
|
||||
);
|
||||
|
||||
for (const fk of dependencies) {
|
||||
const depTable = dbinfo.tables.find(x => x.pureName == fk.pureName && x.schemaName == fk.schemaName);
|
||||
const subFkPath = [...fkPath, fk];
|
||||
if (depTable && depTable.pureName != baseCmd.pureName) {
|
||||
processDependencies(changeSet, result, allForeignKeys, subFkPath, depTable, baseCmd, dbinfo);
|
||||
}
|
||||
|
||||
const refCmd: Delete = {
|
||||
commandType: 'delete',
|
||||
from: {
|
||||
name: {
|
||||
pureName: fk.pureName,
|
||||
schemaName: fk.schemaName,
|
||||
},
|
||||
},
|
||||
where: {
|
||||
conditionType: 'exists',
|
||||
subQuery: {
|
||||
commandType: 'select',
|
||||
selectAll: true,
|
||||
from: {
|
||||
name: {
|
||||
pureName: fk.pureName,
|
||||
schemaName: fk.schemaName,
|
||||
},
|
||||
alias: 't0',
|
||||
relations: subFkPath.map((fkItem, fkIndex) => ({
|
||||
joinType: 'INNER JOIN',
|
||||
alias: `t${fkIndex + 1}`,
|
||||
name: {
|
||||
pureName: fkItem.refTableName,
|
||||
schemaName: fkItem.refSchemaName,
|
||||
},
|
||||
conditions: fkItem.columns.map(column => ({
|
||||
conditionType: 'binary',
|
||||
operator: '=',
|
||||
left: {
|
||||
exprType: 'column',
|
||||
columnName: column.columnName,
|
||||
source: { alias: `t${fkIndex}` },
|
||||
},
|
||||
right: {
|
||||
exprType: 'column',
|
||||
columnName: column.refColumnName,
|
||||
source: { alias: `t${fkIndex + 1}` },
|
||||
},
|
||||
})),
|
||||
})),
|
||||
},
|
||||
where: {
|
||||
conditionType: 'and',
|
||||
conditions: [
|
||||
extractChangeSetCondition(baseCmd, `t${subFkPath.length}`),
|
||||
// @ts-ignore
|
||||
...table.primaryKey.columns.map(column => ({
|
||||
conditionType: 'binary',
|
||||
operator: '=',
|
||||
left: {
|
||||
exprType: 'column',
|
||||
columnName: column.columnName,
|
||||
source: { alias: 't0' },
|
||||
},
|
||||
right: {
|
||||
exprType: 'column',
|
||||
columnName: column.columnName,
|
||||
source: {
|
||||
name: fk,
|
||||
},
|
||||
},
|
||||
})),
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
let resItem = result.find(x => x.title == fk.pureName);
|
||||
if (!resItem) {
|
||||
resItem = {
|
||||
title: fk.pureName,
|
||||
commands: [],
|
||||
};
|
||||
result.push(resItem);
|
||||
}
|
||||
resItem.commands.push(refCmd);
|
||||
}
|
||||
}
|
||||
|
||||
export function getDeleteCascades(changeSet: ChangeSet, dbinfo: DatabaseInfo): ChangeSetDeleteCascade[] {
|
||||
const result: ChangeSetDeleteCascade[] = [];
|
||||
const allForeignKeys = _.flatten(dbinfo.tables.map(x => x.foreignKeys));
|
||||
for (const baseCmd of changeSet.deletes) {
|
||||
const table = dbinfo.tables.find(x => x.pureName == baseCmd.pureName && x.schemaName == baseCmd.schemaName);
|
||||
if (!table.primaryKey) continue;
|
||||
|
||||
processDependencies(changeSet, result, allForeignKeys, [], table, baseCmd, dbinfo);
|
||||
|
||||
// let resItem = result.find(x => x.title == baseCmd.pureName);
|
||||
// if (!resItem) {
|
||||
// resItem = {
|
||||
// title: baseCmd.pureName,
|
||||
// commands: [],
|
||||
// };
|
||||
// result.push(resItem);
|
||||
// }
|
||||
// resItem.commands.push(changeSetDeleteToSql(baseCmd));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
@@ -11,3 +11,4 @@ export * from './runMacro';
|
||||
export * from './FormViewDisplay';
|
||||
export * from './TableFormViewDisplay';
|
||||
export * from './CollectionGridDisplay';
|
||||
export * from './deleteCascade';
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
"@types/node": "^13.7.0",
|
||||
"jest": "^24.9.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"@types/parsimmon": "^1.10.1",
|
||||
|
||||
@@ -32,6 +32,6 @@
|
||||
"@types/node": "^13.7.0",
|
||||
"jest": "^24.9.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^4.4.3"
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,7 @@
|
||||
"devDependencies": {
|
||||
"@types/node": "^13.7.0",
|
||||
"dbgate-types": "^4.1.1",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21"
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
"dbgate-types": "^4.1.1",
|
||||
"jest": "^24.9.0",
|
||||
"ts-jest": "^25.2.1",
|
||||
"typescript": "^3.7.5"
|
||||
"typescript": "^4.4.3"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.21",
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DatabaseInfo, DatabaseModification, EngineDriver } from 'dbgate-types';
|
||||
import { DatabaseInfo, DatabaseModification, EngineDriver, SqlDialect } from 'dbgate-types';
|
||||
import _sortBy from 'lodash/sortBy';
|
||||
import _groupBy from 'lodash/groupBy';
|
||||
import _pick from 'lodash/pick';
|
||||
@@ -13,8 +13,11 @@ export class DatabaseAnalyser {
|
||||
modifications: DatabaseModification[];
|
||||
singleObjectFilter: any;
|
||||
singleObjectId: string = null;
|
||||
dialect: SqlDialect;
|
||||
|
||||
constructor(public pool, public driver: EngineDriver) {}
|
||||
constructor(public pool, public driver: EngineDriver, version) {
|
||||
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(version)) || driver?.dialect;
|
||||
}
|
||||
|
||||
async _runAnalysis() {
|
||||
return DatabaseAnalyser.createEmptyStructure();
|
||||
|
||||
@@ -17,8 +17,8 @@ export const driverBase = {
|
||||
dumperClass: SqlDumper,
|
||||
dialect,
|
||||
|
||||
async analyseFull(pool) {
|
||||
const analyser = new this.analyserClass(pool, this);
|
||||
async analyseFull(pool, version) {
|
||||
const analyser = new this.analyserClass(pool, this, version);
|
||||
return analyser.fullAnalysis();
|
||||
},
|
||||
async analyseSingleObject(pool, name, typeField = 'tables') {
|
||||
@@ -28,8 +28,8 @@ export const driverBase = {
|
||||
analyseSingleTable(pool, name) {
|
||||
return this.analyseSingleObject(pool, name, 'tables');
|
||||
},
|
||||
async analyseIncremental(pool, structure) {
|
||||
const analyser = new this.analyserClass(pool, this);
|
||||
async analyseIncremental(pool, structure, version) {
|
||||
const analyser = new this.analyserClass(pool, this, version);
|
||||
return analyser.incrementalAnalysis(structure);
|
||||
},
|
||||
createDumper() {
|
||||
|
||||
4
packages/types/engines.d.ts
vendored
4
packages/types/engines.d.ts
vendored
@@ -67,8 +67,8 @@ export interface EngineDriver {
|
||||
name: string;
|
||||
}[]
|
||||
>;
|
||||
analyseFull(pool: any): Promise<DatabaseInfo>;
|
||||
analyseIncremental(pool: any, structure: DatabaseInfo): Promise<DatabaseInfo>;
|
||||
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
|
||||
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
||||
dialect: SqlDialect;
|
||||
dialectByVersion(version): SqlDialect;
|
||||
createDumper(): SqlDumper;
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
"devDependencies": {
|
||||
"@ant-design/colors": "^5.0.0",
|
||||
"@mdi/font": "^5.9.55",
|
||||
"@rollup/plugin-commonjs": "^17.0.0",
|
||||
"@rollup/plugin-node-resolve": "^11.0.0",
|
||||
"@rollup/plugin-replace": "^2.4.1",
|
||||
"@rollup/plugin-typescript": "^6.0.0",
|
||||
"@rollup/plugin-commonjs": "^20.0.0",
|
||||
"@rollup/plugin-node-resolve": "^13.0.5",
|
||||
"@rollup/plugin-replace": "^3.0.0",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@tsconfig/svelte": "^1.0.0",
|
||||
"ace-builds": "^1.4.8",
|
||||
"chart.js": "^2.9.4",
|
||||
@@ -32,7 +32,7 @@
|
||||
"lodash": "^4.17.21",
|
||||
"randomcolor": "^0.6.2",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rollup": "^2.3.4",
|
||||
"rollup": "^2.57.0",
|
||||
"rollup-plugin-copy": "^3.3.0",
|
||||
"rollup-plugin-css-only": "^3.1.0",
|
||||
"rollup-plugin-livereload": "^2.0.0",
|
||||
@@ -41,13 +41,13 @@
|
||||
"sirv-cli": "^1.0.0",
|
||||
"socket.io-client": "^2.3.0",
|
||||
"sql-formatter": "^2.3.3",
|
||||
"svelte": "^3.35.0",
|
||||
"svelte": "^3.43.0",
|
||||
"svelte-check": "^1.0.0",
|
||||
"svelte-markdown": "^0.1.4",
|
||||
"svelte-preprocess": "^4.0.0",
|
||||
"svelte-preprocess": "^4.9.5",
|
||||
"svelte-select": "^3.17.0",
|
||||
"tslib": "^2.0.0",
|
||||
"typescript": "^3.9.3",
|
||||
"tslib": "^2.3.1",
|
||||
"typescript": "^4.4.3",
|
||||
"uuid": "^3.4.0"
|
||||
}
|
||||
}
|
||||
@@ -52,8 +52,9 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#each filtered as item}
|
||||
{#each items as item}
|
||||
<AppObjectListItem
|
||||
isHidden={!item.isMatched}
|
||||
{...$$restProps}
|
||||
{module}
|
||||
data={item.data}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import { asyncFilter } from '../utility/common';
|
||||
import AppObjectGroup from './AppObjectGroup.svelte';
|
||||
|
||||
import AppObjectListItem from './AppObjectListItem.svelte';
|
||||
@@ -16,11 +17,36 @@
|
||||
|
||||
export let groupFunc = undefined;
|
||||
|
||||
$: filtered = list.filter(data => {
|
||||
const matcher = module.createMatcher && module.createMatcher(data);
|
||||
if (matcher && !matcher(filter)) return false;
|
||||
return true;
|
||||
});
|
||||
$: filtered = !groupFunc
|
||||
? list.filter(data => {
|
||||
const matcher = module.createMatcher && module.createMatcher(data);
|
||||
if (matcher && !matcher(filter)) return false;
|
||||
return true;
|
||||
})
|
||||
: null;
|
||||
|
||||
$: childrenMatched = !groupFunc
|
||||
? list.filter(data => {
|
||||
const matcher = module.createChildMatcher && module.createChildMatcher(data);
|
||||
if (matcher && !matcher(filter)) return false;
|
||||
return true;
|
||||
})
|
||||
: null;
|
||||
|
||||
|
||||
// let filtered = [];
|
||||
|
||||
// $: {
|
||||
// if (!groupFunc) {
|
||||
// asyncFilter(list, async data => {
|
||||
// const matcher = module.createMatcher && module.createMatcher(data);
|
||||
// if (matcher && !(await matcher(filter))) return false;
|
||||
// return true;
|
||||
// }).then(res => {
|
||||
// filtered = res;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
|
||||
$: listGrouped = groupFunc
|
||||
? _.compact(
|
||||
@@ -34,7 +60,6 @@
|
||||
: null;
|
||||
|
||||
$: groups = groupFunc ? _.groupBy(listGrouped, 'group') : null;
|
||||
|
||||
</script>
|
||||
|
||||
{#if groupFunc}
|
||||
@@ -49,11 +74,13 @@
|
||||
{checkedObjectsStore}
|
||||
{groupFunc}
|
||||
{disableContextMenu}
|
||||
{filter}
|
||||
/>
|
||||
{/each}
|
||||
{:else}
|
||||
{#each filtered as data}
|
||||
{#each list as data}
|
||||
<AppObjectListItem
|
||||
isHidden={!filtered.includes(data)}
|
||||
{module}
|
||||
{subItemsComponent}
|
||||
{expandOnClick}
|
||||
@@ -63,6 +90,8 @@
|
||||
{expandIconFunc}
|
||||
{checkedObjectsStore}
|
||||
{disableContextMenu}
|
||||
{filter}
|
||||
isExpandedBySearch={childrenMatched.includes(data)}
|
||||
/>
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
import { tick } from 'svelte';
|
||||
import { plusExpandIcon } from '../icons/expandIcons';
|
||||
|
||||
export let isHidden;
|
||||
export let filter;
|
||||
export let module;
|
||||
export let data;
|
||||
export let subItemsComponent;
|
||||
@@ -18,6 +20,7 @@
|
||||
export let expandIconFunc = plusExpandIcon;
|
||||
export let checkedObjectsStore = null;
|
||||
export let disableContextMenu = false;
|
||||
export let isExpandedBySearch = false;
|
||||
|
||||
let isExpanded = false;
|
||||
|
||||
@@ -37,21 +40,23 @@
|
||||
$: if (!expandable && isExpanded) isExpanded = false;
|
||||
</script>
|
||||
|
||||
<svelte:component
|
||||
this={module.default}
|
||||
{data}
|
||||
on:click={handleExpand}
|
||||
on:expand={handleExpandButton}
|
||||
expandIcon={getExpandIcon(expandable, subItemsComponent, isExpanded, expandIconFunc)}
|
||||
{checkedObjectsStore}
|
||||
{module}
|
||||
{disableContextMenu}
|
||||
/>
|
||||
{#if !isHidden}
|
||||
<svelte:component
|
||||
this={module.default}
|
||||
{data}
|
||||
on:click={handleExpand}
|
||||
on:expand={handleExpandButton}
|
||||
expandIcon={getExpandIcon(!isExpandedBySearch && expandable, subItemsComponent, isExpanded, expandIconFunc)}
|
||||
{checkedObjectsStore}
|
||||
{module}
|
||||
{disableContextMenu}
|
||||
/>
|
||||
|
||||
{#if isExpanded && subItemsComponent}
|
||||
<div class="subitems">
|
||||
<svelte:component this={subItemsComponent} {data} />
|
||||
</div>
|
||||
{#if (isExpanded || isExpandedBySearch) && subItemsComponent}
|
||||
<div class="subitems">
|
||||
<svelte:component this={subItemsComponent} {data} {filter} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
||||
@@ -1,12 +1,22 @@
|
||||
<script context="module">
|
||||
export const extractKey = data => data._id;
|
||||
export const createMatcher = ({ displayName, server }) => filter => filterName(filter, displayName, server);
|
||||
export const createMatcher = props => filter => {
|
||||
const { _id, displayName, server } = props;
|
||||
const databases = getLocalStorage(`database_list_${_id}`) || [];
|
||||
return filterName(filter, displayName, server, ...databases.map(x => x.name));
|
||||
};
|
||||
export const createChildMatcher = props => filter => {
|
||||
if (!filter) return false;
|
||||
const { _id } = props;
|
||||
const databases = getLocalStorage(`database_list_${_id}`) || [];
|
||||
return filterName(filter, ...databases.map(x => x.name));
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { currentDatabase, extensions, getCurrentConfig, openedConnections } from '../stores';
|
||||
import { currentDatabase, extensions, getCurrentConfig, getOpenedConnections, openedConnections } from '../stores';
|
||||
import axiosInstance from '../utility/axiosInstance';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
@@ -17,6 +27,8 @@
|
||||
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import { getDatabaseList } from '../utility/metadataLoaders';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
|
||||
export let data;
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
|
||||
const electron = getElectron();
|
||||
|
||||
const icons = {
|
||||
export const databaseObjectIcons = {
|
||||
tables: 'img table',
|
||||
collections: 'img collection',
|
||||
views: 'img view',
|
||||
@@ -337,7 +337,7 @@
|
||||
{
|
||||
title: scriptTemplate ? 'Query #' : pureName,
|
||||
tooltip,
|
||||
icon: scriptTemplate ? 'img sql-file' : icons[objectTypeField],
|
||||
icon: scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField],
|
||||
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
|
||||
props: {
|
||||
schemaName,
|
||||
@@ -352,6 +352,24 @@
|
||||
{ forceNewTab }
|
||||
);
|
||||
}
|
||||
|
||||
export function handleDatabaseObjectClick(data, forceNewTab = false) {
|
||||
const { schemaName, pureName, conid, database, objectTypeField } = data;
|
||||
|
||||
openDatabaseObjectDetail(
|
||||
defaultTabs[objectTypeField],
|
||||
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
|
||||
{
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
},
|
||||
forceNewTab,
|
||||
null
|
||||
);
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -378,34 +396,7 @@
|
||||
export let data;
|
||||
|
||||
function handleClick(forceNewTab = false) {
|
||||
const { schemaName, pureName, conid, database, objectTypeField } = data;
|
||||
|
||||
openDatabaseObjectDetail(
|
||||
defaultTabs[objectTypeField],
|
||||
defaultTabs[objectTypeField] ? null : 'CREATE OBJECT',
|
||||
{
|
||||
schemaName,
|
||||
pureName,
|
||||
conid,
|
||||
database,
|
||||
objectTypeField,
|
||||
},
|
||||
forceNewTab,
|
||||
null
|
||||
);
|
||||
|
||||
// openNewTab({
|
||||
// title: data.pureName,
|
||||
// icon: 'img table',
|
||||
// tabComponent: 'TableDataTab',
|
||||
// props: {
|
||||
// schemaName,
|
||||
// pureName,
|
||||
// conid,
|
||||
// database,
|
||||
// objectTypeField,
|
||||
// },
|
||||
// });
|
||||
handleDatabaseObjectClick(data, forceNewTab);
|
||||
}
|
||||
|
||||
const getDriver = async () => {
|
||||
@@ -557,7 +548,7 @@
|
||||
module={$$props.module}
|
||||
{data}
|
||||
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
||||
icon={icons[data.objectTypeField]}
|
||||
icon={databaseObjectIcons[data.objectTypeField]}
|
||||
menu={createMenu}
|
||||
on:click={() => handleClick()}
|
||||
on:middleclick={() => handleClick(true)}
|
||||
|
||||
@@ -1,11 +1,16 @@
|
||||
<script lang="ts">
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { useDatabaseList } from '../utility/metadataLoaders';
|
||||
import AppObjectList from './AppObjectList.svelte';
|
||||
import * as databaseAppObject from './DatabaseAppObject.svelte';
|
||||
|
||||
export let filter;
|
||||
export let data;
|
||||
|
||||
$: databases = useDatabaseList({ conid: data._id });
|
||||
</script>
|
||||
|
||||
<AppObjectList list={($databases || []).map(db => ({ ...db, connection: data }))} module={databaseAppObject} />
|
||||
<AppObjectList
|
||||
list={($databases || []).filter(x => filterName(filter, x.name)).map(db => ({ ...db, connection: data }))}
|
||||
module={databaseAppObject}
|
||||
/>
|
||||
|
||||
@@ -1,17 +1,62 @@
|
||||
<script context="module">
|
||||
const electron = getElectron();
|
||||
|
||||
registerCommand({
|
||||
id: 'commandPalette.show',
|
||||
category: 'Command palette',
|
||||
name: 'Show',
|
||||
toolbarName: 'Menu',
|
||||
toolbarName: 'Menu+Search',
|
||||
toolbarOrder: 0,
|
||||
keyText: 'F1',
|
||||
toolbar: true,
|
||||
showDisabled: true,
|
||||
icon: 'icon menu',
|
||||
onClick: () => visibleCommandPalette.set(true),
|
||||
testEnabled: () => !getVisibleCommandPalette(),
|
||||
onClick: () => visibleCommandPalette.set('menu'),
|
||||
testEnabled: () => getVisibleCommandPalette() != 'menu',
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'database.search',
|
||||
category: 'Database',
|
||||
toolbarName: 'Database search',
|
||||
name: 'Search',
|
||||
keyText: electron ? 'Ctrl+P' : 'F3',
|
||||
onClick: () => visibleCommandPalette.set('database'),
|
||||
testEnabled: () => getVisibleCommandPalette() != 'database',
|
||||
});
|
||||
|
||||
function extractDbItems(db, dbConnectionInfo, connectionList) {
|
||||
const objectList = _.flatten(
|
||||
['tables', 'collections', 'views', 'matviews', 'procedures', 'functions'].map(objectTypeField =>
|
||||
_.sortBy(
|
||||
((db || {})[objectTypeField] || []).map(obj => ({
|
||||
text: obj.pureName,
|
||||
onClick: () => handleDatabaseObjectClick({ objectTypeField, ...dbConnectionInfo, ...obj }),
|
||||
icon: databaseObjectIcons[objectTypeField],
|
||||
})),
|
||||
['text']
|
||||
)
|
||||
)
|
||||
);
|
||||
const databaseList = [];
|
||||
for (const connection of connectionList || []) {
|
||||
const conid = connection._id;
|
||||
const databases = getLocalStorage(`database_list_${conid}`) || [];
|
||||
for (const db of databases) {
|
||||
databaseList.push({
|
||||
text: `${db.name} on ${getConnectionLabel(connection)}`,
|
||||
icon: 'img database',
|
||||
onClick: () => currentDatabase.set({ connection, name: db.name }),
|
||||
});
|
||||
}
|
||||
}
|
||||
return [..._.sortBy(databaseList, 'text'), ...objectList];
|
||||
|
||||
// return db?.tables?.map(table => ({
|
||||
// text: table.pureName,
|
||||
// onClick: () => handleDatabaseObjectClick({ ...dbinfo, ...table }),
|
||||
// }));
|
||||
}
|
||||
</script>
|
||||
|
||||
<script>
|
||||
@@ -19,9 +64,21 @@
|
||||
|
||||
import _ from 'lodash';
|
||||
import { onMount } from 'svelte';
|
||||
import { commands, commandsCustomized, getVisibleCommandPalette, visibleCommandPalette } from '../stores';
|
||||
import { databaseObjectIcons, handleDatabaseObjectClick } from '../appobj/DatabaseObjectAppObject.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import {
|
||||
commands,
|
||||
commandsCustomized,
|
||||
currentDatabase,
|
||||
getVisibleCommandPalette,
|
||||
visibleCommandPalette,
|
||||
} from '../stores';
|
||||
import clickOutside from '../utility/clickOutside';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import keycodes from '../utility/keycodes';
|
||||
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { getLocalStorage } from '../utility/storageCache';
|
||||
import registerCommand from './registerCommand';
|
||||
|
||||
let domInput;
|
||||
@@ -44,9 +101,17 @@
|
||||
'text'
|
||||
);
|
||||
|
||||
$: filteredItems = (parentCommand ? parentCommand.getSubCommands() : sortedComands).filter(
|
||||
x => !x.isGroupCommand && filterName(filter, x.text)
|
||||
);
|
||||
$: conid = _.get($currentDatabase, 'connection._id');
|
||||
$: database = _.get($currentDatabase, 'name');
|
||||
$: databaseInfo = useDatabaseInfo({ conid, database });
|
||||
$: connectionList = useConnectionList();
|
||||
|
||||
$: filteredItems = ($visibleCommandPalette == 'database'
|
||||
? extractDbItems($databaseInfo, { conid, database }, $connectionList)
|
||||
: parentCommand
|
||||
? parentCommand.getSubCommands()
|
||||
: sortedComands
|
||||
).filter(x => !x.isGroupCommand && filterName(filter, x.text));
|
||||
|
||||
function handleCommand(command) {
|
||||
if (command.getSubCommands) {
|
||||
@@ -55,7 +120,7 @@
|
||||
filter = '';
|
||||
selectedIndex = 0;
|
||||
} else {
|
||||
$visibleCommandPalette = false;
|
||||
$visibleCommandPalette = null;
|
||||
command.onClick();
|
||||
}
|
||||
}
|
||||
@@ -68,7 +133,7 @@
|
||||
e.stopPropagation();
|
||||
handleCommand(filteredItems[selectedIndex]);
|
||||
}
|
||||
if (e.keyCode == keycodes.escape) $visibleCommandPalette = false;
|
||||
if (e.keyCode == keycodes.escape) $visibleCommandPalette = null;
|
||||
|
||||
if (e.keyCode == keycodes.pageDown) selectedIndex = Math.min(selectedIndex + 15, filteredItems.length - 1);
|
||||
if (e.keyCode == keycodes.pageUp) selectedIndex = Math.max(selectedIndex - 15, 0);
|
||||
@@ -77,30 +142,65 @@
|
||||
$: if (domItems[selectedIndex]) domItems[selectedIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' });
|
||||
</script>
|
||||
|
||||
<div class="main" use:clickOutside on:clickOutside={() => ($visibleCommandPalette = false)}>
|
||||
<div class="search">
|
||||
<input
|
||||
type="text"
|
||||
bind:this={domInput}
|
||||
bind:value={filter}
|
||||
on:keydown={handleKeyDown}
|
||||
placeholder={parentCommand?.text || ''}
|
||||
/>
|
||||
<div
|
||||
class="main"
|
||||
use:clickOutside
|
||||
on:clickOutside={() => {
|
||||
$visibleCommandPalette = null;
|
||||
}}
|
||||
>
|
||||
<div class="pages">
|
||||
<div
|
||||
class="page"
|
||||
class:selected={$visibleCommandPalette == 'menu'}
|
||||
on:click={() => {
|
||||
$visibleCommandPalette = 'menu';
|
||||
domInput.focus();
|
||||
}}
|
||||
>
|
||||
<FontIcon icon="icon menu" /> Commands
|
||||
</div>
|
||||
<div
|
||||
class="page"
|
||||
class:selected={$visibleCommandPalette == 'database'}
|
||||
on:click={() => {
|
||||
$visibleCommandPalette = 'database';
|
||||
domInput.focus();
|
||||
}}
|
||||
>
|
||||
<FontIcon icon="icon database" /> Database
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">
|
||||
{#each filteredItems as command, index}
|
||||
<div
|
||||
class="command"
|
||||
class:selected={index == selectedIndex}
|
||||
on:click={() => handleCommand(command)}
|
||||
bind:this={domItems[index]}
|
||||
>
|
||||
<div>{command.text}</div>
|
||||
{#if command.keyText}
|
||||
<div class="shortcut">{command.keyText}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
<div class="mainInner">
|
||||
<div class="search">
|
||||
<input
|
||||
type="text"
|
||||
bind:this={domInput}
|
||||
bind:value={filter}
|
||||
on:keydown={handleKeyDown}
|
||||
placeholder={parentCommand?.text || ''}
|
||||
/>
|
||||
</div>
|
||||
<div class="content">
|
||||
{#each filteredItems as command, index}
|
||||
<div
|
||||
class="command"
|
||||
class:selected={index == selectedIndex}
|
||||
on:click={() => handleCommand(command)}
|
||||
bind:this={domItems[index]}
|
||||
>
|
||||
<div>
|
||||
{#if command.icon}
|
||||
<span class="mr-1"><FontIcon icon={command.icon} /></span>
|
||||
{/if}
|
||||
{command.text}
|
||||
</div>
|
||||
{#if command.keyText}
|
||||
<div class="shortcut">{command.keyText}</div>
|
||||
{/if}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -108,6 +208,9 @@
|
||||
.main {
|
||||
width: 500px;
|
||||
background: var(--theme-bg-2);
|
||||
}
|
||||
|
||||
.mainInner {
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
@@ -135,4 +238,19 @@
|
||||
.shortcut {
|
||||
background: var(--theme-bg-3);
|
||||
}
|
||||
|
||||
.pages {
|
||||
display: flex;
|
||||
}
|
||||
.page {
|
||||
padding: 5px;
|
||||
border: 1px solid var(--theme-border);
|
||||
cursor: pointer;
|
||||
}
|
||||
.page:hover {
|
||||
color: var(--theme-font-hover);
|
||||
}
|
||||
.page.selected {
|
||||
background: var(--theme-bg-1);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -266,6 +266,15 @@ if (hasPermission('settings/change')) {
|
||||
});
|
||||
}
|
||||
|
||||
if (electron) {
|
||||
registerCommand({
|
||||
id: 'file.exit',
|
||||
category: 'File',
|
||||
name: 'Exit',
|
||||
onClick: () => electron.remote.getCurrentWindow().close(),
|
||||
});
|
||||
}
|
||||
|
||||
export function registerFileCommands({
|
||||
idPrefix,
|
||||
category,
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
export let isNative = false;
|
||||
export let isMulti = false;
|
||||
export let notSelected = null;
|
||||
export let defaultValue = '';
|
||||
|
||||
let listOpen = false;
|
||||
let isFocused = false;
|
||||
@@ -21,18 +22,19 @@
|
||||
|
||||
{#if isNative}
|
||||
<select
|
||||
value={value || defaultValue}
|
||||
{...$$restProps}
|
||||
on:change={e => {
|
||||
dispatch('change', e.target['value']);
|
||||
}}
|
||||
>
|
||||
{#if notSelected}
|
||||
<option value="" selected={!value}>
|
||||
<option value="">
|
||||
{_.isString(notSelected) ? notSelected : '(not selected)'}
|
||||
</option>
|
||||
{/if}
|
||||
{#each _.compact(options) as x (x.value)}
|
||||
<option value={x.value} selected={value == x.value}>
|
||||
<option value={x.value}>
|
||||
{x.label}
|
||||
</option>
|
||||
{/each}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<script>
|
||||
import _ from 'lodash';
|
||||
import _, { startsWith } from 'lodash';
|
||||
import { writable } from 'svelte/store';
|
||||
import FormStyledButton from '../elements/FormStyledButton.svelte';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import FormProvider from '../forms/FormProvider.svelte';
|
||||
import FormProviderCore from '../forms/FormProviderCore.svelte';
|
||||
import FormSubmit from '../forms/FormSubmit.svelte';
|
||||
import FormValues from '../forms/FormValues.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import SqlEditor from '../query/SqlEditor.svelte';
|
||||
|
||||
@@ -15,20 +15,77 @@
|
||||
export let onConfirm;
|
||||
export let engine;
|
||||
export let recreates;
|
||||
export let deleteCascadesScripts;
|
||||
|
||||
$: isRecreated = _.sum(_.values(recreates || {})) > 0;
|
||||
const values = writable({});
|
||||
|
||||
// $: console.log('recreates', recreates);
|
||||
</script>
|
||||
|
||||
<FormProvider>
|
||||
<FormProviderCore {values}>
|
||||
<ModalBase {...$$restProps}>
|
||||
<div slot="header">Save changes</div>
|
||||
|
||||
<div class="editor">
|
||||
<SqlEditor {engine} value={sql} readOnly />
|
||||
<SqlEditor
|
||||
{engine}
|
||||
value={$values.deleteReferencesCascade
|
||||
? [
|
||||
...deleteCascadesScripts
|
||||
.filter(({ script, title }) => $values[`deleteReferencesFor_${title}`] !== false)
|
||||
.map(({ script, title }) => script),
|
||||
sql,
|
||||
].join('\n')
|
||||
: sql}
|
||||
readOnly
|
||||
/>
|
||||
</div>
|
||||
|
||||
{#if !_.isEmpty(deleteCascadesScripts)}
|
||||
<div class="mt-2">
|
||||
<FormCheckboxField
|
||||
templateProps={{ noMargin: true }}
|
||||
label="Delete references CASCADE"
|
||||
name="deleteReferencesCascade"
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if $values.deleteReferencesCascade}
|
||||
<div class="form-margin flex">
|
||||
<FormStyledButton
|
||||
value="Check all"
|
||||
on:click={() => {
|
||||
$values = _.omitBy($values, (v, k) => k.startsWith('deleteReferencesFor_'));
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton
|
||||
value="Uncheck all"
|
||||
on:click={() => {
|
||||
const newValues = { ...$values };
|
||||
for (const item of deleteCascadesScripts) {
|
||||
newValues[`deleteReferencesFor_${item.title}`] = false;
|
||||
}
|
||||
$values = newValues;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="form-margin flex flex-wrap">
|
||||
{#each _.sortBy(deleteCascadesScripts, 'title') as deleteTable}
|
||||
<div class="mr-1 nowrap">
|
||||
<FormCheckboxField
|
||||
defaultValue={true}
|
||||
templateProps={{ noMargin: true }}
|
||||
label={deleteTable.title}
|
||||
name={`deleteReferencesFor_${deleteTable.title}`}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if isRecreated}
|
||||
<div class="form-margin">
|
||||
<div>
|
||||
@@ -44,20 +101,27 @@
|
||||
{/if}
|
||||
|
||||
<div slot="footer">
|
||||
<FormValues let:values>
|
||||
<FormSubmit
|
||||
value="OK"
|
||||
disabled={isRecreated && !values.allowRecreate}
|
||||
on:click={() => {
|
||||
closeCurrentModal();
|
||||
onConfirm();
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
</FormValues>
|
||||
<FormSubmit
|
||||
value="OK"
|
||||
disabled={isRecreated && !$values.allowRecreate}
|
||||
on:click={e => {
|
||||
closeCurrentModal();
|
||||
onConfirm(
|
||||
e.detail.deleteReferencesCascade
|
||||
? [
|
||||
...deleteCascadesScripts
|
||||
.filter(({ script, title }) => e.detail[`deleteReferencesFor_${title}`] !== false)
|
||||
.map(({ script, title }) => script),
|
||||
sql,
|
||||
].join('\n')
|
||||
: null
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
||||
</div>
|
||||
</ModalBase>
|
||||
</FormProvider>
|
||||
</FormProviderCore>
|
||||
|
||||
<style>
|
||||
.editor {
|
||||
@@ -69,4 +133,8 @@
|
||||
.form-margin {
|
||||
margin: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
.flex-wrap {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
|
||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||
$: defaultDatabase = $values.defaultDatabase;
|
||||
|
||||
</script>
|
||||
|
||||
<FormSelectField
|
||||
@@ -125,6 +124,7 @@
|
||||
label="Password mode"
|
||||
isNative
|
||||
name="passwordMode"
|
||||
defaultValue="saveEncrypted"
|
||||
options={[
|
||||
{ value: 'saveEncrypted', label: 'Save and encrypt' },
|
||||
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
|
||||
@@ -154,5 +154,4 @@
|
||||
.radio :global(label) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
$: {
|
||||
if (!$values.sshMode) setFieldValue('sshMode', 'userPassword');
|
||||
if (!$values.sshPort) setFieldValue('sshPort', '22');
|
||||
if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyFile);
|
||||
if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyfile);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -43,4 +43,4 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormSelectFieldRaw {name} options={getOptions()} />
|
||||
<FormSelectFieldRaw {name} options={getOptions()} isNative />
|
||||
|
||||
@@ -35,7 +35,7 @@ export const openedConnections = writable([]);
|
||||
export const currentDatabase = writable(null);
|
||||
export const openedTabs = writableWithStorage<TabDefinition[]>([], 'openedTabs');
|
||||
export const extensions = writable<ExtensionsDirectory>(null);
|
||||
export const visibleCommandPalette = writable(false);
|
||||
export const visibleCommandPalette = writable(null);
|
||||
export const commands = writable({});
|
||||
export const currentTheme = writableWithStorage('theme-light', 'currentTheme');
|
||||
export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid);
|
||||
@@ -156,3 +156,9 @@ extensions.subscribe(value => {
|
||||
extensionsValue = value;
|
||||
});
|
||||
export const getExtensions = () => extensionsValue;
|
||||
|
||||
let openedConnectionsValue = null;
|
||||
openedConnections.subscribe(value => {
|
||||
openedConnectionsValue = value;
|
||||
});
|
||||
export const getOpenedConnections = () => openedConnectionsValue;
|
||||
|
||||
@@ -16,10 +16,10 @@
|
||||
|
||||
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
|
||||
export const allowAddToFavorites = props => true;
|
||||
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import App from '../App.svelte';
|
||||
import TableDataGrid from '../datagrid/TableDataGrid.svelte';
|
||||
import useGridConfig from '../utility/useGridConfig';
|
||||
@@ -29,6 +29,7 @@
|
||||
createChangeSet,
|
||||
createGridCache,
|
||||
createGridConfig,
|
||||
getDeleteCascades,
|
||||
TableFormViewDisplay,
|
||||
TableGridDisplay,
|
||||
} from 'dbgate-datalib';
|
||||
@@ -87,11 +88,18 @@
|
||||
export function save() {
|
||||
const driver = findEngineDriver($connection, $extensions);
|
||||
const script = changeSetToSql($changeSetStore?.value, $dbinfo);
|
||||
const deleteCascades = getDeleteCascades($changeSetStore?.value, $dbinfo);
|
||||
const sql = scriptToSql(driver, script);
|
||||
const deleteCascadesScripts = _.map(deleteCascades, ({ title, commands }) => ({
|
||||
title,
|
||||
script: scriptToSql(driver, commands),
|
||||
}));
|
||||
console.log('deleteCascadesScripts', deleteCascadesScripts);
|
||||
showModal(ConfirmSqlModal, {
|
||||
sql,
|
||||
onConfirm: () => handleConfirmSql(sql),
|
||||
onConfirm: sqlOverride => handleConfirmSql(sqlOverride || sql),
|
||||
engine: driver.engine,
|
||||
deleteCascadesScripts,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,7 +113,6 @@
|
||||
}
|
||||
|
||||
registerMenu({ command: 'tableData.save', tag: 'save' });
|
||||
|
||||
</script>
|
||||
|
||||
<TableDataGrid
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
'Sorry, DbGate has crashed again.\nDo you want to clear local user data to avoid crashing after next reload?'
|
||||
)
|
||||
) {
|
||||
localStorage.clear();
|
||||
localStorage.removeItem('openedTabs');
|
||||
try {
|
||||
await localforage.clear();
|
||||
} catch (err) {
|
||||
|
||||
@@ -32,3 +32,9 @@ export function getObjectTypeFieldLabel(objectTypeField) {
|
||||
if (objectTypeField == 'matviews') return 'Materialized Views';
|
||||
return _.startCase(objectTypeField);
|
||||
}
|
||||
|
||||
export async function asyncFilter(arr, predicate) {
|
||||
const results = await Promise.all(arr.map(predicate));
|
||||
|
||||
return arr.filter((_v, index) => results[index]);
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import getAsArray from './getAsArray';
|
||||
import { DatabaseInfo } from 'dbgate-types';
|
||||
import { derived } from 'svelte/store';
|
||||
import { extendDatabaseInfo } from 'dbgate-tools';
|
||||
import { setLocalStorage } from '../utility/storageCache';
|
||||
|
||||
const databaseInfoLoader = ({ conid, database }) => ({
|
||||
url: 'database-connections/structure',
|
||||
@@ -74,6 +75,9 @@ const databaseListLoader = ({ conid }) => ({
|
||||
url: 'server-connections/list-databases',
|
||||
params: { conid },
|
||||
reloadTrigger: `database-list-changed-${conid}`,
|
||||
onLoaded: value => {
|
||||
if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value);
|
||||
},
|
||||
});
|
||||
|
||||
const serverVersionLoader = ({ conid }) => ({
|
||||
@@ -135,7 +139,7 @@ const authTypesLoader = ({ engine }) => ({
|
||||
});
|
||||
|
||||
async function getCore(loader, args) {
|
||||
const { url, params, reloadTrigger, transform } = loader(args);
|
||||
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
|
||||
const key = stableStringify({ url, ...params });
|
||||
|
||||
async function doLoad() {
|
||||
@@ -144,7 +148,9 @@ async function getCore(loader, args) {
|
||||
url,
|
||||
params,
|
||||
});
|
||||
return (transform || (x => x))(resp.data);
|
||||
const res = (transform || (x => x))(resp.data);
|
||||
if (onLoaded) onLoaded(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
const fromCache = cacheGet(key);
|
||||
@@ -156,7 +162,7 @@ async function getCore(loader, args) {
|
||||
}
|
||||
|
||||
function useCore(loader, args) {
|
||||
const { url, params, reloadTrigger, transform } = loader(args);
|
||||
const { url, params, reloadTrigger, transform, onLoaded } = loader(args);
|
||||
const cacheKey = stableStringify({ url, ...params });
|
||||
|
||||
return {
|
||||
@@ -168,7 +174,9 @@ function useCore(loader, args) {
|
||||
params,
|
||||
url,
|
||||
});
|
||||
return (transform || (x => x))(resp.data);
|
||||
const res = (transform || (x => x))(resp.data);
|
||||
if (onLoaded) onLoaded(res);
|
||||
return res;
|
||||
}
|
||||
|
||||
if (cacheKey) {
|
||||
|
||||
22
packages/web/src/utility/storageCache.js
Normal file
22
packages/web/src/utility/storageCache.js
Normal file
@@ -0,0 +1,22 @@
|
||||
const cache = {};
|
||||
|
||||
export function getLocalStorage(key) {
|
||||
if (key in cache) return cache[key];
|
||||
const item = localStorage.getItem(key);
|
||||
if (item) {
|
||||
const res = JSON.parse(item);
|
||||
cache[key] = res;
|
||||
return res;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function setLocalStorage(key, value) {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
delete cache[key];
|
||||
}
|
||||
|
||||
export function removeLocalStorage(key) {
|
||||
localStorage.removeItem(key);
|
||||
delete cache[key];
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
<script lang="ts" context="module">
|
||||
const electron = getElectron();
|
||||
|
||||
const closeTabFunc = closeCondition => tabid => {
|
||||
openedTabs.update(files => {
|
||||
const active = files.find(x => x.tabid == tabid);
|
||||
@@ -33,6 +35,9 @@
|
||||
}))
|
||||
);
|
||||
};
|
||||
const closeCurrentTab = () => {
|
||||
closeTab(getActiveTabId());
|
||||
};
|
||||
const closeWithSameDb = closeTabFunc(
|
||||
(x, active) =>
|
||||
_.get(x, 'props.conid') == _.get(active, 'props.conid') &&
|
||||
@@ -96,6 +101,15 @@
|
||||
onClick: closeAll,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.closeTab',
|
||||
category: 'Tabs',
|
||||
name: 'Close tab',
|
||||
keyText: electron ? 'Ctrl+W' : null,
|
||||
testEnabled: () => getOpenedTabs().filter(x => !x.closedTime).length >= 1,
|
||||
onClick: closeCurrentTab,
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'tabs.addToFavorites',
|
||||
category: 'Tabs',
|
||||
@@ -120,11 +134,12 @@
|
||||
import FavoriteModal from '../modals/FavoriteModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
|
||||
import { currentDatabase, getActiveTab, getOpenedTabs, openedTabs, activeTabId } from '../stores';
|
||||
import { currentDatabase, getActiveTab, getOpenedTabs, openedTabs, activeTabId, getActiveTabId } from '../stores';
|
||||
import tabs from '../tabs';
|
||||
import { setSelectedTab } from '../utility/common';
|
||||
import contextMenu from '../utility/contextMenu';
|
||||
import getConnectionLabel from '../utility/getConnectionLabel';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
|
||||
import { duplicateTab } from '../utility/openNewTab';
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@
|
||||
|
||||
<div class="main">
|
||||
{#if !$visibleToolbar}
|
||||
<div class="wrapper mb-3" on:click={() => ($visibleCommandPalette = true)}>
|
||||
<div class="wrapper mb-3" on:click={() => ($visibleCommandPalette = 'menu')}>
|
||||
<FontIcon icon="icon menu" />
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
Reference in New Issue
Block a user