mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-07-20 14:38:04 +00:00
Split app.svelte
This commit is contained in:
173
frontend/src/organisms/connection/collection/find.svelte
Normal file
173
frontend/src/organisms/connection/collection/find.svelte
Normal file
@ -0,0 +1,173 @@
|
||||
<script>
|
||||
import { PerformFind } from '../../../../wailsjs/go/app/App';
|
||||
import CodeExample from '../../../components/code-example.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
import { input } from '../../../actions';
|
||||
import ObjectGrid from '../../../components/objectgrid.svelte';
|
||||
import Icon from '../../../components/icon.svelte';
|
||||
import CodeViewer from '../../../components/codeviewer.svelte';
|
||||
|
||||
export let collection;
|
||||
|
||||
const defaults = {
|
||||
query: '{}',
|
||||
sort: '{ "_id": 1 }',
|
||||
fields: '{}',
|
||||
skip: 0,
|
||||
limit: 15,
|
||||
};
|
||||
|
||||
let form = { ...defaults };
|
||||
let result = {};
|
||||
let submittedForm = {};
|
||||
let queryField;
|
||||
let activeKey = '';
|
||||
let json = '';
|
||||
$: 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})` : ''};`;
|
||||
|
||||
async function submitQuery() {
|
||||
activeKey = '';
|
||||
result = await PerformFind(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
||||
if (result) {
|
||||
submittedForm = JSON.parse(JSON.stringify(form));
|
||||
}
|
||||
resetFocus();
|
||||
}
|
||||
|
||||
function prev() {
|
||||
form.skip -= form.limit;
|
||||
if (form.skip < 0) {
|
||||
form.skip = 0;
|
||||
}
|
||||
submitQuery();
|
||||
}
|
||||
|
||||
function next() {
|
||||
form.skip += form.limit;
|
||||
submitQuery();
|
||||
}
|
||||
|
||||
function remove() {
|
||||
// eslint-disable-next-line no-alert
|
||||
alert('yet to be implemented');
|
||||
}
|
||||
|
||||
function resetFocus() {
|
||||
queryField?.focus();
|
||||
queryField?.select();
|
||||
}
|
||||
|
||||
function openJson(itemId) {
|
||||
const item = result?.results?.filter(i => i._id == itemId);
|
||||
console.log(item);
|
||||
json = JSON.stringify(item, undefined, 2);
|
||||
}
|
||||
|
||||
export function performQuery(q) {
|
||||
form = { ...defaults, ...q };
|
||||
console.log(form);
|
||||
submitQuery();
|
||||
}
|
||||
|
||||
onMount(resetFocus);
|
||||
</script>
|
||||
|
||||
<div class="find">
|
||||
<form on:submit|preventDefault={submitQuery}>
|
||||
<div class="form-row one">
|
||||
<label class="field">
|
||||
<span class="label">Query or id</span>
|
||||
<input type="text" class="code" bind:this={queryField} bind:value={form.query} use:input={{ json: true }} placeholder={defaults.query} />
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="label">Sort</span>
|
||||
<input type="text" class="code" bind:value={form.sort} use:input={{ json: true }} placeholder={defaults.sort} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="form-row two">
|
||||
<label class="field">
|
||||
<span class="label">Fields</span>
|
||||
<input type="text" class="code" bind:value={form.fields} use:input={{ json: true }} placeholder={defaults.fields} />
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="label">Skip</span>
|
||||
<input type="number" min="0" bind:value={form.skip} use:input placeholder={defaults.skip} />
|
||||
</label>
|
||||
|
||||
<label class="field">
|
||||
<span class="label">Limit</span>
|
||||
<input type="number" min="0" bind:value={form.limit} use:input placeholder={defaults.limit} />
|
||||
</label>
|
||||
|
||||
<button type="submit" class="btn">Run</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<CodeExample {code} />
|
||||
|
||||
<div class="result">
|
||||
<div class="grid">
|
||||
{#key result}
|
||||
<ObjectGrid data={result.results} bind:activeKey on:trigger={e => openJson(e.detail)} />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div>
|
||||
{#key result}
|
||||
<span class="flash-green">Results: {result.total || 0}</span>
|
||||
{/key}
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn danger" on:click={remove} disabled={!activeKey}>
|
||||
<Icon name="-" />
|
||||
</button>
|
||||
<button class="btn" on:click={prev} disabled={!submittedForm.limit || (submittedForm.skip <= 0) || !result?.results?.length}>
|
||||
<Icon name="chev-l" />
|
||||
</button>
|
||||
<button class="btn" on:click={next} disabled={!submittedForm.limit || ((result?.results?.length || 0) < submittedForm.limit) || !result?.results?.length}>
|
||||
<Icon name="chev-r" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CodeViewer bind:code={json} language="json" />
|
||||
|
||||
<style>
|
||||
.find {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template: auto auto 1fr / 1fr;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.form-row.one {
|
||||
grid-template: 1fr / 3fr 2fr;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.form-row.two {
|
||||
grid-template: 1fr / 5fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.result {
|
||||
display: grid;
|
||||
grid-template: 1fr auto / 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.result > .grid {
|
||||
overflow: auto;
|
||||
}
|
||||
.result > .controls {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
77
frontend/src/organisms/connection/collection/index.svelte
Normal file
77
frontend/src/organisms/connection/collection/index.svelte
Normal file
@ -0,0 +1,77 @@
|
||||
<script>
|
||||
import BlankState from '../../../components/blankstate.svelte';
|
||||
import { tick } from 'svelte';
|
||||
import TabBar from '../../../components/tabbar.svelte';
|
||||
import Find from './find.svelte';
|
||||
import Indexes from './indexes.svelte';
|
||||
import Insert from './insert.svelte';
|
||||
import Remove from './remove.svelte';
|
||||
import Stats from './stats.svelte';
|
||||
|
||||
export let collection;
|
||||
export let hostKey;
|
||||
export let dbKey;
|
||||
export let collectionKey;
|
||||
|
||||
let tab = 'find';
|
||||
let find;
|
||||
|
||||
$: if (collection) {
|
||||
collection.hostKey = hostKey;
|
||||
collection.dbKey = dbKey;
|
||||
collection.key = collectionKey;
|
||||
}
|
||||
|
||||
async function catchQuery(event) {
|
||||
tab = 'find';
|
||||
await tick();
|
||||
find.performQuery(event.detail);
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="collection" class:empty={!collection}>
|
||||
{#if collection}
|
||||
{#key collection}
|
||||
<TabBar tabs={[
|
||||
{ key: 'stats', title: 'Stats' },
|
||||
{ key: 'find', title: 'Find' },
|
||||
{ key: 'insert', title: 'Insert' },
|
||||
{ key: 'update', title: 'Update' },
|
||||
{ key: 'remove', title: 'Remove' },
|
||||
{ key: 'indexes', title: 'Indexes' },
|
||||
]} bind:selectedKey={tab} />
|
||||
|
||||
<div class="container">
|
||||
{#if tab === 'stats'} <Stats {collection} />
|
||||
{:else if tab === 'find'} <Find {collection} bind:this={find} />
|
||||
{:else if tab === 'insert'} <Insert {collection} on:performFind={catchQuery} />
|
||||
{:else if tab === 'remove'} <Remove {collection} />
|
||||
{:else if tab === 'indexes'} <Indexes {collection} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{:else}
|
||||
<BlankState label="Select a collection to continue" />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.collection {
|
||||
height: 100%;
|
||||
display: grid;
|
||||
grid-template: auto 1fr / 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.collection.empty {
|
||||
grid-template: 1fr / 1fr;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
}
|
||||
.container > :global(*) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
13
frontend/src/organisms/connection/collection/indexes.svelte
Normal file
13
frontend/src/organisms/connection/collection/indexes.svelte
Normal file
@ -0,0 +1,13 @@
|
||||
<script>
|
||||
export let collection;
|
||||
|
||||
const indexes = [];
|
||||
|
||||
function getIndexes() {}
|
||||
</script>
|
||||
|
||||
<div class="buttons">
|
||||
<button class="btn" on:click={getIndexes}>Get indexes</button>
|
||||
<button class="btn danger" disabled={!indexes?.length}>Drop selected</button>
|
||||
<button class="btn">Create…</button>
|
||||
</div>
|
63
frontend/src/organisms/connection/collection/insert.svelte
Normal file
63
frontend/src/organisms/connection/collection/insert.svelte
Normal file
@ -0,0 +1,63 @@
|
||||
<script>
|
||||
import { input } from '../../../actions';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { PerformInsert } from '../../../../wailsjs/go/app/App';
|
||||
|
||||
export let collection;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
let json = '';
|
||||
let insertedIds;
|
||||
|
||||
async function insert() {
|
||||
insertedIds = await PerformInsert(collection.hostKey, collection.dbKey, collection.key, json);
|
||||
}
|
||||
|
||||
function showDocs() {
|
||||
dispatch('performFind', {
|
||||
query: insertedIds.length === 1
|
||||
? `{ "_id": ${JSON.stringify(insertedIds[0])} }`
|
||||
: `{ "_id": { "$in": [ ${insertedIds.map(id => JSON.stringify(id)).join(', ')} ] } }`,
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={insert}>
|
||||
<label class="field">
|
||||
<textarea
|
||||
cols="30"
|
||||
rows="10"
|
||||
placeholder="[]"
|
||||
class="code"
|
||||
bind:value={json}
|
||||
use:input={{ json: true }}
|
||||
></textarea>
|
||||
</label>
|
||||
|
||||
<div class="flex">
|
||||
<div>
|
||||
{#if insertedIds}
|
||||
<span class="flash-green">Success! {insertedIds.length} document{insertedIds.length > 1 ? 's' : ''} inserted</span>
|
||||
{/if}
|
||||
</div>
|
||||
<div>
|
||||
{#if insertedIds}
|
||||
<button class="btn" type="button" on:click={showDocs}>View inserted docs</button>
|
||||
{/if}
|
||||
<button type="submit" class="btn" disabled={!json}>Insert</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr auto;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
36
frontend/src/organisms/connection/collection/remove.svelte
Normal file
36
frontend/src/organisms/connection/collection/remove.svelte
Normal file
@ -0,0 +1,36 @@
|
||||
<script>
|
||||
import CodeExample from '../../../components/code-example.svelte';
|
||||
|
||||
export let collection;
|
||||
|
||||
let remove = '';
|
||||
$: code = `db.${collection.key}.remove(${remove});`;
|
||||
|
||||
function performRemove() {}
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={performRemove}>
|
||||
<CodeExample {code} />
|
||||
|
||||
<label class="field">
|
||||
<textarea cols="30" rows="10" bind:value={remove} placeholder={'{}'} class="code"></textarea>
|
||||
</label>
|
||||
|
||||
<div class="flex">
|
||||
<div></div>
|
||||
<button type="submit" class="btn" disabled={!remove}>Remove</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
form {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.flex {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
</style>
|
19
frontend/src/organisms/connection/collection/stats.svelte
Normal file
19
frontend/src/organisms/connection/collection/stats.svelte
Normal file
@ -0,0 +1,19 @@
|
||||
<script>
|
||||
import ObjectGrid from '../../../components/objectgrid.svelte';
|
||||
import CodeExample from '../../../components/code-example.svelte';
|
||||
|
||||
export let collection;
|
||||
</script>
|
||||
|
||||
<div class="stats">
|
||||
<CodeExample code="db.stats()" />
|
||||
<ObjectGrid data={collection.stats} />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.stats {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template: auto 1fr / 1fr;
|
||||
}
|
||||
</style>
|
127
frontend/src/organisms/connection/dblist.svelte
Normal file
127
frontend/src/organisms/connection/dblist.svelte
Normal file
@ -0,0 +1,127 @@
|
||||
<script>
|
||||
import { busy, contextMenu, connections } from '../../stores';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { DropCollection, DropDatabase, OpenCollection, OpenConnection, OpenDatabase } from '../../../wailsjs/go/app/App';
|
||||
import Grid from '../../components/grid.svelte';
|
||||
|
||||
export let hosts = {};
|
||||
export let activeHostKey = '';
|
||||
export let activeDbKey = '';
|
||||
export let activeCollKey = '';
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
$: host = hosts[activeHostKey];
|
||||
$: connection = $connections[activeHostKey];
|
||||
$: database = connection?.databases[activeDbKey];
|
||||
$: collection = database?.collections?.[activeCollKey];
|
||||
|
||||
export async function reload() {
|
||||
activeHostKey && await openConnection(activeHostKey);
|
||||
activeDbKey && await openDatabase(activeDbKey);
|
||||
activeCollKey && await openCollection(activeCollKey);
|
||||
}
|
||||
|
||||
async function openConnection(hostKey) {
|
||||
busy.start();
|
||||
const databases = await OpenConnection(hostKey);
|
||||
|
||||
if (databases) {
|
||||
$connections[hostKey] = { databases: {} };
|
||||
databases.forEach(dbKey => {
|
||||
$connections[hostKey].databases[dbKey] = { collections: {} };
|
||||
});
|
||||
activeHostKey = hostKey;
|
||||
dispatch('connected', hostKey);
|
||||
window.runtime.WindowSetTitle(`${hosts[activeHostKey].name} - Mongodup`);
|
||||
}
|
||||
|
||||
busy.end();
|
||||
}
|
||||
|
||||
async function openDatabase(dbKey) {
|
||||
busy.start();
|
||||
const collections = await OpenDatabase(activeHostKey, dbKey);
|
||||
|
||||
for (const collKey of collections || []) {
|
||||
$connections[activeHostKey].databases[dbKey].collections[collKey] = {};
|
||||
}
|
||||
|
||||
busy.end();
|
||||
}
|
||||
|
||||
async function dropDatabase(dbKey) {
|
||||
busy.start();
|
||||
await DropDatabase(activeHostKey, dbKey);
|
||||
await reload();
|
||||
busy.end();
|
||||
}
|
||||
|
||||
async function openCollection(collKey) {
|
||||
busy.start();
|
||||
const stats = await OpenCollection(activeHostKey, activeDbKey, collKey);
|
||||
$connections[activeHostKey].databases[activeDbKey].collections[collKey].stats = stats;
|
||||
busy.end();
|
||||
}
|
||||
|
||||
async function dropCollection(dbKey, collKey) {
|
||||
busy.start();
|
||||
await DropCollection(activeHostKey, dbKey, collKey);
|
||||
await reload();
|
||||
busy.end();
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if host && connection}
|
||||
<Grid
|
||||
columns={[ { key: 'id' }, { key: 'collCount', right: true } ]}
|
||||
items={Object.keys(connection.databases).map(dbKey => ({
|
||||
id: dbKey,
|
||||
collCount: Object.keys(connection.databases[dbKey].collections || {}).length || '',
|
||||
children: Object.keys(connection.databases[dbKey].collections).map(collKey => ({
|
||||
id: collKey,
|
||||
menu: [
|
||||
{ label: `Drop ${collKey}`, fn: () => dropCollection(dbKey, collKey) },
|
||||
{ label: `Drop ${dbKey}`, fn: () => dropDatabase(dbKey) },
|
||||
{ separator: true },
|
||||
{ label: 'New database', fn: () => dispatch('newDatabase') },
|
||||
{ label: 'New collection', fn: () => dispatch('newCollection') },
|
||||
],
|
||||
})).sort((a, b) => a.id.localeCompare(b)) || [],
|
||||
menu: [
|
||||
{ label: `Drop ${dbKey}`, fn: () => dropDatabase(dbKey) },
|
||||
{ separator: true },
|
||||
{ label: 'New database', fn: () => dispatch('newDatabase') },
|
||||
{ label: 'New collection', fn: () => dispatch('newCollection') },
|
||||
],
|
||||
}))}
|
||||
actions={[
|
||||
{ icon: 'reload', fn: reload },
|
||||
{ icon: '+', fn: evt => {
|
||||
if (activeDbKey) {
|
||||
contextMenu.show(evt, [
|
||||
{ label: 'New database', fn: () => dispatch('newDatabase') },
|
||||
{ label: 'New collection', fn: () => dispatch('newCollection') },
|
||||
]);
|
||||
}
|
||||
else {
|
||||
dispatch('newDatabase');
|
||||
}
|
||||
} },
|
||||
{ icon: '-', fn: evt => {
|
||||
if (activeCollKey) {
|
||||
contextMenu.show(evt, [
|
||||
{ label: 'Drop database', fn: () => dropDatabase(activeDbKey) },
|
||||
{ label: 'Drop collection', fn: () => dropCollection(activeDbKey, activeCollKey) },
|
||||
]);
|
||||
}
|
||||
else {
|
||||
dropDatabase(activeDbKey);
|
||||
}
|
||||
}, disabled: !activeDbKey },
|
||||
]}
|
||||
bind:activeKey={activeDbKey}
|
||||
bind:activeChildKey={activeCollKey}
|
||||
on:select={e => openDatabase(e.detail)}
|
||||
on:selectChild={e => openCollection(e.detail)}
|
||||
/>
|
||||
{/if}
|
92
frontend/src/organisms/connection/index.svelte
Normal file
92
frontend/src/organisms/connection/index.svelte
Normal file
@ -0,0 +1,92 @@
|
||||
<script>
|
||||
import { onMount, tick } from 'svelte';
|
||||
import { Hosts } from '../../../wailsjs/go/app/App';
|
||||
import { input } from '../../actions';
|
||||
import Modal from '../../components/modal.svelte';
|
||||
import DatabaseList from './dblist.svelte';
|
||||
import { busy, connections } from '../../stores';
|
||||
import CollectionDetail from './collection/index.svelte';
|
||||
|
||||
export let hosts = {};
|
||||
export let activeHostKey = '';
|
||||
export let activeDbKey = '';
|
||||
export let activeCollKey = '';
|
||||
|
||||
let environment;
|
||||
let addressBarModalOpen = true;
|
||||
let dbList;
|
||||
|
||||
let newDb;
|
||||
let newDbInput;
|
||||
let newColl;
|
||||
let newCollInput;
|
||||
|
||||
$: if (newDb) {
|
||||
tick().then(() => newDbInput.focus());
|
||||
}
|
||||
|
||||
async function createDatabase() {
|
||||
busy.start();
|
||||
$connections[activeHostKey].databases[newDb.name] = { collections: {} };
|
||||
newDb = undefined;
|
||||
await dbList.reload();
|
||||
busy.end();
|
||||
}
|
||||
|
||||
async function createCollection() {
|
||||
busy.start();
|
||||
$connections[activeHostKey].databases[activeDbKey].collections[newColl.name] = {};
|
||||
newColl = undefined;
|
||||
await dbList.reload();
|
||||
busy.end();
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
window.runtime.Environment().then(e => environment = e);
|
||||
Hosts().then(h => hosts = h);
|
||||
});
|
||||
</script>
|
||||
|
||||
<DatabaseList
|
||||
{hosts}
|
||||
bind:activeHostKey
|
||||
bind:activeCollKey
|
||||
bind:activeDbKey
|
||||
bind:this={dbList}
|
||||
on:connected={() => addressBarModalOpen = false}
|
||||
on:newDatabase={() => newDb = {}}
|
||||
on:newCollection={() => newColl = {}}
|
||||
/>
|
||||
|
||||
<CollectionDetail
|
||||
collection={$connections[activeHostKey]?.databases[activeDbKey]?.collections?.[activeCollKey]}
|
||||
hostKey={activeHostKey}
|
||||
dbKey={activeDbKey}
|
||||
collectionKey={activeCollKey}
|
||||
/>
|
||||
|
||||
{#if newDb}
|
||||
<Modal bind:show={newDb}>
|
||||
<p><strong>Create a database</strong></p>
|
||||
<p>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.</p>
|
||||
<form on:submit|preventDefault={createDatabase}>
|
||||
<label class="field">
|
||||
<input type="text" spellcheck="false" bind:value={newDb.name} use:input placeholder="New collection name" bind:this={newDbInput} />
|
||||
</label>
|
||||
<button class="btn create" type="submit">Create database</button>
|
||||
</form>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
{#if newColl}
|
||||
<Modal bind:show={newColl}>
|
||||
<p><strong>Create a collections</strong></p>
|
||||
<p>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.</p>
|
||||
<form on:submit|preventDefault={createCollection}>
|
||||
<label class="field">
|
||||
<input type="text" spellcheck="false" bind:value={newColl.name} use:input placeholder="New collection name" bind:this={newCollInput} />
|
||||
</label>
|
||||
<button class="btn create" type="submit">Create collection</button>
|
||||
</form>
|
||||
</Modal>
|
||||
{/if}
|
Reference in New Issue
Block a user