Merge branch 'master' into feature/postgresql-export-bytea

This commit is contained in:
Stela Augustinova
2025-11-13 13:44:11 +01:00
161 changed files with 5638 additions and 1627 deletions

View File

@@ -9,6 +9,8 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { commandsCustomized } from '../stores';
import { _t } from '../translations';
import _ from 'lodash';
export let tabs;
export let onConfirm;
@@ -27,10 +29,10 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Confirm close tabs</svelte:fragment>
<svelte:fragment slot="header">{_t('datagrid.closeTabs.header', { defaultMessage: 'Confirm close tabs' })}</svelte:fragment>
<div>
Following files are modified, really close tabs? After closing, you could reopen them in history
{_t('datagrid.closeTabs.modifiedFiles', { defaultMessage: 'Following files are modified, really close tabs? After closing, you could reopen them in history' })}
<FontIcon icon="icon history" />
widget
</div>
@@ -41,7 +43,7 @@
<svelte:fragment slot="footer">
<FormSubmit
value="Close tabs"
value={_t('datagrid.closeTabs.close', { defaultMessage: 'Close tabs' })}
on:click={() => {
closeCurrentModal();
onConfirm();
@@ -49,7 +51,7 @@
/>
<FormStyledButton
type="button"
value="Cancel"
value={_t('common.cancel', { defaultMessage: 'Cancel' })}
on:click={() => {
closeCurrentModal();
onCancel();

View File

@@ -11,6 +11,7 @@
import KeyboardModal from './KeyboardModal.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal, showModal } from './modalTools';
import { _t } from '../translations';
export let command;
@@ -23,16 +24,16 @@
<FormProviderCore {values}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Configure commmand</svelte:fragment>
<svelte:fragment slot="header">{_t('commandModal.configure', { defaultMessage: 'Configure command' })}</svelte:fragment>
<FormTextField label="Category" name="category" disabled />
<FormTextField label="Name" name="name" disabled />
<FormTextField label={_t('commandModal.category', { defaultMessage: 'Category' })} name="category" disabled />
<FormTextField label={_t('commandModal.name', { defaultMessage: 'Name' })} name="name" disabled />
<div class="row">
<FormTextField label="Keyboard shortcut" name="keyText" templateProps={{ noMargin: true }} focused />
<FormTextField label={_t('commandModal.keyboardShortcut', { defaultMessage: 'Keyboard shortcut' })} name="keyText" templateProps={{ noMargin: true }} focused />
<FormStyledButton
type="button"
value="Keyboard"
value={_t('commandModal.keyboard', { defaultMessage: 'Keyboard' })}
on:click={handleKeyboard}
data-testid="CommandModal_keyboardButton"
/>
@@ -56,7 +57,7 @@
/>
<FormStyledButton
type="button"
value="Reset"
value={_t('common.reset', { defaultMessage: 'Reset' })}
on:click={() => {
closeCurrentModal();
apiCall('config/update-settings', {
@@ -64,7 +65,7 @@
});
}}
/>
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProviderCore>

View File

@@ -13,6 +13,7 @@
import { parseCellValue, safeJsonParse, stringifyCellValue } from 'dbgate-tools';
import { showSnackbarError } from '../utility/snackbar';
import ErrorMessageModal from './ErrorMessageModal.svelte';
import { _t } from '../translations';
export let onSave;
export let value;
@@ -49,14 +50,14 @@
if (parsed) {
textValue = JSON.stringify(parsed, null, 2);
} else {
showModal(ErrorMessageModal, { message: 'Not valid JSON' });
showModal(ErrorMessageModal, { message: _t('dataGrid.formatJson.invalid', { defaultMessage: 'Not valid JSON' }) });
}
}
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<div slot="header">Edit cell value</div>
<div slot="header">{_t('dataGrid.editCellValue', { defaultMessage: 'Edit cell value' })}</div>
<div class="editor">
<AceEditor bind:value={textValue} bind:this={editor} onKeyDown={handleKeyDown} mode={syntaxMode} />
@@ -72,21 +73,21 @@
closeCurrentModal();
}}
/>
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</div>
<div>
<FormStyledButton type="button" value="Format JSON" on:click={handleFormatJson} />
<FormStyledButton type="button" skipWidth={true} value={_t('dataGrid.formatJson', { defaultMessage: 'Format JSON' })} on:click={handleFormatJson} />
Code highlighting:
{_t('dataGrid.codeHighlighting', { defaultMessage: 'Code highlighting:' })}
<SelectField
isNative
value={syntaxMode}
on:change={e => (syntaxMode = e.detail)}
options={[
{ value: 'text', label: 'None (raw text)' },
{ value: 'text', label: _t('dataGrid.codeHighlighting.none', { defaultMessage: 'None (raw text)' }) },
{ value: 'json', label: 'JSON' },
{ value: 'html', label: 'HTML' },
{ value: 'html', label: 'HTML'},
{ value: 'xml', label: 'XML' },
]}
/>

View File

@@ -6,8 +6,9 @@
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let title = 'Error';
export let title = _t('common.error', { defaultMessage: 'Error' });
export let message;
export let showAsCode = false;
</script>
@@ -30,7 +31,7 @@
{/if}
<div slot="footer">
<FormSubmit value="Close" on:click={closeCurrentModal} data-testid="ErrorMessageModal_closeButton" />
<FormSubmit value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} data-testid="ErrorMessageModal_closeButton" />
</div>
</ModalBase>
</FormProvider>

View File

@@ -6,6 +6,7 @@
import FormTextField from '../forms/FormTextField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let header;
export let value;
@@ -29,7 +30,7 @@
<svelte:fragment slot="footer">
<FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} data-testid="InputTextModal_ok" />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} data-testid="InputTextModal_cancel" />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} data-testid="InputTextModal_cancel" />
</svelte:fragment>
</ModalBase>
</FormProvider>

View File

@@ -5,6 +5,7 @@
import keycodes from '../utility/keycodes';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let onChange;
let value;
@@ -38,7 +39,7 @@
</script>
<ModalBase {...$$restProps} simple>
<div class="mb-2">Show desired key combination and press ENTER</div>
<div class="mb-2">_{_t('commandModal.showKeyCombination', { defaultMessage: 'Show desired key combination and press ENTER' })}</div>
<div class="largeFormMarker">
<TextField on:keydown={handleKeyDown} bind:value focused />
</div>

View File

@@ -10,6 +10,7 @@
import ErrorMessageModal from './ErrorMessageModal.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal, showModal } from './modalTools';
import { _t } from '../translations';
export let driver;
export let dbid;
@@ -44,14 +45,14 @@
<FormProvider initialValues={{ name: '' }}>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">
Create {driver?.collectionSingularLabel ?? 'collection/container'}
{_t('dbObject.createCollection', { defaultMessage: 'Create collection/container'})}
</svelte:fragment>
<FormArgumentList args={driver?.newCollectionFormParams} />
<svelte:fragment slot="footer">
<FormSubmit value="OK" on:click={e => handleSubmit(e.detail)} disabled={isSaving} />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.cancel', { defaultMessage: 'Cancel' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>

View File

@@ -7,6 +7,7 @@
import { isProApp } from '../utility/proTools';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let multiTabIndex = undefined;
@@ -14,8 +15,8 @@
{
icon: 'icon sql-file',
colorClass: 'color-icon-blue',
title: 'Query',
description: 'SQL query editor',
title: _t('common.query', { defaultMessage: 'Query' }),
description: _t('common.queryEditor', { defaultMessage: 'SQL query editor' }),
action: () => {
newQuery({ multiTabIndex });
},
@@ -25,47 +26,47 @@
{
icon: 'icon connection',
colorClass: 'color-icon-yellow',
title: 'Connection',
description: 'Database connection stored locally',
title: _t('common.connection', { defaultMessage: 'Connection' }),
description: _t('newObject.connectionLocal', { defaultMessage: 'Database connection stored locally' }),
command: 'new.connection',
changeWidget: 'database',
testid: 'NewObjectModal_connection',
disabledMessage: 'You are not allowed to create new connections',
disabledMessage: _t('newObject.connectionLocalDisabled', { defaultMessage: 'You are not allowed to create new connections' }),
},
{
icon: 'icon cloud-connection',
colorClass: 'color-icon-blue',
title: 'Connection on Cloud',
description: 'Database connection stored on DbGate Cloud',
title: _t('common.connectionOnCloud', { defaultMessage: 'Connection on Cloud' }),
description: _t('newObject.connectionOnCloudDescription', { defaultMessage: 'Database connection stored on DbGate Cloud' }),
command: 'new.connectionOnCloud',
changeWidget: 'cloud-private',
testid: 'NewObjectModal_connectionOnCloud',
disabledMessage: 'For creating connections on DbGate Cloud, you need to be logged in',
disabledMessage: _t('newObject.connectionOnCloudDisabled', { defaultMessage: 'For creating connections on DbGate Cloud, you need to be logged in' }),
},
{
icon: 'icon query-design',
colorClass: 'color-icon-red',
title: 'Query Designer',
description: 'Design SQL queries visually',
title: _t('common.queryDesigner', { defaultMessage: 'Query Designer' }),
description: _t('newObject.queryDesignerDescription', { defaultMessage: 'Design SQL queries visually' }),
command: 'new.queryDesign',
testid: 'NewObjectModal_queryDesign',
disabledMessage: 'Query Designer is not available for current database',
disabledMessage: _t('newObject.queryDesignerDisabled', { defaultMessage: 'Query Designer is not available for current database' }),
isProFeature: true,
},
{
icon: 'icon diagram',
colorClass: 'color-icon-blue',
title: 'ER Diagram',
description: 'Visualize database structure',
title: _t('common.erDiagram', { defaultMessage: 'ER Diagram' }),
description: _t('newObject.erDiagramDescription', { defaultMessage: 'Visualize database structure' }),
command: 'new.diagram',
testid: 'NewObjectModal_diagram',
disabledMessage: 'ER Diagram is not available for current database',
disabledMessage: _t('newObject.erDiagramDisabled', { defaultMessage: 'ER Diagram is not available for current database' }),
},
{
icon: 'icon perspective',
colorClass: 'color-icon-yellow',
title: 'Perspective',
description: 'Join complex data from multiple databases',
title: _t('common.perspective', { defaultMessage: 'Perspective' }),
description: _t('newObject.perspectiveDescription', { defaultMessage: 'Join complex data from multiple databases' }),
command: 'new.perspective',
testid: 'NewObjectModal_perspective',
isProFeature: true,
@@ -73,55 +74,56 @@
{
icon: 'icon table',
colorClass: 'color-icon-blue',
title: 'Table',
description: 'Create table in the current database',
title: _t('common.table', { defaultMessage: 'Table' }),
description: _t('newObject.tableDescription', { defaultMessage: 'Create table in the current database' }),
command: 'new.table',
testid: 'NewObjectModal_table',
disabledMessage: 'Table creation is not available for current database',
disabledMessage: _t('newObject.tableDisabled', { defaultMessage: 'Table creation is not available for current database' }),
},
{
icon: 'icon sql-generator',
colorClass: 'color-icon-green',
title: 'SQL Generator',
description: 'Generate SQL scripts for database objects',
title: _t('common.sqlGenerator', { defaultMessage: 'SQL Generator' }),
description: _t('newObject.sqlGeneratorDescription', { defaultMessage: 'Generate SQL scripts for database objects' }),
command: 'sql.generator',
testid: 'NewObjectModal_sqlGenerator',
disabledMessage: 'SQL Generator is not available for current database',
disabledMessage: _t('newObject.sqlGeneratorDisabled', { defaultMessage: 'SQL Generator is not available for current database' }),
},
{
icon: 'icon export',
colorClass: 'color-icon-green',
title: 'Export database',
description: 'Export to file like CSV, JSON, Excel, or other DB',
title: _t('common.exportDatabase', { defaultMessage: 'Export database' }),
description: _t('newObject.exportDescription', { defaultMessage: 'Export to file like CSV, JSON, Excel, or other DB' }),
command: 'database.export',
isProFeature: true,
testid: 'NewObjectModal_databaseExport',
disabledMessage: 'Export is not available for current database',
disabledMessage: _t('newObject.exportDisabled', { defaultMessage: 'Export is not available for current database' }),
},
{
icon: 'icon compare',
colorClass: 'color-icon-red',
title: 'Compare database',
description: 'Compare database schemas',
title: _t('common.compare', { defaultMessage: 'Compare database' }),
description: _t('newObject.compareDescription', { defaultMessage: 'Compare database schemas' }),
command: 'database.compare',
testid: 'NewObjectModal_databaseCompare',
disabledMessage: 'Database comparison is not available for current database',
disabledMessage: _t('newObject.compareDisabled', { defaultMessage: 'Database comparison is not available for current database' }),
isProFeature: true,
},
{
icon: 'icon ai',
colorClass: 'color-icon-blue',
title: 'Database Chat',
description: 'Chat with your database using AI',
title: _t('common.databaseChat', { defaultMessage: 'Database Chat' }),
description: _t('newObject.databaseChatDescription', { defaultMessage: 'Chat with your database using AI' }),
command: 'database.chat',
isProFeature: true,
disabledMessage: 'Database chat is not available for current database',
disabledMessage: _t('newObject.databaseChatDisabled', { defaultMessage: 'Database chat is not available for current database' }),
testid: 'NewObjectModal_databaseChat',
},
];
</script>
<ModalBase simplefix {...$$restProps}>
<div class="create-header">Create new</div>
<div class="create-header">{_t('common.createNew', { defaultMessage: 'Create new' })}</div>
<div class="wrapper">
{#each NEW_ITEMS as item}
{@const enabled = item.command

View File

@@ -10,6 +10,7 @@
import { closeCurrentModal } from './modalTools';
import FormRadioGroupItem from '../forms/FormRadioGroupItem.svelte';
import FormValues from '../forms/FormValues.svelte';
import { _t } from '../translations';
export let condition1;
export let onFilter;
@@ -44,10 +45,10 @@
<FormProvider initialValues={{ condition1, condition2: '=', joinOperator: ' ' }} template={FormFieldTemplateLarge}>
<ModalBase {...$$restProps}>
<div slot="header">Set filter</div>
<div slot="header">{_t('filter.setFilter', {defaultMessage: 'Set filter'})}</div>
<div class="largeFormMarker">
<div class="row">Show rows where</div>
<div class="row">{_t('filter.showRowsWhere', {defaultMessage: 'Show rows where'})}</div>
<div class="row">
<div class="col-6 mr-1">
<SetFilterModal_Select {filterBehaviour} name="condition1" />
@@ -62,9 +63,9 @@
</div>
<div class="row">
<FormRadioGroupItem name="joinOperator" value=" " text="And" />
<FormRadioGroupItem name="joinOperator" value=" " text={_t('filter.and', {defaultMessage: 'And'})} />
{#if !filterBehaviour.disableOr}
<FormRadioGroupItem name="joinOperator" value="," text="Or" />
<FormRadioGroupItem name="joinOperator" value="," text={_t('filter.or', {defaultMessage: 'Or'})} />
{/if}
</div>
@@ -84,7 +85,7 @@
<div slot="footer">
<FormSubmit value="OK" on:click={handleOk} />
<FormButton type="button" value="Close" on:click={closeCurrentModal} />
<FormButton type="button" value={_t('common.close', {defaultMessage: 'Close'})} on:click={closeCurrentModal} />
</div>
</ModalBase>
</FormProvider>

View File

@@ -1,56 +1,57 @@
<script lang="ts">
import _ from 'lodash';
import FormSelectFieldRaw from '../forms/FormSelectFieldRaw.svelte';
import { _t } from '../translations';
export let name;
export let filterBehaviour;
function getOptions() {
const res = [];
if (filterBehaviour.supportEquals) {
res.push({ value: '=', label: 'equals' }, { value: '<>', label: 'does not equal' });
}
res.push({ value: '=', label: _t('filter.modal.equals', {defaultMessage: 'equals'}) }, { value: '<>', label: _t('filter.modal.doesNotEqual', {defaultMessage: 'does not equal'}) });
};
if (filterBehaviour.supportStringInclusion) {
res.push(
{ value: '+', label: 'contains' },
{ value: '~', label: 'does not contain' },
{ value: '^', label: 'begins with' },
{ value: '!^', label: 'does not begin with' },
{ value: '$', label: 'ends with' },
{ value: '!$', label: 'does not end with' }
{ value: '+', label: _t('filter.modal.contains', {defaultMessage: 'contains'}) },
{ value: '~', label: _t('filter.modal.doesNotContain', {defaultMessage: 'does not contain'}) },
{ value: '^', label: _t('filter.modal.beginsWith', {defaultMessage: 'begins with'}) },
{ value: '!^', label: _t('filter.modal.doesNotBeginWith', {defaultMessage: 'does not begin with'}) },
{ value: '$', label: _t('filter.modal.endsWith', {defaultMessage: 'ends with'}) },
{ value: '!$', label: _t('filter.modal.doesNotEndWith', {defaultMessage: 'does not end with'}) }
);
}
if (filterBehaviour.supportNumberLikeComparison) {
res.push(
{ value: '<', label: 'is smaller' },
{ value: '>', label: 'is greater' },
{ value: '<=', label: 'is smaller or equal' },
{ value: '>=', label: 'is greater or equal' }
{ value: '<', label: _t('filter.isSmaller', {defaultMessage: 'is smaller'}) },
{ value: '>', label: _t('filter.isGreater', {defaultMessage: 'is greater'}) },
{ value: '<=', label: _t('filter.isSmallerOrEqual', {defaultMessage: 'is smaller or equal'}) },
{ value: '>=', label: _t('filter.isGreaterOrEqual', {defaultMessage: 'is greater or equal'}) }
);
}
if (filterBehaviour.supportDatetimeComparison) {
res.push(
{ value: '<', label: 'is before' },
{ value: '>', label: 'is after' },
{ value: '<=', label: 'is before or equal' },
{ value: '>=', label: 'is after or equal' }
{ value: '<', label: _t('filter.isBefore', {defaultMessage: 'is before'}) },
{ value: '>', label: _t('filter.isAfter', {defaultMessage: 'is after'}) },
{ value: '<=', label: _t('filter.isBeforeOrEqual', {defaultMessage: 'is before or equal'}) },
{ value: '>=', label: _t('filter.isAfterOrEqual', {defaultMessage: 'is after or equal'}) }
);
}
if (filterBehaviour.supportNullTesting) {
res.push({ value: 'NULL', label: 'is NULL' }, { value: 'NOT NULL', label: 'is not NULL' });
res.push({ value: 'NULL', label: _t('filter.modal.isNull', {defaultMessage: 'is NULL'}) }, { value: 'NOT NULL', label: _t('filter.modal.isNotNull', {defaultMessage: 'is not NULL'}) });
}
if (filterBehaviour.supportExistsTesting) {
res.push({ value: 'EXISTS', label: 'field exists' }, { value: 'NOT EXISTS', label: 'field does not exist' });
res.push({ value: 'EXISTS', label: _t('filter.modal.fieldExists', {defaultMessage: 'field exists'}) }, { value: 'NOT EXISTS', label: _t('filter.modal.fieldDoesNotExist', {defaultMessage: 'field does not exist'}) });
}
if (filterBehaviour.supportSqlCondition) {
res.push(
{ value: 'sql', label: 'SQL condition' },
{ value: 'sqlRight', label: 'SQL condition - right side only' }
{ value: 'sql', label: _t('filter.modal.sqlCondition', {defaultMessage: 'SQL condition'}) },
{ value: 'sqlRight', label: _t('filter.modal.sqlConditionRight', {defaultMessage: 'SQL condition - right side only'}) }
);
}

View File

@@ -0,0 +1,59 @@
<script lang='ts'>
import _ from 'lodash';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import newQuery from '../query/newQuery';
import SqlEditor from '../query/SqlEditor.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
import { _t } from '../translations';
export let sql;
export let onConfirm;
export let engine = null;
</script>
<ModalBase {...$$restProps}>
<div slot="header">SQL Script</div>
<div class="editor">
<SqlEditor {engine} value={sql} readOnly />
</div>
<div slot="footer">
<FormStyledButton
type="button"
value={_t('common.close', { defaultMessage: 'Close' })}
on:click={closeCurrentModal}
data-testid="ShowSqlModal_closeButton"
/>
<FormStyledButton
type="button"
value="Open script"
on:click={() => {
newQuery({
initialData: sql,
});
closeCurrentModal();
}}
data-testid="ShowSqlModal_openScriptButton"
/>
</div>
</ModalBase>
<style>
.editor {
position: relative;
height: 30vh;
width: 40vw;
}
.form-margin {
margin: var(--dim-large-form-margin);
}
.flex-wrap {
flex-wrap: wrap;
}
</style>

View File

@@ -16,6 +16,7 @@
import { apiCall } from '../utility/api';
import ErrorInfo from '../elements/ErrorInfo.svelte';
import { base64ToHex } from 'dbgate-tools';
import { _t } from '../translations';
export let onConfirm;
export let conid;
@@ -74,15 +75,15 @@
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Choose value from {field}</svelte:fragment>
<svelte:fragment slot="header">{_t('dataGrid.chooseValue', { defaultMessage: 'Choose value from {field}', values: { field } })}</svelte:fragment>
<!-- <FormTextField name="search" label='Search' placeholder="Search" bind:value={search} /> -->
<div class="largeFormMarker">
<SearchInput placeholder="Search" bind:value={search} isDebounced />
<SearchInput placeholder={_t('common.search', { defaultMessage: 'Search' })} bind:value={search} isDebounced />
</div>
{#if isLoading}
<LoadingInfo message="Loading data" />
<LoadingInfo message={_t('common.loadingData', { defaultMessage: 'Loading data' })} />
{/if}
{#if !isLoading && rows}
@@ -112,7 +113,7 @@
},
{
fieldName: 'value',
header: 'Value',
header: _t('dataGrid.value', { defaultMessage: 'Value' }),
formatter: row => (row.value == null ? '(NULL)' : row.value?.$binary?.base64 ? base64ToHex(row.value.$binary.base64) : row.value),
},
]}
@@ -147,7 +148,7 @@
}}
/>
{/if}
<FormStyledButton type="button" value="Close" on:click={closeCurrentModal} />
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>