mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-19 22:26:01 +00:00
Merge branch 'redis'
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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)}
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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" />
|
||||
|
||||
95
packages/web/src/datagrid/DbKeyTableControl.svelte
Normal file
95
packages/web/src/datagrid/DbKeyTableControl.svelte
Normal 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
|
||||
/>
|
||||
44
packages/web/src/dbkeyvalue/DbKeyItemDetail.svelte
Normal file
44
packages/web/src/dbkeyvalue/DbKeyItemDetail.svelte
Normal 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>
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
58
packages/web/src/modals/AddDbKeyModal.svelte
Normal file
58
packages/web/src/modals/AddDbKeyModal.svelte
Normal 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>
|
||||
@@ -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
|
||||
|
||||
47
packages/web/src/modals/DbKeyAddItemModal.svelte
Normal file
47
packages/web/src/modals/DbKeyAddItemModal.svelte
Normal 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>
|
||||
@@ -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)
|
||||
|
||||
183
packages/web/src/tabs/DbKeyDetailTab.svelte
Normal file
183
packages/web/src/tabs/DbKeyDetailTab.svelte
Normal 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>
|
||||
@@ -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 || ''}
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
34
packages/web/src/widgets/DbKeysSubTree.svelte
Normal file
34
packages/web/src/widgets/DbKeysSubTree.svelte
Normal 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}
|
||||
33
packages/web/src/widgets/DbKeysTree.svelte
Normal file
33
packages/web/src/widgets/DbKeysTree.svelte
Normal 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>
|
||||
64
packages/web/src/widgets/DbKeysTreeNode.svelte
Normal file
64
packages/web/src/widgets/DbKeysTreeNode.svelte
Normal 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}
|
||||
@@ -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}
|
||||
|
||||
@@ -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}
|
||||
Reference in New Issue
Block a user