mirror of
https://github.com/DeNNiiInc/dbgate.git
synced 2026-04-17 23:45:59 +00:00
Merge branch 'redis'
This commit is contained in:
5
.github/workflows/build-npm.yaml
vendored
5
.github/workflows/build-npm.yaml
vendored
@@ -133,3 +133,8 @@ jobs:
|
|||||||
working-directory: plugins/dbgate-plugin-sqlite
|
working-directory: plugins/dbgate-plugin-sqlite
|
||||||
run: |
|
run: |
|
||||||
npm publish
|
npm publish
|
||||||
|
|
||||||
|
- name: Publish dbgate-plugin-redis
|
||||||
|
working-directory: plugins/dbgate-plugin-redis
|
||||||
|
run: |
|
||||||
|
npm publish
|
||||||
|
|||||||
@@ -240,6 +240,7 @@ module.exports = {
|
|||||||
|
|
||||||
get_meta: true,
|
get_meta: true,
|
||||||
async get({ conid }) {
|
async get({ conid }) {
|
||||||
|
if (!conid) return null;
|
||||||
if (portalConnections) return portalConnections.find(x => x._id == conid) || null;
|
if (portalConnections) return portalConnections.find(x => x._id == conid) || null;
|
||||||
const res = await this.datastore.get(conid);
|
const res = await this.datastore.get(conid);
|
||||||
return res || null;
|
return res || null;
|
||||||
|
|||||||
@@ -151,6 +151,46 @@ module.exports = {
|
|||||||
return res.result;
|
return res.result;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
loadKeys_meta: true,
|
||||||
|
async loadKeys({ conid, database, root }) {
|
||||||
|
const opened = await this.ensureOpened(conid, database);
|
||||||
|
const res = await this.sendRequest(opened, { msgtype: 'loadKeys', root });
|
||||||
|
if (res.errorMessage) {
|
||||||
|
console.error(res.errorMessage);
|
||||||
|
}
|
||||||
|
return res.result || null;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadKeyInfo_meta: true,
|
||||||
|
async loadKeyInfo({ conid, database, key }) {
|
||||||
|
const opened = await this.ensureOpened(conid, database);
|
||||||
|
const res = await this.sendRequest(opened, { msgtype: 'loadKeyInfo', key });
|
||||||
|
if (res.errorMessage) {
|
||||||
|
console.error(res.errorMessage);
|
||||||
|
}
|
||||||
|
return res.result || null;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadKeyTableRange_meta: true,
|
||||||
|
async loadKeyTableRange({ conid, database, key, cursor, count }) {
|
||||||
|
const opened = await this.ensureOpened(conid, database);
|
||||||
|
const res = await this.sendRequest(opened, { msgtype: 'loadKeyTableRange', key, cursor, count });
|
||||||
|
if (res.errorMessage) {
|
||||||
|
console.error(res.errorMessage);
|
||||||
|
}
|
||||||
|
return res.result || null;
|
||||||
|
},
|
||||||
|
|
||||||
|
callMethod_meta: true,
|
||||||
|
async callMethod({ conid, database, method, args }) {
|
||||||
|
const opened = await this.ensureOpened(conid, database);
|
||||||
|
const res = await this.sendRequest(opened, { msgtype: 'callMethod', method, args });
|
||||||
|
if (res.errorMessage) {
|
||||||
|
console.error(res.errorMessage);
|
||||||
|
}
|
||||||
|
return res.result || null;
|
||||||
|
},
|
||||||
|
|
||||||
updateCollection_meta: true,
|
updateCollection_meta: true,
|
||||||
async updateCollection({ conid, database, changeSet }) {
|
async updateCollection({ conid, database, changeSet }) {
|
||||||
const opened = await this.ensureOpened(conid, database);
|
const opened = await this.ensureOpened(conid, database);
|
||||||
|
|||||||
@@ -183,6 +183,50 @@ async function handleCollectionData({ msgid, options }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleLoadKeys({ msgid, root }) {
|
||||||
|
await waitConnected();
|
||||||
|
const driver = requireEngineDriver(storedConnection);
|
||||||
|
try {
|
||||||
|
const result = await driver.loadKeys(systemConnection, root);
|
||||||
|
process.send({ msgtype: 'response', msgid, result });
|
||||||
|
} catch (err) {
|
||||||
|
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLoadKeyInfo({ msgid, key }) {
|
||||||
|
await waitConnected();
|
||||||
|
const driver = requireEngineDriver(storedConnection);
|
||||||
|
try {
|
||||||
|
const result = await driver.loadKeyInfo(systemConnection, key);
|
||||||
|
process.send({ msgtype: 'response', msgid, result });
|
||||||
|
} catch (err) {
|
||||||
|
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleCallMethod({ msgid, method, args }) {
|
||||||
|
await waitConnected();
|
||||||
|
const driver = requireEngineDriver(storedConnection);
|
||||||
|
try {
|
||||||
|
const result = await driver.callMethod(systemConnection, method, args);
|
||||||
|
process.send({ msgtype: 'response', msgid, result });
|
||||||
|
} catch (err) {
|
||||||
|
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleLoadKeyTableRange({ msgid, key, cursor, count }) {
|
||||||
|
await waitConnected();
|
||||||
|
const driver = requireEngineDriver(storedConnection);
|
||||||
|
try {
|
||||||
|
const result = await driver.loadKeyTableRange(systemConnection, key, cursor, count);
|
||||||
|
process.send({ msgtype: 'response', msgid, result });
|
||||||
|
} catch (err) {
|
||||||
|
process.send({ msgtype: 'response', msgid, errorMessage: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleUpdateCollection({ msgid, changeSet }) {
|
async function handleUpdateCollection({ msgid, changeSet }) {
|
||||||
await waitConnected();
|
await waitConnected();
|
||||||
const driver = requireEngineDriver(storedConnection);
|
const driver = requireEngineDriver(storedConnection);
|
||||||
@@ -248,6 +292,10 @@ const messageHandlers = {
|
|||||||
runScript: handleRunScript,
|
runScript: handleRunScript,
|
||||||
updateCollection: handleUpdateCollection,
|
updateCollection: handleUpdateCollection,
|
||||||
collectionData: handleCollectionData,
|
collectionData: handleCollectionData,
|
||||||
|
loadKeys: handleLoadKeys,
|
||||||
|
loadKeyInfo: handleLoadKeyInfo,
|
||||||
|
callMethod: handleCallMethod,
|
||||||
|
loadKeyTableRange: handleLoadKeyTableRange,
|
||||||
sqlPreview: handleSqlPreview,
|
sqlPreview: handleSqlPreview,
|
||||||
ping: handlePing,
|
ping: handlePing,
|
||||||
syncModel: handleSyncModel,
|
syncModel: handleSyncModel,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ async function tableReader({ connection, pureName, schemaName }) {
|
|||||||
|
|
||||||
const fullName = { pureName, schemaName };
|
const fullName = { pureName, schemaName };
|
||||||
|
|
||||||
if (driver.dialect.nosql) {
|
if (driver.databaseEngineTypes.includes('document')) {
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
console.log(`Reading collection ${fullNameToString(fullName)}`);
|
console.log(`Reading collection ${fullNameToString(fullName)}`);
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ export function createBulkInsertStreamBase(driver, stream, pool, name, options):
|
|||||||
writable.buffer = [];
|
writable.buffer = [];
|
||||||
writable.structure = null;
|
writable.structure = null;
|
||||||
writable.columnNames = null;
|
writable.columnNames = null;
|
||||||
writable.requireFixedStructure = !driver.dialect.nosql;
|
writable.requireFixedStructure = driver.databaseEngineTypes.includes('sql');
|
||||||
|
|
||||||
writable.addRow = async row => {
|
writable.addRow = async row => {
|
||||||
if (writable.structure) {
|
if (writable.structure) {
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export const driverBase = {
|
|||||||
analyserClass: null,
|
analyserClass: null,
|
||||||
dumperClass: SqlDumper,
|
dumperClass: SqlDumper,
|
||||||
dialect,
|
dialect,
|
||||||
|
databaseEngineTypes: ['sql'],
|
||||||
|
|
||||||
async analyseFull(pool, version) {
|
async analyseFull(pool, version) {
|
||||||
const analyser = new this.analyserClass(pool, this, version);
|
const analyser = new this.analyserClass(pool, this, version);
|
||||||
@@ -45,7 +46,7 @@ export const driverBase = {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
getNewObjectTemplates() {
|
getNewObjectTemplates() {
|
||||||
if (!this.dialect?.nosql) {
|
if (this.databaseEngineTypes.includes('sql')) {
|
||||||
return [{ label: 'New view', sql: 'CREATE VIEW myview\nAS\nSELECT * FROM table1' }];
|
return [{ label: 'New view', sql: 'CREATE VIEW myview\nAS\nSELECT * FROM table1' }];
|
||||||
}
|
}
|
||||||
return [];
|
return [];
|
||||||
|
|||||||
@@ -59,3 +59,28 @@ export function safeJsonParse(json, defaultValue?, logError = false) {
|
|||||||
export function isJsonLikeLongString(value) {
|
export function isJsonLikeLongString(value) {
|
||||||
return _isString(value) && value.length > 100 && value.match(/^\s*\{.*\}\s*$|^\s*\[.*\]\s*$/);
|
return _isString(value) && value.length > 100 && value.match(/^\s*\{.*\}\s*$|^\s*\[.*\]\s*$/);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getIconForRedisType(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'dir':
|
||||||
|
return 'img folder';
|
||||||
|
case 'string':
|
||||||
|
return 'img type-string';
|
||||||
|
case 'hash':
|
||||||
|
return 'img type-hash';
|
||||||
|
case 'set':
|
||||||
|
return 'img type-set';
|
||||||
|
case 'list':
|
||||||
|
return 'img type-list';
|
||||||
|
case 'zset':
|
||||||
|
return 'img type-zset';
|
||||||
|
case 'stream':
|
||||||
|
return 'img type-stream';
|
||||||
|
case 'binary':
|
||||||
|
return 'img type-binary';
|
||||||
|
case 'ReJSON-RL':
|
||||||
|
return 'img type-rejson';
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
1
packages/types/dialect.d.ts
vendored
1
packages/types/dialect.d.ts
vendored
@@ -10,7 +10,6 @@ export interface SqlDialect {
|
|||||||
explicitDropConstraint?: boolean;
|
explicitDropConstraint?: boolean;
|
||||||
anonymousPrimaryKey?: boolean;
|
anonymousPrimaryKey?: boolean;
|
||||||
enableConstraintsPerTable?: boolean;
|
enableConstraintsPerTable?: boolean;
|
||||||
nosql?: boolean; // mongo
|
|
||||||
|
|
||||||
dropColumnDependencies?: string[];
|
dropColumnDependencies?: string[];
|
||||||
changeColumnDependencies?: string[];
|
changeColumnDependencies?: string[];
|
||||||
|
|||||||
7
packages/types/engines.d.ts
vendored
7
packages/types/engines.d.ts
vendored
@@ -46,6 +46,8 @@ export interface EngineDriver {
|
|||||||
engine: string;
|
engine: string;
|
||||||
title: string;
|
title: string;
|
||||||
defaultPort?: number;
|
defaultPort?: number;
|
||||||
|
databaseEngineTypes: string[];
|
||||||
|
supportedKeyTypes: { name: string; label: string }[];
|
||||||
supportsDatabaseUrl?: boolean;
|
supportsDatabaseUrl?: boolean;
|
||||||
isElectronOnly?: boolean;
|
isElectronOnly?: boolean;
|
||||||
showConnectionField?: (field: string, values: any) => boolean;
|
showConnectionField?: (field: string, values: any) => boolean;
|
||||||
@@ -74,6 +76,9 @@ export interface EngineDriver {
|
|||||||
name: string;
|
name: string;
|
||||||
}[]
|
}[]
|
||||||
>;
|
>;
|
||||||
|
loadKeys(pool, root: string): Promise;
|
||||||
|
loadKeyInfo(pool, key): Promise;
|
||||||
|
loadKeyTableRange(pool, key, cursor, count): Promise;
|
||||||
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
|
analyseFull(pool: any, serverVersion): Promise<DatabaseInfo>;
|
||||||
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
analyseIncremental(pool: any, structure: DatabaseInfo, serverVersion): Promise<DatabaseInfo>;
|
||||||
dialect: SqlDialect;
|
dialect: SqlDialect;
|
||||||
@@ -87,6 +92,8 @@ export interface EngineDriver {
|
|||||||
getQuerySplitterOptions(usage: 'stream' | 'script'): any;
|
getQuerySplitterOptions(usage: 'stream' | 'script'): any;
|
||||||
script(pool: any, sql: string): Promise;
|
script(pool: any, sql: string): Promise;
|
||||||
getNewObjectTemplates(): NewObjectTemplate[];
|
getNewObjectTemplates(): NewObjectTemplate[];
|
||||||
|
// direct call of pool method, only some methods could be supported, on only some drivers
|
||||||
|
callMethod(pool, method, args);
|
||||||
|
|
||||||
analyserClass?: any;
|
analyserClass?: any;
|
||||||
dumperClass?: any;
|
dumperClass?: any;
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
export let icon;
|
export let icon;
|
||||||
export let title;
|
export let title;
|
||||||
export let data;
|
export let data = null;
|
||||||
export let module;
|
export let module = null;
|
||||||
|
|
||||||
export let isBold = false;
|
export let isBold = false;
|
||||||
export let isBusy = false;
|
export let isBusy = false;
|
||||||
@@ -24,8 +24,10 @@
|
|||||||
export let onPin = null;
|
export let onPin = null;
|
||||||
export let onUnpin = null;
|
export let onUnpin = null;
|
||||||
export let showPinnedInsteadOfUnpin = false;
|
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() {
|
function handleExpand() {
|
||||||
dispatch('expand');
|
dispatch('expand');
|
||||||
@@ -33,7 +35,7 @@
|
|||||||
function handleClick() {
|
function handleClick() {
|
||||||
if (checkedObjectsStore) {
|
if (checkedObjectsStore) {
|
||||||
if (isChecked) {
|
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 {
|
} else {
|
||||||
checkedObjectsStore.update(x => [...x, data]);
|
checkedObjectsStore.update(x => [...x, data]);
|
||||||
}
|
}
|
||||||
@@ -52,12 +54,14 @@
|
|||||||
|
|
||||||
function setChecked(value) {
|
function setChecked(value) {
|
||||||
if (!value && isChecked) {
|
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) {
|
if (value && !isChecked) {
|
||||||
checkedObjectsStore.update(x => [...x, data]);
|
checkedObjectsStore.update(x => [...x, data]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// $: console.log(title, indentLevel);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
@@ -85,6 +89,9 @@
|
|||||||
<FontIcon icon={expandIcon} />
|
<FontIcon icon={expandIcon} />
|
||||||
</span>
|
</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if indentLevel}
|
||||||
|
<span style:margin-right={`${indentLevel * 16}px`} />
|
||||||
|
{/if}
|
||||||
{#if isBusy}
|
{#if isBusy}
|
||||||
<FontIcon icon="icon loading" />
|
<FontIcon icon="icon loading" />
|
||||||
{:else}
|
{:else}
|
||||||
@@ -188,5 +195,4 @@
|
|||||||
float: right;
|
float: right;
|
||||||
color: var(--theme-font-2);
|
color: var(--theme-font-2);
|
||||||
}
|
}
|
||||||
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -174,8 +174,8 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
|
{ onClick: handleNewQuery, text: 'New query', isNewQuery: true },
|
||||||
!driver?.dialect?.nosql && { onClick: handleNewTable, text: 'New table' },
|
driver?.databaseEngineTypes?.includes('sql') && { onClick: handleNewTable, text: 'New table' },
|
||||||
driver?.dialect?.nosql && { onClick: handleNewCollection, text: 'New collection' },
|
driver?.databaseEngineTypes?.includes('document') && { onClick: handleNewCollection, text: 'New collection' },
|
||||||
{ divider: true },
|
{ divider: true },
|
||||||
{ onClick: handleImport, text: 'Import' },
|
{ onClick: handleImport, text: 'Import' },
|
||||||
{ onClick: handleExport, text: 'Export' },
|
{ onClick: handleExport, text: 'Export' },
|
||||||
@@ -256,6 +256,7 @@
|
|||||||
{...$$restProps}
|
{...$$restProps}
|
||||||
{data}
|
{data}
|
||||||
title={data.name}
|
title={data.name}
|
||||||
|
extInfo={data.extInfo}
|
||||||
icon="img database"
|
icon="img database"
|
||||||
colorMark={passProps?.connectionColorFactory &&
|
colorMark={passProps?.connectionColorFactory &&
|
||||||
passProps?.connectionColorFactory({ conid: _.get(data.connection, '_id'), database: data.name }, null, null, false)}
|
passProps?.connectionColorFactory({ conid: _.get(data.connection, '_id'), database: data.name }, null, null, false)}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
<AppObjectList
|
<AppObjectList
|
||||||
list={_.sortBy(
|
list={_.sortBy(
|
||||||
($databases || []).filter(x => filterName(filter, x.name)),
|
($databases || []).filter(x => filterName(filter, x.name)),
|
||||||
'name'
|
x => x.sortOrder ?? x.name
|
||||||
).map(db => ({ ...db, connection: data }))}
|
).map(db => ({ ...db, connection: data }))}
|
||||||
module={databaseAppObject}
|
module={databaseAppObject}
|
||||||
{passProps}
|
{passProps}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { get } from 'svelte/store';
|
|||||||
import { ThemeDefinition } from 'dbgate-types';
|
import { ThemeDefinition } from 'dbgate-types';
|
||||||
import ConnectionModal from '../modals/ConnectionModal.svelte';
|
import ConnectionModal from '../modals/ConnectionModal.svelte';
|
||||||
import AboutModal from '../modals/AboutModal.svelte';
|
import AboutModal from '../modals/AboutModal.svelte';
|
||||||
|
import AddDbKeyModal from '../modals/AddDbKeyModal.svelte';
|
||||||
import SettingsModal from '../settings/SettingsModal.svelte';
|
import SettingsModal from '../settings/SettingsModal.svelte';
|
||||||
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
import ImportExportModal from '../modals/ImportExportModal.svelte';
|
||||||
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
import SqlGeneratorModal from '../modals/SqlGeneratorModal.svelte';
|
||||||
@@ -158,7 +159,7 @@ registerCommand({
|
|||||||
toolbarName: 'New table',
|
toolbarName: 'New table',
|
||||||
testEnabled: () => {
|
testEnabled: () => {
|
||||||
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
|
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
|
||||||
return !!get(currentDatabase) && !driver?.dialect?.nosql;
|
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('sql');
|
||||||
},
|
},
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
const $currentDatabase = get(currentDatabase);
|
const $currentDatabase = get(currentDatabase);
|
||||||
@@ -196,7 +197,7 @@ registerCommand({
|
|||||||
toolbarName: 'New collection',
|
toolbarName: 'New collection',
|
||||||
testEnabled: () => {
|
testEnabled: () => {
|
||||||
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
|
const driver = findEngineDriver(get(currentDatabase)?.connection, getExtensions());
|
||||||
return !!get(currentDatabase) && driver?.dialect?.nosql;
|
return !!get(currentDatabase) && driver?.databaseEngineTypes?.includes('document');
|
||||||
},
|
},
|
||||||
onClick: async () => {
|
onClick: async () => {
|
||||||
const $currentDatabase = get(currentDatabase);
|
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({
|
registerCommand({
|
||||||
id: 'new.markdown',
|
id: 'new.markdown',
|
||||||
category: 'New',
|
category: 'New',
|
||||||
|
|||||||
@@ -266,7 +266,7 @@
|
|||||||
class:isOk
|
class:isOk
|
||||||
placeholder="Filter"
|
placeholder="Filter"
|
||||||
/>
|
/>
|
||||||
{#if conid && database && driver && !driver?.dialect?.nosql}
|
{#if conid && database && driver && driver?.databaseEngineTypes?.includes('sql')}
|
||||||
{#if foreignKey}
|
{#if foreignKey}
|
||||||
<InlineButton on:click={handleShowDictionary} narrow square>
|
<InlineButton on:click={handleShowDictionary} narrow square>
|
||||||
<FontIcon icon="icon dots-horizontal" />
|
<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">
|
<script lang="ts">
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { onMount } from 'svelte';
|
import { onDestroy, onMount } from 'svelte';
|
||||||
import keycodes from '../utility/keycodes';
|
import keycodes from '../utility/keycodes';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import resizeObserver from '../utility/resizeObserver';
|
import resizeObserver from '../utility/resizeObserver';
|
||||||
@@ -27,6 +27,8 @@
|
|||||||
export let selectedIndex = 0;
|
export let selectedIndex = 0;
|
||||||
export let clickable = false;
|
export let clickable = false;
|
||||||
export let disableFocusOutline = false;
|
export let disableFocusOutline = false;
|
||||||
|
export let onLoadNext = null;
|
||||||
|
export let singleLineRow = false;
|
||||||
|
|
||||||
export let domTable = undefined;
|
export let domTable = undefined;
|
||||||
|
|
||||||
@@ -34,6 +36,36 @@
|
|||||||
let headerHeight = 0;
|
let headerHeight = 0;
|
||||||
let domBody;
|
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();
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
$: columnList = _.compact(_.flatten(columns));
|
$: columnList = _.compact(_.flatten(columns));
|
||||||
@@ -86,6 +118,7 @@
|
|||||||
bind:this={domTable}
|
bind:this={domTable}
|
||||||
class:selectable
|
class:selectable
|
||||||
class:disableFocusOutline
|
class:disableFocusOutline
|
||||||
|
class:singleLineRow
|
||||||
on:keydown
|
on:keydown
|
||||||
tabindex={selectable ? -1 : undefined}
|
tabindex={selectable ? -1 : undefined}
|
||||||
on:keydown={handleKeyDown}
|
on:keydown={handleKeyDown}
|
||||||
@@ -163,6 +196,13 @@
|
|||||||
{/each}
|
{/each}
|
||||||
</tr>
|
</tr>
|
||||||
{/each}
|
{/each}
|
||||||
|
{#if onLoadNext}
|
||||||
|
{#key rows}
|
||||||
|
<tr>
|
||||||
|
<td colspan={columnList.length} bind:this={domLoadNext}> Loading next rows... </td>
|
||||||
|
</tr>
|
||||||
|
{/key}
|
||||||
|
{/if}
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,6 +233,11 @@
|
|||||||
table tbody tr td {
|
table tbody tr td {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
table.singleLineRow tbody tr td {
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
table tbody {
|
table tbody {
|
||||||
display: block;
|
display: block;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
|||||||
@@ -8,4 +8,13 @@
|
|||||||
if (focused) onMount(() => domEditor.focus());
|
if (focused) onMount(() => domEditor.focus());
|
||||||
</script>
|
</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 link': 'mdi mdi-link',
|
||||||
'img filter': 'mdi mdi-filter',
|
'img filter': 'mdi mdi-filter',
|
||||||
'img group': 'mdi mdi-group',
|
'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>
|
</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) || [];
|
$: disabledFields = (currentAuthType ? currentAuthType.disabledFields : null) || [];
|
||||||
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
$: driver = $extensions.drivers.find(x => x.engine == engine);
|
||||||
$: defaultDatabase = $values.defaultDatabase;
|
$: defaultDatabase = $values.defaultDatabase;
|
||||||
|
|
||||||
|
$: showUser = !driver?.showConnectionField || driver.showConnectionField('user', $values);
|
||||||
|
$: showPassword = !driver?.showConnectionField || driver.showConnectionField('password', $values);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<FormSelectField
|
<FormSelectField
|
||||||
@@ -100,17 +103,19 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
{#if !driver?.showConnectionField || driver.showConnectionField('user', $values)}
|
{#if showUser && showPassword}
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="col-6 mr-1">
|
{#if showUser}
|
||||||
<FormTextField
|
<div class="col-6 mr-1">
|
||||||
label="User"
|
<FormTextField
|
||||||
name="user"
|
label="User"
|
||||||
disabled={disabledFields.includes('user')}
|
name="user"
|
||||||
templateProps={{ noMargin: true }}
|
disabled={disabledFields.includes('user')}
|
||||||
/>
|
templateProps={{ noMargin: true }}
|
||||||
</div>
|
/>
|
||||||
{#if !driver?.showConnectionField || driver.showConnectionField('password', $values)}
|
</div>
|
||||||
|
{/if}
|
||||||
|
{#if showPassword}
|
||||||
<div class="col-6 mr-1">
|
<div class="col-6 mr-1">
|
||||||
<FormPasswordField
|
<FormPasswordField
|
||||||
label="Password"
|
label="Password"
|
||||||
@@ -122,8 +127,14 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/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
|
<FormSelectField
|
||||||
label="Password mode"
|
label="Password mode"
|
||||||
isNative
|
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,
|
loaded: false,
|
||||||
loadingPackageName: null,
|
loadingPackageName: null,
|
||||||
});
|
});
|
||||||
|
export const activeDbKeysStore = writableWithStorage({}, 'activeDbKeysStore');
|
||||||
|
|
||||||
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
export const currentThemeDefinition = derived([currentTheme, extensions], ([$currentTheme, $extensions]) =>
|
||||||
$extensions.themes.find(x => x.themeClassName == $currentTheme)
|
$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() {
|
export function isSqlEditor() {
|
||||||
return !driver?.dialect?.nosql;
|
return driver?.databaseEngineTypes?.includes('sql');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function canKill() {
|
export function canKill() {
|
||||||
@@ -281,7 +281,7 @@
|
|||||||
<ToolStripContainer>
|
<ToolStripContainer>
|
||||||
<VerticalSplitter isSplitter={visibleResultTabs}>
|
<VerticalSplitter isSplitter={visibleResultTabs}>
|
||||||
<svelte:fragment slot="1">
|
<svelte:fragment slot="1">
|
||||||
{#if driver?.dialect?.nosql}
|
{#if driver?.databaseEngineTypes?.includes('document')}
|
||||||
<AceEditor
|
<AceEditor
|
||||||
mode="javascript"
|
mode="javascript"
|
||||||
value={$editorState.value || ''}
|
value={$editorState.value || ''}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import * as CompareModelTab from './CompareModelTab.svelte';
|
|||||||
import * as JsonTab from './JsonTab.svelte';
|
import * as JsonTab from './JsonTab.svelte';
|
||||||
import * as ChangelogTab from './ChangelogTab.svelte';
|
import * as ChangelogTab from './ChangelogTab.svelte';
|
||||||
import * as DiagramTab from './DiagramTab.svelte';
|
import * as DiagramTab from './DiagramTab.svelte';
|
||||||
|
import * as DbKeyDetailTab from './DbKeyDetailTab.svelte';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
TableDataTab,
|
TableDataTab,
|
||||||
@@ -46,4 +47,5 @@ export default {
|
|||||||
JsonTab,
|
JsonTab,
|
||||||
ChangelogTab,
|
ChangelogTab,
|
||||||
DiagramTab,
|
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 }) => ({
|
const serverVersionLoader = ({ conid }) => ({
|
||||||
url: 'server-connections/version',
|
url: 'server-connections/version',
|
||||||
params: { conid },
|
params: { conid },
|
||||||
@@ -429,3 +435,10 @@ export function getAuthTypes(args) {
|
|||||||
export function useAuthTypes(args) {
|
export function useAuthTypes(args) {
|
||||||
return useCore(authTypesLoader, 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) {
|
function autodetect(selection) {
|
||||||
if (selection[0]?.engine?.dialect?.nosql) {
|
if (selection[0]?.engine?.databaseEngineTypes?.includes('document')) {
|
||||||
return 'jsonRow';
|
return 'jsonRow';
|
||||||
}
|
}
|
||||||
const value = selection.length == 1 ? selection[0].value : null;
|
const value = selection.length == 1 ? selection[0].value : null;
|
||||||
|
|||||||
@@ -5,10 +5,13 @@
|
|||||||
|
|
||||||
import ConnectionList from './ConnectionList.svelte';
|
import ConnectionList from './ConnectionList.svelte';
|
||||||
import PinnedObjectsList from './PinnedObjectsList.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 WidgetColumnBar from './WidgetColumnBar.svelte';
|
||||||
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
import WidgetColumnBarItem from './WidgetColumnBarItem.svelte';
|
||||||
|
import SqlObjectList from './SqlObjectList.svelte';
|
||||||
|
import DbKeysTree from './DbKeysTree.svelte';
|
||||||
|
|
||||||
export let hidden = false;
|
export let hidden = false;
|
||||||
|
|
||||||
@@ -16,6 +19,8 @@
|
|||||||
$: connection = useConnectionInfo({ conid });
|
$: connection = useConnectionInfo({ conid });
|
||||||
$: driver = findEngineDriver($connection, $extensions);
|
$: driver = findEngineDriver($connection, $extensions);
|
||||||
$: config = useConfig();
|
$: config = useConfig();
|
||||||
|
$: singleDatabase = $currentDatabase?.connection?.singleDatabase;
|
||||||
|
$: database = $currentDatabase?.name;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<WidgetColumnBar {hidden}>
|
<WidgetColumnBar {hidden}>
|
||||||
@@ -34,11 +39,26 @@
|
|||||||
>
|
>
|
||||||
<PinnedObjectsList />
|
<PinnedObjectsList />
|
||||||
</WidgetColumnBarItem>
|
</WidgetColumnBarItem>
|
||||||
<WidgetColumnBarItem
|
|
||||||
title={driver?.dialect?.nosql ? 'Collections' : 'Tables, views, functions'}
|
{#if conid && (database || singleDatabase)}
|
||||||
name="dbObjects"
|
{#if driver?.databaseEngineTypes?.includes('sql') || driver?.databaseEngineTypes?.includes('document')}
|
||||||
storageName="dbObjectsWidget"
|
<WidgetColumnBarItem
|
||||||
>
|
title={driver?.databaseEngineTypes?.includes('document') ? 'Collections' : 'Tables, views, functions'}
|
||||||
<SqlObjectListWrapper />
|
name="dbObjects"
|
||||||
</WidgetColumnBarItem>
|
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>
|
</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() {
|
function createAddMenu() {
|
||||||
const res = [];
|
const res = [];
|
||||||
if (driver?.dialect?.nosql) {
|
if (driver?.databaseEngineTypes?.includes('document')) {
|
||||||
res.push({ command: 'new.collection' });
|
res.push({ command: 'new.collection' });
|
||||||
} else {
|
}
|
||||||
|
if (driver?.databaseEngineTypes?.includes('sql')) {
|
||||||
res.push({ command: 'new.table' });
|
res.push({ command: 'new.table' });
|
||||||
}
|
}
|
||||||
if (driver)
|
if (driver)
|
||||||
@@ -100,11 +101,11 @@
|
|||||||
/>
|
/>
|
||||||
<div class="m-1" />
|
<div class="m-1" />
|
||||||
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
<InlineButton on:click={handleRefreshDatabase}>Refresh</InlineButton>
|
||||||
{#if !driver?.dialect?.nosql}
|
{#if driver?.databaseEngineTypes?.includes('sql')}
|
||||||
<div class="m-1" />
|
<div class="m-1" />
|
||||||
<InlineButton on:click={() => runCommand('new.table')}>New table</InlineButton>
|
<InlineButton on:click={() => runCommand('new.table')}>New table</InlineButton>
|
||||||
{/if}
|
{/if}
|
||||||
{#if driver?.dialect?.nosql}
|
{#if driver?.databaseEngineTypes?.includes('document')}
|
||||||
<div class="m-1" />
|
<div class="m-1" />
|
||||||
<InlineButton on:click={() => runCommand('new.collection')}>New collection</InlineButton>
|
<InlineButton on:click={() => runCommand('new.collection')}>New collection</InlineButton>
|
||||||
{/if}
|
{/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}
|
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
const _isString = require('lodash/isString');
|
|
||||||
const { driverBase } = global.DBGATE_TOOLS;
|
const { driverBase } = global.DBGATE_TOOLS;
|
||||||
const Dumper = require('./Dumper');
|
const Dumper = require('./Dumper');
|
||||||
const { mongoSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
const { mongoSplitterOptions } = require('dbgate-query-splitter/lib/options');
|
||||||
@@ -17,7 +16,6 @@ const dialect = {
|
|||||||
offsetFetchRangeSyntax: true,
|
offsetFetchRangeSyntax: true,
|
||||||
stringEscapeChar: "'",
|
stringEscapeChar: "'",
|
||||||
fallbackDataType: 'nvarchar(max)',
|
fallbackDataType: 'nvarchar(max)',
|
||||||
nosql: true,
|
|
||||||
quoteIdentifier(s) {
|
quoteIdentifier(s) {
|
||||||
return `[${s}]`;
|
return `[${s}]`;
|
||||||
},
|
},
|
||||||
@@ -27,6 +25,7 @@ const dialect = {
|
|||||||
const driver = {
|
const driver = {
|
||||||
...driverBase,
|
...driverBase,
|
||||||
dumperClass: Dumper,
|
dumperClass: Dumper,
|
||||||
|
databaseEngineTypes: ['document'],
|
||||||
dialect,
|
dialect,
|
||||||
engine: 'mongo@dbgate-plugin-mongo',
|
engine: 'mongo@dbgate-plugin-mongo',
|
||||||
title: 'MongoDB',
|
title: 'MongoDB',
|
||||||
|
|||||||
25
plugins/dbgate-plugin-redis/.gitignore
vendored
Normal file
25
plugins/dbgate-plugin-redis/.gitignore
vendored
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# production
|
||||||
|
build
|
||||||
|
dist
|
||||||
|
lib
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
.env.local
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
6
plugins/dbgate-plugin-redis/README.md
Normal file
6
plugins/dbgate-plugin-redis/README.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
[](https://github.com/prettier/prettier)
|
||||||
|
[](https://www.npmjs.com/package/dbgate-plugin-redis)
|
||||||
|
|
||||||
|
# dbgate-plugin-redis
|
||||||
|
|
||||||
|
Use DbGate for install of this plugin
|
||||||
35
plugins/dbgate-plugin-redis/icon.svg
Normal file
35
plugins/dbgate-plugin-redis/icon.svg
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 384 384" style="enable-background:new 0 0 384 384;" xml:space="preserve">
|
||||||
|
<polygon style="fill:#EFEEEE;" points="64,0 64,384 288,384 384,288 384,0 "/>
|
||||||
|
<polygon style="fill:#ABABAB;" points="288,288 288,384 384,288 "/>
|
||||||
|
<polygon style="fill:#DEDEDD;" points="192,384 288,384 288,288 "/>
|
||||||
|
<path style="fill:#448E47;" d="M0,96v112h256V96L0,96L0,96z"/>
|
||||||
|
<g>
|
||||||
|
<path style="fill:#FFFFFF;" d="M64.32,130.112c-1.184-2.288-3.344-3.424-6.48-3.424c-1.728,0-3.152,0.464-4.272,1.408
|
||||||
|
c-1.12,0.928-2,2.416-2.64,4.496s-1.088,4.8-1.344,8.176c-0.272,3.36-0.384,7.472-0.384,12.336c0,5.184,0.176,9.376,0.528,12.576
|
||||||
|
c0.336,3.2,0.896,5.664,1.632,7.44s1.664,2.96,2.784,3.552c1.12,0.608,2.416,0.928,3.888,0.928c1.216,0,2.352-0.208,3.408-0.624
|
||||||
|
s1.968-1.248,2.736-2.496c0.784-1.248,1.392-3.008,1.824-5.28c0.448-2.272,0.672-5.264,0.672-8.976H80.48
|
||||||
|
c0,3.696-0.288,7.232-0.864,10.56s-1.664,6.24-3.216,8.736c-1.584,2.48-3.776,4.432-6.624,5.84
|
||||||
|
c-2.848,1.408-6.544,2.128-11.088,2.128c-5.168,0-9.312-0.848-12.368-2.496c-3.072-1.664-5.424-4.064-7.056-7.2
|
||||||
|
s-2.688-6.88-3.168-11.232c-0.464-4.336-0.72-9.152-0.72-14.384c0-5.184,0.256-9.968,0.72-14.352
|
||||||
|
c0.48-4.368,1.552-8.144,3.168-11.28c1.648-3.12,3.984-5.584,7.056-7.344c3.056-1.744,7.2-2.64,12.368-2.64
|
||||||
|
c4.944,0,8.816,0.8,11.664,2.4c2.848,1.6,4.976,3.632,6.368,6.096s2.304,5.12,2.64,7.968c0.352,2.848,0.528,5.52,0.528,8.016H66.08
|
||||||
|
C66.08,136,65.488,132.368,64.32,130.112z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M109.072,167.008c0,1.6,0.144,3.056,0.384,4.352c0.272,1.312,0.736,2.416,1.44,3.312
|
||||||
|
c0.704,0.912,1.664,1.616,2.848,2.128c1.168,0.496,2.672,0.768,4.448,0.768c2.128,0,4.016-0.688,5.712-2.064
|
||||||
|
c1.68-1.376,2.544-3.52,2.544-6.384c0-1.536-0.224-2.864-0.624-3.984c-0.416-1.12-1.104-2.128-2.064-3.008
|
||||||
|
c-0.976-0.912-2.24-1.712-3.792-2.448s-3.504-1.488-5.808-2.256c-3.056-1.024-5.712-2.16-7.968-3.376
|
||||||
|
c-2.24-1.2-4.112-2.624-5.616-4.272c-1.504-1.632-2.608-3.52-3.312-5.664c-0.704-2.16-1.056-4.624-1.056-7.456
|
||||||
|
c0-6.784,1.888-11.824,5.664-15.152c3.76-3.328,8.96-4.992,15.552-4.992c3.072,0,5.904,0.336,8.496,1.008s4.832,1.744,6.72,3.264
|
||||||
|
c1.888,1.504,3.36,3.424,4.416,5.744c1.04,2.336,1.584,5.136,1.584,8.4v1.92h-13.232c0-3.264-0.576-5.776-1.712-7.552
|
||||||
|
c-1.152-1.744-3.072-2.64-5.76-2.64c-1.536,0-2.816,0.24-3.84,0.672c-1.008,0.448-1.84,1.04-2.448,1.776s-1.04,1.616-1.264,2.576
|
||||||
|
c-0.24,0.96-0.336,1.952-0.336,2.976c0,2.128,0.448,3.888,1.344,5.328c0.896,1.456,2.816,2.784,5.76,3.984l10.656,4.608
|
||||||
|
c2.624,1.152,4.768,2.352,6.416,3.616c1.664,1.248,3.008,2.592,3.984,4.032c0.992,1.44,1.68,3.008,2.064,4.752
|
||||||
|
c0.384,1.712,0.576,3.648,0.576,5.744c0,7.232-2.096,12.496-6.288,15.792c-4.192,3.296-10.032,4.96-17.52,4.96
|
||||||
|
c-7.808,0-13.392-1.696-16.768-5.088c-3.36-3.392-5.024-8.256-5.024-14.592v-2.784h13.824L109.072,167.008L109.072,167.008z"/>
|
||||||
|
<path style="fill:#FFFFFF;" d="M177.344,168.544h0.304l10.176-50.688h14.32L186.4,186.4h-17.76l-15.728-68.544h14.784
|
||||||
|
L177.344,168.544z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 3.1 KiB |
43
plugins/dbgate-plugin-redis/package.json
Normal file
43
plugins/dbgate-plugin-redis/package.json
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "dbgate-plugin-redis",
|
||||||
|
"main": "dist/backend.js",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "MIT",
|
||||||
|
"description": "Redis connector plugin for DbGate",
|
||||||
|
"homepage": "https://dbgate.org",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/dbgate/dbgate"
|
||||||
|
},
|
||||||
|
"author": "Jan Prochazka",
|
||||||
|
"keywords": [
|
||||||
|
"dbgate",
|
||||||
|
"dbgateplugin",
|
||||||
|
"redis"
|
||||||
|
],
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build:frontend": "webpack --config webpack-frontend.config",
|
||||||
|
"build:frontend:watch": "webpack --watch --config webpack-frontend.config",
|
||||||
|
"build:backend": "webpack --config webpack-backend.config.js",
|
||||||
|
"build": "yarn build:frontend && yarn build:backend",
|
||||||
|
"plugin": "yarn build && yarn pack && dbgate-plugin dbgate-plugin-redis",
|
||||||
|
"plugout": "dbgate-plugout dbgate-plugin-redis",
|
||||||
|
"copydist": "yarn build && yarn pack && dbgate-copydist ../dist/dbgate-plugin-redis",
|
||||||
|
"prepublishOnly": "yarn build"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"dbgate-plugin-tools": "^1.0.7",
|
||||||
|
"dbgate-query-splitter": "^4.1.1",
|
||||||
|
"dbgate-tools": "^4.1.1",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
|
"webpack": "^4.42.0",
|
||||||
|
"webpack-cli": "^3.3.11"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"async": "^3.2.3",
|
||||||
|
"ioredis": "^4.28.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
8
plugins/dbgate-plugin-redis/prettier.config.js
Normal file
8
plugins/dbgate-plugin-redis/prettier.config.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
module.exports = {
|
||||||
|
trailingComma: 'es5',
|
||||||
|
tabWidth: 2,
|
||||||
|
semi: true,
|
||||||
|
singleQuote: true,
|
||||||
|
arrowParen: 'avoid',
|
||||||
|
printWidth: 120,
|
||||||
|
};
|
||||||
9
plugins/dbgate-plugin-redis/src/backend/Analyser.js
Normal file
9
plugins/dbgate-plugin-redis/src/backend/Analyser.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
const { DatabaseAnalyser } = require('dbgate-tools');
|
||||||
|
|
||||||
|
class Analyser extends DatabaseAnalyser {
|
||||||
|
constructor(pool, driver) {
|
||||||
|
super(pool, driver);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Analyser;
|
||||||
230
plugins/dbgate-plugin-redis/src/backend/driver.js
Normal file
230
plugins/dbgate-plugin-redis/src/backend/driver.js
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
const _ = require('lodash');
|
||||||
|
const async = require('async');
|
||||||
|
const stream = require('stream');
|
||||||
|
const driverBase = require('../frontend/driver');
|
||||||
|
const Analyser = require('./Analyser');
|
||||||
|
const Redis = require('ioredis');
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').EngineDriver} */
|
||||||
|
const driver = {
|
||||||
|
...driverBase,
|
||||||
|
analyserClass: Analyser,
|
||||||
|
async connect({ server, port, password, database }) {
|
||||||
|
let db = 0;
|
||||||
|
if (_.isString(database) && database.startsWith('db')) db = parseInt(database.substring(2));
|
||||||
|
if (_.isNumber(database)) db = database;
|
||||||
|
const pool = new Redis({
|
||||||
|
host: server,
|
||||||
|
port,
|
||||||
|
password,
|
||||||
|
db,
|
||||||
|
});
|
||||||
|
return pool;
|
||||||
|
},
|
||||||
|
// @ts-ignore
|
||||||
|
async query(pool, sql) {
|
||||||
|
return {
|
||||||
|
rows: [],
|
||||||
|
columns: [],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async stream(pool, sql, options) {
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
async readQuery(pool, sql, structure) {
|
||||||
|
const pass = new stream.PassThrough({
|
||||||
|
objectMode: true,
|
||||||
|
highWaterMark: 100,
|
||||||
|
});
|
||||||
|
|
||||||
|
// pass.write(structure)
|
||||||
|
// pass.write(row1)
|
||||||
|
// pass.write(row2)
|
||||||
|
// pass.end()
|
||||||
|
|
||||||
|
return pass;
|
||||||
|
},
|
||||||
|
async writeTable(pool, name, options) {
|
||||||
|
return createBulkInsertStreamBase(this, stream, pool, name, options);
|
||||||
|
},
|
||||||
|
async info(pool) {
|
||||||
|
const info = await pool.info();
|
||||||
|
return _.fromPairs(
|
||||||
|
info
|
||||||
|
.split('\n')
|
||||||
|
.filter((x) => x.trim() && !x.trim().startsWith('#'))
|
||||||
|
.map((x) => x.split(':'))
|
||||||
|
);
|
||||||
|
},
|
||||||
|
async getVersion(pool) {
|
||||||
|
const info = await this.info(pool);
|
||||||
|
|
||||||
|
return {
|
||||||
|
version: info.redis_version,
|
||||||
|
versionText: `Redis ${info.redis_version}`,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
async listDatabases(pool) {
|
||||||
|
const info = await this.info(pool);
|
||||||
|
|
||||||
|
return _.range(16).map((index) => ({ name: `db${index}`, extInfo: info[`db${index}`], sortOrder: index }));
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadKeys(pool, root = '') {
|
||||||
|
const keys = await this.getKeys(pool, root);
|
||||||
|
const res = this.extractKeysFromLevel(root, keys);
|
||||||
|
await this.enrichKeyInfo(pool, res);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getKeys(pool, root = '') {
|
||||||
|
const res = [];
|
||||||
|
let cursor = 0;
|
||||||
|
do {
|
||||||
|
const [strCursor, keys] = await pool.scan(cursor, 'MATCH', root ? `${root}:*` : '*', 'COUNT', 100);
|
||||||
|
res.push(...keys);
|
||||||
|
cursor = parseInt(strCursor);
|
||||||
|
} while (cursor > 0);
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
extractKeysFromLevel(root, keys) {
|
||||||
|
const prefix = root ? `${root}:` : '';
|
||||||
|
const rootSplit = _.compact(root.split(':'));
|
||||||
|
const res = {};
|
||||||
|
for (const key of keys) {
|
||||||
|
if (!key.startsWith(prefix)) continue;
|
||||||
|
const keySplit = key.split(':');
|
||||||
|
if (keySplit.length > rootSplit.length) {
|
||||||
|
const text = keySplit[rootSplit.length];
|
||||||
|
if (keySplit.length == rootSplit.length + 1) {
|
||||||
|
res[text] = {
|
||||||
|
text,
|
||||||
|
key,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
const dctKey = '::' + text;
|
||||||
|
if (res[dctKey]) {
|
||||||
|
res[dctKey].count++;
|
||||||
|
} else {
|
||||||
|
res[dctKey] = {
|
||||||
|
text: text + ':*',
|
||||||
|
type: 'dir',
|
||||||
|
root: keySplit.slice(0, rootSplit.length + 1).join(':'),
|
||||||
|
count: 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Object.values(res);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getKeyCardinality(pool, key, type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'list':
|
||||||
|
return pool.llen(key);
|
||||||
|
case 'set':
|
||||||
|
return pool.scard(key);
|
||||||
|
case 'zset':
|
||||||
|
return pool.zcard(key);
|
||||||
|
case 'stream':
|
||||||
|
return pool.xlen(key);
|
||||||
|
case 'hash':
|
||||||
|
return pool.hlen(key);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async enrichOneKeyInfo(pool, item) {
|
||||||
|
item.type = await pool.type(item.key);
|
||||||
|
item.count = await this.getKeyCardinality(pool, item.key, item.type);
|
||||||
|
},
|
||||||
|
|
||||||
|
async enrichKeyInfo(pool, levelInfo) {
|
||||||
|
await async.eachLimit(
|
||||||
|
levelInfo.filter((x) => x.key),
|
||||||
|
10,
|
||||||
|
async (item) => await this.enrichOneKeyInfo(pool, item)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadKeyInfo(pool, key) {
|
||||||
|
const res = {};
|
||||||
|
const type = await pool.type(key);
|
||||||
|
|
||||||
|
res.key = key;
|
||||||
|
res.type = type;
|
||||||
|
res.ttl = await pool.ttl(key);
|
||||||
|
res.count = await this.getKeyCardinality(pool, key, type);
|
||||||
|
|
||||||
|
switch (type) {
|
||||||
|
case 'string':
|
||||||
|
res.value = await pool.get(key);
|
||||||
|
break;
|
||||||
|
// case 'list':
|
||||||
|
// res.tableColumns = [{ name: 'value' }];
|
||||||
|
// res.addMethod = 'rpush';
|
||||||
|
// break;
|
||||||
|
// case 'set':
|
||||||
|
// res.tableColumns = [{ name: 'value' }];
|
||||||
|
// res.keyColumn = 'value';
|
||||||
|
// res.addMethod = 'sadd';
|
||||||
|
// break;
|
||||||
|
// case 'zset':
|
||||||
|
// res.tableColumns = [{ name: 'score' }, { name: 'value' }];
|
||||||
|
// res.keyColumn = 'value';
|
||||||
|
// res.addMethod = 'zadd';
|
||||||
|
// break;
|
||||||
|
// case 'hash':
|
||||||
|
// res.tableColumns = [{ name: 'key' }, { name: 'value' }];
|
||||||
|
// res.keyColumn = 'key';
|
||||||
|
// res.addMethod = 'hset';
|
||||||
|
// break;
|
||||||
|
}
|
||||||
|
|
||||||
|
res.keyType = this.supportedKeyTypes.find((x) => x.name == type);
|
||||||
|
|
||||||
|
return res;
|
||||||
|
},
|
||||||
|
|
||||||
|
async callMethod(pool, method, args) {
|
||||||
|
return await pool[method](...args);
|
||||||
|
},
|
||||||
|
|
||||||
|
async loadKeyTableRange(pool, key, cursor, count) {
|
||||||
|
const type = await pool.type(key);
|
||||||
|
switch (type) {
|
||||||
|
case 'list': {
|
||||||
|
const res = await pool.lrange(key, cursor, cursor + count);
|
||||||
|
return {
|
||||||
|
cursor: res.length > count ? cursor + count : 0,
|
||||||
|
items: res.map((value) => ({ value })).slice(0, count),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'set': {
|
||||||
|
const res = await pool.sscan(key, cursor, 'COUNT', count);
|
||||||
|
return {
|
||||||
|
cursor: parseInt(res[0]),
|
||||||
|
items: res[1].map((value) => ({ value })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'zset': {
|
||||||
|
const res = await pool.zscan(key, cursor, 'COUNT', count);
|
||||||
|
return {
|
||||||
|
cursor: parseInt(res[0]),
|
||||||
|
items: _.chunk(res[1], 2).map((item) => ({ value: item[0], score: item[1] })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
case 'hash': {
|
||||||
|
const res = await pool.hscan(key, cursor, 'COUNT', count);
|
||||||
|
return {
|
||||||
|
cursor: parseInt(res[0]),
|
||||||
|
items: _.chunk(res[1], 2).map((item) => ({ key: item[0], value: item[1] })),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = driver;
|
||||||
6
plugins/dbgate-plugin-redis/src/backend/index.js
Normal file
6
plugins/dbgate-plugin-redis/src/backend/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const driver = require('./driver');
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
packageName: 'dbgate-plugin-redis',
|
||||||
|
drivers: [driver],
|
||||||
|
};
|
||||||
6
plugins/dbgate-plugin-redis/src/frontend/Dumper.js
Normal file
6
plugins/dbgate-plugin-redis/src/frontend/Dumper.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
const { SqlDumper } = require('dbgate-tools');
|
||||||
|
|
||||||
|
class Dumper extends SqlDumper {
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = Dumper;
|
||||||
70
plugins/dbgate-plugin-redis/src/frontend/driver.js
Normal file
70
plugins/dbgate-plugin-redis/src/frontend/driver.js
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
const { driverBase } = global.DBGATE_TOOLS;
|
||||||
|
const Dumper = require('./Dumper');
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').SqlDialect} */
|
||||||
|
const dialect = {
|
||||||
|
limitSelect: true,
|
||||||
|
rangeSelect: true,
|
||||||
|
offsetFetchRangeSyntax: true,
|
||||||
|
stringEscapeChar: "'",
|
||||||
|
fallbackDataType: 'nvarchar(max)',
|
||||||
|
quoteIdentifier(s) {
|
||||||
|
return `[${s}]`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @type {import('dbgate-types').EngineDriver} */
|
||||||
|
const driver = {
|
||||||
|
...driverBase,
|
||||||
|
dumperClass: Dumper,
|
||||||
|
dialect,
|
||||||
|
engine: 'redis@dbgate-plugin-redis',
|
||||||
|
title: 'Redis',
|
||||||
|
defaultPort: 6379,
|
||||||
|
databaseEngineTypes: ['keyvalue'],
|
||||||
|
supportedKeyTypes: [
|
||||||
|
{
|
||||||
|
name: 'string',
|
||||||
|
label: 'String',
|
||||||
|
dbKeyFields: [{ name: 'value' }],
|
||||||
|
addMethod: 'set',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'list',
|
||||||
|
label: 'List',
|
||||||
|
dbKeyFields: [{ name: 'value' }],
|
||||||
|
addMethod: 'rpush',
|
||||||
|
showItemList: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'set',
|
||||||
|
label: 'Set',
|
||||||
|
dbKeyFields: [{ name: 'value' }],
|
||||||
|
keyColumn: 'value',
|
||||||
|
addMethod: 'sadd',
|
||||||
|
showItemList: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zset',
|
||||||
|
label: 'Sorted Set',
|
||||||
|
dbKeyFields: [{ name: 'score' }, { name: 'value' }],
|
||||||
|
keyColumn: 'value',
|
||||||
|
addMethod: 'zadd',
|
||||||
|
showItemList: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'hash',
|
||||||
|
label: 'Hash',
|
||||||
|
dbKeyFields: [{ name: 'key' }, { name: 'value' }],
|
||||||
|
keyColumn: 'key',
|
||||||
|
addMethod: 'hset',
|
||||||
|
showItemList: true,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
showConnectionField: (field, values) => {
|
||||||
|
return ['server', 'port', 'password'].includes(field);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = driver;
|
||||||
6
plugins/dbgate-plugin-redis/src/frontend/index.js
Normal file
6
plugins/dbgate-plugin-redis/src/frontend/index.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import driver from './driver';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
packageName: 'dbgate-plugin-redis',
|
||||||
|
drivers: [driver],
|
||||||
|
};
|
||||||
23
plugins/dbgate-plugin-redis/webpack-backend.config.js
Normal file
23
plugins/dbgate-plugin-redis/webpack-backend.config.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
var webpack = require('webpack');
|
||||||
|
var path = require('path');
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
context: __dirname + '/src/backend',
|
||||||
|
|
||||||
|
entry: {
|
||||||
|
app: './index.js',
|
||||||
|
},
|
||||||
|
target: 'node',
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, 'dist'),
|
||||||
|
filename: 'backend.js',
|
||||||
|
libraryTarget: 'commonjs2',
|
||||||
|
},
|
||||||
|
|
||||||
|
// uncomment for disable minimalization
|
||||||
|
// optimization: {
|
||||||
|
// minimize: false,
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
24
plugins/dbgate-plugin-redis/webpack-frontend.config.js
Normal file
24
plugins/dbgate-plugin-redis/webpack-frontend.config.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
var webpack = require("webpack");
|
||||||
|
var path = require("path");
|
||||||
|
|
||||||
|
var config = {
|
||||||
|
context: __dirname + "/src/frontend",
|
||||||
|
|
||||||
|
entry: {
|
||||||
|
app: "./index.js",
|
||||||
|
},
|
||||||
|
target: "web",
|
||||||
|
output: {
|
||||||
|
path: path.resolve(__dirname, "dist"),
|
||||||
|
filename: "frontend.js",
|
||||||
|
libraryTarget: "var",
|
||||||
|
library: 'plugin',
|
||||||
|
},
|
||||||
|
|
||||||
|
// uncomment for disable minimalization
|
||||||
|
// optimization: {
|
||||||
|
// minimize: false,
|
||||||
|
// },
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = config;
|
||||||
@@ -49,3 +49,4 @@ changePackageFile('plugins/dbgate-plugin-mysql', json.version);
|
|||||||
changePackageFile('plugins/dbgate-plugin-mongo', json.version);
|
changePackageFile('plugins/dbgate-plugin-mongo', json.version);
|
||||||
changePackageFile('plugins/dbgate-plugin-postgres', json.version);
|
changePackageFile('plugins/dbgate-plugin-postgres', json.version);
|
||||||
changePackageFile('plugins/dbgate-plugin-sqlite', json.version);
|
changePackageFile('plugins/dbgate-plugin-sqlite', json.version);
|
||||||
|
changePackageFile('plugins/dbgate-plugin-redis', json.version);
|
||||||
|
|||||||
81
yarn.lock
81
yarn.lock
@@ -1856,6 +1856,11 @@ async@^2.6.2:
|
|||||||
dependencies:
|
dependencies:
|
||||||
lodash "^4.17.14"
|
lodash "^4.17.14"
|
||||||
|
|
||||||
|
async@^3.2.3:
|
||||||
|
version "3.2.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9"
|
||||||
|
integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g==
|
||||||
|
|
||||||
asynckit@^0.4.0:
|
asynckit@^0.4.0:
|
||||||
version "0.4.0"
|
version "0.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
|
||||||
@@ -2711,6 +2716,11 @@ cliui@^7.0.2:
|
|||||||
strip-ansi "^6.0.0"
|
strip-ansi "^6.0.0"
|
||||||
wrap-ansi "^7.0.0"
|
wrap-ansi "^7.0.0"
|
||||||
|
|
||||||
|
cluster-key-slot@^1.1.0:
|
||||||
|
version "1.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/cluster-key-slot/-/cluster-key-slot-1.1.0.tgz#30474b2a981fb12172695833052bc0d01336d10d"
|
||||||
|
integrity sha512-2Nii8p3RwAPiFwsnZvukotvow2rIHM+yQ6ZcBXGHdniadkYGZYiGmkHJIbZPIV9nfv7m/U1IPMVVcAhoWFeklw==
|
||||||
|
|
||||||
co@^4.6.0:
|
co@^4.6.0:
|
||||||
version "4.6.0"
|
version "4.6.0"
|
||||||
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
|
||||||
@@ -3222,6 +3232,13 @@ debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, debug@~4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
ms "^2.1.1"
|
ms "^2.1.1"
|
||||||
|
|
||||||
|
debug@^4.3.1:
|
||||||
|
version "4.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.3.tgz#04266e0b70a98d4462e6e288e38259213332b664"
|
||||||
|
integrity sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==
|
||||||
|
dependencies:
|
||||||
|
ms "2.1.2"
|
||||||
|
|
||||||
debug@~3.1.0:
|
debug@~3.1.0:
|
||||||
version "3.1.0"
|
version "3.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
|
||||||
@@ -3325,6 +3342,11 @@ delegates@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
|
||||||
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
|
||||||
|
|
||||||
|
denque@^1.1.0:
|
||||||
|
version "1.5.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf"
|
||||||
|
integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw==
|
||||||
|
|
||||||
denque@^1.4.1:
|
denque@^1.4.1:
|
||||||
version "1.5.0"
|
version "1.5.0"
|
||||||
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
|
resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.0.tgz#773de0686ff2d8ec2ff92914316a47b73b1c73de"
|
||||||
@@ -5192,6 +5214,23 @@ invert-kv@^2.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
|
resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-2.0.0.tgz#7393f5afa59ec9ff5f67a27620d11c226e3eec02"
|
||||||
integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
|
integrity sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==
|
||||||
|
|
||||||
|
ioredis@^4.28.5:
|
||||||
|
version "4.28.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/ioredis/-/ioredis-4.28.5.tgz#5c149e6a8d76a7f8fa8a504ffc85b7d5b6797f9f"
|
||||||
|
integrity sha512-3GYo0GJtLqgNXj4YhrisLaNNvWSNwSS2wS4OELGfGxH8I69+XfNdnmV1AyN+ZqMh0i7eX+SWjrwFKDBDgfBC1A==
|
||||||
|
dependencies:
|
||||||
|
cluster-key-slot "^1.1.0"
|
||||||
|
debug "^4.3.1"
|
||||||
|
denque "^1.1.0"
|
||||||
|
lodash.defaults "^4.2.0"
|
||||||
|
lodash.flatten "^4.4.0"
|
||||||
|
lodash.isarguments "^3.1.0"
|
||||||
|
p-map "^2.1.0"
|
||||||
|
redis-commands "1.7.0"
|
||||||
|
redis-errors "^1.2.0"
|
||||||
|
redis-parser "^3.0.0"
|
||||||
|
standard-as-callback "^2.1.0"
|
||||||
|
|
||||||
ip-regex@^2.1.0:
|
ip-regex@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
|
||||||
@@ -6831,6 +6870,21 @@ locate-path@^5.0.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-locate "^4.1.0"
|
p-locate "^4.1.0"
|
||||||
|
|
||||||
|
lodash.defaults@^4.2.0:
|
||||||
|
version "4.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
|
||||||
|
integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
|
||||||
|
|
||||||
|
lodash.flatten@^4.4.0:
|
||||||
|
version "4.4.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.flatten/-/lodash.flatten-4.4.0.tgz#f31c22225a9632d2bbf8e4addbef240aa765a61f"
|
||||||
|
integrity sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=
|
||||||
|
|
||||||
|
lodash.isarguments@^3.1.0:
|
||||||
|
version "3.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a"
|
||||||
|
integrity sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=
|
||||||
|
|
||||||
lodash.memoize@4.x:
|
lodash.memoize@4.x:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
@@ -7998,6 +8052,11 @@ p-locate@^4.1.0:
|
|||||||
dependencies:
|
dependencies:
|
||||||
p-limit "^2.2.0"
|
p-limit "^2.2.0"
|
||||||
|
|
||||||
|
p-map@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/p-map/-/p-map-2.1.0.tgz#310928feef9c9ecc65b68b17693018a665cea175"
|
||||||
|
integrity sha512-y3b8Kpd8OAN444hxfBbFfj1FY/RjtTd8tzYwhUqNYXx0fXx2iX4maP4Qr6qhIKbQXI02wTLAda4fYUbDagTUFw==
|
||||||
|
|
||||||
p-map@^4.0.0:
|
p-map@^4.0.0:
|
||||||
version "4.0.0"
|
version "4.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
|
resolved "https://registry.yarnpkg.com/p-map/-/p-map-4.0.0.tgz#bb2f95a5eda2ec168ec9274e06a747c3e2904d2b"
|
||||||
@@ -8851,6 +8910,23 @@ redent@^3.0.0:
|
|||||||
indent-string "^4.0.0"
|
indent-string "^4.0.0"
|
||||||
strip-indent "^3.0.0"
|
strip-indent "^3.0.0"
|
||||||
|
|
||||||
|
redis-commands@1.7.0:
|
||||||
|
version "1.7.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89"
|
||||||
|
integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ==
|
||||||
|
|
||||||
|
redis-errors@^1.0.0, redis-errors@^1.2.0:
|
||||||
|
version "1.2.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad"
|
||||||
|
integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60=
|
||||||
|
|
||||||
|
redis-parser@^3.0.0:
|
||||||
|
version "3.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4"
|
||||||
|
integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ=
|
||||||
|
dependencies:
|
||||||
|
redis-errors "^1.0.0"
|
||||||
|
|
||||||
regex-not@^1.0.0, regex-not@^1.0.2:
|
regex-not@^1.0.0, regex-not@^1.0.2:
|
||||||
version "1.0.2"
|
version "1.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
|
resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c"
|
||||||
@@ -9832,6 +9908,11 @@ stack-utils@^2.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
escape-string-regexp "^2.0.0"
|
escape-string-regexp "^2.0.0"
|
||||||
|
|
||||||
|
standard-as-callback@^2.1.0:
|
||||||
|
version "2.1.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/standard-as-callback/-/standard-as-callback-2.1.0.tgz#8953fc05359868a77b5b9739a665c5977bb7df45"
|
||||||
|
integrity sha512-qoRRSyROncaz1z0mvYqIE4lCd9p2R90i6GxW3uZv5ucSu8tU7B5HXUP1gG8pVZsYNVaXjk8ClXHPttLyxAL48A==
|
||||||
|
|
||||||
static-extend@^0.1.1:
|
static-extend@^0.1.1:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6"
|
||||||
|
|||||||
Reference in New Issue
Block a user