mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-23 02:16:02 +00:00
Merge branch 'master' into feature/mongosh
This commit is contained in:
2
.github/workflows/build-app-pro-beta.yaml
vendored
2
.github/workflows/build-app-pro-beta.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-app-pro.yaml
vendored
2
.github/workflows/build-app-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-cloud-pro.yaml
vendored
2
.github/workflows/build-cloud-pro.yaml
vendored
@@ -39,7 +39,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-docker-pro.yaml
vendored
2
.github/workflows/build-docker-pro.yaml
vendored
@@ -44,7 +44,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
7
.github/workflows/build-npm-pro.yaml
vendored
7
.github/workflows/build-npm-pro.yaml
vendored
@@ -32,7 +32,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
@@ -98,3 +98,8 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
cd dbgate-merged/plugins/dbgate-plugin-cosmosdb
|
cd dbgate-merged/plugins/dbgate-plugin-cosmosdb
|
||||||
npm publish
|
npm publish
|
||||||
|
- name: Publish dbgate-plugin-firestore
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cd dbgate-merged/plugins/dbgate-plugin-firestore
|
||||||
|
npm publish
|
||||||
|
|||||||
2
.github/workflows/e2e-pro.yaml
vendored
2
.github/workflows/e2e-pro.yaml
vendored
@@ -26,7 +26,7 @@ jobs:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
57
.vscode/launch.json
vendored
57
.vscode/launch.json
vendored
@@ -1,20 +1,41 @@
|
|||||||
{
|
{
|
||||||
// Use IntelliSense to learn about possible attributes.
|
"version": "0.2.0",
|
||||||
// Hover to view descriptions of existing attributes.
|
"configurations": [
|
||||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
{
|
||||||
"version": "0.2.0",
|
"name": "Debug App",
|
||||||
"configurations": [
|
"type": "node",
|
||||||
{
|
"request": "launch",
|
||||||
"type": "node",
|
"program": "${workspaceFolder}/packages/api/src/index.js",
|
||||||
"request": "launch",
|
"envFile": "${workspaceFolder}/packages/api/.env",
|
||||||
"name": "Launch API",
|
"args": ["--listen-api"],
|
||||||
"skipFiles": [
|
"console": "integratedTerminal",
|
||||||
"<node_internals>/**"
|
"restart": true,
|
||||||
],
|
"runtimeExecutable": "node",
|
||||||
"program": "${workspaceFolder}/packages/api/src/index.js",
|
"skipFiles": ["<node_internals>/**"]
|
||||||
"outFiles": [
|
},
|
||||||
"${workspaceFolder}/**/*.js"
|
{
|
||||||
]
|
"name": "Debug App (Break on Start)",
|
||||||
}
|
"type": "node",
|
||||||
]
|
"request": "launch",
|
||||||
|
"program": "${workspaceFolder}/packages/api/src/index.js",
|
||||||
|
"args": ["--listen-api"],
|
||||||
|
"envFile": "${workspaceFolder}/.env",
|
||||||
|
"console": "integratedTerminal",
|
||||||
|
"restart": true,
|
||||||
|
"runtimeExecutable": "node",
|
||||||
|
"skipFiles": ["<node_internals>/**"],
|
||||||
|
"stopOnEntry": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Attach to Process",
|
||||||
|
"type": "node",
|
||||||
|
"request": "attach",
|
||||||
|
"port": 9229,
|
||||||
|
"restart": true,
|
||||||
|
"localRoot": "${workspaceFolder}",
|
||||||
|
"remoteRoot": "${workspaceFolder}",
|
||||||
|
"skipFiles": ["<node_internals>/**"]
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -303,7 +303,8 @@ describe('Data browser data', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('Plugin tab', () => {
|
it('Plugin tab', () => {
|
||||||
cy.testid('WidgetIconPanel_plugins').click();
|
cy.testid('WidgetIconPanel_settings').click();
|
||||||
|
cy.contains('Manage plugins').click();
|
||||||
cy.contains('dbgate-plugin-theme-total-white').click();
|
cy.contains('dbgate-plugin-theme-total-white').click();
|
||||||
// text from plugin markdown
|
// text from plugin markdown
|
||||||
cy.contains('Total white theme');
|
cy.contains('Total white theme');
|
||||||
@@ -380,20 +381,25 @@ describe('Data browser data', () => {
|
|||||||
cy.themeshot('compare-database-settings');
|
cy.themeshot('compare-database-settings');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Query editor - AI assistant', () => {
|
it('Database chat', () => {
|
||||||
cy.contains('MySql-connection').click();
|
cy.contains('MySql-connection').click();
|
||||||
cy.contains('MyChinook').click();
|
cy.contains('MyChinook').click();
|
||||||
cy.testid('TabsPanel_buttonNewObject').click();
|
cy.testid('TabsPanel_buttonNewObject').click();
|
||||||
cy.testid('NewObjectModal_query').click();
|
cy.testid('NewObjectModal_databaseChat').click();
|
||||||
cy.testid('QueryTab_switchAiAssistantButton').click();
|
cy.wait(1000);
|
||||||
cy.testid('QueryAiAssistant_allowSendToAiServiceButton').click();
|
cy.get('body').realType('find most popular artist');
|
||||||
cy.testid('ConfirmModal_okButton').click();
|
cy.get('body').realPress('{enter}');
|
||||||
cy.testid('QueryAiAssistant_promptInput').type('album names');
|
cy.testid('DatabaseChatTab_executeAllQueries', { timeout: 20000 }).click();
|
||||||
cy.testid('QueryAiAssistant_queryFromQuestionButton').click();
|
cy.wait(4000);
|
||||||
cy.contains('Use this', { timeout: 10000 }).click();
|
// cy.contains('Iron Maiden');
|
||||||
cy.testid('QueryTab_executeButton').click();
|
cy.themeshot('database-chat');
|
||||||
cy.contains('Balls to the Wall');
|
|
||||||
cy.themeshot('ai-assistant');
|
// cy.testid('DatabaseChatTab_promptInput').click();
|
||||||
|
// cy.get('body').realType('I need top 10 songs with the biggest income');
|
||||||
|
// cy.get('body').realPress('{enter}');
|
||||||
|
// cy.contains('Hot Girl', { timeout: 20000 });
|
||||||
|
// cy.wait(1000);
|
||||||
|
// cy.themeshot('database-chat');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('Modify data', () => {
|
it('Modify data', () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "6.5.6",
|
"version": "6.5.7-premium-beta.3",
|
||||||
"name": "dbgate-all",
|
"name": "dbgate-all",
|
||||||
"workspaces": [
|
"workspaces": [
|
||||||
"packages/*",
|
"packages/*",
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ DEVMODE=1
|
|||||||
SHELL_SCRIPTING=1
|
SHELL_SCRIPTING=1
|
||||||
ALLOW_DBGATE_PRIVATE_CLOUD=1
|
ALLOW_DBGATE_PRIVATE_CLOUD=1
|
||||||
DEVWEB=1
|
DEVWEB=1
|
||||||
|
# LOCAL_AI_GATEWAY=true
|
||||||
|
|
||||||
# REDIRECT_TO_DBGATE_CLOUD_LOGIN=1
|
# REDIRECT_TO_DBGATE_CLOUD_LOGIN=1
|
||||||
# PROD_DBGATE_CLOUD=1
|
# PROD_DBGATE_CLOUD=1
|
||||||
# PROD_DBGATE_IDENTITY=1
|
# PROD_DBGATE_IDENTITY=1
|
||||||
@@ -14,7 +16,6 @@ DEVWEB=1
|
|||||||
# DISABLE_SHELL=1
|
# DISABLE_SHELL=1
|
||||||
# HIDE_APP_EDITOR=1
|
# HIDE_APP_EDITOR=1
|
||||||
|
|
||||||
|
|
||||||
# DEVWEB=1
|
# DEVWEB=1
|
||||||
# LOGINS=admin,test
|
# LOGINS=admin,test
|
||||||
|
|
||||||
|
|||||||
@@ -68,6 +68,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "env-cmd -f .env node src/index.js --listen-api",
|
"start": "env-cmd -f .env node src/index.js --listen-api",
|
||||||
|
"start:debug": "env-cmd -f .env node --inspect src/index.js --listen-api",
|
||||||
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
"start:portal": "env-cmd -f env/portal/.env node src/index.js --listen-api",
|
||||||
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
"start:singledb": "env-cmd -f env/singledb/.env node src/index.js --listen-api",
|
||||||
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
"start:auth": "env-cmd -f env/auth/.env node src/index.js --listen-api",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ const { getConnectionLabel, getLogger, extractErrorLogData } = require('dbgate-t
|
|||||||
const logger = getLogger('cloud');
|
const logger = getLogger('cloud');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const fs = require('fs-extra');
|
const fs = require('fs-extra');
|
||||||
|
const { getAiGatewayServer } = require('../utility/authProxy');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
publicFiles_meta: true,
|
publicFiles_meta: true,
|
||||||
@@ -276,4 +277,17 @@ module.exports = {
|
|||||||
const resp = await callCloudApiPost(`content-folders/remove-user/${folid}`, { email });
|
const resp = await callCloudApiPost(`content-folders/remove-user/${folid}`, { email });
|
||||||
return resp;
|
return resp;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
getAiGateway_meta: true,
|
||||||
|
async getAiGateway() {
|
||||||
|
return getAiGatewayServer();
|
||||||
|
},
|
||||||
|
|
||||||
|
// chatStream_meta: {
|
||||||
|
// raw: true,
|
||||||
|
// method: 'post',
|
||||||
|
// },
|
||||||
|
// chatStream(req, res) {
|
||||||
|
// callChatStream(req.body, res);
|
||||||
|
// },
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -46,6 +46,10 @@ async function tryToGetRefreshedLicense(oldLicenseKey) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getAiGatewayServer() {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isAuthProxySupported,
|
isAuthProxySupported,
|
||||||
authProxyGetRedirectUrl,
|
authProxyGetRedirectUrl,
|
||||||
@@ -59,4 +63,5 @@ module.exports = {
|
|||||||
callRefactorSqlQueryApi,
|
callRefactorSqlQueryApi,
|
||||||
getLicenseHttpHeaders,
|
getLicenseHttpHeaders,
|
||||||
tryToGetRefreshedLicense,
|
tryToGetRefreshedLicense,
|
||||||
|
getAiGatewayServer,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ function getDisplayColumn(basePath, columnName, display: CollectionGridDisplay)
|
|||||||
isPartitionKey: !!display?.collection?.partitionKey?.find(x => x.columnName == uniqueName),
|
isPartitionKey: !!display?.collection?.partitionKey?.find(x => x.columnName == uniqueName),
|
||||||
isClusterKey: !!display?.collection?.clusterKey?.find(x => x.columnName == uniqueName),
|
isClusterKey: !!display?.collection?.clusterKey?.find(x => x.columnName == uniqueName),
|
||||||
isUniqueKey: !!display?.collection?.uniqueKey?.find(x => x.columnName == uniqueName),
|
isUniqueKey: !!display?.collection?.uniqueKey?.find(x => x.columnName == uniqueName),
|
||||||
|
hasAutoValue: !!display?.collection?.autoValueColumns?.find(x => x.columnName == uniqueName),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,11 +6,13 @@ import { hexStringToArray, parseNumberSafe } from 'dbgate-tools';
|
|||||||
import { FilterBehaviour, TransformType } from 'dbgate-types';
|
import { FilterBehaviour, TransformType } from 'dbgate-types';
|
||||||
|
|
||||||
const binaryCondition =
|
const binaryCondition =
|
||||||
(operator, numberDualTesting = false) =>
|
(operator, filterBehaviour: FilterBehaviour = {}) =>
|
||||||
value => {
|
value => {
|
||||||
|
const { passNumbers, allowNumberDualTesting } = filterBehaviour;
|
||||||
const numValue = parseNumberSafe(value);
|
const numValue = parseNumberSafe(value);
|
||||||
|
|
||||||
if (
|
if (
|
||||||
numberDualTesting &&
|
allowNumberDualTesting &&
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
!isNaN(numValue)
|
!isNaN(numValue)
|
||||||
) {
|
) {
|
||||||
@@ -43,6 +45,21 @@ const binaryCondition =
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
if (passNumbers && !isNaN(numValue)) {
|
||||||
|
return {
|
||||||
|
conditionType: 'binary',
|
||||||
|
operator,
|
||||||
|
left: {
|
||||||
|
exprType: 'placeholder',
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
exprType: 'value',
|
||||||
|
value: numValue,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
conditionType: 'binary',
|
conditionType: 'binary',
|
||||||
operator,
|
operator,
|
||||||
@@ -462,18 +479,18 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
|
|||||||
null: () => word('NULL').map(unaryCondition('isNull')),
|
null: () => word('NULL').map(unaryCondition('isNull')),
|
||||||
isEmpty: r => r.empty.map(unaryCondition('isEmpty')),
|
isEmpty: r => r.empty.map(unaryCondition('isEmpty')),
|
||||||
isNotEmpty: r => r.not.then(r.empty).map(unaryCondition('isNotEmpty')),
|
isNotEmpty: r => r.not.then(r.empty).map(unaryCondition('isNotEmpty')),
|
||||||
true: () => P.regexp(/true/i).map(binaryFixedValueCondition('1')),
|
true: () => P.regexp(/true/i).map(binaryFixedValueCondition(filterBehaviour.passBooleans ? true : '1')),
|
||||||
false: () => P.regexp(/false/i).map(binaryFixedValueCondition('0')),
|
false: () => P.regexp(/false/i).map(binaryFixedValueCondition(filterBehaviour.passBooleans ? false : '0')),
|
||||||
trueNum: () => word('1').map(binaryFixedValueCondition('1')),
|
trueNum: () => word('1').map(binaryFixedValueCondition('1')),
|
||||||
falseNum: () => word('0').map(binaryFixedValueCondition('0')),
|
falseNum: () => word('0').map(binaryFixedValueCondition('0')),
|
||||||
|
|
||||||
eq: r => word('=').then(r.value).map(binaryCondition('=', filterBehaviour.allowNumberDualTesting)),
|
eq: r => word('=').then(r.value).map(binaryCondition('=', filterBehaviour)),
|
||||||
ne: r => word('!=').then(r.value).map(binaryCondition('<>', filterBehaviour.allowNumberDualTesting)),
|
ne: r => word('!=').then(r.value).map(binaryCondition('<>', filterBehaviour)),
|
||||||
ne2: r => word('<>').then(r.value).map(binaryCondition('<>', filterBehaviour.allowNumberDualTesting)),
|
ne2: r => word('<>').then(r.value).map(binaryCondition('<>', filterBehaviour)),
|
||||||
le: r => word('<=').then(r.value).map(binaryCondition('<=', filterBehaviour.allowNumberDualTesting)),
|
le: r => word('<=').then(r.value).map(binaryCondition('<=', filterBehaviour)),
|
||||||
ge: r => word('>=').then(r.value).map(binaryCondition('>=', filterBehaviour.allowNumberDualTesting)),
|
ge: r => word('>=').then(r.value).map(binaryCondition('>=', filterBehaviour)),
|
||||||
lt: r => word('<').then(r.value).map(binaryCondition('<', filterBehaviour.allowNumberDualTesting)),
|
lt: r => word('<').then(r.value).map(binaryCondition('<', filterBehaviour)),
|
||||||
gt: r => word('>').then(r.value).map(binaryCondition('>', filterBehaviour.allowNumberDualTesting)),
|
gt: r => word('>').then(r.value).map(binaryCondition('>', filterBehaviour)),
|
||||||
startsWith: r => word('^').then(r.value).map(likeCondition('like', '#VALUE#%')),
|
startsWith: r => word('^').then(r.value).map(likeCondition('like', '#VALUE#%')),
|
||||||
endsWith: r => word('$').then(r.value).map(likeCondition('like', '%#VALUE#')),
|
endsWith: r => word('$').then(r.value).map(likeCondition('like', '%#VALUE#')),
|
||||||
contains: r => word('+').then(r.value).map(likeCondition('like', '%#VALUE#%')),
|
contains: r => word('+').then(r.value).map(likeCondition('like', '%#VALUE#%')),
|
||||||
@@ -526,8 +543,12 @@ const createParser = (filterBehaviour: FilterBehaviour) => {
|
|||||||
allowedElements.push('exists', 'notExists');
|
allowedElements.push('exists', 'notExists');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterBehaviour.supportArrayTesting) {
|
if (filterBehaviour.supportEmptyArrayTesting) {
|
||||||
allowedElements.push('emptyArray', 'notEmptyArray');
|
allowedElements.push('emptyArray');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filterBehaviour.supportNotEmptyArrayTesting) {
|
||||||
|
allowedElements.push('notEmptyArray');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterBehaviour.supportNullTesting) {
|
if (filterBehaviour.supportNullTesting) {
|
||||||
|
|||||||
@@ -42,8 +42,7 @@ function areDifferentRowCounts(db1: DatabaseInfo, db2: DatabaseInfo) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
export class DatabaseAnalyser<TClient = any> {
|
||||||
export class DatabaseAnalyser {
|
|
||||||
structure: DatabaseInfo;
|
structure: DatabaseInfo;
|
||||||
modifications: DatabaseModification[];
|
modifications: DatabaseModification[];
|
||||||
singleObjectFilter: any;
|
singleObjectFilter: any;
|
||||||
@@ -51,7 +50,7 @@ export class DatabaseAnalyser {
|
|||||||
dialect: SqlDialect;
|
dialect: SqlDialect;
|
||||||
logger: Logger;
|
logger: Logger;
|
||||||
|
|
||||||
constructor(public dbhan: DatabaseHandle, public driver: EngineDriver, version) {
|
constructor(public dbhan: DatabaseHandle<TClient>, public driver: EngineDriver, version) {
|
||||||
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(version)) || driver?.dialect;
|
this.dialect = (driver?.dialectByVersion && driver?.dialectByVersion(version)) || driver?.dialect;
|
||||||
this.logger = logger;
|
this.logger = logger;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ export const stringFilterBehaviour: FilterBehaviour = {
|
|||||||
export const logicalFilterBehaviour: FilterBehaviour = {
|
export const logicalFilterBehaviour: FilterBehaviour = {
|
||||||
supportBooleanValues: true,
|
supportBooleanValues: true,
|
||||||
supportNullTesting: true,
|
supportNullTesting: true,
|
||||||
|
supportBooleanOrNull: true,
|
||||||
supportSqlCondition: true,
|
supportSqlCondition: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -36,7 +37,8 @@ export const datetimeFilterBehaviour: FilterBehaviour = {
|
|||||||
|
|
||||||
export const mongoFilterBehaviour: FilterBehaviour = {
|
export const mongoFilterBehaviour: FilterBehaviour = {
|
||||||
supportEquals: true,
|
supportEquals: true,
|
||||||
supportArrayTesting: true,
|
supportEmptyArrayTesting: true,
|
||||||
|
supportNotEmptyArrayTesting: true,
|
||||||
supportNumberLikeComparison: true,
|
supportNumberLikeComparison: true,
|
||||||
supportStringInclusion: true,
|
supportStringInclusion: true,
|
||||||
supportBooleanValues: true,
|
supportBooleanValues: true,
|
||||||
@@ -57,11 +59,38 @@ export const evalFilterBehaviour: FilterBehaviour = {
|
|||||||
allowStringToken: true,
|
allowStringToken: true,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const firestoreFilterBehaviours: FilterBehaviour = {
|
||||||
|
supportEquals: true,
|
||||||
|
supportEmpty: false,
|
||||||
|
supportNumberLikeComparison: true,
|
||||||
|
supportDatetimeComparison: false,
|
||||||
|
supportNullTesting: true,
|
||||||
|
supportBooleanValues: true,
|
||||||
|
supportEmptyArrayTesting: true,
|
||||||
|
|
||||||
|
supportStringInclusion: false,
|
||||||
|
supportDatetimeSymbols: false,
|
||||||
|
supportExistsTesting: false,
|
||||||
|
supportSqlCondition: false,
|
||||||
|
|
||||||
|
allowStringToken: true,
|
||||||
|
allowNumberToken: true,
|
||||||
|
allowHexString: true,
|
||||||
|
allowNumberDualTesting: false,
|
||||||
|
allowObjectIdTesting: false,
|
||||||
|
|
||||||
|
passBooleans: true,
|
||||||
|
passNumbers: true,
|
||||||
|
|
||||||
|
disableOr: true,
|
||||||
|
};
|
||||||
|
|
||||||
export const standardFilterBehaviours: { [id: string]: FilterBehaviour } = {
|
export const standardFilterBehaviours: { [id: string]: FilterBehaviour } = {
|
||||||
numberFilterBehaviour,
|
numberFilterBehaviour,
|
||||||
stringFilterBehaviour,
|
stringFilterBehaviour,
|
||||||
logicalFilterBehaviour,
|
logicalFilterBehaviour,
|
||||||
datetimeFilterBehaviour,
|
datetimeFilterBehaviour,
|
||||||
mongoFilterBehaviour,
|
mongoFilterBehaviour,
|
||||||
|
firestoreFilterBehaviours,
|
||||||
evalFilterBehaviour,
|
evalFilterBehaviour,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -75,6 +75,37 @@ export function parseCellValue(value, editorTypes?: DataEditorTypesBehaviour) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editorTypes?.parseGeopointAsDollar) {
|
||||||
|
const m = value.match(/^([\d\.]+)\s*°\s*([NS]),\s*([\d\.]+)\s*°\s*([EW])$/i);
|
||||||
|
if (m) {
|
||||||
|
let latitude = parseFloat(m[1]);
|
||||||
|
const latDir = m[2].toUpperCase();
|
||||||
|
let longitude = parseFloat(m[3]);
|
||||||
|
const lonDir = m[4].toUpperCase();
|
||||||
|
|
||||||
|
if (latDir === 'S') latitude = -latitude;
|
||||||
|
if (lonDir === 'W') longitude = -longitude;
|
||||||
|
|
||||||
|
return {
|
||||||
|
$geoPoint: {
|
||||||
|
latitude,
|
||||||
|
longitude,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorTypes?.parseFsDocumentRefAsDollar) {
|
||||||
|
const trimmedValue = value.replace(/\s/g, '');
|
||||||
|
if (trimmedValue.startsWith('$ref:')) {
|
||||||
|
return {
|
||||||
|
$fsDocumentRef: {
|
||||||
|
documentPath: trimmedValue.slice(5),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (editorTypes?.parseJsonNull) {
|
if (editorTypes?.parseJsonNull) {
|
||||||
if (value == 'null') return null;
|
if (value == 'null') return null;
|
||||||
}
|
}
|
||||||
@@ -246,6 +277,32 @@ export function stringifyCellValue(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (editorTypes?.parseGeopointAsDollar) {
|
||||||
|
if (value?.$geoPoint) {
|
||||||
|
const { latitude, longitude } = value.$geoPoint;
|
||||||
|
if (_isNumber(latitude) && _isNumber(longitude)) {
|
||||||
|
const latAbs = Math.abs(latitude);
|
||||||
|
const lonAbs = Math.abs(longitude);
|
||||||
|
const latDir = latitude >= 0 ? 'N' : 'S';
|
||||||
|
const lonDir = longitude >= 0 ? 'E' : 'W';
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: `${latAbs}° ${latDir}, ${lonAbs}° ${lonDir}`,
|
||||||
|
gridStyle: 'valueCellStyle',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (editorTypes?.parseFsDocumentRefAsDollar) {
|
||||||
|
if (value?.$fsDocumentRef) {
|
||||||
|
return {
|
||||||
|
value: `$ref: ${value.$fsDocumentRef.documentPath ?? ''}`,
|
||||||
|
gridStyle: 'valueCellStyle',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (_isArray(value)) {
|
if (_isArray(value)) {
|
||||||
switch (intent) {
|
switch (intent) {
|
||||||
case 'gridCellIntent':
|
case 'gridCellIntent':
|
||||||
|
|||||||
2
packages/types/dbinfo.d.ts
vendored
2
packages/types/dbinfo.d.ts
vendored
@@ -108,6 +108,8 @@ export interface CollectionInfo extends DatabaseObjectInfo {
|
|||||||
// unique combination of columns (should be contatenation of partitionKey and clusterKey)
|
// unique combination of columns (should be contatenation of partitionKey and clusterKey)
|
||||||
uniqueKey?: ColumnReference[];
|
uniqueKey?: ColumnReference[];
|
||||||
|
|
||||||
|
autoValueColumns?: ColumnReference[];
|
||||||
|
|
||||||
// partition key columns
|
// partition key columns
|
||||||
partitionKey?: ColumnReference[];
|
partitionKey?: ColumnReference[];
|
||||||
|
|
||||||
|
|||||||
28
packages/types/engines.d.ts
vendored
28
packages/types/engines.d.ts
vendored
@@ -23,6 +23,28 @@ export interface StreamOptions {
|
|||||||
info?: (info) => void;
|
info?: (info) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type CollectionOperationInfo =
|
||||||
|
| {
|
||||||
|
type: 'createCollection';
|
||||||
|
collection: {
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'dropCollection';
|
||||||
|
collection: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'renameCollection';
|
||||||
|
collection: string;
|
||||||
|
newName: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: 'cloneCollection';
|
||||||
|
collection: string;
|
||||||
|
newName: string;
|
||||||
|
};
|
||||||
|
|
||||||
export interface RunScriptOptions {
|
export interface RunScriptOptions {
|
||||||
useTransaction: boolean;
|
useTransaction: boolean;
|
||||||
logScriptItems?: boolean;
|
logScriptItems?: boolean;
|
||||||
@@ -120,6 +142,8 @@ export interface DataEditorTypesBehaviour {
|
|||||||
parseHexAsBuffer?: boolean;
|
parseHexAsBuffer?: boolean;
|
||||||
parseObjectIdAsDollar?: boolean;
|
parseObjectIdAsDollar?: boolean;
|
||||||
parseDateAsDollar?: boolean;
|
parseDateAsDollar?: boolean;
|
||||||
|
parseGeopointAsDollar?: boolean;
|
||||||
|
parseFsDocumentRefAsDollar?: boolean;
|
||||||
|
|
||||||
explicitDataType?: boolean;
|
explicitDataType?: boolean;
|
||||||
supportNumberType?: boolean;
|
supportNumberType?: boolean;
|
||||||
@@ -217,7 +241,7 @@ export interface EngineDriver<TClient = any> extends FilterBehaviourProvider {
|
|||||||
defaultSocketPath?: string;
|
defaultSocketPath?: string;
|
||||||
authTypeLabel?: string;
|
authTypeLabel?: string;
|
||||||
importExportArgs?: any[];
|
importExportArgs?: any[];
|
||||||
connect({ server, port, user, password, database }): Promise<DatabaseHandle<TClient>>;
|
connect({ server, port, user, password, database, certificateJson }): Promise<DatabaseHandle<TClient>>;
|
||||||
close(dbhan: DatabaseHandle<TClient>): Promise<any>;
|
close(dbhan: DatabaseHandle<TClient>): Promise<any>;
|
||||||
query(dbhan: DatabaseHandle<TClient>, sql: string, options?: QueryOptions): Promise<QueryResult>;
|
query(dbhan: DatabaseHandle<TClient>, sql: string, options?: QueryOptions): Promise<QueryResult>;
|
||||||
stream(dbhan: DatabaseHandle<TClient>, sql: string, options: StreamOptions);
|
stream(dbhan: DatabaseHandle<TClient>, sql: string, options: StreamOptions);
|
||||||
@@ -264,7 +288,7 @@ export interface EngineDriver<TClient = any> extends FilterBehaviourProvider {
|
|||||||
dropDatabase(dbhan: DatabaseHandle<TClient>, name: string): Promise;
|
dropDatabase(dbhan: DatabaseHandle<TClient>, name: string): Promise;
|
||||||
getQuerySplitterOptions(usage: 'stream' | 'script' | 'editor' | 'import'): any;
|
getQuerySplitterOptions(usage: 'stream' | 'script' | 'editor' | 'import'): any;
|
||||||
script(dbhan: DatabaseHandle<TClient>, sql: string, options?: RunScriptOptions): Promise;
|
script(dbhan: DatabaseHandle<TClient>, sql: string, options?: RunScriptOptions): Promise;
|
||||||
operation(dbhan: DatabaseHandle<TClient>, operation: {}, options?: RunScriptOptions): Promise;
|
operation(dbhan: DatabaseHandle<TClient>, operation: CollectionOperationInfo, options?: RunScriptOptions): Promise;
|
||||||
getNewObjectTemplates(): NewObjectTemplate[];
|
getNewObjectTemplates(): NewObjectTemplate[];
|
||||||
// direct call of dbhan.client method, only some methods could be supported, on only some drivers
|
// direct call of dbhan.client method, only some methods could be supported, on only some drivers
|
||||||
callMethod(dbhan: DatabaseHandle<TClient>, method, args);
|
callMethod(dbhan: DatabaseHandle<TClient>, method, args);
|
||||||
|
|||||||
9
packages/types/filter-type.d.ts
vendored
9
packages/types/filter-type.d.ts
vendored
@@ -9,11 +9,18 @@ export interface FilterBehaviour {
|
|||||||
supportExistsTesting?: boolean;
|
supportExistsTesting?: boolean;
|
||||||
supportBooleanValues?: boolean;
|
supportBooleanValues?: boolean;
|
||||||
supportSqlCondition?: boolean;
|
supportSqlCondition?: boolean;
|
||||||
supportArrayTesting?: boolean;
|
supportEmptyArrayTesting?: boolean;
|
||||||
|
supportNotEmptyArrayTesting?: boolean;
|
||||||
|
supportBooleanOrNull?: boolean;
|
||||||
|
|
||||||
allowStringToken?: boolean;
|
allowStringToken?: boolean;
|
||||||
allowNumberToken?: boolean;
|
allowNumberToken?: boolean;
|
||||||
allowHexString?: boolean;
|
allowHexString?: boolean;
|
||||||
allowNumberDualTesting?: boolean;
|
allowNumberDualTesting?: boolean;
|
||||||
allowObjectIdTesting?: boolean;
|
allowObjectIdTesting?: boolean;
|
||||||
|
|
||||||
|
passBooleans?: boolean;
|
||||||
|
passNumbers?: boolean;
|
||||||
|
|
||||||
|
disableOr?: boolean;
|
||||||
}
|
}
|
||||||
|
|||||||
89
packages/types/query.d.ts
vendored
89
packages/types/query.d.ts
vendored
@@ -15,3 +15,92 @@ export interface QueryResult {
|
|||||||
columns?: QueryResultColumn[];
|
columns?: QueryResultColumn[];
|
||||||
rowsAffected?: number;
|
rowsAffected?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type LeftOperand = {
|
||||||
|
exprType: 'placeholder' | 'column';
|
||||||
|
columnName?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type RightOperand = {
|
||||||
|
exprType: 'value';
|
||||||
|
value: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BinaryCondition = {
|
||||||
|
conditionType: 'binary';
|
||||||
|
operator: '=' | '!=' | '<>' | '<' | '<=' | '>' | '>=';
|
||||||
|
left: LeftOperand;
|
||||||
|
right: RightOperand;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AndCondition = {
|
||||||
|
conditionType: 'and';
|
||||||
|
conditions: FilterCondition[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type OrCondition = {
|
||||||
|
conditionType: 'or';
|
||||||
|
conditions: FilterCondition[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NullCondition = {
|
||||||
|
conditionType: 'isNull' | 'isNotNull';
|
||||||
|
expr: LeftOperand;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type NotCondition = {
|
||||||
|
conditionType: 'not';
|
||||||
|
condition: FilterCondition;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LikeCondition = {
|
||||||
|
conditionType: 'like';
|
||||||
|
left: LeftOperand;
|
||||||
|
right: RightOperand;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type PredicateCondition = {
|
||||||
|
conditionType: 'specificPredicate';
|
||||||
|
predicate: 'exists' | 'notExists' | 'emptyArray' | 'notEmptyArray';
|
||||||
|
expr: LeftOperand;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type InCondition = {
|
||||||
|
conditionType: 'in';
|
||||||
|
expr: LeftOperand;
|
||||||
|
values: any[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FilterCondition =
|
||||||
|
| BinaryCondition
|
||||||
|
| AndCondition
|
||||||
|
| OrCondition
|
||||||
|
| NullCondition
|
||||||
|
| NotCondition
|
||||||
|
| LikeCondition
|
||||||
|
| PredicateCondition
|
||||||
|
| InCondition;
|
||||||
|
|
||||||
|
export type SortItem = {
|
||||||
|
columnName: string;
|
||||||
|
direction?: 'ASC' | 'DESC';
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AggregateColumn = {
|
||||||
|
aggregateFunction: 'count' | 'sum' | 'avg' | 'min' | 'max';
|
||||||
|
columnArgument?: string;
|
||||||
|
alias: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CollectionAggregate = {
|
||||||
|
condition?: FilterCondition;
|
||||||
|
groupByColumns: string[];
|
||||||
|
aggregateColumns: AggregateColumn[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FullQueryOptions = {
|
||||||
|
condition?: FilterCondition;
|
||||||
|
sort?: SortItem[];
|
||||||
|
limit?: number;
|
||||||
|
skip?: number;
|
||||||
|
};
|
||||||
|
|||||||
@@ -69,6 +69,7 @@
|
|||||||
"highlight.js": "^11.11.1",
|
"highlight.js": "^11.11.1",
|
||||||
"interval-operations": "^1.0.7",
|
"interval-operations": "^1.0.7",
|
||||||
"leaflet": "^1.8.0",
|
"leaflet": "^1.8.0",
|
||||||
|
"openai": "^5.10.1",
|
||||||
"wellknown": "^0.5.0",
|
"wellknown": "^0.5.0",
|
||||||
"xml-formatter": "^3.6.4"
|
"xml-formatter": "^3.6.4"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,8 @@
|
|||||||
const handleDropDatabase = () => {
|
const handleDropDatabase = () => {
|
||||||
showModal(ConfirmModal, {
|
showModal(ConfirmModal, {
|
||||||
message: _t('database.dropConfirm', {
|
message: _t('database.dropConfirm', {
|
||||||
defaultMessage: 'Really drop database {name}? All opened sessions with this database will be forcefully closed.',
|
defaultMessage:
|
||||||
|
'Really drop database {name}? All opened sessions with this database will be forcefully closed.',
|
||||||
values: { name },
|
values: { name },
|
||||||
}),
|
}),
|
||||||
onConfirm: () =>
|
onConfirm: () =>
|
||||||
@@ -207,6 +208,18 @@
|
|||||||
// showSnackbarSuccess(`Saved to archive ${resp.archiveFolder}`);
|
// showSnackbarSuccess(`Saved to archive ${resp.archiveFolder}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleDatabaseChat = () => {
|
||||||
|
openNewTab({
|
||||||
|
title: 'Chat',
|
||||||
|
icon: 'img ai',
|
||||||
|
tabComponent: 'DatabaseChatTab',
|
||||||
|
props: {
|
||||||
|
conid: connection._id,
|
||||||
|
database: name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const handleCompareWithCurrentDb = () => {
|
const handleCompareWithCurrentDb = () => {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
@@ -312,7 +325,8 @@
|
|||||||
const handleGenerateDropAllObjectsScript = () => {
|
const handleGenerateDropAllObjectsScript = () => {
|
||||||
showModal(ConfirmModal, {
|
showModal(ConfirmModal, {
|
||||||
message: _t('database.dropAllObjectsConfirm', {
|
message: _t('database.dropAllObjectsConfirm', {
|
||||||
defaultMessage: 'This will generate script, after executing this script all objects in {name} will be dropped. Continue?',
|
defaultMessage:
|
||||||
|
'This will generate script, after executing this script all objects in {name} will be dropped. Continue?',
|
||||||
values: { name },
|
values: { name },
|
||||||
}),
|
}),
|
||||||
|
|
||||||
@@ -364,7 +378,9 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
|
|
||||||
const handleShowDataDeployer = () => {
|
const handleShowDataDeployer = () => {
|
||||||
showModal(ChooseArchiveFolderModal, {
|
showModal(ChooseArchiveFolderModal, {
|
||||||
message: _t('database.chooseArchiveFolderForDataDeployer', { defaultMessage: 'Choose archive folder for data deployer' }),
|
message: _t('database.chooseArchiveFolderForDataDeployer', {
|
||||||
|
defaultMessage: 'Choose archive folder for data deployer',
|
||||||
|
}),
|
||||||
onConfirm: archiveFolder => {
|
onConfirm: archiveFolder => {
|
||||||
openNewTab(
|
openNewTab(
|
||||||
{
|
{
|
||||||
@@ -396,57 +412,109 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document');
|
driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document');
|
||||||
|
|
||||||
return [
|
return [
|
||||||
hasPermission(`dbops/query`) && { onClick: handleNewQuery, text: _t('database.newQuery', { defaultMessage: 'New query' }), isNewQuery: true },
|
hasPermission(`dbops/query`) && {
|
||||||
|
onClick: handleNewQuery,
|
||||||
|
text: _t('database.newQuery', { defaultMessage: 'New query' }),
|
||||||
|
isNewQuery: true,
|
||||||
|
},
|
||||||
hasPermission(`dbops/model/edit`) &&
|
hasPermission(`dbops/model/edit`) &&
|
||||||
!connection.isReadOnly &&
|
!connection.isReadOnly &&
|
||||||
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleNewTable, text: _t('database.newTable', { defaultMessage: 'New table' }) },
|
driver?.databaseEngineTypes?.includes('sql') && {
|
||||||
|
onClick: handleNewTable,
|
||||||
|
text: _t('database.newTable', { defaultMessage: 'New table' }),
|
||||||
|
},
|
||||||
!connection.isReadOnly &&
|
!connection.isReadOnly &&
|
||||||
hasPermission(`dbops/model/edit`) &&
|
hasPermission(`dbops/model/edit`) &&
|
||||||
driver?.databaseEngineTypes?.includes('document') && {
|
driver?.databaseEngineTypes?.includes('document') && {
|
||||||
onClick: handleNewCollection,
|
onClick: handleNewCollection,
|
||||||
text: _t('database.newCollection', { defaultMessage: 'New {collectionLabel}', values: { collectionLabel: driver?.collectionSingularLabel ?? 'collection/container' } }),
|
text: _t('database.newCollection', {
|
||||||
|
defaultMessage: 'New {collectionLabel}',
|
||||||
|
values: { collectionLabel: driver?.collectionSingularLabel ?? 'collection/container' },
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
hasPermission(`dbops/query`) &&
|
hasPermission(`dbops/query`) &&
|
||||||
driver?.databaseEngineTypes?.includes('sql') &&
|
driver?.databaseEngineTypes?.includes('sql') &&
|
||||||
isProApp() && { onClick: handleQueryDesigner, text: _t('database.designQuery', { defaultMessage: 'Design query' }) },
|
isProApp() && {
|
||||||
|
onClick: handleQueryDesigner,
|
||||||
|
text: _t('database.designQuery', { defaultMessage: 'Design query' }),
|
||||||
|
},
|
||||||
driver?.databaseEngineTypes?.includes('sql') &&
|
driver?.databaseEngineTypes?.includes('sql') &&
|
||||||
isProApp() && {
|
isProApp() && {
|
||||||
onClick: handleNewPerspective,
|
onClick: handleNewPerspective,
|
||||||
text: _t('database.designPerspectiveQuery', { defaultMessage: 'Design perspective query' }),
|
text: _t('database.designPerspectiveQuery', { defaultMessage: 'Design perspective query' }),
|
||||||
},
|
},
|
||||||
connection.useSeparateSchemas && { onClick: handleRefreshSchemas, text: _t('database.refreshSchemas', { defaultMessage: 'Refresh schemas' }) },
|
connection.useSeparateSchemas && {
|
||||||
|
onClick: handleRefreshSchemas,
|
||||||
|
text: _t('database.refreshSchemas', { defaultMessage: 'Refresh schemas' }),
|
||||||
|
},
|
||||||
|
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
isSqlOrDoc &&
|
isSqlOrDoc &&
|
||||||
!connection.isReadOnly &&
|
!connection.isReadOnly &&
|
||||||
hasPermission(`dbops/import`) && { onClick: handleImport, text: _t('database.import', { defaultMessage: 'Import' }) },
|
hasPermission(`dbops/import`) && {
|
||||||
isSqlOrDoc && hasPermission(`dbops/export`) && { onClick: handleExport, text: _t('database.export', { defaultMessage: 'Export' }) },
|
onClick: handleImport,
|
||||||
|
text: _t('database.import', { defaultMessage: 'Import' }),
|
||||||
|
},
|
||||||
|
isSqlOrDoc &&
|
||||||
|
hasPermission(`dbops/export`) && {
|
||||||
|
onClick: handleExport,
|
||||||
|
text: _t('database.export', { defaultMessage: 'Export' }),
|
||||||
|
},
|
||||||
driver?.supportsDatabaseRestore &&
|
driver?.supportsDatabaseRestore &&
|
||||||
isProApp() &&
|
isProApp() &&
|
||||||
hasPermission(`dbops/sql-dump/import`) &&
|
hasPermission(`dbops/sql-dump/import`) &&
|
||||||
!connection.isReadOnly && { onClick: handleRestoreDatabase, text: _t('database.restoreDatabaseBackup', { defaultMessage: 'Restore database backup' }) },
|
!connection.isReadOnly && {
|
||||||
|
onClick: handleRestoreDatabase,
|
||||||
|
text: _t('database.restoreDatabaseBackup', { defaultMessage: 'Restore database backup' }),
|
||||||
|
},
|
||||||
driver?.supportsDatabaseBackup &&
|
driver?.supportsDatabaseBackup &&
|
||||||
isProApp() &&
|
isProApp() &&
|
||||||
hasPermission(`dbops/sql-dump/export`) && { onClick: handleBackupDatabase, text: _t('database.createDatabaseBackup', { defaultMessage: 'Create database backup' }) },
|
hasPermission(`dbops/sql-dump/export`) && {
|
||||||
|
onClick: handleBackupDatabase,
|
||||||
|
text: _t('database.createDatabaseBackup', { defaultMessage: 'Create database backup' }),
|
||||||
|
},
|
||||||
isSqlOrDoc &&
|
isSqlOrDoc &&
|
||||||
!connection.isReadOnly &&
|
!connection.isReadOnly &&
|
||||||
!connection.singleDatabase &&
|
!connection.singleDatabase &&
|
||||||
isSqlOrDoc &&
|
isSqlOrDoc &&
|
||||||
hasPermission(`dbops/dropdb`) && { onClick: handleDropDatabase, text: _t('database.dropDatabase', { defaultMessage: 'Drop database' }) },
|
hasPermission(`dbops/dropdb`) && {
|
||||||
|
onClick: handleDropDatabase,
|
||||||
|
text: _t('database.dropDatabase', { defaultMessage: 'Drop database' }),
|
||||||
|
},
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleCopyName, text: _t('database.copyDatabaseName', { defaultMessage: 'Copy database name' }) },
|
driver?.databaseEngineTypes?.includes('sql') && {
|
||||||
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleShowDiagram, text: _t('database.showDiagram', { defaultMessage: 'Show diagram' }) },
|
onClick: handleCopyName,
|
||||||
|
text: _t('database.copyDatabaseName', { defaultMessage: 'Copy database name' }),
|
||||||
|
},
|
||||||
|
driver?.databaseEngineTypes?.includes('sql') && {
|
||||||
|
onClick: handleShowDiagram,
|
||||||
|
text: _t('database.showDiagram', { defaultMessage: 'Show diagram' }),
|
||||||
|
},
|
||||||
driver?.databaseEngineTypes?.includes('sql') &&
|
driver?.databaseEngineTypes?.includes('sql') &&
|
||||||
hasPermission(`dbops/sql-generator`) && { onClick: handleSqlGenerator, text: _t('database.sqlGenerator', { defaultMessage: 'SQL Generator' }) },
|
hasPermission(`dbops/sql-generator`) && {
|
||||||
|
onClick: handleSqlGenerator,
|
||||||
|
text: _t('database.sqlGenerator', { defaultMessage: 'SQL Generator' }),
|
||||||
|
},
|
||||||
driver?.supportsDatabaseProfiler &&
|
driver?.supportsDatabaseProfiler &&
|
||||||
isProApp() &&
|
isProApp() &&
|
||||||
hasPermission(`dbops/profiler`) && { onClick: handleDatabaseProfiler, text: _t('database.databaseProfiler', { defaultMessage: 'Database profiler' }) },
|
hasPermission(`dbops/profiler`) && {
|
||||||
|
onClick: handleDatabaseProfiler,
|
||||||
|
text: _t('database.databaseProfiler', { defaultMessage: 'Database profiler' }),
|
||||||
|
},
|
||||||
// isSqlOrDoc &&
|
// isSqlOrDoc &&
|
||||||
// isSqlOrDoc &&
|
// isSqlOrDoc &&
|
||||||
// hasPermission(`dbops/model/view`) && { onClick: handleOpenJsonModel, text: 'Open model as JSON' },
|
// hasPermission(`dbops/model/view`) && { onClick: handleOpenJsonModel, text: 'Open model as JSON' },
|
||||||
isSqlOrDoc &&
|
isSqlOrDoc &&
|
||||||
isProApp() &&
|
isProApp() &&
|
||||||
hasPermission(`dbops/model/view`) && { onClick: handleExportModel, text: _t('database.exportDbModel', { defaultMessage: 'Export DB model' }) },
|
hasPermission(`dbops/model/view`) && {
|
||||||
|
onClick: handleExportModel,
|
||||||
|
text: _t('database.exportDbModel', { defaultMessage: 'Export DB model' }),
|
||||||
|
},
|
||||||
|
isProApp() &&
|
||||||
|
driver?.databaseEngineTypes?.includes('sql') && {
|
||||||
|
onClick: handleDatabaseChat,
|
||||||
|
text: _t('database.databaseChat', { defaultMessage: 'Database chat' }),
|
||||||
|
},
|
||||||
isSqlOrDoc &&
|
isSqlOrDoc &&
|
||||||
_.get($currentDatabase, 'connection._id') &&
|
_.get($currentDatabase, 'connection._id') &&
|
||||||
hasPermission('dbops/model/compare') &&
|
hasPermission('dbops/model/compare') &&
|
||||||
@@ -455,14 +523,23 @@ await dbgateApi.executeQuery(${JSON.stringify(
|
|||||||
(_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
|
(_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
|
||||||
_.get($currentDatabase, 'name') != _.get(connection, 'name'))) && {
|
_.get($currentDatabase, 'name') != _.get(connection, 'name'))) && {
|
||||||
onClick: handleCompareWithCurrentDb,
|
onClick: handleCompareWithCurrentDb,
|
||||||
text: _t('database.compareWithCurrentDb', { defaultMessage: 'Compare with {name}', values: { name: _.get($currentDatabase, 'name') } }),
|
text: _t('database.compareWithCurrentDb', {
|
||||||
|
defaultMessage: 'Compare with {name}',
|
||||||
|
values: { name: _.get($currentDatabase, 'name') },
|
||||||
|
}),
|
||||||
},
|
},
|
||||||
|
|
||||||
driver?.databaseEngineTypes?.includes('keyvalue') && { onClick: handleGenerateScript, text: _t('database.generateScript', { defaultMessage: 'Generate script' }) },
|
driver?.databaseEngineTypes?.includes('keyvalue') && {
|
||||||
|
onClick: handleGenerateScript,
|
||||||
|
text: _t('database.generateScript', { defaultMessage: 'Generate script' }),
|
||||||
|
},
|
||||||
|
|
||||||
($openedSingleDatabaseConnections.includes(connection._id) ||
|
($openedSingleDatabaseConnections.includes(connection._id) ||
|
||||||
(_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
|
(_.get($currentDatabase, 'connection._id') == _.get(connection, '_id') &&
|
||||||
_.get($currentDatabase, 'name') == name)) && { onClick: handleDisconnect, text: _t('database.disconnect', { defaultMessage: 'Disconnect' }) },
|
_.get($currentDatabase, 'name') == name)) && {
|
||||||
|
onClick: handleDisconnect,
|
||||||
|
text: _t('database.disconnect', { defaultMessage: 'Disconnect' }),
|
||||||
|
},
|
||||||
|
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import XmlHighlighter from './XmlHighlighter.svelte';
|
import XmlHighlighter from '../elements/XmlHighlighter.svelte';
|
||||||
|
|
||||||
export let selection;
|
export let selection;
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -714,6 +714,28 @@ if (isProApp()) {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
registerCommand({
|
||||||
|
id: 'database.chat',
|
||||||
|
category: 'Database',
|
||||||
|
name: 'Database chat',
|
||||||
|
toolbar: true,
|
||||||
|
icon: 'icon ai',
|
||||||
|
testEnabled: () =>
|
||||||
|
getCurrentDatabase() != null &&
|
||||||
|
findEngineDriver(getCurrentDatabase()?.connection, getExtensions())?.databaseEngineTypes?.includes('sql'),
|
||||||
|
onClick: () => {
|
||||||
|
openNewTab({
|
||||||
|
title: 'Chat',
|
||||||
|
icon: 'img ai',
|
||||||
|
tabComponent: 'DatabaseChatTab',
|
||||||
|
props: {
|
||||||
|
conid: getCurrentDatabase()?.connection?._id,
|
||||||
|
database: getCurrentDatabase()?.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasPermission('settings/change')) {
|
if (hasPermission('settings/change')) {
|
||||||
|
|||||||
@@ -122,7 +122,11 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
import { registerQuickExportHandler } from '../buttons/ToolStripExportButton.svelte';
|
||||||
import registerCommand from '../commands/registerCommand';
|
import registerCommand from '../commands/registerCommand';
|
||||||
import { extractShellConnection, extractShellConnectionHostable, extractShellHostConnection } from '../impexp/createImpExpScript';
|
import {
|
||||||
|
extractShellConnection,
|
||||||
|
extractShellConnectionHostable,
|
||||||
|
extractShellHostConnection,
|
||||||
|
} from '../impexp/createImpExpScript';
|
||||||
import { apiCall } from '../utility/api';
|
import { apiCall } from '../utility/api';
|
||||||
|
|
||||||
import { registerMenu } from '../utility/contextMenu';
|
import { registerMenu } from '../utility/contextMenu';
|
||||||
|
|||||||
@@ -80,11 +80,12 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterBehaviour.supportArrayTesting) {
|
if (filterBehaviour.supportNotEmptyArrayTesting) {
|
||||||
res.push(
|
res.push({ onClick: () => setFilter('NOT EMPTY ARRAY'), text: 'Array is not empty' });
|
||||||
{ onClick: () => setFilter('NOT EMPTY ARRAY'), text: 'Array is not empty' },
|
}
|
||||||
{ onClick: () => setFilter('EMPTY ARRAY'), text: 'Array is empty' }
|
|
||||||
);
|
if (filterBehaviour.supportEmptyArrayTesting) {
|
||||||
|
res.push({ onClick: () => setFilter('EMPTY ARRAY'), text: 'Array is empty' });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterBehaviour.supportNullTesting) {
|
if (filterBehaviour.supportNullTesting) {
|
||||||
@@ -132,7 +133,7 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filterBehaviour.supportBooleanValues && filterBehaviour.supportNullTesting) {
|
if (filterBehaviour.supportBooleanOrNull) {
|
||||||
res.push(
|
res.push(
|
||||||
{ onClick: () => setFilter('TRUE, NULL'), text: 'Is True or NULL' },
|
{ onClick: () => setFilter('TRUE, NULL'), text: 'Is True or NULL' },
|
||||||
{ onClick: () => setFilter('FALSE, NULL'), text: 'Is False or NULL' }
|
{ onClick: () => setFilter('FALSE, NULL'), text: 'Is False or NULL' }
|
||||||
|
|||||||
41
packages/web/src/elements/SqlHighlighter.svelte
Normal file
41
packages/web/src/elements/SqlHighlighter.svelte
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<script>
|
||||||
|
/* npm i highlight.js sql-formatter */
|
||||||
|
import hljs from 'highlight.js/lib/core';
|
||||||
|
import sqlGrammar from './sqlGrammar';
|
||||||
|
import { onMount, afterUpdate } from 'svelte';
|
||||||
|
|
||||||
|
export let code = '';
|
||||||
|
|
||||||
|
let domCode;
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
hljs.registerLanguage('sql', sqlGrammar);
|
||||||
|
|
||||||
|
// first paint
|
||||||
|
if (domCode) {
|
||||||
|
hljs.highlightElement(domCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
afterUpdate(() => {
|
||||||
|
if (domCode) {
|
||||||
|
hljs.highlightElement(domCode);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#key code}
|
||||||
|
<!--
|
||||||
|
The `sql` class hints the language; highlight.js will
|
||||||
|
read it even though we register the grammar explicitly.
|
||||||
|
-->
|
||||||
|
<pre bind:this={domCode} class="sql">{code}</pre>
|
||||||
|
{/key}
|
||||||
|
|
||||||
|
<style>
|
||||||
|
pre {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
padding: 0.5em;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
export let code = '';
|
export let code = '';
|
||||||
|
|
||||||
$: formattedCode = xmlFormat(code, { indentation: ' ' });
|
$: formattedCode = xmlFormat(code, { indentation: ' ', throwOnFailure: false });
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
hljs.registerLanguage('xml', xmlGrammar);
|
hljs.registerLanguage('xml', xmlGrammar);
|
||||||
691
packages/web/src/elements/sqlGrammar.js
Normal file
691
packages/web/src/elements/sqlGrammar.js
Normal file
@@ -0,0 +1,691 @@
|
|||||||
|
/*
|
||||||
|
Language: SQL
|
||||||
|
Website: https://en.wikipedia.org/wiki/SQL
|
||||||
|
Category: common, database
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
|
||||||
|
Goals:
|
||||||
|
|
||||||
|
SQL is intended to highlight basic/common SQL keywords and expressions
|
||||||
|
|
||||||
|
- If pretty much every single SQL server includes supports, then it's a canidate.
|
||||||
|
- It is NOT intended to include tons of vendor specific keywords (Oracle, MySQL,
|
||||||
|
PostgreSQL) although the list of data types is purposely a bit more expansive.
|
||||||
|
- For more specific SQL grammars please see:
|
||||||
|
- PostgreSQL and PL/pgSQL - core
|
||||||
|
- T-SQL - https://github.com/highlightjs/highlightjs-tsql
|
||||||
|
- sql_more (core)
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
export default function(hljs) {
|
||||||
|
const regex = hljs.regex;
|
||||||
|
const COMMENT_MODE = hljs.COMMENT('--', '$');
|
||||||
|
const STRING = {
|
||||||
|
scope: 'string',
|
||||||
|
variants: [
|
||||||
|
{
|
||||||
|
begin: /'/,
|
||||||
|
end: /'/,
|
||||||
|
contains: [ { match: /''/ } ]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
const QUOTED_IDENTIFIER = {
|
||||||
|
begin: /"/,
|
||||||
|
end: /"/,
|
||||||
|
contains: [ { match: /""/ } ]
|
||||||
|
};
|
||||||
|
|
||||||
|
const LITERALS = [
|
||||||
|
"true",
|
||||||
|
"false",
|
||||||
|
// Not sure it's correct to call NULL literal, and clauses like IS [NOT] NULL look strange that way.
|
||||||
|
// "null",
|
||||||
|
"unknown"
|
||||||
|
];
|
||||||
|
|
||||||
|
const MULTI_WORD_TYPES = [
|
||||||
|
"double precision",
|
||||||
|
"large object",
|
||||||
|
"with timezone",
|
||||||
|
"without timezone"
|
||||||
|
];
|
||||||
|
|
||||||
|
const TYPES = [
|
||||||
|
'bigint',
|
||||||
|
'binary',
|
||||||
|
'blob',
|
||||||
|
'boolean',
|
||||||
|
'char',
|
||||||
|
'character',
|
||||||
|
'clob',
|
||||||
|
'date',
|
||||||
|
'dec',
|
||||||
|
'decfloat',
|
||||||
|
'decimal',
|
||||||
|
'float',
|
||||||
|
'int',
|
||||||
|
'integer',
|
||||||
|
'interval',
|
||||||
|
'nchar',
|
||||||
|
'nclob',
|
||||||
|
'national',
|
||||||
|
'numeric',
|
||||||
|
'real',
|
||||||
|
'row',
|
||||||
|
'smallint',
|
||||||
|
'time',
|
||||||
|
'timestamp',
|
||||||
|
'varchar',
|
||||||
|
'varying', // modifier (character varying)
|
||||||
|
'varbinary'
|
||||||
|
];
|
||||||
|
|
||||||
|
const NON_RESERVED_WORDS = [
|
||||||
|
"add",
|
||||||
|
"asc",
|
||||||
|
"collation",
|
||||||
|
"desc",
|
||||||
|
"final",
|
||||||
|
"first",
|
||||||
|
"last",
|
||||||
|
"view"
|
||||||
|
];
|
||||||
|
|
||||||
|
// https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#reserved-word
|
||||||
|
const RESERVED_WORDS = [
|
||||||
|
"abs",
|
||||||
|
"acos",
|
||||||
|
"all",
|
||||||
|
"allocate",
|
||||||
|
"alter",
|
||||||
|
"and",
|
||||||
|
"any",
|
||||||
|
"are",
|
||||||
|
"array",
|
||||||
|
"array_agg",
|
||||||
|
"array_max_cardinality",
|
||||||
|
"as",
|
||||||
|
"asensitive",
|
||||||
|
"asin",
|
||||||
|
"asymmetric",
|
||||||
|
"at",
|
||||||
|
"atan",
|
||||||
|
"atomic",
|
||||||
|
"authorization",
|
||||||
|
"avg",
|
||||||
|
"begin",
|
||||||
|
"begin_frame",
|
||||||
|
"begin_partition",
|
||||||
|
"between",
|
||||||
|
"bigint",
|
||||||
|
"binary",
|
||||||
|
"blob",
|
||||||
|
"boolean",
|
||||||
|
"both",
|
||||||
|
"by",
|
||||||
|
"call",
|
||||||
|
"called",
|
||||||
|
"cardinality",
|
||||||
|
"cascaded",
|
||||||
|
"case",
|
||||||
|
"cast",
|
||||||
|
"ceil",
|
||||||
|
"ceiling",
|
||||||
|
"char",
|
||||||
|
"char_length",
|
||||||
|
"character",
|
||||||
|
"character_length",
|
||||||
|
"check",
|
||||||
|
"classifier",
|
||||||
|
"clob",
|
||||||
|
"close",
|
||||||
|
"coalesce",
|
||||||
|
"collate",
|
||||||
|
"collect",
|
||||||
|
"column",
|
||||||
|
"commit",
|
||||||
|
"condition",
|
||||||
|
"connect",
|
||||||
|
"constraint",
|
||||||
|
"contains",
|
||||||
|
"convert",
|
||||||
|
"copy",
|
||||||
|
"corr",
|
||||||
|
"corresponding",
|
||||||
|
"cos",
|
||||||
|
"cosh",
|
||||||
|
"count",
|
||||||
|
"covar_pop",
|
||||||
|
"covar_samp",
|
||||||
|
"create",
|
||||||
|
"cross",
|
||||||
|
"cube",
|
||||||
|
"cume_dist",
|
||||||
|
"current",
|
||||||
|
"current_catalog",
|
||||||
|
"current_date",
|
||||||
|
"current_default_transform_group",
|
||||||
|
"current_path",
|
||||||
|
"current_role",
|
||||||
|
"current_row",
|
||||||
|
"current_schema",
|
||||||
|
"current_time",
|
||||||
|
"current_timestamp",
|
||||||
|
"current_path",
|
||||||
|
"current_role",
|
||||||
|
"current_transform_group_for_type",
|
||||||
|
"current_user",
|
||||||
|
"cursor",
|
||||||
|
"cycle",
|
||||||
|
"date",
|
||||||
|
"day",
|
||||||
|
"deallocate",
|
||||||
|
"dec",
|
||||||
|
"decimal",
|
||||||
|
"decfloat",
|
||||||
|
"declare",
|
||||||
|
"default",
|
||||||
|
"define",
|
||||||
|
"delete",
|
||||||
|
"dense_rank",
|
||||||
|
"deref",
|
||||||
|
"describe",
|
||||||
|
"deterministic",
|
||||||
|
"disconnect",
|
||||||
|
"distinct",
|
||||||
|
"double",
|
||||||
|
"drop",
|
||||||
|
"dynamic",
|
||||||
|
"each",
|
||||||
|
"element",
|
||||||
|
"else",
|
||||||
|
"empty",
|
||||||
|
"end",
|
||||||
|
"end_frame",
|
||||||
|
"end_partition",
|
||||||
|
"end-exec",
|
||||||
|
"equals",
|
||||||
|
"escape",
|
||||||
|
"every",
|
||||||
|
"except",
|
||||||
|
"exec",
|
||||||
|
"execute",
|
||||||
|
"exists",
|
||||||
|
"exp",
|
||||||
|
"external",
|
||||||
|
"extract",
|
||||||
|
"false",
|
||||||
|
"fetch",
|
||||||
|
"filter",
|
||||||
|
"first_value",
|
||||||
|
"float",
|
||||||
|
"floor",
|
||||||
|
"for",
|
||||||
|
"foreign",
|
||||||
|
"frame_row",
|
||||||
|
"free",
|
||||||
|
"from",
|
||||||
|
"full",
|
||||||
|
"function",
|
||||||
|
"fusion",
|
||||||
|
"get",
|
||||||
|
"global",
|
||||||
|
"grant",
|
||||||
|
"group",
|
||||||
|
"grouping",
|
||||||
|
"groups",
|
||||||
|
"having",
|
||||||
|
"hold",
|
||||||
|
"hour",
|
||||||
|
"identity",
|
||||||
|
"in",
|
||||||
|
"indicator",
|
||||||
|
"initial",
|
||||||
|
"inner",
|
||||||
|
"inout",
|
||||||
|
"insensitive",
|
||||||
|
"insert",
|
||||||
|
"int",
|
||||||
|
"integer",
|
||||||
|
"intersect",
|
||||||
|
"intersection",
|
||||||
|
"interval",
|
||||||
|
"into",
|
||||||
|
"is",
|
||||||
|
"join",
|
||||||
|
"json_array",
|
||||||
|
"json_arrayagg",
|
||||||
|
"json_exists",
|
||||||
|
"json_object",
|
||||||
|
"json_objectagg",
|
||||||
|
"json_query",
|
||||||
|
"json_table",
|
||||||
|
"json_table_primitive",
|
||||||
|
"json_value",
|
||||||
|
"lag",
|
||||||
|
"language",
|
||||||
|
"large",
|
||||||
|
"last_value",
|
||||||
|
"lateral",
|
||||||
|
"lead",
|
||||||
|
"leading",
|
||||||
|
"left",
|
||||||
|
"like",
|
||||||
|
"like_regex",
|
||||||
|
"listagg",
|
||||||
|
"ln",
|
||||||
|
"local",
|
||||||
|
"localtime",
|
||||||
|
"localtimestamp",
|
||||||
|
"log",
|
||||||
|
"log10",
|
||||||
|
"lower",
|
||||||
|
"match",
|
||||||
|
"match_number",
|
||||||
|
"match_recognize",
|
||||||
|
"matches",
|
||||||
|
"max",
|
||||||
|
"member",
|
||||||
|
"merge",
|
||||||
|
"method",
|
||||||
|
"min",
|
||||||
|
"minute",
|
||||||
|
"mod",
|
||||||
|
"modifies",
|
||||||
|
"module",
|
||||||
|
"month",
|
||||||
|
"multiset",
|
||||||
|
"national",
|
||||||
|
"natural",
|
||||||
|
"nchar",
|
||||||
|
"nclob",
|
||||||
|
"new",
|
||||||
|
"no",
|
||||||
|
"none",
|
||||||
|
"normalize",
|
||||||
|
"not",
|
||||||
|
"nth_value",
|
||||||
|
"ntile",
|
||||||
|
"null",
|
||||||
|
"nullif",
|
||||||
|
"numeric",
|
||||||
|
"octet_length",
|
||||||
|
"occurrences_regex",
|
||||||
|
"of",
|
||||||
|
"offset",
|
||||||
|
"old",
|
||||||
|
"omit",
|
||||||
|
"on",
|
||||||
|
"one",
|
||||||
|
"only",
|
||||||
|
"open",
|
||||||
|
"or",
|
||||||
|
"order",
|
||||||
|
"out",
|
||||||
|
"outer",
|
||||||
|
"over",
|
||||||
|
"overlaps",
|
||||||
|
"overlay",
|
||||||
|
"parameter",
|
||||||
|
"partition",
|
||||||
|
"pattern",
|
||||||
|
"per",
|
||||||
|
"percent",
|
||||||
|
"percent_rank",
|
||||||
|
"percentile_cont",
|
||||||
|
"percentile_disc",
|
||||||
|
"period",
|
||||||
|
"portion",
|
||||||
|
"position",
|
||||||
|
"position_regex",
|
||||||
|
"power",
|
||||||
|
"precedes",
|
||||||
|
"precision",
|
||||||
|
"prepare",
|
||||||
|
"primary",
|
||||||
|
"procedure",
|
||||||
|
"ptf",
|
||||||
|
"range",
|
||||||
|
"rank",
|
||||||
|
"reads",
|
||||||
|
"real",
|
||||||
|
"recursive",
|
||||||
|
"ref",
|
||||||
|
"references",
|
||||||
|
"referencing",
|
||||||
|
"regr_avgx",
|
||||||
|
"regr_avgy",
|
||||||
|
"regr_count",
|
||||||
|
"regr_intercept",
|
||||||
|
"regr_r2",
|
||||||
|
"regr_slope",
|
||||||
|
"regr_sxx",
|
||||||
|
"regr_sxy",
|
||||||
|
"regr_syy",
|
||||||
|
"release",
|
||||||
|
"result",
|
||||||
|
"return",
|
||||||
|
"returns",
|
||||||
|
"revoke",
|
||||||
|
"right",
|
||||||
|
"rollback",
|
||||||
|
"rollup",
|
||||||
|
"row",
|
||||||
|
"row_number",
|
||||||
|
"rows",
|
||||||
|
"running",
|
||||||
|
"savepoint",
|
||||||
|
"scope",
|
||||||
|
"scroll",
|
||||||
|
"search",
|
||||||
|
"second",
|
||||||
|
"seek",
|
||||||
|
"select",
|
||||||
|
"sensitive",
|
||||||
|
"session_user",
|
||||||
|
"set",
|
||||||
|
"show",
|
||||||
|
"similar",
|
||||||
|
"sin",
|
||||||
|
"sinh",
|
||||||
|
"skip",
|
||||||
|
"smallint",
|
||||||
|
"some",
|
||||||
|
"specific",
|
||||||
|
"specifictype",
|
||||||
|
"sql",
|
||||||
|
"sqlexception",
|
||||||
|
"sqlstate",
|
||||||
|
"sqlwarning",
|
||||||
|
"sqrt",
|
||||||
|
"start",
|
||||||
|
"static",
|
||||||
|
"stddev_pop",
|
||||||
|
"stddev_samp",
|
||||||
|
"submultiset",
|
||||||
|
"subset",
|
||||||
|
"substring",
|
||||||
|
"substring_regex",
|
||||||
|
"succeeds",
|
||||||
|
"sum",
|
||||||
|
"symmetric",
|
||||||
|
"system",
|
||||||
|
"system_time",
|
||||||
|
"system_user",
|
||||||
|
"table",
|
||||||
|
"tablesample",
|
||||||
|
"tan",
|
||||||
|
"tanh",
|
||||||
|
"then",
|
||||||
|
"time",
|
||||||
|
"timestamp",
|
||||||
|
"timezone_hour",
|
||||||
|
"timezone_minute",
|
||||||
|
"to",
|
||||||
|
"trailing",
|
||||||
|
"translate",
|
||||||
|
"translate_regex",
|
||||||
|
"translation",
|
||||||
|
"treat",
|
||||||
|
"trigger",
|
||||||
|
"trim",
|
||||||
|
"trim_array",
|
||||||
|
"true",
|
||||||
|
"truncate",
|
||||||
|
"uescape",
|
||||||
|
"union",
|
||||||
|
"unique",
|
||||||
|
"unknown",
|
||||||
|
"unnest",
|
||||||
|
"update",
|
||||||
|
"upper",
|
||||||
|
"user",
|
||||||
|
"using",
|
||||||
|
"value",
|
||||||
|
"values",
|
||||||
|
"value_of",
|
||||||
|
"var_pop",
|
||||||
|
"var_samp",
|
||||||
|
"varbinary",
|
||||||
|
"varchar",
|
||||||
|
"varying",
|
||||||
|
"versioning",
|
||||||
|
"when",
|
||||||
|
"whenever",
|
||||||
|
"where",
|
||||||
|
"width_bucket",
|
||||||
|
"window",
|
||||||
|
"with",
|
||||||
|
"within",
|
||||||
|
"without",
|
||||||
|
"year",
|
||||||
|
];
|
||||||
|
|
||||||
|
// these are reserved words we have identified to be functions
|
||||||
|
// and should only be highlighted in a dispatch-like context
|
||||||
|
// ie, array_agg(...), etc.
|
||||||
|
const RESERVED_FUNCTIONS = [
|
||||||
|
"abs",
|
||||||
|
"acos",
|
||||||
|
"array_agg",
|
||||||
|
"asin",
|
||||||
|
"atan",
|
||||||
|
"avg",
|
||||||
|
"cast",
|
||||||
|
"ceil",
|
||||||
|
"ceiling",
|
||||||
|
"coalesce",
|
||||||
|
"corr",
|
||||||
|
"cos",
|
||||||
|
"cosh",
|
||||||
|
"count",
|
||||||
|
"covar_pop",
|
||||||
|
"covar_samp",
|
||||||
|
"cume_dist",
|
||||||
|
"dense_rank",
|
||||||
|
"deref",
|
||||||
|
"element",
|
||||||
|
"exp",
|
||||||
|
"extract",
|
||||||
|
"first_value",
|
||||||
|
"floor",
|
||||||
|
"json_array",
|
||||||
|
"json_arrayagg",
|
||||||
|
"json_exists",
|
||||||
|
"json_object",
|
||||||
|
"json_objectagg",
|
||||||
|
"json_query",
|
||||||
|
"json_table",
|
||||||
|
"json_table_primitive",
|
||||||
|
"json_value",
|
||||||
|
"lag",
|
||||||
|
"last_value",
|
||||||
|
"lead",
|
||||||
|
"listagg",
|
||||||
|
"ln",
|
||||||
|
"log",
|
||||||
|
"log10",
|
||||||
|
"lower",
|
||||||
|
"max",
|
||||||
|
"min",
|
||||||
|
"mod",
|
||||||
|
"nth_value",
|
||||||
|
"ntile",
|
||||||
|
"nullif",
|
||||||
|
"percent_rank",
|
||||||
|
"percentile_cont",
|
||||||
|
"percentile_disc",
|
||||||
|
"position",
|
||||||
|
"position_regex",
|
||||||
|
"power",
|
||||||
|
"rank",
|
||||||
|
"regr_avgx",
|
||||||
|
"regr_avgy",
|
||||||
|
"regr_count",
|
||||||
|
"regr_intercept",
|
||||||
|
"regr_r2",
|
||||||
|
"regr_slope",
|
||||||
|
"regr_sxx",
|
||||||
|
"regr_sxy",
|
||||||
|
"regr_syy",
|
||||||
|
"row_number",
|
||||||
|
"sin",
|
||||||
|
"sinh",
|
||||||
|
"sqrt",
|
||||||
|
"stddev_pop",
|
||||||
|
"stddev_samp",
|
||||||
|
"substring",
|
||||||
|
"substring_regex",
|
||||||
|
"sum",
|
||||||
|
"tan",
|
||||||
|
"tanh",
|
||||||
|
"translate",
|
||||||
|
"translate_regex",
|
||||||
|
"treat",
|
||||||
|
"trim",
|
||||||
|
"trim_array",
|
||||||
|
"unnest",
|
||||||
|
"upper",
|
||||||
|
"value_of",
|
||||||
|
"var_pop",
|
||||||
|
"var_samp",
|
||||||
|
"width_bucket",
|
||||||
|
];
|
||||||
|
|
||||||
|
// these functions can
|
||||||
|
const POSSIBLE_WITHOUT_PARENS = [
|
||||||
|
"current_catalog",
|
||||||
|
"current_date",
|
||||||
|
"current_default_transform_group",
|
||||||
|
"current_path",
|
||||||
|
"current_role",
|
||||||
|
"current_schema",
|
||||||
|
"current_transform_group_for_type",
|
||||||
|
"current_user",
|
||||||
|
"session_user",
|
||||||
|
"system_time",
|
||||||
|
"system_user",
|
||||||
|
"current_time",
|
||||||
|
"localtime",
|
||||||
|
"current_timestamp",
|
||||||
|
"localtimestamp"
|
||||||
|
];
|
||||||
|
|
||||||
|
// those exist to boost relevance making these very
|
||||||
|
// "SQL like" keyword combos worth +1 extra relevance
|
||||||
|
const COMBOS = [
|
||||||
|
"create table",
|
||||||
|
"insert into",
|
||||||
|
"primary key",
|
||||||
|
"foreign key",
|
||||||
|
"not null",
|
||||||
|
"alter table",
|
||||||
|
"add constraint",
|
||||||
|
"grouping sets",
|
||||||
|
"on overflow",
|
||||||
|
"character set",
|
||||||
|
"respect nulls",
|
||||||
|
"ignore nulls",
|
||||||
|
"nulls first",
|
||||||
|
"nulls last",
|
||||||
|
"depth first",
|
||||||
|
"breadth first"
|
||||||
|
];
|
||||||
|
|
||||||
|
const FUNCTIONS = RESERVED_FUNCTIONS;
|
||||||
|
|
||||||
|
const KEYWORDS = [
|
||||||
|
...RESERVED_WORDS,
|
||||||
|
...NON_RESERVED_WORDS
|
||||||
|
].filter((keyword) => {
|
||||||
|
return !RESERVED_FUNCTIONS.includes(keyword);
|
||||||
|
});
|
||||||
|
|
||||||
|
const VARIABLE = {
|
||||||
|
scope: "variable",
|
||||||
|
match: /@[a-z0-9][a-z0-9_]*/,
|
||||||
|
};
|
||||||
|
|
||||||
|
const OPERATOR = {
|
||||||
|
scope: "operator",
|
||||||
|
match: /[-+*/=%^~]|&&?|\|\|?|!=?|<(?:=>?|<|>)?|>[>=]?/,
|
||||||
|
relevance: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
const FUNCTION_CALL = {
|
||||||
|
match: regex.concat(/\b/, regex.either(...FUNCTIONS), /\s*\(/),
|
||||||
|
relevance: 0,
|
||||||
|
keywords: { built_in: FUNCTIONS }
|
||||||
|
};
|
||||||
|
|
||||||
|
// turns a multi-word keyword combo into a regex that doesn't
|
||||||
|
// care about extra whitespace etc.
|
||||||
|
// input: "START QUERY"
|
||||||
|
// output: /\bSTART\s+QUERY\b/
|
||||||
|
function kws_to_regex(list) {
|
||||||
|
return regex.concat(
|
||||||
|
/\b/,
|
||||||
|
regex.either(...list.map((kw) => {
|
||||||
|
return kw.replace(/\s+/, "\\s+")
|
||||||
|
})),
|
||||||
|
/\b/
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MULTI_WORD_KEYWORDS = {
|
||||||
|
scope: "keyword",
|
||||||
|
match: kws_to_regex(COMBOS),
|
||||||
|
relevance: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
// keywords with less than 3 letters are reduced in relevancy
|
||||||
|
function reduceRelevancy(list, {
|
||||||
|
exceptions, when
|
||||||
|
} = {}) {
|
||||||
|
const qualifyFn = when;
|
||||||
|
exceptions = exceptions || [];
|
||||||
|
return list.map((item) => {
|
||||||
|
if (item.match(/\|\d+$/) || exceptions.includes(item)) {
|
||||||
|
return item;
|
||||||
|
} else if (qualifyFn(item)) {
|
||||||
|
return `${item}|0`;
|
||||||
|
} else {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: 'SQL',
|
||||||
|
case_insensitive: true,
|
||||||
|
// does not include {} or HTML tags `</`
|
||||||
|
illegal: /[{}]|<\//,
|
||||||
|
keywords: {
|
||||||
|
$pattern: /\b[\w\.]+/,
|
||||||
|
keyword:
|
||||||
|
reduceRelevancy(KEYWORDS, { when: (x) => x.length < 3 }),
|
||||||
|
literal: LITERALS,
|
||||||
|
type: TYPES,
|
||||||
|
built_in: POSSIBLE_WITHOUT_PARENS
|
||||||
|
},
|
||||||
|
contains: [
|
||||||
|
{
|
||||||
|
scope: "type",
|
||||||
|
match: kws_to_regex(MULTI_WORD_TYPES)
|
||||||
|
},
|
||||||
|
MULTI_WORD_KEYWORDS,
|
||||||
|
FUNCTION_CALL,
|
||||||
|
VARIABLE,
|
||||||
|
STRING,
|
||||||
|
QUOTED_IDENTIFIER,
|
||||||
|
hljs.C_NUMBER_MODE,
|
||||||
|
hljs.C_BLOCK_COMMENT_MODE,
|
||||||
|
COMMENT_MODE,
|
||||||
|
OPERATOR
|
||||||
|
]
|
||||||
|
};
|
||||||
|
}
|
||||||
58
packages/web/src/forms/FormJsonFileInputField.svelte
Normal file
58
packages/web/src/forms/FormJsonFileInputField.svelte
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import SimpleFilesInput, { ProcessedFile } from '../impexp/SimpleFilesInput.svelte';
|
||||||
|
import { FileParseResult, parseFileAsJson } from '../utility/parseFileAsJson';
|
||||||
|
import { getFormContext } from './FormProviderCore.svelte';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
|
export let label: string;
|
||||||
|
export let buttonLabel: string = 'Choose File';
|
||||||
|
export let name: string;
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
export let accept: string = '.json,application/json';
|
||||||
|
export let templateProps = {};
|
||||||
|
|
||||||
|
const { template, setFieldValue, values } = getFormContext();
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
|
let fileName: string | null = null;
|
||||||
|
$: hasValue = $values?.[name] != null;
|
||||||
|
$: displayLabel = getDisplayLabel(buttonLabel, hasValue, fileName);
|
||||||
|
|
||||||
|
async function handleFileChange(fileData: ProcessedFile): Promise<void> {
|
||||||
|
const parseResult: FileParseResult = await parseFileAsJson(fileData.file);
|
||||||
|
|
||||||
|
if (parseResult.success) {
|
||||||
|
fileName = fileData.name;
|
||||||
|
setFieldValue(name, parseResult.data);
|
||||||
|
dispatch('change', {
|
||||||
|
success: true,
|
||||||
|
data: parseResult.data,
|
||||||
|
fileName: fileData.name,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
fileName = null;
|
||||||
|
setFieldValue(name, null);
|
||||||
|
dispatch('change', {
|
||||||
|
success: false,
|
||||||
|
error: parseResult.error,
|
||||||
|
fileName: fileData.name,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getDisplayLabel(baseLabel: string, hasValue: boolean, fileName: string | null): string {
|
||||||
|
if (!hasValue) {
|
||||||
|
return baseLabel;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fileName) {
|
||||||
|
return `${baseLabel} (${fileName})`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${baseLabel} (JSON loaded)`;
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<svelte:component this={template} type="file" {label} {disabled} {...templateProps}>
|
||||||
|
<SimpleFilesInput label={displayLabel} {accept} {disabled} onChange={handleFileChange} {...$$restProps} />
|
||||||
|
</svelte:component>
|
||||||
@@ -73,6 +73,7 @@
|
|||||||
'icon scheduler-event': 'mdi mdi-calendar-blank',
|
'icon scheduler-event': 'mdi mdi-calendar-blank',
|
||||||
'icon arrow-link': 'mdi mdi-arrow-top-right-thick',
|
'icon arrow-link': 'mdi mdi-arrow-top-right-thick',
|
||||||
'icon reset': 'mdi mdi-cancel',
|
'icon reset': 'mdi mdi-cancel',
|
||||||
|
'icon send': 'mdi mdi-send',
|
||||||
|
|
||||||
'icon window-restore': 'mdi mdi-window-restore',
|
'icon window-restore': 'mdi mdi-window-restore',
|
||||||
'icon window-maximize': 'mdi mdi-window-maximize',
|
'icon window-maximize': 'mdi mdi-window-maximize',
|
||||||
@@ -163,8 +164,10 @@
|
|||||||
'icon wait': 'mdi mdi-timer-sand',
|
'icon wait': 'mdi mdi-timer-sand',
|
||||||
'icon more': 'mdi mdi-more',
|
'icon more': 'mdi mdi-more',
|
||||||
'icon copy': 'mdi mdi-content-copy',
|
'icon copy': 'mdi mdi-content-copy',
|
||||||
|
'icon arrow-start-here': 'mdi mdi-arrow-down-bold-circle',
|
||||||
|
|
||||||
'icon run': 'mdi mdi-play',
|
'icon run': 'mdi mdi-play',
|
||||||
|
'icon run-settings': 'mdi mdi-cog-play',
|
||||||
'icon chevron-down': 'mdi mdi-chevron-down',
|
'icon chevron-down': 'mdi mdi-chevron-down',
|
||||||
'icon chevron-left': 'mdi mdi-chevron-left',
|
'icon chevron-left': 'mdi mdi-chevron-left',
|
||||||
'icon chevron-right': 'mdi mdi-chevron-right',
|
'icon chevron-right': 'mdi mdi-chevron-right',
|
||||||
@@ -280,6 +283,8 @@
|
|||||||
'img admin': 'mdi mdi-security color-icon-blue',
|
'img admin': 'mdi mdi-security color-icon-blue',
|
||||||
'img auth': 'mdi mdi-account-key color-icon-blue',
|
'img auth': 'mdi mdi-account-key color-icon-blue',
|
||||||
'img cloud-connection': 'mdi mdi-cloud-lock color-icon-blue',
|
'img cloud-connection': 'mdi mdi-cloud-lock color-icon-blue',
|
||||||
|
'img ai': 'mdi mdi-head-lightbulb color-icon-yellow',
|
||||||
|
'img run': 'mdi mdi-play color-icon-blue',
|
||||||
|
|
||||||
'img add': 'mdi mdi-plus-circle color-icon-green',
|
'img add': 'mdi mdi-plus-circle color-icon-green',
|
||||||
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
'img minus': 'mdi mdi-minus-circle color-icon-red',
|
||||||
@@ -338,6 +343,7 @@
|
|||||||
'img db-restore': 'mdi mdi-database-import color-icon-red',
|
'img db-restore': 'mdi mdi-database-import color-icon-red',
|
||||||
'img settings': 'mdi mdi-cog color-icon-blue',
|
'img settings': 'mdi mdi-cog color-icon-blue',
|
||||||
'img data-deploy': 'mdi mdi-database-settings color-icon-green',
|
'img data-deploy': 'mdi mdi-database-settings color-icon-green',
|
||||||
|
'img arrow-start-here': 'mdi mdi-arrow-down-bold-circle color-icon-green',
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
51
packages/web/src/icons/ThinkingIcon.svelte
Normal file
51
packages/web/src/icons/ThinkingIcon.svelte
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
<!-- Chatbot “thinking” indicator -->
|
||||||
|
<div class="typing-indicator" aria-label="Chatbot is thinking">
|
||||||
|
<span></span><span></span><span></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
/* Container keeps the dots neatly aligned */
|
||||||
|
.typing-indicator {
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 0.4rem;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
/* Optional tweaks */
|
||||||
|
height: 1.25rem; /* keeps layout height consistent */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Each dot */
|
||||||
|
.typing-indicator span {
|
||||||
|
width: 0.5rem;
|
||||||
|
height: 0.5rem;
|
||||||
|
background: currentColor; /* inherits text color */
|
||||||
|
border-radius: 50%;
|
||||||
|
opacity: 0.2; /* start slightly faded */
|
||||||
|
animation: pulse 1s infinite ease-in-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Delay each dot so they animate sequentially */
|
||||||
|
.typing-indicator span:nth-child(1) {
|
||||||
|
animation-delay: 0s;
|
||||||
|
}
|
||||||
|
.typing-indicator span:nth-child(2) {
|
||||||
|
animation-delay: 0.15s;
|
||||||
|
}
|
||||||
|
.typing-indicator span:nth-child(3) {
|
||||||
|
animation-delay: 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyframes for the pulsing effect */
|
||||||
|
@keyframes pulse {
|
||||||
|
0%,
|
||||||
|
80%,
|
||||||
|
100% {
|
||||||
|
opacity: 0.2;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
40% {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(-0.2rem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
105
packages/web/src/impexp/SimpleFilesInput.svelte
Normal file
105
packages/web/src/impexp/SimpleFilesInput.svelte
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<script context="module" lang="ts">
|
||||||
|
export type ProcessedFile = {
|
||||||
|
name: string;
|
||||||
|
size: number;
|
||||||
|
type: string;
|
||||||
|
lastModified: number;
|
||||||
|
content: string | ArrayBuffer | null;
|
||||||
|
file: File;
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
export let disabled: boolean = false;
|
||||||
|
export let label: string = 'Choose File';
|
||||||
|
export let onChange: ((fileData: ProcessedFile | ProcessedFile[]) => void) | null = null;
|
||||||
|
export let accept: string = '*';
|
||||||
|
export let multiple: boolean = false;
|
||||||
|
|
||||||
|
let fileInput: HTMLInputElement;
|
||||||
|
|
||||||
|
function handleFileChange(event: Event): void {
|
||||||
|
const target = event.target as HTMLInputElement;
|
||||||
|
const files = target.files;
|
||||||
|
if (!files || files.length < 0 || !onChange) return;
|
||||||
|
|
||||||
|
if (multiple) {
|
||||||
|
const processedFiles = Array.from(files).map(processFile);
|
||||||
|
Promise.all(processedFiles).then((results: ProcessedFile[]) => {
|
||||||
|
onChange(results);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
processFile(files[0]).then((result: ProcessedFile) => {
|
||||||
|
onChange(result);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function processFile(file: File): Promise<ProcessedFile> {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
const reader = new FileReader();
|
||||||
|
|
||||||
|
reader.onload = (e: ProgressEvent<FileReader>) => {
|
||||||
|
resolve({
|
||||||
|
name: file.name,
|
||||||
|
size: file.size,
|
||||||
|
type: file.type,
|
||||||
|
lastModified: file.lastModified,
|
||||||
|
content: e.target?.result || null,
|
||||||
|
file: file,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function triggerFileInput(): void {
|
||||||
|
fileInput.click();
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<input
|
||||||
|
{disabled}
|
||||||
|
bind:this={fileInput}
|
||||||
|
type="file"
|
||||||
|
{accept}
|
||||||
|
{multiple}
|
||||||
|
on:change={handleFileChange}
|
||||||
|
style="display: none;"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<button {disabled} on:click={triggerFileInput} class="file-input-btn">
|
||||||
|
{label}
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.file-input-btn {
|
||||||
|
border: 1px solid var(--theme-bg-button-inv-2);
|
||||||
|
padding: 5px;
|
||||||
|
margin: 2px;
|
||||||
|
background-color: var(--theme-bg-button-inv);
|
||||||
|
color: var(--theme-font-inv-1);
|
||||||
|
border-radius: 2px;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input-btn:hover:not(:disabled) {
|
||||||
|
background-color: var(--theme-bg-button-inv-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input-btn:active:not(:disabled) {
|
||||||
|
background-color: var(--theme-bg-button-inv-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input-btn:focus {
|
||||||
|
outline: 2px solid var(--theme-bg-button-inv-2);
|
||||||
|
outline-offset: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-input-btn:disabled {
|
||||||
|
background-color: var(--theme-bg-button-inv-3);
|
||||||
|
color: var(--theme-font-inv-3);
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -173,7 +173,9 @@
|
|||||||
|
|
||||||
{#if storageType == 'database' || storageType == 'query'}
|
{#if storageType == 'database' || storageType == 'query'}
|
||||||
<FormConnectionSelect name={connectionIdField} label="Server" {direction} />
|
<FormConnectionSelect name={connectionIdField} label="Server" {direction} />
|
||||||
<FormDatabaseSelect conidName={connectionIdField} name={databaseNameField} label="Database" />
|
{#if !$connectionInfo?.singleDatabase}
|
||||||
|
<FormDatabaseSelect conidName={connectionIdField} name={databaseNameField} label="Database" />
|
||||||
|
{/if}
|
||||||
{/if}
|
{/if}
|
||||||
{#if storageType == 'database'}
|
{#if storageType == 'database'}
|
||||||
<FormSchemaSelect
|
<FormSchemaSelect
|
||||||
|
|||||||
@@ -105,6 +105,16 @@
|
|||||||
disabledMessage: 'Database comparison is not available for current database',
|
disabledMessage: 'Database comparison is not available for current database',
|
||||||
isProFeature: true,
|
isProFeature: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
icon: 'icon ai',
|
||||||
|
colorClass: 'color-icon-blue',
|
||||||
|
title: 'Database Chat',
|
||||||
|
description: 'Chat with your database using AI',
|
||||||
|
command: 'database.chat',
|
||||||
|
isProFeature: true,
|
||||||
|
disabledMessage: 'Database chat is not available for current database',
|
||||||
|
testid: 'NewObjectModal_databaseChat',
|
||||||
|
}
|
||||||
];
|
];
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -63,7 +63,9 @@
|
|||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<FormRadioGroupItem name="joinOperator" value=" " text="And" />
|
<FormRadioGroupItem name="joinOperator" value=" " text="And" />
|
||||||
<FormRadioGroupItem name="joinOperator" value="," text="Or" />
|
{#if !filterBehaviour.disableOr}
|
||||||
|
<FormRadioGroupItem name="joinOperator" value="," text="Or" />
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
AI Assistant
|
|
||||||
@@ -18,6 +18,9 @@
|
|||||||
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
|
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
|
||||||
import { getConnectionLabel } from 'dbgate-tools';
|
import { getConnectionLabel } from 'dbgate-tools';
|
||||||
import { _t } from '../translations';
|
import { _t } from '../translations';
|
||||||
|
import FilesInput from '../impexp/FilesInput.svelte';
|
||||||
|
import SimpleFilesInput from '../impexp/SimpleFilesInput.svelte';
|
||||||
|
import FormJsonFileInputField from '../forms/FormJsonFileInputField.svelte';
|
||||||
|
|
||||||
export let getDatabaseList;
|
export let getDatabaseList;
|
||||||
export let currentConnection;
|
export let currentConnection;
|
||||||
@@ -462,6 +465,10 @@
|
|||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
|
{#if driver?.showConnectionField('certificateJson', $values, showConnectionFieldArgs)}
|
||||||
|
<FormJsonFileInputField disabled={isConnected} label="Service account key JSON" name="certificateJson" />
|
||||||
|
{/if}
|
||||||
|
|
||||||
{#if driver}
|
{#if driver}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 mr-1">
|
<div class="col-6 mr-1">
|
||||||
|
|||||||
@@ -13,15 +13,6 @@
|
|||||||
testEnabled: () => getCurrentEditor()?.isSqlEditor(),
|
testEnabled: () => getCurrentEditor()?.isSqlEditor(),
|
||||||
onClick: () => getCurrentEditor().formatCode(),
|
onClick: () => getCurrentEditor().formatCode(),
|
||||||
});
|
});
|
||||||
registerCommand({
|
|
||||||
id: 'query.switchAiAssistant',
|
|
||||||
category: 'Query',
|
|
||||||
name: 'AI Assistant',
|
|
||||||
keyText: 'Shift+Alt+A',
|
|
||||||
icon: 'icon ai',
|
|
||||||
testEnabled: () => isProApp(),
|
|
||||||
onClick: () => getCurrentEditor().toggleAiAssistant(),
|
|
||||||
});
|
|
||||||
registerCommand({
|
registerCommand({
|
||||||
id: 'query.insertSqlJoin',
|
id: 'query.insertSqlJoin',
|
||||||
category: 'Query',
|
category: 'Query',
|
||||||
@@ -157,7 +148,6 @@
|
|||||||
import QueryParametersModal from '../modals/QueryParametersModal.svelte';
|
import QueryParametersModal from '../modals/QueryParametersModal.svelte';
|
||||||
import { isProApp } from '../utility/proTools';
|
import { isProApp } from '../utility/proTools';
|
||||||
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||||
import QueryAiAssistant from '../query/QueryAiAssistant.svelte';
|
|
||||||
import uuidv1 from 'uuid/v1';
|
import uuidv1 from 'uuid/v1';
|
||||||
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
import ToolStripButton from '../buttons/ToolStripButton.svelte';
|
||||||
import { getIntSettingsValue } from '../settings/settingsTools';
|
import { getIntSettingsValue } from '../settings/settingsTools';
|
||||||
@@ -212,8 +202,6 @@
|
|||||||
let domEditor;
|
let domEditor;
|
||||||
let domToolStrip;
|
let domToolStrip;
|
||||||
let intervalId;
|
let intervalId;
|
||||||
let isAiAssistantVisible = isProApp() && localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
|
|
||||||
let domAiAssistant;
|
|
||||||
let isInTransaction = false;
|
let isInTransaction = false;
|
||||||
let isAutocommit = false;
|
let isAutocommit = false;
|
||||||
let splitterInitialValue = undefined;
|
let splitterInitialValue = undefined;
|
||||||
@@ -287,12 +275,6 @@
|
|||||||
domEditor?.getEditor()?.focus();
|
domEditor?.getEditor()?.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
$: {
|
|
||||||
if (!isAiAssistantVisible && domEditor) {
|
|
||||||
domEditor?.getEditor()?.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export function isSqlEditor() {
|
export function isSqlEditor() {
|
||||||
return driver?.databaseEngineTypes?.includes('sql');
|
return driver?.databaseEngineTypes?.includes('sql');
|
||||||
}
|
}
|
||||||
@@ -317,10 +299,6 @@
|
|||||||
visibleResultTabs = !visibleResultTabs;
|
visibleResultTabs = !visibleResultTabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function toggleAiAssistant() {
|
|
||||||
isAiAssistantVisible = !isAiAssistantVisible;
|
|
||||||
}
|
|
||||||
|
|
||||||
function getParameterSplitterOptions() {
|
function getParameterSplitterOptions() {
|
||||||
if (!queryParameterStyle) {
|
if (!queryParameterStyle) {
|
||||||
return null;
|
return null;
|
||||||
@@ -631,29 +609,6 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleKeyDown(event) {
|
|
||||||
if (isProApp()) {
|
|
||||||
if (event.code == 'Space' && event.shiftKey && event.ctrlKey && !isAiAssistantVisible) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
toggleAiAssistant();
|
|
||||||
await sleep(100);
|
|
||||||
if (domAiAssistant) {
|
|
||||||
domAiAssistant.handleCompleteOnCursor();
|
|
||||||
domEditor?.getEditor()?.focus();
|
|
||||||
}
|
|
||||||
} else if (event.code == 'Space' && event.shiftKey && event.ctrlKey && isAiAssistantVisible && domAiAssistant) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
domAiAssistant.handleCompleteOnCursor();
|
|
||||||
} else if (event.code?.startsWith('Digit') && event.altKey && isAiAssistantVisible && domAiAssistant) {
|
|
||||||
event.preventDefault();
|
|
||||||
event.stopPropagation();
|
|
||||||
domAiAssistant.insertCompletion(parseInt(event.code.substring(5)) - 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function createMenu() {
|
function createMenu() {
|
||||||
return [
|
return [
|
||||||
{ command: 'query.execute' },
|
{ command: 'query.execute' },
|
||||||
@@ -675,7 +630,6 @@
|
|||||||
{ command: 'query.replace' },
|
{ command: 'query.replace' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ command: 'query.toggleVisibleResultTabs' },
|
{ command: 'query.toggleVisibleResultTabs' },
|
||||||
{ command: 'query.switchAiAssistant', hideDisabled: true },
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -695,127 +649,91 @@
|
|||||||
localStorage.getItem(`tabdata_queryParamStyle_${tabid}`) ??
|
localStorage.getItem(`tabdata_queryParamStyle_${tabid}`) ??
|
||||||
initialArgs?.queryParameterStyle ??
|
initialArgs?.queryParameterStyle ??
|
||||||
(initialArgs?.scriptTemplate == 'CALL OBJECT' ? ':' : '');
|
(initialArgs?.scriptTemplate == 'CALL OBJECT' ? ':' : '');
|
||||||
|
|
||||||
$: localStorage.setItem(`tabdata_isAiAssistantVisible_${tabid}`, isAiAssistantVisible ? 'true' : 'false');
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripContainer bind:this={domToolStrip}>
|
<ToolStripContainer bind:this={domToolStrip}>
|
||||||
<HorizontalSplitter isSplitter={isAiAssistantVisible} initialSizeRight={300}>
|
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}>
|
||||||
<svelte:fragment slot="1">
|
<svelte:fragment slot="1">
|
||||||
<VerticalSplitter isSplitter={visibleResultTabs} initialValue={splitterInitialValue}>
|
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||||
<svelte:fragment slot="1">
|
<SqlEditor
|
||||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
engine={$connection && $connection.engine}
|
||||||
<SqlEditor
|
{conid}
|
||||||
engine={$connection && $connection.engine}
|
{database}
|
||||||
{conid}
|
splitterOptions={{
|
||||||
{database}
|
...driver?.getQuerySplitterOptions('editor'),
|
||||||
splitterOptions={{
|
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
|
||||||
...driver?.getQuerySplitterOptions('editor'),
|
}}
|
||||||
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
|
options={{
|
||||||
}}
|
wrap: enableWrap,
|
||||||
options={{
|
}}
|
||||||
wrap: enableWrap,
|
value={$editorState.value || ''}
|
||||||
}}
|
menu={createMenu()}
|
||||||
value={$editorState.value || ''}
|
on:input={e => {
|
||||||
menu={createMenu()}
|
setEditorData(e.detail);
|
||||||
on:input={e => {
|
if (isInitialized) {
|
||||||
setEditorData(e.detail);
|
markTabUnsaved(tabid);
|
||||||
if (isInitialized) {
|
}
|
||||||
markTabUnsaved(tabid);
|
errorMessages = [];
|
||||||
}
|
}}
|
||||||
errorMessages = [];
|
on:focus={() => {
|
||||||
}}
|
activator.activate();
|
||||||
on:focus={() => {
|
domToolStrip?.activate();
|
||||||
activator.activate();
|
invalidateCommands();
|
||||||
domToolStrip?.activate();
|
setTimeout(() => {
|
||||||
invalidateCommands();
|
isInitialized = true;
|
||||||
setTimeout(() => {
|
}, 100);
|
||||||
isInitialized = true;
|
}}
|
||||||
}, 100);
|
bind:this={domEditor}
|
||||||
}}
|
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
||||||
bind:this={domEditor}
|
{errorMessages}
|
||||||
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
/>
|
||||||
{errorMessages}
|
{:else}
|
||||||
onKeyDown={handleKeyDown}
|
<AceEditor
|
||||||
/>
|
mode={driver?.editorMode || 'sql'}
|
||||||
{:else}
|
value={$editorState.value || ''}
|
||||||
<AceEditor
|
splitterOptions={{
|
||||||
mode={driver?.editorMode || 'sql'}
|
...driver?.getQuerySplitterOptions('editor'),
|
||||||
value={$editorState.value || ''}
|
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
|
||||||
splitterOptions={{
|
}}
|
||||||
...driver?.getQuerySplitterOptions('editor'),
|
options={{
|
||||||
splitByEmptyLine: !$settingsValue?.['sqlEditor.disableSplitByEmptyLine'],
|
wrap: enableWrap,
|
||||||
}}
|
}}
|
||||||
options={{
|
menu={createMenu()}
|
||||||
wrap: enableWrap,
|
on:input={e => setEditorData(e.detail)}
|
||||||
}}
|
on:focus={() => {
|
||||||
menu={createMenu()}
|
activator.activate();
|
||||||
on:input={e => setEditorData(e.detail)}
|
domToolStrip?.activate();
|
||||||
on:focus={() => {
|
invalidateCommands();
|
||||||
activator.activate();
|
}}
|
||||||
domToolStrip?.activate();
|
bind:this={domEditor}
|
||||||
invalidateCommands();
|
/>
|
||||||
}}
|
{/if}
|
||||||
bind:this={domEditor}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
</svelte:fragment>
|
|
||||||
<svelte:fragment slot="2">
|
|
||||||
<ResultTabs
|
|
||||||
bind:this={domResultTabs}
|
|
||||||
tabs={[{ label: 'Messages', slot: 0 }]}
|
|
||||||
{sessionId}
|
|
||||||
{executeNumber}
|
|
||||||
bind:resultCount
|
|
||||||
{driver}
|
|
||||||
onSetFrontMatterField={handleSetFrontMatterField}
|
|
||||||
onGetFrontMatter={() => getSqlFrontMatter($editorValue, yaml)}
|
|
||||||
>
|
|
||||||
<svelte:fragment slot="0">
|
|
||||||
<SocketMessageView
|
|
||||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
|
||||||
onMessageClick={handleMesageClick}
|
|
||||||
{executeNumber}
|
|
||||||
startLine={executeStartLine}
|
|
||||||
showProcedure
|
|
||||||
showLine
|
|
||||||
onChangeErrors={handleChangeErrors}
|
|
||||||
/>
|
|
||||||
</svelte:fragment>
|
|
||||||
</ResultTabs>
|
|
||||||
</svelte:fragment>
|
|
||||||
</VerticalSplitter>
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
<svelte:fragment slot="2">
|
<svelte:fragment slot="2">
|
||||||
<QueryAiAssistant
|
<ResultTabs
|
||||||
bind:this={domAiAssistant}
|
bind:this={domResultTabs}
|
||||||
{conid}
|
tabs={[{ label: 'Messages', slot: 0 }]}
|
||||||
{database}
|
{sessionId}
|
||||||
|
{executeNumber}
|
||||||
|
bind:resultCount
|
||||||
{driver}
|
{driver}
|
||||||
onClose={() => {
|
onSetFrontMatterField={handleSetFrontMatterField}
|
||||||
isAiAssistantVisible = false;
|
onGetFrontMatter={() => getSqlFrontMatter($editorValue, yaml)}
|
||||||
}}
|
>
|
||||||
text={$editorValue}
|
<svelte:fragment slot="0">
|
||||||
getLine={() => domEditor.getEditor().getSelectionRange().start.row}
|
<SocketMessageView
|
||||||
onInsertAtCursor={text => {
|
eventName={sessionId ? `session-info-${sessionId}` : null}
|
||||||
const editor = domEditor.getEditor();
|
onMessageClick={handleMesageClick}
|
||||||
editor.session.insert(editor.getCursorPosition(), text);
|
{executeNumber}
|
||||||
domEditor?.getEditor()?.focus();
|
startLine={executeStartLine}
|
||||||
}}
|
showProcedure
|
||||||
getTextOrSelectedText={() => domEditor.getEditor().getSelectedText() || $editorValue}
|
showLine
|
||||||
onSetSelectedText={text => {
|
onChangeErrors={handleChangeErrors}
|
||||||
const editor = domEditor.getEditor();
|
/>
|
||||||
if (editor.getSelectedText()) {
|
</svelte:fragment>
|
||||||
const range = editor.selection.getRange();
|
</ResultTabs>
|
||||||
editor.session.replace(range, text);
|
|
||||||
} else {
|
|
||||||
editor.setValue(text);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
{tabid}
|
|
||||||
/>
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</HorizontalSplitter>
|
</VerticalSplitter>
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandSplitButton
|
<ToolStripCommandSplitButton
|
||||||
commands={['query.execute', 'query.executeCurrent']}
|
commands={['query.execute', 'query.executeCurrent']}
|
||||||
@@ -854,13 +772,6 @@
|
|||||||
icon="icon at"
|
icon="icon at"
|
||||||
title="Query parameter style"
|
title="Query parameter style"
|
||||||
/>
|
/>
|
||||||
<ToolStripCommandButton
|
|
||||||
command="query.switchAiAssistant"
|
|
||||||
hideDisabled
|
|
||||||
data-testid="QueryTab_switchAiAssistantButton"
|
|
||||||
>
|
|
||||||
AI Assistant
|
|
||||||
</ToolStripCommandButton>
|
|
||||||
<ToolStripCommandButton
|
<ToolStripCommandButton
|
||||||
command="query.beginTransaction"
|
command="query.beginTransaction"
|
||||||
data-testid="QueryTab_beginTransactionButton"
|
data-testid="QueryTab_beginTransactionButton"
|
||||||
|
|||||||
38
packages/web/src/utility/parseFileAsJson.js
Normal file
38
packages/web/src/utility/parseFileAsJson.js
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
/**
|
||||||
|
* @template [T = any]
|
||||||
|
* @typedef {Object} FileParseResultSuccess
|
||||||
|
* @property {true} success
|
||||||
|
* @property {T} data
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} FileParseResultError
|
||||||
|
* @property {false} success
|
||||||
|
* @property {string} error
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template [T = any]
|
||||||
|
* @typedef {FileParseResultSuccess<T> | FileParseResultError} FileParseResult
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template [T = any]
|
||||||
|
* @param {File} file
|
||||||
|
* @returns {Promise<FileParseResult<T>>}
|
||||||
|
*/
|
||||||
|
export async function parseFileAsJson(file) {
|
||||||
|
try {
|
||||||
|
const text = await file.text();
|
||||||
|
const data = JSON.parse(text);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error: error instanceof Error ? error.message : 'Unknown parsing error',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -60,11 +60,11 @@
|
|||||||
name: 'archive',
|
name: 'archive',
|
||||||
title: 'Archive (saved tabular data)',
|
title: 'Archive (saved tabular data)',
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
icon: 'icon plugin',
|
// icon: 'icon plugin',
|
||||||
name: 'plugins',
|
// name: 'plugins',
|
||||||
title: 'Extensions & Plugins',
|
// title: 'Extensions & Plugins',
|
||||||
},
|
// },
|
||||||
{
|
{
|
||||||
icon: 'icon cell-data',
|
icon: 'icon cell-data',
|
||||||
name: 'cell-data',
|
name: 'cell-data',
|
||||||
@@ -116,6 +116,13 @@
|
|||||||
$visibleWidgetSideBar = true;
|
$visibleWidgetSideBar = true;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
text: 'Manage plugins',
|
||||||
|
onClick: () => {
|
||||||
|
$selectedWidget = 'plugins';
|
||||||
|
$visibleWidgetSideBar = true;
|
||||||
|
},
|
||||||
|
},
|
||||||
];
|
];
|
||||||
currentDropDownMenu.set({ left, top, items });
|
currentDropDownMenu.set({ left, top, items });
|
||||||
}
|
}
|
||||||
@@ -159,6 +166,7 @@
|
|||||||
{#each widgets
|
{#each widgets
|
||||||
.filter(x => x && hasPermission(`widgets/${x.name}`))
|
.filter(x => x && hasPermission(`widgets/${x.name}`))
|
||||||
.filter(x => !x.isPremiumPromo || !isProApp())
|
.filter(x => !x.isPremiumPromo || !isProApp())
|
||||||
|
// .filter(x => !x.isPremiumOnly || isProApp())
|
||||||
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
|
.filter(x => x.name != 'cloud-private' || $cloudSigninTokenHolder) as item}
|
||||||
<div
|
<div
|
||||||
class="wrapper"
|
class="wrapper"
|
||||||
|
|||||||
20
silent
Normal file
20
silent
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
DEVMODE=1
|
||||||
|
SHELL_SCRIPTING=1
|
||||||
|
# LOCAL_DBGATE_CLOUD=1
|
||||||
|
# LOCAL_DBGATE_IDENTITY=1
|
||||||
|
|
||||||
|
# CLOUD_UPGRADE_FILE=c:\test\upg\upgrade.zip
|
||||||
|
|
||||||
|
# PERMISSIONS=~widgets/app,~widgets/plugins
|
||||||
|
# DISABLE_SHELL=1
|
||||||
|
# HIDE_APP_EDITOR=1
|
||||||
|
|
||||||
|
# DEVWEB=1
|
||||||
|
# LOGINS=admin,test
|
||||||
|
|
||||||
|
# LOGIN_PASSWORD_admin=admin
|
||||||
|
# LOGIN_PERMISSIONS_admin=*
|
||||||
|
|
||||||
|
# LOGIN_PASSWORD_test=test
|
||||||
|
# LOGIN_PERMISSIONS_test=~*, widgets/database
|
||||||
|
# WORKSPACE_DIR=/home/jena/dbgate-data-2
|
||||||
@@ -100,3 +100,9 @@ jobs:
|
|||||||
cd ..
|
cd ..
|
||||||
cd dbgate-merged/plugins/dbgate-plugin-cosmosdb
|
cd dbgate-merged/plugins/dbgate-plugin-cosmosdb
|
||||||
npm publish
|
npm publish
|
||||||
|
|
||||||
|
- name: Publish dbgate-plugin-firestore
|
||||||
|
run: |
|
||||||
|
cd ..
|
||||||
|
cd dbgate-merged/plugins/dbgate-plugin-firestore
|
||||||
|
npm publish
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ checkout-and-merge-pro:
|
|||||||
repository: dbgate/dbgate-pro
|
repository: dbgate/dbgate-pro
|
||||||
token: ${{ secrets.GH_TOKEN }}
|
token: ${{ secrets.GH_TOKEN }}
|
||||||
path: dbgate-pro
|
path: dbgate-pro
|
||||||
ref: 458d8843318c2f65aae6524bbea30513d88f4bf6
|
ref: 8a4dc2732a7097b5c4c48b4feb62609111cdf3e0
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
Reference in New Issue
Block a user