mirror of
https://github.com/garraflavatra/rolens.git
synced 2025-07-20 06:28:04 +00:00
Shell feature (#44)
Resolves #37: adds a shell tab where the user can write mongo shell scripts and execute them.
This commit is contained in:
@ -9,6 +9,7 @@
|
||||
import Indexes from './indexes.svelte';
|
||||
import Insert from './insert.svelte';
|
||||
import Remove from './remove.svelte';
|
||||
import Shell from '../shell.svelte';
|
||||
import Stats from './stats.svelte';
|
||||
import Update from './update.svelte';
|
||||
|
||||
@ -42,16 +43,19 @@
|
||||
<div class="view" class:empty={!collection}>
|
||||
{#if collection}
|
||||
{#key collection}
|
||||
<TabBar tabs={[
|
||||
{ key: 'stats', icon: 'chart', title: 'Stats' },
|
||||
{ key: 'find', icon: 'db', title: 'Find' },
|
||||
{ key: 'insert', icon: '+', title: 'Insert' },
|
||||
{ key: 'update', icon: 'edit', title: 'Update' },
|
||||
{ key: 'remove', icon: 'trash', title: 'Remove' },
|
||||
{ key: 'indexes', icon: 'list', title: 'Indexes' },
|
||||
{ key: 'aggregate', icon: 're', title: 'Aggregate' },
|
||||
]}
|
||||
bind:selectedKey={tab} />
|
||||
<TabBar
|
||||
tabs={[
|
||||
{ key: 'stats', icon: 'chart', title: 'Stats' },
|
||||
{ key: 'find', icon: 'db', title: 'Find' },
|
||||
{ key: 'insert', icon: '+', title: 'Insert' },
|
||||
{ key: 'update', icon: 'edit', title: 'Update' },
|
||||
{ key: 'remove', icon: 'trash', title: 'Remove' },
|
||||
{ key: 'indexes', icon: 'list', title: 'Indexes' },
|
||||
{ key: 'aggregate', icon: 're', title: 'Aggregate' },
|
||||
{ key: 'shell', icon: 'shell', title: 'Shell' },
|
||||
]}
|
||||
bind:selectedKey={tab}
|
||||
/>
|
||||
|
||||
<div class="container">
|
||||
{#if tab === 'stats'} <Stats {collection} />
|
||||
@ -61,6 +65,7 @@
|
||||
{:else if tab === 'remove'} <Remove {collection} />
|
||||
{:else if tab === 'indexes'} <Indexes {collection} />
|
||||
{:else if tab === 'aggregate'} <Aggregate {collection} />
|
||||
{:else if tab === 'shell'} <Shell {collection} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
|
@ -2,6 +2,8 @@
|
||||
import BlankState from '$components/blankstate.svelte';
|
||||
import TabBar from '$components/tabbar.svelte';
|
||||
import { EventsOn } from '$wails/runtime/runtime';
|
||||
|
||||
import Shell from '../shell.svelte';
|
||||
import Stats from './stats.svelte';
|
||||
|
||||
export let database;
|
||||
@ -24,9 +26,15 @@
|
||||
<div class="view" class:empty={!database}>
|
||||
{#if database}
|
||||
{#key database}
|
||||
<TabBar tabs={[ { key: 'stats', icon: 'chart', title: 'Database stats' } ]} bind:selectedKey={tab} />
|
||||
<TabBar
|
||||
tabs={[
|
||||
{ key: 'stats', icon: 'chart', title: 'Database stats' },
|
||||
{ key: 'shell', icon: 'shell', title: 'Shell' },
|
||||
]}
|
||||
bind:selectedKey={tab} />
|
||||
<div class="container">
|
||||
{#if tab === 'stats'} <Stats {database} />
|
||||
{:else if tab === 'shell'} <Shell {database} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
|
@ -4,6 +4,7 @@
|
||||
import { EventsOn } from '$wails/runtime/runtime';
|
||||
|
||||
import Logs from './logs.svelte';
|
||||
import Shell from '../shell.svelte';
|
||||
import Status from './status.svelte';
|
||||
import SystemInfo from './systeminfo.svelte';
|
||||
|
||||
@ -28,6 +29,7 @@
|
||||
<TabBar
|
||||
tabs={[
|
||||
{ key: 'status', icon: 'chart', title: 'Host status' },
|
||||
{ key: 'shell', icon: 'shell', title: 'Shell' },
|
||||
{ key: 'logs', icon: 'doc', title: 'Logs' },
|
||||
{ key: 'systemInfo', icon: 'server', title: 'System info' },
|
||||
]}
|
||||
@ -38,6 +40,7 @@
|
||||
{#if tab === 'status'} <Status {host} />
|
||||
{:else if tab === 'logs'} <Logs {host} />
|
||||
{:else if tab === 'systemInfo'} <SystemInfo {host} />
|
||||
{:else if tab === 'shell'} <Shell {host} />
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
|
146
frontend/src/organisms/connection/shell.svelte
Normal file
146
frontend/src/organisms/connection/shell.svelte
Normal file
@ -0,0 +1,146 @@
|
||||
<script>
|
||||
import BlankState from '$components/blankstate.svelte';
|
||||
import CodeEditor from '$components/codeeditor.svelte';
|
||||
import Icon from '$components/icon.svelte';
|
||||
import { javascript } from '@codemirror/lang-javascript';
|
||||
import { onDestroy, onMount } from 'svelte';
|
||||
|
||||
export let host = undefined;
|
||||
export let database = undefined;
|
||||
export let collection = undefined;
|
||||
|
||||
const placeholder = '// Write your script here...';
|
||||
const extensions = [ javascript() ];
|
||||
let script = '';
|
||||
let result = {};
|
||||
let copySucceeded = false;
|
||||
let timeout;
|
||||
let busy = false;
|
||||
let editor;
|
||||
|
||||
async function run() {
|
||||
busy = true;
|
||||
|
||||
if (collection) {
|
||||
result = await collection.executeShellScript(script);
|
||||
}
|
||||
else if (database) {
|
||||
result = await database.executeShellScript(script);
|
||||
}
|
||||
else if (host) {
|
||||
result = await host.executeShellScript(script);
|
||||
}
|
||||
|
||||
busy = false;
|
||||
}
|
||||
|
||||
async function copyErrorDescription() {
|
||||
await navigator.clipboard.writeText(result.errorDescription);
|
||||
copySucceeded = true;
|
||||
timeout = setTimeout(() => copySucceeded = false, 1500);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
editor.dispatch({
|
||||
changes: {
|
||||
from: 0,
|
||||
to: editor.state.doc.length,
|
||||
insert: placeholder,
|
||||
},
|
||||
selection: {
|
||||
from: 0,
|
||||
anchor: 0,
|
||||
to: placeholder.length,
|
||||
head: placeholder.length,
|
||||
},
|
||||
});
|
||||
editor.focus();
|
||||
});
|
||||
onDestroy(() => clearTimeout(timeout));
|
||||
</script>
|
||||
|
||||
<div class="shell">
|
||||
<div class="overflow">
|
||||
<!-- svelte-ignore a11y-label-has-associated-control -->
|
||||
<label class="field">
|
||||
<CodeEditor bind:editor bind:text={script} {extensions} />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="output">
|
||||
{#if busy}
|
||||
<BlankState icon="loading" label="Executing…" />
|
||||
{:else if result.errorTitle || result.errorDescription}
|
||||
<BlankState title={result.errorTitle} label={result.errorDescription} icon="!">
|
||||
<button class="button-small" on:click={copyErrorDescription}>
|
||||
<Icon name={copySucceeded ? 'check' : 'clipboard'} /> Copy error message
|
||||
</button>
|
||||
</BlankState>
|
||||
{:else}
|
||||
<pre>{result.output || ''}</pre>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
{#key result}
|
||||
<div class="status flash-green">
|
||||
{#if result?.status}
|
||||
Exit code: {result.status}
|
||||
{/if}
|
||||
</div>
|
||||
{/key}
|
||||
|
||||
<button class="btn" on:click={run}>
|
||||
<Icon name="play" /> Run
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.shell {
|
||||
display: grid;
|
||||
grid-template: 1fr auto / 1fr 1fr;
|
||||
}
|
||||
|
||||
.overflow {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.field {
|
||||
height: 100%;
|
||||
}
|
||||
.field :global(.editor) {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
.output {
|
||||
background-color: #111;
|
||||
color: #fff;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
}
|
||||
.output :global(*) {
|
||||
color: #fff;
|
||||
}
|
||||
.output pre {
|
||||
font-family: monospace;
|
||||
padding: 0.5rem;
|
||||
user-select: text;
|
||||
-webkit-user-select: text;
|
||||
cursor: text;
|
||||
}
|
||||
.output :global(.blankstate) {
|
||||
margin: auto;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 0.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
grid-column: 1 / 3;
|
||||
}
|
||||
.controls .status {
|
||||
margin-right: auto;
|
||||
}
|
||||
</style>
|
Reference in New Issue
Block a user