mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-07-20 14:38:04 +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:
@ -1,72 +0,0 @@
|
||||
<script>
|
||||
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=" ">
|
||||
<div class="brand">
|
||||
<img src="/logo.png" alt="Rolens logo" />
|
||||
<div>
|
||||
<div class="title">
|
||||
Rolens
|
||||
<span class="version">{$environment.version}</span>
|
||||
</div>
|
||||
<div class="description">Intuitive MongoDB <br /> administration tool</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
|
||||
<div class="info">
|
||||
<p class="copy">© Romein van Buren, 2023.</p>
|
||||
<p>
|
||||
<a href="https://garraflavatra.github.io/rolens/" use:alink>Documentation</a> |
|
||||
<a href="https://github.com/garraflavatra/rolens" use:alink>GitHub</a> |
|
||||
<a href="https://github.com/garraflavatra/rolens/issues/new" use:alink>Report a bug</a> |
|
||||
<a href="https://github.com/garraflavatra/rolens/blob/main/LICENSE" use:alink>License</a>
|
||||
</p>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-around;
|
||||
}
|
||||
.brand > img {
|
||||
width: 125px;
|
||||
flex: 0 1 125px;
|
||||
}
|
||||
.brand .title {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 600;
|
||||
line-height: 2.5rem;
|
||||
}
|
||||
.brand .title .version {
|
||||
font-size: 80%;
|
||||
font-weight: 300;
|
||||
opacity: 0.65;
|
||||
}
|
||||
.brand .description {
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.6rem;
|
||||
}
|
||||
|
||||
hr {
|
||||
margin: 2rem 1rem;
|
||||
}
|
||||
|
||||
.info {
|
||||
font-size: 1.25rem;
|
||||
line-height: 1.25rem;
|
||||
margin: 0 1rem 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
.info .copy {
|
||||
font-weight: 500;
|
||||
}
|
||||
</style>
|
@ -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))
|
||||
.sort((a, b) => sortTabKeys(a[0], b[0]))
|
||||
.map(([ key, v ]) => {
|
||||
return { key, title: v.name, closable: key !== 'list' };
|
||||
});
|
||||
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 drop(key) {
|
||||
if (typeof key !== 'string') {
|
||||
key = activePath[0];
|
||||
async function dropIndex(indexName) {
|
||||
if (typeof indexName !== 'string') {
|
||||
indexName = 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 => {
|
||||
return {
|
||||
id: dbKey,
|
||||
name: dbKey,
|
||||
icon: 'db',
|
||||
count: Object.keys(connection.databases[dbKey].collections || {}).length || '',
|
||||
children: Object.keys(connection.databases[dbKey].collections).sort().map(collKey => {
|
||||
return {
|
||||
id: collKey,
|
||||
name: collKey,
|
||||
icon: 'list',
|
||||
menu: [
|
||||
{ label: 'Export collection (JSON, CSV)…', fn: () => dispatch('exportCollection', collKey) },
|
||||
{ label: 'Dump collection (BSON via mongodump)…', fn: () => dispatch('dumpCollection', collKey) },
|
||||
{ separator: true },
|
||||
{ label: 'Rename collection…', fn: () => dispatch('renameCollection', collKey) },
|
||||
{ label: 'Truncate collection…', fn: () => truncateCollection(dbKey, collKey) },
|
||||
{ label: 'Drop collection…', fn: () => dropCollection(dbKey, collKey) },
|
||||
{ separator: true },
|
||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
||||
],
|
||||
};
|
||||
}) || [],
|
||||
menu: [
|
||||
{ label: 'Drop database…', fn: () => dropDatabase(dbKey) },
|
||||
{ separator: true },
|
||||
{ label: 'New database…', fn: () => dispatch('newDatabase') },
|
||||
{ label: 'New collection…', fn: () => dispatch('newCollection') },
|
||||
],
|
||||
};
|
||||
}),
|
||||
children: Object.values(host.databases || {})
|
||||
.sort((a, b) => a.key.localeCompare(b))
|
||||
.map(database => {
|
||||
return {
|
||||
id: database.key,
|
||||
name: database.key,
|
||||
icon: 'db',
|
||||
count: Object.keys(database.collections || {}).length || '',
|
||||
children: Object.values(database.collections)
|
||||
.sort((a, b) => a.key.localeCompare(b))
|
||||
.map(collection => {
|
||||
return {
|
||||
id: collection.key,
|
||||
name: collection.key,
|
||||
icon: 'list',
|
||||
menu: [
|
||||
{ label: 'Export collection…', fn: collection.export },
|
||||
{ label: 'Dump collection (BSON via mongodump)…', fn: collection.dump },
|
||||
{ separator: true },
|
||||
{ label: 'Rename collection…', fn: collection.rename },
|
||||
{ label: 'Truncate collection…', fn: collection.truncate },
|
||||
{ label: 'Drop collection…', fn: collection.drop },
|
||||
{ separator: true },
|
||||
{ label: 'New collection…', fn: database.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: [
|
||||
{ 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>
|
@ -1,44 +0,0 @@
|
||||
<script>
|
||||
import DirectoryChooser from '$components/directorychooser.svelte';
|
||||
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>
|
||||
<div class="prefs">
|
||||
<label for="defaultLimit">Initial number of items to retrieve using one query (limit):</label>
|
||||
<label class="field">
|
||||
<input type="number" bind:value={$settings.defaultLimit} id="defaultLimit" use:input={{ autofocus: true }} />
|
||||
<span class="label">items</span>
|
||||
</label>
|
||||
|
||||
<label for="defaultSort">Default sort query</label>
|
||||
<label class="field">
|
||||
<input type="text" class="code" bind:value={$settings.defaultSort} id="defaultSort" use:input={{ type: 'json' }} />
|
||||
</label>
|
||||
|
||||
<label for="autosubmitQuery">Autosubmit query</label>
|
||||
<span>
|
||||
<input type="checkbox" id="autosubmitQuery" bind:checked={$settings.autosubmitQuery} />
|
||||
<label for="autosubmitQuery">Query items automatically after opening a collection</label>
|
||||
</span>
|
||||
|
||||
<label for="defaultExportDirectory">Default export directory</label>
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="field">
|
||||
<DirectoryChooser id="defaultExportDirectory" bind:value={$settings.defaultExportDirectory} />
|
||||
</label>
|
||||
</div>
|
||||
</Modal>
|
||||
|
||||
<style>
|
||||
.prefs {
|
||||
display: grid;
|
||||
grid-template-columns: auto auto;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user