mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-01-18 13:07:58 +00:00
Implement OOP hosttree (#32)
Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
This commit is contained in:
parent
3fe5f09163
commit
a1456b3987
@ -15,6 +15,7 @@
|
|||||||
<div></div>
|
<div></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="dialogoutlets"></div>
|
||||||
<script src="./src/main.js" type="module"></script>
|
<script src="./src/main.js" type="module"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
@ -1 +1 @@
|
|||||||
499da1237327bf4ddd2de74bfd6635c7
|
5d3dd16f94d140a9f950a48981534162
|
@ -1,39 +1,44 @@
|
|||||||
<script>
|
<script>
|
||||||
import BlankState from '$components/blankstate.svelte';
|
import BlankState from '$components/blankstate.svelte';
|
||||||
import ContextMenu from '$components/contextmenu.svelte';
|
import ContextMenu from '$components/contextmenu.svelte';
|
||||||
|
import dialogs from '$lib/dialogs';
|
||||||
import contextMenu from '$lib/stores/contextmenu';
|
import contextMenu from '$lib/stores/contextmenu';
|
||||||
import environment from '$lib/stores/environment';
|
import environment from '$lib/stores/environment';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import applicationInited from '$lib/stores/inited';
|
import applicationInited from '$lib/stores/inited';
|
||||||
import windowTitle from '$lib/stores/windowtitle';
|
import windowTitle from '$lib/stores/windowtitle';
|
||||||
import About from '$organisms/about.svelte';
|
|
||||||
import Connection from '$organisms/connection/index.svelte';
|
import Connection from '$organisms/connection/index.svelte';
|
||||||
import Settings from '$organisms/settings/index.svelte';
|
|
||||||
import { EventsOn } from '$wails/runtime';
|
import { EventsOn } from '$wails/runtime';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
import AboutDialog from './dialogs/about.svelte';
|
||||||
|
import SettingsDialog from './dialogs/settings/index.svelte';
|
||||||
|
|
||||||
const activeHostKey = '';
|
|
||||||
let activeDbKey = '';
|
|
||||||
let activeCollKey = '';
|
|
||||||
let settingsModalOpen = false;
|
|
||||||
let aboutModalOpen = false;
|
|
||||||
let connectionManager;
|
|
||||||
let showWelcomeScreen = undefined;
|
let showWelcomeScreen = undefined;
|
||||||
|
|
||||||
hosts.subscribe(h => {
|
applicationInited.defer(() => {
|
||||||
if (h && (showWelcomeScreen === undefined)) {
|
hostTree.subscribe(hosts => {
|
||||||
showWelcomeScreen = !Object.keys($hosts || {}).length;
|
if (hostTree.hasBeenInited() && (showWelcomeScreen === undefined)) {
|
||||||
}
|
showWelcomeScreen = !Object.keys(hosts || {}).length;
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createFirstHost() {
|
async function createFirstHost() {
|
||||||
showWelcomeScreen = false;
|
showWelcomeScreen = false;
|
||||||
await tick();
|
await tick();
|
||||||
connectionManager.createHost();
|
hostTree.newHost();
|
||||||
}
|
}
|
||||||
|
|
||||||
EventsOn('OpenPreferences', () => settingsModalOpen = true);
|
function showAboutDialog() {
|
||||||
EventsOn('OpenAboutModal', () => aboutModalOpen = true);
|
dialogs.new(AboutDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
function showSettings() {
|
||||||
|
dialogs.new(SettingsDialog);
|
||||||
|
}
|
||||||
|
|
||||||
|
EventsOn('OpenPreferences', showSettings);
|
||||||
|
EventsOn('OpenAboutModal', showAboutDialog);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:contextmenu|preventDefault />
|
<svelte:window on:contextmenu|preventDefault />
|
||||||
@ -41,23 +46,20 @@
|
|||||||
<div id="root" class="platform-{$environment?.platform}">
|
<div id="root" class="platform-{$environment?.platform}">
|
||||||
<div class="titlebar">{$windowTitle}</div>
|
<div class="titlebar">{$windowTitle}</div>
|
||||||
|
|
||||||
{#if $applicationInited && $hosts && (showWelcomeScreen !== undefined)}
|
{#if $applicationInited && (showWelcomeScreen !== undefined)}
|
||||||
<main class:empty={showWelcomeScreen}>
|
<main class:empty={showWelcomeScreen}>
|
||||||
{#if showWelcomeScreen}
|
{#if showWelcomeScreen}
|
||||||
<BlankState label="Welcome to Rolens!" image="/logo.png" pale={false} big={true}>
|
<BlankState label="Welcome to Rolens!" image="/logo.png" pale={false} big={true}>
|
||||||
<button class="btn" on:click={createFirstHost}>Add your first host</button>
|
<button class="btn" on:click={createFirstHost}>Add your first host</button>
|
||||||
</BlankState>
|
</BlankState>
|
||||||
{:else}
|
{:else}
|
||||||
<Connection {activeHostKey} bind:activeCollKey bind:activeDbKey bind:this={connectionManager} />
|
<Connection />
|
||||||
{/if}
|
{/if}
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
{#key $contextMenu}
|
{#key $contextMenu}
|
||||||
<ContextMenu {...$contextMenu} on:close={contextMenu.hide} />
|
<ContextMenu {...$contextMenu} on:close={contextMenu.hide} />
|
||||||
{/key}
|
{/key}
|
||||||
|
|
||||||
<Settings bind:show={settingsModalOpen} />
|
|
||||||
<About bind:show={aboutModalOpen} />
|
|
||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
|
|
||||||
activeKey = itemKey;
|
activeKey = itemKey;
|
||||||
activePath = [ ...path.slice(0, level), itemKey ];
|
activePath = [ ...path.slice(0, level), itemKey ];
|
||||||
dispatch('select', { level, itemKey, index });
|
dispatch('select', { level, itemKey, index, path: activePath });
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAll() {
|
function closeAll() {
|
||||||
|
@ -4,15 +4,17 @@
|
|||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { Beep } from '$wails/go/ui/UI';
|
import { Beep } from '$wails/go/ui/UI';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { fade, fly } from 'svelte/transition';
|
import { fade, fly } from 'svelte/transition';
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
|
|
||||||
export let show = false;
|
export let show = true;
|
||||||
export let title = undefined;
|
export let title = undefined;
|
||||||
export let contentPadding = true;
|
export let contentPadding = true;
|
||||||
export let width = '80vw';
|
export let width = '80vw';
|
||||||
export let overflow = true;
|
export let overflow = true;
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
const level = numberOfModalsOpen + 1;
|
const level = numberOfModalsOpen + 1;
|
||||||
let isNew = true;
|
let isNew = true;
|
||||||
|
|
||||||
@ -29,9 +31,13 @@
|
|||||||
function keydown(event) {
|
function keydown(event) {
|
||||||
if ((event.key === 'Escape') && (level === numberOfModalsOpen)) {
|
if ((event.key === 'Escape') && (level === numberOfModalsOpen)) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
show = false;
|
close();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function close() {
|
||||||
|
dispatch('close');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:window on:keydown={keydown} />
|
<svelte:window on:keydown={keydown} />
|
||||||
@ -42,7 +48,7 @@
|
|||||||
{#if title}
|
{#if title}
|
||||||
<header>
|
<header>
|
||||||
<div class="title">{title}</div>
|
<div class="title">{title}</div>
|
||||||
<button class="btn close" on:click={() => show = false} title="close" type="button">
|
<button class="btn close" on:click={close} title="close" type="button">
|
||||||
<Icon name="x" />
|
<Icon name="x" />
|
||||||
</button>
|
</button>
|
||||||
</header>
|
</header>
|
||||||
@ -69,6 +75,7 @@
|
|||||||
background-color: rgba(0, 0, 0, 0.5);
|
background-color: rgba(0, 0, 0, 0.5);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-top: 50px;
|
padding-top: 50px;
|
||||||
|
--wails-draggable: drag;
|
||||||
}
|
}
|
||||||
:global(#root.platform-darwin) .outer {
|
:global(#root.platform-darwin) .outer {
|
||||||
margin-top: var(--darwin-titlebar-height);
|
margin-top: var(--darwin-titlebar-height);
|
||||||
@ -86,6 +93,7 @@
|
|||||||
flex-flow: column;
|
flex-flow: column;
|
||||||
cursor: auto;
|
cursor: auto;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
--wails-draggable: nodrag;
|
||||||
}
|
}
|
||||||
.inner > :global(*:first-child) {
|
.inner > :global(*:first-child) {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
<script>
|
<script>
|
||||||
import { looseJsonIsValid } from '$lib/strings';
|
import { looseJsonIsValid } from '$lib/strings';
|
||||||
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||||
import Icon from './icon.svelte';
|
import Icon from './icon.svelte';
|
||||||
import Modal from './modal.svelte';
|
import Modal from './modal.svelte';
|
||||||
import ObjectEditor from './objecteditor.svelte';
|
import ObjectEditor from './objecteditor.svelte';
|
||||||
import { EJSON } from 'bson';
|
|
||||||
|
|
||||||
export let data;
|
export let data;
|
||||||
export let saveable = false;
|
export let saveable = false;
|
||||||
|
@ -2,11 +2,9 @@
|
|||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import alink from '$lib/actions/alink';
|
import alink from '$lib/actions/alink';
|
||||||
import environment from '$lib/stores/environment';
|
import environment from '$lib/stores/environment';
|
||||||
|
|
||||||
export let show = true;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show width="400px" title=" ">
|
<Modal width="400px" title=" " on:close>
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<img src="/logo.png" alt="Rolens logo" />
|
<img src="/logo.png" alt="Rolens logo" />
|
||||||
<div>
|
<div>
|
@ -3,11 +3,9 @@
|
|||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import settings from '$lib/stores/settings';
|
import settings from '$lib/stores/settings';
|
||||||
|
|
||||||
export let show = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="Preferences" bind:show>
|
<Modal title="Preferences" on:close>
|
||||||
<div class="prefs">
|
<div class="prefs">
|
||||||
<label for="defaultLimit">Initial number of items to retrieve using one query (limit):</label>
|
<label for="defaultLimit">Initial number of items to retrieve using one query (limit):</label>
|
||||||
<label class="field">
|
<label class="field">
|
20
frontend/src/lib/dialogs.js
Normal file
20
frontend/src/lib/dialogs.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
function newDialog(dialogComponent, data = {}) {
|
||||||
|
const outlet = document.createElement('div');
|
||||||
|
outlet.className = 'dialogoutlet';
|
||||||
|
document.getElementById('dialogoutlets').appendChild(outlet);
|
||||||
|
|
||||||
|
const instance = new dialogComponent({ target: outlet, props: data });
|
||||||
|
|
||||||
|
instance.$close = function() {
|
||||||
|
instance.$destroy();
|
||||||
|
outlet.remove();
|
||||||
|
};
|
||||||
|
|
||||||
|
instance.$on('close', instance.$close);
|
||||||
|
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dialogs = { new: newDialog };
|
||||||
|
|
||||||
|
export default dialogs;
|
@ -1,5 +0,0 @@
|
|||||||
import { writable } from 'svelte/store';
|
|
||||||
|
|
||||||
const connections = writable({});
|
|
||||||
|
|
||||||
export default connections;
|
|
@ -1,11 +0,0 @@
|
|||||||
import { Hosts } from '$wails/go/app/App';
|
|
||||||
import { writable } from 'svelte/store';
|
|
||||||
import applicationInited from './inited';
|
|
||||||
|
|
||||||
const { set, subscribe } = writable();
|
|
||||||
|
|
||||||
const update = async() => set(await Hosts());
|
|
||||||
applicationInited.defer(update);
|
|
||||||
|
|
||||||
const hosts = { update, subscribe };
|
|
||||||
export default hosts;
|
|
312
frontend/src/lib/stores/hosttree.js
Normal file
312
frontend/src/lib/stores/hosttree.js
Normal file
@ -0,0 +1,312 @@
|
|||||||
|
import dialogs from '$lib/dialogs';
|
||||||
|
import { startProgress } from '$lib/progress';
|
||||||
|
import { get, writable } from 'svelte/store';
|
||||||
|
import applicationInited from './inited';
|
||||||
|
import queries from './queries';
|
||||||
|
import windowTitle from './windowtitle';
|
||||||
|
|
||||||
|
import ExportDialog from '$organisms/connection/collection/dialogs/export.svelte';
|
||||||
|
import IndexDetailDialog from '$organisms/connection/collection/dialogs/indexdetail.svelte';
|
||||||
|
import QueryChooserDialog from '$organisms/connection/collection/dialogs/querychooser.svelte';
|
||||||
|
import DumpDialog from '$organisms/connection/database/dialogs/dump.svelte';
|
||||||
|
import HostDetailDialog from '$organisms/connection/host/dialogs/hostdetail.svelte';
|
||||||
|
|
||||||
|
import {
|
||||||
|
CreateIndex,
|
||||||
|
DropCollection,
|
||||||
|
DropDatabase,
|
||||||
|
DropIndex,
|
||||||
|
GetIndexes,
|
||||||
|
Hosts,
|
||||||
|
OpenCollection,
|
||||||
|
OpenConnection,
|
||||||
|
OpenDatabase,
|
||||||
|
PerformDump,
|
||||||
|
PerformFindExport,
|
||||||
|
RemoveHost,
|
||||||
|
RenameCollection,
|
||||||
|
TruncateCollection
|
||||||
|
} from '$wails/go/app/App';
|
||||||
|
|
||||||
|
const { set, subscribe } = writable({});
|
||||||
|
const getValue = () => get({ subscribe });
|
||||||
|
let hostTreeInited = false;
|
||||||
|
|
||||||
|
async function refresh() {
|
||||||
|
const hosts = await Hosts();
|
||||||
|
const hostTree = getValue();
|
||||||
|
|
||||||
|
for (const [ hostKey, hostDetails ] of Object.entries(hosts)) {
|
||||||
|
hostTree[hostKey] = hostTree[hostKey] || {};
|
||||||
|
const host = hostTree[hostKey];
|
||||||
|
host.key = hostKey;
|
||||||
|
host.name = hostDetails.name;
|
||||||
|
host.uri = hostDetails.uri;
|
||||||
|
|
||||||
|
host.open = async function() {
|
||||||
|
const progress = startProgress(`Connecting to "${hostKey}"…`);
|
||||||
|
const { databases: dbNames, status, systemInfo } = await OpenConnection(hostKey);
|
||||||
|
host.status = status;
|
||||||
|
host.systemInfo = systemInfo;
|
||||||
|
host.databases = host.databases || {};
|
||||||
|
|
||||||
|
if (!dbNames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const dbKey of dbNames.sort((a, b) => a.localeCompare(b))) {
|
||||||
|
host.databases[dbKey] = host.databases[dbKey] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [ dbKey, database ] of Object.entries(host.databases)) {
|
||||||
|
if (!dbNames.includes(dbKey)) {
|
||||||
|
delete host.databases[dbKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
database.key = dbKey;
|
||||||
|
database.hostKey = hostKey;
|
||||||
|
database.collections = database.collections || {};
|
||||||
|
|
||||||
|
database.open = async function() {
|
||||||
|
const progress = startProgress(`Opening database "${dbKey}"…`);
|
||||||
|
const { collections: collNames, stats } = await OpenDatabase(hostKey, dbKey);
|
||||||
|
database.stats = stats;
|
||||||
|
|
||||||
|
if (!collNames) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const collKey of collNames.sort((a, b) => a.localeCompare(b))) {
|
||||||
|
database.collections[collKey] = database.collections[collKey] || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [ collKey, collection ] of Object.entries(database.collections)) {
|
||||||
|
if (!collNames.includes(collKey)) {
|
||||||
|
delete database.collections[collKey];
|
||||||
|
}
|
||||||
|
|
||||||
|
collection.key = collKey;
|
||||||
|
collection.dbKey = dbKey;
|
||||||
|
collection.hostKey = hostKey;
|
||||||
|
collection.indexes = collection.indexes || [];
|
||||||
|
|
||||||
|
collection.open = async function() {
|
||||||
|
const progress = startProgress(`Opening database "${dbKey}"…`);
|
||||||
|
const stats = await OpenCollection(hostKey, dbKey, collKey);
|
||||||
|
collection.stats = stats;
|
||||||
|
await refresh();
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.rename = async function() {
|
||||||
|
const newCollKey = await dialogs.enterText('Rename collection', `Enter a new name for collection ${collKey}.`, collKey);
|
||||||
|
if (newCollKey && (newCollKey !== collKey)) {
|
||||||
|
const progress = startProgress(`Renaming collection "${collKey}" to "${newCollKey}"…`);
|
||||||
|
const ok = await RenameCollection(hostKey, dbKey, collKey, newCollKey);
|
||||||
|
await database.open();
|
||||||
|
progress.end();
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.export = function(query) {
|
||||||
|
const dialog = dialogs.new(ExportDialog, { collection, query });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('export', async event => {
|
||||||
|
const success = await PerformFindExport(hostKey, dbKey, collKey, JSON.stringify(event.detail.exportInfo));
|
||||||
|
if (success) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.dump = function() {
|
||||||
|
const dialog = dialogs.new(DumpDialog, { info: {
|
||||||
|
hostKey,
|
||||||
|
dbKey,
|
||||||
|
collKeys: [ collKey ],
|
||||||
|
} });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('dump', async event => {
|
||||||
|
const success = await PerformDump(JSON.stringify(event.detail.info));
|
||||||
|
if (success) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.truncate = async function() {
|
||||||
|
const progress = startProgress(`Truncating collection "${collKey}"…`);
|
||||||
|
await TruncateCollection(hostKey, dbKey, collKey);
|
||||||
|
await refresh();
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.drop = async function() {
|
||||||
|
const progress = startProgress(`Dropping collection "${collKey}"…`);
|
||||||
|
const success = await DropCollection(hostKey, dbKey, collKey);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.getIndexes = async function() {
|
||||||
|
const progress = startProgress(`Retrieving indexes of "${collKey}"…`);
|
||||||
|
collection.indexes = [];
|
||||||
|
const indexes = await GetIndexes(hostKey, dbKey, collKey);
|
||||||
|
|
||||||
|
for (const indexDetails of indexes) {
|
||||||
|
const index = {
|
||||||
|
name: indexDetails.name,
|
||||||
|
background: indexDetails.background || false,
|
||||||
|
unique: indexDetails.unique || false,
|
||||||
|
sparse: indexDetails.sparse || false,
|
||||||
|
model: indexDetails.model,
|
||||||
|
};
|
||||||
|
|
||||||
|
index.drop = async function() {
|
||||||
|
const progress = startProgress(`Dropping index ${index.name}…`);
|
||||||
|
const hasBeenDropped = await DropIndex(hostKey, dbKey, collKey, index.name);
|
||||||
|
progress.end();
|
||||||
|
return hasBeenDropped;
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.indexes.push(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
return collection.indexes;
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.getIndexByName = function(indesName) {
|
||||||
|
return collection.indexes.find(idx => idx.name = indesName);
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.newIndex = function() {
|
||||||
|
const dialog = dialogs.new(IndexDetailDialog, { collection });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('create', async event => {
|
||||||
|
const progress = startProgress('Creating index…');
|
||||||
|
const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(event.detail.index));
|
||||||
|
|
||||||
|
if (newIndexName) {
|
||||||
|
dialog.$close();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
resolve(newIndexName);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
collection.openQueryChooser = function(queryToSave = undefined) {
|
||||||
|
const dialog = dialogs.new(QueryChooserDialog, { collection, queryToSave });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('select', async event => {
|
||||||
|
dialog.$close();
|
||||||
|
resolve(event.detail.query);
|
||||||
|
});
|
||||||
|
|
||||||
|
dialog.$on('create', async event => {
|
||||||
|
const ok = await queries.create(event.detail.query);
|
||||||
|
if (ok) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve(event.detail.query);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
progress.end();
|
||||||
|
windowTitle.setSegments(dbKey, host.name, 'Rolens');
|
||||||
|
};
|
||||||
|
|
||||||
|
database.dump = function() {
|
||||||
|
const dialog = dialogs.new(DumpDialog, { info: { hostKey, dbKey } });
|
||||||
|
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('dump', async event => {
|
||||||
|
const success = await PerformDump(JSON.stringify(event.detail.info));
|
||||||
|
if (success) {
|
||||||
|
dialog.$close();
|
||||||
|
resolve();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
database.drop = async function() {
|
||||||
|
const progress = startProgress(`Dropping database "${dbKey}"…`);
|
||||||
|
const success = await DropDatabase(hostKey, dbKey);
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
database.newCollection = async function() {
|
||||||
|
const name = await dialogs.enterText('Create a collection', 'Note: collections in MongoDB do not exist until they have at least one item. Your new collection will not persist on the server; fill it to have it created.', '');
|
||||||
|
if (name) {
|
||||||
|
database.collections[name] = {};
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
host.newDatabase = async function() {
|
||||||
|
const name = await dialogs.enterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.', '');
|
||||||
|
if (name) {
|
||||||
|
host.databases[name] = {};
|
||||||
|
await refresh();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
await refresh();
|
||||||
|
progress.end();
|
||||||
|
};
|
||||||
|
|
||||||
|
host.remove = async function() {
|
||||||
|
await RemoveHost(hostKey);
|
||||||
|
await refresh();
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
hostTreeInited = true;
|
||||||
|
set(hostTree);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newHost() {
|
||||||
|
const dialog = dialogs.new(HostDetailDialog, { hostKey: '' });
|
||||||
|
return new Promise(resolve => {
|
||||||
|
dialog.$on('close', () => {
|
||||||
|
refresh().then(resolve);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
applicationInited.defer(refresh);
|
||||||
|
|
||||||
|
const hostTree = {
|
||||||
|
refresh,
|
||||||
|
subscribe,
|
||||||
|
get: getValue,
|
||||||
|
newHost,
|
||||||
|
hasBeenInited: () => hostTreeInited,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default hostTree;
|
@ -18,16 +18,12 @@ const { subscribe } = derived([ environment, applicationSettings ], ([ env, sett
|
|||||||
if (alreadyInited) {
|
if (alreadyInited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
else if (env && settings) {
|
||||||
if (env && settings) {
|
Promise.all(listeners.map(l => l())).then(() => {
|
||||||
set(true);
|
set(true);
|
||||||
alreadyInited = true;
|
alreadyInited = true;
|
||||||
|
document.getElementById('app-loading')?.remove();
|
||||||
// Remove loading spinner.
|
});
|
||||||
document.getElementById('app-loading')?.remove();
|
|
||||||
|
|
||||||
// Call hooks
|
|
||||||
listeners.forEach(l => l());
|
|
||||||
}
|
}
|
||||||
}, false);
|
}, false);
|
||||||
|
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import dialogs from '$lib/dialogs';
|
||||||
|
import ViewConfigDialog from '$organisms/connection/collection/dialogs/viewconfig.svelte';
|
||||||
import { UpdateViewStore, Views } from '$wails/go/app/App';
|
import { UpdateViewStore, Views } from '$wails/go/app/App';
|
||||||
import { get, writable } from 'svelte/store';
|
import { get, writable } from 'svelte/store';
|
||||||
|
|
||||||
@ -23,6 +25,11 @@ function forCollection(hostKey, dbKey, collKey) {
|
|||||||
return Object.fromEntries(entries);
|
return Object.fromEntries(entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openConfig(collection, firstItem = {}) {
|
||||||
|
const dialog = dialogs.new(ViewConfigDialog, { collection, firstItem });
|
||||||
|
return dialog;
|
||||||
|
}
|
||||||
|
|
||||||
reload();
|
reload();
|
||||||
subscribe(newViewStore => {
|
subscribe(newViewStore => {
|
||||||
if (skipUpdate) {
|
if (skipUpdate) {
|
||||||
@ -32,5 +39,5 @@ subscribe(newViewStore => {
|
|||||||
UpdateViewStore(JSON.stringify(newViewStore));
|
UpdateViewStore(JSON.stringify(newViewStore));
|
||||||
});
|
});
|
||||||
|
|
||||||
const views = { reload, set, subscribe, forCollection };
|
const views = { reload, set, subscribe, forCollection, openConfig };
|
||||||
export default views;
|
export default views;
|
||||||
|
@ -2,9 +2,9 @@
|
|||||||
import Details from '$components/details.svelte';
|
import Details from '$components/details.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import Collation from '$lib/mongo/collation.svelte';
|
|
||||||
import ObjectEditor from '$components/objecteditor.svelte';
|
import ObjectEditor from '$components/objecteditor.svelte';
|
||||||
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
||||||
|
import Collation from '$lib/mongo/collation.svelte';
|
||||||
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
||||||
import { Aggregate } from '$wails/go/app/App';
|
import { Aggregate } from '$wails/go/app/App';
|
||||||
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<script>
|
<script>
|
||||||
import FormInput from '$components/forminput.svelte';
|
import FormInput from '$components/forminput.svelte';
|
||||||
|
import Hint from '$components/hint.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import { inputTypes } from '$lib/mongo';
|
import { inputTypes } from '$lib/mongo';
|
||||||
import { resolveKeypath, setValue } from '$lib/objects';
|
import { resolveKeypath, setValue } from '$lib/objects';
|
||||||
import Hint from '$components/hint.svelte';
|
|
||||||
|
|
||||||
export let item = {};
|
export let item = {};
|
||||||
export let view = {};
|
export let view = {};
|
||||||
|
@ -2,43 +2,34 @@
|
|||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
import { PerformFindExport } from '$wails/go/app/App';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let info;
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
export let query = undefined;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let viewKey = collection.viewKey;
|
const exportInfo = { ...query, viewKey: collection.viewKey };
|
||||||
$: viewKey = collection.viewKey;
|
|
||||||
$: if (info) {
|
|
||||||
info.viewKey = viewKey;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function performExport() {
|
function submit() {
|
||||||
info.view = $views[viewKey];
|
exportInfo.view = $views[exportInfo.viewKey];
|
||||||
const success = await PerformFindExport(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(info));
|
dispatch('export', { exportInfo });
|
||||||
|
|
||||||
if (success) {
|
|
||||||
info = undefined;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show={info} title="Export results" width="450px">
|
<Modal title="Export results" width="450px" on:close>
|
||||||
<form on:submit|preventDefault={performExport}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Export</span>
|
<span class="label">Export</span>
|
||||||
<select bind:value={info.contents}>
|
<select bind:value={exportInfo.contents}>
|
||||||
<option value="all">all records</option>
|
<option value="all">all records</option>
|
||||||
<option value="query">all records matching query</option>
|
<option value="query" disabled={!query}>all records matching query</option>
|
||||||
<option value="querylimitskip">all records matching query, considering limit and skip</option>
|
<option value="querylimitskip" disabled={!query}>all records matching query, considering limit and skip</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Format</span>
|
<span class="label">Format</span>
|
||||||
<select bind:value={info.format}>
|
<select bind:value={exportInfo.format}>
|
||||||
<option value="jsonarray">JSON array</option>
|
<option value="jsonarray">JSON array</option>
|
||||||
<option value="ndjson">Newline delimited JSON</option>
|
<option value="ndjson">Newline delimited JSON</option>
|
||||||
<option value="csv">CSV</option>
|
<option value="csv">CSV</option>
|
||||||
@ -47,7 +38,7 @@
|
|||||||
|
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">View to use</span>
|
<span class="label">View to use</span>
|
||||||
<select bind:value={viewKey}>
|
<select bind:value={exportInfo.viewKey}>
|
||||||
{#each Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key)) as [ key, { name } ]}
|
{#each Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key)) as [ key, { name } ]}
|
||||||
<option value={key}>{name}</option>
|
<option value={key}>{name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
@ -59,7 +50,7 @@
|
|||||||
</form>
|
</form>
|
||||||
|
|
||||||
<svelte:fragment slot="footer">
|
<svelte:fragment slot="footer">
|
||||||
<button class="btn" on:click={performExport}>
|
<button class="btn" on:click={submit}>
|
||||||
<Icon name="play" /> Start export
|
<Icon name="play" /> Start export
|
||||||
</button>
|
</button>
|
||||||
</svelte:fragment>
|
</svelte:fragment>
|
@ -2,14 +2,12 @@
|
|||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import { CreateIndex } from '$wails/go/app/App';
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let collection = {};
|
export let collection;
|
||||||
export let creatingNewIndex = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let index = { model: [] };
|
const index = { model: [] };
|
||||||
|
|
||||||
function addRule() {
|
function addRule() {
|
||||||
index.model = [ ...index.model, {} ];
|
index.model = [ ...index.model, {} ];
|
||||||
@ -21,16 +19,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function create() {
|
async function create() {
|
||||||
const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(index));
|
dispatch('create', { index });
|
||||||
if (newIndexName) {
|
|
||||||
creatingNewIndex = false;
|
|
||||||
index = { model: [] };
|
|
||||||
dispatch('reload');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="Create new index {collection ? `on collection ${collection.key}` : ''}" bind:show={creatingNewIndex}>
|
<Modal title="Create new index on {collection.key}" on:close>
|
||||||
<form on:submit|preventDefault={create}>
|
<form on:submit|preventDefault={create}>
|
||||||
<label class="field name">
|
<label class="field name">
|
||||||
<span class="label">Name</span>
|
<span class="label">Name</span>
|
@ -4,13 +4,12 @@
|
|||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import queries from '$lib/stores/queries';
|
import queries from '$lib/stores/queries';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let queryToSave = undefined;
|
export let queryToSave = undefined;
|
||||||
export let collection = {};
|
export let collection = {};
|
||||||
export let show = false;
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let gridSelectedPath = [];
|
let gridSelectedPath = [];
|
||||||
@ -22,23 +21,16 @@
|
|||||||
queryToSave.dbKey = collection.dbKey;
|
queryToSave.dbKey = collection.dbKey;
|
||||||
queryToSave.collKey = collection.key;
|
queryToSave.collKey = collection.key;
|
||||||
|
|
||||||
const newId = queries.create(queryToSave);
|
dispatch('create', { query: queryToSave });
|
||||||
|
selectedKey = queryToSave.name;
|
||||||
if (newId) {
|
|
||||||
dispatch('created', newId);
|
|
||||||
queryToSave = undefined;
|
|
||||||
selectedKey = newId;
|
|
||||||
select();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
select();
|
selectActive();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function select() {
|
function selectActive() {
|
||||||
dispatch('select', selectedKey);
|
dispatch('select', { query: $queries[selectedKey] });
|
||||||
show = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function gridSelect(event) {
|
function gridSelect(event) {
|
||||||
@ -53,7 +45,7 @@
|
|||||||
|
|
||||||
function gridTrigger(event) {
|
function gridTrigger(event) {
|
||||||
gridSelect(event);
|
gridSelect(event);
|
||||||
select();
|
selectActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function gridRemove(event) {
|
async function gridRemove(event) {
|
||||||
@ -71,7 +63,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show title={queryToSave ? 'Save query' : 'Load query'} width="500px">
|
<Modal title={queryToSave ? 'Save query' : 'Load query'} width="500px" on:close>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
{#if queryToSave}
|
{#if queryToSave}
|
||||||
<label class="field queryname">
|
<label class="field queryname">
|
||||||
@ -88,7 +80,11 @@
|
|||||||
columns={[ { key: 'n', title: 'Query name' }, { key: 'h', title: 'Host' }, { key: 'ns', title: 'Namespace' } ]}
|
columns={[ { key: 'n', title: 'Query name' }, { key: 'h', title: 'Host' }, { key: 'ns', title: 'Namespace' } ]}
|
||||||
key="n"
|
key="n"
|
||||||
items={Object.entries($queries).reduce((object, [ name, query ]) => {
|
items={Object.entries($queries).reduce((object, [ name, query ]) => {
|
||||||
object[query.name] = { n: name, h: $hosts[query.hostKey]?.name || '?', ns: `${query.dbKey}.${query.collKey}` };
|
object[query.name] = {
|
||||||
|
n: name,
|
||||||
|
h: $hostTree[query.hostKey]?.name || '?',
|
||||||
|
ns: `${query.dbKey}.${query.collKey}`,
|
||||||
|
};
|
||||||
return object;
|
return object;
|
||||||
}, {})}
|
}, {})}
|
||||||
showHeaders={true}
|
showHeaders={true}
|
@ -7,15 +7,18 @@
|
|||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
export let show = false;
|
|
||||||
export let activeViewKey = 'list';
|
|
||||||
export let firstItem = {};
|
export let firstItem = {};
|
||||||
|
|
||||||
$: tabs = Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key))
|
let tabs = [];
|
||||||
.sort((a, b) => sortTabKeys(a[0], b[0]))
|
$: $views && refresh();
|
||||||
.map(([ key, v ]) => {
|
|
||||||
return { key, title: v.name, closable: key !== 'list' };
|
function refresh() {
|
||||||
});
|
tabs = Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key))
|
||||||
|
.sort((a, b) => sortTabKeys(a[0], b[0]))
|
||||||
|
.map(([ key, v ]) => {
|
||||||
|
return { key, title: v.name, closable: key !== 'list' };
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function sortTabKeys(a, b) {
|
function sortTabKeys(a, b) {
|
||||||
if (a === 'list') {
|
if (a === 'list') {
|
||||||
@ -39,29 +42,29 @@
|
|||||||
type: 'table',
|
type: 'table',
|
||||||
columns: [ { key: '_id', showInTable: true, inputType: 'objectid', mandatory: true } ],
|
columns: [ { key: '_id', showInTable: true, inputType: 'objectid', mandatory: true } ],
|
||||||
};
|
};
|
||||||
activeViewKey = newViewKey;
|
collection.viewKey = newViewKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeView(viewKey) {
|
function removeView(viewKey) {
|
||||||
const keys = Object.keys($views).sort(sortTabKeys);
|
const keys = Object.keys($views).sort(sortTabKeys);
|
||||||
const oldIndex = keys.indexOf(viewKey);
|
const oldIndex = keys.indexOf(viewKey);
|
||||||
const newKey = keys[oldIndex - 1];
|
const newKey = keys[oldIndex - 1];
|
||||||
activeViewKey = newKey;
|
collection.viewKey = newKey;
|
||||||
delete $views[viewKey];
|
delete $views[viewKey];
|
||||||
$views = $views;
|
$views = $views;
|
||||||
}
|
}
|
||||||
|
|
||||||
function addColumn(before) {
|
function addColumn(before) {
|
||||||
if (typeof before === 'number') {
|
if (typeof before === 'number') {
|
||||||
$views[activeViewKey].columns = [
|
$views[collection.viewKey].columns = [
|
||||||
...$views[activeViewKey].columns.slice(0, before),
|
...$views[collection.viewKey].columns.slice(0, before),
|
||||||
{ showInTable: true, inputType: 'none' },
|
{ showInTable: true, inputType: 'none' },
|
||||||
...$views[activeViewKey].columns.slice(before),
|
...$views[collection.viewKey].columns.slice(before),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
$views[activeViewKey].columns = [
|
$views[collection.viewKey].columns = [
|
||||||
...$views[activeViewKey].columns,
|
...$views[collection.viewKey].columns,
|
||||||
{ showInTable: true, inputType: 'none' },
|
{ showInTable: true, inputType: 'none' },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@ -72,7 +75,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$views[activeViewKey].columns = Object.keys(firstItem).sort().map(key => {
|
$views[collection.viewKey].columns = Object.keys(firstItem).sort().map(key => {
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
@ -82,57 +85,57 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function moveColumn(oldIndex, delta) {
|
function moveColumn(oldIndex, delta) {
|
||||||
const column = $views[activeViewKey].columns[oldIndex];
|
const column = $views[collection.viewKey].columns[oldIndex];
|
||||||
const newIndex = oldIndex + delta;
|
const newIndex = oldIndex + delta;
|
||||||
|
|
||||||
$views[activeViewKey].columns.splice(oldIndex, 1);
|
$views[collection.viewKey].columns.splice(oldIndex, 1);
|
||||||
$views[activeViewKey].columns.splice(newIndex, 0, column);
|
$views[collection.viewKey].columns.splice(newIndex, 0, column);
|
||||||
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
$views[collection.viewKey].columns = $views[collection.viewKey].columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeColumn(index) {
|
function removeColumn(index) {
|
||||||
$views[activeViewKey].columns.splice(index, 1);
|
$views[collection.viewKey].columns.splice(index, 1);
|
||||||
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
$views[collection.viewKey].columns = $views[collection.viewKey].columns;
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal title="View configuration" bind:show contentPadding={false}>
|
<Modal title="View configuration" contentPadding={false} on:close>
|
||||||
<TabBar
|
<TabBar
|
||||||
{tabs}
|
{tabs}
|
||||||
canAddTab={true}
|
canAddTab={true}
|
||||||
on:addTab={createView}
|
on:addTab={createView}
|
||||||
on:closeTab={e => removeView(e.detail)}
|
on:closeTab={e => removeView(e.detail)}
|
||||||
bind:selectedKey={activeViewKey}
|
bind:selectedKey={collection.viewKey}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="options">
|
<div class="options">
|
||||||
{#if $views[activeViewKey]}
|
{#if $views[collection.viewKey]}
|
||||||
<div class="meta">
|
<div class="meta">
|
||||||
{#key activeViewKey}
|
{#key collection.viewKey}
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">View name</span>
|
<span class="label">View name</span>
|
||||||
<input type="text" use:input={{ autofocus: true }} bind:value={$views[activeViewKey].name} disabled={activeViewKey === 'list'} />
|
<input type="text" use:input={{ autofocus: true }} bind:value={$views[collection.viewKey].name} disabled={collection.viewKey === 'list'} />
|
||||||
</label>
|
</label>
|
||||||
{/key}
|
{/key}
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">View type</span>
|
<span class="label">View type</span>
|
||||||
<select bind:value={$views[activeViewKey].type} disabled>
|
<select bind:value={$views[collection.viewKey].type} disabled>
|
||||||
<option value="list">List view</option>
|
<option value="list">List view</option>
|
||||||
<option value="table">Table view</option>
|
<option value="table">Table view</option>
|
||||||
</select>
|
</select>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if $views[activeViewKey].type === 'list'}
|
{#if $views[collection.viewKey].type === 'list'}
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<input type="checkbox" id="hideObjectIndicators" bind:checked={$views[activeViewKey].hideObjectIndicators} />
|
<input type="checkbox" id="hideObjectIndicators" bind:checked={$views[collection.viewKey].hideObjectIndicators} />
|
||||||
<label for="hideObjectIndicators">
|
<label for="hideObjectIndicators">
|
||||||
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
{:else if $views[activeViewKey].type === 'table'}
|
{:else if $views[collection.viewKey].type === 'table'}
|
||||||
<div class="columns">
|
<div class="columns">
|
||||||
{#each $views[activeViewKey].columns as column, columnIndex}
|
{#each $views[collection.viewKey].columns as column, columnIndex}
|
||||||
<div class="column">
|
<div class="column">
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<input type="text" use:input bind:value={column.key} placeholder="Column keypath" />
|
<input type="text" use:input bind:value={column.key} placeholder="Column keypath" />
|
||||||
@ -188,7 +191,7 @@
|
|||||||
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, -1)} disabled={columnIndex === 0} title="Move column one position up">
|
||||||
<Icon name="chev-u" />
|
<Icon name="chev-u" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === $views[activeViewKey].columns.length - 1} title="Move column one position down">
|
<button class="btn" type="button" on:click={() => moveColumn(columnIndex, 1)} disabled={columnIndex === $views[collection.viewKey].columns.length - 1} title="Move column one position down">
|
||||||
<Icon name="chev-d" />
|
<Icon name="chev-d" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
<button class="btn danger" type="button" on:click={() => removeColumn(columnIndex)} title="Remove this column">
|
@ -6,18 +6,15 @@
|
|||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import { deepClone } from '$lib/objects';
|
import { deepClone } from '$lib/objects';
|
||||||
import { startProgress } from '$lib/progress';
|
import { startProgress } from '$lib/progress';
|
||||||
import queries from '$lib/stores/queries';
|
|
||||||
import applicationSettings from '$lib/stores/settings';
|
import applicationSettings from '$lib/stores/settings';
|
||||||
import views from '$lib/stores/views';
|
import views from '$lib/stores/views';
|
||||||
|
import { convertLooseJson } from '$lib/strings';
|
||||||
import { FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App';
|
import { FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App';
|
||||||
import { EJSON } from 'bson';
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { onMount } from 'svelte';
|
||||||
import ExportInfo from './components/export.svelte';
|
|
||||||
import QueryChooser from './components/querychooser.svelte';
|
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
const defaults = {
|
const defaults = {
|
||||||
query: '{}',
|
query: '{}',
|
||||||
sort: $applicationSettings.defaultSort || '{ "_id": 1 }',
|
sort: $applicationSettings.defaultSort || '{ "_id": 1 }',
|
||||||
@ -32,17 +29,18 @@
|
|||||||
let queryField;
|
let queryField;
|
||||||
let activePath = [];
|
let activePath = [];
|
||||||
let objectViewerData;
|
let objectViewerData;
|
||||||
let queryToSave;
|
|
||||||
let showQueryChooser = false;
|
|
||||||
let exportInfo;
|
|
||||||
let querying = false;
|
let querying = false;
|
||||||
let objectViewerSuccessMessage = '';
|
let objectViewerSuccessMessage = '';
|
||||||
|
let viewsForCollection = {};
|
||||||
|
|
||||||
// $: code = `db.${collection.key}.find(${form.query || '{}'}${form.fields && form.fields !== '{}' ? `, ${form.fields}` : ''}).sort(${form.sort})${form.skip ? `.skip(${form.skip})` : ''}${form.limit ? `.limit(${form.limit})` : ''};`;
|
// $: code = `db.${collection.key}.find(${form.query || '{}'}${form.fields && form.fields !== '{}' ? `, ${form.fields}` : ''}).sort(${form.sort})${form.skip ? `.skip(${form.skip})` : ''}${form.limit ? `.limit(${form.limit})` : ''};`;
|
||||||
$: viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
|
||||||
$: lastPage = (submittedForm.limit && result?.results?.length) ? Math.max(0, Math.ceil((result.total - submittedForm.limit) / submittedForm.limit)) : 0;
|
$: lastPage = (submittedForm.limit && result?.results?.length) ? Math.max(0, Math.ceil((result.total - submittedForm.limit) / submittedForm.limit)) : 0;
|
||||||
$: activePage = (submittedForm.limit && submittedForm.skip && result?.results?.length) ? submittedForm.skip / submittedForm.limit : 0;
|
$: activePage = (submittedForm.limit && submittedForm.skip && result?.results?.length) ? submittedForm.skip / submittedForm.limit : 0;
|
||||||
|
|
||||||
|
$: if ($views) {
|
||||||
|
viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
||||||
|
}
|
||||||
|
|
||||||
async function submitQuery() {
|
async function submitQuery() {
|
||||||
if (querying) {
|
if (querying) {
|
||||||
return;
|
return;
|
||||||
@ -70,19 +68,18 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadQuery() {
|
async function loadQuery() {
|
||||||
queryToSave = undefined;
|
const query = await collection.openQueryChooser();
|
||||||
showQueryChooser = true;
|
if (query) {
|
||||||
|
form = { ...query };
|
||||||
|
submitQuery();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveQuery() {
|
async function saveQuery() {
|
||||||
queryToSave = form;
|
const query = await collection.openQueryChooser(form);
|
||||||
showQueryChooser = true;
|
if (query) {
|
||||||
}
|
form = { ...query };
|
||||||
|
|
||||||
function queryChosen(event) {
|
|
||||||
if ($queries[event?.detail]) {
|
|
||||||
form = { ...$queries[event?.detail] };
|
|
||||||
submitQuery();
|
submitQuery();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -130,6 +127,10 @@
|
|||||||
objectViewerData = item;
|
objectViewerData = item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openViewConfig() {
|
||||||
|
views.openConfig(collection, result.results?.[0] || {});
|
||||||
|
}
|
||||||
|
|
||||||
export function performQuery(q) {
|
export function performQuery(q) {
|
||||||
form = { ...defaults, ...q };
|
form = { ...defaults, ...q };
|
||||||
submitQuery();
|
submitQuery();
|
||||||
@ -142,7 +143,7 @@
|
|||||||
collection.dbKey,
|
collection.dbKey,
|
||||||
collection.key,
|
collection.key,
|
||||||
EJSON.stringify({ _id: event.detail.originalData._id }),
|
EJSON.stringify({ _id: event.detail.originalData._id }),
|
||||||
event.detail.text
|
convertLooseJson(event.detail.text)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
@ -207,7 +208,7 @@
|
|||||||
<button type="submit" class="btn" title="Run query">
|
<button type="submit" class="btn" title="Run query">
|
||||||
<Icon name="play" /> Run
|
<Icon name="play" /> Run
|
||||||
</button>
|
</button>
|
||||||
<button class="btn secondary" type="button" on:click={() => exportInfo = {}}>
|
<button class="btn secondary" type="button" on:click={() => collection.export(form)}>
|
||||||
<Icon name="save" /> Export results…
|
<Icon name="save" /> Export results…
|
||||||
</button>
|
</button>
|
||||||
<div class="field">
|
<div class="field">
|
||||||
@ -259,7 +260,7 @@
|
|||||||
<option value={key}>{view.name}</option>
|
<option value={key}>{view.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<button class="btn" on:click={() => dispatch('openViewConfig', { firstItem: result.results?.[0] })} title="Configure view">
|
<button class="btn" on:click={openViewConfig} title="Configure view">
|
||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
@ -283,15 +284,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<QueryChooser
|
|
||||||
bind:queryToSave
|
|
||||||
bind:show={showQueryChooser}
|
|
||||||
on:select={queryChosen}
|
|
||||||
{collection}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<ExportInfo on:openViewConfig bind:collection bind:info={exportInfo} />
|
|
||||||
|
|
||||||
{#if objectViewerData}
|
{#if objectViewerData}
|
||||||
<!-- @todo Implement save -->
|
<!-- @todo Implement save -->
|
||||||
<ObjectViewer bind:data={objectViewerData} saveable on:save={saveDocument} bind:successMessage={objectViewerSuccessMessage} />
|
<ObjectViewer bind:data={objectViewerData} saveable on:save={saveDocument} bind:successMessage={objectViewerSuccessMessage} />
|
||||||
|
@ -3,8 +3,8 @@
|
|||||||
import TabBar from '$components/tabbar.svelte';
|
import TabBar from '$components/tabbar.svelte';
|
||||||
import { EventsOn } from '$wails/runtime/runtime';
|
import { EventsOn } from '$wails/runtime/runtime';
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
|
|
||||||
import Aggregate from './aggregate.svelte';
|
import Aggregate from './aggregate.svelte';
|
||||||
import ViewConfig from './components/viewconfig.svelte';
|
|
||||||
import Find from './find.svelte';
|
import Find from './find.svelte';
|
||||||
import Indexes from './indexes.svelte';
|
import Indexes from './indexes.svelte';
|
||||||
import Insert from './insert.svelte';
|
import Insert from './insert.svelte';
|
||||||
@ -19,8 +19,6 @@
|
|||||||
|
|
||||||
let tab = 'find';
|
let tab = 'find';
|
||||||
let find;
|
let find;
|
||||||
let viewConfigModalOpen = false;
|
|
||||||
let firstItem;
|
|
||||||
|
|
||||||
$: if (collection) {
|
$: if (collection) {
|
||||||
collection.hostKey = hostKey;
|
collection.hostKey = hostKey;
|
||||||
@ -39,11 +37,6 @@
|
|||||||
await tick();
|
await tick();
|
||||||
find.performQuery(event.detail);
|
find.performQuery(event.detail);
|
||||||
}
|
}
|
||||||
|
|
||||||
function openViewConfig(event) {
|
|
||||||
firstItem = event.detail?.firstItem;
|
|
||||||
viewConfigModalOpen = true;
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="view" class:empty={!collection}>
|
<div class="view" class:empty={!collection}>
|
||||||
@ -62,8 +55,8 @@
|
|||||||
|
|
||||||
<div class="container">
|
<div class="container">
|
||||||
{#if tab === 'stats'} <Stats {collection} />
|
{#if tab === 'stats'} <Stats {collection} />
|
||||||
{:else if tab === 'find'} <Find {collection} bind:this={find} on:openViewConfig={openViewConfig} />
|
{:else if tab === 'find'} <Find {collection} bind:this={find} />
|
||||||
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} on:openViewConfig={openViewConfig} />
|
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} />
|
||||||
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
|
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
|
||||||
{:else if tab === 'remove'} <Remove {collection} />
|
{:else if tab === 'remove'} <Remove {collection} />
|
||||||
{:else if tab === 'indexes'} <Indexes {collection} />
|
{:else if tab === 'indexes'} <Indexes {collection} />
|
||||||
@ -76,15 +69,6 @@
|
|||||||
{/if}
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if collection}
|
|
||||||
<ViewConfig
|
|
||||||
bind:show={viewConfigModalOpen}
|
|
||||||
bind:activeViewKey={collection.viewKey}
|
|
||||||
{firstItem}
|
|
||||||
{collection}
|
|
||||||
/>
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.view {
|
.view {
|
||||||
height: 100%;
|
height: 100%;
|
||||||
|
@ -1,65 +1,72 @@
|
|||||||
<script>
|
<script>
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import ObjectGrid from '$components/objectgrid.svelte';
|
import ObjectGrid from '$components/objectgrid.svelte';
|
||||||
import { DropIndex, GetIndexes } from '$wails/go/app/App';
|
import { onMount } from 'svelte';
|
||||||
import IndexDetail from './components/indexdetail.svelte';
|
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
let indexes = [];
|
|
||||||
let activePath = [];
|
let activePath = [];
|
||||||
let creatingNewIndex = false;
|
let _indexes = [];
|
||||||
|
|
||||||
$: collection && getIndexes();
|
async function refresh() {
|
||||||
|
await collection.getIndexes();
|
||||||
|
_indexes = collection.indexes.map(idx => {
|
||||||
|
return {
|
||||||
|
name: idx.name,
|
||||||
|
background: idx.background || false,
|
||||||
|
unique: idx.unique || false,
|
||||||
|
sparse: idx.sparse || false,
|
||||||
|
model: idx.model,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async function getIndexes() {
|
async function createIndex() {
|
||||||
const result = await GetIndexes(collection.hostKey, collection.dbKey, collection.key);
|
const newIndexName = await collection.newIndex();
|
||||||
if (result) {
|
if (newIndexName) {
|
||||||
indexes = result;
|
await refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createIndex() {
|
async function dropIndex(indexName) {
|
||||||
creatingNewIndex = true;
|
if (typeof indexName !== 'string') {
|
||||||
}
|
indexName = activePath[0];
|
||||||
|
|
||||||
async function drop(key) {
|
|
||||||
if (typeof key !== 'string') {
|
|
||||||
key = activePath[0];
|
|
||||||
}
|
}
|
||||||
const success = await DropIndex(collection.hostKey, collection.dbKey, collection.key, key);
|
|
||||||
|
const success = await collection.getIndexByName(indexName).drop();
|
||||||
|
|
||||||
if (success) {
|
if (success) {
|
||||||
await getIndexes();
|
|
||||||
activePath[0] = '';
|
activePath[0] = '';
|
||||||
|
await refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(refresh);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="indexes">
|
<div class="indexes">
|
||||||
<div class="grid">
|
<div class="grid">
|
||||||
<ObjectGrid
|
<ObjectGrid
|
||||||
key="name"
|
key="name"
|
||||||
data={indexes}
|
data={_indexes}
|
||||||
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => drop(idx.name) } ]}
|
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => dropIndex(idx.name) } ]}
|
||||||
bind:activePath
|
bind:activePath
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="actions">
|
<div class="actions">
|
||||||
<button class="btn" on:click={getIndexes}>
|
<button class="btn" on:click={refresh}>
|
||||||
<Icon name="reload" /> Reload
|
<Icon name="reload" /> Reload
|
||||||
</button>
|
</button>
|
||||||
<button class="btn" on:click={createIndex}>
|
<button class="btn" on:click={createIndex}>
|
||||||
<Icon name="+" /> Create index…
|
<Icon name="+" /> Create index…
|
||||||
</button>
|
</button>
|
||||||
<button class="btn danger" on:click={drop} disabled={!indexes?.length || !activePath[0]}>
|
<button class="btn danger" on:click={dropIndex} disabled={!_indexes.length || !activePath[0]}>
|
||||||
<Icon name="x" /> Drop selected
|
<Icon name="x" /> Drop selected
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<IndexDetail bind:creatingNewIndex {collection} on:reload={getIndexes} />
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.indexes {
|
.indexes {
|
||||||
display: grid;
|
display: grid;
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import Details from '$components/details.svelte';
|
import Details from '$components/details.svelte';
|
||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
|
import ObjectEditor from '$components/objecteditor.svelte';
|
||||||
import ObjectViewer from '$components/objectviewer.svelte';
|
import ObjectViewer from '$components/objectviewer.svelte';
|
||||||
import { randomString } from '$lib/math';
|
import { randomString } from '$lib/math';
|
||||||
import { inputTypes } from '$lib/mongo';
|
import { inputTypes } from '$lib/mongo';
|
||||||
@ -11,7 +12,6 @@
|
|||||||
import { EJSON } from 'bson';
|
import { EJSON } from 'bson';
|
||||||
import { createEventDispatcher, onMount } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
import Form from './components/form.svelte';
|
import Form from './components/form.svelte';
|
||||||
import ObjectEditor from '$components/objecteditor.svelte';
|
|
||||||
|
|
||||||
export let collection;
|
export let collection;
|
||||||
|
|
||||||
@ -25,8 +25,8 @@
|
|||||||
let objectViewerData = '';
|
let objectViewerData = '';
|
||||||
let viewType = 'form';
|
let viewType = 'form';
|
||||||
let allValid = false;
|
let allValid = false;
|
||||||
|
let viewsForCollection = {};
|
||||||
|
|
||||||
$: viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
|
||||||
$: oppositeViewType = viewType === 'table' ? 'form' : 'table';
|
$: oppositeViewType = viewType === 'table' ? 'form' : 'table';
|
||||||
$: allValid = Object.values(formValidity).every(v => v !== false);
|
$: allValid = Object.values(formValidity).every(v => v !== false);
|
||||||
|
|
||||||
@ -46,6 +46,10 @@
|
|||||||
newItems = [ {} ];
|
newItems = [ {} ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$: if ($views) {
|
||||||
|
viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
||||||
|
}
|
||||||
|
|
||||||
async function insert() {
|
async function insert() {
|
||||||
insertedIds = await InsertItems(
|
insertedIds = await InsertItems(
|
||||||
collection.hostKey,
|
collection.hostKey,
|
||||||
@ -95,6 +99,10 @@
|
|||||||
newItems = newItems;
|
newItems = newItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function openViewConfig() {
|
||||||
|
views.openConfig(collection);
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (collection.viewKey === 'list') {
|
if (collection.viewKey === 'list') {
|
||||||
editor.dispatch({
|
editor.dispatch({
|
||||||
@ -190,7 +198,7 @@
|
|||||||
<option value={key}>{key === 'list' ? 'Raw JSON' : view.name}</option>
|
<option value={key}>{key === 'list' ? 'Raw JSON' : view.name}</option>
|
||||||
{/each}
|
{/each}
|
||||||
</select>
|
</select>
|
||||||
<button class="btn" type="button" on:click={() => dispatch('openViewConfig')} title="Configure view">
|
<button class="btn" type="button" on:click={openViewConfig} title="Configure view">
|
||||||
<Icon name="cog" />
|
<Icon name="cog" />
|
||||||
</button>
|
</button>
|
||||||
</label>
|
</label>
|
||||||
|
@ -3,12 +3,14 @@
|
|||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import { startProgress } from '$lib/progress';
|
import { startProgress } from '$lib/progress';
|
||||||
import connections from '$lib/stores/connections';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import hosts from '$lib/stores/hosts';
|
|
||||||
import applicationSettings from '$lib/stores/settings';
|
import applicationSettings from '$lib/stores/settings';
|
||||||
import { OpenConnection, OpenDatabase, PerformDump } from '$wails/go/app/App';
|
import { OpenConnection, OpenDatabase } from '$wails/go/app/App';
|
||||||
|
import { createEventDispatcher } from 'svelte';
|
||||||
|
|
||||||
export let info;
|
export let info = {};
|
||||||
|
|
||||||
|
const dispatch = createEventDispatcher();
|
||||||
|
|
||||||
$: if (info) {
|
$: if (info) {
|
||||||
info.outdir = info.outdir || $applicationSettings.defaultExportDirectory;
|
info.outdir = info.outdir || $applicationSettings.defaultExportDirectory;
|
||||||
@ -24,10 +26,10 @@
|
|||||||
const progress = startProgress(`Opening connection to host "${hostKey}"`);
|
const progress = startProgress(`Opening connection to host "${hostKey}"`);
|
||||||
const databases = await OpenConnection(hostKey);
|
const databases = await OpenConnection(hostKey);
|
||||||
|
|
||||||
if (databases && !$connections[hostKey]) {
|
if (databases && !$hostTree[hostKey]) {
|
||||||
$connections[hostKey] = { databases: {} };
|
$hostTree[hostKey] = { databases: {} };
|
||||||
databases.sort().forEach(dbKey => {
|
databases.sort().forEach(dbKey => {
|
||||||
$connections[hostKey].databases[dbKey] = $connections[hostKey].databases[dbKey] || { collections: {} };
|
$hostTree[hostKey].databases[dbKey] = $hostTree[hostKey].databases[dbKey] || { collections: {} };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -44,26 +46,23 @@
|
|||||||
const collections = await OpenDatabase(info.hostKey, dbKey);
|
const collections = await OpenDatabase(info.hostKey, dbKey);
|
||||||
|
|
||||||
for (const collKey of collections?.sort() || []) {
|
for (const collKey of collections?.sort() || []) {
|
||||||
$connections[info.hostKey].databases[dbKey].collections[collKey] = {};
|
$hostTree[info.hostKey].databases[dbKey].collections[collKey] = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
progress.end();
|
progress.end();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function performDump() {
|
|
||||||
const ok = await PerformDump(JSON.stringify(info));
|
|
||||||
if (ok) {
|
|
||||||
info = undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function selectCollection(collKey) {
|
function selectCollection(collKey) {
|
||||||
info.collKeys = [ collKey ];
|
info.collKeys = [ collKey ];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function performDump() {
|
||||||
|
dispatch('dump', { info });
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show={info} title="Perform dump">
|
<Modal title="Perform dump" on:close>
|
||||||
<form on:submit|preventDefault={performDump}>
|
<form on:submit|preventDefault={performDump}>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Output destination:</span>
|
<span class="label">Output destination:</span>
|
||||||
@ -82,8 +81,8 @@
|
|||||||
hideChildrenToggles
|
hideChildrenToggles
|
||||||
items={[
|
items={[
|
||||||
{ id: undefined, name: '(localhost)' },
|
{ id: undefined, name: '(localhost)' },
|
||||||
...Object.keys($hosts).map(id => {
|
...Object.keys($hostTree).map(id => {
|
||||||
return { id, name: $hosts[id]?.name };
|
return { id, name: $hostTree[id]?.name };
|
||||||
}),
|
}),
|
||||||
]}
|
]}
|
||||||
on:select={e => selectHost(e.detail?.itemKey)}
|
on:select={e => selectHost(e.detail?.itemKey)}
|
||||||
@ -98,7 +97,7 @@
|
|||||||
hideChildrenToggles
|
hideChildrenToggles
|
||||||
items={[
|
items={[
|
||||||
{ id: undefined, name: '(all databases)' },
|
{ id: undefined, name: '(all databases)' },
|
||||||
...($connections[info.hostKey]?.databases ? Object.keys($connections[info.hostKey].databases).map(id => {
|
...($hostTree[info.hostKey]?.databases ? Object.keys($hostTree[info.hostKey].databases).map(id => {
|
||||||
return { id, name: id };
|
return { id, name: id };
|
||||||
}) : []
|
}) : []
|
||||||
),
|
),
|
||||||
@ -115,7 +114,7 @@
|
|||||||
hideChildrenToggles
|
hideChildrenToggles
|
||||||
items={[
|
items={[
|
||||||
{ id: undefined, name: '(all collections)' },
|
{ id: undefined, name: '(all collections)' },
|
||||||
...($connections[info.hostKey]?.databases[info.dbKey]?.collections ? Object.keys($connections[info.hostKey].databases[info.dbKey].collections).map(id => {
|
...($hostTree[info.hostKey]?.databases[info.dbKey]?.collections ? Object.keys($hostTree[info.hostKey].databases[info.dbKey].collections).map(id => {
|
||||||
return { id, name: id };
|
return { id, name: id };
|
||||||
}) : []
|
}) : []
|
||||||
),
|
),
|
@ -1,26 +1,17 @@
|
|||||||
<script>
|
<script>
|
||||||
import Modal from '$components/modal.svelte';
|
import Modal from '$components/modal.svelte';
|
||||||
import input from '$lib/actions/input';
|
import input from '$lib/actions/input';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import { AddHost, UpdateHost } from '$wails/go/app/App';
|
import { AddHost, UpdateHost } from '$wails/go/app/App';
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher, onMount } from 'svelte';
|
||||||
|
|
||||||
export let show = false;
|
|
||||||
export let hostKey = '';
|
export let hostKey = '';
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
const dispatch = createEventDispatcher();
|
||||||
let form = {};
|
let form = {};
|
||||||
let error = '';
|
let error = '';
|
||||||
$: valid = validate(form);
|
$: valid = validate(form);
|
||||||
$: host = $hosts[hostKey];
|
$: host = $hostTree[hostKey];
|
||||||
|
|
||||||
$: if (show || !show) {
|
|
||||||
init();
|
|
||||||
}
|
|
||||||
|
|
||||||
function init() {
|
|
||||||
form = { ...(host || {}) };
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate(form) {
|
function validate(form) {
|
||||||
return form.name && form.uri && true;
|
return form.name && form.uri && true;
|
||||||
@ -41,16 +32,20 @@
|
|||||||
hostKey = newHostKey;
|
hostKey = newHostKey;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
show = false;
|
dispatch('updated', form);
|
||||||
dispatch('reload');
|
dispatch('close');
|
||||||
}
|
}
|
||||||
catch (e) {
|
catch (e) {
|
||||||
error = e;
|
error = e;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(() => {
|
||||||
|
form = { ...(host || {}) };
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Modal bind:show title={host ? `Edit ${host.name}` : 'Create a new host'}>
|
<Modal title={host ? `Edit ${host.name}` : 'Create a new host'} on:close>
|
||||||
<form on:submit|preventDefault={submit}>
|
<form on:submit|preventDefault={submit}>
|
||||||
<label class="field">
|
<label class="field">
|
||||||
<span class="label">Label</span>
|
<span class="label">Label</span>
|
@ -1,180 +1,70 @@
|
|||||||
<script>
|
<script>
|
||||||
import Grid from '$components/grid.svelte';
|
import Grid from '$components/grid.svelte';
|
||||||
import { startProgress } from '$lib/progress';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
import connections from '$lib/stores/connections';
|
|
||||||
import { createEventDispatcher, tick } from 'svelte';
|
|
||||||
import { DropCollection, DropDatabase, OpenCollection, OpenConnection, OpenDatabase, RemoveHost, TruncateCollection } from '../../../wailsjs/go/app/App';
|
|
||||||
import hosts from '$lib/stores/hosts';
|
|
||||||
import windowTitle from '$lib/stores/windowtitle';
|
|
||||||
|
|
||||||
export let activeHostKey = '';
|
export let path = [];
|
||||||
export let activeDbKey = '';
|
|
||||||
export let activeCollKey = '';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
let activeGridPath = [];
|
|
||||||
// $: activeGridPath[0] = activeHostKey || undefined;
|
|
||||||
// $: activeGridPath[1] = activeDbKey || undefined;
|
|
||||||
// $: activeGridPath[2] = activeCollKey || undefined;
|
|
||||||
$: connection = $connections[activeHostKey];
|
|
||||||
|
|
||||||
export async function reload() {
|
|
||||||
activeHostKey && await openConnection(activeHostKey);
|
|
||||||
activeDbKey && await openDatabase(activeDbKey);
|
|
||||||
activeCollKey && await openCollection(activeCollKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openConnection(hostKey) {
|
|
||||||
const progress = startProgress(`Connecting to "${hostKey}"…`);
|
|
||||||
|
|
||||||
activeCollKey = '';
|
|
||||||
activeDbKey = '';
|
|
||||||
activeHostKey = hostKey;
|
|
||||||
|
|
||||||
const { databases, status, systemInfo } = await OpenConnection(hostKey);
|
|
||||||
|
|
||||||
if (databases) {
|
|
||||||
$connections[hostKey] = $connections[hostKey] || {};
|
|
||||||
$connections[hostKey].status = status;
|
|
||||||
$connections[hostKey].systemInfo = systemInfo;
|
|
||||||
|
|
||||||
$connections[hostKey].databases = $connections[hostKey].databases || {};
|
|
||||||
databases.forEach(dbKey => {
|
|
||||||
$connections[hostKey].databases[dbKey] =
|
|
||||||
$connections[hostKey].databases[dbKey] || { collections: {} };
|
|
||||||
});
|
|
||||||
|
|
||||||
activeHostKey = hostKey;
|
|
||||||
dispatch('connected', hostKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.end();
|
|
||||||
|
|
||||||
if (databases) {
|
|
||||||
windowTitle.setSegments($hosts[activeHostKey].name, 'Rolens');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function removeHost(hostKey) {
|
|
||||||
activeCollKey = '';
|
|
||||||
activeDbKey = '';
|
|
||||||
activeHostKey = '';
|
|
||||||
|
|
||||||
await tick();
|
|
||||||
await RemoveHost(hostKey);
|
|
||||||
await reload();
|
|
||||||
await hosts.update();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openDatabase(dbKey) {
|
|
||||||
const progress = startProgress(`Opening database "${dbKey}"…`);
|
|
||||||
const { collections, stats } = await OpenDatabase(activeHostKey, dbKey);
|
|
||||||
activeDbKey = dbKey;
|
|
||||||
activeCollKey = '';
|
|
||||||
$connections[activeHostKey].databases[dbKey].stats = stats;
|
|
||||||
|
|
||||||
for (const collKey of collections || []) {
|
|
||||||
$connections[activeHostKey].databases[dbKey].collections[collKey] =
|
|
||||||
$connections[activeHostKey].databases[dbKey].collections[collKey] || {};
|
|
||||||
}
|
|
||||||
|
|
||||||
progress.end();
|
|
||||||
windowTitle.setSegments(activeDbKey, $hosts[activeHostKey].name, 'Rolens');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function dropDatabase(dbKey) {
|
|
||||||
const progress = startProgress(`Dropping database "${dbKey}"…`);
|
|
||||||
const success = await DropDatabase(activeHostKey, dbKey);
|
|
||||||
if (success) {
|
|
||||||
activeCollKey = '';
|
|
||||||
activeDbKey = '';
|
|
||||||
await reload();
|
|
||||||
}
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function openCollection(collKey) {
|
|
||||||
const progress = startProgress(`Opening collection "${collKey}"…`);
|
|
||||||
const stats = await OpenCollection(activeHostKey, activeDbKey, collKey);
|
|
||||||
activeCollKey = collKey;
|
|
||||||
$connections[activeHostKey].databases[activeDbKey].collections[collKey] = $connections[activeHostKey].databases[activeDbKey].collections[collKey] || {};
|
|
||||||
$connections[activeHostKey].databases[activeDbKey].collections[collKey].stats = stats;
|
|
||||||
progress.end();
|
|
||||||
windowTitle.setSegments(activeDbKey + '.' + activeCollKey, $hosts[activeHostKey].name, 'Rolens');
|
|
||||||
}
|
|
||||||
|
|
||||||
async function truncateCollection(dbKey, collKey) {
|
|
||||||
const progress = startProgress(`Truncating collection "${collKey}"…`);
|
|
||||||
await TruncateCollection(activeHostKey, dbKey, collKey);
|
|
||||||
await reload();
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
async function dropCollection(dbKey, collKey) {
|
|
||||||
const progress = startProgress(`Dropping collection "${collKey}"…`);
|
|
||||||
const success = await DropCollection(activeHostKey, dbKey, collKey);
|
|
||||||
if (success) {
|
|
||||||
activeCollKey = '';
|
|
||||||
await reload();
|
|
||||||
}
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
striped={false}
|
striped={false}
|
||||||
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
|
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
|
||||||
bind:activePath={activeGridPath}
|
items={Object.values($hostTree || {}).map(host => {
|
||||||
items={Object.keys($hosts).map(hostKey => {
|
|
||||||
return {
|
return {
|
||||||
id: hostKey,
|
id: host.key,
|
||||||
name: $hosts[hostKey].name,
|
name: host.name,
|
||||||
icon: 'server',
|
icon: 'server',
|
||||||
children: Object.keys(connection?.databases || {}).sort().map(dbKey => {
|
children: Object.values(host.databases || {})
|
||||||
return {
|
.sort((a, b) => a.key.localeCompare(b))
|
||||||
id: dbKey,
|
.map(database => {
|
||||||
name: dbKey,
|
return {
|
||||||
icon: 'db',
|
id: database.key,
|
||||||
count: Object.keys(connection.databases[dbKey].collections || {}).length || '',
|
name: database.key,
|
||||||
children: Object.keys(connection.databases[dbKey].collections).sort().map(collKey => {
|
icon: 'db',
|
||||||
return {
|
count: Object.keys(database.collections || {}).length || '',
|
||||||
id: collKey,
|
children: Object.values(database.collections)
|
||||||
name: collKey,
|
.sort((a, b) => a.key.localeCompare(b))
|
||||||
icon: 'list',
|
.map(collection => {
|
||||||
menu: [
|
return {
|
||||||
{ label: 'Export collection (JSON, CSV)…', fn: () => dispatch('exportCollection', collKey) },
|
id: collection.key,
|
||||||
{ label: 'Dump collection (BSON via mongodump)…', fn: () => dispatch('dumpCollection', collKey) },
|
name: collection.key,
|
||||||
{ separator: true },
|
icon: 'list',
|
||||||
{ label: 'Rename collection…', fn: () => dispatch('renameCollection', collKey) },
|
menu: [
|
||||||
{ label: 'Truncate collection…', fn: () => truncateCollection(dbKey, collKey) },
|
{ label: 'Export collection…', fn: collection.export },
|
||||||
{ label: 'Drop collection…', fn: () => dropCollection(dbKey, collKey) },
|
{ label: 'Dump collection (BSON via mongodump)…', fn: collection.dump },
|
||||||
{ separator: true },
|
{ separator: true },
|
||||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
{ label: 'Rename collection…', fn: collection.rename },
|
||||||
],
|
{ label: 'Truncate collection…', fn: collection.truncate },
|
||||||
};
|
{ label: 'Drop collection…', fn: collection.drop },
|
||||||
}) || [],
|
{ separator: true },
|
||||||
menu: [
|
{ label: 'New collection…', fn: database.newCollection },
|
||||||
{ label: 'Drop database…', fn: () => dropDatabase(dbKey) },
|
],
|
||||||
{ separator: true },
|
};
|
||||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
}) || [],
|
||||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
menu: [
|
||||||
],
|
{ label: 'Dump database (BSON via mongodump)…', fn: database.dump },
|
||||||
};
|
{ label: 'Drop database…', fn: database.drop },
|
||||||
}),
|
{ separator: true },
|
||||||
|
{ label: 'New database…', fn: host.newDatabase },
|
||||||
|
{ label: 'New collection…', fn: database.newCollection },
|
||||||
|
],
|
||||||
|
};
|
||||||
|
}),
|
||||||
menu: [
|
menu: [
|
||||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
{ label: 'New database…', fn: host.newDatabase },
|
||||||
{ separator: true },
|
{ separator: true },
|
||||||
{ label: `Edit host ${$hosts[hostKey].name}…`, fn: () => dispatch('editHost', hostKey) },
|
{ label: `Edit host ${host.name}…`, fn: host.edit },
|
||||||
{ label: 'Remove host…', fn: () => removeHost(hostKey) },
|
{ label: 'Remove host…', fn: host.remove },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
})}
|
})}
|
||||||
on:select={e => {
|
on:select={e => {
|
||||||
const key = e.detail.itemKey;
|
let level;
|
||||||
switch (e.detail?.level) {
|
({ path, level } = e.detail);
|
||||||
case 0: return openConnection(key);
|
|
||||||
case 1: return openDatabase(key);
|
switch (level) {
|
||||||
case 2: return openCollection(key);
|
case 0: return $hostTree[path[0]].open();
|
||||||
|
case 1: return $hostTree[path[0]].databases[path[1]].open();
|
||||||
|
case 2: return $hostTree[path[0]].databases[path[1]].collections[path[2]].open();
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -1,144 +1,53 @@
|
|||||||
<script>
|
<script>
|
||||||
import { startProgress } from '$lib/progress';
|
|
||||||
import connections from '$lib/stores/connections';
|
|
||||||
import { RenameCollection } from '$wails/go/app/App';
|
|
||||||
import { EnterText } from '$wails/go/ui/UI';
|
|
||||||
import { EventsOn } from '$wails/runtime/runtime';
|
|
||||||
import HostView from './host/index.svelte';
|
|
||||||
import DatabaseView from './database/index.svelte';
|
|
||||||
import CollectionView from './collection/index.svelte';
|
|
||||||
import DumpInfo from './dump.svelte';
|
|
||||||
import HostDetail from './hostdetail.svelte';
|
|
||||||
import HostTree from './hosttree.svelte';
|
|
||||||
import sharedState from '$lib/stores/sharedstate';
|
|
||||||
import Icon from '$components/icon.svelte';
|
import Icon from '$components/icon.svelte';
|
||||||
import hosts from '$lib/stores/hosts';
|
import hostTree from '$lib/stores/hosttree';
|
||||||
|
import sharedState from '$lib/stores/sharedstate';
|
||||||
|
import CollectionView from './collection/index.svelte';
|
||||||
|
import DatabaseView from './database/index.svelte';
|
||||||
|
import HostView from './host/index.svelte';
|
||||||
|
import HostTree from './hosttree.svelte';
|
||||||
|
|
||||||
export let activeHostKey = '';
|
let path = [];
|
||||||
export let activeDbKey = '';
|
|
||||||
export let activeCollKey = '';
|
|
||||||
|
|
||||||
let hostTree;
|
$: activeHostKey = path[0];
|
||||||
let showHostDetail = false;
|
$: activeDbKey = path[1];
|
||||||
let hostDetailKey = '';
|
$: activeCollKey = path[2];
|
||||||
let exportInfo;
|
|
||||||
|
|
||||||
$: sharedState.currentHost.set(activeHostKey);
|
$: sharedState.currentHost.set(activeHostKey);
|
||||||
$: sharedState.currentDb.set(activeDbKey);
|
$: sharedState.currentDb.set(activeDbKey);
|
||||||
$: sharedState.currentColl.set(activeCollKey);
|
$: sharedState.currentColl.set(activeCollKey);
|
||||||
|
|
||||||
export function createHost() {
|
|
||||||
hostDetailKey = '';
|
|
||||||
showHostDetail = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function editHost(hostKey) {
|
|
||||||
hostDetailKey = hostKey;
|
|
||||||
showHostDetail = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createDatabase() {
|
|
||||||
const name = await EnterText('Create a database', 'Enter the database name. Note: databases in MongoDB do not exist until they have a collection and an item. Your new database will not persist on the server; fill it to have it created.');
|
|
||||||
if (name) {
|
|
||||||
$connections[activeHostKey].databases[name] = { collections: {} };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function renameCollection(oldCollKey) {
|
|
||||||
const newCollKey = await EnterText('Rename collection', `Enter a new name for collection ${oldCollKey}.`, oldCollKey);
|
|
||||||
if (newCollKey && (newCollKey !== oldCollKey)) {
|
|
||||||
const progress = startProgress(`Renaming collection "${oldCollKey}" to "${newCollKey}"…`);
|
|
||||||
const ok = await RenameCollection(activeHostKey, activeDbKey, oldCollKey, newCollKey);
|
|
||||||
if (ok) {
|
|
||||||
activeCollKey = newCollKey;
|
|
||||||
await hostTree.reload();
|
|
||||||
}
|
|
||||||
progress.end();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function createCollection() {
|
|
||||||
const name = await EnterText('Create a collection', 'Note: collections in MongoDB do not exist until they have at least one item. Your new collection will not persist on the server; fill it to have it created.');
|
|
||||||
if (name) {
|
|
||||||
$connections[activeHostKey].databases[activeDbKey].collections[name] = {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function exportCollection(collKey) {
|
|
||||||
exportInfo = {
|
|
||||||
type: 'export',
|
|
||||||
filetype: 'json',
|
|
||||||
hostKey: activeHostKey,
|
|
||||||
dbKey: activeDbKey,
|
|
||||||
collKeys: [ collKey ],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function dumpCollection(collKey) {
|
|
||||||
exportInfo = {
|
|
||||||
type: 'dump',
|
|
||||||
filetype: 'bson',
|
|
||||||
hostKey: activeHostKey,
|
|
||||||
dbKey: activeDbKey,
|
|
||||||
collKeys: [ collKey ],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
EventsOn('CreateHost', createHost);
|
|
||||||
EventsOn('CreateDatabase', createDatabase);
|
|
||||||
EventsOn('CreateCollection', createCollection);
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="tree">
|
<div class="tree">
|
||||||
<div class="tree-buttons">
|
<div class="tree-buttons">
|
||||||
<button class="button-small" on:click={createHost}>
|
<button class="button-small" on:click={hostTree.newHost}>
|
||||||
<Icon name="+" /> New host
|
<Icon name="+" /> New host
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<HostTree
|
<HostTree bind:path />
|
||||||
bind:activeHostKey
|
|
||||||
bind:activeCollKey
|
|
||||||
bind:activeDbKey
|
|
||||||
bind:this={hostTree}
|
|
||||||
on:newHost={createHost}
|
|
||||||
on:newDatabase={createDatabase}
|
|
||||||
on:newCollection={createCollection}
|
|
||||||
on:editHost={e => editHost(e.detail)}
|
|
||||||
on:renameCollection={e => renameCollection(e.detail)}
|
|
||||||
on:exportCollection={e => exportCollection(e.detail)}
|
|
||||||
on:dumpCollection={e => dumpCollection(e.detail)}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{#if activeCollKey}
|
{#if activeCollKey}
|
||||||
<CollectionView
|
<CollectionView
|
||||||
collection={$connections[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
||||||
hostKey={activeHostKey}
|
hostKey={activeHostKey}
|
||||||
dbKey={activeDbKey}
|
dbKey={activeDbKey}
|
||||||
collKey={activeCollKey}
|
collKey={activeCollKey}
|
||||||
/>
|
/>
|
||||||
{:else if activeDbKey}
|
{:else if activeDbKey}
|
||||||
<DatabaseView
|
<DatabaseView
|
||||||
database={$connections[activeHostKey]?.databases[activeDbKey]}
|
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
|
||||||
hostKey={activeHostKey}
|
hostKey={activeHostKey}
|
||||||
dbKey={activeDbKey}
|
dbKey={activeDbKey}
|
||||||
/>
|
/>
|
||||||
{:else if activeHostKey}
|
{:else if activeHostKey}
|
||||||
<HostView
|
<HostView
|
||||||
host={$connections[activeHostKey]}
|
host={$hostTree[activeHostKey]}
|
||||||
hostKey={activeHostKey}
|
hostKey={activeHostKey}
|
||||||
/>
|
/>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<HostDetail
|
|
||||||
bind:show={showHostDetail}
|
|
||||||
on:reload={hosts.update}
|
|
||||||
hostKey={hostDetailKey}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<DumpInfo bind:info={exportInfo} />
|
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
.tree {
|
.tree {
|
||||||
padding: 0.5rem;
|
padding: 0.5rem;
|
||||||
|
@ -1,60 +0,0 @@
|
|||||||
<script>
|
|
||||||
import { createEventDispatcher } from 'svelte';
|
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
function createHost() {
|
|
||||||
dispatch('createHost');
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="welcome">
|
|
||||||
<div class="brand">
|
|
||||||
<img src="/logo.png" alt="" class="logo" />
|
|
||||||
<div class="text">
|
|
||||||
<div class="name">Welcome to Rolens!</div>
|
|
||||||
<div class="subtitle">A modest MongoDB client</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="btn" on:click={createHost}>Create your first host</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.welcome {
|
|
||||||
/* transform: translateY(-80px); */
|
|
||||||
margin-top: -90px;
|
|
||||||
padding: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.brand {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.brand .logo {
|
|
||||||
height: 200px;
|
|
||||||
}
|
|
||||||
.brand .text {
|
|
||||||
align-self: flex-end;
|
|
||||||
margin: 0 0 4rem 1rem;
|
|
||||||
}
|
|
||||||
.brand .text .name {
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin-bottom: 1.5rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.brand .text .subtitle {
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
height: 250px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* .title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
} */
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
margin-top: 2rem;
|
|
||||||
}
|
|
||||||
</style>
|
|
@ -36,8 +36,6 @@ func (a *App) Menu() *menu.Menu {
|
|||||||
|
|
||||||
fileMenu := appMenu.AddSubmenu("File")
|
fileMenu := appMenu.AddSubmenu("File")
|
||||||
fileMenu.AddText("New host…", keys.CmdOrCtrl("y"), menuCallbackEmit(a, "CreateHost"))
|
fileMenu.AddText("New host…", keys.CmdOrCtrl("y"), menuCallbackEmit(a, "CreateHost"))
|
||||||
fileMenu.AddText("New database", keys.CmdOrCtrl("y"), menuCallbackEmit(a, "CreateDatabase"))
|
|
||||||
fileMenu.AddText("New collection…", keys.CmdOrCtrl("i"), menuCallbackEmit(a, "CreateCollection"))
|
|
||||||
fileMenu.AddSeparator()
|
fileMenu.AddSeparator()
|
||||||
fileMenu.AddText("Stats", keys.Combo("h", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "stats"))
|
fileMenu.AddText("Stats", keys.Combo("h", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "stats"))
|
||||||
fileMenu.AddText("Find", keys.Combo("f", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "find"))
|
fileMenu.AddText("Find", keys.Combo("f", keys.CmdOrCtrlKey, keys.OptionOrAltKey), menuCallbackEmit(a, "OpenCollectionTab", "find"))
|
||||||
|
@ -134,9 +134,9 @@ func (a *App) UpdateFoundDocument(hostKey, dbKey, collKey, idJson, newDocJson st
|
|||||||
}
|
}
|
||||||
defer close()
|
defer close()
|
||||||
|
|
||||||
if _, err := client.Database(dbKey).Collection(collKey).UpdateOne(ctx, id, bson.M{"$set": newDoc}); err != nil {
|
if _, err := client.Database(dbKey).Collection(collKey).ReplaceOne(ctx, id, newDoc); err != nil {
|
||||||
runtime.LogInfof(a.ctx, "Error while performing find/update: %s", err.Error())
|
runtime.LogInfof(a.ctx, "Error while replacing document: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Unable to perform update"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Unable to replace document"), zenity.ErrorIcon)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -246,7 +246,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
csvItem = append(csvItem, string(v.(string)))
|
csvItem = append(csvItem, fmt.Sprintf("%v", v))
|
||||||
}
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
|
@ -21,13 +21,14 @@ type HostInfo struct {
|
|||||||
func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, func(), error) {
|
func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, func(), error) {
|
||||||
hosts, err := a.Hosts()
|
hosts, err := a.Hosts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
runtime.LogInfof(a.ctx, "Error while getting hosts: %s", err.Error())
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while getting hosts"), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Error while getting hosts"), zenity.ErrorIcon)
|
||||||
return nil, nil, nil, errors.New("could not retrieve hosts")
|
return nil, nil, nil, errors.New("could not retrieve hosts")
|
||||||
}
|
}
|
||||||
|
|
||||||
h := hosts[hostKey]
|
h := hosts[hostKey]
|
||||||
if len(h.URI) == 0 {
|
if len(h.URI) == 0 {
|
||||||
runtime.LogInfo(a.ctx, "Invalid URI (len == 0) for host "+hostKey)
|
runtime.LogInfof(a.ctx, "Invalid URI (len == 0) for host %s", hostKey)
|
||||||
zenity.Warning("You haven't specified a valid uri for the selected host.", zenity.Title("Invalid query"), zenity.WarningIcon)
|
zenity.Warning("You haven't specified a valid uri for the selected host.", zenity.Title("Invalid query"), zenity.WarningIcon)
|
||||||
return nil, nil, nil, errors.New("invalid uri")
|
return nil, nil, nil, errors.New("invalid uri")
|
||||||
}
|
}
|
||||||
@ -35,8 +36,7 @@ func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, fun
|
|||||||
client, err := mongo.NewClient(mongoOptions.Client().ApplyURI(h.URI))
|
client, err := mongo.NewClient(mongoOptions.Client().ApplyURI(h.URI))
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.LogWarning(a.ctx, "Could not connect to host "+hostKey)
|
runtime.LogWarningf(a.ctx, "Could not connect to host %s: %s", hostKey, err.Error())
|
||||||
runtime.LogWarning(a.ctx, err.Error())
|
|
||||||
zenity.Error(err.Error(), zenity.Title("Error while connecting to "+h.Name), zenity.ErrorIcon)
|
zenity.Error(err.Error(), zenity.Title("Error while connecting to "+h.Name), zenity.ErrorIcon)
|
||||||
return nil, nil, nil, errors.New("could not establish a connection with " + h.Name)
|
return nil, nil, nil, errors.New("could not establish a connection with " + h.Name)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user