Merge branch 'master' into develop

This commit is contained in:
Jan Prochazka
2021-09-30 08:28:25 +02:00
56 changed files with 752 additions and 225 deletions

View File

@@ -19,7 +19,7 @@ jobs:
env: env:
GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js 10.x - name: Use Node.js 10.x

View File

@@ -23,7 +23,7 @@ jobs:
env: env:
GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js 10.x - name: Use Node.js 10.x

View File

@@ -21,7 +21,7 @@ jobs:
env: env:
GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js 10.x - name: Use Node.js 10.x

View File

@@ -27,7 +27,7 @@ jobs:
env: env:
GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js 10.x - name: Use Node.js 10.x

View File

@@ -27,7 +27,7 @@ jobs:
env: env:
GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: Use Node.js 10.x - name: Use Node.js 10.x

View File

@@ -15,7 +15,7 @@ jobs:
env: env:
GITHUB_CONTEXT: ${{ toJson(github) }} GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT" run: echo "$GITHUB_CONTEXT"
- uses: actions/checkout@v1 - uses: actions/checkout@v2
with: with:
fetch-depth: 1 fetch-depth: 1
- name: yarn install - name: yarn install

View File

@@ -1,5 +1,12 @@
# ChangeLog # ChangeLog
### 4.3.1
- FIXED: #173 Using key phrase for SSH key file connection
- ADDED: #172 Abiloity to quick search within database names
- ADDED: Database search added to command palette (Ctrl+P)
- FIXED: #171 fixed PostgreSQL analyser for older versions than 9.3 (matviews don't exist)
- ADDED: DELETE cascade option - ability to delete all referenced rows, when deleting rows
### 4.3.0 ### 4.3.0
- ADDED: Table structure editor - ADDED: Table structure editor
- ADDED: Index support - ADDED: Index support

View File

@@ -50,8 +50,10 @@ function buildMenu() {
commandItem('file.open'), commandItem('file.open'),
commandItem('group.save'), commandItem('group.save'),
commandItem('group.saveAs'), commandItem('group.saveAs'),
commandItem('database.search'),
{ type: 'separator' }, { type: 'separator' },
{ role: 'close' }, commandItem('tabs.closeTab'),
commandItem('file.exit'),
], ],
}, },
{ {

View File

@@ -1,6 +1,6 @@
{ {
"private": true, "private": true,
"version": "4.3.0", "version": "4.3.1",
"name": "dbgate-all", "name": "dbgate-all",
"workspaces": [ "workspaces": [
"packages/*", "packages/*",

View File

@@ -66,7 +66,7 @@
"env-cmd": "^10.1.0", "env-cmd": "^10.1.0",
"node-loader": "^1.0.2", "node-loader": "^1.0.2",
"nodemon": "^2.0.2", "nodemon": "^2.0.2",
"typescript": "^3.7.4", "typescript": "^4.4.3",
"webpack": "^4.42.0", "webpack": "^4.42.0",
"webpack-cli": "^3.3.11" "webpack-cli": "^3.3.11"
}, },

View File

@@ -14,6 +14,7 @@ let analysedStructure = null;
let lastPing = null; let lastPing = null;
let lastStatus = null; let lastStatus = null;
let analysedTime = 0; let analysedTime = 0;
let serverVersion;
async function checkedAsyncCall(promise) { async function checkedAsyncCall(promise) {
try { try {
@@ -36,7 +37,7 @@ async function handleFullRefresh() {
loadingModel = true; loadingModel = true;
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
setStatusName('loadStructure'); setStatusName('loadStructure');
analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection)); analysedStructure = await checkedAsyncCall(driver.analyseFull(systemConnection, serverVersion));
analysedTime = new Date().getTime(); analysedTime = new Date().getTime();
process.send({ msgtype: 'structure', structure: analysedStructure }); process.send({ msgtype: 'structure', structure: analysedStructure });
process.send({ msgtype: 'structureTime', analysedTime }); process.send({ msgtype: 'structureTime', analysedTime });
@@ -48,7 +49,7 @@ async function handleIncrementalRefresh(forceSend) {
loadingModel = true; loadingModel = true;
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
setStatusName('checkStructure'); setStatusName('checkStructure');
const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure)); const newStructure = await checkedAsyncCall(driver.analyseIncremental(systemConnection, analysedStructure, serverVersion));
analysedTime = new Date().getTime(); analysedTime = new Date().getTime();
if (newStructure != null) { if (newStructure != null) {
analysedStructure = newStructure; analysedStructure = newStructure;
@@ -84,6 +85,7 @@ async function readVersion() {
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
const version = await driver.getVersion(systemConnection); const version = await driver.getVersion(systemConnection);
process.send({ msgtype: 'version', version }); process.send({ msgtype: 'version', version });
serverVersion = version;
} }
async function handleConnect({ connection, structure, globalSettings }) { async function handleConnect({ connection, structure, globalSettings }) {
@@ -93,7 +95,7 @@ async function handleConnect({ connection, structure, globalSettings }) {
if (!structure) setStatusName('pending'); if (!structure) setStatusName('pending');
const driver = requireEngineDriver(storedConnection); const driver = requireEngineDriver(storedConnection);
systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection)); systemConnection = await checkedAsyncCall(connectUtility(driver, storedConnection));
readVersion(); await checkedAsyncCall(readVersion());
if (structure) { if (structure) {
analysedStructure = structure; analysedStructure = structure;
handleIncrementalRefresh(true); handleIncrementalRefresh(true);

View File

@@ -70,14 +70,14 @@ function decryptPasswordField(connection, field) {
function encryptConnection(connection) { function encryptConnection(connection) {
connection = encryptPasswordField(connection, 'password'); connection = encryptPasswordField(connection, 'password');
connection = encryptPasswordField(connection, 'sshPassword'); connection = encryptPasswordField(connection, 'sshPassword');
connection = encryptPasswordField(connection, 'sshKeyFilePassword'); connection = encryptPasswordField(connection, 'sshKeyfilePassword');
return connection; return connection;
} }
function decryptConnection(connection) { function decryptConnection(connection) {
connection = decryptPasswordField(connection, 'password'); connection = decryptPasswordField(connection, 'password');
connection = decryptPasswordField(connection, 'sshPassword'); connection = decryptPasswordField(connection, 'sshPassword');
connection = decryptPasswordField(connection, 'sshKeyFilePassword'); connection = decryptPasswordField(connection, 'sshKeyfilePassword');
return connection; return connection;
} }

View File

@@ -37,7 +37,7 @@ const platformInfo = {
environment: process.env.NODE_ENV, environment: process.env.NODE_ENV,
platform, platform,
runningInWebpack: !!process.env.WEBPACK_DEV_SERVER_URL, 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; module.exports = platformInfo;

View File

@@ -16,9 +16,9 @@ const CONNECTION_FIELDS = [
'sshLogin', 'sshLogin',
'sshPassword', 'sshPassword',
'sshMode', 'sshMode',
'sshKeyFile', 'sshKeyfile',
'sshBastionHost', 'sshBastionHost',
'sshKeyFilePassword', 'sshKeyfilePassword',
]; ];
const TUNNEL_FIELDS = [...CONNECTION_FIELDS, 'server', 'port']; const TUNNEL_FIELDS = [...CONNECTION_FIELDS, 'server', 'port'];
@@ -31,7 +31,7 @@ async function getSshConnection(connection) {
endPort: connection.sshPort || 22, endPort: connection.sshPort || 22,
bastionHost: connection.sshBastionHost || '', bastionHost: connection.sshBastionHost || '',
agentForward: connection.sshMode == 'agent', agentForward: connection.sshMode == 'agent',
passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyFilePassword : undefined, passphrase: connection.sshMode == 'keyFile' ? connection.sshKeyfilePassword : undefined,
username: connection.sshLogin, username: connection.sshLogin,
password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined, password: connection.sshMode == 'userPassword' ? connection.sshPassword : undefined,
agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined, agentSocket: connection.sshMode == 'agent' ? platformInfo.sshAuthSock : undefined,

View File

@@ -17,6 +17,6 @@
"devDependencies": { "devDependencies": {
"dbgate-types": "^4.1.1", "dbgate-types": "^4.1.1",
"@types/node": "^13.7.0", "@types/node": "^13.7.0",
"typescript": "^3.7.5" "typescript": "^4.4.3"
} }
} }

View File

@@ -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; if (!changeSet || !definition) return changeSet;
let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition); let [fieldName, existingItem] = findExistingChangeSetItem(changeSet, definition);
if (fieldName == 'deletes') { if (fieldName == 'deletes') {
@@ -213,7 +217,7 @@ function extractFields(item: ChangeSetItem, allowNulls = true): UpdateField[] {
})); }));
} }
function insertToSql( function changeSetInsertToSql(
item: ChangeSetItem, item: ChangeSetItem,
dbinfo: DatabaseInfo = null dbinfo: DatabaseInfo = null
): [AllowIdentityInsert, Insert, AllowIdentityInsert] { ): [AllowIdentityInsert, Insert, AllowIdentityInsert] {
@@ -257,7 +261,7 @@ function insertToSql(
]; ];
} }
function extractCondition(item: ChangeSetItem): Condition { export function extractChangeSetCondition(item: ChangeSetItem, alias?: string): Condition {
return { return {
conditionType: 'and', conditionType: 'and',
conditions: _.keys(item.condition).map(columnName => ({ conditions: _.keys(item.condition).map(columnName => ({
@@ -271,6 +275,7 @@ function extractCondition(item: ChangeSetItem): Condition {
pureName: item.pureName, pureName: item.pureName,
schemaName: item.schemaName, schemaName: item.schemaName,
}, },
alias,
}, },
}, },
right: { right: {
@@ -281,7 +286,7 @@ function extractCondition(item: ChangeSetItem): Condition {
}; };
} }
function updateToSql(item: ChangeSetItem): Update { function changeSetUpdateToSql(item: ChangeSetItem): Update {
return { return {
from: { from: {
name: { name: {
@@ -291,11 +296,11 @@ function updateToSql(item: ChangeSetItem): Update {
}, },
commandType: 'update', commandType: 'update',
fields: extractFields(item), fields: extractFields(item),
where: extractCondition(item), where: extractChangeSetCondition(item),
}; };
} }
function deleteToSql(item: ChangeSetItem): Delete { function changeSetDeleteToSql(item: ChangeSetItem): Delete {
return { return {
from: { from: {
name: { name: {
@@ -304,16 +309,16 @@ function deleteToSql(item: ChangeSetItem): Delete {
}, },
}, },
commandType: 'delete', commandType: 'delete',
where: extractCondition(item), where: extractChangeSetCondition(item),
}; };
} }
export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Command[] { export function changeSetToSql(changeSet: ChangeSet, dbinfo: DatabaseInfo): Command[] {
return _.compact( return _.compact(
_.flatten([ _.flatten([
...(changeSet.inserts.map(item => insertToSql(item, dbinfo)) as any), ...(changeSet.inserts.map(item => changeSetInsertToSql(item, dbinfo)) as any),
...changeSet.updates.map(updateToSql), ...changeSet.updates.map(changeSetUpdateToSql),
...changeSet.deletes.map(deleteToSql), ...changeSet.deletes.map(changeSetDeleteToSql),
]) ])
); );
} }

View 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;
}

View File

@@ -11,3 +11,4 @@ export * from './runMacro';
export * from './FormViewDisplay'; export * from './FormViewDisplay';
export * from './TableFormViewDisplay'; export * from './TableFormViewDisplay';
export * from './CollectionGridDisplay'; export * from './CollectionGridDisplay';
export * from './deleteCascade';

View File

@@ -18,7 +18,7 @@
"@types/node": "^13.7.0", "@types/node": "^13.7.0",
"jest": "^24.9.0", "jest": "^24.9.0",
"ts-jest": "^25.2.1", "ts-jest": "^25.2.1",
"typescript": "^3.7.5" "typescript": "^4.4.3"
}, },
"dependencies": { "dependencies": {
"@types/parsimmon": "^1.10.1", "@types/parsimmon": "^1.10.1",

View File

@@ -32,6 +32,6 @@
"@types/node": "^13.7.0", "@types/node": "^13.7.0",
"jest": "^24.9.0", "jest": "^24.9.0",
"ts-jest": "^25.2.1", "ts-jest": "^25.2.1",
"typescript": "^3.7.5" "typescript": "^4.4.3"
} }
} }

View File

@@ -28,7 +28,7 @@
"devDependencies": { "devDependencies": {
"@types/node": "^13.7.0", "@types/node": "^13.7.0",
"dbgate-types": "^4.1.1", "dbgate-types": "^4.1.1",
"typescript": "^3.7.5" "typescript": "^4.4.3"
}, },
"dependencies": { "dependencies": {
"lodash": "^4.17.21" "lodash": "^4.17.21"

View File

@@ -28,7 +28,7 @@
"dbgate-types": "^4.1.1", "dbgate-types": "^4.1.1",
"jest": "^24.9.0", "jest": "^24.9.0",
"ts-jest": "^25.2.1", "ts-jest": "^25.2.1",
"typescript": "^3.7.5" "typescript": "^4.4.3"
}, },
"dependencies": { "dependencies": {
"lodash": "^4.17.21", "lodash": "^4.17.21",

View File

@@ -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 _sortBy from 'lodash/sortBy';
import _groupBy from 'lodash/groupBy'; import _groupBy from 'lodash/groupBy';
import _pick from 'lodash/pick'; import _pick from 'lodash/pick';
@@ -13,8 +13,11 @@ export class DatabaseAnalyser {
modifications: DatabaseModification[]; modifications: DatabaseModification[];
singleObjectFilter: any; singleObjectFilter: any;
singleObjectId: string = null; 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() { async _runAnalysis() {
return DatabaseAnalyser.createEmptyStructure(); return DatabaseAnalyser.createEmptyStructure();

View File

@@ -17,8 +17,8 @@ export const driverBase = {
dumperClass: SqlDumper, dumperClass: SqlDumper,
dialect, dialect,
async analyseFull(pool) { async analyseFull(pool, version) {
const analyser = new this.analyserClass(pool, this); const analyser = new this.analyserClass(pool, this, version);
return analyser.fullAnalysis(); return analyser.fullAnalysis();
}, },
async analyseSingleObject(pool, name, typeField = 'tables') { async analyseSingleObject(pool, name, typeField = 'tables') {
@@ -28,8 +28,8 @@ export const driverBase = {
analyseSingleTable(pool, name) { analyseSingleTable(pool, name) {
return this.analyseSingleObject(pool, name, 'tables'); return this.analyseSingleObject(pool, name, 'tables');
}, },
async analyseIncremental(pool, structure) { async analyseIncremental(pool, structure, version) {
const analyser = new this.analyserClass(pool, this); const analyser = new this.analyserClass(pool, this, version);
return analyser.incrementalAnalysis(structure); return analyser.incrementalAnalysis(structure);
}, },
createDumper() { createDumper() {

View File

@@ -67,8 +67,8 @@ export interface EngineDriver {
name: string; name: string;
}[] }[]
>; >;
analyseFull(pool: any): Promise<DatabaseInfo>; analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
analyseIncremental(pool: any, structure: DatabaseInfo): Promise<DatabaseInfo>; analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
dialect: SqlDialect; dialect: SqlDialect;
dialectByVersion(version): SqlDialect; dialectByVersion(version): SqlDialect;
createDumper(): SqlDumper; createDumper(): SqlDumper;

View File

@@ -14,10 +14,10 @@
"devDependencies": { "devDependencies": {
"@ant-design/colors": "^5.0.0", "@ant-design/colors": "^5.0.0",
"@mdi/font": "^5.9.55", "@mdi/font": "^5.9.55",
"@rollup/plugin-commonjs": "^17.0.0", "@rollup/plugin-commonjs": "^20.0.0",
"@rollup/plugin-node-resolve": "^11.0.0", "@rollup/plugin-node-resolve": "^13.0.5",
"@rollup/plugin-replace": "^2.4.1", "@rollup/plugin-replace": "^3.0.0",
"@rollup/plugin-typescript": "^6.0.0", "@rollup/plugin-typescript": "^8.2.5",
"@tsconfig/svelte": "^1.0.0", "@tsconfig/svelte": "^1.0.0",
"ace-builds": "^1.4.8", "ace-builds": "^1.4.8",
"chart.js": "^2.9.4", "chart.js": "^2.9.4",
@@ -32,7 +32,7 @@
"lodash": "^4.17.21", "lodash": "^4.17.21",
"randomcolor": "^0.6.2", "randomcolor": "^0.6.2",
"resize-observer-polyfill": "^1.5.1", "resize-observer-polyfill": "^1.5.1",
"rollup": "^2.3.4", "rollup": "^2.57.0",
"rollup-plugin-copy": "^3.3.0", "rollup-plugin-copy": "^3.3.0",
"rollup-plugin-css-only": "^3.1.0", "rollup-plugin-css-only": "^3.1.0",
"rollup-plugin-livereload": "^2.0.0", "rollup-plugin-livereload": "^2.0.0",
@@ -41,13 +41,13 @@
"sirv-cli": "^1.0.0", "sirv-cli": "^1.0.0",
"socket.io-client": "^2.3.0", "socket.io-client": "^2.3.0",
"sql-formatter": "^2.3.3", "sql-formatter": "^2.3.3",
"svelte": "^3.35.0", "svelte": "^3.43.0",
"svelte-check": "^1.0.0", "svelte-check": "^1.0.0",
"svelte-markdown": "^0.1.4", "svelte-markdown": "^0.1.4",
"svelte-preprocess": "^4.0.0", "svelte-preprocess": "^4.9.5",
"svelte-select": "^3.17.0", "svelte-select": "^3.17.0",
"tslib": "^2.0.0", "tslib": "^2.3.1",
"typescript": "^3.9.3", "typescript": "^4.4.3",
"uuid": "^3.4.0" "uuid": "^3.4.0"
} }
} }

View File

@@ -52,8 +52,9 @@
</div> </div>
{/if} {/if}
{#each filtered as item} {#each items as item}
<AppObjectListItem <AppObjectListItem
isHidden={!item.isMatched}
{...$$restProps} {...$$restProps}
{module} {module}
data={item.data} data={item.data}

View File

@@ -1,5 +1,6 @@
<script> <script>
import _ from 'lodash'; import _ from 'lodash';
import { asyncFilter } from '../utility/common';
import AppObjectGroup from './AppObjectGroup.svelte'; import AppObjectGroup from './AppObjectGroup.svelte';
import AppObjectListItem from './AppObjectListItem.svelte'; import AppObjectListItem from './AppObjectListItem.svelte';
@@ -16,11 +17,36 @@
export let groupFunc = undefined; export let groupFunc = undefined;
$: filtered = list.filter(data => { $: filtered = !groupFunc
? list.filter(data => {
const matcher = module.createMatcher && module.createMatcher(data); const matcher = module.createMatcher && module.createMatcher(data);
if (matcher && !matcher(filter)) return false; if (matcher && !matcher(filter)) return false;
return true; 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 $: listGrouped = groupFunc
? _.compact( ? _.compact(
@@ -34,7 +60,6 @@
: null; : null;
$: groups = groupFunc ? _.groupBy(listGrouped, 'group') : null; $: groups = groupFunc ? _.groupBy(listGrouped, 'group') : null;
</script> </script>
{#if groupFunc} {#if groupFunc}
@@ -49,11 +74,13 @@
{checkedObjectsStore} {checkedObjectsStore}
{groupFunc} {groupFunc}
{disableContextMenu} {disableContextMenu}
{filter}
/> />
{/each} {/each}
{:else} {:else}
{#each filtered as data} {#each list as data}
<AppObjectListItem <AppObjectListItem
isHidden={!filtered.includes(data)}
{module} {module}
{subItemsComponent} {subItemsComponent}
{expandOnClick} {expandOnClick}
@@ -63,6 +90,8 @@
{expandIconFunc} {expandIconFunc}
{checkedObjectsStore} {checkedObjectsStore}
{disableContextMenu} {disableContextMenu}
{filter}
isExpandedBySearch={childrenMatched.includes(data)}
/> />
{/each} {/each}
{/if} {/if}

View File

@@ -10,6 +10,8 @@
import { tick } from 'svelte'; import { tick } from 'svelte';
import { plusExpandIcon } from '../icons/expandIcons'; import { plusExpandIcon } from '../icons/expandIcons';
export let isHidden;
export let filter;
export let module; export let module;
export let data; export let data;
export let subItemsComponent; export let subItemsComponent;
@@ -18,6 +20,7 @@
export let expandIconFunc = plusExpandIcon; export let expandIconFunc = plusExpandIcon;
export let checkedObjectsStore = null; export let checkedObjectsStore = null;
export let disableContextMenu = false; export let disableContextMenu = false;
export let isExpandedBySearch = false;
let isExpanded = false; let isExpanded = false;
@@ -37,21 +40,23 @@
$: if (!expandable && isExpanded) isExpanded = false; $: if (!expandable && isExpanded) isExpanded = false;
</script> </script>
<svelte:component {#if !isHidden}
<svelte:component
this={module.default} this={module.default}
{data} {data}
on:click={handleExpand} on:click={handleExpand}
on:expand={handleExpandButton} on:expand={handleExpandButton}
expandIcon={getExpandIcon(expandable, subItemsComponent, isExpanded, expandIconFunc)} expandIcon={getExpandIcon(!isExpandedBySearch && expandable, subItemsComponent, isExpanded, expandIconFunc)}
{checkedObjectsStore} {checkedObjectsStore}
{module} {module}
{disableContextMenu} {disableContextMenu}
/> />
{#if isExpanded && subItemsComponent} {#if (isExpanded || isExpandedBySearch) && subItemsComponent}
<div class="subitems"> <div class="subitems">
<svelte:component this={subItemsComponent} {data} /> <svelte:component this={subItemsComponent} {data} {filter} />
</div> </div>
{/if}
{/if} {/if}
<style> <style>

View File

@@ -1,12 +1,22 @@
<script context="module"> <script context="module">
export const extractKey = data => data._id; 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>
<script lang="ts"> <script lang="ts">
import _ from 'lodash'; import _ from 'lodash';
import AppObjectCore from './AppObjectCore.svelte'; 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 axiosInstance from '../utility/axiosInstance';
import { filterName } from 'dbgate-tools'; import { filterName } from 'dbgate-tools';
import { showModal } from '../modals/modalTools'; import { showModal } from '../modals/modalTools';
@@ -17,6 +27,8 @@
import { getDatabaseMenuItems } from './DatabaseAppObject.svelte'; import { getDatabaseMenuItems } from './DatabaseAppObject.svelte';
import getElectron from '../utility/getElectron'; import getElectron from '../utility/getElectron';
import getConnectionLabel from '../utility/getConnectionLabel'; import getConnectionLabel from '../utility/getConnectionLabel';
import { getDatabaseList } from '../utility/metadataLoaders';
import { getLocalStorage } from '../utility/storageCache';
export let data; export let data;

View File

@@ -3,7 +3,7 @@
export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName); export const createMatcher = ({ pureName }) => filter => filterName(filter, pureName);
const electron = getElectron(); const electron = getElectron();
const icons = { export const databaseObjectIcons = {
tables: 'img table', tables: 'img table',
collections: 'img collection', collections: 'img collection',
views: 'img view', views: 'img view',
@@ -337,7 +337,7 @@
{ {
title: scriptTemplate ? 'Query #' : pureName, title: scriptTemplate ? 'Query #' : pureName,
tooltip, tooltip,
icon: scriptTemplate ? 'img sql-file' : icons[objectTypeField], icon: scriptTemplate ? 'img sql-file' : databaseObjectIcons[objectTypeField],
tabComponent: scriptTemplate ? 'QueryTab' : tabComponent, tabComponent: scriptTemplate ? 'QueryTab' : tabComponent,
props: { props: {
schemaName, schemaName,
@@ -352,6 +352,24 @@
{ forceNewTab } { 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>
<script lang="ts"> <script lang="ts">
@@ -378,34 +396,7 @@
export let data; export let data;
function handleClick(forceNewTab = false) { function handleClick(forceNewTab = false) {
const { schemaName, pureName, conid, database, objectTypeField } = data; handleDatabaseObjectClick(data, forceNewTab);
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,
// },
// });
} }
const getDriver = async () => { const getDriver = async () => {
@@ -557,7 +548,7 @@
module={$$props.module} module={$$props.module}
{data} {data}
title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName} title={data.schemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
icon={icons[data.objectTypeField]} icon={databaseObjectIcons[data.objectTypeField]}
menu={createMenu} menu={createMenu}
on:click={() => handleClick()} on:click={() => handleClick()}
on:middleclick={() => handleClick(true)} on:middleclick={() => handleClick(true)}

View File

@@ -1,11 +1,16 @@
<script lang="ts"> <script lang="ts">
import { filterName } from 'dbgate-tools';
import { useDatabaseList } from '../utility/metadataLoaders'; import { useDatabaseList } from '../utility/metadataLoaders';
import AppObjectList from './AppObjectList.svelte'; import AppObjectList from './AppObjectList.svelte';
import * as databaseAppObject from './DatabaseAppObject.svelte'; import * as databaseAppObject from './DatabaseAppObject.svelte';
export let filter;
export let data; export let data;
$: databases = useDatabaseList({ conid: data._id }); $: databases = useDatabaseList({ conid: data._id });
</script> </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}
/>

View File

@@ -1,17 +1,62 @@
<script context="module"> <script context="module">
const electron = getElectron();
registerCommand({ registerCommand({
id: 'commandPalette.show', id: 'commandPalette.show',
category: 'Command palette', category: 'Command palette',
name: 'Show', name: 'Show',
toolbarName: 'Menu', toolbarName: 'Menu+Search',
toolbarOrder: 0, toolbarOrder: 0,
keyText: 'F1', keyText: 'F1',
toolbar: true, toolbar: true,
showDisabled: true, showDisabled: true,
icon: 'icon menu', icon: 'icon menu',
onClick: () => visibleCommandPalette.set(true), onClick: () => visibleCommandPalette.set('menu'),
testEnabled: () => !getVisibleCommandPalette(), 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>
<script> <script>
@@ -19,9 +64,21 @@
import _ from 'lodash'; import _ from 'lodash';
import { onMount } from 'svelte'; 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 clickOutside from '../utility/clickOutside';
import getConnectionLabel from '../utility/getConnectionLabel';
import getElectron from '../utility/getElectron';
import keycodes from '../utility/keycodes'; import keycodes from '../utility/keycodes';
import { useConnectionList, useDatabaseInfo } from '../utility/metadataLoaders';
import { getLocalStorage } from '../utility/storageCache';
import registerCommand from './registerCommand'; import registerCommand from './registerCommand';
let domInput; let domInput;
@@ -44,9 +101,17 @@
'text' 'text'
); );
$: filteredItems = (parentCommand ? parentCommand.getSubCommands() : sortedComands).filter( $: conid = _.get($currentDatabase, 'connection._id');
x => !x.isGroupCommand && filterName(filter, x.text) $: 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) { function handleCommand(command) {
if (command.getSubCommands) { if (command.getSubCommands) {
@@ -55,7 +120,7 @@
filter = ''; filter = '';
selectedIndex = 0; selectedIndex = 0;
} else { } else {
$visibleCommandPalette = false; $visibleCommandPalette = null;
command.onClick(); command.onClick();
} }
} }
@@ -68,7 +133,7 @@
e.stopPropagation(); e.stopPropagation();
handleCommand(filteredItems[selectedIndex]); 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.pageDown) selectedIndex = Math.min(selectedIndex + 15, filteredItems.length - 1);
if (e.keyCode == keycodes.pageUp) selectedIndex = Math.max(selectedIndex - 15, 0); if (e.keyCode == keycodes.pageUp) selectedIndex = Math.max(selectedIndex - 15, 0);
@@ -77,7 +142,36 @@
$: if (domItems[selectedIndex]) domItems[selectedIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' }); $: if (domItems[selectedIndex]) domItems[selectedIndex].scrollIntoView({ block: 'nearest', inline: 'nearest' });
</script> </script>
<div class="main" use:clickOutside on:clickOutside={() => ($visibleCommandPalette = false)}> <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="mainInner">
<div class="search"> <div class="search">
<input <input
type="text" type="text"
@@ -95,19 +189,28 @@
on:click={() => handleCommand(command)} on:click={() => handleCommand(command)}
bind:this={domItems[index]} bind:this={domItems[index]}
> >
<div>{command.text}</div> <div>
{#if command.icon}
<span class="mr-1"><FontIcon icon={command.icon} /></span>
{/if}
{command.text}
</div>
{#if command.keyText} {#if command.keyText}
<div class="shortcut">{command.keyText}</div> <div class="shortcut">{command.keyText}</div>
{/if} {/if}
</div> </div>
{/each} {/each}
</div> </div>
</div>
</div> </div>
<style> <style>
.main { .main {
width: 500px; width: 500px;
background: var(--theme-bg-2); background: var(--theme-bg-2);
}
.mainInner {
padding: 5px; padding: 5px;
} }
@@ -135,4 +238,19 @@
.shortcut { .shortcut {
background: var(--theme-bg-3); 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> </style>

View File

@@ -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({ export function registerFileCommands({
idPrefix, idPrefix,
category, category,

View File

@@ -10,6 +10,7 @@
export let isNative = false; export let isNative = false;
export let isMulti = false; export let isMulti = false;
export let notSelected = null; export let notSelected = null;
export let defaultValue = '';
let listOpen = false; let listOpen = false;
let isFocused = false; let isFocused = false;
@@ -21,18 +22,19 @@
{#if isNative} {#if isNative}
<select <select
value={value || defaultValue}
{...$$restProps} {...$$restProps}
on:change={e => { on:change={e => {
dispatch('change', e.target['value']); dispatch('change', e.target['value']);
}} }}
> >
{#if notSelected} {#if notSelected}
<option value="" selected={!value}> <option value="">
{_.isString(notSelected) ? notSelected : '(not selected)'} {_.isString(notSelected) ? notSelected : '(not selected)'}
</option> </option>
{/if} {/if}
{#each _.compact(options) as x (x.value)} {#each _.compact(options) as x (x.value)}
<option value={x.value} selected={value == x.value}> <option value={x.value}>
{x.label} {x.label}
</option> </option>
{/each} {/each}

View File

@@ -1,10 +1,10 @@
<script> <script>
import _ from 'lodash'; import _, { startsWith } from 'lodash';
import { writable } from 'svelte/store';
import FormStyledButton from '../elements/FormStyledButton.svelte'; import FormStyledButton from '../elements/FormStyledButton.svelte';
import FormCheckboxField from '../forms/FormCheckboxField.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 FormSubmit from '../forms/FormSubmit.svelte';
import FormValues from '../forms/FormValues.svelte';
import FontIcon from '../icons/FontIcon.svelte'; import FontIcon from '../icons/FontIcon.svelte';
import SqlEditor from '../query/SqlEditor.svelte'; import SqlEditor from '../query/SqlEditor.svelte';
@@ -15,20 +15,77 @@
export let onConfirm; export let onConfirm;
export let engine; export let engine;
export let recreates; export let recreates;
export let deleteCascadesScripts;
$: isRecreated = _.sum(_.values(recreates || {})) > 0; $: isRecreated = _.sum(_.values(recreates || {})) > 0;
const values = writable({});
// $: console.log('recreates', recreates); // $: console.log('recreates', recreates);
</script> </script>
<FormProvider> <FormProviderCore {values}>
<ModalBase {...$$restProps}> <ModalBase {...$$restProps}>
<div slot="header">Save changes</div> <div slot="header">Save changes</div>
<div class="editor"> <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> </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} {#if isRecreated}
<div class="form-margin"> <div class="form-margin">
<div> <div>
@@ -44,20 +101,27 @@
{/if} {/if}
<div slot="footer"> <div slot="footer">
<FormValues let:values>
<FormSubmit <FormSubmit
value="OK" value="OK"
disabled={isRecreated && !values.allowRecreate} disabled={isRecreated && !$values.allowRecreate}
on:click={() => { on:click={e => {
closeCurrentModal(); closeCurrentModal();
onConfirm(); 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} /> <FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
</FormValues>
</div> </div>
</ModalBase> </ModalBase>
</FormProvider> </FormProviderCore>
<style> <style>
.editor { .editor {
@@ -69,4 +133,8 @@
.form-margin { .form-margin {
margin: var(--dim-large-form-margin); margin: var(--dim-large-form-margin);
} }
.flex-wrap {
flex-wrap: wrap;
}
</style> </style>

View File

@@ -24,7 +24,6 @@
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || []; $: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
$: driver = $extensions.drivers.find(x => x.engine == engine); $: driver = $extensions.drivers.find(x => x.engine == engine);
$: defaultDatabase = $values.defaultDatabase; $: defaultDatabase = $values.defaultDatabase;
</script> </script>
<FormSelectField <FormSelectField
@@ -125,6 +124,7 @@
label="Password mode" label="Password mode"
isNative isNative
name="passwordMode" name="passwordMode"
defaultValue="saveEncrypted"
options={[ options={[
{ value: 'saveEncrypted', label: 'Save and encrypt' }, { value: 'saveEncrypted', label: 'Save and encrypt' },
{ value: 'saveRaw', label: 'Save raw (UNSAFE!!)' }, { value: 'saveRaw', label: 'Save raw (UNSAFE!!)' },
@@ -154,5 +154,4 @@
.radio :global(label) { .radio :global(label) {
margin-right: 10px; margin-right: 10px;
} }
</style> </style>

View File

@@ -22,7 +22,7 @@
$: { $: {
if (!$values.sshMode) setFieldValue('sshMode', 'userPassword'); if (!$values.sshMode) setFieldValue('sshMode', 'userPassword');
if (!$values.sshPort) setFieldValue('sshPort', '22'); if (!$values.sshPort) setFieldValue('sshPort', '22');
if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyFile); if (!$values.sshKeyfile && $platformInfo) setFieldValue('sshKeyfile', $platformInfo.defaultKeyfile);
} }
</script> </script>

View File

@@ -43,4 +43,4 @@
} }
</script> </script>
<FormSelectFieldRaw {name} options={getOptions()} /> <FormSelectFieldRaw {name} options={getOptions()} isNative />

View File

@@ -35,7 +35,7 @@ export const openedConnections = writable([]);
export const currentDatabase = writable(null); export const currentDatabase = writable(null);
export const openedTabs = writableWithStorage<TabDefinition[]>([], 'openedTabs'); export const openedTabs = writableWithStorage<TabDefinition[]>([], 'openedTabs');
export const extensions = writable<ExtensionsDirectory>(null); export const extensions = writable<ExtensionsDirectory>(null);
export const visibleCommandPalette = writable(false); export const visibleCommandPalette = writable(null);
export const commands = writable({}); export const commands = writable({});
export const currentTheme = writableWithStorage('theme-light', 'currentTheme'); export const currentTheme = writableWithStorage('theme-light', 'currentTheme');
export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid); export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid);
@@ -156,3 +156,9 @@ extensions.subscribe(value => {
extensionsValue = value; extensionsValue = value;
}); });
export const getExtensions = () => extensionsValue; export const getExtensions = () => extensionsValue;
let openedConnectionsValue = null;
openedConnections.subscribe(value => {
openedConnectionsValue = value;
});
export const getOpenedConnections = () => openedConnectionsValue;

View File

@@ -16,10 +16,10 @@
export const matchingProps = ['conid', 'database', 'schemaName', 'pureName']; export const matchingProps = ['conid', 'database', 'schemaName', 'pureName'];
export const allowAddToFavorites = props => true; export const allowAddToFavorites = props => true;
</script> </script>
<script lang="ts"> <script lang="ts">
import _ from 'lodash';
import App from '../App.svelte'; import App from '../App.svelte';
import TableDataGrid from '../datagrid/TableDataGrid.svelte'; import TableDataGrid from '../datagrid/TableDataGrid.svelte';
import useGridConfig from '../utility/useGridConfig'; import useGridConfig from '../utility/useGridConfig';
@@ -29,6 +29,7 @@
createChangeSet, createChangeSet,
createGridCache, createGridCache,
createGridConfig, createGridConfig,
getDeleteCascades,
TableFormViewDisplay, TableFormViewDisplay,
TableGridDisplay, TableGridDisplay,
} from 'dbgate-datalib'; } from 'dbgate-datalib';
@@ -87,11 +88,18 @@
export function save() { export function save() {
const driver = findEngineDriver($connection, $extensions); const driver = findEngineDriver($connection, $extensions);
const script = changeSetToSql($changeSetStore?.value, $dbinfo); const script = changeSetToSql($changeSetStore?.value, $dbinfo);
const deleteCascades = getDeleteCascades($changeSetStore?.value, $dbinfo);
const sql = scriptToSql(driver, script); const sql = scriptToSql(driver, script);
const deleteCascadesScripts = _.map(deleteCascades, ({ title, commands }) => ({
title,
script: scriptToSql(driver, commands),
}));
console.log('deleteCascadesScripts', deleteCascadesScripts);
showModal(ConfirmSqlModal, { showModal(ConfirmSqlModal, {
sql, sql,
onConfirm: () => handleConfirmSql(sql), onConfirm: sqlOverride => handleConfirmSql(sqlOverride || sql),
engine: driver.engine, engine: driver.engine,
deleteCascadesScripts,
}); });
} }
@@ -105,7 +113,6 @@
} }
registerMenu({ command: 'tableData.save', tag: 'save' }); registerMenu({ command: 'tableData.save', tag: 'save' });
</script> </script>
<TableDataGrid <TableDataGrid

View File

@@ -22,7 +22,7 @@
'Sorry, DbGate has crashed again.\nDo you want to clear local user data to avoid crashing after next reload?' '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 { try {
await localforage.clear(); await localforage.clear();
} catch (err) { } catch (err) {

View File

@@ -32,3 +32,9 @@ export function getObjectTypeFieldLabel(objectTypeField) {
if (objectTypeField == 'matviews') return 'Materialized Views'; if (objectTypeField == 'matviews') return 'Materialized Views';
return _.startCase(objectTypeField); return _.startCase(objectTypeField);
} }
export async function asyncFilter(arr, predicate) {
const results = await Promise.all(arr.map(predicate));
return arr.filter((_v, index) => results[index]);
}

View File

@@ -8,6 +8,7 @@ import getAsArray from './getAsArray';
import { DatabaseInfo } from 'dbgate-types'; import { DatabaseInfo } from 'dbgate-types';
import { derived } from 'svelte/store'; import { derived } from 'svelte/store';
import { extendDatabaseInfo } from 'dbgate-tools'; import { extendDatabaseInfo } from 'dbgate-tools';
import { setLocalStorage } from '../utility/storageCache';
const databaseInfoLoader = ({ conid, database }) => ({ const databaseInfoLoader = ({ conid, database }) => ({
url: 'database-connections/structure', url: 'database-connections/structure',
@@ -74,6 +75,9 @@ const databaseListLoader = ({ conid }) => ({
url: 'server-connections/list-databases', url: 'server-connections/list-databases',
params: { conid }, params: { conid },
reloadTrigger: `database-list-changed-${conid}`, reloadTrigger: `database-list-changed-${conid}`,
onLoaded: value => {
if (value?.length > 0) setLocalStorage(`database_list_${conid}`, value);
},
}); });
const serverVersionLoader = ({ conid }) => ({ const serverVersionLoader = ({ conid }) => ({
@@ -135,7 +139,7 @@ const authTypesLoader = ({ engine }) => ({
}); });
async function getCore(loader, args) { 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 }); const key = stableStringify({ url, ...params });
async function doLoad() { async function doLoad() {
@@ -144,7 +148,9 @@ async function getCore(loader, args) {
url, url,
params, params,
}); });
return (transform || (x => x))(resp.data); const res = (transform || (x => x))(resp.data);
if (onLoaded) onLoaded(res);
return res;
} }
const fromCache = cacheGet(key); const fromCache = cacheGet(key);
@@ -156,7 +162,7 @@ async function getCore(loader, args) {
} }
function useCore(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 }); const cacheKey = stableStringify({ url, ...params });
return { return {
@@ -168,7 +174,9 @@ function useCore(loader, args) {
params, params,
url, url,
}); });
return (transform || (x => x))(resp.data); const res = (transform || (x => x))(resp.data);
if (onLoaded) onLoaded(res);
return res;
} }
if (cacheKey) { if (cacheKey) {

View 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];
}

View File

@@ -1,4 +1,6 @@
<script lang="ts" context="module"> <script lang="ts" context="module">
const electron = getElectron();
const closeTabFunc = closeCondition => tabid => { const closeTabFunc = closeCondition => tabid => {
openedTabs.update(files => { openedTabs.update(files => {
const active = files.find(x => x.tabid == tabid); const active = files.find(x => x.tabid == tabid);
@@ -33,6 +35,9 @@
})) }))
); );
}; };
const closeCurrentTab = () => {
closeTab(getActiveTabId());
};
const closeWithSameDb = closeTabFunc( const closeWithSameDb = closeTabFunc(
(x, active) => (x, active) =>
_.get(x, 'props.conid') == _.get(active, 'props.conid') && _.get(x, 'props.conid') == _.get(active, 'props.conid') &&
@@ -96,6 +101,15 @@
onClick: closeAll, 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({ registerCommand({
id: 'tabs.addToFavorites', id: 'tabs.addToFavorites',
category: 'Tabs', category: 'Tabs',
@@ -120,11 +134,12 @@
import FavoriteModal from '../modals/FavoriteModal.svelte'; import FavoriteModal from '../modals/FavoriteModal.svelte';
import { showModal } from '../modals/modalTools'; 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 tabs from '../tabs';
import { setSelectedTab } from '../utility/common'; import { setSelectedTab } from '../utility/common';
import contextMenu from '../utility/contextMenu'; import contextMenu from '../utility/contextMenu';
import getConnectionLabel from '../utility/getConnectionLabel'; import getConnectionLabel from '../utility/getConnectionLabel';
import getElectron from '../utility/getElectron';
import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders'; import { getConnectionInfo, useConnectionList } from '../utility/metadataLoaders';
import { duplicateTab } from '../utility/openNewTab'; import { duplicateTab } from '../utility/openNewTab';

View File

@@ -66,7 +66,7 @@
<div class="main"> <div class="main">
{#if !$visibleToolbar} {#if !$visibleToolbar}
<div class="wrapper mb-3" on:click={() => ($visibleCommandPalette = true)}> <div class="wrapper mb-3" on:click={() => ($visibleCommandPalette = 'menu')}>
<FontIcon icon="icon menu" /> <FontIcon icon="icon menu" />
</div> </div>
{/if} {/if}

View File

@@ -1,9 +1,9 @@
diff --git a/node_modules/svelte/internal/index.js b/node_modules/svelte/internal/index.js diff --git a/node_modules/svelte/internal/index.js b/node_modules/svelte/internal/index.js
index ee20a17..7b6fff8 100644 index 1cce90d..6220522 100644
--- a/node_modules/svelte/internal/index.js --- a/node_modules/svelte/internal/index.js
+++ b/node_modules/svelte/internal/index.js +++ b/node_modules/svelte/internal/index.js
@@ -200,7 +200,7 @@ function insert(target, node, anchor) { @@ -374,7 +374,7 @@ function insert_hydration(target, node, anchor) {
target.insertBefore(node, anchor || null); }
} }
function detach(node) { function detach(node) {
- node.parentNode.removeChild(node); - node.parentNode.removeChild(node);
@@ -12,11 +12,11 @@ index ee20a17..7b6fff8 100644
function destroy_each(iterations, detaching) { function destroy_each(iterations, detaching) {
for (let i = 0; i < iterations.length; i += 1) { for (let i = 0; i < iterations.length; i += 1) {
diff --git a/node_modules/svelte/internal/index.mjs b/node_modules/svelte/internal/index.mjs diff --git a/node_modules/svelte/internal/index.mjs b/node_modules/svelte/internal/index.mjs
index 4146e56..f5f00c5 100644 index 6650e85..b746187 100644
--- a/node_modules/svelte/internal/index.mjs --- a/node_modules/svelte/internal/index.mjs
+++ b/node_modules/svelte/internal/index.mjs +++ b/node_modules/svelte/internal/index.mjs
@@ -196,7 +196,7 @@ function insert(target, node, anchor) { @@ -370,7 +370,7 @@ function insert_hydration(target, node, anchor) {
target.insertBefore(node, anchor || null); }
} }
function detach(node) { function detach(node) {
- node.parentNode.removeChild(node); - node.parentNode.removeChild(node);

View File

@@ -1,8 +1,8 @@
const { DatabaseAnalyser } = require('dbgate-tools'); const { DatabaseAnalyser } = require('dbgate-tools');
class Analyser extends DatabaseAnalyser { class Analyser extends DatabaseAnalyser {
constructor(pool, driver) { constructor(pool, driver, version) {
super(pool, driver); super(pool, driver, version);
} }
async _runAnalysis() { async _runAnalysis() {

View File

@@ -50,8 +50,8 @@ function getColumnInfo({
} }
class MsSqlAnalyser extends DatabaseAnalyser { class MsSqlAnalyser extends DatabaseAnalyser {
constructor(pool, driver) { constructor(pool, driver, version) {
super(pool, driver); super(pool, driver, version);
} }
createQuery(resFileName, typeFields) { createQuery(resFileName, typeFields) {

View File

@@ -29,8 +29,8 @@ function getColumnInfo({
} }
class Analyser extends DatabaseAnalyser { class Analyser extends DatabaseAnalyser {
constructor(pool, driver) { constructor(pool, driver, version) {
super(pool, driver); super(pool, driver, version);
} }
createQuery(resFileName, typeFields) { createQuery(resFileName, typeFields) {

View File

@@ -36,8 +36,8 @@ function getColumnInfo({
} }
class Analyser extends DatabaseAnalyser { class Analyser extends DatabaseAnalyser {
constructor(pool, driver) { constructor(pool, driver, version) {
super(pool, driver); super(pool, driver, version);
} }
createQuery(resFileName, typeFields) { createQuery(resFileName, typeFields) {

View File

@@ -133,10 +133,15 @@ const drivers = driverBases.map(driverBase => ({
const m = version.match(/([\d\.]+)/); const m = version.match(/([\d\.]+)/);
let versionText = null; let versionText = null;
let versionMajor = null;
let versionMinor = null;
if (m) { if (m) {
if (isCockroach) versionText = `CockroachDB ${m[1]}`; if (isCockroach) versionText = `CockroachDB ${m[1]}`;
if (isRedshift) versionText = `Redshift ${m[1]}`; if (isRedshift) versionText = `Redshift ${m[1]}`;
if (isPostgres) versionText = `PostgreSQL ${m[1]}`; if (isPostgres) versionText = `PostgreSQL ${m[1]}`;
const numbers = m[1].split('.');
if (numbers[0]) versionMajor = parseInt(numbers[0]);
if (numbers[1]) versionMinor = parseInt(numbers[1]);
} }
return { return {
@@ -145,6 +150,8 @@ const drivers = driverBases.map(driverBase => ({
isPostgres, isPostgres,
isCockroach, isCockroach,
isRedshift, isRedshift,
versionMajor,
versionMinor,
}; };
}, },
async readQuery(client, sql, structure) { async readQuery(client, sql, structure) {

View File

@@ -57,6 +57,20 @@ const postgresDriver = {
...dialect, ...dialect,
materializedViews: true, materializedViews: true,
}, },
dialectByVersion(version) {
if (version) {
return {
...dialect,
materializedViews:
version &&
version.versionMajor != null &&
version.versionMinor != null &&
(version.versionMajor > 9 || version.versionMajor == 9 || version.versionMinor >= 3),
};
}
return dialect;
},
}; };
/** @type {import('dbgate-types').EngineDriver} */ /** @type {import('dbgate-types').EngineDriver} */

View File

@@ -16,8 +16,8 @@ SELECT
`; `;
class Analyser extends DatabaseAnalyser { class Analyser extends DatabaseAnalyser {
constructor(pool, driver) { constructor(pool, driver, version) {
super(pool, driver); super(pool, driver, version);
} }
async _getFastSnapshot() { async _getFastSnapshot() {

121
yarn.lock
View File

@@ -942,10 +942,10 @@
resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71" resolved "https://registry.yarnpkg.com/@polka/url/-/url-1.0.0-next.11.tgz#aeb16f50649a91af79dbe36574b66d0f9e4d9f71"
integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA== integrity sha512-3NsZsJIA/22P3QUyrEDNA2D133H4j224twJrdipXN38dpnIOzAbUDtOwkcJ5pXmn75w7LSQDjA4tO9dm1XlqlA==
"@rollup/plugin-commonjs@^17.0.0": "@rollup/plugin-commonjs@^20.0.0":
version "17.1.0" version "20.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-17.1.0.tgz#757ec88737dffa8aa913eb392fade2e45aef2a2d" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-20.0.0.tgz#3246872dcbcb18a54aaa6277a8c7d7f1b155b745"
integrity sha512-PoMdXCw0ZyvjpCMT5aV4nkL0QywxP29sODQsSGeDpr/oI49Qq9tRtAsb/LbYbDzFlOydVEqHmmZWFtXJEAX9ew== integrity sha512-5K0g5W2Ol8hAcTHqcTBHiA7M58tfmYi1o9KxeJuuRNpGaTa5iLjcyemBitCBcKXaHamOBBEH2dGom6v6Unmqjg==
dependencies: dependencies:
"@rollup/pluginutils" "^3.1.0" "@rollup/pluginutils" "^3.1.0"
commondir "^1.0.1" commondir "^1.0.1"
@@ -955,10 +955,10 @@
magic-string "^0.25.7" magic-string "^0.25.7"
resolve "^1.17.0" resolve "^1.17.0"
"@rollup/plugin-node-resolve@^11.0.0": "@rollup/plugin-node-resolve@^13.0.5":
version "11.2.0" version "13.0.5"
resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.0.tgz#a5ab88c35bb7622d115f44984dee305112b6f714" resolved "https://registry.yarnpkg.com/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.5.tgz#016abe58796a4ff544d6beac7818921e3d3777fc"
integrity sha512-qHjNIKYt5pCcn+5RUBQxK8krhRvf1HnyVgUCcFFcweDS7fhkOLZeYh0mhHK6Ery8/bb9tvN/ubPzmfF0qjDCTA== integrity sha512-mVaw6uxtvuGx/XCI4qBQXsDZJUfyx5vp39iE0J/7Hd6wDhEbjHr6aES7Nr9yWbuE0BY+oKp6N7Bq6jX5NCGNmQ==
dependencies: dependencies:
"@rollup/pluginutils" "^3.1.0" "@rollup/pluginutils" "^3.1.0"
"@types/resolve" "1.17.1" "@types/resolve" "1.17.1"
@@ -967,18 +967,18 @@
is-module "^1.0.0" is-module "^1.0.0"
resolve "^1.19.0" resolve "^1.19.0"
"@rollup/plugin-replace@^2.4.1": "@rollup/plugin-replace@^3.0.0":
version "2.4.1" version "3.0.0"
resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-2.4.1.tgz#c411b5ab72809fb1bfc8b487d8d02eef661460d3" resolved "https://registry.yarnpkg.com/@rollup/plugin-replace/-/plugin-replace-3.0.0.tgz#3a4c9665d4e7a4ce2c360cf021232784892f3fac"
integrity sha512-XwC1oK5rrtRJ0tn1ioLHS6OV5JTluJF7QE1J/q1hN3bquwjnVxjtMyY9iCnoyH9DQbf92CxajB3o98wZbP3oAQ== integrity sha512-3c7JCbMuYXM4PbPWT4+m/4Y6U60SgsnDT/cCyAyUKwFHg7pTSfsSQzIpETha3a3ig6OdOKzZz87D9ZXIK3qsDg==
dependencies: dependencies:
"@rollup/pluginutils" "^3.1.0" "@rollup/pluginutils" "^3.1.0"
magic-string "^0.25.7" magic-string "^0.25.7"
"@rollup/plugin-typescript@^6.0.0": "@rollup/plugin-typescript@^8.2.5":
version "6.1.0" version "8.2.5"
resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-6.1.0.tgz#289e7f0ea12fd659bd13ad59dda73b9055538b83" resolved "https://registry.yarnpkg.com/@rollup/plugin-typescript/-/plugin-typescript-8.2.5.tgz#e0319761b2b5105615e5a0c371ae05bc2984b7de"
integrity sha512-hJxaiE6WyNOsK+fZpbFh9CUijZYqPQuAOWO5khaGTUkM8DYNNyA2TDlgamecE+qLOG1G1+CwbWMAx3rbqpp6xQ== integrity sha512-QL/LvDol/PAGB2O0S7/+q2HpSUNodpw7z6nGn9BfoVCPOZ0r4EALrojFU29Bkoi2Hr2jgTocTejJ5GGWZfOxbQ==
dependencies: dependencies:
"@rollup/pluginutils" "^3.1.0" "@rollup/pluginutils" "^3.1.0"
resolve "^1.17.0" resolve "^1.17.0"
@@ -2310,6 +2310,11 @@ bson@^1.1.4:
resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a" resolved "https://registry.yarnpkg.com/bson/-/bson-1.1.6.tgz#fb819be9a60cd677e0853aee4ca712a785d6618a"
integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg== integrity sha512-EvVNVeGo4tHxwi8L6bPj3y3itEvStdwvvlojVxxbyYfoaxJ6keLgrTuKdyfEAszFK+H3olzBuafE0yoh0D1gdg==
buffer-crc32@^0.2.5:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=
buffer-equal-constant-time@1.0.1: buffer-equal-constant-time@1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@@ -3705,6 +3710,11 @@ es-to-primitive@^1.2.1:
is-date-object "^1.0.1" is-date-object "^1.0.1"
is-symbol "^1.0.2" is-symbol "^1.0.2"
es6-promise@^3.1.2:
version "3.3.1"
resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-3.3.1.tgz#a08cdde84ccdbf34d027a1451bc91d4bcd28a613"
integrity sha1-oIzd6EzNvzTQJ6FFG8kdS80ophM=
escalade@^3.1.1: escalade@^3.1.1:
version "3.1.1" version "3.1.1"
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
@@ -4448,7 +4458,7 @@ fsevents@^1.2.7:
bindings "^1.5.0" bindings "^1.5.0"
nan "^2.12.1" nan "^2.12.1"
fsevents@^2.3.2, fsevents@~2.3.1: fsevents@^2.3.2, fsevents@~2.3.1, fsevents@~2.3.2:
version "2.3.2" version "2.3.2"
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
@@ -4715,6 +4725,11 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==
graceful-fs@^4.1.3:
version "4.2.8"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.8.tgz#e412b8d33f5e006593cbd3cee6df9f2cebbe802a"
integrity sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==
graceful-fs@^4.2.3, graceful-fs@^4.2.4: graceful-fs@^4.2.3, graceful-fs@^4.2.4:
version "4.2.6" version "4.2.6"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.6.tgz#ff040b2b0853b23c3d31027523706f1885d76bee"
@@ -9037,7 +9052,7 @@ rimraf@2.6.3:
dependencies: dependencies:
glob "^7.1.3" glob "^7.1.3"
rimraf@^2.5.4, rimraf@^2.6.3: rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.3:
version "2.7.1" version "2.7.1"
resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec"
integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==
@@ -9109,12 +9124,12 @@ rollup-pluginutils@^2.8.2:
dependencies: dependencies:
estree-walker "^0.6.1" estree-walker "^0.6.1"
rollup@^2.3.4: rollup@^2.57.0:
version "2.39.0" version "2.57.0"
resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.39.0.tgz#be4f98c9e421793a8fec82c854fb567c35e22ab6" resolved "https://registry.yarnpkg.com/rollup/-/rollup-2.57.0.tgz#c1694475eb22e1022477c0f4635fd0ac80713173"
integrity sha512-+WR3bttcq7zE+BntH09UxaW3bQo3vItuYeLsyk4dL2tuwbeSKJuvwiawyhEnvRdRgrII0Uzk00FpctHO/zB1kw== integrity sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==
optionalDependencies: optionalDependencies:
fsevents "~2.3.1" fsevents "~2.3.2"
rsvp@^4.8.4: rsvp@^4.8.4:
version "4.8.5" version "4.8.5"
@@ -9178,6 +9193,16 @@ safe-regex@^1.1.0:
resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
sander@^0.5.0:
version "0.5.1"
resolved "https://registry.yarnpkg.com/sander/-/sander-0.5.1.tgz#741e245e231f07cafb6fdf0f133adfa216a502ad"
integrity sha1-dB4kXiMfB8r7b98PEzrfohalAq0=
dependencies:
es6-promise "^3.1.2"
graceful-fs "^4.1.3"
mkdirp "^0.5.1"
rimraf "^2.5.2"
sane@^4.0.3: sane@^4.0.3:
version "4.1.0" version "4.1.0"
resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded" resolved "https://registry.yarnpkg.com/sane/-/sane-4.1.0.tgz#ed881fd922733a6c461bc189dc2b6c006f3ffded"
@@ -9562,6 +9587,16 @@ socks@^2.3.3:
ip "^1.1.5" ip "^1.1.5"
smart-buffer "^4.1.0" smart-buffer "^4.1.0"
sorcery@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/sorcery/-/sorcery-0.10.0.tgz#8ae90ad7d7cb05fc59f1ab0c637845d5c15a52b7"
integrity sha1-iukK19fLBfxZ8asMY3hF1cFaUrc=
dependencies:
buffer-crc32 "^0.2.5"
minimist "^1.2.0"
sander "^0.5.0"
sourcemap-codec "^1.3.0"
source-list-map@^2.0.0: source-list-map@^2.0.0:
version "2.0.1" version "2.0.1"
resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34"
@@ -9614,7 +9649,7 @@ source-map@^0.7.3, source-map@~0.7.2:
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383"
integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==
sourcemap-codec@^1.4.4: sourcemap-codec@^1.3.0, sourcemap-codec@^1.4.4:
version "1.4.8" version "1.4.8"
resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4"
integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==
@@ -10076,15 +10111,27 @@ svelte-preprocess@^4.0.0:
detect-indent "^6.0.0" detect-indent "^6.0.0"
strip-indent "^3.0.0" strip-indent "^3.0.0"
svelte-preprocess@^4.9.5:
version "4.9.5"
resolved "https://registry.yarnpkg.com/svelte-preprocess/-/svelte-preprocess-4.9.5.tgz#e11bdf3fcdacbd90188cdf29a7371030991f9eba"
integrity sha512-RbJbtuwKbuZq9RyzlljZUmmFNaojrg/zUEyDrS8io7haTcuITQmE4NERx8qiqHreApo6cQst5Qtp4MxUwr58Ew==
dependencies:
"@types/pug" "^2.0.4"
"@types/sass" "^1.16.0"
detect-indent "^6.0.0"
magic-string "^0.25.7"
sorcery "^0.10.0"
strip-indent "^3.0.0"
svelte-select@^3.17.0: svelte-select@^3.17.0:
version "3.17.0" version "3.17.0"
resolved "https://registry.yarnpkg.com/svelte-select/-/svelte-select-3.17.0.tgz#6bea0cb8d0c9465d28a2bac562f2de8a61f48a9f" resolved "https://registry.yarnpkg.com/svelte-select/-/svelte-select-3.17.0.tgz#6bea0cb8d0c9465d28a2bac562f2de8a61f48a9f"
integrity sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA== integrity sha512-ITmX/XUiSdkaILmsTviKRkZPaXckM5/FA7Y8BhiUPoamaZG/ZDyOo6ydjFu9fDVFTbwoAUGUi6HBjs+ZdK2AwA==
svelte@^3.35.0: svelte@^3.43.0:
version "3.35.0" version "3.43.0"
resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.35.0.tgz#e0d0ba60c4852181c2b4fd851194be6fda493e65" resolved "https://registry.yarnpkg.com/svelte/-/svelte-3.43.0.tgz#d28d06ec523bf0cea3de15558a3241c339a85762"
integrity sha512-gknlZkR2sXheu/X+B7dDImwANVvK1R0QGQLd8CNIfxxGPeXBmePnxfzb6fWwTQRsYQG7lYkZXvpXJvxvpsoB7g== integrity sha512-T2pMPHrxXp+SM8pLLUXLQgkdo+JhTls7aqj9cD7z8wT2ccP+OrCAmtQS7h6pvMjitaZhXFNnCK582NxDpy8HSw==
symbol-tree@^3.2.2, symbol-tree@^3.2.4: symbol-tree@^3.2.2, symbol-tree@^3.2.4:
version "3.2.4" version "3.2.4"
@@ -10468,6 +10515,11 @@ tslib@^2.0.0, tslib@^2.0.3:
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.1.0.tgz#da60860f1c2ecaa5703ab7d39bc05b6bf988b97a"
integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A== integrity sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==
tslib@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.3.1.tgz#e8a335add5ceae51aa261d32a490158ef042ef01"
integrity sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw==
tty-browserify@0.0.0: tty-browserify@0.0.0:
version "0.0.0" version "0.0.0"
resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6"
@@ -10542,15 +10594,10 @@ typescript@*:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.1.5.tgz#123a3b214aaff3be32926f0d8f1f6e704eb89a72"
integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA== integrity sha512-6OSu9PTIzmn9TCDiovULTnET6BgXtDYL4Gg4szY+cGsc3JP1dQL8qvE8kShTRx1NIw4Q9IBHlwODjkjWEtMUyA==
typescript@^3.7.4, typescript@^3.7.5: typescript@^4.4.3:
version "3.7.5" version "4.4.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.5.tgz#0692e21f65fd4108b9330238aac11dd2e177a1ae" resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.4.3.tgz#bdc5407caa2b109efd4f82fe130656f977a29324"
integrity sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw== integrity sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==
typescript@^3.9.3:
version "3.9.9"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.9.9.tgz#e69905c54bc0681d0518bd4d587cc6f2d0b1a674"
integrity sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
tz-offset@0.0.1: tz-offset@0.0.1:
version "0.0.1" version "0.0.1"