mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 22:26:01 +00:00
Merge branch 'master' into feature/cassandra
This commit is contained in:
@@ -17,4 +17,5 @@
|
||||
{menu}
|
||||
{hideDisabled}
|
||||
{buttonLabel}
|
||||
{...$$restProps}
|
||||
/>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
|
||||
<div class="button" class:disabled {title}>
|
||||
<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>
|
||||
<slot />
|
||||
</div>
|
||||
|
||||
@@ -19,6 +19,6 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<ToolStripSplitButton {title} {icon} {disabled} on:splitclick={handleClick} on:click>
|
||||
<ToolStripSplitButton {title} {icon} {disabled} on:splitclick={handleClick} on:click {...$$restProps}>
|
||||
<slot />
|
||||
</ToolStripSplitButton>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
export let selection;
|
||||
export let showWholeRow = false;
|
||||
export let expandAll = false;
|
||||
|
||||
let json = null;
|
||||
let error = null;
|
||||
@@ -31,7 +32,7 @@
|
||||
{:else}
|
||||
<div class="outer">
|
||||
<div class="inner">
|
||||
<JSONTree value={json} expanded />
|
||||
<JSONTree value={json} {expandAll} expanded />
|
||||
</div>
|
||||
</div>
|
||||
{/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 download': 'mdi mdi-download',
|
||||
'icon text': 'mdi mdi-text',
|
||||
'icon ai': 'mdi mdi-head-lightbulb',
|
||||
|
||||
'icon run': 'mdi mdi-play',
|
||||
'icon chevron-down': 'mdi mdi-chevron-down',
|
||||
|
||||
@@ -21,8 +21,14 @@
|
||||
closeCurrentModal();
|
||||
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>
|
||||
</ModalBase>
|
||||
</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' },
|
||||
]}
|
||||
/>
|
||||
|
||||
{#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 slot="7">
|
||||
|
||||
@@ -115,6 +115,7 @@ export const currentEditorFontSize = getElectron()
|
||||
? writableSettingsValue(null, 'currentEditorFontSize')
|
||||
: writableWithStorage(null, 'currentEditorFontSize');
|
||||
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 activeTab = derived([openedTabs], ([$openedTabs]) => $openedTabs.find(x => x.selected));
|
||||
export const recentDatabases = writableWithStorage([], 'recentDatabases');
|
||||
|
||||
@@ -12,6 +12,15 @@
|
||||
testEnabled: () => getCurrentEditor()?.isSqlEditor(),
|
||||
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({
|
||||
id: 'query.insertSqlJoin',
|
||||
category: 'Query',
|
||||
@@ -89,6 +98,9 @@
|
||||
import ToolStripDropDownButton from '../buttons/ToolStripDropDownButton.svelte';
|
||||
import { extractQueryParameters, replaceQueryParameters } from 'dbgate-query-splitter';
|
||||
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 conid;
|
||||
@@ -137,6 +149,7 @@
|
||||
let domEditor;
|
||||
let domToolStrip;
|
||||
let intervalId;
|
||||
let isAiAssistantVisible = localStorage.getItem(`tabdata_isAiAssistantVisible_${tabid}`) == 'true';
|
||||
|
||||
onMount(() => {
|
||||
intervalId = setInterval(() => {
|
||||
@@ -210,6 +223,10 @@
|
||||
visibleResultTabs = !visibleResultTabs;
|
||||
}
|
||||
|
||||
export function toggleAiAssistant() {
|
||||
isAiAssistantVisible = !isAiAssistantVisible;
|
||||
}
|
||||
|
||||
function getParameterSplitterOptions() {
|
||||
if (!queryParameterStyle) {
|
||||
return null;
|
||||
@@ -401,6 +418,7 @@
|
||||
{ command: 'query.replace' },
|
||||
{ divider: true },
|
||||
{ command: 'query.toggleVisibleResultTabs' },
|
||||
{ command: 'query.switchAiAssistant', hideDisabled: true },
|
||||
];
|
||||
}
|
||||
|
||||
@@ -420,79 +438,116 @@
|
||||
localStorage.getItem(`tabdata_queryParamStyle_${tabid}`) ??
|
||||
initialArgs?.queryParameterStyle ??
|
||||
(initialArgs?.scriptTemplate == 'CALL OBJECT' ? ':' : '');
|
||||
|
||||
$: localStorage.setItem(`tabdata_isAiAssistantVisible_${tabid}`, isAiAssistantVisible ? 'true' : 'false');
|
||||
</script>
|
||||
|
||||
<ToolStripContainer bind:this={domToolStrip}>
|
||||
<VerticalSplitter isSplitter={visibleResultTabs}>
|
||||
<HorizontalSplitter isSplitter={isAiAssistantVisible} initialSizeRight={300}>
|
||||
<svelte:fragment slot="1">
|
||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||
<SqlEditor
|
||||
engine={$connection && $connection.engine}
|
||||
{conid}
|
||||
{database}
|
||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||
options={{
|
||||
wrap: enableWrap,
|
||||
}}
|
||||
value={$editorState.value || ''}
|
||||
menu={createMenu()}
|
||||
on:input={e => {
|
||||
setEditorData(e.detail);
|
||||
if (isInitialized) {
|
||||
markTabUnsaved(tabid);
|
||||
}
|
||||
errorMessages = [];
|
||||
}}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
setTimeout(() => {
|
||||
isInitialized = true;
|
||||
}, 100);
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
||||
{errorMessages}
|
||||
/>
|
||||
{:else}
|
||||
<AceEditor
|
||||
mode={driver?.editorMode || 'sql'}
|
||||
value={$editorState.value || ''}
|
||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||
options={{
|
||||
wrap: enableWrap,
|
||||
}}
|
||||
menu={createMenu()}
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
/>
|
||||
{/if}
|
||||
<VerticalSplitter isSplitter={visibleResultTabs}>
|
||||
<svelte:fragment slot="1">
|
||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||
<SqlEditor
|
||||
engine={$connection && $connection.engine}
|
||||
{conid}
|
||||
{database}
|
||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||
options={{
|
||||
wrap: enableWrap,
|
||||
}}
|
||||
value={$editorState.value || ''}
|
||||
menu={createMenu()}
|
||||
on:input={e => {
|
||||
setEditorData(e.detail);
|
||||
if (isInitialized) {
|
||||
markTabUnsaved(tabid);
|
||||
}
|
||||
errorMessages = [];
|
||||
}}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
setTimeout(() => {
|
||||
isInitialized = true;
|
||||
}, 100);
|
||||
}}
|
||||
bind:this={domEditor}
|
||||
onExecuteFragment={(sql, startLine) => executeCore(sql, startLine)}
|
||||
{errorMessages}
|
||||
/>
|
||||
{:else}
|
||||
<AceEditor
|
||||
mode={driver?.editorMode || 'sql'}
|
||||
value={$editorState.value || ''}
|
||||
splitterOptions={driver?.getQuerySplitterOptions('editor')}
|
||||
options={{
|
||||
wrap: enableWrap,
|
||||
}}
|
||||
menu={createMenu()}
|
||||
on:input={e => setEditorData(e.detail)}
|
||||
on:focus={() => {
|
||||
activator.activate();
|
||||
domToolStrip?.activate();
|
||||
invalidateCommands();
|
||||
}}
|
||||
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 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>
|
||||
<QueryAiAssistant
|
||||
{conid}
|
||||
{database}
|
||||
{driver}
|
||||
onClose={() => {
|
||||
isAiAssistantVisible = false;
|
||||
}}
|
||||
text={$editorValue}
|
||||
getLine={() => domEditor.getEditor().getSelectionRange().start.row}
|
||||
onInsertAtCursor={text => {
|
||||
const editor = domEditor.getEditor();
|
||||
editor.session.insert(editor.getCursorPosition(), text);
|
||||
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>
|
||||
</VerticalSplitter>
|
||||
</HorizontalSplitter>
|
||||
<svelte:fragment slot="toolstrip">
|
||||
<ToolStripCommandSplitButton commands={['query.execute', 'query.executeCurrent']} />
|
||||
<ToolStripCommandButton command="query.kill" />
|
||||
<ToolStripCommandSplitButton
|
||||
commands={['query.execute', 'query.executeCurrent']}
|
||||
data-testid="QueryTab_executeButton"
|
||||
/>
|
||||
<ToolStripCommandButton command="query.kill" data-testid="QueryTab_killButton" />
|
||||
<ToolStripSaveButton idPrefix="query" />
|
||||
<ToolStripCommandButton command="query.formatCode" />
|
||||
{#if resultCount == 1}
|
||||
@@ -511,6 +566,13 @@
|
||||
icon="icon at"
|
||||
title="Query parameter style"
|
||||
/>
|
||||
<ToolStripCommandButton
|
||||
command="query.switchAiAssistant"
|
||||
hideDisabled
|
||||
data-testid="QueryTab_switchAiAssistantButton"
|
||||
>
|
||||
AI Assistant
|
||||
</ToolStripCommandButton>
|
||||
</svelte:fragment>
|
||||
</ToolStripContainer>
|
||||
|
||||
|
||||
@@ -20,6 +20,12 @@
|
||||
component: JsonCellView,
|
||||
single: true,
|
||||
},
|
||||
{
|
||||
type: 'jsonExpanded',
|
||||
title: 'Json - expanded',
|
||||
component: JsonExpandedCellView,
|
||||
single: true,
|
||||
},
|
||||
{
|
||||
type: 'jsonRow',
|
||||
title: 'Json - Row',
|
||||
@@ -84,6 +90,7 @@
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import { selectedCellsCallback } from '../stores';
|
||||
import WidgetTitle from './WidgetTitle.svelte';
|
||||
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
|
||||
|
||||
let selectedFormatType = 'autodetect';
|
||||
|
||||
@@ -107,6 +114,7 @@
|
||||
isNative
|
||||
value={selectedFormatType}
|
||||
on:change={e => (selectedFormatType = e.detail)}
|
||||
data-testid="CellDataWidget_selectFormat"
|
||||
options={[
|
||||
{ value: 'autodetect', label: `Autodetect - ${autodetectFormat.title}` },
|
||||
...formats.map(fmt => ({ label: fmt.title, value: fmt.type })),
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
export let collapsed = null;
|
||||
|
||||
export let storageName = null;
|
||||
export let onClose = null;
|
||||
|
||||
let size = 0;
|
||||
|
||||
@@ -70,7 +71,8 @@
|
||||
<WidgetTitle
|
||||
clickable={collapsible}
|
||||
on:click={collapsible ? () => (visible = !visible) : null}
|
||||
data-testid={$$props['data-testid']}>{title}</WidgetTitle
|
||||
data-testid={$$props['data-testid']}
|
||||
{onClose}>{title}</WidgetTitle
|
||||
>
|
||||
|
||||
{#if visible}
|
||||
|
||||
@@ -1,18 +1,35 @@
|
||||
<script lang="ts">
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
|
||||
export let clickable = false;
|
||||
export let onClose = null;
|
||||
</script>
|
||||
|
||||
<div on:click class:clickable {...$$restProps}>
|
||||
<div on:click class:clickable {...$$restProps} class="wrapper">
|
||||
<slot />
|
||||
{#if onClose}
|
||||
<div class="close" on:click={onClose}>
|
||||
<FontIcon icon="icon close" />
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div {
|
||||
.wrapper {
|
||||
padding: 5px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
background-color: var(--theme-bg-1);
|
||||
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 {
|
||||
background-color: var(--theme-bg-2);
|
||||
|
||||
Reference in New Issue
Block a user