mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 23:45:59 +00:00
Merge branch 'master' of https://github.com/dbgate/dbgate
This commit is contained in:
@@ -71,6 +71,7 @@ module.exports = {
|
||||
const isLicenseValid = checkedLicense?.status == 'ok';
|
||||
const logoutUrl = storageConnectionError ? null : await authProvider.getLogoutUrl();
|
||||
const adminConfig = storageConnectionError ? null : await storage.readConfig({ group: 'admin' });
|
||||
const settingsConfig = storageConnectionError ? null : await storage.readConfig({ group: 'settings' });
|
||||
|
||||
storage.startRefreshLicense();
|
||||
|
||||
@@ -121,6 +122,7 @@ module.exports = {
|
||||
allowPrivateCloud: platformInfo.isElectron || !!process.env.ALLOW_DBGATE_PRIVATE_CLOUD,
|
||||
...currentVersion,
|
||||
redirectToDbGateCloudLogin: !!process.env.REDIRECT_TO_DBGATE_CLOUD_LOGIN,
|
||||
preferrendLanguage: settingsConfig?.['storage.language'] || process.env.LANGUAGE || null,
|
||||
};
|
||||
|
||||
return configResult;
|
||||
|
||||
@@ -1533,6 +1533,12 @@ module.exports = {
|
||||
"columnName": "name",
|
||||
"dataType": "varchar(250)",
|
||||
"notNull": true
|
||||
},
|
||||
{
|
||||
"pureName": "team_file_types",
|
||||
"columnName": "format",
|
||||
"dataType": "varchar(50)",
|
||||
"notNull": false
|
||||
}
|
||||
],
|
||||
"foreignKeys": [],
|
||||
@@ -1549,7 +1555,38 @@ module.exports = {
|
||||
"preloadedRows": [
|
||||
{
|
||||
"id": -1,
|
||||
"name": "sql"
|
||||
"name": "sql",
|
||||
"format": "text"
|
||||
},
|
||||
{
|
||||
"id": -2,
|
||||
"name": "diagrams",
|
||||
"format": "json"
|
||||
},
|
||||
{
|
||||
"id": -3,
|
||||
"name": "query",
|
||||
"format": "json"
|
||||
},
|
||||
{
|
||||
"id": -4,
|
||||
"name": "perspectives",
|
||||
"format": "json"
|
||||
},
|
||||
{
|
||||
"id": -5,
|
||||
"name": "impexp",
|
||||
"format": "json"
|
||||
},
|
||||
{
|
||||
"id": -6,
|
||||
"name": "shell",
|
||||
"format": "text"
|
||||
},
|
||||
{
|
||||
"id": -7,
|
||||
"name": "dbcompare",
|
||||
"format": "json"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -2,4 +2,5 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['js'],
|
||||
reporters: ['default', 'github-actions'],
|
||||
};
|
||||
|
||||
@@ -35,6 +35,12 @@ program
|
||||
.option('-u, --user <user>', 'user name')
|
||||
.option('-p, --password <password>', 'password')
|
||||
.option('-d, --database <database>', 'database name')
|
||||
.option('--url <url>', 'database url')
|
||||
.option('--file <file>', 'database file')
|
||||
.option('--socket-path <socketPath>', 'socket path')
|
||||
.option('--service-name <serviceName>', 'service name (for Oracle)')
|
||||
.option('--auth-type <authType>', 'authentication type')
|
||||
.option('--use-ssl', 'use SSL connection')
|
||||
.option('--auto-index-foreign-keys', 'automatically adds indexes to all foreign keys')
|
||||
.option(
|
||||
'--load-data-condition <condition>',
|
||||
@@ -48,7 +54,7 @@ program
|
||||
.command('deploy <modelFolder>')
|
||||
.description('Deploys model to database')
|
||||
.action(modelFolder => {
|
||||
const { engine, server, user, password, database, transaction } = program.opts();
|
||||
const { engine, server, user, password, database, url, file, transaction } = program.opts();
|
||||
// const hooks = [];
|
||||
// if (program.autoIndexForeignKeys) hooks.push(dbmodel.hooks.autoIndexForeignKeys);
|
||||
|
||||
@@ -60,6 +66,13 @@ program
|
||||
user,
|
||||
password,
|
||||
database,
|
||||
databaseUrl: url,
|
||||
useDatabaseUrl: !!url,
|
||||
databaseFile: file,
|
||||
socketPath: program.socketPath,
|
||||
serviceName: program.serviceName,
|
||||
authType: program.authType,
|
||||
useSsl: program.useSsl,
|
||||
},
|
||||
modelFolder,
|
||||
useTransaction: transaction,
|
||||
|
||||
@@ -2,4 +2,5 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['js'],
|
||||
reporters: ['default', 'github-actions'],
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { SqlDumper } from 'dbgate-types';
|
||||
import { Command, Select, Update, Delete, Insert } from './types';
|
||||
import { dumpSqlExpression } from './dumpSqlExpression';
|
||||
import { dumpSqlFromDefinition, dumpSqlSourceRef } from './dumpSqlSource';
|
||||
import { dumpSqlFromDefinition, dumpSqlSourceDef, dumpSqlSourceRef } from './dumpSqlSource';
|
||||
import { dumpSqlCondition } from './dumpSqlCondition';
|
||||
|
||||
export function dumpSqlSelect(dmp: SqlDumper, cmd: Select) {
|
||||
@@ -115,7 +115,10 @@ export function dumpSqlInsert(dmp: SqlDumper, cmd: Insert) {
|
||||
cmd.fields.map(x => x.targetColumn)
|
||||
);
|
||||
dmp.putCollection(',', cmd.fields, x => dumpSqlExpression(dmp, x));
|
||||
if (dmp.dialect.requireFromDual) {
|
||||
if (cmd.whereNotExistsSource) {
|
||||
dmp.put(' ^from ');
|
||||
dumpSqlSourceDef(dmp, cmd.whereNotExistsSource);
|
||||
} else if (dmp.dialect.requireFromDual) {
|
||||
dmp.put(' ^from ^dual ');
|
||||
}
|
||||
dmp.put(' ^where ^not ^exists (^select * ^from %f ^where ', cmd.targetTable);
|
||||
|
||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
||||
import type { SqlDumper } from 'dbgate-types';
|
||||
import { Expression, ColumnRefExpression } from './types';
|
||||
import { dumpSqlSourceRef } from './dumpSqlSource';
|
||||
import { dumpSqlSelect } from './dumpSqlCommand';
|
||||
|
||||
export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
||||
switch (expr.exprType) {
|
||||
@@ -67,5 +68,11 @@ export function dumpSqlExpression(dmp: SqlDumper, expr: Expression) {
|
||||
});
|
||||
dmp.put(')');
|
||||
break;
|
||||
|
||||
case 'select':
|
||||
dmp.put('(');
|
||||
dumpSqlSelect(dmp, expr.select);
|
||||
dmp.put(')');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface Insert {
|
||||
fields: UpdateField[];
|
||||
targetTable: NamedObjectInfo;
|
||||
insertWhereNotExistsCondition?: Condition;
|
||||
whereNotExistsSource?: Source;
|
||||
}
|
||||
|
||||
export interface AllowIdentityInsert {
|
||||
@@ -226,6 +227,11 @@ export interface RowNumberExpression {
|
||||
orderBy: OrderByExpression[];
|
||||
}
|
||||
|
||||
export interface SelectExpression {
|
||||
exprType: 'select';
|
||||
select: Select;
|
||||
}
|
||||
|
||||
export type Expression =
|
||||
| ColumnRefExpression
|
||||
| ValueExpression
|
||||
@@ -235,7 +241,8 @@ export type Expression =
|
||||
| CallExpression
|
||||
| MethodCallExpression
|
||||
| TranformExpression
|
||||
| RowNumberExpression;
|
||||
| RowNumberExpression
|
||||
| SelectExpression;
|
||||
export type OrderByExpression = Expression & { direction: 'ASC' | 'DESC' };
|
||||
|
||||
export type ResultField = Expression & { alias?: string };
|
||||
|
||||
@@ -2,4 +2,5 @@ module.exports = {
|
||||
preset: 'ts-jest',
|
||||
testEnvironment: 'node',
|
||||
moduleFileExtensions: ['js'],
|
||||
reporters: ['default', 'github-actions'],
|
||||
};
|
||||
|
||||
1
packages/types/dumper.d.ts
vendored
1
packages/types/dumper.d.ts
vendored
@@ -16,6 +16,7 @@ export interface SqlDumper extends AlterProcessor {
|
||||
transform(type: TransformType, dumpExpr: () => void);
|
||||
createDatabase(name: string);
|
||||
dropDatabase(name: string);
|
||||
comment(value: string);
|
||||
|
||||
callableTemplate(func: CallableObjectInfo);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@
|
||||
initializeAppUpdates();
|
||||
installCloudListeners();
|
||||
refreshPublicCloudFiles();
|
||||
saveSelectedLanguageToCache();
|
||||
saveSelectedLanguageToCache(config.preferrendLanguage);
|
||||
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" context="module">
|
||||
import { copyTextToClipboard } from '../utility/clipboard';
|
||||
import { _t, _tval, DefferedTranslationResult } from '../translations';
|
||||
import sqlFormatter from 'sql-formatter';
|
||||
|
||||
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
||||
export const createMatcher =
|
||||
@@ -88,7 +89,8 @@
|
||||
isRename?: boolean;
|
||||
isTruncate?: boolean;
|
||||
isCopyTableName?: boolean;
|
||||
isDuplicateTable?: boolean;
|
||||
isTableBackup?: boolean;
|
||||
isTableRestore?: boolean;
|
||||
isDiagram?: boolean;
|
||||
functionName?: string;
|
||||
isExport?: boolean;
|
||||
@@ -106,6 +108,8 @@
|
||||
}
|
||||
|
||||
function createMenusCore(objectTypeField, driver, data): DbObjMenuItem[] {
|
||||
const backupMatch = data.objectTypeField === 'tables' ? data.pureName.match(TABLE_BACKUP_REGEX) : null;
|
||||
|
||||
switch (objectTypeField) {
|
||||
case 'tables':
|
||||
return [
|
||||
@@ -175,11 +179,18 @@
|
||||
isCopyTableName: true,
|
||||
requiresWriteAccess: false,
|
||||
},
|
||||
hasPermission('dbops/table/backup') && {
|
||||
label: _t('dbObject.createTableBackup', { defaultMessage: 'Create table backup' }),
|
||||
isDuplicateTable: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/table/backup') &&
|
||||
!backupMatch && {
|
||||
label: _t('dbObject.createTableBackup', { defaultMessage: 'Create table backup' }),
|
||||
isTableBackup: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/table/restore') &&
|
||||
backupMatch && {
|
||||
label: _t('dbObject.createRestoreScript', { defaultMessage: 'Create restore script' }),
|
||||
isTableRestore: true,
|
||||
requiresWriteAccess: true,
|
||||
},
|
||||
hasPermission('dbops/model/view') && {
|
||||
label: _t('dbObject.showDiagram', { defaultMessage: 'Show diagram' }),
|
||||
isDiagram: true,
|
||||
@@ -637,7 +648,7 @@
|
||||
});
|
||||
},
|
||||
});
|
||||
} else if (menu.isDuplicateTable) {
|
||||
} else if (menu.isTableBackup) {
|
||||
const driver = await getDriver();
|
||||
const dmp = driver.createDumper();
|
||||
const newTable = _.cloneDeep(data);
|
||||
@@ -671,6 +682,25 @@
|
||||
},
|
||||
engine: driver.engine,
|
||||
});
|
||||
} else if (menu.isTableRestore) {
|
||||
const backupMatch = data.objectTypeField === 'tables' ? data.pureName.match(TABLE_BACKUP_REGEX) : null;
|
||||
|
||||
const driver = await getDriver();
|
||||
const dmp = driver.createDumper();
|
||||
const db = await getDatabaseInfo(data);
|
||||
if (db) {
|
||||
const originalTable = db?.tables?.find(x => x.pureName == backupMatch[1] && x.schemaName == data.schemaName);
|
||||
if (originalTable) {
|
||||
createTableRestoreScript(data, originalTable, dmp);
|
||||
newQuery({
|
||||
title: _t('dbObject.restoreScript', {
|
||||
defaultMessage: 'Restore {name} #',
|
||||
values: { name: backupMatch[1] },
|
||||
}),
|
||||
initialData: sqlFormatter.format(dmp.s),
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (menu.isImport) {
|
||||
const { conid, database } = data;
|
||||
openImportExportTab({
|
||||
@@ -1008,6 +1038,8 @@
|
||||
|
||||
return handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
|
||||
}
|
||||
|
||||
export const TABLE_BACKUP_REGEX = /^_(.*)_(\d\d\d\d)-(\d\d)-(\d\d)-(\d\d)-(\d\d)-(\d\d)$/;
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -1025,7 +1057,7 @@
|
||||
} from '../stores';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { extractDbNameFromComposite, filterNameCompoud, getConnectionLabel } from 'dbgate-tools';
|
||||
import { getConnectionInfo } from '../utility/metadataLoaders';
|
||||
import { getConnectionInfo, getDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import fullDisplayName from '../utility/fullDisplayName';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { findEngineDriver } from 'dbgate-tools';
|
||||
@@ -1047,6 +1079,8 @@
|
||||
import { getBoolSettingsValue, getOpenDetailOnArrowsSettings } from '../settings/settingsTools';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import formatFileSize from '../utility/formatFileSize';
|
||||
import { createTableRestoreScript } from '../utility/tableRestoreScript';
|
||||
import newQuery from '../query/newQuery';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
@@ -1086,14 +1120,21 @@
|
||||
}
|
||||
|
||||
$: isPinned = !!$pinnedTables.find(x => testEqual(data, x));
|
||||
|
||||
$: backupParsed = data.objectTypeField === 'tables' ? data.pureName.match(TABLE_BACKUP_REGEX) : null;
|
||||
$: backupTitle =
|
||||
backupParsed != null
|
||||
? `${backupParsed[1]} (${backupParsed[2]}-${backupParsed[3]}-${backupParsed[4]} ${backupParsed[5]}:${backupParsed[6]}:${backupParsed[7]})`
|
||||
: null;
|
||||
</script>
|
||||
|
||||
<AppObjectCore
|
||||
{...$$restProps}
|
||||
module={$$props.module}
|
||||
{data}
|
||||
title={data.schemaName && !passProps?.hideSchemaName ? `${data.schemaName}.${data.pureName}` : data.pureName}
|
||||
icon={databaseObjectIcons[data.objectTypeField]}
|
||||
title={backupTitle ??
|
||||
(data.schemaName && !passProps?.hideSchemaName ? `${data.schemaName}.${data.pureName}` : data.pureName)}
|
||||
icon={backupParsed ? 'img table-backup' : databaseObjectIcons[data.objectTypeField]}
|
||||
menu={createMenu}
|
||||
showPinnedInsteadOfUnpin={passProps?.showPinnedInsteadOfUnpin}
|
||||
onPin={passProps?.ingorePin ? null : isPinned ? null : () => pinnedTables.update(list => [...list, data])}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import getElectron from '../utility/getElectron';
|
||||
import InlineButtonLabel from '../buttons/InlineButtonLabel.svelte';
|
||||
import resolveApi, { resolveApiHeaders } from '../utility/resolveApi';
|
||||
import { _t } from '../translations';
|
||||
|
||||
import uuidv1 from 'uuid/v1';
|
||||
|
||||
@@ -49,11 +50,11 @@
|
||||
</script>
|
||||
|
||||
{#if electron}
|
||||
<InlineButton on:click={handleOpenElectronFile} title="Open file" data-testid={$$props['data-testid']}>
|
||||
<InlineButton on:click={handleOpenElectronFile} title={_t('files.openFile', { defaultMessage: "Open file" })} data-testid={$$props['data-testid']}>
|
||||
<FontIcon {icon} />
|
||||
</InlineButton>
|
||||
{:else}
|
||||
<InlineButtonLabel on:click={() => {}} title="Upload file" data-testid={$$props['data-testid']} htmlFor={inputId}>
|
||||
<InlineButtonLabel on:click={() => {}} title={_t('files.uploadFile', { defaultMessage: "Upload file" })} data-testid={$$props['data-testid']} htmlFor={inputId}>
|
||||
<FontIcon {icon} />
|
||||
</InlineButtonLabel>
|
||||
{/if}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts">
|
||||
import FormStyledButtonLikeLabel from '../buttons/FormStyledButtonLikeLabel.svelte';
|
||||
import uploadFiles from '../utility/uploadFiles';
|
||||
import { _t } from '../translations';
|
||||
|
||||
const handleChange = e => {
|
||||
const files = [...e.target.files];
|
||||
@@ -9,6 +10,6 @@
|
||||
</script>
|
||||
|
||||
<div class="m-1">
|
||||
<FormStyledButtonLikeLabel htmlFor="uploadFileButton">Upload file</FormStyledButtonLikeLabel>
|
||||
<FormStyledButtonLikeLabel htmlFor="uploadFileButton">{_t('files.uploadFile', { defaultMessage: "Upload file" })}</FormStyledButtonLikeLabel>
|
||||
<input type="file" id="uploadFileButton" hidden on:change={handleChange} />
|
||||
</div>
|
||||
|
||||
@@ -3,7 +3,7 @@ import { currentDatabase, getCurrentDatabase } from '../stores';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import registerCommand from './registerCommand';
|
||||
import { apiCall } from '../utility/api';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { getDatabasStatusMenu, switchCurrentDatabase } from '../utility/common';
|
||||
import { __t } from '../translations';
|
||||
|
||||
registerCommand({
|
||||
@@ -18,33 +18,7 @@ registerCommand({
|
||||
conid: connection._id,
|
||||
database: name,
|
||||
};
|
||||
return [
|
||||
{
|
||||
text: 'Sync model (incremental)',
|
||||
onClick: () => {
|
||||
apiCall('database-connections/sync-model', dbid);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Sync model (full)',
|
||||
onClick: () => {
|
||||
apiCall('database-connections/sync-model', { ...dbid, isFullRefresh: true });
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Reopen',
|
||||
onClick: () => {
|
||||
apiCall('database-connections/refresh', dbid);
|
||||
},
|
||||
},
|
||||
{
|
||||
text: 'Disconnect',
|
||||
onClick: () => {
|
||||
const electron = getElectron();
|
||||
if (electron) apiCall('database-connections/disconnect', dbid);
|
||||
switchCurrentDatabase(null);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
return getDatabasStatusMenu(dbid);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
promoWidgetPreview,
|
||||
visibleToolbar,
|
||||
visibleWidgetSideBar,
|
||||
selectedWidget,
|
||||
} from '../stores';
|
||||
import registerCommand from './registerCommand';
|
||||
import { get } from 'svelte/store';
|
||||
@@ -115,13 +116,13 @@ registerCommand({
|
||||
toolbar: true,
|
||||
icon: 'icon new-connection',
|
||||
toolbarName: __t('command.new.connection', { defaultMessage: 'Add connection' }),
|
||||
category: __t('command.new', { defaultMessage: 'New'}),
|
||||
category: __t('command.new', { defaultMessage: 'New' }),
|
||||
toolbarOrder: 1,
|
||||
name: __t('command.new.connection', { defaultMessage: 'Connection' }),
|
||||
testEnabled: () => !getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase,
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'New Connection',
|
||||
title: _t('common.newConnection', { defaultMessage: 'New Connection' }),
|
||||
icon: 'img connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
});
|
||||
@@ -140,7 +141,7 @@ registerCommand({
|
||||
!getCurrentConfig()?.runAsPortal && !getCurrentConfig()?.storageDatabase && !!getCloudSigninTokenHolder(),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'New Connection on Cloud',
|
||||
title: _t('common.newConnectionCloud', { defaultMessage: 'New Connection on Cloud' }),
|
||||
icon: 'img cloud-connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
props: {
|
||||
@@ -561,7 +562,10 @@ registerCommand({
|
||||
testEnabled: () => true,
|
||||
onClick: () => {
|
||||
showModal(ConfirmModal, {
|
||||
message: _t('command.file.resetLayoutConfirm', { defaultMessage: 'Really reset layout data? All opened tabs, settings and layout data will be lost. Connections and saved files will be preserved. After this, restart DbGate for applying changes.' }),
|
||||
message: _t('command.file.resetLayoutConfirm', {
|
||||
defaultMessage:
|
||||
'Really reset layout data? All opened tabs, settings and layout data will be lost. Connections and saved files will be preserved. After this, restart DbGate for applying changes.',
|
||||
}),
|
||||
onConfirm: async () => {
|
||||
await apiCall('config/delete-settings');
|
||||
localStorage.clear();
|
||||
@@ -665,7 +669,9 @@ registerCommand({
|
||||
'currentArchive',
|
||||
];
|
||||
for (const key of keys) removeLocalStorage(key);
|
||||
showSnackbarSuccess(_t('command.view.restart', { defaultMessage: 'Restart DbGate (or reload on web) for applying changes' }));
|
||||
showSnackbarSuccess(
|
||||
_t('command.view.restart', { defaultMessage: 'Restart DbGate (or reload on web) for applying changes' })
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
@@ -762,6 +768,19 @@ if (isProApp()) {
|
||||
}
|
||||
|
||||
if (hasPermission('settings/change')) {
|
||||
registerCommand({
|
||||
id: 'settings.settingsTab',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
name: __t('command.settings.settingsTab', { defaultMessage: 'Settings tab' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: _t('command.settings.settingsTab', { defaultMessage: 'Settings tab' }),
|
||||
icon: 'icon settings',
|
||||
tabComponent: 'SettingsTab',
|
||||
props: {},
|
||||
});
|
||||
},
|
||||
});
|
||||
registerCommand({
|
||||
id: 'settings.commands',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
@@ -777,14 +796,14 @@ if (hasPermission('settings/change')) {
|
||||
testEnabled: () => hasPermission('settings/change'),
|
||||
});
|
||||
|
||||
registerCommand({
|
||||
id: 'settings.show',
|
||||
category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
name: __t('command.settings.change', { defaultMessage: 'Change' }),
|
||||
toolbarName: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
onClick: () => showModal(SettingsModal),
|
||||
testEnabled: () => hasPermission('settings/change'),
|
||||
});
|
||||
// registerCommand({
|
||||
// id: 'settings.show',
|
||||
// category: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
// name: __t('command.settings.change', { defaultMessage: 'Change' }),
|
||||
// toolbarName: __t('command.settings', { defaultMessage: 'Settings' }),
|
||||
// onClick: () => showModal(SettingsModal),
|
||||
// testEnabled: () => hasPermission('settings/change'),
|
||||
// });
|
||||
}
|
||||
|
||||
registerCommand({
|
||||
@@ -799,7 +818,9 @@ registerCommand({
|
||||
registerCommand({
|
||||
id: 'file.exit',
|
||||
category: __t('command.file', { defaultMessage: 'File' }),
|
||||
name: isMac() ? __t('command.file.quit', { defaultMessage: 'Quit' }) : __t('command.file.exit', { defaultMessage: 'Exit' }),
|
||||
name: isMac()
|
||||
? __t('command.file.quit', { defaultMessage: 'Quit' })
|
||||
: __t('command.file.exit', { defaultMessage: 'Exit' }),
|
||||
// keyText: isMac() ? 'Command+Q' : null,
|
||||
testEnabled: () => getElectron() != null,
|
||||
onClick: () => getElectron().send('quit-app'),
|
||||
@@ -862,6 +883,7 @@ export function registerFileCommands({
|
||||
undoRedo = false,
|
||||
executeAdditionalCondition = null,
|
||||
copyPaste = false,
|
||||
defaultTeamFolder = false,
|
||||
}) {
|
||||
if (save) {
|
||||
registerCommand({
|
||||
@@ -874,7 +896,7 @@ export function registerFileCommands({
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save', folder, format, fileExtension),
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save', folder, format, fileExtension, defaultTeamFolder),
|
||||
});
|
||||
registerCommand({
|
||||
id: idPrefix + '.saveAs',
|
||||
@@ -882,14 +904,14 @@ export function registerFileCommands({
|
||||
category,
|
||||
name: __t('command.saveAs', { defaultMessage: 'Save As' }),
|
||||
testEnabled: () => getCurrentEditor() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-as', folder, format, fileExtension),
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-as', folder, format, fileExtension, defaultTeamFolder),
|
||||
});
|
||||
registerCommand({
|
||||
id: idPrefix + '.saveToDisk',
|
||||
category,
|
||||
name: __t('command.saveToDisk', { defaultMessage: 'Save to disk' }),
|
||||
testEnabled: () => getCurrentEditor() != null && getElectron() != null,
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-to-disk', folder, format, fileExtension),
|
||||
onClick: () => saveTabFile(getCurrentEditor(), 'save-to-disk', folder, format, fileExtension, defaultTeamFolder),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1202,6 +1224,35 @@ registerCommand({
|
||||
},
|
||||
});
|
||||
|
||||
if ( hasPermission('application-log'))
|
||||
{
|
||||
registerCommand({
|
||||
id: 'app.showLogs',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.showLogs', { defaultMessage: 'View application logs' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Application log',
|
||||
icon: 'img applog',
|
||||
tabComponent: 'AppLogTab',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
if (hasPermission('widgets/plugins'))
|
||||
{
|
||||
registerCommand({
|
||||
id: 'app.managePlugins',
|
||||
category: __t('command.application', { defaultMessage: 'Application' }),
|
||||
name: __t('command.application.managePlugins', { defaultMessage: 'Manage plugins' }),
|
||||
onClick: () => {
|
||||
selectedWidget.set('plugins');
|
||||
visibleWidgetSideBar.set(true);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const electron = getElectron();
|
||||
if (electron) {
|
||||
electron.addEventListener('run-command', (e, commandId) => runCommand(commandId));
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import moveDrag from '../utility/moveDrag';
|
||||
import ColumnLine from './ColumnLine.svelte';
|
||||
import DomTableRef from './DomTableRef';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let conid;
|
||||
export let database;
|
||||
@@ -185,8 +186,8 @@
|
||||
const handleSetTableAlias = () => {
|
||||
showModal(InputTextModal, {
|
||||
value: alias || '',
|
||||
label: 'New alias',
|
||||
header: 'Set table alias',
|
||||
label: _t('designerTable.newAlias', { defaultMessage: 'New alias' }),
|
||||
header: _t('designerTable.setTableAlias', { defaultMessage: 'Set table alias' }),
|
||||
onConfirm: newAlias => {
|
||||
onChangeTable({
|
||||
...table,
|
||||
@@ -210,13 +211,13 @@
|
||||
return settings?.tableMenu({ designer, designerId, onRemoveTable });
|
||||
}
|
||||
return [
|
||||
{ text: 'Remove', onClick: () => onRemoveTable({ designerId }) },
|
||||
{ text: _t('common.remove', { defaultMessage: 'Remove' }), onClick: () => onRemoveTable({ designerId }) },
|
||||
{ divider: true },
|
||||
settings?.allowTableAlias &&
|
||||
!isMultipleTableSelection && [
|
||||
{ text: 'Set table alias', onClick: handleSetTableAlias },
|
||||
{ text: _t('designerTable.setTableAlias', { defaultMessage: 'Set table alias' }), onClick: handleSetTableAlias },
|
||||
alias && {
|
||||
text: 'Remove table alias',
|
||||
text: _t('designerTable.removeTableAlias', { defaultMessage: 'Remove table alias' }),
|
||||
onClick: () =>
|
||||
onChangeTable({
|
||||
...table,
|
||||
@@ -225,11 +226,11 @@
|
||||
},
|
||||
],
|
||||
settings?.allowAddAllReferences &&
|
||||
!isMultipleTableSelection && { text: 'Add references', onClick: () => onAddAllReferences(table) },
|
||||
settings?.allowChangeColor && { text: 'Change color', onClick: () => onChangeTableColor(table) },
|
||||
!isMultipleTableSelection && { text: _t('designerTable.addReferences', { defaultMessage: 'Add references' }), onClick: () => onAddAllReferences(table) },
|
||||
settings?.allowChangeColor && { text: _t('designerTable.changeColor', { defaultMessage: 'Change color' }), onClick: () => onChangeTableColor(table) },
|
||||
settings?.allowDefineVirtualReferences &&
|
||||
!isMultipleTableSelection && {
|
||||
text: 'Define virtual foreign key',
|
||||
text: _t('designerTable.defineVirtualForeignKey', { defaultMessage: 'Define virtual foreign key' }),
|
||||
onClick: () => handleDefineVirtualForeignKey(table),
|
||||
},
|
||||
settings?.appendTableSystemMenu &&
|
||||
|
||||
165
packages/web/src/elements/SettingsMenuControl.svelte
Normal file
165
packages/web/src/elements/SettingsMenuControl.svelte
Normal file
@@ -0,0 +1,165 @@
|
||||
<script lang="ts">
|
||||
import _ from 'lodash';
|
||||
import HorizontalSplitter from './HorizontalSplitter.svelte';
|
||||
|
||||
interface MenuItemDef {
|
||||
label: string;
|
||||
slot?: number;
|
||||
component?: any;
|
||||
props?: any;
|
||||
testid?: string;
|
||||
identifier?: string;
|
||||
}
|
||||
|
||||
export let items: MenuItemDef[];
|
||||
export let value: string | number = 0;
|
||||
export let containerMaxWidth = undefined;
|
||||
export let containerMaxHeight = undefined;
|
||||
export let flex1 = true;
|
||||
export let flexColContainer = false;
|
||||
export let maxHeight100 = false;
|
||||
export let scrollableContentContainer = false;
|
||||
export let contentTestId = undefined;
|
||||
export let onUserChange = null;
|
||||
|
||||
export function setValue(index) {
|
||||
value = index;
|
||||
}
|
||||
export function getValue() {
|
||||
return value;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="main" class:maxHeight100 class:flex1>
|
||||
<HorizontalSplitter initialValue="20%">
|
||||
<svelte:fragment slot="1">
|
||||
<div class="menu">
|
||||
{#each _.compact(items) as item, index}
|
||||
<div
|
||||
class="menu-item"
|
||||
class:selected={value == (item.identifier ?? index)}
|
||||
on:click={() => {
|
||||
value = item.identifier ?? index;
|
||||
onUserChange?.(item.identifier ?? index);
|
||||
}}
|
||||
data-testid={item.testid}
|
||||
>
|
||||
<span class="ml-2 noselect">
|
||||
{item.label}
|
||||
</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
<div
|
||||
class="content-container"
|
||||
class:scrollableContentContainer
|
||||
style:max-height={containerMaxHeight}
|
||||
data-testid={contentTestId}
|
||||
>
|
||||
{#each _.compact(items) as item, index}
|
||||
<div
|
||||
class="container"
|
||||
class:flexColContainer
|
||||
class:maxHeight100
|
||||
class:itemVisible={(item.identifier ?? index) == value}
|
||||
style:max-width={containerMaxWidth}
|
||||
>
|
||||
<svelte:component
|
||||
this={item.component}
|
||||
{...item.props}
|
||||
itemVisible={(item.identifier ?? index) == value}
|
||||
menuControlHiddenItem={(item.identifier ?? index) != value}
|
||||
/>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</HorizontalSplitter>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.main {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.main.flex1 {
|
||||
flex: 1;
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.main.maxHeight100 {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--theme-bg-2);
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.menu::-webkit-scrollbar {
|
||||
width: 7px;
|
||||
}
|
||||
|
||||
.menu-item {
|
||||
white-space: nowrap;
|
||||
padding: 12px 20px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
border-bottom: 1px solid var(--theme-border);
|
||||
transition: background-color 0.2s ease;
|
||||
}
|
||||
|
||||
.menu-item:hover {
|
||||
background-color: var(--theme-bg-hover);
|
||||
}
|
||||
|
||||
.menu-item.selected {
|
||||
background-color: var(--theme-bg-1);
|
||||
font-weight: 600;
|
||||
border-left: 3px solid var(--theme-font-link);
|
||||
}
|
||||
|
||||
.content-container {
|
||||
flex: 1;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.scrollableContentContainer {
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.container.maxHeight100 {
|
||||
max-height: 100%;
|
||||
}
|
||||
|
||||
.container.flexColContainer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.container {
|
||||
position: absolute;
|
||||
display: flex;
|
||||
left: 0;
|
||||
right: 0;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
overflow-y: auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.container:not(.itemVisible) {
|
||||
visibility: hidden;
|
||||
}
|
||||
</style>
|
||||
@@ -7,6 +7,7 @@
|
||||
import { getFormContext } from './FormProviderCore.svelte';
|
||||
|
||||
import FormSelectField from './FormSelectField.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let folderName;
|
||||
export let name;
|
||||
@@ -28,10 +29,10 @@
|
||||
<div>
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value="All files"
|
||||
value={_t('common.allFiles', { defaultMessage: "All files" })}
|
||||
on:click={() => setFieldValue(name, _.uniq([...($values[name] || []), ...($files && $files.map(x => x.name))]))}
|
||||
/>
|
||||
<FormStyledButton type="button" value="Remove all" on:click={() => setFieldValue(name, [])} />
|
||||
<FormStyledButton type="button" value={_t('common.removeAll', { defaultMessage: "Remove all" })} on:click={() => setFieldValue(name, [])} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -353,6 +353,7 @@
|
||||
'img data-deploy': 'mdi mdi-database-settings color-icon-green',
|
||||
'img arrow-start-here': 'mdi mdi-arrow-down-bold-circle color-icon-green',
|
||||
'img team-file': 'mdi mdi-account-file color-icon-red',
|
||||
'img table-backup': 'mdi mdi-cube color-icon-yellow',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
import ElectronFilesInput from './ElectronFilesInput.svelte';
|
||||
import { addFilesToSourceList } from './ImportExportConfigurator.svelte';
|
||||
import UploadButton from '../buttons/UploadButton.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let setPreviewSource = undefined;
|
||||
|
||||
@@ -55,10 +56,10 @@
|
||||
{:else}
|
||||
<UploadButton />
|
||||
{/if}
|
||||
<FormStyledButton value="Add web URL" on:click={handleAddUrl} />
|
||||
<FormStyledButton value={_t('importExport.addWebUrl', { defaultMessage: "Add web URL" })} on:click={handleAddUrl} />
|
||||
</div>
|
||||
|
||||
<div class="wrapper">Drag & drop imported files here</div>
|
||||
<div class="wrapper">{_t('importExport.dragDropImportedFilesHere', { defaultMessage: "Drag & drop imported files here" })}</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { getFormContext } from '../forms/FormProviderCore.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import { useConnectionInfo, useDatabaseInfo } from '../utility/metadataLoaders';
|
||||
import { _t } from '../translations';
|
||||
|
||||
export let conidName;
|
||||
export let databaseName;
|
||||
@@ -41,7 +42,7 @@
|
||||
{#if $dbinfo && $dbinfo[field]?.length > 0}
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value={`All ${field}`}
|
||||
value={_t('common.allFields', { defaultMessage: 'All {field}', values: { field } })}
|
||||
data-testid={`FormTablesSelect_buttonAll_${field}`}
|
||||
on:click={() =>
|
||||
setFieldValue(
|
||||
@@ -52,7 +53,7 @@
|
||||
{/if}
|
||||
{/each}
|
||||
|
||||
<FormStyledButton type="button" value="Remove all" on:click={() => setFieldValue(name, [])} />
|
||||
<FormStyledButton type="button" value={_t('common.removeAll', { defaultMessage: "Remove all" })} on:click={() => setFieldValue(name, [])} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
import createRef from '../utility/createRef';
|
||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
||||
import ErrorMessageModal from '../modals/ErrorMessageModal.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
// export let uploadedFile = undefined;
|
||||
// export let openedFile = undefined;
|
||||
@@ -211,7 +212,7 @@
|
||||
</div>
|
||||
|
||||
<div class="m-2">
|
||||
<div class="title"><FontIcon icon="icon tables" /> Map source tables/files</div>
|
||||
<div class="title"><FontIcon icon="icon tables" /> {_t('importExport.mapSourceTablesFiles', { defaultMessage: "Map source tables/files" })}</div>
|
||||
|
||||
{#key targetEditKey}
|
||||
{#key progressHolder}
|
||||
@@ -220,34 +221,34 @@
|
||||
columns={[
|
||||
{
|
||||
fieldName: 'source',
|
||||
header: 'Source',
|
||||
header: _t('importExport.source', { defaultMessage: "Source" }),
|
||||
component: SourceName,
|
||||
getProps: row => ({ name: row }),
|
||||
},
|
||||
{
|
||||
fieldName: 'action',
|
||||
header: 'Action',
|
||||
header: _t('importExport.action', { defaultMessage: "Action" }),
|
||||
component: SourceAction,
|
||||
getProps: row => ({ name: row, targetDbinfo }),
|
||||
},
|
||||
{
|
||||
fieldName: 'target',
|
||||
header: 'Target',
|
||||
header: _t('importExport.target', { defaultMessage: "Target" }),
|
||||
slot: 1,
|
||||
},
|
||||
supportsPreview && {
|
||||
fieldName: 'preview',
|
||||
header: 'Preview',
|
||||
header: _t('importExport.preview', { defaultMessage: "Preview" }),
|
||||
slot: 0,
|
||||
},
|
||||
!!progressHolder && {
|
||||
fieldName: 'status',
|
||||
header: 'Status',
|
||||
header: _t('importExport.status', { defaultMessage: "Status" }),
|
||||
slot: 3,
|
||||
},
|
||||
{
|
||||
fieldName: 'columns',
|
||||
header: 'Columns',
|
||||
header: _t('importExport.columns', { defaultMessage: "Columns" }),
|
||||
slot: 2,
|
||||
},
|
||||
]}
|
||||
|
||||
@@ -73,19 +73,19 @@
|
||||
<div class="column">
|
||||
{#if direction == 'source'}
|
||||
<div class="title">
|
||||
<FontIcon icon="icon import" /> Source configuration
|
||||
<FontIcon icon="icon import" /> {_t('importExport.sourceConfiguration', { defaultMessage: 'Source configuration' })}
|
||||
</div>
|
||||
{/if}
|
||||
{#if direction == 'target'}
|
||||
<div class="title">
|
||||
<FontIcon icon="icon export" /> Target configuration
|
||||
<FontIcon icon="icon export" /> {_t('importExport.targetConfiguration', { defaultMessage: 'Target configuration' })}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="buttons">
|
||||
{#if $currentDatabase}
|
||||
<FormStyledButton
|
||||
value="Current DB"
|
||||
value={_t('importExport.currentDatabase', { defaultMessage: "Current DB" })}
|
||||
on:click={() => {
|
||||
values.update(x => ({
|
||||
...x,
|
||||
@@ -97,7 +97,7 @@
|
||||
/>
|
||||
{/if}
|
||||
<FormStyledButton
|
||||
value="Current archive"
|
||||
value={_t('importExport.currentArchive', { defaultMessage: "Current archive" })}
|
||||
data-testid={direction == 'source'
|
||||
? 'SourceTargetConfig_buttonCurrentArchive_source'
|
||||
: 'SourceTargetConfig_buttonCurrentArchive_target'}
|
||||
@@ -111,7 +111,7 @@
|
||||
/>
|
||||
{#if direction == 'target'}
|
||||
<FormStyledButton
|
||||
value="New archive"
|
||||
value={_t('importExport.newArchive', { defaultMessage: "New archive" })}
|
||||
on:click={() => {
|
||||
showModal(InputTextModal, {
|
||||
header: 'Archive',
|
||||
@@ -133,7 +133,7 @@
|
||||
<FormSelectField
|
||||
options={types.filter(x => x.directions.includes(direction))}
|
||||
name={storageTypeField}
|
||||
label="Storage type"
|
||||
label={_t('importExport.storageType', { defaultMessage: "Storage type" })}
|
||||
/>
|
||||
|
||||
{#if format && isProApp()}
|
||||
@@ -172,9 +172,9 @@
|
||||
{/if}
|
||||
|
||||
{#if storageType == 'database' || storageType == 'query'}
|
||||
<FormConnectionSelect name={connectionIdField} label="Server" {direction} />
|
||||
<FormConnectionSelect name={connectionIdField} label={_t('common.server', { defaultMessage: 'Server' })} {direction} />
|
||||
{#if !$connectionInfo?.singleDatabase}
|
||||
<FormDatabaseSelect conidName={connectionIdField} name={databaseNameField} label="Database" />
|
||||
<FormDatabaseSelect conidName={connectionIdField} name={databaseNameField} label={_t('common.database', { defaultMessage: 'Database' })} />
|
||||
{/if}
|
||||
{/if}
|
||||
{#if storageType == 'database'}
|
||||
@@ -210,7 +210,7 @@
|
||||
|
||||
{#if storageType == 'archive'}
|
||||
<FormArchiveFolderSelect
|
||||
label="Archive folder"
|
||||
label={_t('importExport.archiveFolder', { defaultMessage: "Archive folder" })}
|
||||
name={archiveFolderField}
|
||||
additionalFolders={_.compact([$values[archiveFolderField]])}
|
||||
allowCreateNew={direction == 'target'}
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
import { closeCurrentModal, showModal } from './modalTools';
|
||||
import FormCloudFolderSelect from '../forms/FormCloudFolderSelect.svelte';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import { useConfig } from '../utility/metadataLoaders';
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
|
||||
export let data;
|
||||
export let name;
|
||||
@@ -24,26 +26,39 @@
|
||||
export let onSave = undefined;
|
||||
export let folid;
|
||||
export let skipLocal = false;
|
||||
export let defaultTeamFolder = false;
|
||||
// export let cntid;
|
||||
|
||||
const values = writable({ name, cloudFolder: folid ?? '__local' });
|
||||
const configValue = useConfig();
|
||||
|
||||
const values = writable({
|
||||
name,
|
||||
cloudFolder: folid ?? '__local',
|
||||
saveToTeamFolder: !!(getCurrentConfig()?.storageDatabase && defaultTeamFolder),
|
||||
});
|
||||
|
||||
const electron = getElectron();
|
||||
|
||||
const handleSubmit = async e => {
|
||||
const { name, cloudFolder } = e.detail;
|
||||
if ($values['saveToTeamFolder']) {
|
||||
const { teamFileId } = await apiCall('team-files/create-new', { fileType: folder, file: name, data });
|
||||
closeCurrentModal();
|
||||
if (onSave) {
|
||||
onSave(name, {
|
||||
savedFile: name,
|
||||
savedFolder: folder,
|
||||
savedFilePath: null,
|
||||
savedCloudFolderId: null,
|
||||
savedCloudContentId: null,
|
||||
savedTeamFileId: teamFileId,
|
||||
});
|
||||
const resp = await apiCall('team-files/create-new', { fileType: folder, file: name, data });
|
||||
if (resp?.apiErrorMessage) {
|
||||
showSnackbarError(resp.apiErrorMessage);
|
||||
} else if (resp?.teamFileId) {
|
||||
closeCurrentModal();
|
||||
if (onSave) {
|
||||
onSave(name, {
|
||||
savedFile: name,
|
||||
savedFolder: folder,
|
||||
savedFilePath: null,
|
||||
savedCloudFolderId: null,
|
||||
savedCloudContentId: null,
|
||||
savedTeamFileId: resp.teamFileId,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
showSnackbarError('Failed to save to team folder.');
|
||||
}
|
||||
} else if (cloudFolder === '__local') {
|
||||
await apiCall('files/save', { folder, file: name, data, format });
|
||||
@@ -124,7 +139,7 @@
|
||||
]}
|
||||
/>
|
||||
{/if}
|
||||
{#if getCurrentConfig().storageDatabase}
|
||||
{#if $configValue?.storageDatabase}
|
||||
<FormCheckboxField label="Save to team folder" name="saveToTeamFolder" />
|
||||
{/if}
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
import WidgetsInnerContainer from '../widgets/WidgetsInnerContainer.svelte';
|
||||
import PluginsList from './PluginsList.svelte';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
let filter = '';
|
||||
// let search = '';
|
||||
@@ -20,7 +21,7 @@
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search extensions on web" {filter} bind:value={filter} />
|
||||
<SearchInput placeholder={_t('plugins.searchExtensionsOnWeb', { defaultMessage: 'Search extensions on web' })} {filter} bind:value={filter} />
|
||||
</SearchBoxWrapper>
|
||||
<WidgetsInnerContainer>
|
||||
{#if $plugins?.errorMessage}
|
||||
|
||||
70
packages/web/src/settings/BehaviourSettings.svelte
Normal file
70
packages/web/src/settings/BehaviourSettings.svelte
Normal file
@@ -0,0 +1,70 @@
|
||||
<script lang="ts">
|
||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
||||
import { _t } from "../translations";
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import FormValues from "../forms/FormValues.svelte";
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<FormValues let:values>
|
||||
<div class="heading">{_t('settings.behaviour', { defaultMessage: 'Behaviour' })}</div>
|
||||
|
||||
<FormCheckboxField
|
||||
name="behaviour.useTabPreviewMode"
|
||||
label={_t('settings.behaviour.useTabPreviewMode', { defaultMessage: 'Use tab preview mode' })}
|
||||
defaultValue={true}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="behaviour.jsonPreviewWrap"
|
||||
label={_t('settings.behaviour.jsonPreviewWrap', { defaultMessage: 'Wrap JSON in preview' })}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<div class="tip">
|
||||
<FontIcon icon="img tip" />
|
||||
{_t('settings.behaviour.singleClickPreview', {
|
||||
defaultMessage:
|
||||
'When you single-click or select a file in the "Tables, Views, Functions" view, it is shown in a preview mode and reuses an existing tab (preview tab). This is useful if you are quickly browsing tables and don\'t want every visited table to have its own tab. When you start editing the table or use double-click to open the table from the "Tables" view, a new tab is dedicated to that table.',
|
||||
})}
|
||||
</div>
|
||||
|
||||
<FormCheckboxField
|
||||
name="behaviour.openDetailOnArrows"
|
||||
label={_t('settings.behaviour.openDetailOnArrows', {
|
||||
defaultMessage: 'Open detail on keyboard navigation',
|
||||
})}
|
||||
defaultValue={true}
|
||||
disabled={values['behaviour.useTabPreviewMode'] === false}
|
||||
/>
|
||||
|
||||
<div class="heading">{_t('settings.confirmations', { defaultMessage: 'Confirmations' })}</div>
|
||||
|
||||
<FormCheckboxField
|
||||
name="skipConfirm.tableDataSave"
|
||||
label={_t('settings.confirmations.skipConfirm.tableDataSave', {
|
||||
defaultMessage: 'Skip confirmation when saving table data (SQL)',
|
||||
})}
|
||||
/>
|
||||
<FormCheckboxField
|
||||
name="skipConfirm.collectionDataSave"
|
||||
label={_t('settings.confirmations.skipConfirm.collectionDataSave', {
|
||||
defaultMessage: 'Skip confirmation when saving collection data (NoSQL)',
|
||||
})}
|
||||
/>
|
||||
</FormValues>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
||||
87
packages/web/src/settings/ConnectionSettings.svelte
Normal file
87
packages/web/src/settings/ConnectionSettings.svelte
Normal file
@@ -0,0 +1,87 @@
|
||||
<script lang="ts">
|
||||
import CheckboxField from "../forms/CheckboxField.svelte";
|
||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
||||
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
|
||||
import FormSelectField from "../forms/FormSelectField.svelte";
|
||||
import FormTextField from "../forms/FormTextField.svelte";
|
||||
import FormValues from "../forms/FormValues.svelte";
|
||||
import { lockedDatabaseMode } from "../stores";
|
||||
import { _t } from "../translations";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<FormValues let:values>
|
||||
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
|
||||
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.connection.showOnlyTabsFromSelectedDatabase', {
|
||||
defaultMessage: 'Show only tabs from selected database',
|
||||
})}
|
||||
type="checkbox"
|
||||
labelProps={{
|
||||
onClick: () => {
|
||||
$lockedDatabaseMode = !$lockedDatabaseMode;
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CheckboxField checked={$lockedDatabaseMode} on:change={e => ($lockedDatabaseMode = e.target.checked)} />
|
||||
</FormFieldTemplateLarge>
|
||||
|
||||
<FormCheckboxField
|
||||
name="connection.autoRefresh"
|
||||
label={_t('settings.connection.autoRefresh', {
|
||||
defaultMessage: 'Automatic refresh of database model on background',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
<FormTextField
|
||||
name="connection.autoRefreshInterval"
|
||||
label={_t('settings.connection.autoRefreshInterval', {
|
||||
defaultMessage: 'Interval between automatic DB structure reloads in seconds',
|
||||
})}
|
||||
defaultValue="30"
|
||||
disabled={values['connection.autoRefresh'] === false}
|
||||
/>
|
||||
<FormSelectField
|
||||
label={_t('settings.connection.sshBindHost', { defaultMessage: 'Local host address for SSH connections' })}
|
||||
name="connection.sshBindHost"
|
||||
isNative
|
||||
defaultValue="127.0.0.1"
|
||||
options={[
|
||||
{ value: '127.0.0.1', label: '127.0.0.1 (IPv4)' },
|
||||
{ value: '::1', label: '::1 (IPv6)' },
|
||||
{ value: 'localhost', label: 'localhost (domain name)' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div class="heading">{_t('settings.session', { defaultMessage: 'Query sessions' })}</div>
|
||||
<FormCheckboxField
|
||||
name="session.autoClose"
|
||||
label={_t('settings.session.autoClose', {
|
||||
defaultMessage: 'Automatic close query sessions after period without any activity',
|
||||
})}
|
||||
defaultValue={true}
|
||||
/>
|
||||
<FormTextField
|
||||
name="session.autoCloseTimeout"
|
||||
label={_t('settings.session.autoCloseTimeout', {
|
||||
defaultMessage: 'Interval, after which query session without activity is closed (in minutes)',
|
||||
})}
|
||||
defaultValue="15"
|
||||
disabled={values['session.autoClose'] === false}
|
||||
/>
|
||||
</FormValues>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
</style>
|
||||
100
packages/web/src/settings/DataGridSettings.svelte
Normal file
100
packages/web/src/settings/DataGridSettings.svelte
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
||||
import FormSelectField from "../forms/FormSelectField.svelte";
|
||||
import FormTextField from "../forms/FormTextField.svelte";
|
||||
import { _t } from "../translations";
|
||||
import { isProApp } from "../utility/proTools";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="heading">{_t('settings.dataGrid.title', { defaultMessage: 'Data grid' })}</div>
|
||||
<FormTextField
|
||||
name="dataGrid.pageSize"
|
||||
label={_t('settings.dataGrid.pageSize', {
|
||||
defaultMessage: 'Page size (number of rows for incremental loading, must be between 5 and 50000)',
|
||||
})}
|
||||
defaultValue="100"
|
||||
/>
|
||||
{#if isProApp()}
|
||||
<FormCheckboxField
|
||||
name="dataGrid.showHintColumns"
|
||||
label={_t('settings.dataGrid.showHintColumns', { defaultMessage: 'Show foreign key hints' })}
|
||||
defaultValue={true}
|
||||
/>
|
||||
{/if}
|
||||
<!-- <FormCheckboxField name="dataGrid.showHintColumns" label="Show foreign key hints" defaultValue={true} /> -->
|
||||
|
||||
<FormCheckboxField
|
||||
name="dataGrid.thousandsSeparator"
|
||||
label={_t('settings.dataGrid.thousandsSeparator', {
|
||||
defaultMessage: 'Use thousands separator for numbers',
|
||||
})}
|
||||
/>
|
||||
|
||||
<FormTextField
|
||||
name="dataGrid.defaultAutoRefreshInterval"
|
||||
label={_t('settings.dataGrid.defaultAutoRefreshInterval', {
|
||||
defaultMessage: 'Default grid auto refresh interval in seconds',
|
||||
})}
|
||||
defaultValue="10"
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="dataGrid.alignNumbersRight"
|
||||
label={_t('settings.dataGrid.alignNumbersRight', { defaultMessage: 'Align numbers to right' })}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<FormTextField
|
||||
name="dataGrid.collectionPageSize"
|
||||
label={_t('settings.dataGrid.collectionPageSize', {
|
||||
defaultMessage: 'Collection page size (for MongoDB JSON view, must be between 5 and 1000)',
|
||||
})}
|
||||
defaultValue="50"
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
label={_t('settings.dataGrid.coloringMode', { defaultMessage: 'Row coloring mode' })}
|
||||
name="dataGrid.coloringMode"
|
||||
isNative
|
||||
defaultValue="36"
|
||||
options={[
|
||||
{
|
||||
value: '36',
|
||||
label: _t('settings.dataGrid.coloringMode.36', { defaultMessage: 'Every 3rd and 6th row' }),
|
||||
},
|
||||
{
|
||||
value: '2-primary',
|
||||
label: _t('settings.dataGrid.coloringMode.2-primary', {
|
||||
defaultMessage: 'Every 2-nd row, primary color',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: '2-secondary',
|
||||
label: _t('settings.dataGrid.coloringMode.2-secondary', {
|
||||
defaultMessage: 'Every 2-nd row, secondary color',
|
||||
}),
|
||||
},
|
||||
{ value: 'none', label: _t('settings.dataGrid.coloringMode.none', { defaultMessage: 'None' }) },
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="dataGrid.showAllColumnsWhenSearch"
|
||||
label={_t('settings.dataGrid.showAllColumnsWhenSearch', {
|
||||
defaultMessage: 'Show all columns when searching',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
||||
103
packages/web/src/settings/DefaultActionsSettings.svelte
Normal file
103
packages/web/src/settings/DefaultActionsSettings.svelte
Normal file
@@ -0,0 +1,103 @@
|
||||
<script lang="ts">
|
||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
||||
import FormSelectField from "../forms/FormSelectField.svelte";
|
||||
import FormValues from "../forms/FormValues.svelte";
|
||||
import { _t } from "../translations";
|
||||
import FormDefaultActionField from "./FormDefaultActionField.svelte";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<FormValues let:values>
|
||||
<div class="heading">{_t('settings.defaultActions', { defaultMessage: 'Default actions' })}</div>
|
||||
|
||||
<FormSelectField
|
||||
label={_t('settings.defaultActions.connectionClick', { defaultMessage: 'Connection click' })}
|
||||
name="defaultAction.connectionClick"
|
||||
isNative
|
||||
defaultValue="connect"
|
||||
options={[
|
||||
{
|
||||
value: 'openDetails',
|
||||
label: _t('settings.defaultActions.connectionClick.openDetails', {
|
||||
defaultMessage: 'Edit / open details',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: 'connect',
|
||||
label: _t('settings.defaultActions.connectionClick.connect', { defaultMessage: 'Connect' }),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label: _t('settings.defaultActions.connectionClick.none', { defaultMessage: 'Do nothing' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
label={_t('settings.defaultActions.databaseClick', { defaultMessage: 'Database click' })}
|
||||
name="defaultAction.databaseClick"
|
||||
isNative
|
||||
defaultValue="switch"
|
||||
options={[
|
||||
{
|
||||
value: 'switch',
|
||||
label: _t('settings.defaultActions.databaseClick.switch', { defaultMessage: 'Switch database' }),
|
||||
},
|
||||
{
|
||||
value: 'none',
|
||||
label: _t('settings.defaultActions.databaseClick.none', { defaultMessage: 'Do nothing' }),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="defaultAction.useLastUsedAction"
|
||||
label={_t('settings.defaultActions.useLastUsedAction', { defaultMessage: 'Use last used action' })}
|
||||
defaultValue={true}
|
||||
/>
|
||||
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.tableClick', { defaultMessage: 'Table click' })}
|
||||
objectTypeField="tables"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.viewClick', { defaultMessage: 'View click' })}
|
||||
objectTypeField="views"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.materializedViewClick', { defaultMessage: 'Materialized view click' })}
|
||||
objectTypeField="matviews"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.procedureClick', { defaultMessage: 'Procedure click' })}
|
||||
objectTypeField="procedures"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.functionClick', { defaultMessage: 'Function click' })}
|
||||
objectTypeField="functions"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
<FormDefaultActionField
|
||||
label={_t('settings.defaultActions.collectionClick', { defaultMessage: 'NoSQL collection click' })}
|
||||
objectTypeField="collections"
|
||||
disabled={values['defaultAction.useLastUsedAction'] !== false}
|
||||
/>
|
||||
</FormValues>
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
</style>
|
||||
50
packages/web/src/settings/ExternalToolsSettings.svelte
Normal file
50
packages/web/src/settings/ExternalToolsSettings.svelte
Normal file
@@ -0,0 +1,50 @@
|
||||
<script lang="ts">
|
||||
import FormTextField from "../forms/FormTextField.svelte";
|
||||
import { _t } from "../translations";
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="heading">{_t('settings.externalTools', { defaultMessage: 'External tools' })}</div>
|
||||
<FormTextField
|
||||
name="externalTools.mysqldump"
|
||||
label={_t('settings.other.externalTools.mysqldump', {
|
||||
defaultMessage: 'mysqldump (backup MySQL database)',
|
||||
})}
|
||||
defaultValue="mysqldump"
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.mysql"
|
||||
label={_t('settings.other.externalTools.mysql', { defaultMessage: 'mysql (restore MySQL database)' })}
|
||||
defaultValue="mysql"
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.mysqlPlugins"
|
||||
label={_t('settings.other.externalTools.mysqlPlugins', {
|
||||
defaultMessage:
|
||||
'Folder with mysql plugins (for example for authentication). Set only in case of problems',
|
||||
})}
|
||||
defaultValue=""
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.pg_dump"
|
||||
label={_t('settings.other.externalTools.pg_dump', {
|
||||
defaultMessage: 'pg_dump (backup PostgreSQL database)',
|
||||
})}
|
||||
defaultValue="pg_dump"
|
||||
/>
|
||||
<FormTextField
|
||||
name="externalTools.psql"
|
||||
label={_t('settings.other.externalTools.psql', { defaultMessage: 'psql (restore PostgreSQL database)' })}
|
||||
defaultValue="psql"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
</style>
|
||||
100
packages/web/src/settings/GeneralSettings.svelte
Normal file
100
packages/web/src/settings/GeneralSettings.svelte
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import { internalRedirectTo } from '../clientAuth';
|
||||
import CheckboxField from '../forms/CheckboxField.svelte';
|
||||
import FormCheckboxField from '../forms/FormCheckboxField.svelte';
|
||||
import FormFieldTemplateLarge from '../forms/FormFieldTemplateLarge.svelte';
|
||||
import FormSelectField from '../forms/FormSelectField.svelte';
|
||||
import FormTextField from '../forms/FormTextField.svelte';
|
||||
import SelectField from '../forms/SelectField.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { EDITOR_KEYBINDINGS_MODES } from '../query/AceEditor.svelte';
|
||||
import { currentEditorKeybindigMode, currentEditorWrapEnabled } from '../stores';
|
||||
import { _t, getSelectedLanguage, setSelectedLanguage } from '../translations';
|
||||
import { isMac } from '../utility/common';
|
||||
import getElectron from '../utility/getElectron';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import ConfirmModal from '../modals/ConfirmModal.svelte';
|
||||
|
||||
const electron = getElectron();
|
||||
let restartWarning = false;
|
||||
</script>
|
||||
<div class="wrapper">
|
||||
<div class="heading">{_t('settings.general', { defaultMessage: 'General' })}</div>
|
||||
{#if electron}
|
||||
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Appearance' })}</div>
|
||||
<FormCheckboxField
|
||||
name="app.useNativeMenu"
|
||||
label={isMac()
|
||||
? _t('settings.useNativeWindowTitle', { defaultMessage: 'Use native window title' })
|
||||
: _t('settings.useSystemNativeMenu', { defaultMessage: 'Use system native menu' })}
|
||||
on:change={() => {
|
||||
restartWarning = true;
|
||||
}}
|
||||
/>
|
||||
{#if restartWarning}
|
||||
<div class="ml-5 mb-3">
|
||||
<FontIcon icon="img warn" />
|
||||
{_t('settings.nativeMenuRestartWarning', {
|
||||
defaultMessage: 'Native menu settings will be applied after app restart',
|
||||
})}
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
<FormCheckboxField
|
||||
name="tabGroup.showServerName"
|
||||
label={_t('settings.tabGroup.showServerName', {
|
||||
defaultMessage: 'Show server name alongside database name in title of the tab group',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
<div class="heading">{_t('settings.localization', { defaultMessage: 'Localization' })}</div>
|
||||
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.localization.language', { defaultMessage: 'Language' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
data-testid="SettingsModal_languageSelect"
|
||||
options={[
|
||||
{ value: 'cs', label: 'Čeština' },
|
||||
{ value: 'de', label: 'Deutsch' },
|
||||
{ value: 'en', label: 'English' },
|
||||
{ value: 'es', label: 'Español' },
|
||||
{ value: 'fr', label: 'Français' },
|
||||
{ value: 'it', label: 'Italiano' },
|
||||
{ value: 'pt', label: 'Português (Brasil)' },
|
||||
{ value: 'sk', label: 'Slovenčina' },
|
||||
{ value: 'ja', label: '日本語' },
|
||||
{ value: 'zh', label: '中文' },
|
||||
]}
|
||||
defaultValue={getSelectedLanguage()}
|
||||
value={getSelectedLanguage()}
|
||||
on:change={e => {
|
||||
setSelectedLanguage(e.detail);
|
||||
showModal(ConfirmModal, {
|
||||
message: _t('settings.localization.reloadWarning', {
|
||||
defaultMessage: 'Application will be reloaded to apply new language settings',
|
||||
}),
|
||||
onConfirm: () => {
|
||||
setTimeout(() => {
|
||||
internalRedirectTo(electron ? '/index.html' : '/');
|
||||
}, 100);
|
||||
},
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
</style>
|
||||
91
packages/web/src/settings/LicenseSettings.svelte
Normal file
91
packages/web/src/settings/LicenseSettings.svelte
Normal file
@@ -0,0 +1,91 @@
|
||||
<script lang="ts">
|
||||
import { safeFormatDate } from "dbgate-tools";
|
||||
import FormStyledButton from "../buttons/FormStyledButton.svelte";
|
||||
import FormTextAreaField from "../forms/FormTextAreaField.svelte";
|
||||
import FontIcon from "../icons/FontIcon.svelte";
|
||||
import { _t } from "../translations";
|
||||
import { apiCall } from "../utility/api";
|
||||
import { useSettings } from "../utility/metadataLoaders";
|
||||
import { derived } from "svelte/store";
|
||||
|
||||
const settings = useSettings();
|
||||
const settingsValues = derived(settings, $settings => {
|
||||
if (!$settings) {
|
||||
return {};
|
||||
}
|
||||
return $settings;
|
||||
});
|
||||
let licenseKeyCheckResult = null;
|
||||
|
||||
$: licenseKey = $settingsValues['other.licenseKey'];
|
||||
|
||||
</script>
|
||||
|
||||
<div class="heading">{_t('settings.other.license', { defaultMessage: 'License' })}</div>
|
||||
<FormTextAreaField
|
||||
name="other.licenseKey"
|
||||
label={_t('settings.other.licenseKey', { defaultMessage: 'License key' })}
|
||||
rows={7}
|
||||
onChange={async value => {
|
||||
licenseKeyCheckResult = await apiCall('config/check-license', { licenseKey: value });
|
||||
}}
|
||||
/>
|
||||
{#if licenseKeyCheckResult}
|
||||
<div class="m-3 ml-5">
|
||||
{#if licenseKeyCheckResult.status == 'ok'}
|
||||
<div>
|
||||
<FontIcon icon="img ok" />
|
||||
{_t('settings.other.licenseKey.valid', { defaultMessage: 'License key is valid' })}
|
||||
</div>
|
||||
{#if licenseKeyCheckResult.validTo}
|
||||
<div>
|
||||
{_t('settings.other.licenseKey.validTo', { defaultMessage: 'License valid to:' })}
|
||||
{licenseKeyCheckResult.validTo}
|
||||
</div>
|
||||
{/if}
|
||||
{#if licenseKeyCheckResult.expiration}
|
||||
<div>
|
||||
{_t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' })}
|
||||
<b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b>
|
||||
</div>
|
||||
{/if}
|
||||
{:else if licenseKeyCheckResult.status == 'error'}
|
||||
<div>
|
||||
<FontIcon icon="img error" />
|
||||
{licenseKeyCheckResult.errorMessage ??
|
||||
_t('settings.other.licenseKey.invalid', { defaultMessage: 'License key is invalid' })}
|
||||
{#if licenseKeyCheckResult.expiration}
|
||||
<div>
|
||||
{_t('settings.other.licenseKey.expiration', { defaultMessage: 'License key expiration:' })}
|
||||
<b>{safeFormatDate(licenseKeyCheckResult.expiration)}</b>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
{#if licenseKeyCheckResult.isExpired}
|
||||
<div class="mt-2">
|
||||
<FormStyledButton
|
||||
value={_t('settings.other.licenseKey.checkForNew', {
|
||||
defaultMessage: 'Check for new license key',
|
||||
})}
|
||||
skipWidth
|
||||
on:click={async () => {
|
||||
licenseKeyCheckResult = await apiCall('config/get-new-license', { oldLicenseKey: licenseKey });
|
||||
if (licenseKeyCheckResult.licenseKey) {
|
||||
apiCall('config/update-settings', { 'other.licenseKey': licenseKeyCheckResult.licenseKey });
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
||||
64
packages/web/src/settings/OtherSettings.svelte
Normal file
64
packages/web/src/settings/OtherSettings.svelte
Normal file
@@ -0,0 +1,64 @@
|
||||
<script lang="ts">
|
||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
||||
import FormSelectField from "../forms/FormSelectField.svelte";
|
||||
import FormTextField from "../forms/FormTextField.svelte";
|
||||
import { _t } from "../translations";
|
||||
import { isProApp } from "../utility/proTools";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="heading">{_t('settings.other', { defaultMessage: 'Other' })}</div>
|
||||
|
||||
<FormTextField
|
||||
name="other.gistCreateToken"
|
||||
label={_t('settings.other.gistCreateToken', { defaultMessage: 'API token for creating error gists' })}
|
||||
defaultValue=""
|
||||
/>
|
||||
|
||||
<FormSelectField
|
||||
label={_t('settings.other.autoUpdateApplication', { defaultMessage: 'Auto update application' })}
|
||||
name="app.autoUpdateMode"
|
||||
isNative
|
||||
defaultValue=""
|
||||
options={[
|
||||
{
|
||||
value: 'skip',
|
||||
label: _t('settings.other.autoUpdateApplication.skip', {
|
||||
defaultMessage: 'Do not check for new versions',
|
||||
}),
|
||||
},
|
||||
{
|
||||
value: '',
|
||||
label: _t('settings.other.autoUpdateApplication.check', { defaultMessage: 'Check for new versions' }),
|
||||
},
|
||||
{
|
||||
value: 'download',
|
||||
label: _t('settings.other.autoUpdateApplication.download', {
|
||||
defaultMessage: 'Check and download new versions',
|
||||
}),
|
||||
},
|
||||
]}
|
||||
/>
|
||||
|
||||
{#if isProApp()}
|
||||
<FormCheckboxField
|
||||
name="ai.allowSendModels"
|
||||
label={_t('settings.other.ai.allowSendModels', {
|
||||
defaultMessage: 'Allow to send DB models and query snippets to AI service',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
||||
100
packages/web/src/settings/SQLEditorSettings.svelte
Normal file
100
packages/web/src/settings/SQLEditorSettings.svelte
Normal file
@@ -0,0 +1,100 @@
|
||||
<script lang="ts">
|
||||
import CheckboxField from "../forms/CheckboxField.svelte";
|
||||
import FormCheckboxField from "../forms/FormCheckboxField.svelte";
|
||||
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
|
||||
import FormSelectField from "../forms/FormSelectField.svelte";
|
||||
import FormTextField from "../forms/FormTextField.svelte";
|
||||
import SelectField from "../forms/SelectField.svelte";
|
||||
import { EDITOR_KEYBINDINGS_MODES } from "../query/AceEditor.svelte";
|
||||
import { currentEditorKeybindigMode, currentEditorWrapEnabled } from "../stores";
|
||||
import { _t } from "../translations";
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="heading">{_t('settings.sqlEditor', { defaultMessage: 'SQL editor' })}</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="col-3">
|
||||
<FormSelectField
|
||||
label={_t('settings.sqlEditor.sqlCommandsCase', { defaultMessage: 'SQL commands case' })}
|
||||
name="sqlEditor.sqlCommandsCase"
|
||||
isNative
|
||||
defaultValue="upperCase"
|
||||
options={[
|
||||
{ value: 'upperCase', label: 'UPPER CASE' },
|
||||
{ value: 'lowerCase', label: 'lower case' },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.editor.keybinds', { defaultMessage: 'Editor keybinds' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
defaultValue="default"
|
||||
options={EDITOR_KEYBINDINGS_MODES.map(mode => ({ label: mode.label, value: mode.value }))}
|
||||
value={$currentEditorKeybindigMode}
|
||||
on:change={e => ($currentEditorKeybindigMode = e.detail)}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.editor.wordWrap', { defaultMessage: 'Enable word wrap' })}
|
||||
type="combo"
|
||||
>
|
||||
<CheckboxField
|
||||
checked={$currentEditorWrapEnabled}
|
||||
on:change={e => ($currentEditorWrapEnabled = e.target.checked)}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<FormTextField
|
||||
name="sqlEditor.limitRows"
|
||||
label={_t('settings.sqlEditor.limitRows', { defaultMessage: 'Return only N rows from query' })}
|
||||
placeholder={_t('settings.sqlEditor.limitRowsPlaceholder', { defaultMessage: '(No rows limit)' })}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.showTableAliasesInCodeCompletion"
|
||||
label={_t('settings.sqlEditor.showTableAliasesInCodeCompletion', {
|
||||
defaultMessage: 'Show table aliases in code completion',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.disableSplitByEmptyLine"
|
||||
label={_t('settings.sqlEditor.disableSplitByEmptyLine', { defaultMessage: 'Disable split by empty line' })}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.disableExecuteCurrentLine"
|
||||
label={_t('settings.sqlEditor.disableExecuteCurrentLine', {
|
||||
defaultMessage: 'Disable current line execution (Execute current)',
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.hideColumnsPanel"
|
||||
label={_t('settings.sqlEditor.hideColumnsPanel', { defaultMessage: 'Hide Columns/Filters panel by default' })}
|
||||
defaultValue={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
||||
@@ -197,6 +197,7 @@ ORDER BY
|
||||
{ value: 'it', label: 'Italiano' },
|
||||
{ value: 'pt', label: 'Português (Brasil)' },
|
||||
{ value: 'sk', label: 'Slovenčina' },
|
||||
{ value: 'ja', label: '日本語' },
|
||||
{ value: 'zh', label: '中文' },
|
||||
]}
|
||||
defaultValue={getSelectedLanguage()}
|
||||
@@ -366,6 +367,13 @@ ORDER BY
|
||||
})}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
<FormCheckboxField
|
||||
name="sqlEditor.hideColumnsPanel"
|
||||
label={_t('settings.sqlEditor.hideColumnsPanel', { defaultMessage: 'Hide Columns/Filters panel by default' })}
|
||||
defaultValue={false}
|
||||
/>
|
||||
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="2">
|
||||
<div class="heading">{_t('settings.connection', { defaultMessage: 'Connection' })}</div>
|
||||
|
||||
164
packages/web/src/settings/ThemeSettings.svelte
Normal file
164
packages/web/src/settings/ThemeSettings.svelte
Normal file
@@ -0,0 +1,164 @@
|
||||
<script lang="ts">
|
||||
import Link from "../elements/Link.svelte";
|
||||
import CheckboxField from "../forms/CheckboxField.svelte";
|
||||
import FormFieldTemplateLarge from "../forms/FormFieldTemplateLarge.svelte";
|
||||
import SelectField from "../forms/SelectField.svelte";
|
||||
import { currentEditorFontSize, currentEditorTheme, currentTheme, extensions, getSystemTheme, selectedWidget, visibleWidgetSideBar } from "../stores";
|
||||
import { _t } from "../translations";
|
||||
import ThemeSkeleton from "./ThemeSkeleton.svelte";
|
||||
import { EDITOR_THEMES, FONT_SIZES } from '../query/AceEditor.svelte';
|
||||
import { closeCurrentModal } from "../modals/modalTools";
|
||||
import TextField from "../forms/TextField.svelte";
|
||||
import FormTextField from "../forms/FormTextField.svelte";
|
||||
import SqlEditor from "../query/SqlEditor.svelte";
|
||||
|
||||
function openThemePlugins() {
|
||||
closeCurrentModal();
|
||||
$selectedWidget = 'plugins';
|
||||
$visibleWidgetSideBar = true;
|
||||
}
|
||||
|
||||
const sqlPreview = `-- example query
|
||||
SELECT
|
||||
MAX(Album.AlbumId) AS max_album,
|
||||
MAX(Album.Title) AS max_title,
|
||||
Artist.ArtistId,
|
||||
'album' AS test_string,
|
||||
123 AS test_number
|
||||
FROM
|
||||
Album
|
||||
INNER JOIN Artist ON Album.ArtistId = Artist.ArtistId
|
||||
GROUP BY
|
||||
Artist.ArtistId
|
||||
ORDER BY
|
||||
Artist.Name ASC
|
||||
`;
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<div class="heading">{_t('settings.appearance', { defaultMessage: 'Application theme' })}</div>
|
||||
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.useSystemTheme', { defaultMessage: 'Use system theme' })}
|
||||
type="checkbox"
|
||||
labelProps={{
|
||||
onClick: () => {
|
||||
if ($currentTheme) {
|
||||
$currentTheme = null;
|
||||
} else {
|
||||
$currentTheme = getSystemTheme();
|
||||
}
|
||||
},
|
||||
}}
|
||||
>
|
||||
<CheckboxField
|
||||
checked={!$currentTheme}
|
||||
on:change={e => {
|
||||
if (e.target['checked']) {
|
||||
$currentTheme = null;
|
||||
} else {
|
||||
$currentTheme = getSystemTheme();
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
|
||||
<div class="themes">
|
||||
{#each $extensions.themes as theme}
|
||||
<ThemeSkeleton {theme} />
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
<div class="m-5">
|
||||
{_t('settings.appearance.moreThemes', { defaultMessage: 'More themes are available as' })}
|
||||
<Link onClick={openThemePlugins}>plugins</Link>
|
||||
<br />
|
||||
{_t('settings.appearance.afterInstalling', {
|
||||
defaultMessage:
|
||||
'After installing theme plugin (try search "theme" in available extensions) new themes will be available here.',
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="heading">{_t('settings.appearance.editorTheme', { defaultMessage: 'Editor theme' })}</div>
|
||||
|
||||
<div class="flex">
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.editorTheme', { defaultMessage: 'Theme' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
notSelected={_t('settings.appearance.editorTheme.default', { defaultMessage: '(use theme default)' })}
|
||||
options={EDITOR_THEMES.map(theme => ({ label: theme, value: theme }))}
|
||||
value={$currentEditorTheme}
|
||||
on:change={e => ($currentEditorTheme = e.detail)}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.fontSize', { defaultMessage: 'Font size' })}
|
||||
type="combo"
|
||||
>
|
||||
<SelectField
|
||||
isNative
|
||||
notSelected="(default)"
|
||||
options={FONT_SIZES}
|
||||
value={FONT_SIZES.find(x => x.value == $currentEditorFontSize) ? $currentEditorFontSize : 'custom'}
|
||||
on:change={e => ($currentEditorFontSize = e.detail)}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormFieldTemplateLarge
|
||||
label={_t('settings.appearance.customSize', { defaultMessage: 'Custom size' })}
|
||||
type="text"
|
||||
>
|
||||
<TextField
|
||||
value={$currentEditorFontSize == 'custom' ? '' : $currentEditorFontSize}
|
||||
on:change={e => ($currentEditorFontSize = e.target['value'])}
|
||||
disabled={!!FONT_SIZES.find(x => x.value == $currentEditorFontSize) &&
|
||||
$currentEditorFontSize != 'custom'}
|
||||
/>
|
||||
</FormFieldTemplateLarge>
|
||||
</div>
|
||||
|
||||
<div class="col-3">
|
||||
<FormTextField
|
||||
name="editor.fontFamily"
|
||||
label={_t('settings.appearance.fontFamily', { defaultMessage: 'Editor font family' })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="editor">
|
||||
<SqlEditor value={sqlPreview} readOnly />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.heading {
|
||||
font-size: 20px;
|
||||
margin: 5px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
.themes {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
.editor {
|
||||
position: relative;
|
||||
height: 250px;
|
||||
width: 400px;
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
margin-bottom: var(--dim-large-form-margin);
|
||||
}
|
||||
</style>
|
||||
@@ -60,7 +60,11 @@
|
||||
|
||||
<FormProvider>
|
||||
<ModalBase {...$$restProps}>
|
||||
<svelte:fragment slot="header">{constraintInfo ? _t('foreignKeyEditor.editForeignKey', { defaultMessage: 'Edit foreign key' }) : _t('foreignKeyEditor.addForeignKey', { defaultMessage: 'Add foreign key' })}</svelte:fragment>
|
||||
<svelte:fragment slot="header"
|
||||
>{constraintInfo
|
||||
? _t('foreignKeyEditor.editForeignKey', { defaultMessage: 'Edit foreign key' })
|
||||
: _t('foreignKeyEditor.addForeignKey', { defaultMessage: 'Add foreign key' })}</svelte:fragment
|
||||
>
|
||||
|
||||
<div class="largeFormMarker">
|
||||
<div class="row">
|
||||
@@ -92,6 +96,19 @@
|
||||
const name = fullNameFromString(e.detail);
|
||||
refTableName = name.pureName;
|
||||
refSchemaName = name.schemaName;
|
||||
|
||||
if (!columns?.find(x => x.columnName)) {
|
||||
const refTable = dbInfo?.tables?.find(
|
||||
x => x.pureName == refTableName && x.schemaName == refSchemaName
|
||||
);
|
||||
if (refTable?.primaryKey) {
|
||||
columns = refTable.primaryKey.columns.map(col => ({
|
||||
refColumnName: col.columnName,
|
||||
}));
|
||||
} else {
|
||||
columns = [];
|
||||
}
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
@@ -135,7 +152,8 @@
|
||||
{_t('foreignKeyEditor.baseColumn', { defaultMessage: 'Base column - ' })}{tableInfo.pureName}
|
||||
</div>
|
||||
<div class="col-5 ml-1">
|
||||
{_t('foreignKeyEditor.refColumn', { defaultMessage: 'Ref column - ' })}{refTableName || _t('foreignKeyEditor.tableNotSet', { defaultMessage: '(table not set)' })}
|
||||
{_t('foreignKeyEditor.refColumn', { defaultMessage: 'Ref column - ' })}{refTableName ||
|
||||
_t('foreignKeyEditor.tableNotSet', { defaultMessage: '(table not set)' })}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -217,7 +235,11 @@
|
||||
}}
|
||||
/>
|
||||
|
||||
<FormStyledButton type="button" value={_t('common.close', { defaultMessage: 'Close' })} on:click={closeCurrentModal} />
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
value={_t('common.close', { defaultMessage: 'Close' })}
|
||||
on:click={closeCurrentModal}
|
||||
/>
|
||||
{#if constraintInfo}
|
||||
<FormStyledButton
|
||||
type="button"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tableEditor.addColumn',
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor'}),
|
||||
name: __t('tableEditor.addColumn', { defaultMessage: 'Add column'}),
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor' }),
|
||||
name: __t('tableEditor.addColumn', { defaultMessage: 'Add column' }),
|
||||
icon: 'icon add-column',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -14,8 +14,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tableEditor.addPrimaryKey',
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor'}),
|
||||
name: __t('tableEditor.addPrimaryKey', { defaultMessage: 'Add primary key'}),
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor' }),
|
||||
name: __t('tableEditor.addPrimaryKey', { defaultMessage: 'Add primary key' }),
|
||||
icon: 'icon add-key',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -25,8 +25,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tableEditor.addForeignKey',
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor'}),
|
||||
name: __t('tableEditor.addForeignKey', { defaultMessage: 'Add foreign key'}),
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor' }),
|
||||
name: __t('tableEditor.addForeignKey', { defaultMessage: 'Add foreign key' }),
|
||||
icon: 'icon add-key',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -36,8 +36,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tableEditor.addIndex',
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor'}),
|
||||
name: __t('tableEditor.addIndex', { defaultMessage: 'Add index'}),
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor' }),
|
||||
name: __t('tableEditor.addIndex', { defaultMessage: 'Add index' }),
|
||||
icon: 'icon add-key',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -47,8 +47,8 @@
|
||||
|
||||
registerCommand({
|
||||
id: 'tableEditor.addUnique',
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor'}),
|
||||
name: __t('tableEditor.addUnique', { defaultMessage: 'Add unique'}),
|
||||
category: __t('tableEditor', { defaultMessage: 'Table editor' }),
|
||||
name: __t('tableEditor.addUnique', { defaultMessage: 'Add unique' }),
|
||||
icon: 'icon add-key',
|
||||
toolbar: true,
|
||||
isRelatedToTab: true,
|
||||
@@ -188,7 +188,10 @@
|
||||
|
||||
<ObjectListControl
|
||||
collection={columns?.map((x, index) => ({ ...x, ordinal: index + 1 }))}
|
||||
title={_t('tableEditor.columns', { defaultMessage: 'Columns ({columnCount})', values: { columnCount: columns?.length || 0 } })}
|
||||
title={_t('tableEditor.columns', {
|
||||
defaultMessage: 'Columns ({columnCount})',
|
||||
values: { columnCount: columns?.length || 0 },
|
||||
})}
|
||||
emptyMessage={_t('tableEditor.nocolumnsdefined', { defaultMessage: 'No columns defined' })}
|
||||
clickable
|
||||
on:clickrow={e => showModal(ColumnEditorModal, { columnInfo: e.detail, tableInfo, setTableInfo, driver })}
|
||||
@@ -217,9 +220,7 @@
|
||||
text: _t('tableEditor.copydefinitions', { defaultMessage: 'Copy definitions' }),
|
||||
icon: 'icon copy',
|
||||
onClick: selected => {
|
||||
const names = selected
|
||||
.map(x => `${x.columnName} ${x.dataType}${x.notNull ? ' NOT NULL' : ''}`)
|
||||
.join(',\n');
|
||||
const names = selected.map(x => `${x.columnName} ${x.dataType}${x.notNull ? ' NOT NULL' : ''}`).join(',\n');
|
||||
navigator.clipboard.writeText(names);
|
||||
},
|
||||
},
|
||||
@@ -288,9 +289,21 @@
|
||||
: null,
|
||||
]}
|
||||
>
|
||||
<svelte:fragment slot="0" let:row>{row?.notNull ? _t('tableEditor.notnull', { defaultMessage: 'NOT NULL' }) : _t('tableEditor.null', { defaultMessage: 'NULL' })}</svelte:fragment>
|
||||
<svelte:fragment slot="1" let:row>{row?.isSparse ? _t('tableEditor.yes', { defaultMessage: 'YES' }) : _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment>
|
||||
<svelte:fragment slot="2" let:row>{row?.isPersisted ? _t('tableEditor.yes', { defaultMessage: 'YES' }) : _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment>
|
||||
<svelte:fragment slot="0" let:row
|
||||
>{row?.notNull
|
||||
? _t('tableEditor.notnull', { defaultMessage: 'NOT NULL' })
|
||||
: _t('tableEditor.null', { defaultMessage: 'NULL' })}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="1" let:row
|
||||
>{row?.isSparse
|
||||
? _t('tableEditor.yes', { defaultMessage: 'YES' })
|
||||
: _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="2" let:row
|
||||
>{row?.isPersisted
|
||||
? _t('tableEditor.yes', { defaultMessage: 'YES' })
|
||||
: _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="3" let:row
|
||||
><Link
|
||||
onClick={e => {
|
||||
@@ -299,8 +312,16 @@
|
||||
}}>{_t('tableEditor.remove', { defaultMessage: 'Remove' })}</Link
|
||||
></svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="4" let:row>{row?.isUnsigned ? _t('tableEditor.yes', { defaultMessage: 'YES' }) : _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment>
|
||||
<svelte:fragment slot="5" let:row>{row?.isZerofill ? _t('tableEditor.yes', { defaultMessage: 'YES' }) : _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment>
|
||||
<svelte:fragment slot="4" let:row
|
||||
>{row?.isUnsigned
|
||||
? _t('tableEditor.yes', { defaultMessage: 'YES' })
|
||||
: _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="5" let:row
|
||||
>{row?.isZerofill
|
||||
? _t('tableEditor.yes', { defaultMessage: 'YES' })
|
||||
: _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="name" let:row><ColumnLabel {...row} forceIcon /></svelte:fragment>
|
||||
</ObjectListControl>
|
||||
|
||||
@@ -321,7 +342,10 @@
|
||||
<ObjectListControl
|
||||
collection={indexes}
|
||||
onAddNew={isWritable && columns?.length > 0 ? addIndex : null}
|
||||
title={_t('tableEditor.indexes', { defaultMessage: 'Indexes ({indexCount})', values: { indexCount: indexes?.length || 0 } })}
|
||||
title={_t('tableEditor.indexes', {
|
||||
defaultMessage: 'Indexes ({indexCount})',
|
||||
values: { indexCount: indexes?.length || 0 },
|
||||
})}
|
||||
emptyMessage={isWritable ? _t('tableEditor.noindexdefined', { defaultMessage: 'No index defined' }) : null}
|
||||
clickable
|
||||
on:clickrow={e => showModal(IndexEditorModal, { constraintInfo: e.detail, tableInfo, setTableInfo, driver })}
|
||||
@@ -348,7 +372,11 @@
|
||||
>
|
||||
<svelte:fragment slot="name" let:row><ConstraintLabel {...row} /></svelte:fragment>
|
||||
<svelte:fragment slot="0" let:row>{row?.columns.map(x => x.columnName).join(', ')}</svelte:fragment>
|
||||
<svelte:fragment slot="1" let:row>{row?.isUnique ? _t('tableEditor.yes', { defaultMessage: 'YES' }) : _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment>
|
||||
<svelte:fragment slot="1" let:row
|
||||
>{row?.isUnique
|
||||
? _t('tableEditor.yes', { defaultMessage: 'YES' })
|
||||
: _t('tableEditor.no', { defaultMessage: 'NO' })}</svelte:fragment
|
||||
>
|
||||
<svelte:fragment slot="2" let:row
|
||||
><Link
|
||||
onClick={e => {
|
||||
@@ -364,7 +392,10 @@
|
||||
<ObjectListControl
|
||||
collection={uniques}
|
||||
onAddNew={isWritable && columns?.length > 0 ? addUnique : null}
|
||||
title={_t('tableEditor.uniqueConstraints', { defaultMessage: 'Unique constraints ({constraintCount})', values: { constraintCount: uniques?.length || 0 } })}
|
||||
title={_t('tableEditor.uniqueConstraints', {
|
||||
defaultMessage: 'Unique constraints ({constraintCount})',
|
||||
values: { constraintCount: uniques?.length || 0 },
|
||||
})}
|
||||
emptyMessage={isWritable ? _t('tableEditor.nouniquedefined', { defaultMessage: 'No unique defined' }) : null}
|
||||
clickable
|
||||
on:clickrow={e => showModal(UniqueEditorModal, { constraintInfo: e.detail, tableInfo, setTableInfo })}
|
||||
@@ -401,13 +432,21 @@
|
||||
<ForeignKeyObjectListControl
|
||||
collection={foreignKeys}
|
||||
onAddNew={isWritable && columns?.length > 0 ? addForeignKey : null}
|
||||
title={_t('tableEditor.foreignKeys', { defaultMessage: 'Foreign keys ({foreignKeyCount})', values: { foreignKeyCount: foreignKeys?.length || 0 } })}
|
||||
emptyMessage={isWritable ? _t('tableEditor.noforeignkeydefined', { defaultMessage: 'No foreign key defined' }) : null}
|
||||
title={_t('tableEditor.foreignKeys', {
|
||||
defaultMessage: 'Foreign keys ({foreignKeyCount})',
|
||||
values: { foreignKeyCount: foreignKeys?.length || 0 },
|
||||
})}
|
||||
emptyMessage={isWritable
|
||||
? _t('tableEditor.noforeignkeydefined', { defaultMessage: 'No foreign key defined' })
|
||||
: null}
|
||||
clickable
|
||||
onRemove={row => setTableInfo(tbl => editorDeleteConstraint(tbl, row))}
|
||||
on:clickrow={e => showModal(ForeignKeyEditorModal, { constraintInfo: e.detail, tableInfo, setTableInfo, dbInfo })}
|
||||
/>
|
||||
<ForeignKeyObjectListControl collection={dependencies} title={_t('tableEditor.dependencies', { defaultMessage: 'Dependencies' })} />
|
||||
<ForeignKeyObjectListControl
|
||||
collection={dependencies}
|
||||
title={_t('tableEditor.dependencies', { defaultMessage: 'Dependencies' })}
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
createQuickExportHandlerRef,
|
||||
registerQuickExportHandler,
|
||||
} from '../buttons/ToolStripExportButton.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
let loadedRows = [];
|
||||
let loadedAll = false;
|
||||
@@ -191,8 +192,8 @@
|
||||
<SelectField
|
||||
isNative
|
||||
options={[
|
||||
{ label: 'Recent logs', value: 'recent' },
|
||||
{ label: 'Choose date', value: 'date' },
|
||||
{ label: _t('logs.recentLogs', { defaultMessage: 'Recent logs' }), value: 'recent' },
|
||||
{ label: _t('logs.chooseDate', { defaultMessage: 'Choose date' }), value: 'date' },
|
||||
]}
|
||||
value={mode}
|
||||
on:change={e => {
|
||||
@@ -202,7 +203,7 @@
|
||||
/>
|
||||
|
||||
{#if mode === 'recent'}
|
||||
<div class="filter-label ml-2">Auto-scroll</div>
|
||||
<div class="filter-label ml-2">{_t('logs.autoScroll', { defaultMessage: 'Auto-scroll' })}</div>
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={autoScroll}
|
||||
@@ -213,7 +214,7 @@
|
||||
{/if}
|
||||
|
||||
{#if mode === 'date'}
|
||||
<div class="filter-label">Date:</div>
|
||||
<div class="filter-label">{_t('logs.date', { defaultMessage: 'Date:' })}</div>
|
||||
<DateRangeSelector
|
||||
onChange={value => {
|
||||
dateFilter = value;
|
||||
@@ -225,12 +226,12 @@
|
||||
data-testid="AdminAuditLogTab_addFilter"
|
||||
icon="icon filter"
|
||||
menu={[
|
||||
{ text: 'Connection ID', onClick: () => filterBy('conid') },
|
||||
{ text: 'Database', onClick: () => filterBy('database') },
|
||||
{ text: 'Engine', onClick: () => filterBy('engine') },
|
||||
{ text: 'Message code', onClick: () => filterBy('msgcode') },
|
||||
{ text: 'Caller', onClick: () => filterBy('caller') },
|
||||
{ text: 'Name', onClick: () => filterBy('name') },
|
||||
{ text: _t('logs.connectionId', { defaultMessage: 'Connection ID' }), onClick: () => filterBy('conid') },
|
||||
{ text: _t('logs.database', { defaultMessage: 'Database' }), onClick: () => filterBy('database') },
|
||||
{ text: _t('logs.engine', { defaultMessage: 'Engine' }), onClick: () => filterBy('engine') },
|
||||
{ text: _t('logs.messageCode', { defaultMessage: 'Message code' }), onClick: () => filterBy('msgcode') },
|
||||
{ text: _t('logs.caller', { defaultMessage: 'Caller' }), onClick: () => filterBy('caller') },
|
||||
{ text: _t('logs.name', { defaultMessage: 'Name' }), onClick: () => filterBy('name') },
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
@@ -259,15 +260,15 @@
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="width:80px">Date</th>
|
||||
<th>Time</th>
|
||||
<th>Code</th>
|
||||
<th>Message</th>
|
||||
<th>Connection</th>
|
||||
<th>Database</th>
|
||||
<th>Engine</th>
|
||||
<th>Caller</th>
|
||||
<th>Name</th>
|
||||
<th style="width:80px">{_t('logs.dateTab', { defaultMessage: 'Date' })}</th>
|
||||
<th>{_t('logs.timeTab', { defaultMessage: 'Time' })}</th>
|
||||
<th>{_t('logs.codeTab', { defaultMessage: 'Code' })}</th>
|
||||
<th>{_t('logs.messageTab', { defaultMessage: 'Message' })}</th>
|
||||
<th>{_t('logs.connectionTab', { defaultMessage: 'Connection' })}</th>
|
||||
<th>{_t('logs.databaseTab', { defaultMessage: 'Database' })}</th>
|
||||
<th>{_t('logs.engineTab', { defaultMessage: 'Engine' })}</th>
|
||||
<th>{_t('logs.callerTab', { defaultMessage: 'Caller' })}</th>
|
||||
<th>{_t('logs.nameTab', { defaultMessage: 'Name' })}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -299,14 +300,14 @@
|
||||
<TabControl
|
||||
isInline
|
||||
tabs={_.compact([
|
||||
{ label: 'Details', slot: 1 },
|
||||
{ label: _t('logs.details', { defaultMessage: 'Details' }), slot: 1 },
|
||||
{ label: 'JSON', slot: 2 },
|
||||
])}
|
||||
>
|
||||
<svelte:fragment slot="1">
|
||||
<div class="details-wrap">
|
||||
<div class="row">
|
||||
<div>Message code:</div>
|
||||
<div>{_t('logs.messageCode', { defaultMessage: 'Message code:' })}</div>
|
||||
{#if mode == 'date'}
|
||||
<Link onClick={() => doSetFilter('msgcode', [row.msgcode])}>{row.msgcode || 'N/A'}</Link>
|
||||
{:else}
|
||||
@@ -314,15 +315,15 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>Message:</div>
|
||||
<div>{_t('logs.message', { defaultMessage: 'Message:' })}</div>
|
||||
{row.msg}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>Time:</div>
|
||||
<div>{_t('logs.time', { defaultMessage: 'Time:' })}</div>
|
||||
<b>{row.time ? format(new Date(parseInt(row.time)), 'yyyy-MM-dd HH:mm:ss') : ''}</b>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>Caller:</div>
|
||||
<div>{_t('logs.caller', { defaultMessage: 'Caller:' })}</div>
|
||||
{#if mode == 'date'}
|
||||
<Link onClick={() => doSetFilter('caller', [row.caller])}>{row.caller || 'N/A'}</Link>
|
||||
{:else}
|
||||
@@ -330,7 +331,7 @@
|
||||
{/if}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div>Name:</div>
|
||||
<div>{_t('logs.name', { defaultMessage: 'Name:' })}</div>
|
||||
{#if mode == 'date'}
|
||||
<Link onClick={() => doSetFilter('name', [row.name])}>{row.name || 'N/A'}</Link>
|
||||
{:else}
|
||||
@@ -339,7 +340,7 @@
|
||||
</div>
|
||||
{#if row.conid}
|
||||
<div class="row">
|
||||
<div>Connection ID:</div>
|
||||
<div>{_t('logs.connectionId', { defaultMessage: 'Connection ID:' })}</div>
|
||||
{#if mode == 'date'}
|
||||
<Link onClick={() => doSetFilter('conid', [row.conid])}
|
||||
>{formatPossibleUuid(row.conid)}</Link
|
||||
@@ -351,7 +352,7 @@
|
||||
{/if}
|
||||
{#if row.database}
|
||||
<div class="row">
|
||||
<div>Database:</div>
|
||||
<div>{_t('logs.database', { defaultMessage: 'Database:' })}</div>
|
||||
{#if mode == 'date'}
|
||||
<Link onClick={() => doSetFilter('database', [row.database])}>{row.database}</Link>
|
||||
{:else}
|
||||
@@ -361,7 +362,7 @@
|
||||
{/if}
|
||||
{#if row.engine}
|
||||
<div class="row">
|
||||
<div>Engine:</div>
|
||||
<div>{_t('logs.engine', { defaultMessage: 'Engine:' })}</div>
|
||||
{#if mode == 'date'}
|
||||
<Link onClick={() => doSetFilter('engine', [row.engine])}>{row.engine}</Link>
|
||||
{:else}
|
||||
@@ -381,13 +382,13 @@
|
||||
{/each}
|
||||
{#if !loadedRows?.length && mode === 'date'}
|
||||
<tr>
|
||||
<td colspan="6">No data for selected date</td>
|
||||
<td colspan="6">{_t('logs.noDataForSelectedDate', { defaultMessage: "No data for selected date" })}</td>
|
||||
</tr>
|
||||
{/if}
|
||||
{#if !loadedAll && mode === 'date'}
|
||||
{#key loadedRows}
|
||||
<tr>
|
||||
<td colspan="6" bind:this={domLoadNext}>Loading next rows... </td>
|
||||
<td colspan="6" bind:this={domLoadNext}>{_t('logs.loadingNextRows', { defaultMessage: "Loading next rows..." })}</td>
|
||||
</tr>
|
||||
{/key}
|
||||
{/if}
|
||||
@@ -402,7 +403,7 @@
|
||||
data-testid="AdminAuditLogTab_refreshButton"
|
||||
on:click={() => {
|
||||
reloadData();
|
||||
}}>Refresh</ToolStripButton
|
||||
}}>{_t('logs.refresh', { defaultMessage: 'Refresh' })}</ToolStripButton
|
||||
>
|
||||
<ToolStripExportButton {quickExportHandlerRef} />
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import CommandModal from '../modals/CommandModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import { commandsCustomized } from '../stores';
|
||||
import { _tval } from '../translations';
|
||||
|
||||
$: commandList = _.sortBy(_.values($commandsCustomized), ['category', 'name']);
|
||||
let filter;
|
||||
@@ -27,7 +28,9 @@
|
||||
<div class="table-wrapper">
|
||||
<TableControl
|
||||
clickable
|
||||
rows={commandList.filter(cmd => filterName(filter, cmd['category'], cmd['name'], cmd['keyText'], cmd['id']))}
|
||||
rows={commandList.filter(cmd =>
|
||||
filterName(filter, _tval(cmd['category']), _tval(cmd['name']), _tval(cmd['keyText']), cmd['id'])
|
||||
)}
|
||||
columns={[
|
||||
{ header: 'Category', fieldName: 'category' },
|
||||
{ header: 'Name', fieldName: 'name' },
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
folder: 'diagrams',
|
||||
format: 'json',
|
||||
fileExtension: 'diagram',
|
||||
defaultTeamFolder: true,
|
||||
|
||||
undoRedo: true,
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
fileExtension: 'impexp',
|
||||
|
||||
// undoRedo: true,
|
||||
defaultTeamFolder: true,
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -53,6 +54,7 @@
|
||||
import uuidv1 from 'uuid/v1';
|
||||
import { tick } from 'svelte';
|
||||
import { showSnackbarError } from '../utility/snackbar';
|
||||
import { _t } from '../translations';
|
||||
|
||||
let busy = false;
|
||||
let executeNumber = 0;
|
||||
@@ -288,21 +290,21 @@
|
||||
/>
|
||||
|
||||
{#if busy}
|
||||
<LoadingInfo wrapper message="Processing import/export ..." />
|
||||
<LoadingInfo wrapper message={_t('importExport.processingImportExport', { defaultMessage: "Processing import/export ..." })} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<svelte:fragment slot="2">
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem
|
||||
title="Output files"
|
||||
title={_t('importExport.outputFiles', { defaultMessage: "Output files" })}
|
||||
name="output"
|
||||
height="20%"
|
||||
data-testid="ImportExportTab_outputFiles"
|
||||
>
|
||||
<RunnerOutputFiles {runnerId} {executeNumber} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Messages" name="messages">
|
||||
<WidgetColumnBarItem title={_t('importExport.messages', { defaultMessage: "Messages" })} name="messages">
|
||||
<SocketMessageView
|
||||
eventName={runnerId ? `runner-info-${runnerId}` : null}
|
||||
{executeNumber}
|
||||
@@ -311,16 +313,16 @@
|
||||
/>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem
|
||||
title="Preview"
|
||||
title={_t('importExport.preview', { defaultMessage: "Preview" })}
|
||||
name="preview"
|
||||
skip={!$previewReaderStore}
|
||||
data-testid="ImportExportTab_preview"
|
||||
>
|
||||
<PreviewDataGrid reader={$previewReaderStore} />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Advanced configuration" name="config" collapsed>
|
||||
<FormTextField label="Schedule" name="schedule" />
|
||||
<FormTextField label="Start variable index" name="startVariableIndex" />
|
||||
<WidgetColumnBarItem title={_t('importExport.advancedConfiguration', { defaultMessage: "Advanced configuration" })} name="config" collapsed>
|
||||
<FormTextField label={_t('importExport.schedule', { defaultMessage: "Schedule" })} name="schedule" />
|
||||
<FormTextField label={_t('importExport.startVariableIndex', { defaultMessage: "Start variable index" })} name="startVariableIndex" />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
</svelte:fragment>
|
||||
@@ -329,15 +331,15 @@
|
||||
<svelte:fragment slot="toolstrip">
|
||||
{#if busy}
|
||||
<ToolStripButton icon="icon stop" on:click={handleCancel} data-testid="ImportExportTab_stopButton"
|
||||
>Stop</ToolStripButton
|
||||
>{_t('importExport.stop', { defaultMessage: "Stop" })}</ToolStripButton
|
||||
>
|
||||
{:else}
|
||||
<ToolStripButton on:click={handleExecute} icon="icon run" data-testid="ImportExportTab_executeButton"
|
||||
>Run</ToolStripButton
|
||||
>{_t('importExport.run', { defaultMessage: "Run" })}</ToolStripButton
|
||||
>
|
||||
{/if}
|
||||
<ToolStripButton icon="img shell" on:click={handleGenerateScript} data-testid="ImportExportTab_generateScriptButton"
|
||||
>Generate script</ToolStripButton
|
||||
>{_t('importExport.generateScript', { defaultMessage: "Generate script" })}</ToolStripButton
|
||||
>
|
||||
<ToolStripSaveButton idPrefix="impexp" />
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -52,6 +52,7 @@
|
||||
findReplace: true,
|
||||
executeAdditionalCondition: () => getCurrentEditor()?.hasConnection() && hasPermission('dbops/query'),
|
||||
copyPaste: true,
|
||||
defaultTeamFolder: true,
|
||||
});
|
||||
registerCommand({
|
||||
id: 'query.executeCurrent',
|
||||
@@ -123,8 +124,9 @@
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import { getContext, onDestroy, onMount, tick } from 'svelte';
|
||||
import { getContext, onDestroy, onMount, setContext, tick } from 'svelte';
|
||||
import sqlFormatter from 'sql-formatter';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
|
||||
import SqlEditor from '../query/SqlEditor.svelte';
|
||||
@@ -166,6 +168,7 @@
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
import QueryAiAssistant from '../ai/QueryAiAssistant.svelte';
|
||||
import { getCurrentSettings } from '../stores';
|
||||
|
||||
export let tabid;
|
||||
export let conid;
|
||||
@@ -175,6 +178,9 @@
|
||||
|
||||
export const activator = createActivator('QueryTab', false);
|
||||
|
||||
const collapsedLeftColumnStore = writable(getCurrentSettings()['sqlEditor.hideColumnsPanel'] ?? false);
|
||||
setContext('collapsedLeftColumnStore', collapsedLeftColumnStore);
|
||||
|
||||
const QUERY_PARAMETER_STYLES = [
|
||||
{
|
||||
value: '',
|
||||
|
||||
121
packages/web/src/tabs/SettingsTab.svelte
Normal file
121
packages/web/src/tabs/SettingsTab.svelte
Normal file
@@ -0,0 +1,121 @@
|
||||
<script lang="ts" context="module">
|
||||
export const matchingProps = [];
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import SettingsMenuControl from "../elements/SettingsMenuControl.svelte";
|
||||
import GeneralSettings from "../settings/GeneralSettings.svelte";
|
||||
import SettingsFormProvider from "../forms/SettingsFormProvider.svelte";
|
||||
import ConnectionSettings from "../settings/ConnectionSettings.svelte";
|
||||
import ThemeSettings from "../settings/ThemeSettings.svelte";
|
||||
import DefaultActionsSettings from "../settings/DefaultActionsSettings.svelte";
|
||||
import BehaviourSettings from "../settings/BehaviourSettings.svelte";
|
||||
import ExternalToolsSettings from "../settings/ExternalToolsSettings.svelte";
|
||||
import OtherSettings from "../settings/OtherSettings.svelte";
|
||||
import LicenseSettings from "../settings/LicenseSettings.svelte";
|
||||
import { isProApp } from "../utility/proTools";
|
||||
import { _t } from "../translations";
|
||||
import CommandListTab from "./CommandListTab.svelte";
|
||||
import DataGridSettings from "../settings/DataGridSettings.svelte";
|
||||
import SQLEditorSettings from "../settings/SQLEditorSettings.svelte";
|
||||
import AiSettingsTab from "../settings/AiSettingsTab.svelte";
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
label: _t('settings.general', { defaultMessage: 'General' }),
|
||||
identifier: 'general',
|
||||
component: GeneralSettings,
|
||||
props: {},
|
||||
testid: 'settings-general',
|
||||
},
|
||||
{
|
||||
label: _t('settings.connection', { defaultMessage: 'Connection' }),
|
||||
identifier: 'connection',
|
||||
component: ConnectionSettings,
|
||||
props: {},
|
||||
testid: 'settings-connection',
|
||||
},
|
||||
{
|
||||
label: _t('settings.dataGrid.title', { defaultMessage: 'Data grid' }),
|
||||
identifier: 'data-grid',
|
||||
component: DataGridSettings,
|
||||
props: {},
|
||||
testid: 'settings-data-grid',
|
||||
},
|
||||
{
|
||||
label: _t('settings.sqlEditor.title', { defaultMessage: 'SQL Editor' }),
|
||||
identifier: 'sql-editor',
|
||||
component: SQLEditorSettings,
|
||||
props: {},
|
||||
testid: 'settings-sql-editor',
|
||||
},
|
||||
{
|
||||
label: _t('settings.theme', { defaultMessage: 'Themes' }),
|
||||
identifier: 'theme',
|
||||
component: ThemeSettings,
|
||||
props: {},
|
||||
testid: 'settings-themes',
|
||||
},
|
||||
{
|
||||
label: _t('settings.defaultActions', { defaultMessage: 'Default Actions' }),
|
||||
identifier: 'default-actions',
|
||||
component: DefaultActionsSettings,
|
||||
props: {},
|
||||
testid: 'settings-default-actions',
|
||||
},
|
||||
{
|
||||
label: _t('settings.behaviour', { defaultMessage: 'Behaviour' }),
|
||||
identifier: 'behaviour',
|
||||
component: BehaviourSettings,
|
||||
props: {},
|
||||
testid: 'settings-behaviour',
|
||||
},
|
||||
{
|
||||
label: _t('settings.externalTools', { defaultMessage: 'External Tools' }),
|
||||
identifier: 'external-tools',
|
||||
component: ExternalToolsSettings,
|
||||
props: {},
|
||||
testid: 'settings-external-tools',
|
||||
},
|
||||
{
|
||||
label: _t('command.settings.shortcuts', { defaultMessage: 'Keyboard shortcuts' }),
|
||||
identifier: 'shortcuts',
|
||||
component: CommandListTab,
|
||||
props: {},
|
||||
testid: 'settings-shortcuts',
|
||||
},
|
||||
isProApp() && {
|
||||
label: _t('settings.license', { defaultMessage: 'License' }),
|
||||
identifier: 'license',
|
||||
component: LicenseSettings,
|
||||
props: {},
|
||||
testid: 'settings-license',
|
||||
},
|
||||
isProApp() && {
|
||||
label: _t('settings.AI', { defaultMessage: 'AI'}),
|
||||
identifier: 'ai',
|
||||
component: AiSettingsTab,
|
||||
props: {},
|
||||
testid: 'settings-ai',
|
||||
},
|
||||
{
|
||||
label: _t('settings.other', { defaultMessage: 'Other' }),
|
||||
identifier: 'other',
|
||||
component: OtherSettings,
|
||||
props: {},
|
||||
testid: 'settings-other',
|
||||
},
|
||||
];
|
||||
|
||||
let selectedItem = 'general';
|
||||
</script>
|
||||
|
||||
<SettingsFormProvider>
|
||||
<SettingsMenuControl
|
||||
items={menuItems}
|
||||
bind:value={selectedItem}
|
||||
flex1={true}
|
||||
flexColContainer={true}
|
||||
scrollableContentContainer={true}
|
||||
/>
|
||||
</SettingsFormProvider>
|
||||
@@ -12,6 +12,7 @@
|
||||
execute: true,
|
||||
toggleComment: true,
|
||||
findReplace: true,
|
||||
defaultTeamFolder: true,
|
||||
executeAdditionalCondition: () => getCurrentConfig().allowShellScripting,
|
||||
});
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ import * as ServerSummaryTab from './ServerSummaryTab.svelte';
|
||||
import * as ImportExportTab from './ImportExportTab.svelte';
|
||||
import * as SqlObjectTab from './SqlObjectTab.svelte';
|
||||
import * as AppLogTab from './AppLogTab.svelte';
|
||||
import * as SettingsTab from './SettingsTab.svelte';
|
||||
|
||||
import protabs from './index-pro';
|
||||
|
||||
@@ -56,5 +57,6 @@ export default {
|
||||
ImportExportTab,
|
||||
SqlObjectTab,
|
||||
AppLogTab,
|
||||
SettingsTab,
|
||||
...protabs,
|
||||
};
|
||||
|
||||
@@ -6,6 +6,7 @@ import es from '../../../translations/es.json';
|
||||
import zh from '../../../translations/zh.json';
|
||||
import pt from '../../../translations/pt.json';
|
||||
import it from '../../../translations/it.json';
|
||||
import ja from '../../../translations/ja.json';
|
||||
|
||||
import MessageFormat, { MessageFunction } from '@messageformat/core';
|
||||
import { getStringSettingsValue } from './settings/settingsTools';
|
||||
@@ -22,6 +23,7 @@ const translations = {
|
||||
es,
|
||||
pt,
|
||||
it,
|
||||
ja,
|
||||
};
|
||||
const supportedLanguages = Object.keys(translations);
|
||||
|
||||
@@ -31,13 +33,16 @@ const defaultLanguage = 'en';
|
||||
|
||||
let selectedLanguageCache: string | null = null;
|
||||
|
||||
export function getSelectedLanguage(): string {
|
||||
export function getSelectedLanguage(preferrendLanguage?: string): string {
|
||||
if (selectedLanguageCache) return selectedLanguageCache;
|
||||
|
||||
// const browserLanguage = getBrowserLanguage();
|
||||
if (preferrendLanguage == 'auto') {
|
||||
preferrendLanguage = getBrowserLanguage();
|
||||
}
|
||||
|
||||
const selectedLanguage = getElectron()
|
||||
? getStringSettingsValue('localization.language', null)
|
||||
: localStorage.getItem('selectedLanguage');
|
||||
? getStringSettingsValue('localization.language', preferrendLanguage)
|
||||
: localStorage.getItem('selectedLanguage') ?? preferrendLanguage;
|
||||
|
||||
if (!selectedLanguage || !supportedLanguages.includes(selectedLanguage)) return defaultLanguage;
|
||||
return selectedLanguage;
|
||||
@@ -51,8 +56,8 @@ export async function setSelectedLanguage(language: string) {
|
||||
}
|
||||
}
|
||||
|
||||
export function saveSelectedLanguageToCache() {
|
||||
selectedLanguageCache = getSelectedLanguage();
|
||||
export function saveSelectedLanguageToCache(preferrendLanguage?: string) {
|
||||
selectedLanguageCache = getSelectedLanguage(preferrendLanguage);
|
||||
}
|
||||
|
||||
export function getBrowserLanguage(): string {
|
||||
|
||||
@@ -4,6 +4,8 @@ import _ from 'lodash';
|
||||
import { getSchemaList } from './metadataLoaders';
|
||||
import { showSnackbarError } from './snackbar';
|
||||
import { _t } from '../translations';
|
||||
import { apiCall } from './api';
|
||||
import getElectron from './getElectron';
|
||||
|
||||
export class LoadingToken {
|
||||
isCanceled = false;
|
||||
@@ -63,7 +65,8 @@ export function getObjectTypeFieldLabel(objectTypeField, driver?) {
|
||||
if (objectTypeField == 'procedures') return _t('dbObject.procedures', { defaultMessage: 'Procedures' });
|
||||
if (objectTypeField == 'functions') return _t('dbObject.functions', { defaultMessage: 'Functions' });
|
||||
if (objectTypeField == 'triggers') return _t('dbObject.triggers', { defaultMessage: 'Triggers' });
|
||||
if (objectTypeField == 'schedulerEvents') return _t('dbObject.schedulerEvents', { defaultMessage: 'Scheduler Events' });
|
||||
if (objectTypeField == 'schedulerEvents')
|
||||
return _t('dbObject.schedulerEvents', { defaultMessage: 'Scheduler Events' });
|
||||
if (objectTypeField == 'matviews') return _t('dbObject.matviews', { defaultMessage: 'Materialized Views' });
|
||||
if (objectTypeField == 'collections') return _t('dbObject.collections', { defaultMessage: 'Collections/Containers' });
|
||||
return _.startCase(objectTypeField);
|
||||
@@ -151,3 +154,40 @@ export function getKeyTextFromEvent(e) {
|
||||
keyText += e.key;
|
||||
return keyText;
|
||||
}
|
||||
|
||||
export function getDatabasStatusMenu(dbid) {
|
||||
function callSchemalListChanged() {
|
||||
apiCall('database-connections/dispatch-database-changed-event', { event: 'schema-list-changed', ...dbid });
|
||||
}
|
||||
return [
|
||||
{
|
||||
text: _t('command.database.refreshIncremental', { defaultMessage: 'Refresh DB structure (incremental)' }),
|
||||
onClick: () => {
|
||||
apiCall('database-connections/sync-model', dbid);
|
||||
callSchemalListChanged();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _t('command.database.refreshFull', { defaultMessage: 'Refresh DB structure (full)' }),
|
||||
onClick: () => {
|
||||
apiCall('database-connections/sync-model', { ...dbid, isFullRefresh: true });
|
||||
callSchemalListChanged();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _t('command.database.reopenConnection', { defaultMessage: 'Reopen connection' }),
|
||||
onClick: () => {
|
||||
apiCall('database-connections/refresh', dbid);
|
||||
callSchemalListChanged();
|
||||
},
|
||||
},
|
||||
{
|
||||
text: _t('command.database.disconnect', { defaultMessage: 'Disconnect' }),
|
||||
onClick: () => {
|
||||
const electron = getElectron();
|
||||
if (electron) apiCall('database-connections/disconnect', dbid);
|
||||
switchCurrentDatabase(null);
|
||||
},
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import getElectron from './getElectron';
|
||||
// return derived(editorStore, editor => editor != null);
|
||||
// }
|
||||
|
||||
export default async function saveTabFile(editor, saveMode, folder, format, fileExtension) {
|
||||
export default async function saveTabFile(editor, saveMode, folder, format, fileExtension, defaultTeamFolder) {
|
||||
const tabs = get(openedTabs);
|
||||
const tabid = editor.activator.tabid;
|
||||
const data = editor.getData();
|
||||
@@ -94,6 +94,7 @@ export default async function saveTabFile(editor, saveMode, folder, format, file
|
||||
filePath: savedFilePath,
|
||||
onSave,
|
||||
folid: savedCloudFolderId,
|
||||
defaultTeamFolder,
|
||||
// cntid: savedCloudContentId,
|
||||
});
|
||||
}
|
||||
|
||||
132
packages/web/src/utility/tableRestoreScript.ts
Normal file
132
packages/web/src/utility/tableRestoreScript.ts
Normal file
@@ -0,0 +1,132 @@
|
||||
import _ from 'lodash';
|
||||
import { Condition, dumpSqlInsert, dumpSqlUpdate, Insert, Update, Delete, dumpSqlDelete } from 'dbgate-sqltree';
|
||||
import { TableInfo, SqlDumper } from 'dbgate-types';
|
||||
|
||||
export function createTableRestoreScript(backupTable: TableInfo, originalTable: TableInfo, dmp: SqlDumper) {
|
||||
const bothColumns = _.intersection(
|
||||
backupTable.columns.map(x => x.columnName),
|
||||
originalTable.columns.map(x => x.columnName)
|
||||
);
|
||||
const keyColumns = _.intersection(
|
||||
originalTable.primaryKey?.columns?.map(x => x.columnName) || [],
|
||||
backupTable.columns.map(x => x.columnName)
|
||||
);
|
||||
const valueColumns = _.difference(bothColumns, keyColumns);
|
||||
|
||||
function makeColumnCond(colName: string, operator: '=' | '<>' | '<' | '>' | '<=' | '>=' = '='): Condition {
|
||||
return {
|
||||
conditionType: 'binary',
|
||||
operator,
|
||||
left: {
|
||||
exprType: 'column',
|
||||
columnName: colName,
|
||||
source: { name: originalTable },
|
||||
},
|
||||
right: {
|
||||
exprType: 'column',
|
||||
columnName: colName,
|
||||
source: { alias: 'bak' },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
function putTitle(title: string) {
|
||||
dmp.putRaw('\n\n');
|
||||
dmp.comment(`******************** ${title} ********************`);
|
||||
dmp.putRaw('\n');
|
||||
}
|
||||
|
||||
dmp.comment(`Restoring data into table ${originalTable.pureName} from backup table ${backupTable.pureName}`);
|
||||
dmp.putRaw('\n');
|
||||
dmp.comment(`Key columns: ${keyColumns.join(', ')}`);
|
||||
dmp.putRaw('\n');
|
||||
dmp.comment(`Value columns: ${valueColumns.join(', ')}`);
|
||||
dmp.putRaw('\n');
|
||||
dmp.comment(`Follows UPDATE, DELETE, INSERT statements to restore data`);
|
||||
dmp.putRaw('\n');
|
||||
|
||||
const update: Update = {
|
||||
commandType: 'update',
|
||||
from: { name: originalTable },
|
||||
fields: valueColumns.map(colName => ({
|
||||
exprType: 'select',
|
||||
select: {
|
||||
commandType: 'select',
|
||||
from: { name: backupTable, alias: 'bak' },
|
||||
columns: [
|
||||
{
|
||||
exprType: 'column',
|
||||
columnName: colName,
|
||||
source: { alias: 'bak' },
|
||||
},
|
||||
],
|
||||
where: {
|
||||
conditionType: 'and',
|
||||
conditions: keyColumns.map(colName => makeColumnCond(colName)),
|
||||
},
|
||||
},
|
||||
targetColumn: colName,
|
||||
})),
|
||||
where: {
|
||||
conditionType: 'exists',
|
||||
subQuery: {
|
||||
commandType: 'select',
|
||||
from: { name: backupTable, alias: 'bak' },
|
||||
selectAll: true,
|
||||
where: {
|
||||
conditionType: 'and',
|
||||
conditions: [
|
||||
...keyColumns.map(keyColName => makeColumnCond(keyColName)),
|
||||
{
|
||||
conditionType: 'or',
|
||||
conditions: valueColumns.map(colName => makeColumnCond(colName, '<>')),
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
putTitle('UPDATE');
|
||||
dumpSqlUpdate(dmp, update);
|
||||
dmp.endCommand();
|
||||
|
||||
const delcmd: Delete = {
|
||||
commandType: 'delete',
|
||||
from: { name: originalTable },
|
||||
where: {
|
||||
conditionType: 'notExists',
|
||||
subQuery: {
|
||||
commandType: 'select',
|
||||
from: { name: backupTable, alias: 'bak' },
|
||||
selectAll: true,
|
||||
where: {
|
||||
conditionType: 'and',
|
||||
conditions: keyColumns.map(colName => makeColumnCond(colName)),
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
putTitle('DELETE');
|
||||
dumpSqlDelete(dmp, delcmd);
|
||||
dmp.endCommand();
|
||||
|
||||
const insert: Insert = {
|
||||
commandType: 'insert',
|
||||
targetTable: originalTable,
|
||||
fields: bothColumns.map(colName => ({
|
||||
targetColumn: colName,
|
||||
exprType: 'column',
|
||||
columnName: colName,
|
||||
source: { alias: 'bak' },
|
||||
})),
|
||||
whereNotExistsSource: { name: backupTable, alias: 'bak' },
|
||||
insertWhereNotExistsCondition: {
|
||||
conditionType: 'and',
|
||||
conditions: keyColumns.map(colName => makeColumnCond(colName)),
|
||||
},
|
||||
};
|
||||
|
||||
putTitle('INSERT');
|
||||
dumpSqlInsert(dmp, insert);
|
||||
dmp.endCommand();
|
||||
}
|
||||
@@ -101,6 +101,7 @@
|
||||
import WidgetTitle from './WidgetTitle.svelte';
|
||||
import JsonExpandedCellView from '../celldata/JsonExpandedCellView.svelte';
|
||||
import XmlCellView from '../celldata/XmlCellView.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
let selectedFormatType = 'autodetect';
|
||||
|
||||
@@ -116,7 +117,7 @@
|
||||
</script>
|
||||
|
||||
<div class="wrapper">
|
||||
<WidgetTitle>Cell data view</WidgetTitle>
|
||||
<WidgetTitle>{_t('cellDataWidget.title', { defaultMessage: "Cell data view" })}</WidgetTitle>
|
||||
<div class="main">
|
||||
<div class="toolbar">
|
||||
Format:<span> </span>
|
||||
@@ -126,18 +127,18 @@
|
||||
on:change={e => (selectedFormatType = e.detail)}
|
||||
data-testid="CellDataWidget_selectFormat"
|
||||
options={[
|
||||
{ value: 'autodetect', label: `Autodetect - ${autodetectFormat.title}` },
|
||||
{ value: 'autodetect', label: _t('cellDataWidget.autodetect', { defaultMessage: "Autodetect - {autoDetectTitle}", values : { autoDetectTitle: autodetectFormat.title } }) },
|
||||
...formats.map(fmt => ({ label: fmt.title, value: fmt.type })),
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
<div class="data">
|
||||
{#if usedFormat.single && selection?.length != 1}
|
||||
<ErrorInfo message="Must be selected one cell" alignTop />
|
||||
<ErrorInfo message={_t('cellDataWidget.mustSelectOneCell', { defaultMessage: "Must be selected one cell" })} alignTop />
|
||||
{:else if usedFormat == null}
|
||||
<ErrorInfo message="Format not selected" alignTop />
|
||||
<ErrorInfo message={_t('cellDataWidget.formatNotSelected', { defaultMessage: "Format not selected" })} alignTop />
|
||||
{:else if !selection || selection.length == 0}
|
||||
<ErrorInfo message="No data selected" alignTop />
|
||||
<ErrorInfo message={_t('cellDataWidget.noDataSelected', { defaultMessage: "No data selected" })} alignTop />
|
||||
{:else}
|
||||
<svelte:component this={usedFormat?.component} {selection} />
|
||||
{/if}
|
||||
|
||||
@@ -19,12 +19,12 @@
|
||||
</script>
|
||||
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Saved files" name="files" height="70%" storageName="savedFilesWidget">
|
||||
<WidgetColumnBarItem title={_t('files.savedFiles', { defaultMessage: "Saved files" })} name="files" height="70%" storageName="savedFilesWidget">
|
||||
<SavedFilesList />
|
||||
</WidgetColumnBarItem>
|
||||
|
||||
{#if hasPermission('files/favorites/read')}
|
||||
<WidgetColumnBarItem title="Favorites" name="favorites" storageName="favoritesWidget">
|
||||
<WidgetColumnBarItem title={_t('files.favorites', { defaultMessage: "Favorites" })} name="favorites" storageName="favoritesWidget">
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList list={$favorites || []} module={favoriteFileAppObject} />
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -13,13 +13,14 @@
|
||||
import WidgetColumnBar from './WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
||||
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
|
||||
import { _t } from '../translations';
|
||||
|
||||
$: favorites = useFavorites();
|
||||
|
||||
</script>
|
||||
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Recently closed tabs" name="closedTabs" storageName='closedTabsWidget'>
|
||||
<WidgetColumnBarItem title={_t('history.recentlyClosedTabs', { defaultMessage: "Recently closed tabs" })} name="closedTabs" storageName='closedTabsWidget'>
|
||||
<WidgetsInnerContainer>
|
||||
<AppObjectList
|
||||
list={_.sortBy(
|
||||
@@ -30,7 +31,7 @@
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Query history" name="queryHistory" storageName='queryHistoryWidget'>
|
||||
<WidgetColumnBarItem title={_t('history.queryHistory', { defaultMessage: "Query history" })} name="queryHistory" storageName='queryHistoryWidget'>
|
||||
<QueryHistoryList />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
|
||||
@@ -4,13 +4,15 @@
|
||||
|
||||
import WidgetColumnBar from './WidgetColumnBar.svelte';
|
||||
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
||||
|
||||
import { _t } from '../translations';
|
||||
</script>
|
||||
|
||||
<WidgetColumnBar>
|
||||
<WidgetColumnBarItem title="Installed extensions" name="installed" height="50%" storageName='installedPluginsWidget'>
|
||||
<WidgetColumnBarItem title={_t('widgets.installedExtensions', { defaultMessage: 'Installed extensions' })} name="installed" height="50%" storageName='installedPluginsWidget'>
|
||||
<InstalledPluginsList />
|
||||
</WidgetColumnBarItem>
|
||||
<WidgetColumnBarItem title="Available extensions" name="all" storageName='allPluginsWidget'>
|
||||
<WidgetColumnBarItem title={_t('widgets.availableExtensions', { defaultMessage: 'Available extensions' })} name="all" storageName='allPluginsWidget'>
|
||||
<AvailablePluginsList />
|
||||
</WidgetColumnBarItem>
|
||||
</WidgetColumnBar>
|
||||
|
||||
@@ -30,11 +30,11 @@
|
||||
<WidgetColumnBarItem title="Public Knowledge Base" name="publicCloud" storageName="publicCloudItems">
|
||||
<WidgetsInnerContainer>
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search public files" bind:value={filter} />
|
||||
<SearchInput placeholder={_t('publicCloudWidget.searchPublicFiles', { defaultMessage: "Search public files" })} bind:value={filter} />
|
||||
<CloseSearchButton bind:filter />
|
||||
<InlineButton
|
||||
on:click={handleRefreshPublic}
|
||||
title="Refresh files"
|
||||
title={_t('publicCloudWidget.refreshFiles', { defaultMessage: "Refresh files" })}
|
||||
data-testid="CloudItemsWidget_buttonRefreshPublic"
|
||||
>
|
||||
<FontIcon icon="icon refresh" />
|
||||
@@ -52,9 +52,9 @@
|
||||
<ErrorInfo message="No files found for your configuration" />
|
||||
<div class="error-info">
|
||||
<div class="m-1">
|
||||
Only files relevant for your connections, platform and DbGate edition are listed. Please define connections at first.
|
||||
{_t('publicCloudWidget.onlyRelevantFilesListed', { defaultMessage: "Only files relevant for your connections, platform and DbGate edition are listed. Please define connections at first." })}
|
||||
</div>
|
||||
<FormStyledButton value={`Refresh list`} skipWidth on:click={handleRefreshPublic} />
|
||||
<FormStyledButton value={_t('publicCloudWidget.refreshList', { defaultMessage: "Refresh list" })} skipWidth on:click={handleRefreshPublic} />
|
||||
</div>
|
||||
{/if}
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||
import { apiCall, apiOff, apiOn } from '../utility/api';
|
||||
import { _t } from '../translations';
|
||||
|
||||
let filter = '';
|
||||
let search = '';
|
||||
@@ -38,7 +39,7 @@
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search query history" {filter} bind:value={filter} />
|
||||
<SearchInput placeholder={_t('history.searchQueryHistory', { defaultMessage: "Search query history" })} {filter} bind:value={filter} />
|
||||
<CloseSearchButton
|
||||
bind:filter
|
||||
on:click={() => {
|
||||
@@ -54,7 +55,7 @@
|
||||
on:click={() => {
|
||||
openNewTab(
|
||||
{
|
||||
title: 'Query #',
|
||||
title: _t('database.queryDesigner', { defaultMessage: "Query #" }),
|
||||
icon: 'icon sql-file',
|
||||
tabComponent: 'QueryTab',
|
||||
focused: true,
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { isProApp } from '../utility/proTools';
|
||||
import InlineUploadButton from '../buttons/InlineUploadButton.svelte';
|
||||
import { DATA_FOLDER_NAMES } from 'dbgate-tools';
|
||||
import { _t } from '../translations';
|
||||
|
||||
let filter = '';
|
||||
|
||||
@@ -65,19 +66,19 @@
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
<SearchInput placeholder="Search saved files" bind:value={filter} />
|
||||
<SearchInput placeholder={_t('files.searchSavedFiles', { defaultMessage: "Search saved files" })} bind:value={filter} />
|
||||
<CloseSearchButton bind:filter />
|
||||
<InlineUploadButton
|
||||
filters={[
|
||||
{
|
||||
name: `All supported files`,
|
||||
name: _t('files.allSupportedFiles', { defaultMessage: "All supported files" }),
|
||||
extensions: ['sql'],
|
||||
},
|
||||
{ name: `SQL files`, extensions: ['sql'] },
|
||||
{ name: _t('files.sqlFiles', { defaultMessage: "SQL files" }), extensions: ['sql'] },
|
||||
]}
|
||||
onProcessFile={handleUploadedFile}
|
||||
/>
|
||||
<InlineButton on:click={handleRefreshFiles} title="Refresh files" data-testid="SavedFileList_buttonRefresh">
|
||||
<InlineButton on:click={handleRefreshFiles} title={_t('files.refreshFiles', { defaultMessage: "Refresh files" })} data-testid="SavedFileList_buttonRefresh">
|
||||
<FontIcon icon="icon refresh" />
|
||||
</InlineButton>
|
||||
</SearchBoxWrapper>
|
||||
@@ -86,7 +87,7 @@
|
||||
<AppObjectList
|
||||
list={files}
|
||||
module={savedFileAppObject}
|
||||
groupFunc={data => (data.teamFileId ? 'Team files' : dataFolderTitle(data.folder))}
|
||||
groupFunc={data => (data.teamFileId ? _t('files.teamFiles', { defaultMessage: "Team files" }) : dataFolderTitle(data.folder))}
|
||||
{filter}
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
|
||||
@@ -31,7 +31,7 @@
|
||||
import { chevronExpandIcon } from '../icons/expandIcons';
|
||||
import ErrorInfo from '../elements/ErrorInfo.svelte';
|
||||
import LoadingInfo from '../elements/LoadingInfo.svelte';
|
||||
import { getObjectTypeFieldLabel } from '../utility/common';
|
||||
import { getDatabasStatusMenu, getObjectTypeFieldLabel } from '../utility/common';
|
||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
|
||||
@@ -120,11 +120,6 @@
|
||||
// setInterval(() => (generateIndex += 1), 2000);
|
||||
// $: objectList = generateObjectList(generateIndex);
|
||||
|
||||
const handleRefreshDatabase = () => {
|
||||
apiCall('database-connections/refresh', { conid, database });
|
||||
apiCall('database-connections/dispatch-database-changed-event', { event: 'schema-list-changed', conid, database });
|
||||
};
|
||||
|
||||
function createAddMenu() {
|
||||
const res = [];
|
||||
if (driver?.databaseEngineTypes?.includes('document')) {
|
||||
@@ -147,6 +142,15 @@
|
||||
return res;
|
||||
}
|
||||
|
||||
function createRefreshDatabaseMenu() {
|
||||
return getDatabasStatusMenu({ conid, database });
|
||||
}
|
||||
|
||||
function handleFullRefreshDatabase() {
|
||||
apiCall('database-connections/sync-model', { conid, database, isFullRefresh: true });
|
||||
apiCall('database-connections/dispatch-database-changed-event', { event: 'schema-list-changed', conid, database });
|
||||
}
|
||||
|
||||
function createSearchMenu() {
|
||||
const res = [];
|
||||
res.push({ label: _t('sqlObject.searchBy', { defaultMessage: 'Search by:' }), isBold: true, disabled: true });
|
||||
@@ -228,6 +232,15 @@
|
||||
$focusedConnectionOrDatabase?.database != extractDbNameFromComposite(database)));
|
||||
|
||||
// $: console.log('STATUS', $status);
|
||||
|
||||
function getAppObjectGroup(data) {
|
||||
if (data.objectTypeField == 'tables') {
|
||||
if (data.pureName.match(databaseObjectAppObject.TABLE_BACKUP_REGEX)) {
|
||||
return _t('dbObject.tableBackups', { defaultMessage: 'Table Backups' });
|
||||
}
|
||||
}
|
||||
return getObjectTypeFieldLabel(data.objectTypeField, driver);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if $status && $status.name == 'error'}
|
||||
@@ -237,7 +250,18 @@
|
||||
|
||||
<WidgetsInnerContainer hideContent={differentFocusedDb}>
|
||||
<ErrorInfo message={$status.message} icon="img error" />
|
||||
<InlineButton on:click={handleRefreshDatabase}>{_t('common.refresh', { defaultMessage: 'Refresh' })}</InlineButton>
|
||||
<InlineButton on:click={handleFullRefreshDatabase}
|
||||
>{_t('common.refresh', { defaultMessage: 'Refresh' })}</InlineButton
|
||||
>
|
||||
|
||||
<DropDownButton
|
||||
menu={createRefreshDatabaseMenu}
|
||||
title={_t('sqlObjectList.refreshDatabase', { defaultMessage: 'Refresh database connection and object list' })}
|
||||
square
|
||||
narrow={false}
|
||||
data-testid="SqlObjectList_refreshButton"
|
||||
icon="icon dots-vertical"
|
||||
/>
|
||||
</WidgetsInnerContainer>
|
||||
{:else if objectList.length == 0 && $status && $status.name != 'pending' && $status.name != 'checkStructure' && $status.name != 'loadStructure' && $objects}
|
||||
<SchemaSelector
|
||||
@@ -262,7 +286,7 @@
|
||||
icon="img alert"
|
||||
/>
|
||||
<div class="m-1" />
|
||||
<InlineButton on:click={handleRefreshDatabase}>{_t('common.refresh', { defaultMessage: 'Refresh' })}</InlineButton>
|
||||
<InlineButton on:click={handleFullRefreshDatabase}>{_t('common.refresh', { defaultMessage: 'Refresh' })}</InlineButton>
|
||||
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||
<div class="m-1" />
|
||||
<InlineButton on:click={() => runCommand('new.table')}
|
||||
@@ -298,14 +322,14 @@
|
||||
{#if !filter}
|
||||
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
||||
{/if}
|
||||
<InlineButton
|
||||
on:click={handleRefreshDatabase}
|
||||
<DropDownButton
|
||||
menu={createRefreshDatabaseMenu}
|
||||
title={_t('sqlObjectList.refreshDatabase', { defaultMessage: 'Refresh database connection and object list' })}
|
||||
square
|
||||
narrow={false}
|
||||
data-testid="SqlObjectList_refreshButton"
|
||||
>
|
||||
<FontIcon icon="icon refresh" />
|
||||
</InlineButton>
|
||||
icon="icon dots-vertical"
|
||||
/>
|
||||
</SearchBoxWrapper>
|
||||
<SchemaSelector
|
||||
schemaList={_.isArray($schemaList) ? $schemaList : null}
|
||||
@@ -356,7 +380,7 @@
|
||||
.filter(x => x.schemaName == null || ($appliedCurrentSchema ? x.schemaName == $appliedCurrentSchema : true))
|
||||
.map(x => ({ ...x, conid, database }))}
|
||||
module={databaseObjectAppObject}
|
||||
groupFunc={data => getObjectTypeFieldLabel(data.objectTypeField, driver)}
|
||||
groupFunc={getAppObjectGroup}
|
||||
subItemsComponent={(data, { isExpandedBySearch }) =>
|
||||
data.objectTypeField == 'procedures' || data.objectTypeField == 'functions'
|
||||
? isExpandedBySearch
|
||||
|
||||
@@ -35,16 +35,16 @@
|
||||
getCurrentConfig().storageDatabase && {
|
||||
icon: 'icon admin',
|
||||
name: 'admin',
|
||||
title: 'Administration',
|
||||
title: _t('widgets.administration', { defaultMessage: 'Administration' }),
|
||||
},
|
||||
{
|
||||
icon: 'icon database',
|
||||
name: 'database',
|
||||
title: 'Database connections',
|
||||
title: _t('widgets.databaseConnections', { defaultMessage: 'Database connections' }),
|
||||
},
|
||||
getCurrentConfig().allowPrivateCloud && {
|
||||
name: 'cloud-private',
|
||||
title: 'DbGate Cloud',
|
||||
title: _t('widgets.dbgateCloud', { defaultMessage: 'DbGate Cloud' }),
|
||||
icon: 'icon cloud-private',
|
||||
},
|
||||
|
||||
@@ -55,17 +55,17 @@
|
||||
{
|
||||
icon: 'icon file',
|
||||
name: 'file',
|
||||
title: 'Favorites & Saved files',
|
||||
title: _t('widgets.favoritesAndSavedFiles', { defaultMessage: 'Favorites & Saved files' }),
|
||||
},
|
||||
{
|
||||
icon: 'icon history',
|
||||
name: 'history',
|
||||
title: 'Query history & Closed tabs',
|
||||
title: _t('widgets.queryHistoryAndClosedTabs', { defaultMessage: 'Query history & Closed tabs' }),
|
||||
},
|
||||
isProApp() && {
|
||||
icon: 'icon archive',
|
||||
name: 'archive',
|
||||
title: 'Archive (saved tabular data)',
|
||||
title: _t('widgets.archive', { defaultMessage: 'Archive (saved tabular data)' }),
|
||||
},
|
||||
// {
|
||||
// icon: 'icon plugin',
|
||||
@@ -75,17 +75,17 @@
|
||||
{
|
||||
icon: 'icon cell-data',
|
||||
name: 'cell-data',
|
||||
title: 'Selected cell data detail view',
|
||||
title: _t('widgets.selectedCellDataDetailView', { defaultMessage: 'Selected cell data detail view' }),
|
||||
},
|
||||
{
|
||||
name: 'cloud-public',
|
||||
title: 'DbGate Cloud',
|
||||
title: _t('widgets.dbgateCloud', { defaultMessage: 'DbGate Cloud' }),
|
||||
icon: 'icon cloud-public',
|
||||
},
|
||||
{
|
||||
icon: 'icon premium',
|
||||
name: 'premium',
|
||||
title: 'Premium promo',
|
||||
title: _t('widgets.premiumPromo', { defaultMessage: 'Premium promo' }),
|
||||
isPremiumPromo: true,
|
||||
},
|
||||
// {
|
||||
@@ -113,32 +113,12 @@
|
||||
//const handleChangeWidget= e => (selectedWidget.set(item.name))
|
||||
|
||||
function handleSettingsMenu() {
|
||||
const rect = domSettings.getBoundingClientRect();
|
||||
const left = rect.right;
|
||||
const top = rect.bottom;
|
||||
const items = [
|
||||
hasPermission('settings/change') && { command: 'settings.show' },
|
||||
{ command: 'theme.changeTheme' },
|
||||
hasPermission('settings/change') && { command: 'settings.commands' },
|
||||
hasPermission('widgets/plugins') && {
|
||||
text: _t('widgets.managePlugins', { defaultMessage: 'Manage plugins' }),
|
||||
onClick: () => {
|
||||
$selectedWidget = 'plugins';
|
||||
$visibleWidgetSideBar = true;
|
||||
},
|
||||
},
|
||||
hasPermission('application-log') && {
|
||||
text: _t('widgets.viewApplicationLogs', { defaultMessage: 'View application logs' }),
|
||||
onClick: () => {
|
||||
openNewTab({
|
||||
title: 'Application log',
|
||||
icon: 'img applog',
|
||||
tabComponent: 'AppLogTab',
|
||||
});
|
||||
},
|
||||
},
|
||||
];
|
||||
currentDropDownMenu.set({ left, top, items });
|
||||
openNewTab({
|
||||
title: 'Settings',
|
||||
icon: 'icon settings',
|
||||
tabComponent: 'SettingsTab',
|
||||
props: {},
|
||||
});
|
||||
}
|
||||
|
||||
function handleCloudAccountMenu() {
|
||||
@@ -213,7 +193,7 @@
|
||||
class="wrapper"
|
||||
on:click={() => showModal(NewObjectModal)}
|
||||
data-testid="WidgetIconPanel_addButton"
|
||||
title="Add New"
|
||||
title={_t('widgets.addNew', { defaultMessage: 'Add New' })}
|
||||
>
|
||||
<FontIcon icon="icon add" />
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user