From 1b1f8f2167cb0bb8cdf16291fe068f92ee381c59 Mon Sep 17 00:00:00 2001 From: Erik Brakkee Date: Mon, 5 Aug 2024 22:51:49 +0200 Subject: [PATCH] A lot of work in getting cut and paste from the UI to work properly. Wrote two web components. One for cut and paste in general, and another for code samples. --- cmd/templaterender/render.go | 12 ++++ pkg/server/templates/basepage.templ | 3 + pkg/server/templates/sessions.templ | 2 +- pkg/server/templates/usage.templ | 105 +++++++++++++++------------- pkg/server/templates/usageinputs.go | 2 +- render.sh | 3 +- static/js/copy-to-clipboard.js | 76 ++++++++++++++++++++ static/js/custom.js | 2 + 8 files changed, 152 insertions(+), 53 deletions(-) create mode 100644 static/js/copy-to-clipboard.js create mode 100644 static/js/custom.js diff --git a/cmd/templaterender/render.go b/cmd/templaterender/render.go index 55b0bf9..a414ea7 100644 --- a/cmd/templaterender/render.go +++ b/cmd/templaterender/render.go @@ -51,9 +51,21 @@ func main() { return templates2.UsageTab(access) } + usageInputs := templates2.UsageInputs{ + Id: "myid", + SshKeys: []string{"a", "b"}, + ErrorMessages: []string{}, + RemoteShells: map[string]bool{templates2.POWERSHELL: true}, + LocalShells: nil, + } + shellUsage := func() templ.Component { + return templates2.ShellUsageTab(access, usageInputs) + } + render(dir, "fullindex.html", fullindex) render(dir, "index.html", templates2.AboutTab) render(dir, "usage.html", usage) + render(dir, "shellusage.html", shellUsage) render(dir, "downloads.html", templates2.Downloads) render(dir, "sessions-none.html", func() templ.Component { diff --git a/pkg/server/templates/basepage.templ b/pkg/server/templates/basepage.templ index d0cb04d..57a7d15 100644 --- a/pkg/server/templates/basepage.templ +++ b/pkg/server/templates/basepage.templ @@ -16,10 +16,13 @@ templ BasePage(tab int) { + + + Converge diff --git a/pkg/server/templates/sessions.templ b/pkg/server/templates/sessions.templ index 6886cc9..f8ba748 100644 --- a/pkg/server/templates/sessions.templ +++ b/pkg/server/templates/sessions.templ @@ -91,4 +91,4 @@ templ SessionsTab(state *models.State, loc *time.Location) { @BasePage(4) { @Sessions(state, loc) } -} \ No newline at end of file +} diff --git a/pkg/server/templates/usage.templ b/pkg/server/templates/usage.templ index 1ea00ea..37ae488 100644 --- a/pkg/server/templates/usage.templ +++ b/pkg/server/templates/usage.templ @@ -13,32 +13,31 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) { }

Downloading and running the agent

-

This is what you run on a remote server, typically in your continuous integration job.

if usageInputs.RemoteShells[BASH] { -
{addSshKeys(BASH, usageInputs.SshKeys)}
-        curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent > agent{`
-        chmod 755 agent
-        `}./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
-        rm -f agent
-        `}
+ @templ.Raw(addSshKeys(BASH, usageInputs.SshKeys))
+ curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent > agent
+ chmod 755 agent
+ ./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}
+ rm -f agent
+
} if usageInputs.RemoteShells[CMD] { -
{addSshKeys(CMD, usageInputs.SshKeys)}
-        curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe{`
-        `}agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
-        del agent.exe
-        `}
+ @templ.Raw(addSshKeys(CMD, usageInputs.SshKeys))
+ curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe
+ agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}
+ del agent.exe
+
} if usageInputs.RemoteShells[POWERSHELL] { -
{addSshKeys(POWERSHELL, usageInputs.SshKeys)}
-        curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe{`
-        `}agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
-        del agent.exe
-        `}
+ @templ.Raw(addSshKeys(POWERSHELL, usageInputs.SshKeys))
+ curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe
+ agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}
+ del agent.exe
+
}

@@ -61,10 +60,13 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) { by the same user that started the agent.

-
{`
-          `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}"  { access.Username }{"@localhost"}   {`
-          `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" { access.Username }{"@localhost"}   {`
-          `}
+ + ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" { access.Username }{"@localhost"} + + + sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" { access.Username }{"@localhost"} + +

This requires the wsproxy utility which is available in the downloads section. This utility must be downloaded @@ -76,49 +78,44 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) { way to connect. In this method, a local port forwarder is started that forwards a local port to the webserver. Then you can start an ssh client that connects to the local tcp port.

-
{`
-         `}ssh -oServerAliveInterval=10 -p 10000  { access.Username }{"@localhost"}   {`
-         `}sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}   {`
-         `}
+ ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} + sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}

This requires the tcptows utility which is available in the downloads section. The utility must be started beforehand using:

-
{`
-         `}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}   {`
-         `}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}   {`
-         `}
+ tcptows 10000 ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}

Working with the agent

if usageInputs.RemoteShells[BASH] { -
{`
-        # cd back to the agent directory
-        cd $agentdir
-
-        # prevent logout when last user exits
-        touch $agentdir/.hold
-       `}
+ + # cd back to the agent directory
+ cd $agentdir
+
+ # prevent logout when last user exits
+ touch $agentdir/.hold
+
} if usageInputs.RemoteShells[CMD] { -
{`
-        # cd back to the agent directory
-        cd %agentdir%
-
-        # prevent logout when last user exits
-        echo > %agentdir%\.hold
-        `}
+ + # cd back to the agent directory
+ cd %agentdir%
+
+ # prevent logout when last user exits
+ echo > %agentdir%\.hold
+
} if usageInputs.RemoteShells[POWERSHELL] { -
{`
-        # cd back to the agent directory
-        cd $env:agentdir
-
-        # prevent logout when last user exits
-        $null > $env:agentdir\.hold
-        `}
+ + # cd back to the agent directory
+ cd $env:agentdir
+
+ # prevent logout when last user exits
+ $null > $env:agentdir\.hold
+
} if usageInputs.RemoteShells[CMD] || usageInputs.RemoteShells[POWERSHELL] {

@@ -145,6 +142,7 @@ templ Usage(access models.ConvergeAccess) {

Usage

+ + + `; + static IN_ACTIVE = CopyToClipboardComponent.STYLE + `
copy
`; + static ACTIVE = CopyToClipboardComponent.STYLE + `
copied
`; + + constructor() { + super(); + this.attachShadow({ mode: 'open' }); + } + + connectedCallback() { + this.render(CopyToClipboardComponent.IN_ACTIVE); + this.addEventListeners(); + } + + render(value) { + this.shadowRoot.innerHTML = value; + } + + addEventListeners() { + this.addEventListener('click', this.copyToClipboard.bind(this)); + } + + copyToClipboard() { + const fromElement = document.getElementById(this.getAttribute('from')); + if (fromElement) { + navigator.clipboard.writeText(fromElement.innerText) + .then(() => { + console.log('Content copied to clipboard'); + this.render(CopyToClipboardComponent.ACTIVE) + setTimeout((target) => { + this.render(CopyToClipboardComponent.IN_ACTIVE) + }, 3000, this); + }) + .catch(err => { + console.error('Failed to copy: ', err); + }); + } else { + console.error('Element not found'); + } + } +} + + + +customElements.define('copy-to-clipboard', CopyToClipboardComponent); + + + + +class CodeSampleComponent extends HTMLElement { + connectedCallback() { + this.render(); + } + + render() { + let id = this.getAttribute("id"); + let html = "
"; + html += "

" + this.innerHTML + "

"; + console.log("html=" + html); + this.innerHTML = html; + } +} + +customElements.define('code-sample', CodeSampleComponent); + diff --git a/static/js/custom.js b/static/js/custom.js new file mode 100644 index 0000000..06db985 --- /dev/null +++ b/static/js/custom.js @@ -0,0 +1,2 @@ + +import './copy-to-clipboard.js'; \ No newline at end of file