Merge branch 'redis'

This commit is contained in:
Jan Prochazka
2022-03-16 18:41:17 +01:00
51 changed files with 1446 additions and 65 deletions

View File

@@ -8,8 +8,8 @@
export let icon;
export let title;
export let data;
export let module;
export let data = null;
export let module = null;
export let isBold = false;
export let isBusy = false;
@@ -24,8 +24,10 @@
export let onPin = null;
export let onUnpin = null;
export let showPinnedInsteadOfUnpin = false;
export let indentLevel = 0;
$: isChecked = checkedObjectsStore && $checkedObjectsStore.find(x => module.extractKey(data) == module.extractKey(x));
$: isChecked =
checkedObjectsStore && $checkedObjectsStore.find(x => module?.extractKey(data) == module?.extractKey(x));
function handleExpand() {
dispatch('expand');
@@ -33,7 +35,7 @@
function handleClick() {
if (checkedObjectsStore) {
if (isChecked) {
checkedObjectsStore.update(x => x.filter(y => module.extractKey(data) != module.extractKey(y)));
checkedObjectsStore.update(x => x.filter(y => module?.extractKey(data) != module?.extractKey(y)));
} else {
checkedObjectsStore.update(x => [...x, data]);
}
@@ -52,12 +54,14 @@
function setChecked(value) {
if (!value && isChecked) {
checkedObjectsStore.update(x => x.filter(y => module.extractKey(data) != module.extractKey(y)));
checkedObjectsStore.update(x => x.filter(y => module?.extractKey(data) != module?.extractKey(y)));
}
if (value && !isChecked) {
checkedObjectsStore.update(x => [...x, data]);
}
}
// $: console.log(title, indentLevel);
</script>
<div
@@ -85,6 +89,9 @@
<FontIcon icon={expandIcon} />
</span>
{/if}
{#if indentLevel}
<span style:margin-right={`${indentLevel * 16}px`} />
{/if}
{#if isBusy}
<FontIcon icon="icon loading" />
{:else}
@@ -188,5 +195,4 @@
float: right;
color: var(--theme-font-2);
}
</style>

View File

@@ -174,8 +174,8 @@
return [
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
!driver?.dialect?.nosql && { onClick: handleNewTable, text: 'New table' },
driver?.dialect?.nosql && { onClick: handleNewCollection, text: 'New collection' },
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleNewTable, text: 'New table' },
driver?.databaseEngineTypes?.includes('document') && { onClick: handleNewCollection, text: 'New collection' },
{ divider: true },
{ onClick: handleImport, text: 'Import' },
{ onClick: handleExport, text: 'Export' },
@@ -256,6 +256,7 @@
{...$$restProps}
{data}
title={data.name}
extInfo={data.extInfo}
icon="img database"
colorMark={passProps?.connectionColorFactory &&
passProps?.connectionColorFactory({ conid: _.get(data.connection, '_id'), database: data.name }, null, null, false)}

View File

@@ -15,7 +15,7 @@
<AppObjectList
list={_.sortBy(
($databases || []).filter(x => filterName(filter, x.name)),
'name'
x => x.sortOrder ?? x.name
).map(db => ({ ...db, connection: data }))}
module={databaseAppObject}
{passProps}

View File

@@ -4,6 +4,7 @@ import { get } from 'svelte/store';
import { ThemeDefinition } from 'dbgate-types';
import ConnectionModal from '../modals/ConnectionModal.svelte';
import AboutModal from '../modals/AboutModal.svelte';
import AddDbKeyModal from '../modals/AddDbKeyModal.svelte';
import SettingsModal from '../settings/SettingsModal.svelte';
import ImportExportModal from '../modals/ImportExportModal.svelte';
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
@@ -158,7 +159,7 @@ registerCommand({
toolbarName: 'New table',
testEnabled: () => {
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
return !!get(currentDatabase) && !driver?.dialect?.nosql;
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('sql');
},
onClick: () => {
const $currentDatabase = get(currentDatabase);
@@ -196,7 +197,7 @@ registerCommand({
toolbarName: 'New collection',
testEnabled: () => {
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
return !!get(currentDatabase) && driver?.dialect?.nosql;
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('document');
},
onClick: async () => {
const $currentDatabase = get(currentDatabase);
@@ -217,6 +218,30 @@ registerCommand({
},
});
registerCommand({
id: 'new.dbKey',
category: 'New',
name: 'Key',
toolbar: true,
toolbarName: 'New key',
testEnabled: () => {
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('keyvalue');
},
onClick: async () => {
const $currentDatabase = get(currentDatabase);
const connection = _.get($currentDatabase, 'connection') || {};
const database = _.get($currentDatabase, 'name');
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
showModal(AddDbKeyModal, {
conid: connection._id,
database,
driver,
});
},
});
registerCommand({
id: 'new.markdown',
category: 'New',

View File

@@ -266,7 +266,7 @@
class:isOk
placeholder="Filter"
/>
{#if conid && database && driver && !driver?.dialect?.nosql}
{#if conid && database && driver && driver?.databaseEngineTypes?.includes('sql')}
{#if foreignKey}
<InlineButton on:click={handleShowDictionary} narrow square>
<FontIcon icon="icon dots-horizontal" />

View File

@@ -0,0 +1,95 @@
<script lang="ts">
import { onMount, tick } from 'svelte';
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
import { apiCall } from '../utility/api';
import createRef from '../utility/createRef';
export let conid;
export let database;
export let keyInfo;
export let onChangeSelected;
let rows = [];
let cursor = 0;
let isLoading = false;
let loadNextNeeded = false;
let isLoadedAll = false;
let selectedIndex;
const oldIndexRef = createRef(null);
async function loadNextRows() {
if (isLoadedAll) {
return;
}
if (isLoading) {
// console.log('ALREADY LOADING');
loadNextNeeded = true;
return;
}
isLoading = true;
try {
const res = await apiCall('database-connections/load-key-table-range', {
conid,
database,
key: keyInfo.key,
cursor,
count: 10,
});
const newRows = [...rows];
for (const row of res.items) {
if (keyInfo.keyColumn && newRows.find(x => x[keyInfo.keyColumn] == row[keyInfo.keyColumn])) {
continue;
}
newRows.push({ rowNumber: newRows.length + 1, ...row });
}
rows = newRows;
cursor = res.cursor;
isLoadedAll = cursor == 0;
} finally {
isLoading = false;
}
if (loadNextNeeded) {
loadNextNeeded = false;
await tick();
loadNextRows();
}
}
$: {
if (onChangeSelected && rows[selectedIndex]) {
if (oldIndexRef.get() != selectedIndex) {
oldIndexRef.set(selectedIndex);
onChangeSelected(rows[selectedIndex]);
}
}
}
$: {
keyInfo;
}
onMount(() => {
loadNextRows();
});
</script>
<ScrollableTableControl
columns={[
{
fieldName: 'rowNumber',
header: 'num',
width: '60px',
},
...keyInfo.keyType.dbKeyFields.map(column => ({
fieldName: column.name,
header: column.name,
})),
]}
{rows}
onLoadNext={isLoadedAll ? null : loadNextRows}
selectable
singleLineRow
bind:selectedIndex
/>

View File

@@ -0,0 +1,44 @@
<script lang="ts">
import AceEditor from '../query/AceEditor.svelte';
export let dbKeyFields;
export let item;
export let onChangeItem = null;
</script>
<div class="props">
{#each dbKeyFields as column}
<div class="colname">{column.name}</div>
<div class="colvalue">
<AceEditor
readOnly={!onChangeItem}
value={item && item[column.name]}
on:input={e => {
if (onChangeItem) {
onChangeItem({
...item,
[column.name]: e.detail,
});
}
}}
/>
</div>
{/each}
</div>
<style>
.props {
flex: 1;
display: flex;
flex-direction: column;
}
.colname {
margin: 10px;
}
.colvalue {
position: relative;
flex: 1;
}
</style>

View File

@@ -15,7 +15,7 @@
<script lang="ts">
import _ from 'lodash';
import { onMount } from 'svelte';
import { onDestroy, onMount } from 'svelte';
import keycodes from '../utility/keycodes';
import { createEventDispatcher } from 'svelte';
import resizeObserver from '../utility/resizeObserver';
@@ -27,6 +27,8 @@
export let selectedIndex = 0;
export let clickable = false;
export let disableFocusOutline = false;
export let onLoadNext = null;
export let singleLineRow = false;
export let domTable = undefined;
@@ -34,6 +36,36 @@
let headerHeight = 0;
let domBody;
let domLoadNext;
let observer;
function startObserver(dom) {
if (observer) {
// console.log('STOP OBSERVE');
observer.disconnect();
observer = null;
}
if (dom) {
// console.log('OBSERVE');
observer = new IntersectionObserver(entries => {
// console.log('ENTRIES', entries);
if (entries.find(x => x.isIntersecting)) {
// console.log('INVOKE LOAD NEXT');
onLoadNext();
}
});
observer.observe(dom);
}
}
$: startObserver(domLoadNext);
onDestroy(() => {
if (observer) {
observer.disconnect();
}
});
const dispatch = createEventDispatcher();
$: columnList = _.compact(_.flatten(columns));
@@ -86,6 +118,7 @@
bind:this={domTable}
class:selectable
class:disableFocusOutline
class:singleLineRow
on:keydown
tabindex={selectable ? -1 : undefined}
on:keydown={handleKeyDown}
@@ -163,6 +196,13 @@
{/each}
</tr>
{/each}
{#if onLoadNext}
{#key rows}
<tr>
<td colspan={columnList.length} bind:this={domLoadNext}> Loading next rows... </td>
</tr>
{/key}
{/if}
</tbody>
</table>
</div>
@@ -193,6 +233,11 @@
table tbody tr td {
overflow: hidden;
}
table.singleLineRow tbody tr td {
white-space: nowrap;
}
table tbody {
display: block;
overflow-y: scroll;

View File

@@ -8,4 +8,13 @@
if (focused) onMount(() => domEditor.focus());
</script>
<input type="text" {...$$restProps} bind:value on:change on:input bind:this={domEditor} on:keydown autocomplete="new-password" />
<input
type="text"
{...$$restProps}
bind:value
on:change
on:input
bind:this={domEditor}
on:keydown
autocomplete="new-password"
/>

View File

@@ -167,6 +167,16 @@
'img link': 'mdi mdi-link',
'img filter': 'mdi mdi-filter',
'img group': 'mdi mdi-group',
'img folder': 'mdi mdi-folder color-icon-yellow',
'img type-string': 'mdi mdi-alphabetical color-icon-blue',
'img type-hash': 'mdi mdi-pound color-icon-blue',
'img type-set': 'mdi mdi-format-list-bulleted color-icon-blue',
'img type-list': 'mdi mdi-format-list-numbered color-icon-blue',
'img type-zset': 'mdi mdi-format-list-checks color-icon-blue',
'img type-stream': 'mdi mdi-view-stream color-icon-blue',
'img type-binary': 'mdi mdi-file color-icon-blue',
'img type-rejson': 'mdi mdi-color-json color-icon-blue',
};
</script>

View File

@@ -0,0 +1,58 @@
<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import SelectField from '../forms/SelectField.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
export let conid;
export let database;
export let driver;
export let onConfirm;
let item = {};
let type = driver.supportedKeyTypes[0].name;
const handleSubmit = async () => {
closeCurrentModal();
onConfirm(item);
};
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Add item</svelte:fragment>
<div class="container">
<SelectField
options={driver.supportedKeyTypes.map(t => ({ value: t.name, label: t.label }))}
value={type}
on:change={e => {
type = e.detail;
}}
/>
<DbKeyItemDetail
dbKeyFields={driver.supportedKeyTypes.find(x => x.name == type).dbKeyFields}
{item}
onChangeItem={value => {
item = value;
}}
/>
</div>
<svelte:fragment slot="footer">
<FormStyledButton value="OK" on:click={e => handleSubmit()} />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
<style>
.container {
display: flex;
height: 30vh;
}
</style>

View File

@@ -26,6 +26,9 @@
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
$: driver = $extensions.drivers.find(x => x.engine == engine);
$: defaultDatabase = $values.defaultDatabase;
$: showUser = !driver?.showConnectionField || driver.showConnectionField('user', $values);
$: showPassword = !driver?.showConnectionField || driver.showConnectionField('password', $values);
</script>
<FormSelectField
@@ -100,17 +103,19 @@
</div>
{/if}
{#if !driver?.showConnectionField || driver.showConnectionField('user', $values)}
{#if showUser && showPassword}
<div class="row">
<div class="col-6 mr-1">
<FormTextField
label="User"
name="user"
disabled={disabledFields.includes('user')}
templateProps={{ noMargin: true }}
/>
</div>
{#if !driver?.showConnectionField || driver.showConnectionField('password', $values)}
{#if showUser}
<div class="col-6 mr-1">
<FormTextField
label="User"
name="user"
disabled={disabledFields.includes('user')}
templateProps={{ noMargin: true }}
/>
</div>
{/if}
{#if showPassword}
<div class="col-6 mr-1">
<FormPasswordField
label="Password"
@@ -122,8 +127,14 @@
{/if}
</div>
{/if}
{#if showUser && !showPassword}
<FormTextField label="User" name="user" disabled={disabledFields.includes('user')} />
{/if}
{#if !showUser && showPassword}
<FormPasswordField label="Password" name="password" disabled={disabledFields.includes('password')} />
{/if}
{#if !disabledFields.includes('password') && (!driver?.showConnectionField || driver.showConnectionField('password', $values))}
{#if !disabledFields.includes('password') && showPassword}
<FormSelectField
label="Password mode"
isNative

View File

@@ -0,0 +1,47 @@
<script lang="ts">
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
import FormProvider from '../forms/FormProvider.svelte';
import ModalBase from './ModalBase.svelte';
import { closeCurrentModal } from './modalTools';
export let keyInfo;
export let label;
export let onConfirm;
let item = {};
const handleSubmit = async () => {
closeCurrentModal();
onConfirm(item);
};
</script>
<FormProvider>
<ModalBase {...$$restProps}>
<svelte:fragment slot="header">Add item</svelte:fragment>
<div class="container">
<DbKeyItemDetail
dbKeyFields={keyInfo.keyType.dbKeyFields}
{item}
onChangeItem={value => {
item = value;
}}
/>
</div>
<svelte:fragment slot="footer">
<FormStyledButton value="OK" on:click={e => handleSubmit()} />
<FormStyledButton type="button" value="Cancel" on:click={closeCurrentModal} />
</svelte:fragment>
</ModalBase>
</FormProvider>
<style>
.container {
display: flex;
height: 30vh;
}
</style>

View File

@@ -93,6 +93,7 @@ export const loadingPluginStore = writable({
loaded: false,
loadingPackageName: null,
});
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
$extensions.themes.find(x => x.themeClassName == $currentTheme)

View File

@@ -0,0 +1,183 @@
<script lang="ts" context="module">
import createActivator, { getActiveComponent } from '../utility/createActivator';
const getCurrentEditor = () => getActiveComponent('DbKeyDetailTab');
export const matchingProps = ['conid', 'database', 'isDefaultBrowser'];
export const allowAddToFavorites = props => true;
</script>
<script lang="ts">
import { activeDbKeysStore } from '../stores';
import { apiCall } from '../utility/api';
import LoadingInfo from '../elements/LoadingInfo.svelte';
import ScrollableTableControl from '../elements/ScrollableTableControl.svelte';
import AceEditor from '../query/AceEditor.svelte';
import VerticalSplitter from '../elements/VerticalSplitter.svelte';
import FormStyledButton from '../buttons/FormStyledButton.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import { getIconForRedisType } from 'dbgate-tools';
import TextField from '../forms/TextField.svelte';
import DbKeyTableControl from '../datagrid/DbKeyTableControl.svelte';
import { showModal } from '../modals/modalTools';
import InputTextModal from '../modals/InputTextModal.svelte';
import _ from 'lodash';
import DbKeyItemDetail from '../dbkeyvalue/DbKeyItemDetail.svelte';
import DbKeyAddItemModal from '../modals/DbKeyAddItemModal.svelte';
export let conid;
export let database;
export let key;
export let isDefaultBrowser = false;
export const activator = createActivator('DbKeyDetailTab', true);
let currentRow;
$: key = $activeDbKeysStore[`${conid}:${database}`];
let refreshToken = 0;
let editedValue = null;
function handleChangeTtl(keyInfo) {
showModal(InputTextModal, {
value: keyInfo.ttl,
label: 'New TTL value (-1=key never expires)',
header: `Set TTL for key ${keyInfo.key}`,
onConfirm: async value => {
const ttl = parseInt(value);
if (_.isNumber(ttl)) {
if (ttl < 0) {
await apiCall('database-connections/call-method', {
conid,
database,
method: 'persist',
args: [keyInfo.key],
});
} else {
await apiCall('database-connections/call-method', {
conid,
database,
method: 'expire',
args: [keyInfo.key, ttl],
});
}
refresh();
}
},
});
}
function refresh() {
editedValue = null;
refreshToken += 1;
}
async function saveString() {
await apiCall('database-connections/call-method', {
conid,
database,
method: 'set',
args: [key, editedValue],
});
refresh();
}
async function addItem(keyInfo) {
showModal(DbKeyAddItemModal, {
keyInfo,
onConfirm: async row => {
await apiCall('database-connections/call-method', {
conid,
database,
method: keyInfo.keyType.addMethod,
args: [keyInfo.key, ...keyInfo.keyType.dbKeyFields.map(col => row[col.name])],
});
refresh();
},
});
}
</script>
{#await apiCall('database-connections/load-key-info', { conid, database, key, refreshToken })}
<LoadingInfo message="Loading key details" wrapper />
{:then keyInfo}
<div class="container">
<div class="top-panel">
<div class="type">
<FontIcon icon={getIconForRedisType(keyInfo.type)} padRight />
{keyInfo.type}
</div>
<div class="key-name">
<TextField value={key} readOnly />
</div>
<FormStyledButton value={`TTL:${keyInfo.ttl}`} on:click={() => handleChangeTtl(keyInfo)} />
{#if keyInfo.type == 'string'}
<FormStyledButton value="Save" on:click={saveString} disabled={!editedValue} />
{/if}
{#if keyInfo.keyType?.addMethod && keyInfo.keyType?.showItemList}
<FormStyledButton value="Add item" on:click={() => addItem(keyInfo)} />
{/if}
<FormStyledButton value="Refresh" on:click={refresh} />
</div>
<div class="content">
{#if keyInfo.keyType?.dbKeyFields && keyInfo.keyType?.showItemList}
<VerticalSplitter>
<svelte:fragment slot="1">
<DbKeyTableControl
{conid}
{database}
{keyInfo}
onChangeSelected={row => {
currentRow = row;
}}
/>
</svelte:fragment>
<svelte:fragment slot="2">
<DbKeyItemDetail dbKeyFields={keyInfo.keyType.dbKeyFields} item={currentRow} />
</svelte:fragment>
</VerticalSplitter>
{:else}
<AceEditor
value={editedValue || keyInfo.value}
on:input={e => {
editedValue = e.detail;
}}
/>
{/if}
</div>
</div>
{/await}
<style>
.container {
display: flex;
flex-direction: column;
flex: 1;
}
.content {
flex: 1;
position: relative;
}
.top-panel {
display: flex;
background: var(--theme-bg-2);
}
.type {
font-weight: bold;
margin-right: 10px;
align-self: center;
}
.key-name {
flex-grow: 1;
display: flex;
}
.key-name :global(input) {
flex-grow: 1;
}
</style>

View File

@@ -124,7 +124,7 @@
}
export function isSqlEditor() {
return !driver?.dialect?.nosql;
return driver?.databaseEngineTypes?.includes('sql');
}
export function canKill() {
@@ -281,7 +281,7 @@
<ToolStripContainer>
<VerticalSplitter isSplitter={visibleResultTabs}>
<svelte:fragment slot="1">
{#if driver?.dialect?.nosql}
{#if driver?.databaseEngineTypes?.includes('document')}
<AceEditor
mode="javascript"
value={$editorState.value || ''}

View File

@@ -21,6 +21,7 @@ import * as CompareModelTab from './CompareModelTab.svelte';
import * as JsonTab from './JsonTab.svelte';
import * as ChangelogTab from './ChangelogTab.svelte';
import * as DiagramTab from './DiagramTab.svelte';
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
export default {
TableDataTab,
@@ -46,4 +47,5 @@ export default {
JsonTab,
ChangelogTab,
DiagramTab,
DbKeyDetailTab,
};

View File

@@ -78,6 +78,12 @@ const databaseListLoader = ({ conid }) => ({
},
});
const databaseKeysLoader = ({ conid, database, root }) => ({
url: 'database-connections/load-keys',
params: { conid, database, root },
reloadTrigger: `database-keys-changed-${conid}-${database}`,
});
const serverVersionLoader = ({ conid }) => ({
url: 'server-connections/version',
params: { conid },
@@ -429,3 +435,10 @@ export function getAuthTypes(args) {
export function useAuthTypes(args) {
return useCore(authTypesLoader, args);
}
export function getDatabaseKeys(args) {
return getCore(databaseKeysLoader, args);
}
export function useDatabaseKeys(args) {
return useCore(databaseKeysLoader, args);
}

View File

@@ -39,7 +39,7 @@
];
function autodetect(selection) {
if (selection[0]?.engine?.dialect?.nosql) {
if (selection[0]?.engine?.databaseEngineTypes?.includes('document')) {
return 'jsonRow';
}
const value = selection.length == 1 ? selection[0].value : null;

View File

@@ -5,10 +5,13 @@
import ConnectionList from './ConnectionList.svelte';
import PinnedObjectsList from './PinnedObjectsList.svelte';
import SqlObjectListWrapper from './SqlObjectListWrapper.svelte';
import ErrorInfo from '../elements/ErrorInfo.svelte';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
import WidgetColumnBar from './WidgetColumnBar.svelte';
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
import SqlObjectList from './SqlObjectList.svelte';
import DbKeysTree from './DbKeysTree.svelte';
export let hidden = false;
@@ -16,6 +19,8 @@
$: connection = useConnectionInfo({ conid });
$: driver = findEngineDriver($connection, $extensions);
$: config = useConfig();
$: singleDatabase = $currentDatabase?.connection?.singleDatabase;
$: database = $currentDatabase?.name;
</script>
<WidgetColumnBar {hidden}>
@@ -34,11 +39,26 @@
>
<PinnedObjectsList />
</WidgetColumnBarItem>
<WidgetColumnBarItem
title={driver?.dialect?.nosql ? 'Collections' : 'Tables, views, functions'}
name="dbObjects"
storageName="dbObjectsWidget"
>
<SqlObjectListWrapper />
</WidgetColumnBarItem>
{#if conid && (database || singleDatabase)}
{#if driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document')}
<WidgetColumnBarItem
title={driver?.databaseEngineTypes?.includes('document') ? 'Collections' : 'Tables, views, functions'}
name="dbObjects"
storageName="dbObjectsWidget"
>
<SqlObjectList {conid} {database} />
</WidgetColumnBarItem>
{:else if driver?.databaseEngineTypes?.includes('keyvalue')}
<WidgetColumnBarItem title={'Keys'} name="dbObjects" storageName="dbObjectsWidget">
<DbKeysTree {conid} {database} />
</WidgetColumnBarItem>
{/if}
{:else}
<WidgetColumnBarItem title="Database content" name="dbObjects" storageName="dbObjectsWidget">
<WidgetsInnerContainer>
<ErrorInfo message="Database not selected" icon="img alert" />
</WidgetsInnerContainer>
</WidgetColumnBarItem>
{/if}
</WidgetColumnBar>

View File

@@ -0,0 +1,34 @@
<script lang="ts">
import AppObjectCore from '../appobj/AppObjectCore.svelte';
const SHOW_INCREMENT = 500;
import { useDatabaseKeys } from '../utility/metadataLoaders';
import DbKeysTreeNode from './DbKeysTreeNode.svelte';
export let conid;
export let database;
export let root;
export let indentLevel = 0;
let maxShowCount = SHOW_INCREMENT;
$: items = useDatabaseKeys({ conid, database, root });
</script>
{#each ($items || []).slice(0, maxShowCount) as item}
<DbKeysTreeNode {conid} {database} {root} {item} {indentLevel} />
{/each}
{#if ($items || []).length > maxShowCount}
<AppObjectCore
{indentLevel}
title="Show more..."
icon="icon dots-horizontal"
expandIcon="icon invisible-box"
on:click={() => {
maxShowCount += SHOW_INCREMENT;
}}
/>
{/if}

View File

@@ -0,0 +1,33 @@
<script lang="ts">
import CloseSearchButton from '../buttons/CloseSearchButton.svelte';
import InlineButton from '../buttons/InlineButton.svelte';
import runCommand from '../commands/runCommand';
import SearchBoxWrapper from '../elements/SearchBoxWrapper.svelte';
import SearchInput from '../elements/SearchInput.svelte';
import FontIcon from '../icons/FontIcon.svelte';
import DbKeysSubTree from './DbKeysSubTree.svelte';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
export let conid;
export let database;
let filter;
function handleRefreshDatabase() {}
</script>
<SearchBoxWrapper>
<SearchInput placeholder="Search keys" bind:value={filter} />
<CloseSearchButton bind:filter />
<InlineButton on:click={() => runCommand('new.dbKey')} title="Add new key">
<FontIcon icon="icon plus-thick" />
</InlineButton>
<InlineButton on:click={handleRefreshDatabase} title="Refresh key list">
<FontIcon icon="icon refresh" />
</InlineButton>
</SearchBoxWrapper>
<WidgetsInnerContainer>
<DbKeysSubTree {conid} {database} root="" />
</WidgetsInnerContainer>

View File

@@ -0,0 +1,64 @@
<script lang="ts">
import { getIconForRedisType } from 'dbgate-tools';
import AppObjectCore from '../appobj/AppObjectCore.svelte';
import { plusExpandIcon } from '../icons/expandIcons';
import FontIcon from '../icons/FontIcon.svelte';
import { activeDbKeysStore } from '../stores';
import openNewTab from '../utility/openNewTab';
import DbKeysSubTree from './DbKeysSubTree.svelte';
export let conid;
export let database;
export let root;
export let item;
export let indentLevel = 0;
let isExpanded;
// $: console.log(item.text, indentLevel);
</script>
<AppObjectCore
icon={getIconForRedisType(item.type)}
title={item.text}
expandIcon={item.type == 'dir' ? plusExpandIcon(isExpanded) : 'icon invisible-box'}
on:expand={() => {
if (item.type == 'dir') {
isExpanded = !isExpanded;
}
}}
on:click={() => {
if (item.type == 'dir') {
isExpanded = !isExpanded;
} else {
openNewTab({
tabComponent: 'DbKeyDetailTab',
title: 'Key: ' + database,
props: {
isDefaultBrowser: true,
conid,
database,
},
});
$activeDbKeysStore = {
...$activeDbKeysStore,
[`${conid}:${database}`]: item.key,
};
}
}}
extInfo={item.count ? `(${item.count})` : null}
{indentLevel}
/>
<!-- <div on:click={() => (isExpanded = !isExpanded)}>
<FontIcon icon={} />
{item.text}
</div> -->
{#if isExpanded}
<DbKeysSubTree {conid} {database} root={item.root} indentLevel={indentLevel + 1} />
{/if}

View File

@@ -67,9 +67,10 @@
function createAddMenu() {
const res = [];
if (driver?.dialect?.nosql) {
if (driver?.databaseEngineTypes?.includes('document')) {
res.push({ command: 'new.collection' });
} else {
}
if (driver?.databaseEngineTypes?.includes('sql')) {
res.push({ command: 'new.table' });
}
if (driver)
@@ -100,11 +101,11 @@
/>
<div class="m-1" />
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
{#if !driver?.dialect?.nosql}
{#if driver?.databaseEngineTypes?.includes('sql')}
<div class="m-1" />
<InlineButton on:click={() => runCommand('new.table')}>New table</InlineButton>
{/if}
{#if driver?.dialect?.nosql}
{#if driver?.databaseEngineTypes?.includes('document')}
<div class="m-1" />
<InlineButton on:click={() => runCommand('new.collection')}>New collection</InlineButton>
{/if}

View File

@@ -1,19 +0,0 @@
<script lang="ts">
import _ from 'lodash';
import { currentDatabase } from '../stores';
import ErrorInfo from '../elements/ErrorInfo.svelte';
import SqlObjectList from './SqlObjectList.svelte';
import WidgetsInnerContainer from './WidgetsInnerContainer.svelte';
$: conid = _.get($currentDatabase, 'connection._id');
$: singleDatabase = _.get($currentDatabase, 'connection.singleDatabase');
$: database = _.get($currentDatabase, 'name');
</script>
{#if conid && (database || singleDatabase)}
<SqlObjectList {conid} {database} />
{:else}
<WidgetsInnerContainer>
<ErrorInfo message="Database not selected" icon="img alert" />
</WidgetsInnerContainer>
{/if}