mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-18 00:56:02 +00:00
Merge branch 'master' into feature/cassandra
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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- 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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-aws-pro.yaml
vendored
2
.github/workflows/build-aws-pro.yaml
vendored
@@ -36,7 +36,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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- 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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
2
.github/workflows/build-npm-pro.yaml
vendored
2
.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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- name: Merge dbgate/dbgate-pro
|
- name: Merge dbgate/dbgate-pro
|
||||||
run: |
|
run: |
|
||||||
mkdir ../dbgate-pro
|
mkdir ../dbgate-pro
|
||||||
|
|||||||
@@ -349,4 +349,17 @@ describe('Data browser data', () => {
|
|||||||
cy.testid('CompareModelTab_tabOperations').click();
|
cy.testid('CompareModelTab_tabOperations').click();
|
||||||
cy.themeshot('comparesettings');
|
cy.themeshot('comparesettings');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it.only('Query editor - AI assistant', () => {
|
||||||
|
cy.contains('MySql-connection').click();
|
||||||
|
cy.contains('MyChinook').click();
|
||||||
|
cy.testid('TabsPanel_buttonNewQuery').click();
|
||||||
|
cy.testid('QueryTab_switchAiAssistantButton').click();
|
||||||
|
cy.testid('QueryAiAssistant_promptInput').type('album names');
|
||||||
|
cy.testid('QueryAiAssistant_queryFromQuestionButton').click();
|
||||||
|
cy.contains('Use this').click();
|
||||||
|
cy.testid('QueryTab_executeButton').click();
|
||||||
|
cy.contains('Balls to the Wall');
|
||||||
|
cy.themeshot('aiassistant');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ const pipeForkLogs = require('../utility/pipeForkLogs');
|
|||||||
const crypto = require('crypto');
|
const crypto = require('crypto');
|
||||||
const loadModelTransform = require('../utility/loadModelTransform');
|
const loadModelTransform = require('../utility/loadModelTransform');
|
||||||
const exportDbModelSql = require('../utility/exportDbModelSql');
|
const exportDbModelSql = require('../utility/exportDbModelSql');
|
||||||
|
const axios = require('axios');
|
||||||
|
const { callTextToSqlApi, callCompleteOnCursorApi, callRefactorSqlQueryApi } = require('../utility/authProxy');
|
||||||
|
|
||||||
const logger = getLogger('databaseConnections');
|
const logger = getLogger('databaseConnections');
|
||||||
|
|
||||||
@@ -562,4 +564,47 @@ module.exports = {
|
|||||||
|
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
textToSql_meta: true,
|
||||||
|
async textToSql({ conid, database, text, dialect }) {
|
||||||
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
|
const { structure } = existing || {};
|
||||||
|
if (!structure) return { errorMessage: 'No database structure' };
|
||||||
|
|
||||||
|
const res = await callTextToSqlApi(text, structure, dialect);
|
||||||
|
|
||||||
|
if (!res?.sql) {
|
||||||
|
return { errorMessage: 'No SQL generated' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
completeOnCursor_meta: true,
|
||||||
|
async completeOnCursor({ conid, database, text, dialect, line }) {
|
||||||
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
|
const { structure } = existing || {};
|
||||||
|
if (!structure) return { errorMessage: 'No database structure' };
|
||||||
|
const res = await callCompleteOnCursorApi(text, structure, dialect, line);
|
||||||
|
|
||||||
|
if (!res?.variants) {
|
||||||
|
return { errorMessage: 'No SQL generated' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
refactorSqlQuery_meta: true,
|
||||||
|
async refactorSqlQuery({ conid, database, query, task, dialect }) {
|
||||||
|
const existing = this.opened.find(x => x.conid == conid && x.database == database);
|
||||||
|
const { structure } = existing || {};
|
||||||
|
if (!structure) return { errorMessage: 'No database structure' };
|
||||||
|
const res = await callRefactorSqlQueryApi(query, task, structure, dialect);
|
||||||
|
|
||||||
|
if (!res?.sql) {
|
||||||
|
return { errorMessage: 'No SQL generated' };
|
||||||
|
}
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -24,6 +24,18 @@ async function getAwsIamToken(params) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function callTextToSqlApi(text, structure, dialect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callCompleteOnCursorApi(cursorId, query, position, dialect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function callRefactorSqlQueryApi(query, task, structure, dialect) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
isAuthProxySupported,
|
isAuthProxySupported,
|
||||||
authProxyGetRedirectUrl,
|
authProxyGetRedirectUrl,
|
||||||
@@ -32,4 +44,7 @@ module.exports = {
|
|||||||
getAuthProxyUrl,
|
getAuthProxyUrl,
|
||||||
supportsAwsIam,
|
supportsAwsIam,
|
||||||
getAwsIamToken,
|
getAwsIamToken,
|
||||||
|
callTextToSqlApi,
|
||||||
|
callCompleteOnCursorApi,
|
||||||
|
callRefactorSqlQueryApi,
|
||||||
};
|
};
|
||||||
|
|||||||
31
packages/types/dbinfo.d.ts
vendored
31
packages/types/dbinfo.d.ts
vendored
@@ -189,3 +189,34 @@ export interface DatabaseInfoObjects {
|
|||||||
export interface DatabaseInfo extends DatabaseInfoObjects {
|
export interface DatabaseInfo extends DatabaseInfoObjects {
|
||||||
engine?: string;
|
engine?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ColumnReferenceTiny {
|
||||||
|
n: string; // name
|
||||||
|
r?: string; // ref name
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PrimaryKeyInfoTiny {
|
||||||
|
c: ColumnReferenceTiny[]; // columns
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ForeignKeyInfoTiny {
|
||||||
|
c: ColumnReferenceTiny[]; // columns
|
||||||
|
r: string; // reference table name
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ColumnInfoTiny {
|
||||||
|
n: string; // name
|
||||||
|
t: string; // type
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TableInfoTiny {
|
||||||
|
n: string; //name
|
||||||
|
o: string; // comment
|
||||||
|
c: ColumnInfoTiny[]; // columns
|
||||||
|
p?: PrimaryKeyInfoTiny; // primary key
|
||||||
|
f?: ForeignKeyInfoTiny[]; // foreign keys
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DatabaseInfoTiny {
|
||||||
|
t: TableInfoTiny[]; // tables
|
||||||
|
}
|
||||||
|
|||||||
48
packages/web/public/bulma.css
vendored
48
packages/web/public/bulma.css
vendored
@@ -2,6 +2,30 @@
|
|||||||
margin: 0 !important;
|
margin: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.m-1 {
|
||||||
|
margin: 0.25rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-2 {
|
||||||
|
margin: 0.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-3 {
|
||||||
|
margin: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-4 {
|
||||||
|
margin: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-5 {
|
||||||
|
margin: 1.5rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.m-6 {
|
||||||
|
margin: 3rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
.mt-0 {
|
.mt-0 {
|
||||||
margin-top: 0 !important;
|
margin-top: 0 !important;
|
||||||
}
|
}
|
||||||
@@ -28,10 +52,6 @@
|
|||||||
margin-bottom: 0 !important;
|
margin-bottom: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-1 {
|
|
||||||
margin: 0.25rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-1 {
|
.mt-1 {
|
||||||
margin-top: 0.25rem !important;
|
margin-top: 0.25rem !important;
|
||||||
}
|
}
|
||||||
@@ -58,10 +78,6 @@
|
|||||||
margin-bottom: 0.25rem !important;
|
margin-bottom: 0.25rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-2 {
|
|
||||||
margin: 0.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-2 {
|
.mt-2 {
|
||||||
margin-top: 0.5rem !important;
|
margin-top: 0.5rem !important;
|
||||||
}
|
}
|
||||||
@@ -88,10 +104,6 @@
|
|||||||
margin-bottom: 0.5rem !important;
|
margin-bottom: 0.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-3 {
|
|
||||||
margin: 0.75rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-3 {
|
.mt-3 {
|
||||||
margin-top: 0.75rem !important;
|
margin-top: 0.75rem !important;
|
||||||
}
|
}
|
||||||
@@ -118,10 +130,6 @@
|
|||||||
margin-bottom: 0.75rem !important;
|
margin-bottom: 0.75rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-4 {
|
|
||||||
margin: 1rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-4 {
|
.mt-4 {
|
||||||
margin-top: 1rem !important;
|
margin-top: 1rem !important;
|
||||||
}
|
}
|
||||||
@@ -148,10 +156,6 @@
|
|||||||
margin-bottom: 1rem !important;
|
margin-bottom: 1rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-5 {
|
|
||||||
margin: 1.5rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-5 {
|
.mt-5 {
|
||||||
margin-top: 1.5rem !important;
|
margin-top: 1.5rem !important;
|
||||||
}
|
}
|
||||||
@@ -178,10 +182,6 @@
|
|||||||
margin-bottom: 1.5rem !important;
|
margin-bottom: 1.5rem !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.m-6 {
|
|
||||||
margin: 3rem !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-6 {
|
.mt-6 {
|
||||||
margin-top: 3rem !important;
|
margin-top: 3rem !important;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,4 +17,5 @@
|
|||||||
{menu}
|
{menu}
|
||||||
{hideDisabled}
|
{hideDisabled}
|
||||||
{buttonLabel}
|
{buttonLabel}
|
||||||
|
{...$$restProps}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<div class="button" class:disabled {title}>
|
<div class="button" class:disabled {title}>
|
||||||
<div class="inner" class:disabled>
|
<div class="inner" class:disabled>
|
||||||
<div class="main" class:disabled on:click={handleClick}>
|
<div class="main" class:disabled on:click={handleClick} data-testid={$$props['data-testid']}>
|
||||||
<span class="icon" class:disabled><FontIcon {icon} /></span>
|
<span class="icon" class:disabled><FontIcon {icon} /></span>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -19,6 +19,6 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<ToolStripSplitButton {title} {icon} {disabled} on:splitclick={handleClick} on:click>
|
<ToolStripSplitButton {title} {icon} {disabled} on:splitclick={handleClick} on:click {...$$restProps}>
|
||||||
<slot />
|
<slot />
|
||||||
</ToolStripSplitButton>
|
</ToolStripSplitButton>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
export let selection;
|
export let selection;
|
||||||
export let showWholeRow = false;
|
export let showWholeRow = false;
|
||||||
|
export let expandAll = false;
|
||||||
|
|
||||||
let json = null;
|
let json = null;
|
||||||
let error = null;
|
let error = null;
|
||||||
@@ -31,7 +32,7 @@
|
|||||||
{:else}
|
{:else}
|
||||||
<div class="outer">
|
<div class="outer">
|
||||||
<div class="inner">
|
<div class="inner">
|
||||||
<JSONTree value={json} expanded />
|
<JSONTree value={json} {expandAll} expanded />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|||||||
6
packages/web/src/celldata/JsonExpandedCellView.svelte
Normal file
6
packages/web/src/celldata/JsonExpandedCellView.svelte
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import JsonCellView from './JsonCellView.svelte';
|
||||||
|
export let selection;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<JsonCellView {selection} expandAll />
|
||||||
@@ -148,6 +148,7 @@
|
|||||||
'icon parent-filter-outline': 'mdi mdi-home-alert-outline',
|
'icon parent-filter-outline': 'mdi mdi-home-alert-outline',
|
||||||
'icon download': 'mdi mdi-download',
|
'icon download': 'mdi mdi-download',
|
||||||
'icon text': 'mdi mdi-text',
|
'icon text': 'mdi mdi-text',
|
||||||
|
'icon ai': 'mdi mdi-head-lightbulb',
|
||||||
|
|
||||||
'icon run': 'mdi mdi-play',
|
'icon run': 'mdi mdi-play',
|
||||||
'icon chevron-down': 'mdi mdi-chevron-down',
|
'icon chevron-down': 'mdi mdi-chevron-down',
|
||||||
|
|||||||
@@ -21,8 +21,14 @@
|
|||||||
closeCurrentModal();
|
closeCurrentModal();
|
||||||
onConfirm();
|
onConfirm();
|
||||||
}}
|
}}
|
||||||
|
data-testid="ConfirmModal_okButton"
|
||||||
|
/>
|
||||||
|
<FormStyledButton
|
||||||
|
type="button"
|
||||||
|
value="Close"
|
||||||
|
on:click={closeCurrentModal}
|
||||||
|
data-testid="ConfirmModal_closeButton"
|
||||||
/>
|
/>
|
||||||
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
|
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ModalBase>
|
</ModalBase>
|
||||||
</FormProvider>
|
</FormProvider>
|
||||||
|
|||||||
1
packages/web/src/query/QueryAiAssistant.svelte
Normal file
1
packages/web/src/query/QueryAiAssistant.svelte
Normal file
@@ -0,0 +1 @@
|
|||||||
|
AI Assistant
|
||||||
@@ -410,6 +410,14 @@ ORDER BY
|
|||||||
{ value: 'download', label: 'Check and download new versions' },
|
{ value: 'download', label: 'Check and download new versions' },
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{#if isProApp()}
|
||||||
|
<FormCheckboxField
|
||||||
|
name="ai.allowSendModels"
|
||||||
|
label="Allow to send DB models and query snippets to AI service"
|
||||||
|
defaultValue={false}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
|
|
||||||
<svelte:fragment slot="7">
|
<svelte:fragment slot="7">
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ export const currentEditorFontSize = getElectron()
|
|||||||
? writableSettingsValue(null, 'currentEditorFontSize')
|
? writableSettingsValue(null, 'currentEditorFontSize')
|
||||||
: writableWithStorage(null, 'currentEditorFontSize');
|
: writableWithStorage(null, 'currentEditorFontSize');
|
||||||
export const currentEditorFont = writableSettingsValue(null, 'editor.fontFamily');
|
export const currentEditorFont = writableSettingsValue(null, 'editor.fontFamily');
|
||||||
|
export const allowedSendToAiService = writableSettingsValue(false, 'ai.allowSendModels');
|
||||||
export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid);
|
export const activeTabId = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected)?.tabid);
|
||||||
export const activeTab = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected));
|
export const activeTab = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected));
|
||||||
export const recentDatabases = writableWithStorage([], 'recentDatabases');
|
export const recentDatabases = writableWithStorage([], 'recentDatabases');
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
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',
|
||||||
@@ -89,6 +98,9 @@
|
|||||||
import ToolStripDropDownButton from '../buttons/ToolStripDropDownButton.svelte';
|
import ToolStripDropDownButton from '../buttons/ToolStripDropDownButton.svelte';
|
||||||
import { extractQueryParameters, replaceQueryParameters } from 'dbgate-query-splitter';
|
import { extractQueryParameters, replaceQueryParameters } from 'dbgate-query-splitter';
|
||||||
import QueryParametersModal from '../modals/QueryParametersModal.svelte';
|
import QueryParametersModal from '../modals/QueryParametersModal.svelte';
|
||||||
|
import { isProApp } from '../utility/proTools';
|
||||||
|
import HorizontalSplitter from '../elements/HorizontalSplitter.svelte';
|
||||||
|
import QueryAiAssistant from '../query/QueryAiAssistant.svelte';
|
||||||
|
|
||||||
export let tabid;
|
export let tabid;
|
||||||
export let conid;
|
export let conid;
|
||||||
@@ -137,6 +149,7 @@
|
|||||||
let domEditor;
|
let domEditor;
|
||||||
let domToolStrip;
|
let domToolStrip;
|
||||||
let intervalId;
|
let intervalId;
|
||||||
|
let isAiAssistantVisible = localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
intervalId = setInterval(() => {
|
intervalId = setInterval(() => {
|
||||||
@@ -210,6 +223,10 @@
|
|||||||
visibleResultTabs = !visibleResultTabs;
|
visibleResultTabs = !visibleResultTabs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggleAiAssistant() {
|
||||||
|
isAiAssistantVisible = !isAiAssistantVisible;
|
||||||
|
}
|
||||||
|
|
||||||
function getParameterSplitterOptions() {
|
function getParameterSplitterOptions() {
|
||||||
if (!queryParameterStyle) {
|
if (!queryParameterStyle) {
|
||||||
return null;
|
return null;
|
||||||
@@ -401,6 +418,7 @@
|
|||||||
{ command: 'query.replace' },
|
{ command: 'query.replace' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ command: 'query.toggleVisibleResultTabs' },
|
{ command: 'query.toggleVisibleResultTabs' },
|
||||||
|
{ command: 'query.switchAiAssistant', hideDisabled: true },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -420,79 +438,116 @@
|
|||||||
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}>
|
||||||
<VerticalSplitter isSplitter={visibleResultTabs}>
|
<HorizontalSplitter isSplitter={isAiAssistantVisible} initialSizeRight={300}>
|
||||||
<svelte:fragment slot="1">
|
<svelte:fragment slot="1">
|
||||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
<VerticalSplitter isSplitter={visibleResultTabs}>
|
||||||
<SqlEditor
|
<svelte:fragment slot="1">
|
||||||
engine={$connection && $connection.engine}
|
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||||
{conid}
|
<SqlEditor
|
||||||
{database}
|
engine={$connection && $connection.engine}
|
||||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
{conid}
|
||||||
options={{
|
{database}
|
||||||
wrap: enableWrap,
|
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||||
}}
|
options={{
|
||||||
value={$editorState.value || ''}
|
wrap: enableWrap,
|
||||||
menu={createMenu()}
|
}}
|
||||||
on:input={e => {
|
value={$editorState.value || ''}
|
||||||
setEditorData(e.detail);
|
menu={createMenu()}
|
||||||
if (isInitialized) {
|
on:input={e => {
|
||||||
markTabUnsaved(tabid);
|
setEditorData(e.detail);
|
||||||
}
|
if (isInitialized) {
|
||||||
errorMessages = [];
|
markTabUnsaved(tabid);
|
||||||
}}
|
}
|
||||||
on:focus={() => {
|
errorMessages = [];
|
||||||
activator.activate();
|
}}
|
||||||
domToolStrip?.activate();
|
on:focus={() => {
|
||||||
invalidateCommands();
|
activator.activate();
|
||||||
setTimeout(() => {
|
domToolStrip?.activate();
|
||||||
isInitialized = true;
|
invalidateCommands();
|
||||||
}, 100);
|
setTimeout(() => {
|
||||||
}}
|
isInitialized = true;
|
||||||
bind:this={domEditor}
|
}, 100);
|
||||||
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
}}
|
||||||
{errorMessages}
|
bind:this={domEditor}
|
||||||
/>
|
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
||||||
{:else}
|
{errorMessages}
|
||||||
<AceEditor
|
/>
|
||||||
mode={driver?.editorMode || 'sql'}
|
{:else}
|
||||||
value={$editorState.value || ''}
|
<AceEditor
|
||||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
mode={driver?.editorMode || 'sql'}
|
||||||
options={{
|
value={$editorState.value || ''}
|
||||||
wrap: enableWrap,
|
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||||
}}
|
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 tabs={[{ label: 'Messages', slot: 0 }]} {sessionId} {executeNumber} bind:resultCount {driver}>
|
||||||
|
<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">
|
||||||
<ResultTabs tabs={[{ label: 'Messages', slot: 0 }]} {sessionId} {executeNumber} bind:resultCount {driver}>
|
<QueryAiAssistant
|
||||||
<svelte:fragment slot="0">
|
{conid}
|
||||||
<SocketMessageView
|
{database}
|
||||||
eventName={sessionId ? `session-info-${sessionId}` : null}
|
{driver}
|
||||||
onMessageClick={handleMesageClick}
|
onClose={() => {
|
||||||
{executeNumber}
|
isAiAssistantVisible = false;
|
||||||
startLine={executeStartLine}
|
}}
|
||||||
showProcedure
|
text={$editorValue}
|
||||||
showLine
|
getLine={() => domEditor.getEditor().getSelectionRange().start.row}
|
||||||
onChangeErrors={handleChangeErrors}
|
onInsertAtCursor={text => {
|
||||||
/>
|
const editor = domEditor.getEditor();
|
||||||
</svelte:fragment>
|
editor.session.insert(editor.getCursorPosition(), text);
|
||||||
</ResultTabs>
|
domEditor?.getEditor()?.focus();
|
||||||
|
}}
|
||||||
|
getTextOrSelectedText={() => domEditor.getEditor().getSelectedText() || $editorValue}
|
||||||
|
onSetSelectedText={text => {
|
||||||
|
const editor = domEditor.getEditor();
|
||||||
|
if (editor.getSelectedText()) {
|
||||||
|
const range = editor.selection.getRange();
|
||||||
|
editor.session.replace(range, text);
|
||||||
|
} else {
|
||||||
|
editor.setValue(text);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
{tabid}
|
||||||
|
/>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</VerticalSplitter>
|
</HorizontalSplitter>
|
||||||
<svelte:fragment slot="toolstrip">
|
<svelte:fragment slot="toolstrip">
|
||||||
<ToolStripCommandSplitButton commands={['query.execute', 'query.executeCurrent']} />
|
<ToolStripCommandSplitButton
|
||||||
<ToolStripCommandButton command="query.kill" />
|
commands={['query.execute', 'query.executeCurrent']}
|
||||||
|
data-testid="QueryTab_executeButton"
|
||||||
|
/>
|
||||||
|
<ToolStripCommandButton command="query.kill" data-testid="QueryTab_killButton" />
|
||||||
<ToolStripSaveButton idPrefix="query" />
|
<ToolStripSaveButton idPrefix="query" />
|
||||||
<ToolStripCommandButton command="query.formatCode" />
|
<ToolStripCommandButton command="query.formatCode" />
|
||||||
{#if resultCount == 1}
|
{#if resultCount == 1}
|
||||||
@@ -511,6 +566,13 @@
|
|||||||
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>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
||||||
</ToolStripContainer>
|
</ToolStripContainer>
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,12 @@
|
|||||||
component: JsonCellView,
|
component: JsonCellView,
|
||||||
single: true,
|
single: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
type: 'jsonExpanded',
|
||||||
|
title: 'Json - expanded',
|
||||||
|
component: JsonExpandedCellView,
|
||||||
|
single: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
type: 'jsonRow',
|
type: 'jsonRow',
|
||||||
title: 'Json - Row',
|
title: 'Json - Row',
|
||||||
@@ -84,6 +90,7 @@
|
|||||||
import SelectField from '../forms/SelectField.svelte';
|
import SelectField from '../forms/SelectField.svelte';
|
||||||
import { selectedCellsCallback } from '../stores';
|
import { selectedCellsCallback } from '../stores';
|
||||||
import WidgetTitle from './WidgetTitle.svelte';
|
import WidgetTitle from './WidgetTitle.svelte';
|
||||||
|
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
|
||||||
|
|
||||||
let selectedFormatType = 'autodetect';
|
let selectedFormatType = 'autodetect';
|
||||||
|
|
||||||
@@ -107,6 +114,7 @@
|
|||||||
isNative
|
isNative
|
||||||
value={selectedFormatType}
|
value={selectedFormatType}
|
||||||
on:change={e => (selectedFormatType = e.detail)}
|
on:change={e => (selectedFormatType = e.detail)}
|
||||||
|
data-testid="CellDataWidget_selectFormat"
|
||||||
options={[
|
options={[
|
||||||
{ value: 'autodetect', label: `Autodetect - ${autodetectFormat.title}` },
|
{ value: 'autodetect', label: `Autodetect - ${autodetectFormat.title}` },
|
||||||
...formats.map(fmt => ({ label: fmt.title, value: fmt.type })),
|
...formats.map(fmt => ({ label: fmt.title, value: fmt.type })),
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
export let collapsed = null;
|
export let collapsed = null;
|
||||||
|
|
||||||
export let storageName = null;
|
export let storageName = null;
|
||||||
|
export let onClose = null;
|
||||||
|
|
||||||
let size = 0;
|
let size = 0;
|
||||||
|
|
||||||
@@ -70,7 +71,8 @@
|
|||||||
<WidgetTitle
|
<WidgetTitle
|
||||||
clickable={collapsible}
|
clickable={collapsible}
|
||||||
on:click={collapsible ? () => (visible = !visible) : null}
|
on:click={collapsible ? () => (visible = !visible) : null}
|
||||||
data-testid={$$props['data-testid']}>{title}</WidgetTitle
|
data-testid={$$props['data-testid']}
|
||||||
|
{onClose}>{title}</WidgetTitle
|
||||||
>
|
>
|
||||||
|
|
||||||
{#if visible}
|
{#if visible}
|
||||||
|
|||||||
@@ -1,18 +1,35 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import FontIcon from '../icons/FontIcon.svelte';
|
||||||
|
|
||||||
export let clickable = false;
|
export let clickable = false;
|
||||||
|
export let onClose = null;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div on:click class:clickable {...$$restProps}>
|
<div on:click class:clickable {...$$restProps} class="wrapper">
|
||||||
<slot />
|
<slot />
|
||||||
|
{#if onClose}
|
||||||
|
<div class="close" on:click={onClose}>
|
||||||
|
<FontIcon icon="icon close" />
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
div {
|
.wrapper {
|
||||||
padding: 5px;
|
padding: 5px;
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
background-color: var(--theme-bg-1);
|
background-color: var(--theme-bg-1);
|
||||||
border: 2px solid var(--theme-border);
|
border: 2px solid var(--theme-border);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
.close {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.close:hover {
|
||||||
|
color: var(--theme-font-hover);
|
||||||
}
|
}
|
||||||
div.clickable:hover {
|
div.clickable:hover {
|
||||||
background-color: var(--theme-bg-2);
|
background-color: var(--theme-bg-2);
|
||||||
|
|||||||
@@ -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: a770b7e7a4d0ced5f1ade7cba4ba516220765648
|
ref: a2f824dc711b510a5e8235d3faf4aafab1965184
|
||||||
- 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