From 9d577ac429c02c9464c71203ceec8302cb367318 Mon Sep 17 00:00:00 2001 From: Romein van Buren Date: Wed, 19 Jul 2023 20:01:15 +0200 Subject: [PATCH] Added functionality to save and import shell scripts (#37) --- frontend/src/lib/strings.js | 3 - .../connection/collection/index.svelte | 4 + .../connection/database/index.svelte | 3 +- .../src/organisms/connection/index.svelte | 13 +- .../src/organisms/connection/shell.svelte | 45 +++++- frontend/wailsjs/go/app/App.d.ts | 4 + frontend/wailsjs/go/app/App.js | 8 ++ internal/app/host_shell.go | 135 ++++++++++++++---- 8 files changed, 176 insertions(+), 39 deletions(-) diff --git a/frontend/src/lib/strings.js b/frontend/src/lib/strings.js index 1d05eab..cc06f29 100644 --- a/frontend/src/lib/strings.js +++ b/frontend/src/lib/strings.js @@ -24,8 +24,5 @@ export function looseJsonIsValid(json) { } export function stringCouldBeID(string) { - if (looseJsonIsValid(string)) { - return false; - } return /^[a-zA-Z0-9_-]{1,}$/.test(string); } diff --git a/frontend/src/organisms/connection/collection/index.svelte b/frontend/src/organisms/connection/collection/index.svelte index e0018ec..696f8b9 100644 --- a/frontend/src/organisms/connection/collection/index.svelte +++ b/frontend/src/organisms/connection/collection/index.svelte @@ -12,6 +12,8 @@ import Stats from './stats.svelte'; import Update from './update.svelte'; + export let host; + export let database; export let collection; export let tab = 'stats'; @@ -47,6 +49,8 @@ this={view.component} visible={tab === view.key} on:performFind={catchQuery} + {host} + {database} {collection} /> diff --git a/frontend/src/organisms/connection/database/index.svelte b/frontend/src/organisms/connection/database/index.svelte index 2ee1327..9de360f 100644 --- a/frontend/src/organisms/connection/database/index.svelte +++ b/frontend/src/organisms/connection/database/index.svelte @@ -6,6 +6,7 @@ import Shell from '../shell.svelte'; import Stats from './stats.svelte'; + export let host; export let database; export let tab = 'stats'; @@ -28,7 +29,7 @@ {#each Object.values(tabs) as view}
- +
{/each} {/key} diff --git a/frontend/src/organisms/connection/index.svelte b/frontend/src/organisms/connection/index.svelte index 277d5cd..370bfb4 100644 --- a/frontend/src/organisms/connection/index.svelte +++ b/frontend/src/organisms/connection/index.svelte @@ -64,11 +64,20 @@ {#if activeCollKey} {#key activeCollKey} - + {/key} {:else if activeDbKey} {#key activeDbKey} - + {/key} {:else if activeHostKey} {#key activeHostKey} diff --git a/frontend/src/organisms/connection/shell.svelte b/frontend/src/organisms/connection/shell.svelte index 5ebf6ea..aeb2e2b 100644 --- a/frontend/src/organisms/connection/shell.svelte +++ b/frontend/src/organisms/connection/shell.svelte @@ -2,6 +2,7 @@ import BlankState from '$components/blankstate.svelte'; import CodeEditor from '$components/codeeditor.svelte'; import Icon from '$components/icon.svelte'; + import { OpenShellScript, SaveShellScript } from '$wails/go/app/App'; import { javascript } from '@codemirror/lang-javascript'; import { onDestroy, onMount } from 'svelte'; @@ -19,7 +20,7 @@ let busy = false; let editor; - async function run() { + async function runScript() { busy = true; if (collection) { @@ -35,6 +36,36 @@ busy = false; } + async function loadScript() { + const _script = await OpenShellScript(); + if (_script) { + script = _script; + editor.dispatch({ + changes: { + from: 0, + to: editor.state.doc.length, + insert: script, + }, + selection: { + from: 0, + anchor: 0, + to: 0, + head: 0, + }, + }); + } + } + + async function saveScript() { + await SaveShellScript( + host?.key || '', + database?.key || '', + collection?.key || '', + script, + false // not temporary + ); + } + async function copyErrorDescription() { await navigator.clipboard.writeText(result.errorDescription); copySucceeded = true; @@ -86,10 +117,19 @@
- +
+ + +
+ {#key result}
{#if result?.status} @@ -145,6 +185,7 @@ .controls { margin-top: 0.5rem; display: flex; + gap: 0.2rem; align-items: center; grid-column: 1 / 3; } diff --git a/frontend/wailsjs/go/app/App.d.ts b/frontend/wailsjs/go/app/App.d.ts index 565eb1a..bc85840 100755 --- a/frontend/wailsjs/go/app/App.d.ts +++ b/frontend/wailsjs/go/app/App.d.ts @@ -40,6 +40,8 @@ export function OpenConnection(arg1:string):Promise; export function OpenDatabase(arg1:string,arg2:string):Promise; +export function OpenShellScript():Promise; + export function PerformDump(arg1:string):Promise; export function PerformFindExport(arg1:string,arg2:string,arg3:string,arg4:string):Promise; @@ -62,6 +64,8 @@ export function ReportSharedStateVariable(arg1:string,arg2:string):Promise export function SaveQuery(arg1:string):Promise; +export function SaveShellScript(arg1:string,arg2:string,arg3:string,arg4:string,arg5:boolean):Promise; + export function SavedQueries():Promise; export function Settings():Promise; diff --git a/frontend/wailsjs/go/app/App.js b/frontend/wailsjs/go/app/App.js index aee0d09..581ca98 100755 --- a/frontend/wailsjs/go/app/App.js +++ b/frontend/wailsjs/go/app/App.js @@ -70,6 +70,10 @@ export function OpenDatabase(arg1, arg2) { return window['go']['app']['App']['OpenDatabase'](arg1, arg2); } +export function OpenShellScript() { + return window['go']['app']['App']['OpenShellScript'](); +} + export function PerformDump(arg1) { return window['go']['app']['App']['PerformDump'](arg1); } @@ -114,6 +118,10 @@ export function SaveQuery(arg1) { return window['go']['app']['App']['SaveQuery'](arg1); } +export function SaveShellScript(arg1, arg2, arg3, arg4, arg5) { + return window['go']['app']['App']['SaveShellScript'](arg1, arg2, arg3, arg4, arg5); +} + export function SavedQueries() { return window['go']['app']['App']['SavedQueries'](); } diff --git a/internal/app/host_shell.go b/internal/app/host_shell.go index 9a4a9ad..5675e60 100644 --- a/internal/app/host_shell.go +++ b/internal/app/host_shell.go @@ -20,6 +20,13 @@ type ExecuteShellScriptResult struct { ErrorDescription string `json:"errorDescription"` } +type SaveShellScriptResult struct { + Host Host `json:"host"` + Fname string `json:"filename"` + 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" @@ -27,8 +34,40 @@ func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result return } + saveRes := a.SaveShellScript(hostKey, dbKey, collKey, script, true) + if (saveRes.ErrorTitle != "") || (saveRes.ErrorDescription != "") { + result.ErrorTitle = saveRes.ErrorTitle + result.ErrorDescription = saveRes.ErrorDescription + return + } + + var outbuf, errbuf strings.Builder + cmd := exec.Command("mongosh", "--file", saveRes.Fname, saveRes.Host.URI) + cmd.Stdout = &outbuf + cmd.Stderr = &errbuf + err := cmd.Run() + + 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", saveRes.Fname, err.Error()) + result.ErrorTitle = "mongosh failure" + result.ErrorDescription = err.Error() + return + } else { + result.Status = 0 + } + + os.Remove(saveRes.Fname) + result.Output = outbuf.String() + result.Stderr = errbuf.String() + return +} + +func (a *App) SaveShellScript(hostKey, dbKey, collKey, script string, temp bool) (result SaveShellScriptResult) { hosts, err := a.Hosts() if err != nil { + runtime.LogWarningf(a.ctx, "Shell: could not get hosts: %s", err.Error()) result.ErrorTitle = "Could not get hosts" result.ErrorDescription = err.Error() return @@ -36,10 +75,12 @@ func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result host, hostFound := hosts[hostKey] if !hostFound { + runtime.LogWarningf(a.ctx, "Shell: host %s does not exist", host) result.ErrorTitle = "The specified host does not seem to exist" return } + result.Host = host id, err := uuid.NewRandom() if err != nil { runtime.LogErrorf(a.ctx, "Shell: failed to generate a UUID: %s", err.Error()) @@ -48,14 +89,35 @@ func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result return } - dirname := path.Join(a.Env.DataDirectory, "Shell Scripts") - fname := path.Join(dirname, fmt.Sprintf("%s.mongosh.js", id.String())) + if temp { + dirname, err := os.MkdirTemp(os.TempDir(), "rolens-script") + if err != nil { + runtime.LogErrorf(a.ctx, "Shell: failed to create temporary directory: %s", err.Error()) + result.ErrorTitle = "Could not generate temporary directory for script" + result.ErrorDescription = err.Error() + return + } + result.Fname = path.Join(dirname, fmt.Sprintf("%s.mongosh.js", id.String())) + } else { + result.Fname, err = runtime.SaveFileDialog(a.ctx, runtime.SaveDialogOptions{ + DefaultFilename: "New Script.js", + DefaultDirectory: path.Join(a.Env.DataDirectory, "Shell Scripts"), + Title: "Save MongoDB Shell Script", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "MongoDB Shell Script (*.js)", + Pattern: "*.js", + }, + }, + }) - 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 + if err != nil { + runtime.LogErrorf(a.ctx, "Shell: failed to save script: %s", err.Error()) + result.ErrorTitle = "Could not save shell script" + result.ErrorDescription = err.Error() + return + } } scriptHeader := fmt.Sprintf("// Namespace: %s.%s\n", dbKey, collKey) @@ -77,35 +139,46 @@ func (a *App) ExecuteShellScript(hostKey, dbKey, collKey, script string) (result scriptHeader = scriptHeader + fmt.Sprintf("coll = db.getCollection('%s');\n", collKey) } - scriptHeader = scriptHeader + "\n// Start of user script\n" - script = scriptHeader + script + scriptHeader = scriptHeader + "\n" + script = scriptHeader + strings.TrimLeft(strings.TrimRight(script, " \t\n"), "\n") - if err := os.WriteFile(fname, []byte(script), os.ModePerm); err != nil { - runtime.LogWarningf(a.ctx, "Shell: failed to write script to %s", err.Error()) + if err := os.WriteFile(result.Fname, []byte(script), os.ModePerm); err != nil { + runtime.LogWarningf(a.ctx, "Shell: failed to write script to %s: %s", result.Fname, err.Error()) result.ErrorTitle = "Could not create temporary script file" result.ErrorDescription = err.Error() return } - var outbuf, errbuf strings.Builder - cmd := exec.Command("mongosh", "--file", fname, host.URI) - cmd.Stdout = &outbuf - cmd.Stderr = &errbuf - err = cmd.Run() - - 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 = "mongosh failure" - result.ErrorDescription = err.Error() - return - } else { - result.Status = 0 - } - - os.Remove(fname) - result.Output = outbuf.String() - result.Stderr = errbuf.String() return } + +func (a *App) OpenShellScript() string { + dir := path.Join(a.Env.DataDirectory, "Shell Scripts") + os.MkdirAll(dir, os.ModePerm) + + fname, err := runtime.OpenFileDialog(a.ctx, runtime.OpenDialogOptions{ + DefaultDirectory: path.Join(a.Env.DataDirectory, "Shell Scripts"), + Title: "Load a MongoDB Shell Script", + CanCreateDirectories: true, + Filters: []runtime.FileFilter{ + { + DisplayName: "MongoDB Shell Script (*.js)", + Pattern: "*.js", + }, + }, + }) + + if err != nil { + runtime.LogWarningf(a.ctx, "Shell: error opening script: %s", err.Error()) + return "" + } + + script, err := os.ReadFile(fname) + + if err != nil { + runtime.LogWarningf(a.ctx, "Shell: error reading script %s: %s", fname, err.Error()) + return "" + } + + return string(script) +}