diff --git a/frontend/src/components/codeeditor.svelte b/frontend/src/components/codeeditor.svelte
new file mode 100644
index 0000000..262eaea
--- /dev/null
+++ b/frontend/src/components/codeeditor.svelte
@@ -0,0 +1,57 @@
+
+
+
{#if collection}
{#key collection}
-
+
{#if tab === 'stats'}
@@ -61,6 +65,7 @@
{:else if tab === 'remove'}
{:else if tab === 'indexes'}
{:else if tab === 'aggregate'}
+ {:else if tab === 'shell'}
{/if}
{/key}
diff --git a/frontend/src/organisms/connection/database/index.svelte b/frontend/src/organisms/connection/database/index.svelte
index c5f13ea..b3d37eb 100644
--- a/frontend/src/organisms/connection/database/index.svelte
+++ b/frontend/src/organisms/connection/database/index.svelte
@@ -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 @@
{#if database}
{#key database}
-
+
{#if tab === 'stats'}
+ {:else if tab === 'shell'}
{/if}
{/key}
diff --git a/frontend/src/organisms/connection/host/index.svelte b/frontend/src/organisms/connection/host/index.svelte
index f46a37d..3da1b4a 100644
--- a/frontend/src/organisms/connection/host/index.svelte
+++ b/frontend/src/organisms/connection/host/index.svelte
@@ -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 @@
{:else if tab === 'logs'}
{:else if tab === 'systemInfo'}
+ {:else if tab === 'shell'}
{/if}
{/key}
diff --git a/frontend/src/organisms/connection/shell.svelte b/frontend/src/organisms/connection/shell.svelte
new file mode 100644
index 0000000..3e4d62f
--- /dev/null
+++ b/frontend/src/organisms/connection/shell.svelte
@@ -0,0 +1,146 @@
+
+
+
+
+
+
+
+
+
+ {#if busy}
+
+ {:else if result.errorTitle || result.errorDescription}
+
+
+
+ {:else}
+
{result.output || ''}
+ {/if}
+
+
+
+ {#key result}
+
+ {#if result?.status}
+ Exit code: {result.status}
+ {/if}
+
+ {/key}
+
+
+
+
+
+
diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts
index ecd015c..565eb1a 100755
--- a/frontend/wailsjs/go/app/App.d.ts
+++ b/frontend/wailsjs/go/app/App.d.ts
@@ -20,6 +20,8 @@ export function DropIndex(arg1:string,arg2:string,arg3:string,arg4:string):Promi
export function Environment():Promise
;
+export function ExecuteShellScript(arg1:string,arg2:string,arg3:string,arg4:string):Promise;
+
export function FindItems(arg1:string,arg2:string,arg3:string,arg4:string):Promise;
export function GetIndexes(arg1:string,arg2:string,arg3:string):Promise;
diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js
index a2548f1..aee0d09 100755
--- a/frontend/wailsjs/go/app/App.js
+++ b/frontend/wailsjs/go/app/App.js
@@ -30,6 +30,10 @@ export function Environment() {
return window['go']['app']['App']['Environment']();
}
+export function ExecuteShellScript(arg1, arg2, arg3, arg4) {
+ return window['go']['app']['App']['ExecuteShellScript'](arg1, arg2, arg3, arg4);
+}
+
export function FindItems(arg1, arg2, arg3, arg4) {
return window['go']['app']['App']['FindItems'](arg1, arg2, arg3, arg4);
}
diff --git a/internal/app/app.go b/internal/app/app.go
index 4ea2f99..096b039 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -25,6 +25,7 @@ type EnvironmentInfo struct {
HasMongoExport bool `json:"hasMongoExport"`
HasMongoDump bool `json:"hasMongoDump"`
+ HasMongoShell bool `json:"hasMongoShell"`
HomeDirectory string `json:"homeDirectory"`
DataDirectory string `json:"dataDirectory"`
@@ -49,6 +50,9 @@ func NewApp(version string) *App {
_, err = exec.LookPath("mongoexport")
a.Env.HasMongoExport = err == nil
+ _, err = exec.LookPath("mongosh")
+ a.Env.HasMongoShell = err == nil
+
a.Env.HomeDirectory, err = os.UserHomeDir()
if err != nil {
panic(errors.New("encountered an error while getting home directory"))
diff --git a/internal/app/host_shell.go b/internal/app/host_shell.go
new file mode 100644
index 0000000..9b27c07
--- /dev/null
+++ b/internal/app/host_shell.go
@@ -0,0 +1,105 @@
+package app
+
+import (
+ "fmt"
+ "net/url"
+ "os"
+ "os/exec"
+ "path"
+
+ "github.com/google/uuid"
+ "github.com/wailsapp/wails/v2/pkg/runtime"
+)
+
+type ExecuteShellScriptResult struct {
+ Output string `json:"output"`
+ Status int `json:"status"`
+ ErrorTitle string `json:"errorTitle"`
+ ErrorDescription string `json:"errorDescription"`
+}
+
+func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result ExecuteShellScriptResult) {
+ if !a.Env.HasMongoShell {
+ result.ErrorTitle = "mongosh not found"
+ result.ErrorDescription = "The mongosh executable is required to run a shell script. Please see https://www.mongodb.com/docs/mongodb-shell/install/"
+ return
+ }
+
+ hosts, err := a.Hosts()
+ if err != nil {
+ result.ErrorTitle = "Could not get hosts"
+ result.ErrorDescription = err.Error()
+ return
+ }
+
+ host, hostFound := hosts[hostKey]
+ if !hostFound {
+ result.ErrorTitle = "The specified host does not seem to exist"
+ return
+ }
+
+ id, err := uuid.NewRandom()
+ if err != nil {
+ runtime.LogErrorf(a.ctx, "Shell: failed to generate a UUID: %s", err.Error())
+ result.ErrorTitle = "Could not generate UUID"
+ result.ErrorDescription = err.Error()
+ return
+ }
+
+ dirname := path.Join(a.Env.DataDirectory, "Shell Scripts")
+ fname := path.Join(dirname, fmt.Sprintf("%s.mongosh.js", id.String()))
+
+ if err := os.MkdirAll(dirname, os.ModePerm); err != nil {
+ runtime.LogWarningf(a.ctx, "Shell: failed to mkdir %s", err.Error())
+ result.ErrorTitle = "Could not create temporary directory"
+ result.ErrorDescription = err.Error()
+ return
+ }
+
+ scriptHeader := fmt.Sprintf("// Namespace: %s.%s\n", dbKey, collKey)
+
+ if dbKey != "" {
+ url, err := url.Parse(host.URI)
+ if err != nil {
+ runtime.LogWarningf(a.ctx, "Shell: failed to parse host URI %s: %s", host.URI, err.Error())
+ result.ErrorTitle = "Could parse host URI"
+ result.ErrorDescription = err.Error()
+ return
+ }
+
+ url.Path = "/" + dbKey
+ scriptHeader = scriptHeader + fmt.Sprintf("db = connect('%s');\n", url.String())
+ }
+
+ if collKey != "" {
+ scriptHeader = scriptHeader + fmt.Sprintf("coll = db.getCollection('%s');\n", collKey)
+ }
+
+ scriptHeader = scriptHeader + "\n// Start of user script\n"
+ script = scriptHeader + script
+
+ if err := os.WriteFile(fname, []byte(script), os.ModePerm); err != nil {
+ runtime.LogWarningf(a.ctx, "Shell: failed to write script to %s", err.Error())
+ result.ErrorTitle = "Could not create temporary script file"
+ result.ErrorDescription = err.Error()
+ return
+ }
+
+ cmd := exec.Command("mongosh", "--file", fname, host.URI)
+ stdout, err := cmd.Output()
+
+ if exiterr, ok := err.(*exec.ExitError); ok {
+ result.Status = exiterr.ExitCode()
+ } else if err != nil {
+ runtime.LogWarningf(a.ctx, "Shell: failed to execute: mongosh --file %s: %s", fname, err.Error())
+ result.ErrorTitle = "Could not execute script"
+ result.ErrorDescription = err.Error()
+ return
+ } else {
+ result.Status = 0
+ }
+
+ os.Remove(fname)
+ result.Output = string(stdout)
+ return
+}