Compare commits
30 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| be9df93a7e | |||
| 8ff30e426e | |||
| 7cdbef609e | |||
| f6195a468d | |||
| c23bf72d55 | |||
| c02441402b | |||
| b9a8764b55 | |||
| a2374c1981 | |||
| 9cfd5af704 | |||
| a6f473b8ed | |||
| e0a74402cb | |||
| c6e57b278e | |||
| e63f1f8f09 | |||
| 57da9c9885 | |||
| e6a3acf4c2 | |||
| 537869e862 | |||
| ae2ff7b3b1 | |||
| 1db01dbdb1 | |||
| 7988438dc7 | |||
| 3b32823f94 | |||
| 3370c754f2 | |||
| 2d84e5a611 | |||
| 8d5f73849e | |||
| 7a5019164a | |||
| f5733ea2d7 | |||
| 92e13220d8 | |||
| 2a5fdd852a | |||
| 7759fd862f | |||
| ca5dd0ac30 | |||
| 18be29fd88 |
@@ -123,6 +123,7 @@ jobs:
|
||||
cp ../dbgate-merged/app/dist/*win_arm64.zip artifacts/dbgate-premium-windows-latest-arm64.zip || true
|
||||
cp ../dbgate-merged/app/dist/*-mac_universal.dmg artifacts/dbgate-premium-latest.dmg || true
|
||||
cp ../dbgate-merged/app/dist/*-mac_x64.dmg artifacts/dbgate-premium-latest-x64.dmg || true
|
||||
cp ../dbgate-merged/app/dist/*-mac_arm64.dmg artifacts/dbgate-premium-latest-arm64.dmg || true
|
||||
|
||||
mv ../dbgate-merged/app/dist/*.exe artifacts/ || true
|
||||
mv ../dbgate-merged/app/dist/*.zip artifacts/ || true
|
||||
|
||||
@@ -98,6 +98,7 @@ jobs:
|
||||
cp app/dist/*win_arm64.zip artifacts/dbgate-windows-latest-arm64.zip || true
|
||||
cp app/dist/*-mac_universal.dmg artifacts/dbgate-latest.dmg || true
|
||||
cp app/dist/*-mac_x64.dmg artifacts/dbgate-latest-x64.dmg || true
|
||||
cp app/dist/*-mac_arm64.dmg artifacts/dbgate-latest-arm64.dmg || true
|
||||
|
||||
mv app/dist/*.exe artifacts/ || true
|
||||
mv app/dist/*.zip artifacts/ || true
|
||||
|
||||
@@ -8,6 +8,22 @@ Builds:
|
||||
- linux - application for linux
|
||||
- win - application for Windows
|
||||
|
||||
### 6.1.0
|
||||
- ADDED: Fulltext search in DB model and connections, highlight searched names
|
||||
- ADDED: Tab preview mode configuration #963
|
||||
- CHANGED: Single-click to open server connection/database + ability to configure this #959
|
||||
- ADDED: Option to align numbers to right in data grid #957
|
||||
- FIXED: Cursor Becomes Stuck When Escaping "Case" #954
|
||||
- ADDED: Postgres GEOGRAPHY types are shown on map, event when executing query #948
|
||||
- FIXED: Error displaying CLOB and NCLOB in Oracle
|
||||
- FIXED: Analysing of foreign keys in Postgres and MS SQL, when the same FKS are used across different schemas
|
||||
- ADDED: Support of views, procedures, functions to Oracle. Added integration tests for Oracle
|
||||
- ADDED: Display "No rows" message, quick add new row
|
||||
- ADDED: Choose default database from list
|
||||
- ADDED: Default database is automatically selected on connect
|
||||
- ADDED: Apple-Silicon-only build for Mac #949
|
||||
- ADDED: Display comment into tables and column list #755
|
||||
|
||||
### 6.0.0
|
||||
- ADDED: Order or filter the indexes for huge tables #922
|
||||
- ADDED: Empty string filters
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"private": true,
|
||||
"version": "6.0.1-beta.3",
|
||||
"version": "6.1.0",
|
||||
"name": "dbgate-all",
|
||||
"workspaces": [
|
||||
"packages/*",
|
||||
|
||||
@@ -222,7 +222,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
test_meta: true,
|
||||
test(connection) {
|
||||
test({ connection, requestDbList }) {
|
||||
const subprocess = fork(
|
||||
global['API_PACKAGE'] || process.argv[1],
|
||||
[
|
||||
@@ -237,7 +237,7 @@ module.exports = {
|
||||
}
|
||||
);
|
||||
pipeForkLogs(subprocess);
|
||||
subprocess.send(connection);
|
||||
subprocess.send({ connection, requestDbList });
|
||||
return new Promise(resolve => {
|
||||
subprocess.on('message', resp => {
|
||||
if (handleProcessCommunication(resp, subprocess)) return;
|
||||
|
||||
@@ -16,13 +16,19 @@ Platform: ${process.platform}
|
||||
|
||||
function start() {
|
||||
childProcessChecker();
|
||||
process.on('message', async connection => {
|
||||
process.on('message', async args => {
|
||||
// @ts-ignore
|
||||
const { connection, requestDbList } = args;
|
||||
if (handleProcessCommunication(connection)) return;
|
||||
try {
|
||||
const driver = requireEngineDriver(connection);
|
||||
const dbhan = await connectUtility(driver, connection, 'app');
|
||||
const res = await driver.getVersion(dbhan);
|
||||
process.send({ msgtype: 'connected', ...res });
|
||||
let databases = undefined;
|
||||
if (requestDbList) {
|
||||
databases = await driver.listDatabases(dbhan);
|
||||
}
|
||||
process.send({ msgtype: 'connected', ...res, databases });
|
||||
await driver.close(dbhan);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
||||
@@ -90,11 +90,16 @@ export function tokenizeBySearchFilter(text: string, filter: string): { text: st
|
||||
for (const item of res) {
|
||||
const indexes = [];
|
||||
for (const char of token) {
|
||||
const index = item.text.indexOf(char, indexes.length > 0 ? indexes[indexes.length - 1] + 1 : 0);
|
||||
if (index < 0) {
|
||||
indexes.push(-1);
|
||||
if (indexes.length == 0 && char == item.text[0]?.toUpperCase()) {
|
||||
// handle first letter of camelcase
|
||||
indexes.push(0);
|
||||
} else {
|
||||
indexes.push(index);
|
||||
const index = item.text.indexOf(char, indexes.length > 0 ? indexes[indexes.length - 1] + 1 : 0);
|
||||
if (index < 0) {
|
||||
indexes.push(-1);
|
||||
} else {
|
||||
indexes.push(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (indexes.some(x => x < 0)) {
|
||||
@@ -129,7 +134,13 @@ export function tokenizeBySearchFilter(text: string, filter: string): { text: st
|
||||
res = nextres;
|
||||
}
|
||||
|
||||
return res.filter(x => x.text.length > 0);
|
||||
res = res.filter(x => x.text.length > 0);
|
||||
|
||||
if (res.length == 1 && !res[0].isMatch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
// const result = [];
|
||||
// let lastMatch = 0;
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
"@rollup/plugin-replace": "^3.0.0",
|
||||
"@rollup/plugin-typescript": "^8.2.5",
|
||||
"@tsconfig/svelte": "^1.0.0",
|
||||
"ace-builds": "^1.4.8",
|
||||
"ace-builds": "^1.36.5",
|
||||
"chart.js": "^4.4.2",
|
||||
"chartjs-adapter-moment": "^1.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
|
||||
@@ -89,6 +89,7 @@
|
||||
{passProps}
|
||||
isExpandedBySearch={filter && item.isChildMatched}
|
||||
{filter}
|
||||
isMainMatched={item.isMainMatched}
|
||||
/>
|
||||
{/each}
|
||||
</div>
|
||||
|
||||
@@ -6,6 +6,8 @@
|
||||
import AppObjectListItem from './AppObjectListItem.svelte';
|
||||
import { writable } from 'svelte/store';
|
||||
import Link from '../elements/Link.svelte';
|
||||
import { focusedConnectionOrDatabase } from '../stores';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
export let list;
|
||||
export let module;
|
||||
@@ -40,33 +42,33 @@
|
||||
const matchResult = matcher ? matcher(data) : true;
|
||||
|
||||
let isMatched = true;
|
||||
let isMainMatched = true;
|
||||
let isChildMatched = true;
|
||||
|
||||
if (matchResult == false) {
|
||||
isMatched = false;
|
||||
isChildMatched = false;
|
||||
isMainMatched = false;
|
||||
} else if (matchResult == 'child') {
|
||||
isMatched = true;
|
||||
isChildMatched = true;
|
||||
isMainMatched = false;
|
||||
} else if (matchResult == 'main') {
|
||||
isMatched = true;
|
||||
isChildMatched = false;
|
||||
} else if (matchResult == 'none') {
|
||||
isMatched = false;
|
||||
isChildMatched = false;
|
||||
isMainMatched = false;
|
||||
} else if (matchResult == 'both') {
|
||||
isMatched = true;
|
||||
isChildMatched = !module.disableShowChildrenWithParentMatch;
|
||||
}
|
||||
|
||||
const group = groupFunc ? groupFunc(data) : undefined;
|
||||
return { group, data, isMatched, isChildMatched };
|
||||
return { group, data, isMatched, isChildMatched, isMainMatched };
|
||||
})
|
||||
);
|
||||
|
||||
$: filtered = dataLabeled.filter(x => x.isMatched).map(x => x.data);
|
||||
|
||||
$: childrenMatched = dataLabeled.filter(x => x.isChildMatched).map(x => x.data);
|
||||
$: mainMatched = dataLabeled.filter(x => x.isMainMatched).map(x => x.data);
|
||||
|
||||
// let filtered = [];
|
||||
|
||||
@@ -93,10 +95,24 @@
|
||||
return res;
|
||||
}
|
||||
|
||||
function setExpandLimited() {
|
||||
expandLimited = true;
|
||||
}
|
||||
|
||||
$: groups = groupFunc ? extendGroups(_.groupBy(dataLabeled, 'group'), emptyGroupNames) : null;
|
||||
|
||||
$: listLimited = isExpandedBySearch && !expandLimited ? filtered.slice(0, Math.min(filter.trim().length, 3)) : list;
|
||||
$: listLimited = isExpandedBySearch && !expandLimited ? filtered.slice(0, filter.trim().length < 3 ? 1 : 3) : list;
|
||||
$: isListLimited = isExpandedBySearch && listLimited.length < filtered.length;
|
||||
$: listMissingItems = isListLimited ? filtered.slice(listLimited.length) : [];
|
||||
|
||||
$: if (
|
||||
$focusedConnectionOrDatabase &&
|
||||
listMissingItems.some(
|
||||
x => $focusedConnectionOrDatabase.conid == x?.connection?._id && $focusedConnectionOrDatabase.database == x?.name
|
||||
)
|
||||
) {
|
||||
tick().then(setExpandLimited);
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if groupFunc}
|
||||
@@ -136,6 +152,7 @@
|
||||
{disableContextMenu}
|
||||
{filter}
|
||||
isExpandedBySearch={filter && childrenMatched.includes(data)}
|
||||
isMainMatched={filter && mainMatched.includes(data)}
|
||||
{passProps}
|
||||
{getIsExpanded}
|
||||
{setIsExpanded}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
export let passProps;
|
||||
export let getIsExpanded = null;
|
||||
export let setIsExpanded = null;
|
||||
export let isMainMatched = false;
|
||||
|
||||
let isExpandedCore = false;
|
||||
|
||||
@@ -70,7 +71,8 @@
|
||||
{filter}
|
||||
{passProps}
|
||||
{isExpandedBySearch}
|
||||
isExpandedOnlyBySearch={isExpandedBySearch && !isExpanded}
|
||||
{isExpanded}
|
||||
{isMainMatched}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
export const extractKey = ({ columnName }) => columnName;
|
||||
|
||||
export const createMatcher =
|
||||
(filter, cfg = DEFAULT_SEARCH_SETTINGS) =>
|
||||
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
|
||||
data => {
|
||||
const filterArgs = [];
|
||||
if (cfg.columnName) filterArgs.push(data.columnName);
|
||||
@@ -21,7 +21,7 @@
|
||||
import { renameDatabaseObjectDialog, alterDatabaseDialog } from '../utility/alterDatabaseTools';
|
||||
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { DEFAULT_SEARCH_SETTINGS } from '../stores';
|
||||
import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
|
||||
export let data;
|
||||
|
||||
@@ -1,14 +1,24 @@
|
||||
<script context="module">
|
||||
export const extractKey = data => data._id;
|
||||
export const createMatcher = filter => props => {
|
||||
const { _id, displayName, server } = props;
|
||||
const databases = getLocalStorage(`database_list_${_id}`) || [];
|
||||
return filterNameCompoud(
|
||||
filter,
|
||||
[displayName, server],
|
||||
databases.map(x => x.name)
|
||||
);
|
||||
};
|
||||
export const createMatcher =
|
||||
(filter, cfg = DEFAULT_CONNECTION_SEARCH_SETTINGS) =>
|
||||
props => {
|
||||
const { _id, displayName, server, user, engine } = props;
|
||||
const databases = getLocalStorage(`database_list_${_id}`) || [];
|
||||
const match = (engine || '').match(/^([^@]*)@/);
|
||||
const engineDisplay = match ? match[1] : engine;
|
||||
|
||||
return filterNameCompoud(
|
||||
filter,
|
||||
[
|
||||
cfg.displayName ? displayName : null,
|
||||
cfg.server ? server : null,
|
||||
cfg.user ? user : null,
|
||||
cfg.engine ? engineDisplay : null,
|
||||
],
|
||||
cfg.database ? databases.map(x => x.name) : []
|
||||
);
|
||||
};
|
||||
export function openConnection(connection, disableExpand = false) {
|
||||
if (connection.singleDatabase) {
|
||||
if (getOpenedSingleDatabaseConnections().includes(connection._id)) {
|
||||
@@ -40,6 +50,10 @@
|
||||
expandedConnections.update(x => _.uniq([...x, connection._id]));
|
||||
}
|
||||
|
||||
if (connection.defaultDatabase) {
|
||||
switchCurrentDatabase({ connection, name: connection.defaultDatabase });
|
||||
}
|
||||
|
||||
// if (!config.runAsPortal && getCurrentSettings()['defaultAction.connectionClick'] != 'connect') {
|
||||
// expandedConnections.update(x => _.uniq([...x, connection._id]));
|
||||
// }
|
||||
@@ -92,6 +106,7 @@
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import {
|
||||
currentDatabase,
|
||||
DEFAULT_CONNECTION_SEARCH_SETTINGS,
|
||||
expandedConnections,
|
||||
extensions,
|
||||
focusedConnectionOrDatabase,
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
export const extractKey = ({ schemaName, pureName }) => (schemaName ? `${schemaName}.${pureName}` : pureName);
|
||||
export const createMatcher =
|
||||
(filter, cfg = DEFAULT_SEARCH_SETTINGS) =>
|
||||
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
|
||||
({ schemaName, pureName, objectComment, tableEngine, columns, objectTypeField, createSql }) => {
|
||||
const mainArgs = [];
|
||||
const childArgs = [];
|
||||
@@ -18,6 +18,8 @@
|
||||
if (cfg.columnComment) childArgs.push(column.columnComment);
|
||||
if (cfg.columnDataType) childArgs.push(column.dataType);
|
||||
}
|
||||
} else if (objectTypeField == 'collections') {
|
||||
if (cfg.collectionName) mainArgs.push(pureName);
|
||||
} else {
|
||||
if (cfg.sqlObjectName) mainArgs.push(pureName);
|
||||
if (cfg.sqlObjectText) childArgs.push(createSql);
|
||||
@@ -885,7 +887,35 @@
|
||||
return createDatabaseObjectMenu(data);
|
||||
}
|
||||
|
||||
export function handleObjectClick(data, { forceNewTab = false, tabPreviewMode = false, focusTab = false }) {
|
||||
export function handleObjectClick(data, clickAction) {
|
||||
// on:click={() => handleObjectClick(data, { tabPreviewMode: true })}
|
||||
// on:middleclick={() => handleObjectClick(data, { forceNewTab: true })}
|
||||
// on:dblclick={() => handleObjectClick(data, { tabPreviewMode: false, focusTab: true })}
|
||||
const openDetailOnArrows = getOpenDetailOnArrowsSettings();
|
||||
|
||||
let forceNewTab = false;
|
||||
let tabPreviewMode = false;
|
||||
let focusTab = false;
|
||||
|
||||
switch (clickAction) {
|
||||
case 'leftClick':
|
||||
tabPreviewMode = true;
|
||||
break;
|
||||
case 'middleClick':
|
||||
forceNewTab = true;
|
||||
break;
|
||||
case 'dblClick':
|
||||
focusTab = true;
|
||||
break;
|
||||
case 'keyEnter':
|
||||
focusTab = true;
|
||||
break;
|
||||
case 'keyArrow':
|
||||
if (!openDetailOnArrows) return;
|
||||
tabPreviewMode = true;
|
||||
break;
|
||||
}
|
||||
|
||||
return handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
|
||||
}
|
||||
</script>
|
||||
@@ -895,7 +925,7 @@
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import {
|
||||
currentDatabase,
|
||||
DEFAULT_SEARCH_SETTINGS,
|
||||
DEFAULT_OBJECT_SEARCH_SETTINGS,
|
||||
extensions,
|
||||
getActiveTab,
|
||||
getCurrentSettings,
|
||||
@@ -937,16 +967,12 @@
|
||||
import { openImportExportTab } from '../utility/importExportTools';
|
||||
import { defaultDatabaseObjectAppObjectActions, matchDatabaseObjectAppObject } from './appObjectTools';
|
||||
import { getSupportedScriptTemplates } from '../utility/applyScriptTemplate';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
import { getBoolSettingsValue, getOpenDetailOnArrowsSettings } from '../settings/settingsTools';
|
||||
import { isProApp } from '../utility/proTools';
|
||||
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
function handleClick({ forceNewTab = false, tabPreviewMode = false, focusTab = false } = {}) {
|
||||
handleDatabaseObjectClick(data, { forceNewTab, tabPreviewMode, focusTab });
|
||||
}
|
||||
|
||||
function createMenu() {
|
||||
return createDatabaseObjectMenu(data, passProps?.connection);
|
||||
}
|
||||
@@ -981,9 +1007,9 @@
|
||||
onUnpin={isPinned ? () => pinnedTables.update(list => list.filter(x => !testEqual(x, data))) : null}
|
||||
extInfo={getExtInfo(data)}
|
||||
isChoosed={matchDatabaseObjectAppObject($selectedDatabaseObjectAppObject, data)}
|
||||
on:click={() => handleClick({ tabPreviewMode: true })}
|
||||
on:middleclick={() => handleClick({ forceNewTab: true })}
|
||||
on:dblclick={() => handleClick({ tabPreviewMode: false, focusTab: true })}
|
||||
on:click={() => handleObjectClick(data, 'leftClick')}
|
||||
on:middleclick={() => handleObjectClick(data, 'middleClick')}
|
||||
on:dblclick={() => handleObjectClick(data, 'dblClick')}
|
||||
on:expand
|
||||
on:dragstart
|
||||
on:dragenter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
export const extractKey = ({ columnName }) => columnName;
|
||||
|
||||
export const createMatcher =
|
||||
(filter, cfg = DEFAULT_SEARCH_SETTINGS) =>
|
||||
(filter, cfg = DEFAULT_OBJECT_SEARCH_SETTINGS) =>
|
||||
data => {
|
||||
const filterArgs = [];
|
||||
if (cfg.sqlObjectText) filterArgs.push(data.lineData);
|
||||
@@ -15,7 +15,7 @@
|
||||
<script lang="ts">
|
||||
import AppObjectCore from './AppObjectCore.svelte';
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { DEFAULT_SEARCH_SETTINGS } from '../stores';
|
||||
import { DEFAULT_OBJECT_SEARCH_SETTINGS } from '../stores';
|
||||
|
||||
export let data;
|
||||
</script>
|
||||
|
||||
@@ -11,8 +11,11 @@
|
||||
export let data;
|
||||
export let passProps;
|
||||
|
||||
export let isExpandedOnlyBySearch;
|
||||
export let isExpandedBySearch;
|
||||
export let isExpanded;
|
||||
export let isMainMatched;
|
||||
|
||||
$: isExpandedOnlyBySearch = isExpandedBySearch && !isExpanded;
|
||||
|
||||
$: databases = useDatabaseList({ conid: isExpandedOnlyBySearch ? null : data._id });
|
||||
$: dbList = isExpandedOnlyBySearch ? getLocalStorage(`database_list_${data._id}`) || [] : $databases || [];
|
||||
@@ -24,6 +27,6 @@
|
||||
list={_.sortBy(dbList, x => x.sortOrder ?? x.name).map(db => ({ ...db, connection: data }))}
|
||||
module={databaseAppObject}
|
||||
{passProps}
|
||||
{filter}
|
||||
filter={isMainMatched ? '' : filter}
|
||||
{isExpandedBySearch}
|
||||
/>
|
||||
|
||||
@@ -7,20 +7,32 @@
|
||||
|
||||
export let icon = 'icon chevron-down';
|
||||
export let menu;
|
||||
export let asyncMenu = undefined;
|
||||
export let narrow = false;
|
||||
export let square = true;
|
||||
export let disabled = false;
|
||||
let domButton;
|
||||
|
||||
function handleClick() {
|
||||
let domButton;
|
||||
let isLoading = false;
|
||||
|
||||
async function handleClick() {
|
||||
if (disabled) return;
|
||||
|
||||
|
||||
let items = menu;
|
||||
|
||||
if (asyncMenu) {
|
||||
isLoading = true;
|
||||
items = await asyncMenu();
|
||||
isLoading = false;
|
||||
}
|
||||
|
||||
const rect = domButton.getBoundingClientRect();
|
||||
const left = rect.left;
|
||||
const top = rect.bottom;
|
||||
currentDropDownMenu.set({ left, top, items: menu });
|
||||
currentDropDownMenu.set({ left, top, items });
|
||||
}
|
||||
</script>
|
||||
|
||||
<InlineButton square {narrow} on:click={handleClick} bind:this={domButton} {disabled}>
|
||||
<FontIcon {icon} />
|
||||
<InlineButton {square} {narrow} on:click={handleClick} bind:this={domButton} {disabled}>
|
||||
<FontIcon icon={isLoading ? 'icon loading' : icon} />
|
||||
</InlineButton>
|
||||
|
||||
@@ -54,6 +54,8 @@
|
||||
|
||||
// don't parse JSON for explicit data types
|
||||
$: jsonParsedValue = !editorTypes?.explicitDataType && isJsonLikeLongString(value) ? safeJsonParse(value) : null;
|
||||
|
||||
$: showHint = allowHintField && rowData && _.some(col.hintColumnNames, hintColumnName => rowData[hintColumnName]);
|
||||
</script>
|
||||
|
||||
<td
|
||||
@@ -68,11 +70,12 @@
|
||||
class:isDeleted
|
||||
class:isAutofillSelected
|
||||
class:isFocusedColumn
|
||||
class:alignRight={_.isNumber(value) && !showHint}
|
||||
{style}
|
||||
>
|
||||
<CellValue {rowData} {value} {jsonParsedValue} {editorTypes} />
|
||||
|
||||
{#if allowHintField && rowData && _.some(col.hintColumnNames, hintColumnName => rowData[hintColumnName])}
|
||||
{#if showHint}
|
||||
<span class="hint"
|
||||
>{col.hintColumnNames.map(hintColumnName => rowData[hintColumnName]).join(col.hintColumnDelimiter || ' ')}</span
|
||||
>
|
||||
@@ -199,4 +202,9 @@
|
||||
overflow: visible;
|
||||
cursor: crosshair;
|
||||
}
|
||||
|
||||
.alignRight {
|
||||
color: var(--theme-icon-green);
|
||||
text-align: var(--data-grid-numbers-align);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -416,7 +416,6 @@
|
||||
import GenerateSqlFromDataModal from '../modals/GenerateSqlFromDataModal.svelte';
|
||||
import { showModal } from '../modals/modalTools';
|
||||
import StatusBarTabItem from '../widgets/StatusBarTabItem.svelte';
|
||||
import { findCommand } from '../commands/runCommand';
|
||||
import { openJsonDocument } from '../tabs/JsonTab.svelte';
|
||||
import EditJsonModal from '../modals/EditJsonModal.svelte';
|
||||
import { apiCall } from '../utility/api';
|
||||
@@ -430,8 +429,7 @@
|
||||
import { openJsonLinesData } from '../utility/openJsonLinesData';
|
||||
import contextMenuActivator from '../utility/contextMenuActivator';
|
||||
import InputTextModal from '../modals/InputTextModal.svelte';
|
||||
import hasPermission from '../utility/hasPermission';
|
||||
|
||||
|
||||
export let onLoadNextData = undefined;
|
||||
export let grider = undefined;
|
||||
export let display: GridDisplay = undefined;
|
||||
@@ -483,6 +481,7 @@
|
||||
let domFocusField;
|
||||
let domHorizontalScroll;
|
||||
let domVerticalScroll;
|
||||
let domContainer;
|
||||
|
||||
let currentCell = topLeftCell;
|
||||
let selectedCells = [topLeftCell];
|
||||
@@ -492,7 +491,7 @@
|
||||
let autofillSelectedCells = emptyCellArray;
|
||||
const domFilterControlsRef = createRef({});
|
||||
|
||||
let isGridFocused=false;
|
||||
let isGridFocused = false;
|
||||
|
||||
const tabid = getContext('tabid');
|
||||
|
||||
@@ -596,6 +595,7 @@
|
||||
selectedCells = [cell];
|
||||
await tick();
|
||||
scrollIntoView(cell);
|
||||
domFocusField?.focus();
|
||||
}
|
||||
|
||||
export async function cloneRows() {
|
||||
@@ -1748,6 +1748,11 @@
|
||||
// { text: 'Copy as JSON', onClick: () => copyToClipboardCore('json') },
|
||||
],
|
||||
},
|
||||
{
|
||||
text: 'Paste',
|
||||
onClick: () => domFocusField.paste(),
|
||||
keyText: 'CtrlOrCommand+V',
|
||||
},
|
||||
{ placeTag: 'switch' },
|
||||
{ divider: true },
|
||||
{ placeTag: 'save' },
|
||||
@@ -1838,11 +1843,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
{:else if isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
|
||||
<div>
|
||||
<div class="ml-2">
|
||||
<ErrorInfo
|
||||
alignTop
|
||||
message={grider.editable
|
||||
? 'No rows loaded, check filter or add new documents. You could copy documents from ohter collections/tables with Copy advanved/Copy as JSON command.'
|
||||
? 'No rows loaded, check filter or add new documents. You could copy documents from other collections/tables with Copy advanved/Copy as JSON command.'
|
||||
: 'No rows loaded'}
|
||||
/>
|
||||
{#if display.filterCount > 0}
|
||||
@@ -1867,9 +1872,15 @@
|
||||
class:data-grid-focused={isGridFocused}
|
||||
bind:clientWidth={containerWidth}
|
||||
bind:clientHeight={containerHeight}
|
||||
bind:this={domContainer}
|
||||
use:contextMenu={buildMenu}
|
||||
use:contextMenuActivator={activator}
|
||||
on:wheel={handleGridWheel}
|
||||
on:click={e => {
|
||||
if (e.target == domContainer) {
|
||||
domFocusField?.focus();
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
@@ -2014,6 +2025,24 @@
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{#if !isDynamicStructure && isLoadedAll && grider?.rowCount == 0}
|
||||
<div class="no-rows-info ml-2">
|
||||
<div class="mb-3">
|
||||
<ErrorInfo alignTop message="No rows loaded" icon="img info" />
|
||||
</div>
|
||||
{#if display.filterCount > 0}
|
||||
<FormStyledButton value="Reset filter" on:click={() => display.clearFilters()} />
|
||||
{/if}
|
||||
{#if grider.editable}
|
||||
<FormStyledButton value="Add row" on:click={insertNewRow} />
|
||||
{/if}
|
||||
{#if onOpenQuery}
|
||||
<FormStyledButton value="Open Query" on:click={onOpenQuery} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<HorizontalScrollBar
|
||||
minimum={0}
|
||||
maximum={maxScrollColumn}
|
||||
@@ -2092,4 +2121,8 @@
|
||||
right: 40px;
|
||||
bottom: 20px;
|
||||
}
|
||||
|
||||
.no-rows-info {
|
||||
margin-top: 60px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
|
||||
import SearchInput from '../elements/SearchInput.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import ColumnManagerRow from './ColumnManagerRow.svelte';
|
||||
import TokenizedFilteredText from '../widgets/TokenizedFilteredText.svelte';
|
||||
|
||||
export let managerSize;
|
||||
export let display: GridDisplay;
|
||||
@@ -21,7 +21,6 @@
|
||||
|
||||
$: foreignKeys = display?.baseTable?.foreignKeys || [];
|
||||
$: dependencies = display?.baseTable?.dependencies || [];
|
||||
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
@@ -46,7 +45,7 @@
|
||||
>
|
||||
<FontIcon icon="img link" />
|
||||
<div class="ml-1 nowrap">
|
||||
{fk.refTableName}
|
||||
<TokenizedFilteredText text={fk.refTableName} {filter} />
|
||||
({fk.columns.map(x => x.columnName).join(', ')})
|
||||
</div>
|
||||
</div>
|
||||
@@ -70,7 +69,7 @@
|
||||
>
|
||||
<FontIcon icon="img reference" />
|
||||
<div class="ml-1 nowrap">
|
||||
{fk.pureName}
|
||||
<TokenizedFilteredText text={fk.pureName} {filter} />
|
||||
({fk.columns.map(x => x.columnName).join(', ')})
|
||||
</div>
|
||||
</div>
|
||||
@@ -89,5 +88,4 @@
|
||||
.link:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
export let disabled = false;
|
||||
export let defaultValue;
|
||||
export let menu;
|
||||
export let asyncMenu;
|
||||
|
||||
const { values, setFieldValue } = getFormContext();
|
||||
|
||||
@@ -26,5 +27,5 @@
|
||||
value={$values[name] ?? defaultValue}
|
||||
on:input={e => setFieldValue(name, e.target['value'])}
|
||||
/>
|
||||
<DropDownButton {menu} {disabled} />
|
||||
<DropDownButton {menu} {asyncMenu} {disabled} />
|
||||
</div>
|
||||
|
||||
@@ -287,6 +287,7 @@
|
||||
'img import': 'mdi mdi-database-import color-icon-green',
|
||||
'img export': 'mdi mdi-database-export color-icon-green',
|
||||
'img transform': 'mdi mdi-rotate-orbit color-icon-blue',
|
||||
'img tip': 'mdi mdi-lightbulb-on color-icon-yellow',
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@
|
||||
submenuOffset = hoverOffset;
|
||||
return;
|
||||
}
|
||||
if (item.switchStore) {
|
||||
if (item.switchStore && item.switchValue) {
|
||||
item.switchStore.update(x => ({
|
||||
...x,
|
||||
[item.switchValue]: !x[item.switchValue],
|
||||
@@ -142,12 +142,12 @@
|
||||
>
|
||||
<a on:click={e => handleClick(e, item)} class:disabled={item.disabled} class:bold={item.isBold}>
|
||||
<span>
|
||||
{#if item.switchStoreGetter}
|
||||
{#if item.switchValue && item.switchStoreGetter}
|
||||
{#key switchIndex}
|
||||
{#if item.switchStoreGetter()[item.switchValue]}
|
||||
<FontIcon icon="icon checkbox-marked" padRight />
|
||||
<FontIcon icon="icon check" padRight />
|
||||
{:else}
|
||||
<FontIcon icon="icon checkbox-blank" padRight />
|
||||
<FontIcon icon="icon invisible-box" padRight />
|
||||
{/if}
|
||||
{/key}
|
||||
{/if}
|
||||
|
||||
@@ -16,8 +16,12 @@
|
||||
import FormColorField from '../forms/FormColorField.svelte';
|
||||
import FontIcon from '../icons/FontIcon.svelte';
|
||||
import FormDropDownTextField from '../forms/FormDropDownTextField.svelte';
|
||||
import { getConnectionLabel } from 'dbgate-tools';
|
||||
|
||||
const { values } = getFormContext();
|
||||
export let getDatabaseList;
|
||||
export let currentConnection;
|
||||
|
||||
const { values, setFieldValue } = getFormContext();
|
||||
const electron = getElectron();
|
||||
|
||||
$: authType = $values.authType;
|
||||
@@ -69,6 +73,14 @@
|
||||
'me-central-1',
|
||||
'sa-east-1',
|
||||
];
|
||||
|
||||
async function createDatabasesMenu() {
|
||||
const databases = await getDatabaseList();
|
||||
return databases.map(db => ({
|
||||
text: db.name,
|
||||
onClick: () => setFieldValue('defaultDatabase', db.name),
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormSelectField
|
||||
@@ -377,11 +389,13 @@
|
||||
{/if}
|
||||
|
||||
{#if driver?.showConnectionField('defaultDatabase', $values, showConnectionFieldArgs)}
|
||||
<FormTextField
|
||||
<FormDropDownTextField
|
||||
label="Default database"
|
||||
name="defaultDatabase"
|
||||
disabled={isConnected}
|
||||
data-testid="ConnectionDriverFields_defaultDatabase"
|
||||
asyncMenu={createDatabasesMenu}
|
||||
placeholder="(not selected - optional)"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
@@ -412,7 +426,8 @@
|
||||
templateProps={{ noMargin: true }}
|
||||
disabled={isConnected}
|
||||
data-testid="ConnectionDriverFields_displayName"
|
||||
/>
|
||||
placeholder={getConnectionLabel(currentConnection)}
|
||||
/>
|
||||
</div>
|
||||
<div class="col-6 mr-1">
|
||||
<FormColorField
|
||||
|
||||
@@ -100,7 +100,7 @@ ORDER BY
|
||||
{ label: 'Connection', slot: 2 },
|
||||
{ label: 'Themes', slot: 3 },
|
||||
{ label: 'Default Actions', slot: 4 },
|
||||
{ label: 'Confirmations', slot: 5 },
|
||||
{ label: 'Behaviour', slot: 5 },
|
||||
{ label: 'Other', slot: 6 },
|
||||
]}
|
||||
>
|
||||
@@ -138,6 +138,8 @@ ORDER BY
|
||||
defaultValue="10"
|
||||
/>
|
||||
|
||||
<FormCheckboxField name="dataGrid.alignNumbersRight" label="Align numbers to right" defaultValue={false} />
|
||||
|
||||
<div class="heading">SQL editor</div>
|
||||
|
||||
<div class="flex">
|
||||
@@ -331,6 +333,24 @@ ORDER BY
|
||||
/>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="5">
|
||||
<div class="heading">Behaviour</div>
|
||||
|
||||
<FormCheckboxField name="behaviour.useTabPreviewMode" label="Use tab preview mode" defaultValue={true} />
|
||||
|
||||
<div class="tip">
|
||||
<FontIcon icon="img tip" /> 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="Open detail on keyboard navigation"
|
||||
defaultValue={true}
|
||||
disabled={values['behaviour.useTabPreviewMode'] === false}
|
||||
/>
|
||||
|
||||
<div class="heading">Confirmations</div>
|
||||
|
||||
<FormCheckboxField name="skipConfirm.tableDataSave" label="Skip confirmation when saving table data (SQL)" />
|
||||
@@ -405,6 +425,11 @@ ORDER BY
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
.tip {
|
||||
margin-left: var(--dim-large-form-margin);
|
||||
margin-top: var(--dim-large-form-margin);
|
||||
}
|
||||
|
||||
.themes {
|
||||
overflow-x: scroll;
|
||||
display: flex;
|
||||
|
||||
@@ -36,3 +36,10 @@ export function getConnectionClickActionSetting(): 'connect' | 'openDetails' | '
|
||||
export function getDatabaseClickActionSetting(): 'switch' | 'none' {
|
||||
return getStringSettingsValue('defaultAction.databaseClick', 'switch');
|
||||
}
|
||||
|
||||
export function getOpenDetailOnArrowsSettings(): boolean {
|
||||
return (
|
||||
getBoolSettingsValue('behaviour.useTabPreviewMode', true) &&
|
||||
getBoolSettingsValue('behaviour.openDetailOnArrows', true)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -161,7 +161,7 @@ export const lastUsedDefaultActions = writableWithStorage({}, 'lastUsedDefaultAc
|
||||
export const selectedDatabaseObjectAppObject = writable(null);
|
||||
export const focusedConnectionOrDatabase = writable<{ conid: string; database?: string; connection: any }>(null);
|
||||
|
||||
export const DEFAULT_SEARCH_SETTINGS = {
|
||||
export const DEFAULT_OBJECT_SEARCH_SETTINGS = {
|
||||
collectionName: true,
|
||||
schemaName: false,
|
||||
tableName: true,
|
||||
@@ -175,11 +175,24 @@ export const DEFAULT_SEARCH_SETTINGS = {
|
||||
tableEngine: false,
|
||||
};
|
||||
|
||||
export const DEFAULT_CONNECTION_SEARCH_SETTINGS = {
|
||||
displayName: true,
|
||||
server: true,
|
||||
user: false,
|
||||
engine: false,
|
||||
database: true,
|
||||
};
|
||||
|
||||
export const databaseObjectAppObjectSearchSettings = writableWithStorage(
|
||||
DEFAULT_SEARCH_SETTINGS,
|
||||
DEFAULT_OBJECT_SEARCH_SETTINGS,
|
||||
'databaseObjectAppObjectSearchSettings'
|
||||
);
|
||||
|
||||
export const connectionAppObjectSearchSettings = writableWithStorage(
|
||||
DEFAULT_CONNECTION_SEARCH_SETTINGS,
|
||||
'connectionAppObjectSearchSettings'
|
||||
);
|
||||
|
||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
||||
);
|
||||
@@ -205,6 +218,9 @@ export const visibleTitleBar = derived(useSettings(), $settings => {
|
||||
// console.log('nativeMenuOnStartup', nativeMenuOnStartup);
|
||||
return !$settings['app.fullscreen'] && !nativeMenuOnStartup;
|
||||
});
|
||||
export const alignDataGridNumbersToRight = derived(useSettings(), $settings => {
|
||||
return !!$settings?.['dataGrid.alignNumbersRight'];
|
||||
});
|
||||
|
||||
export const visibleHamburgerMenuWidget = derived(useSettings(), $settings => {
|
||||
const electron = getElectron();
|
||||
@@ -218,6 +234,7 @@ subscribeCssVariable(visibleSelectedWidget, x => (x ? 1 : 0), '--dim-visible-lef
|
||||
subscribeCssVariable(leftPanelWidth, x => `${x}px`, '--dim-left-panel-width');
|
||||
subscribeCssVariable(visibleTitleBar, x => (x ? 1 : 0), '--dim-visible-titlebar');
|
||||
subscribeCssVariable(lockedDatabaseMode, x => (x ? 0 : 1), '--dim-visible-tabs-databases');
|
||||
subscribeCssVariable(alignDataGridNumbersToRight, x => (x ? 'right' : 'left'), '--data-grid-numbers-align');
|
||||
|
||||
let activeTabIdValue = null;
|
||||
activeTabId.subscribe(value => {
|
||||
@@ -379,10 +396,18 @@ lastUsedDefaultActions.subscribe(value => {
|
||||
});
|
||||
export const getLastUsedDefaultActions = () => lastUsedDefaultActionsValue;
|
||||
|
||||
let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_SEARCH_SETTINGS = {
|
||||
...DEFAULT_SEARCH_SETTINGS,
|
||||
let databaseObjectAppObjectSearchSettingsValue: typeof DEFAULT_OBJECT_SEARCH_SETTINGS = {
|
||||
...DEFAULT_OBJECT_SEARCH_SETTINGS,
|
||||
};
|
||||
databaseObjectAppObjectSearchSettings.subscribe(value => {
|
||||
databaseObjectAppObjectSearchSettingsValue = value;
|
||||
});
|
||||
export const getDatabaseObjectAppObjectSearchSettings = () => databaseObjectAppObjectSearchSettingsValue;
|
||||
|
||||
let connectionAppObjectSearchSettingsValue: typeof DEFAULT_CONNECTION_SEARCH_SETTINGS = {
|
||||
...DEFAULT_CONNECTION_SEARCH_SETTINGS,
|
||||
};
|
||||
connectionAppObjectSearchSettings.subscribe(value => {
|
||||
connectionAppObjectSearchSettingsValue = value;
|
||||
});
|
||||
export const getConnectionAppObjectSearchSettings = () => connectionAppObjectSearchSettingsValue;
|
||||
|
||||
@@ -63,15 +63,16 @@
|
||||
|
||||
const testIdRef = createRef(0);
|
||||
|
||||
async function handleTest(e) {
|
||||
async function handleTest(e, requestDbList = false) {
|
||||
isTesting = true;
|
||||
testIdRef.update(x => x + 1);
|
||||
const testid = testIdRef.get();
|
||||
const resp = await apiCall('connections/test', e.detail);
|
||||
const resp = await apiCall('connections/test', { connection: e.detail, requestDbList });
|
||||
if (testIdRef.get() != testid) return;
|
||||
|
||||
isTesting = false;
|
||||
sqlConnectResult = resp;
|
||||
return resp;
|
||||
}
|
||||
|
||||
function handleCancelTest() {
|
||||
@@ -80,6 +81,10 @@
|
||||
}
|
||||
|
||||
function getCurrentConnection() {
|
||||
return getCurrentConnectionCore($values, driver);
|
||||
}
|
||||
|
||||
function getCurrentConnectionCore(values, driver) {
|
||||
const allProps = [
|
||||
'databaseFile',
|
||||
'useDatabaseUrl',
|
||||
@@ -94,15 +99,15 @@
|
||||
'socketPath',
|
||||
'serviceName',
|
||||
];
|
||||
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, $values, { config: $config }));
|
||||
const visibleProps = allProps.filter(x => driver?.showConnectionField(x, values, { config: $config }));
|
||||
const omitProps = _.difference(allProps, visibleProps);
|
||||
if (!$values.defaultDatabase) omitProps.push('singleDatabase');
|
||||
if (!values.defaultDatabase) omitProps.push('singleDatabase');
|
||||
|
||||
let connection: Dictionary<string | boolean> = _.omit($values, omitProps);
|
||||
let connection: Dictionary<string | boolean> = _.omit(values, omitProps);
|
||||
if (driver?.beforeConnectionSave) connection = driver?.beforeConnectionSave(connection);
|
||||
|
||||
if (driver?.showConnectionTab('sshTunnel', $values)) {
|
||||
if (!$values.useSshTunnel) {
|
||||
if (driver?.showConnectionTab('sshTunnel', values)) {
|
||||
if (!values.useSshTunnel) {
|
||||
connection = _.omitBy(connection, (v, k) => k.startsWith('ssh'));
|
||||
}
|
||||
} else {
|
||||
@@ -110,8 +115,8 @@
|
||||
connection = _.omitBy(connection, (v, k) => k.startsWith('ssh'));
|
||||
}
|
||||
|
||||
if (driver?.showConnectionTab('ssl', $values)) {
|
||||
if (!$values.useSsl) {
|
||||
if (driver?.showConnectionTab('ssl', values)) {
|
||||
if (!values.useSsl) {
|
||||
connection = _.omitBy(connection, (v, k) => k.startsWith('ssl'));
|
||||
}
|
||||
} else {
|
||||
@@ -122,6 +127,8 @@
|
||||
return connection;
|
||||
}
|
||||
|
||||
$: currentConnection = getCurrentConnectionCore($values, driver);
|
||||
|
||||
async function handleSave() {
|
||||
let connection = getCurrentConnection();
|
||||
connection = {
|
||||
@@ -190,6 +197,14 @@
|
||||
$: isConnected = $openedConnections.includes($values._id) || $openedSingleDatabaseConnections.includes($values._id);
|
||||
|
||||
// $: console.log('CONN VALUES', $values);
|
||||
|
||||
async function getDatabaseList() {
|
||||
const resp = await handleTest({ detail: getCurrentConnection() }, true);
|
||||
if (resp && resp.msgtype == 'connected') {
|
||||
return resp.databases;
|
||||
}
|
||||
return [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<FormProviderCore template={FormFieldTemplateLarge} {values}>
|
||||
@@ -202,6 +217,7 @@
|
||||
{
|
||||
label: 'General',
|
||||
component: ConnectionDriverFields,
|
||||
props: { getDatabaseList, currentConnection },
|
||||
},
|
||||
driver?.showConnectionTab('sshTunnel', $values) && {
|
||||
label: 'SSH Tunnel',
|
||||
@@ -223,20 +239,24 @@
|
||||
<div class="buttons">
|
||||
{#if onlyTestButton}
|
||||
{#if isTesting}
|
||||
<FormButton value="Cancel test" on:click={handleCancelTest} data-testid="ConnectionTab_buttonCancelTest" />
|
||||
<FormButton
|
||||
value="Cancel test"
|
||||
on:click={handleCancelTest}
|
||||
data-testid="ConnectionTab_buttonCancelTest"
|
||||
/>
|
||||
{:else}
|
||||
<FormButton value="Test connection" on:click={handleTest} data-testid="ConnectionTab_buttonDisconnect" />
|
||||
{/if}
|
||||
{:else if isConnected}
|
||||
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid='ConnectionTab_buttonDisconnect' />
|
||||
<FormButton value="Disconnect" on:click={handleDisconnect} data-testid="ConnectionTab_buttonDisconnect" />
|
||||
{:else}
|
||||
<FormButton value="Connect" on:click={handleConnect} data-testid='ConnectionTab_buttonConnect' />
|
||||
<FormButton value="Connect" on:click={handleConnect} data-testid="ConnectionTab_buttonConnect" />
|
||||
{#if isTesting}
|
||||
<FormButton value="Cancel test" on:click={handleCancelTest} />
|
||||
{:else}
|
||||
<FormButton value="Test" on:click={handleTest} data-testid='ConnectionTab_buttonTest' />
|
||||
<FormButton value="Test" on:click={handleTest} data-testid="ConnectionTab_buttonTest" />
|
||||
{/if}
|
||||
<FormButton value="Save" on:click={handleSave} data-testid='ConnectionTab_buttonSave' />
|
||||
<FormButton value="Save" on:click={handleSave} data-testid="ConnectionTab_buttonSave" />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="test-result">
|
||||
|
||||
@@ -7,6 +7,7 @@ import localforage from 'localforage';
|
||||
import stableStringify from 'json-stable-stringify';
|
||||
import { saveAllPendingEditorData } from '../query/useEditorData';
|
||||
import { getConnectionInfo } from './metadataLoaders';
|
||||
import { getBoolSettingsValue } from '../settings/settingsTools';
|
||||
|
||||
function findFreeNumber(numbers: number[]) {
|
||||
if (numbers.length == 0) return 1;
|
||||
@@ -22,6 +23,13 @@ export default async function openNewTab(newTab, initialData: any = undefined, o
|
||||
|
||||
let existing = null;
|
||||
|
||||
if (!getBoolSettingsValue('behaviour.useTabPreviewMode', true) && newTab.tabPreviewMode) {
|
||||
newTab = {
|
||||
...newTab,
|
||||
tabPreviewMode: false,
|
||||
};
|
||||
}
|
||||
|
||||
const { savedFile, savedFolder, savedFilePath, conid, database } = newTab.props || {};
|
||||
|
||||
if (conid && database) {
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
let isListFocused = false;
|
||||
let domDiv = null;
|
||||
let lastInputMethod = null;
|
||||
export let hideContent = false;
|
||||
|
||||
function handleKeyDown(ev) {
|
||||
@@ -25,6 +26,11 @@
|
||||
const selected = getSelectedObject();
|
||||
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
|
||||
|
||||
if (index < 0) {
|
||||
focusFirst();
|
||||
return;
|
||||
}
|
||||
|
||||
if (index == 0 && diff < 0) {
|
||||
onFocusFilterBox?.();
|
||||
return;
|
||||
@@ -41,7 +47,7 @@
|
||||
|
||||
if (listInstance[newIndex]) {
|
||||
selectedObjectStore.set(listInstance[newIndex]);
|
||||
handleObjectClick?.(listInstance[newIndex], { tabPreviewMode: true });
|
||||
handleObjectClick?.(listInstance[newIndex], 'keyArrow');
|
||||
}
|
||||
|
||||
if (newIndex == 0) {
|
||||
@@ -58,7 +64,7 @@
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.enter) {
|
||||
handleObjectClick?.(getSelectedObject(), { tabPreviewMode: false, focusTab: true });
|
||||
handleObjectClick?.(getSelectedObject(), 'keyEnter');
|
||||
ev.preventDefault();
|
||||
}
|
||||
if (ev.keyCode == keycodes.pageDown) {
|
||||
@@ -72,14 +78,14 @@
|
||||
if (ev.keyCode == keycodes.home) {
|
||||
if (listInstance[0]) {
|
||||
selectedObjectStore.set(listInstance[0]);
|
||||
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
|
||||
handleObjectClick?.(listInstance[0], 'keyArrow');
|
||||
onScrollTop?.();
|
||||
}
|
||||
}
|
||||
if (ev.keyCode == keycodes.end) {
|
||||
if (listInstance[listInstance.length - 1]) {
|
||||
selectedObjectStore.set(listInstance[listInstance.length - 1]);
|
||||
handleObjectClick?.(listInstance[listInstance.length - 1], { tabPreviewMode: true });
|
||||
handleObjectClick?.(listInstance[listInstance.length - 1], 'keyArrow');
|
||||
}
|
||||
}
|
||||
if (ev.keyCode == keycodes.numPadAdd) {
|
||||
@@ -110,17 +116,18 @@
|
||||
domDiv?.focus();
|
||||
if (listInstance[0]) {
|
||||
selectedObjectStore.set(listInstance[0]);
|
||||
handleObjectClick?.(listInstance[0], { tabPreviewMode: true });
|
||||
handleObjectClick?.(listInstance[0], 'keyArrow');
|
||||
onScrollTop?.();
|
||||
}
|
||||
}
|
||||
|
||||
async function handleFocus() {
|
||||
async function handleFocus(e) {
|
||||
isListFocused = true;
|
||||
|
||||
// await tick();
|
||||
await sleep(100);
|
||||
// console.log('ON FOCUS AFTER SLEEP');
|
||||
if (lastInputMethod == 'mouse') {
|
||||
return;
|
||||
}
|
||||
|
||||
const listInstance = _.isFunction(list) ? list() : list;
|
||||
const selected = getSelectedObject();
|
||||
const index = _.findIndex(listInstance, x => selectedObjectMatcher(x, selected));
|
||||
@@ -130,7 +137,7 @@
|
||||
const index2 = _.findIndex(listInstance, x => selectedObjectMatcher(x, focused));
|
||||
if (index2 >= 0) {
|
||||
selectedObjectStore.set(focused);
|
||||
handleObjectClick?.(focused, { tabPreviewMode: true });
|
||||
handleObjectClick?.(focused, 'keyArrow');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -139,6 +146,15 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window
|
||||
on:keydown={() => {
|
||||
lastInputMethod = 'keyboard';
|
||||
}}
|
||||
on:mousedown={() => {
|
||||
lastInputMethod = 'mouse';
|
||||
}}
|
||||
/>
|
||||
|
||||
<div
|
||||
tabindex="0"
|
||||
on:keydown={handleKeyDown}
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
getFocusedConnectionOrDatabase,
|
||||
currentDatabase,
|
||||
getCurrentConfig,
|
||||
connectionAppObjectSearchSettings,
|
||||
getConnectionAppObjectSearchSettings,
|
||||
} from '../stores';
|
||||
import runCommand from '../commands/runCommand';
|
||||
import { filterName, getConnectionLabel } from 'dbgate-tools';
|
||||
@@ -38,7 +40,13 @@
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import openNewTab from '../utility/openNewTab';
|
||||
import { openConnection } from '../appobj/ConnectionAppObject.svelte';
|
||||
import { getConnectionClickActionSetting } from '../settings/settingsTools';
|
||||
import {
|
||||
getBoolSettingsValue,
|
||||
getConnectionClickActionSetting,
|
||||
getDatabaseClickActionSetting,
|
||||
getOpenDetailOnArrowsSettings,
|
||||
} from '../settings/settingsTools';
|
||||
import DropDownButton from '../buttons/DropDownButton.svelte';
|
||||
|
||||
const connections = useConnectionList();
|
||||
const serverStatus = useServerStatus();
|
||||
@@ -182,6 +190,21 @@
|
||||
{ text: 'Delete', onClick: handleDelete },
|
||||
];
|
||||
}
|
||||
|
||||
function createSearchMenu() {
|
||||
const res = [];
|
||||
res.push({ label: 'Search by:', isBold: true, disabled: true });
|
||||
res.push({ label: 'Display name', switchValue: 'displayName' });
|
||||
res.push({ label: 'Server', switchValue: 'server' });
|
||||
res.push({ label: 'User', switchValue: 'user' });
|
||||
res.push({ label: 'Database engine', switchValue: 'engine' });
|
||||
res.push({ label: 'Database name', switchValue: 'database' });
|
||||
return res.map(item => ({
|
||||
...item,
|
||||
switchStore: connectionAppObjectSearchSettings,
|
||||
switchStoreGetter: getConnectionAppObjectSearchSettings,
|
||||
}));
|
||||
}
|
||||
</script>
|
||||
|
||||
<SearchBoxWrapper>
|
||||
@@ -194,6 +217,7 @@
|
||||
}}
|
||||
/>
|
||||
<CloseSearchButton bind:filter />
|
||||
<DropDownButton icon="icon filter" menu={createSearchMenu} square={!!filter} narrow={false} />
|
||||
{#if $commandsCustomized['new.connection']?.enabled}
|
||||
<InlineButton
|
||||
on:click={() => runCommand('new.connection')}
|
||||
@@ -239,30 +263,35 @@
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
handleObjectClick={(data, options) => {
|
||||
handleObjectClick={(data, clickAction) => {
|
||||
const connectionClickAction = getConnectionClickActionSetting();
|
||||
const databaseClickAction = getDatabaseClickActionSetting();
|
||||
const openDetailOnArrows = getOpenDetailOnArrowsSettings();
|
||||
|
||||
if (data.database) {
|
||||
if (options.focusTab) {
|
||||
if ($openedSingleDatabaseConnections.includes(data.conid)) {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
} else {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
// console.log('FOCUSING DB', passProps);
|
||||
// passProps?.onFocusSqlObjectList?.();
|
||||
if (databaseClickAction == 'switch' && clickAction == 'leftClick') {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
|
||||
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
} else {
|
||||
if (options.focusTab) {
|
||||
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
|
||||
openConnection(data.connection);
|
||||
} else {
|
||||
const config = getCurrentConfig();
|
||||
if (config.runAsPortal == false && !config.storageDatabase && connectionClickAction == 'openDetails') {
|
||||
if (
|
||||
config.runAsPortal == false &&
|
||||
!config.storageDatabase &&
|
||||
connectionClickAction == 'openDetails' &&
|
||||
(clickAction == 'leftClick' || (clickAction == 'keyArrow' && openDetailOnArrows))
|
||||
) {
|
||||
openNewTab({
|
||||
title: getConnectionLabel(data.connection),
|
||||
icon: 'img connection',
|
||||
tabComponent: 'ConnectionTab',
|
||||
tabPreviewMode: options.tabPreviewMode,
|
||||
tabPreviewMode: true,
|
||||
props: {
|
||||
conid: data.conid,
|
||||
},
|
||||
@@ -289,6 +318,7 @@
|
||||
...passProps,
|
||||
connectionColorFactory: $connectionColorFactory,
|
||||
showPinnedInsteadOfUnpin: true,
|
||||
searchSettings: $connectionAppObjectSearchSettings,
|
||||
}}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
@@ -316,6 +346,7 @@
|
||||
passProps={{
|
||||
connectionColorFactory: $connectionColorFactory,
|
||||
showPinnedInsteadOfUnpin: true,
|
||||
searchSettings: $connectionAppObjectSearchSettings,
|
||||
}}
|
||||
getIsExpanded={data => $expandedConnections.includes(data._id) && !data.singleDatabase}
|
||||
setIsExpanded={(data, value) => {
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
import { filterName } from 'dbgate-tools';
|
||||
import { currentDatabase, focusedConnectionOrDatabase, getFocusedConnectionOrDatabase } from '../stores';
|
||||
import { switchCurrentDatabase } from '../utility/common';
|
||||
import { getConnectionClickActionSetting, getDatabaseClickActionSetting } from '../settings/settingsTools';
|
||||
|
||||
export let connection;
|
||||
|
||||
@@ -81,9 +82,16 @@
|
||||
onFocusFilterBox={text => {
|
||||
domFilter?.focus(text);
|
||||
}}
|
||||
handleObjectClick={(data, options) => {
|
||||
handleObjectClick={(data, clickAction) => {
|
||||
const connectionClickAction = getConnectionClickActionSetting();
|
||||
const databaseClickAction = getDatabaseClickActionSetting();
|
||||
|
||||
if (data.database) {
|
||||
if (options.focusTab) {
|
||||
if (databaseClickAction == 'switch' && clickAction == 'leftClick') {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
|
||||
if (clickAction == 'keyEnter' || clickAction == 'dblClick') {
|
||||
switchCurrentDatabase({ connection: data.connection, name: data.database });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,8 +129,9 @@
|
||||
|
||||
function createSearchMenu() {
|
||||
const res = [];
|
||||
res.push({ label: 'Search by:', isBold: true, disabled: true });
|
||||
if (driver?.databaseEngineTypes?.includes('document')) {
|
||||
res.push({ label: 'Collection names' });
|
||||
res.push({ label: 'Collection names', switchValue: 'collectionName' });
|
||||
}
|
||||
if (driver?.databaseEngineTypes?.includes('sql')) {
|
||||
res.push({ label: 'Schema name', switchValue: 'schemaName' });
|
||||
@@ -216,9 +217,7 @@
|
||||
}}
|
||||
/>
|
||||
<CloseSearchButton bind:filter />
|
||||
{#if filter}
|
||||
<DropDownButton icon="icon filter" menu={createSearchMenu} />
|
||||
{/if}
|
||||
<DropDownButton icon="icon filter" menu={createSearchMenu} square={!!filter} narrow={false} />
|
||||
{#if !filter}
|
||||
<DropDownButton icon="icon plus-thick" menu={createAddMenu} />
|
||||
{/if}
|
||||
@@ -250,7 +249,7 @@
|
||||
selectedObjectStore={selectedDatabaseObjectAppObject}
|
||||
getSelectedObject={getSelectedDatabaseObjectAppObject}
|
||||
selectedObjectMatcher={matchDatabaseObjectAppObject}
|
||||
handleObjectClick={(data, options) => databaseObjectAppObject.handleObjectClick(data, options)}
|
||||
handleObjectClick={(data, clickAction) => databaseObjectAppObject.handleObjectClick(data, clickAction)}
|
||||
onScrollTop={() => {
|
||||
domContainer?.scrollTop();
|
||||
}}
|
||||
|
||||
@@ -3,7 +3,7 @@ const stream = require('stream');
|
||||
|
||||
const driverBase = require('../frontend/driver');
|
||||
const Analyser = require('./Analyser');
|
||||
const { createBulkInsertStreamBase, makeUniqueColumnNames } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
const { makeUniqueColumnNames } = global.DBGATE_PACKAGES['dbgate-tools'];
|
||||
const createOracleBulkInsertStream = require('./createOracleBulkInsertStream');
|
||||
|
||||
let platformInfo;
|
||||
@@ -12,16 +12,11 @@ let oracledbValue;
|
||||
function getOracledb() {
|
||||
if (!oracledbValue) {
|
||||
oracledbValue = require('oracledb');
|
||||
oracledbValue.fetchAsString = [oracledbValue.CLOB, oracledbValue.NCLOB];
|
||||
}
|
||||
return oracledbValue;
|
||||
}
|
||||
|
||||
/*
|
||||
pg.types.setTypeParser(1082, 'text', val => val); // date
|
||||
pg.types.setTypeParser(1114, 'text', val => val); // timestamp without timezone
|
||||
pg.types.setTypeParser(1184, 'text', val => val); // timestamp
|
||||
*/
|
||||
|
||||
function extractOracleColumns(result) {
|
||||
if (!result /*|| !result.fields */) return [];
|
||||
const res = result.map(fld => ({
|
||||
|
||||
@@ -30,12 +30,12 @@
|
||||
"prepublishOnly": "yarn build"
|
||||
},
|
||||
"devDependencies": {
|
||||
"wkx": "^0.5.0",
|
||||
"dbgate-plugin-tools": "^1.0.7",
|
||||
"webpack": "^5.91.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"wkx": "^0.5.0",
|
||||
"pg-copy-streams": "^6.0.6",
|
||||
"dbgate-query-splitter": "^4.11.2",
|
||||
"dbgate-tools": "^6.0.0-alpha.1",
|
||||
|
||||
@@ -134,16 +134,8 @@ const drivers = driverBases.map(driverBase => ({
|
||||
database,
|
||||
};
|
||||
|
||||
const datatypes = await this.query(
|
||||
dbhan,
|
||||
`SELECT oid AS datatypeid, typname AS datatypename FROM pg_type WHERE typname in ('geography')`
|
||||
);
|
||||
|
||||
const typeIdToName = datatypes.rows.reduce((acc, cur) => {
|
||||
acc[cur.datatypeid] = cur.datatypename;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const datatypes = await this.query(dbhan, `SELECT oid, typname FROM pg_type WHERE typname in ('geography')`);
|
||||
const typeIdToName = _.fromPairs(datatypes.rows.map(cur => [cur.oid, cur.typname]));
|
||||
dbhan['typeIdToName'] = typeIdToName;
|
||||
|
||||
if (isReadOnly) {
|
||||
|
||||
@@ -2685,10 +2685,10 @@ accepts@~1.3.8:
|
||||
mime-types "~2.1.34"
|
||||
negotiator "0.6.3"
|
||||
|
||||
ace-builds@^1.4.8:
|
||||
version "1.33.1"
|
||||
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.33.1.tgz#a37904fb2d37756733346c688dcb235662877d6e"
|
||||
integrity sha512-pj5mcXV1n3s86UI4SWUt8X0ltN8cTaYcvF76cSmvy5i2ZDtXX9KkjVcYTGkCV7ox6VUrzqHByeqH0xRsMjXi4g==
|
||||
ace-builds@^1.36.5:
|
||||
version "1.36.5"
|
||||
resolved "https://registry.yarnpkg.com/ace-builds/-/ace-builds-1.36.5.tgz#ae9cc7a32eccc2f484926131c00545cd6b78a6a6"
|
||||
integrity sha512-mZ5KVanRT6nLRDLqtG/1YQQLX/gZVC/v526cm1Ru/MTSlrbweSmqv2ZT0d2GaHpJq035MwCMIrj+LgDAUnDXrg==
|
||||
|
||||
acorn-globals@^4.1.0:
|
||||
version "4.3.4"
|
||||
|
||||
Reference in New Issue
Block a user