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 id="dialogoutlets"></div>
|
||||
<script src="./src/main.js" type="module"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
@ -1 +1 @@
|
||||
499da1237327bf4ddd2de74bfd6635c7
|
||||
5d3dd16f94d140a9f950a48981534162
|
@ -1,39 +1,44 @@
|
||||
<script>
|
||||
import BlankState from '$components/blankstate.svelte';
|
||||
import ContextMenu from '$components/contextmenu.svelte';
|
||||
import dialogs from '$lib/dialogs';
|
||||
import contextMenu from '$lib/stores/contextmenu';
|
||||
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 windowTitle from '$lib/stores/windowtitle';
|
||||
import About from '$organisms/about.svelte';
|
||||
import Connection from '$organisms/connection/index.svelte';
|
||||
import Settings from '$organisms/settings/index.svelte';
|
||||
import { EventsOn } from '$wails/runtime';
|
||||
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;
|
||||
|
||||
hosts.subscribe(h => {
|
||||
if (h && (showWelcomeScreen === undefined)) {
|
||||
showWelcomeScreen = !Object.keys($hosts || {}).length;
|
||||
applicationInited.defer(() => {
|
||||
hostTree.subscribe(hosts => {
|
||||
if (hostTree.hasBeenInited() && (showWelcomeScreen === undefined)) {
|
||||
showWelcomeScreen = !Object.keys(hosts || {}).length;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async function createFirstHost() {
|
||||
showWelcomeScreen = false;
|
||||
await tick();
|
||||
connectionManager.createHost();
|
||||
hostTree.newHost();
|
||||
}
|
||||
|
||||
EventsOn('OpenPreferences', () => settingsModalOpen = true);
|
||||
EventsOn('OpenAboutModal', () => aboutModalOpen = true);
|
||||
function showAboutDialog() {
|
||||
dialogs.new(AboutDialog);
|
||||
}
|
||||
|
||||
function showSettings() {
|
||||
dialogs.new(SettingsDialog);
|
||||
}
|
||||
|
||||
EventsOn('OpenPreferences', showSettings);
|
||||
EventsOn('OpenAboutModal', showAboutDialog);
|
||||
</script>
|
||||
|
||||
<svelte:window on:contextmenu|preventDefault />
|
||||
@ -41,23 +46,20 @@
|
||||
<div id="root" class="platform-{$environment?.platform}">
|
||||
<div class="titlebar">{$windowTitle}</div>
|
||||
|
||||
{#if $applicationInited && $hosts && (showWelcomeScreen !== undefined)}
|
||||
{#if $applicationInited && (showWelcomeScreen !== undefined)}
|
||||
<main class:empty={showWelcomeScreen}>
|
||||
{#if showWelcomeScreen}
|
||||
<BlankState label="Welcome to Rolens!" image="/logo.png" pale={false} big={true}>
|
||||
<button class="btn" on:click={createFirstHost}>Add your first host</button>
|
||||
</BlankState>
|
||||
{:else}
|
||||
<Connection {activeHostKey} bind:activeCollKey bind:activeDbKey bind:this={connectionManager} />
|
||||
<Connection />
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
{#key $contextMenu}
|
||||
<ContextMenu {...$contextMenu} on:close={contextMenu.hide} />
|
||||
{/key}
|
||||
|
||||
<Settings bind:show={settingsModalOpen} />
|
||||
<About bind:show={aboutModalOpen} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
|
@ -66,7 +66,7 @@
|
||||
|
||||
activeKey = itemKey;
|
||||
activePath = [ ...path.slice(0, level), itemKey ];
|
||||
dispatch('select', { level, itemKey, index });
|
||||
dispatch('select', { level, itemKey, index, path: activePath });
|
||||
}
|
||||
|
||||
function closeAll() {
|
||||
|
@ -4,15 +4,17 @@
|
||||
|
||||
<script>
|
||||
import { Beep } from '$wails/go/ui/UI';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { fade, fly } from 'svelte/transition';
|
||||
import Icon from './icon.svelte';
|
||||
|
||||
export let show = false;
|
||||
export let show = true;
|
||||
export let title = undefined;
|
||||
export let contentPadding = true;
|
||||
export let width = '80vw';
|
||||
export let overflow = true;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const level = numberOfModalsOpen + 1;
|
||||
let isNew = true;
|
||||
|
||||
@ -29,9 +31,13 @@
|
||||
function keydown(event) {
|
||||
if ((event.key === 'Escape') && (level === numberOfModalsOpen)) {
|
||||
event.preventDefault();
|
||||
show = false;
|
||||
close();
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
dispatch('close');
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:keydown={keydown} />
|
||||
@ -42,7 +48,7 @@
|
||||
{#if title}
|
||||
<header>
|
||||
<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" />
|
||||
</button>
|
||||
</header>
|
||||
@ -69,6 +75,7 @@
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
margin: 0;
|
||||
padding-top: 50px;
|
||||
--wails-draggable: drag;
|
||||
}
|
||||
:global(#root.platform-darwin) .outer {
|
||||
margin-top: var(--darwin-titlebar-height);
|
||||
@ -86,6 +93,7 @@
|
||||
flex-flow: column;
|
||||
cursor: auto;
|
||||
overflow: hidden;
|
||||
--wails-draggable: nodrag;
|
||||
}
|
||||
.inner > :global(*:first-child) {
|
||||
margin-top: 0;
|
||||
|
@ -1,10 +1,10 @@
|
||||
<script>
|
||||
import { looseJsonIsValid } from '$lib/strings';
|
||||
import { EJSON } from 'bson';
|
||||
import { createEventDispatcher, onDestroy } from 'svelte';
|
||||
import Icon from './icon.svelte';
|
||||
import Modal from './modal.svelte';
|
||||
import ObjectEditor from './objecteditor.svelte';
|
||||
import { EJSON } from 'bson';
|
||||
|
||||
export let data;
|
||||
export let saveable = false;
|
||||
|
@ -2,11 +2,9 @@
|
||||
import Modal from '$components/modal.svelte';
|
||||
import alink from '$lib/actions/alink';
|
||||
import environment from '$lib/stores/environment';
|
||||
|
||||
export let show = true;
|
||||
</script>
|
||||
|
||||
<Modal bind:show width="400px" title=" ">
|
||||
<Modal width="400px" title=" " on:close>
|
||||
<div class="brand">
|
||||
<img src="/logo.png" alt="Rolens logo" />
|
||||
<div>
|
@ -3,11 +3,9 @@
|
||||
import Modal from '$components/modal.svelte';
|
||||
import input from '$lib/actions/input';
|
||||
import settings from '$lib/stores/settings';
|
||||
|
||||
export let show = false;
|
||||
</script>
|
||||
|
||||
<Modal title="Preferences" bind:show>
|
||||
<Modal title="Preferences" on:close>
|
||||
<div class="prefs">
|
||||
<label for="defaultLimit">Initial number of items to retrieve using one query (limit):</label>
|
||||
<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) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (env && settings) {
|
||||
else if (env && settings) {
|
||||
Promise.all(listeners.map(l => l())).then(() => {
|
||||
set(true);
|
||||
alreadyInited = true;
|
||||
|
||||
// Remove loading spinner.
|
||||
document.getElementById('app-loading')?.remove();
|
||||
|
||||
// Call hooks
|
||||
listeners.forEach(l => l());
|
||||
});
|
||||
}
|
||||
}, 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 { get, writable } from 'svelte/store';
|
||||
|
||||
@ -23,6 +25,11 @@ function forCollection(hostKey, dbKey, collKey) {
|
||||
return Object.fromEntries(entries);
|
||||
}
|
||||
|
||||
function openConfig(collection, firstItem = {}) {
|
||||
const dialog = dialogs.new(ViewConfigDialog, { collection, firstItem });
|
||||
return dialog;
|
||||
}
|
||||
|
||||
reload();
|
||||
subscribe(newViewStore => {
|
||||
if (skipUpdate) {
|
||||
@ -32,5 +39,5 @@ subscribe(newViewStore => {
|
||||
UpdateViewStore(JSON.stringify(newViewStore));
|
||||
});
|
||||
|
||||
const views = { reload, set, subscribe, forCollection };
|
||||
const views = { reload, set, subscribe, forCollection, openConfig };
|
||||
export default views;
|
||||
|
@ -2,9 +2,9 @@
|
||||
import Details from '$components/details.svelte';
|
||||
import Icon from '$components/icon.svelte';
|
||||
import Modal from '$components/modal.svelte';
|
||||
import Collation from '$lib/mongo/collation.svelte';
|
||||
import ObjectEditor from '$components/objecteditor.svelte';
|
||||
import { aggregationStageDocumentationURL, aggregationStages } from '$lib/mongo';
|
||||
import Collation from '$lib/mongo/collation.svelte';
|
||||
import { jsonLooseParse, looseJsonIsValid } from '$lib/strings';
|
||||
import { Aggregate } from '$wails/go/app/App';
|
||||
import { BrowserOpenURL } from '$wails/runtime/runtime';
|
||||
|
@ -1,9 +1,9 @@
|
||||
<script>
|
||||
import FormInput from '$components/forminput.svelte';
|
||||
import Hint from '$components/hint.svelte';
|
||||
import Icon from '$components/icon.svelte';
|
||||
import { inputTypes } from '$lib/mongo';
|
||||
import { resolveKeypath, setValue } from '$lib/objects';
|
||||
import Hint from '$components/hint.svelte';
|
||||
|
||||
export let item = {};
|
||||
export let view = {};
|
||||
|
@ -2,43 +2,34 @@
|
||||
import Icon from '$components/icon.svelte';
|
||||
import Modal from '$components/modal.svelte';
|
||||
import views from '$lib/stores/views';
|
||||
import { PerformFindExport } from '$wails/go/app/App';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let info;
|
||||
export let collection;
|
||||
export let query = undefined;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let viewKey = collection.viewKey;
|
||||
$: viewKey = collection.viewKey;
|
||||
$: if (info) {
|
||||
info.viewKey = viewKey;
|
||||
}
|
||||
const exportInfo = { ...query, viewKey: collection.viewKey };
|
||||
|
||||
async function performExport() {
|
||||
info.view = $views[viewKey];
|
||||
const success = await PerformFindExport(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(info));
|
||||
|
||||
if (success) {
|
||||
info = undefined;
|
||||
}
|
||||
function submit() {
|
||||
exportInfo.view = $views[exportInfo.viewKey];
|
||||
dispatch('export', { exportInfo });
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show={info} title="Export results" width="450px">
|
||||
<form on:submit|preventDefault={performExport}>
|
||||
<Modal title="Export results" width="450px" on:close>
|
||||
<form on:submit|preventDefault={submit}>
|
||||
<label class="field">
|
||||
<span class="label">Export</span>
|
||||
<select bind:value={info.contents}>
|
||||
<select bind:value={exportInfo.contents}>
|
||||
<option value="all">all records</option>
|
||||
<option value="query">all records matching query</option>
|
||||
<option value="querylimitskip">all records matching query, considering limit and skip</option>
|
||||
<option value="query" disabled={!query}>all records matching query</option>
|
||||
<option value="querylimitskip" disabled={!query}>all records matching query, considering limit and skip</option>
|
||||
</select>
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="label">Format</span>
|
||||
<select bind:value={info.format}>
|
||||
<select bind:value={exportInfo.format}>
|
||||
<option value="jsonarray">JSON array</option>
|
||||
<option value="ndjson">Newline delimited JSON</option>
|
||||
<option value="csv">CSV</option>
|
||||
@ -47,7 +38,7 @@
|
||||
|
||||
<label class="field">
|
||||
<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 } ]}
|
||||
<option value={key}>{name}</option>
|
||||
{/each}
|
||||
@ -59,7 +50,7 @@
|
||||
</form>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<button class="btn" on:click={performExport}>
|
||||
<button class="btn" on:click={submit}>
|
||||
<Icon name="play" /> Start export
|
||||
</button>
|
||||
</svelte:fragment>
|
@ -2,14 +2,12 @@
|
||||
import Icon from '$components/icon.svelte';
|
||||
import Modal from '$components/modal.svelte';
|
||||
import input from '$lib/actions/input';
|
||||
import { CreateIndex } from '$wails/go/app/App';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let collection = {};
|
||||
export let creatingNewIndex = false;
|
||||
export let collection;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let index = { model: [] };
|
||||
const index = { model: [] };
|
||||
|
||||
function addRule() {
|
||||
index.model = [ ...index.model, {} ];
|
||||
@ -21,16 +19,11 @@
|
||||
}
|
||||
|
||||
async function create() {
|
||||
const newIndexName = await CreateIndex(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(index));
|
||||
if (newIndexName) {
|
||||
creatingNewIndex = false;
|
||||
index = { model: [] };
|
||||
dispatch('reload');
|
||||
}
|
||||
dispatch('create', { index });
|
||||
}
|
||||
</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}>
|
||||
<label class="field name">
|
||||
<span class="label">Name</span>
|
@ -4,13 +4,12 @@
|
||||
import Icon from '$components/icon.svelte';
|
||||
import Modal from '$components/modal.svelte';
|
||||
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 { createEventDispatcher } from 'svelte';
|
||||
|
||||
export let queryToSave = undefined;
|
||||
export let collection = {};
|
||||
export let show = false;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let gridSelectedPath = [];
|
||||
@ -22,23 +21,16 @@
|
||||
queryToSave.dbKey = collection.dbKey;
|
||||
queryToSave.collKey = collection.key;
|
||||
|
||||
const newId = queries.create(queryToSave);
|
||||
|
||||
if (newId) {
|
||||
dispatch('created', newId);
|
||||
queryToSave = undefined;
|
||||
selectedKey = newId;
|
||||
select();
|
||||
}
|
||||
dispatch('create', { query: queryToSave });
|
||||
selectedKey = queryToSave.name;
|
||||
}
|
||||
else {
|
||||
select();
|
||||
selectActive();
|
||||
}
|
||||
}
|
||||
|
||||
function select() {
|
||||
dispatch('select', selectedKey);
|
||||
show = false;
|
||||
function selectActive() {
|
||||
dispatch('select', { query: $queries[selectedKey] });
|
||||
}
|
||||
|
||||
function gridSelect(event) {
|
||||
@ -53,7 +45,7 @@
|
||||
|
||||
function gridTrigger(event) {
|
||||
gridSelect(event);
|
||||
select();
|
||||
selectActive();
|
||||
}
|
||||
|
||||
async function gridRemove(event) {
|
||||
@ -71,7 +63,7 @@
|
||||
}
|
||||
</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}>
|
||||
{#if queryToSave}
|
||||
<label class="field queryname">
|
||||
@ -88,7 +80,11 @@
|
||||
columns={[ { key: 'n', title: 'Query name' }, { key: 'h', title: 'Host' }, { key: 'ns', title: 'Namespace' } ]}
|
||||
key="n"
|
||||
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;
|
||||
}, {})}
|
||||
showHeaders={true}
|
@ -7,15 +7,18 @@
|
||||
import views from '$lib/stores/views';
|
||||
|
||||
export let collection;
|
||||
export let show = false;
|
||||
export let activeViewKey = 'list';
|
||||
export let firstItem = {};
|
||||
|
||||
$: tabs = Object.entries(views.forCollection(collection.hostKey, collection.dbKey, collection.key))
|
||||
let tabs = [];
|
||||
$: $views && refresh();
|
||||
|
||||
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) {
|
||||
if (a === 'list') {
|
||||
@ -39,29 +42,29 @@
|
||||
type: 'table',
|
||||
columns: [ { key: '_id', showInTable: true, inputType: 'objectid', mandatory: true } ],
|
||||
};
|
||||
activeViewKey = newViewKey;
|
||||
collection.viewKey = newViewKey;
|
||||
}
|
||||
|
||||
function removeView(viewKey) {
|
||||
const keys = Object.keys($views).sort(sortTabKeys);
|
||||
const oldIndex = keys.indexOf(viewKey);
|
||||
const newKey = keys[oldIndex - 1];
|
||||
activeViewKey = newKey;
|
||||
collection.viewKey = newKey;
|
||||
delete $views[viewKey];
|
||||
$views = $views;
|
||||
}
|
||||
|
||||
function addColumn(before) {
|
||||
if (typeof before === 'number') {
|
||||
$views[activeViewKey].columns = [
|
||||
...$views[activeViewKey].columns.slice(0, before),
|
||||
$views[collection.viewKey].columns = [
|
||||
...$views[collection.viewKey].columns.slice(0, before),
|
||||
{ showInTable: true, inputType: 'none' },
|
||||
...$views[activeViewKey].columns.slice(before),
|
||||
...$views[collection.viewKey].columns.slice(before),
|
||||
];
|
||||
}
|
||||
else {
|
||||
$views[activeViewKey].columns = [
|
||||
...$views[activeViewKey].columns,
|
||||
$views[collection.viewKey].columns = [
|
||||
...$views[collection.viewKey].columns,
|
||||
{ showInTable: true, inputType: 'none' },
|
||||
];
|
||||
}
|
||||
@ -72,7 +75,7 @@
|
||||
return;
|
||||
}
|
||||
|
||||
$views[activeViewKey].columns = Object.keys(firstItem).sort().map(key => {
|
||||
$views[collection.viewKey].columns = Object.keys(firstItem).sort().map(key => {
|
||||
return {
|
||||
key,
|
||||
showInTable: true,
|
||||
@ -82,57 +85,57 @@
|
||||
}
|
||||
|
||||
function moveColumn(oldIndex, delta) {
|
||||
const column = $views[activeViewKey].columns[oldIndex];
|
||||
const column = $views[collection.viewKey].columns[oldIndex];
|
||||
const newIndex = oldIndex + delta;
|
||||
|
||||
$views[activeViewKey].columns.splice(oldIndex, 1);
|
||||
$views[activeViewKey].columns.splice(newIndex, 0, column);
|
||||
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
||||
$views[collection.viewKey].columns.splice(oldIndex, 1);
|
||||
$views[collection.viewKey].columns.splice(newIndex, 0, column);
|
||||
$views[collection.viewKey].columns = $views[collection.viewKey].columns;
|
||||
}
|
||||
|
||||
function removeColumn(index) {
|
||||
$views[activeViewKey].columns.splice(index, 1);
|
||||
$views[activeViewKey].columns = $views[activeViewKey].columns;
|
||||
$views[collection.viewKey].columns.splice(index, 1);
|
||||
$views[collection.viewKey].columns = $views[collection.viewKey].columns;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="View configuration" bind:show contentPadding={false}>
|
||||
<Modal title="View configuration" contentPadding={false} on:close>
|
||||
<TabBar
|
||||
{tabs}
|
||||
canAddTab={true}
|
||||
on:addTab={createView}
|
||||
on:closeTab={e => removeView(e.detail)}
|
||||
bind:selectedKey={activeViewKey}
|
||||
bind:selectedKey={collection.viewKey}
|
||||
/>
|
||||
|
||||
<div class="options">
|
||||
{#if $views[activeViewKey]}
|
||||
{#if $views[collection.viewKey]}
|
||||
<div class="meta">
|
||||
{#key activeViewKey}
|
||||
{#key collection.viewKey}
|
||||
<label class="field">
|
||||
<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>
|
||||
{/key}
|
||||
<label class="field">
|
||||
<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="table">Table view</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
{#if $views[activeViewKey].type === 'list'}
|
||||
{#if $views[collection.viewKey].type === 'list'}
|
||||
<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">
|
||||
Hide object indicators ({'{...}'} and [...]) in list view and show nothing instead
|
||||
</label>
|
||||
</div>
|
||||
{:else if $views[activeViewKey].type === 'table'}
|
||||
{:else if $views[collection.viewKey].type === 'table'}
|
||||
<div class="columns">
|
||||
{#each $views[activeViewKey].columns as column, columnIndex}
|
||||
{#each $views[collection.viewKey].columns as column, columnIndex}
|
||||
<div class="column">
|
||||
<label class="field">
|
||||
<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">
|
||||
<Icon name="chev-u" />
|
||||
</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" />
|
||||
</button>
|
||||
<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 { deepClone } from '$lib/objects';
|
||||
import { startProgress } from '$lib/progress';
|
||||
import queries from '$lib/stores/queries';
|
||||
import applicationSettings from '$lib/stores/settings';
|
||||
import views from '$lib/stores/views';
|
||||
import { convertLooseJson } from '$lib/strings';
|
||||
import { FindItems, RemoveItemById, UpdateFoundDocument } from '$wails/go/app/App';
|
||||
import { EJSON } from 'bson';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import ExportInfo from './components/export.svelte';
|
||||
import QueryChooser from './components/querychooser.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let collection;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
const defaults = {
|
||||
query: '{}',
|
||||
sort: $applicationSettings.defaultSort || '{ "_id": 1 }',
|
||||
@ -32,17 +29,18 @@
|
||||
let queryField;
|
||||
let activePath = [];
|
||||
let objectViewerData;
|
||||
let queryToSave;
|
||||
let showQueryChooser = false;
|
||||
let exportInfo;
|
||||
let querying = false;
|
||||
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})` : ''};`;
|
||||
$: 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;
|
||||
$: 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() {
|
||||
if (querying) {
|
||||
return;
|
||||
@ -70,19 +68,18 @@
|
||||
}
|
||||
}
|
||||
|
||||
function loadQuery() {
|
||||
queryToSave = undefined;
|
||||
showQueryChooser = true;
|
||||
async function loadQuery() {
|
||||
const query = await collection.openQueryChooser();
|
||||
if (query) {
|
||||
form = { ...query };
|
||||
submitQuery();
|
||||
}
|
||||
}
|
||||
|
||||
function saveQuery() {
|
||||
queryToSave = form;
|
||||
showQueryChooser = true;
|
||||
}
|
||||
|
||||
function queryChosen(event) {
|
||||
if ($queries[event?.detail]) {
|
||||
form = { ...$queries[event?.detail] };
|
||||
async function saveQuery() {
|
||||
const query = await collection.openQueryChooser(form);
|
||||
if (query) {
|
||||
form = { ...query };
|
||||
submitQuery();
|
||||
}
|
||||
}
|
||||
@ -130,6 +127,10 @@
|
||||
objectViewerData = item;
|
||||
}
|
||||
|
||||
function openViewConfig() {
|
||||
views.openConfig(collection, result.results?.[0] || {});
|
||||
}
|
||||
|
||||
export function performQuery(q) {
|
||||
form = { ...defaults, ...q };
|
||||
submitQuery();
|
||||
@ -142,7 +143,7 @@
|
||||
collection.dbKey,
|
||||
collection.key,
|
||||
EJSON.stringify({ _id: event.detail.originalData._id }),
|
||||
event.detail.text
|
||||
convertLooseJson(event.detail.text)
|
||||
);
|
||||
|
||||
if (success) {
|
||||
@ -207,7 +208,7 @@
|
||||
<button type="submit" class="btn" title="Run query">
|
||||
<Icon name="play" /> Run
|
||||
</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…
|
||||
</button>
|
||||
<div class="field">
|
||||
@ -259,7 +260,7 @@
|
||||
<option value={key}>{view.name}</option>
|
||||
{/each}
|
||||
</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" />
|
||||
</button>
|
||||
</label>
|
||||
@ -283,15 +284,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<QueryChooser
|
||||
bind:queryToSave
|
||||
bind:show={showQueryChooser}
|
||||
on:select={queryChosen}
|
||||
{collection}
|
||||
/>
|
||||
|
||||
<ExportInfo on:openViewConfig bind:collection bind:info={exportInfo} />
|
||||
|
||||
{#if objectViewerData}
|
||||
<!-- @todo Implement save -->
|
||||
<ObjectViewer bind:data={objectViewerData} saveable on:save={saveDocument} bind:successMessage={objectViewerSuccessMessage} />
|
||||
|
@ -3,8 +3,8 @@
|
||||
import TabBar from '$components/tabbar.svelte';
|
||||
import { EventsOn } from '$wails/runtime/runtime';
|
||||
import { tick } from 'svelte';
|
||||
|
||||
import Aggregate from './aggregate.svelte';
|
||||
import ViewConfig from './components/viewconfig.svelte';
|
||||
import Find from './find.svelte';
|
||||
import Indexes from './indexes.svelte';
|
||||
import Insert from './insert.svelte';
|
||||
@ -19,8 +19,6 @@
|
||||
|
||||
let tab = 'find';
|
||||
let find;
|
||||
let viewConfigModalOpen = false;
|
||||
let firstItem;
|
||||
|
||||
$: if (collection) {
|
||||
collection.hostKey = hostKey;
|
||||
@ -39,11 +37,6 @@
|
||||
await tick();
|
||||
find.performQuery(event.detail);
|
||||
}
|
||||
|
||||
function openViewConfig(event) {
|
||||
firstItem = event.detail?.firstItem;
|
||||
viewConfigModalOpen = true;
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="view" class:empty={!collection}>
|
||||
@ -62,8 +55,8 @@
|
||||
|
||||
<div class="container">
|
||||
{#if tab === 'stats'} <Stats {collection} />
|
||||
{:else if tab === 'find'} <Find {collection} bind:this={find} on:openViewConfig={openViewConfig} />
|
||||
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} on:openViewConfig={openViewConfig} />
|
||||
{:else if tab === 'find'} <Find {collection} bind:this={find} />
|
||||
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} />
|
||||
{:else if tab === 'update'} <Update {collection} on:performFind={catchQuery} />
|
||||
{:else if tab === 'remove'} <Remove {collection} />
|
||||
{:else if tab === 'indexes'} <Indexes {collection} />
|
||||
@ -76,15 +69,6 @@
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
{#if collection}
|
||||
<ViewConfig
|
||||
bind:show={viewConfigModalOpen}
|
||||
bind:activeViewKey={collection.viewKey}
|
||||
{firstItem}
|
||||
{collection}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.view {
|
||||
height: 100%;
|
||||
|
@ -1,65 +1,72 @@
|
||||
<script>
|
||||
import Icon from '$components/icon.svelte';
|
||||
import ObjectGrid from '$components/objectgrid.svelte';
|
||||
import { DropIndex, GetIndexes } from '$wails/go/app/App';
|
||||
import IndexDetail from './components/indexdetail.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
export let collection;
|
||||
|
||||
let indexes = [];
|
||||
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() {
|
||||
const result = await GetIndexes(collection.hostKey, collection.dbKey, collection.key);
|
||||
if (result) {
|
||||
indexes = result;
|
||||
async function createIndex() {
|
||||
const newIndexName = await collection.newIndex();
|
||||
if (newIndexName) {
|
||||
await refresh();
|
||||
}
|
||||
}
|
||||
|
||||
function createIndex() {
|
||||
creatingNewIndex = true;
|
||||
async function dropIndex(indexName) {
|
||||
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) {
|
||||
await getIndexes();
|
||||
activePath[0] = '';
|
||||
await refresh();
|
||||
}
|
||||
}
|
||||
|
||||
onMount(refresh);
|
||||
</script>
|
||||
|
||||
<div class="indexes">
|
||||
<div class="grid">
|
||||
<ObjectGrid
|
||||
key="name"
|
||||
data={indexes}
|
||||
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => drop(idx.name) } ]}
|
||||
data={_indexes}
|
||||
getRootMenu={(_, idx) => [ { label: 'Drop this index', fn: () => dropIndex(idx.name) } ]}
|
||||
bind:activePath
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
<button class="btn" on:click={getIndexes}>
|
||||
<button class="btn" on:click={refresh}>
|
||||
<Icon name="reload" /> Reload
|
||||
</button>
|
||||
<button class="btn" on:click={createIndex}>
|
||||
<Icon name="+" /> Create index…
|
||||
</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
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<IndexDetail bind:creatingNewIndex {collection} on:reload={getIndexes} />
|
||||
|
||||
<style>
|
||||
.indexes {
|
||||
display: grid;
|
||||
|
@ -2,6 +2,7 @@
|
||||
import Details from '$components/details.svelte';
|
||||
import Grid from '$components/grid.svelte';
|
||||
import Icon from '$components/icon.svelte';
|
||||
import ObjectEditor from '$components/objecteditor.svelte';
|
||||
import ObjectViewer from '$components/objectviewer.svelte';
|
||||
import { randomString } from '$lib/math';
|
||||
import { inputTypes } from '$lib/mongo';
|
||||
@ -11,7 +12,6 @@
|
||||
import { EJSON } from 'bson';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
import Form from './components/form.svelte';
|
||||
import ObjectEditor from '$components/objecteditor.svelte';
|
||||
|
||||
export let collection;
|
||||
|
||||
@ -25,8 +25,8 @@
|
||||
let objectViewerData = '';
|
||||
let viewType = 'form';
|
||||
let allValid = false;
|
||||
let viewsForCollection = {};
|
||||
|
||||
$: viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
||||
$: oppositeViewType = viewType === 'table' ? 'form' : 'table';
|
||||
$: allValid = Object.values(formValidity).every(v => v !== false);
|
||||
|
||||
@ -46,6 +46,10 @@
|
||||
newItems = [ {} ];
|
||||
}
|
||||
|
||||
$: if ($views) {
|
||||
viewsForCollection = views.forCollection(collection.hostKey, collection.dbKey, collection.key);
|
||||
}
|
||||
|
||||
async function insert() {
|
||||
insertedIds = await InsertItems(
|
||||
collection.hostKey,
|
||||
@ -95,6 +99,10 @@
|
||||
newItems = newItems;
|
||||
}
|
||||
|
||||
function openViewConfig() {
|
||||
views.openConfig(collection);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
if (collection.viewKey === 'list') {
|
||||
editor.dispatch({
|
||||
@ -190,7 +198,7 @@
|
||||
<option value={key}>{key === 'list' ? 'Raw JSON' : view.name}</option>
|
||||
{/each}
|
||||
</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" />
|
||||
</button>
|
||||
</label>
|
||||
|
@ -3,12 +3,14 @@
|
||||
import Grid from '$components/grid.svelte';
|
||||
import Modal from '$components/modal.svelte';
|
||||
import { startProgress } from '$lib/progress';
|
||||
import connections from '$lib/stores/connections';
|
||||
import hosts from '$lib/stores/hosts';
|
||||
import hostTree from '$lib/stores/hosttree';
|
||||
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) {
|
||||
info.outdir = info.outdir || $applicationSettings.defaultExportDirectory;
|
||||
@ -24,10 +26,10 @@
|
||||
const progress = startProgress(`Opening connection to host "${hostKey}"`);
|
||||
const databases = await OpenConnection(hostKey);
|
||||
|
||||
if (databases && !$connections[hostKey]) {
|
||||
$connections[hostKey] = { databases: {} };
|
||||
if (databases && !$hostTree[hostKey]) {
|
||||
$hostTree[hostKey] = { databases: {} };
|
||||
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);
|
||||
|
||||
for (const collKey of collections?.sort() || []) {
|
||||
$connections[info.hostKey].databases[dbKey].collections[collKey] = {};
|
||||
$hostTree[info.hostKey].databases[dbKey].collections[collKey] = {};
|
||||
}
|
||||
|
||||
progress.end();
|
||||
}
|
||||
}
|
||||
|
||||
async function performDump() {
|
||||
const ok = await PerformDump(JSON.stringify(info));
|
||||
if (ok) {
|
||||
info = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
function selectCollection(collKey) {
|
||||
info.collKeys = [ collKey ];
|
||||
}
|
||||
|
||||
function performDump() {
|
||||
dispatch('dump', { info });
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal bind:show={info} title="Perform dump">
|
||||
<Modal title="Perform dump" on:close>
|
||||
<form on:submit|preventDefault={performDump}>
|
||||
<label class="field">
|
||||
<span class="label">Output destination:</span>
|
||||
@ -82,8 +81,8 @@
|
||||
hideChildrenToggles
|
||||
items={[
|
||||
{ id: undefined, name: '(localhost)' },
|
||||
...Object.keys($hosts).map(id => {
|
||||
return { id, name: $hosts[id]?.name };
|
||||
...Object.keys($hostTree).map(id => {
|
||||
return { id, name: $hostTree[id]?.name };
|
||||
}),
|
||||
]}
|
||||
on:select={e => selectHost(e.detail?.itemKey)}
|
||||
@ -98,7 +97,7 @@
|
||||
hideChildrenToggles
|
||||
items={[
|
||||
{ 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 };
|
||||
}) : []
|
||||
),
|
||||
@ -115,7 +114,7 @@
|
||||
hideChildrenToggles
|
||||
items={[
|
||||
{ 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 };
|
||||
}) : []
|
||||
),
|
@ -1,26 +1,17 @@
|
||||
<script>
|
||||
import Modal from '$components/modal.svelte';
|
||||
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 { createEventDispatcher } from 'svelte';
|
||||
import { createEventDispatcher, onMount } from 'svelte';
|
||||
|
||||
export let show = false;
|
||||
export let hostKey = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let form = {};
|
||||
let error = '';
|
||||
$: valid = validate(form);
|
||||
$: host = $hosts[hostKey];
|
||||
|
||||
$: if (show || !show) {
|
||||
init();
|
||||
}
|
||||
|
||||
function init() {
|
||||
form = { ...(host || {}) };
|
||||
}
|
||||
$: host = $hostTree[hostKey];
|
||||
|
||||
function validate(form) {
|
||||
return form.name && form.uri && true;
|
||||
@ -41,16 +32,20 @@
|
||||
hostKey = newHostKey;
|
||||
}
|
||||
}
|
||||
show = false;
|
||||
dispatch('reload');
|
||||
dispatch('updated', form);
|
||||
dispatch('close');
|
||||
}
|
||||
catch (e) {
|
||||
error = e;
|
||||
}
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
form = { ...(host || {}) };
|
||||
});
|
||||
</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}>
|
||||
<label class="field">
|
||||
<span class="label">Label</span>
|
@ -1,180 +1,70 @@
|
||||
<script>
|
||||
import Grid from '$components/grid.svelte';
|
||||
import { startProgress } from '$lib/progress';
|
||||
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';
|
||||
import hostTree from '$lib/stores/hosttree';
|
||||
|
||||
export let activeHostKey = '';
|
||||
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();
|
||||
}
|
||||
export let path = [];
|
||||
</script>
|
||||
|
||||
<Grid
|
||||
striped={false}
|
||||
columns={[ { key: 'name' }, { key: 'count', right: true } ]}
|
||||
bind:activePath={activeGridPath}
|
||||
items={Object.keys($hosts).map(hostKey => {
|
||||
items={Object.values($hostTree || {}).map(host => {
|
||||
return {
|
||||
id: hostKey,
|
||||
name: $hosts[hostKey].name,
|
||||
id: host.key,
|
||||
name: host.name,
|
||||
icon: 'server',
|
||||
children: Object.keys(connection?.databases || {}).sort().map(dbKey => {
|
||||
children: Object.values(host.databases || {})
|
||||
.sort((a, b) => a.key.localeCompare(b))
|
||||
.map(database => {
|
||||
return {
|
||||
id: dbKey,
|
||||
name: dbKey,
|
||||
id: database.key,
|
||||
name: database.key,
|
||||
icon: 'db',
|
||||
count: Object.keys(connection.databases[dbKey].collections || {}).length || '',
|
||||
children: Object.keys(connection.databases[dbKey].collections).sort().map(collKey => {
|
||||
count: Object.keys(database.collections || {}).length || '',
|
||||
children: Object.values(database.collections)
|
||||
.sort((a, b) => a.key.localeCompare(b))
|
||||
.map(collection => {
|
||||
return {
|
||||
id: collKey,
|
||||
name: collKey,
|
||||
id: collection.key,
|
||||
name: collection.key,
|
||||
icon: 'list',
|
||||
menu: [
|
||||
{ label: 'Export collection (JSON, CSV)…', fn: () => dispatch('exportCollection', collKey) },
|
||||
{ label: 'Dump collection (BSON via mongodump)…', fn: () => dispatch('dumpCollection', collKey) },
|
||||
{ label: 'Export collection…', fn: collection.export },
|
||||
{ label: 'Dump collection (BSON via mongodump)…', fn: collection.dump },
|
||||
{ separator: true },
|
||||
{ label: 'Rename collection…', fn: () => dispatch('renameCollection', collKey) },
|
||||
{ label: 'Truncate collection…', fn: () => truncateCollection(dbKey, collKey) },
|
||||
{ label: 'Drop collection…', fn: () => dropCollection(dbKey, collKey) },
|
||||
{ label: 'Rename collection…', fn: collection.rename },
|
||||
{ label: 'Truncate collection…', fn: collection.truncate },
|
||||
{ label: 'Drop collection…', fn: collection.drop },
|
||||
{ separator: true },
|
||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
||||
{ label: 'New collection…', fn: database.newCollection },
|
||||
],
|
||||
};
|
||||
}) || [],
|
||||
menu: [
|
||||
{ label: 'Drop database…', fn: () => dropDatabase(dbKey) },
|
||||
{ label: 'Dump database (BSON via mongodump)…', fn: database.dump },
|
||||
{ label: 'Drop database…', fn: database.drop },
|
||||
{ separator: true },
|
||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
||||
{ label: 'New database…', fn: host.newDatabase },
|
||||
{ label: 'New collection…', fn: database.newCollection },
|
||||
],
|
||||
};
|
||||
}),
|
||||
menu: [
|
||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
||||
{ label: 'New database…', fn: host.newDatabase },
|
||||
{ separator: true },
|
||||
{ label: `Edit host ${$hosts[hostKey].name}…`, fn: () => dispatch('editHost', hostKey) },
|
||||
{ label: 'Remove host…', fn: () => removeHost(hostKey) },
|
||||
{ label: `Edit host ${host.name}…`, fn: host.edit },
|
||||
{ label: 'Remove host…', fn: host.remove },
|
||||
],
|
||||
};
|
||||
})}
|
||||
on:select={e => {
|
||||
const key = e.detail.itemKey;
|
||||
switch (e.detail?.level) {
|
||||
case 0: return openConnection(key);
|
||||
case 1: return openDatabase(key);
|
||||
case 2: return openCollection(key);
|
||||
let level;
|
||||
({ path, level } = e.detail);
|
||||
|
||||
switch (level) {
|
||||
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>
|
||||
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 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 = '';
|
||||
export let activeDbKey = '';
|
||||
export let activeCollKey = '';
|
||||
let path = [];
|
||||
|
||||
let hostTree;
|
||||
let showHostDetail = false;
|
||||
let hostDetailKey = '';
|
||||
let exportInfo;
|
||||
$: activeHostKey = path[0];
|
||||
$: activeDbKey = path[1];
|
||||
$: activeCollKey = path[2];
|
||||
|
||||
$: sharedState.currentHost.set(activeHostKey);
|
||||
$: sharedState.currentDb.set(activeDbKey);
|
||||
$: 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>
|
||||
|
||||
<div class="tree">
|
||||
<div class="tree-buttons">
|
||||
<button class="button-small" on:click={createHost}>
|
||||
<button class="button-small" on:click={hostTree.newHost}>
|
||||
<Icon name="+" /> New host
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<HostTree
|
||||
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)}
|
||||
/>
|
||||
<HostTree bind:path />
|
||||
</div>
|
||||
|
||||
{#if activeCollKey}
|
||||
<CollectionView
|
||||
collection={$connections[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
||||
collection={$hostTree[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
||||
hostKey={activeHostKey}
|
||||
dbKey={activeDbKey}
|
||||
collKey={activeCollKey}
|
||||
/>
|
||||
{:else if activeDbKey}
|
||||
<DatabaseView
|
||||
database={$connections[activeHostKey]?.databases[activeDbKey]}
|
||||
database={$hostTree[activeHostKey]?.databases[activeDbKey]}
|
||||
hostKey={activeHostKey}
|
||||
dbKey={activeDbKey}
|
||||
/>
|
||||
{:else if activeHostKey}
|
||||
<HostView
|
||||
host={$connections[activeHostKey]}
|
||||
host={$hostTree[activeHostKey]}
|
||||
hostKey={activeHostKey}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<HostDetail
|
||||
bind:show={showHostDetail}
|
||||
on:reload={hosts.update}
|
||||
hostKey={hostDetailKey}
|
||||
/>
|
||||
|
||||
<DumpInfo bind:info={exportInfo} />
|
||||
|
||||
<style>
|
||||
.tree {
|
||||
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.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.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"))
|
||||
|
@ -134,9 +134,9 @@ func (a *App) UpdateFoundDocument(hostKey, dbKey, collKey, idJson, newDocJson st
|
||||
}
|
||||
defer close()
|
||||
|
||||
if _, err := client.Database(dbKey).Collection(collKey).UpdateOne(ctx, id, bson.M{"$set": newDoc}); err != nil {
|
||||
runtime.LogInfof(a.ctx, "Error while performing find/update: %s", err.Error())
|
||||
zenity.Error(err.Error(), zenity.Title("Unable to perform update"), zenity.ErrorIcon)
|
||||
if _, err := client.Database(dbKey).Collection(collKey).ReplaceOne(ctx, id, newDoc); err != nil {
|
||||
runtime.LogInfof(a.ctx, "Error while replacing document: %s", err.Error())
|
||||
zenity.Error(err.Error(), zenity.Title("Unable to replace document"), zenity.ErrorIcon)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -246,7 +246,7 @@ func (a *App) PerformFindExport(hostKey, dbKey, collKey, settingsJson string) bo
|
||||
continue
|
||||
}
|
||||
|
||||
csvItem = append(csvItem, string(v.(string)))
|
||||
csvItem = append(csvItem, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
default:
|
||||
|
@ -21,13 +21,14 @@ type HostInfo struct {
|
||||
func (a *App) connectToHost(hostKey string) (*mongo.Client, context.Context, func(), error) {
|
||||
hosts, err := a.Hosts()
|
||||
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)
|
||||
return nil, nil, nil, errors.New("could not retrieve hosts")
|
||||
}
|
||||
|
||||
h := hosts[hostKey]
|
||||
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)
|
||||
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))
|
||||
|
||||
if err != nil {
|
||||
runtime.LogWarning(a.ctx, "Could not connect to host "+hostKey)
|
||||
runtime.LogWarning(a.ctx, err.Error())
|
||||
runtime.LogWarningf(a.ctx, "Could not connect to host %s: %s", hostKey, err.Error())
|
||||
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)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user