mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-01-18 13:07:58 +00:00
Many small tweaks
This commit is contained in:
parent
f4bf270a20
commit
90ffdc6a8b
@ -11,7 +11,7 @@
|
||||
let activeHostKey = '';
|
||||
let activeDbKey = '';
|
||||
let activeCollKey = '';
|
||||
let addressBarModalOpen = false;
|
||||
let addressBarModalOpen = true;
|
||||
|
||||
$: host = hosts[activeHostKey];
|
||||
$: connection = connections[activeHostKey];
|
||||
@ -61,60 +61,46 @@
|
||||
<main>
|
||||
<AddressBar {hosts} bind:activeHostKey on:select={e => openConnection(e.detail)} bind:modalOpen={addressBarModalOpen} />
|
||||
|
||||
<div class="columns">
|
||||
{#if host && connection}
|
||||
<div class="hostlist">
|
||||
<Grid
|
||||
columns={[ { key: 'id' }, { key: 'collCount', right: true } ]}
|
||||
items={Object.keys(connection.databases).map(id => ({
|
||||
id,
|
||||
collCount: Object.keys(connection.databases[id].collections || {}).length,
|
||||
children: connection.databases[id].collections || [],
|
||||
}))}
|
||||
bind:activeKey={activeDbKey}
|
||||
bind:activeChildKey={activeCollKey}
|
||||
on:select={e => openDatabase(e.detail)}
|
||||
on:selectChild={e => openCollection(e.detail)}
|
||||
/>
|
||||
</div>
|
||||
{#if host && connection}
|
||||
<div class="hostlist">
|
||||
<Grid
|
||||
columns={[ { key: 'id' }, { key: 'collCount', right: true } ]}
|
||||
items={Object.keys(connection.databases).map(id => ({
|
||||
id,
|
||||
collCount: Object.keys(connection.databases[id].collections || {}).length,
|
||||
children: connection.databases[id].collections || [],
|
||||
}))}
|
||||
bind:activeKey={activeDbKey}
|
||||
bind:activeChildKey={activeCollKey}
|
||||
on:select={e => openDatabase(e.detail)}
|
||||
on:selectChild={e => openCollection(e.detail)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="collection">
|
||||
<CollectionDetail
|
||||
{collection}
|
||||
hostKey={activeHostKey}
|
||||
dbKey={activeDbKey}
|
||||
collectionKey={activeCollKey}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="collection">
|
||||
<CollectionDetail
|
||||
{collection}
|
||||
hostKey={activeHostKey}
|
||||
dbKey={activeDbKey}
|
||||
collectionKey={activeCollKey}
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
</main>
|
||||
|
||||
<style>
|
||||
main {
|
||||
height: 100vh;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
display: grid;
|
||||
grid-template: 3rem auto / 250px 1fr;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.columns {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
.columns > :global(*) {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
main > :global(.addressbar) {
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
|
||||
.hostlist {
|
||||
flex: 0 0 250px;
|
||||
overflow: scroll;
|
||||
}
|
||||
.collection {
|
||||
flex: 1;
|
||||
width: auto;
|
||||
}
|
||||
</style>
|
||||
|
@ -13,7 +13,6 @@
|
||||
border-radius: 10px;
|
||||
padding: 0.5rem;
|
||||
opacity: 0.6;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
strong {
|
||||
|
@ -107,8 +107,7 @@
|
||||
.grid {
|
||||
background-color: #fff;
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
max-height: 400px; /* fixme */
|
||||
height: 100%;
|
||||
}
|
||||
.grid.contained {
|
||||
border: 1px solid #ccc;
|
||||
|
@ -18,8 +18,13 @@
|
||||
|
||||
<div class="addressbar">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<div class="address" class:empty={!host?.uri} on:click={() => modalOpen = true}>
|
||||
{host?.uri || 'No host selected'}
|
||||
<div class="address" on:click={() => modalOpen = true}>
|
||||
{#if host?.uri}
|
||||
{@const split = host.uri.split('://').map(s => s.split('/')).flat()}
|
||||
<span class="protocol">{split[0]}://</span><span class="hostname">{split[1]}</span><span class="path">{split.slice(2).join('/')}</span>
|
||||
{:else}
|
||||
<span class="empty">no host selected</span>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="actions">
|
||||
@ -48,14 +53,15 @@
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 1rem;
|
||||
padding: 0.5rem 0.5rem 0.5rem 1rem;
|
||||
height: 3rem;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.address.empty {
|
||||
font-style: italic;
|
||||
.address .protocol,
|
||||
.address .path,
|
||||
.address .empty {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
|
@ -24,20 +24,19 @@
|
||||
limit: 30,
|
||||
};
|
||||
|
||||
let result = [];
|
||||
let result = {};
|
||||
let submittedForm = {};
|
||||
let queryField;
|
||||
let activeKey = '';
|
||||
$: 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})` : ''};`;
|
||||
|
||||
$: if (collection) {
|
||||
result = [];
|
||||
}
|
||||
|
||||
async function submitQuery() {
|
||||
activeKey = '';
|
||||
result = await PerformFind(collection.hostKey, collection.dbKey, collection.key, JSON.stringify(form));
|
||||
queryField?.focus();
|
||||
queryField?.select();
|
||||
if (result) {
|
||||
submittedForm = JSON.parse(JSON.stringify(form));
|
||||
}
|
||||
resetFocus();
|
||||
}
|
||||
|
||||
function prev() {
|
||||
@ -58,92 +57,104 @@
|
||||
alert('yet to be implemented');
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
function resetFocus() {
|
||||
queryField?.focus();
|
||||
queryField?.select();
|
||||
});
|
||||
}
|
||||
|
||||
onMount(resetFocus);
|
||||
</script>
|
||||
|
||||
<form on:submit|preventDefault={submitQuery}>
|
||||
<div class="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>
|
||||
<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="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">
|
||||
<ObjectGrid data={result} bind:activeKey />
|
||||
<div class="controls">
|
||||
<div>
|
||||
{#if result}
|
||||
Results: {result.length}
|
||||
{/if}
|
||||
<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>
|
||||
<button class="btn danger" on:click={remove} disabled={!activeKey}>
|
||||
<Icon name="-" />
|
||||
</button>
|
||||
<button class="btn" on:click={prev} disabled={!form.limit || (form.skip <= 0) || !result?.length}>
|
||||
<Icon name="chev-l" />
|
||||
</button>
|
||||
<button class="btn" on:click={next} disabled={!form.limit || ((result?.length || Infinity) < form.limit) || !result?.length}>
|
||||
<Icon name="chev-r" />
|
||||
</button>
|
||||
|
||||
<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 />
|
||||
{/key}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div>
|
||||
{#if result}
|
||||
Results: {result.total || 0}
|
||||
{/if}
|
||||
</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>
|
||||
|
||||
<style>
|
||||
.row-one {
|
||||
.find {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
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;
|
||||
}
|
||||
.row-two {
|
||||
display: grid;
|
||||
gap: 0.5rem;
|
||||
grid-template-columns: 5fr 1fr 1fr 1fr;
|
||||
margin-bottom: 0.5rem;
|
||||
.form-row.two {
|
||||
grid-template: 1fr / 5fr 1fr 1fr 1fr;
|
||||
}
|
||||
|
||||
.result {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
margin-top: 0.5rem;
|
||||
display: grid;
|
||||
grid-template: 1fr auto / 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.result > :global(.grid) {
|
||||
flex: 1;
|
||||
.result > .grid {
|
||||
overflow: auto;
|
||||
}
|
||||
.result > .controls {
|
||||
display: flex;
|
||||
|
@ -1,11 +1,10 @@
|
||||
<script>
|
||||
import ObjectGrid from '../../components/objectgrid.svelte';
|
||||
import CodeExample from '../../components/code-example.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;
|
||||
@ -23,29 +22,25 @@
|
||||
|
||||
<div class="collection" class:empty={!collection}>
|
||||
{#if 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} />
|
||||
{#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'}
|
||||
<CodeExample code="db.stats()" />
|
||||
<ObjectGrid data={collection.stats} />
|
||||
{:else if tab === 'find'}
|
||||
<Find {collection} />
|
||||
{:else if tab === 'insert'}
|
||||
<Insert {collection} />
|
||||
{:else if tab === 'remove'}
|
||||
<Remove {collection} />
|
||||
{:else if tab === 'indexes'}
|
||||
<Indexes {collection} />
|
||||
{/if}
|
||||
</div>
|
||||
<div class="container">
|
||||
{#if tab === 'stats'} <Stats {collection} />
|
||||
{:else if tab === 'find'} <Find {collection} />
|
||||
{:else if tab === 'insert'} <Insert {collection} />
|
||||
{:else if tab === 'remove'} <Remove {collection} />
|
||||
{:else if tab === 'indexes'} <Indexes {collection} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
{:else}
|
||||
No collection selected
|
||||
{/if}
|
||||
@ -53,16 +48,18 @@
|
||||
|
||||
<style>
|
||||
.collection {
|
||||
margin: 1rem 1rem 1rem 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
display: grid;
|
||||
grid-template: auto 1fr / 1fr;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
padding: 0.5rem 0.5rem 0;
|
||||
flex: 1;
|
||||
padding: 0 0.5rem;
|
||||
display: flex;
|
||||
flex-flow: column;
|
||||
align-items: stretch;
|
||||
}
|
||||
.container > :global(*) {
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
@ -6,10 +6,6 @@
|
||||
let input = '';
|
||||
let insertedIds;
|
||||
|
||||
$: if (collection) {
|
||||
insertedIds = undefined;
|
||||
}
|
||||
|
||||
async function insert() {
|
||||
insertedIds = await PerformInsert(collection.hostKey, collection.dbKey, collection.key, input);
|
||||
}
|
||||
|
19
frontend/src/organisms/collection-detail/stats.svelte
Normal file
19
frontend/src/organisms/collection-detail/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>
|
3
frontend/wailsjs/go/main/App.d.ts
vendored
3
frontend/wailsjs/go/main/App.d.ts
vendored
@ -2,6 +2,7 @@
|
||||
// This file is automatically generated. DO NOT EDIT
|
||||
import {map[string]main} from '../models';
|
||||
import {primitive} from '../models';
|
||||
import {main} from '../models';
|
||||
|
||||
export function Hosts():Promise<map[string]main.Host>;
|
||||
|
||||
@ -11,6 +12,6 @@ export function OpenConnection(arg1:string):Promise<Array<string>>;
|
||||
|
||||
export function OpenDatabase(arg1:string,arg2:string):Promise<Array<string>>;
|
||||
|
||||
export function PerformFind(arg1:string,arg2:string,arg3:string,arg4:string):Promise<any>;
|
||||
export function PerformFind(arg1:string,arg2:string,arg3:string,arg4:string):Promise<main.findResult>;
|
||||
|
||||
export function PerformInsert(arg1:string,arg2:string,arg3:string,arg4:string):Promise<any>;
|
||||
|
19
frontend/wailsjs/go/models.ts
Executable file
19
frontend/wailsjs/go/models.ts
Executable file
@ -0,0 +1,19 @@
|
||||
export namespace main {
|
||||
|
||||
export class findResult {
|
||||
total: number;
|
||||
results: any;
|
||||
|
||||
static createFrom(source: any = {}) {
|
||||
return new findResult(source);
|
||||
}
|
||||
|
||||
constructor(source: any = {}) {
|
||||
if ('string' === typeof source) source = JSON.parse(source);
|
||||
this.total = source["total"];
|
||||
this.results = source["results"];
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
39
main.go
39
main.go
@ -141,7 +141,13 @@ func (a *App) OpenCollection(hostKey, dbKey, collKey string) (result bson.M) {
|
||||
return result
|
||||
}
|
||||
|
||||
func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) interface{} {
|
||||
type findResult struct {
|
||||
Total int64 `json:"total"`
|
||||
Results interface{} `json:"results"`
|
||||
}
|
||||
|
||||
func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) findResult {
|
||||
var out findResult
|
||||
var form struct {
|
||||
Fields string `json:"fields"`
|
||||
Limit int64 `json:"limit"`
|
||||
@ -158,13 +164,13 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Title: "Couldn't parse form",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
client, ctx, close, err := a.connectToHost(hostKey)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
defer close()
|
||||
@ -180,7 +186,7 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Title: "Invalid query",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(form.Fields), &projection)
|
||||
@ -191,7 +197,7 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Title: "Invalid projection",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
err = json.Unmarshal([]byte(form.Sort), &sort)
|
||||
@ -202,7 +208,7 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Title: "Invalid sort",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
opt := mongoOptions.FindOptions{
|
||||
@ -212,7 +218,18 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Sort: sort,
|
||||
}
|
||||
|
||||
cur, err := client.Database(dbKey).Collection(collKey).Find(ctx, bson.D{}, &opt)
|
||||
total, err := client.Database(dbKey).Collection(collKey).CountDocuments(ctx, query, nil)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||
Type: runtime.ErrorDialog,
|
||||
Title: "Encountered an error while counting documents",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return out
|
||||
}
|
||||
|
||||
cur, err := client.Database(dbKey).Collection(collKey).Find(ctx, query, &opt)
|
||||
if err != nil {
|
||||
fmt.Println(err.Error())
|
||||
runtime.MessageDialog(a.ctx, runtime.MessageDialogOptions{
|
||||
@ -220,7 +237,7 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Title: "Encountered an error while performing query",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
defer cur.Close(ctx)
|
||||
@ -234,10 +251,12 @@ func (a *App) PerformFind(hostKey, dbKey, collKey string, formJson string) inter
|
||||
Title: "Encountered an error while performing query",
|
||||
Message: err.Error(),
|
||||
})
|
||||
return nil
|
||||
return out
|
||||
}
|
||||
|
||||
return results
|
||||
out.Results = results
|
||||
out.Total = total
|
||||
return out
|
||||
}
|
||||
|
||||
func (a *App) PerformInsert(hostKey, dbKey, collKey, jsonData string) interface{} {
|
||||
|
Loading…
Reference in New Issue
Block a user