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.
This commit is contained in:
Erik Brakkee 2024-08-05 22:51:49 +02:00
parent 02914ae40f
commit 7e60e23df1
8 changed files with 152 additions and 53 deletions

View File

@ -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 {

View File

@ -16,10 +16,13 @@ templ BasePage(tab int) {
<meta charset="UTF-8">
<link rel="stylesheet" href="../static/css/bootstrap.min.css"
crossorigin="anonymous">
<link rel="stylesheet" href="../static/icons/font/bootstrap-icons.css">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<script src="../static/js/htmx.1.9.12.js"></script>
<script src="../static/js/htmx.ws.1.9.12.js"></script>
<script type="module" src="../static/js/custom.js"></script>
<title>Converge</title>
</head>

View File

@ -13,32 +13,31 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
}
<h2>Downloading and running the agent</h2>
<p>
This is what you run on a remote server, typically in your continuous integration job.
</p>
if usageInputs.RemoteShells[BASH] {
<pre>{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
`}</pre>
<code-sample id="run-agent-bash">@templ.Raw(addSshKeys(BASH, usageInputs.SshKeys)) <br/>
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent > agent <br/>
chmod 755 agent <br/>
./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}<br/>
rm -f agent <br/>
</code-sample>
}
if usageInputs.RemoteShells[CMD] {
<pre>{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
`}</pre>
<code-sample id="run-agent-cmd">@templ.Raw(addSshKeys(CMD, usageInputs.SshKeys)) <br/>
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe <br/>
agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
del agent.exe <br/>
</code-sample>
}
if usageInputs.RemoteShells[POWERSHELL] {
<pre>{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
`}</pre>
<code-sample id="run-agent-powershell">@templ.Raw(addSshKeys(POWERSHELL, usageInputs.SshKeys)) <br/>
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe <br/>
agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
del agent.exe <br/>
</code-sample>
}
<p>
@ -61,10 +60,13 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
by the same user that started the agent.
</p>
<pre>{`
`}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"} {`
`}</pre>
<code-sample id="ssh-connect">
ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" { access.Username }{"@localhost"}
</code-sample>
<code-sample id="sftp-connect">
sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}" { access.Username }{"@localhost"}
</code-sample>
<p>This requires the <code>wsproxy</code> utility which is available in the
<a href="downloads.html">downloads</a> 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.
</p>
<pre>{`
`}ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {`
`}sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {`
`}</pre>
<code-sample id="tcptows-ssh">ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}</code-sample>
<code-sample id="tcptows-sftp">sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}</code-sample>
<p>This requires the <code>tcptows</code> utility which is available in the
<a href="downloads.html">downloads</a> section. The utility must be started beforehand
using:
</p>
<pre>{`
`}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id} {`
`}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id} {`
`}</pre>
<code-sample id="tcptows-ssh">tcptows 10000 ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}</code-sample>
<h2>Working with the agent</h2>
if usageInputs.RemoteShells[BASH] {
<pre>{`
# cd back to the agent directory
cd $agentdir
# prevent logout when last user exits
touch $agentdir/.hold
`}</pre>
<code-sammple id="session-bash">
# cd back to the agent directory <br/>
cd $agentdir <br/>
<br/>
# prevent logout when last user exits <br/>
touch $agentdir/.hold<br/>
</code-sammple>
}
if usageInputs.RemoteShells[CMD] {
<pre>{`
# cd back to the agent directory
cd %agentdir%
# prevent logout when last user exits
echo > %agentdir%\.hold
`}</pre>
<code-sample id="session-cmd">
# cd back to the agent directory <br/>
cd %agentdir% <br/>
<br/>
# prevent logout when last user exits <br/>
echo > %agentdir%\.hold <br/>
</code-sample>
}
if usageInputs.RemoteShells[POWERSHELL] {
<pre>{`
# cd back to the agent directory
cd $env:agentdir
# prevent logout when last user exits
$null > $env:agentdir\.hold
`}</pre>
<code-sample id="session-powershell">
# cd back to the agent directory <br/>
cd $env:agentdir <br/>
<br/>
# prevent logout when last user exits <br/>
$null > $env:agentdir\.hold <br/>
</code-sample>
}
if usageInputs.RemoteShells[CMD] || usageInputs.RemoteShells[POWERSHELL] {
<p>
@ -145,6 +142,7 @@ templ Usage(access models.ConvergeAccess) {
<div>
<h1>Usage</h1>
<!-- TODO Investigate how to do this properly with bootstrap -->
<style>
.minimal-width {
width: 1%;
@ -308,3 +306,10 @@ templ UsageTab(access models.ConvergeAccess) {
@Usage(access)
}
}
templ ShellUsageTab(access models.ConvergeAccess, usageInputs UsageInputs) {
@BasePage(2) {
@ShellUsage(access, usageInputs)
}
}

View File

@ -40,7 +40,7 @@ func addSshKeys(shell string, keys []string) string {
if index == 0 {
operator = ">"
}
res += fmt.Sprintf(" echo %s%s%s %s .authorized_keys\n", quote, key, quote,
res += fmt.Sprintf(" echo %s%s%s %s .authorized_keys<br/>", quote, key, quote,
operator)
}
return res + " "

View File

@ -5,10 +5,11 @@ make
cd bin
mkdir -p html
mkdir -p html/static/css html/static/js html/docs
mkdir -p html/static/css html/static/js html/static/icons html/docs
rsync -av ../static/css/ html/static/css/
rsync -av ../static/js/ html/static/js/
rsync -av ../static/icons/ html/static/icons/
mkdir -p html/docs
rsync -av agent* wsproxy* wstotcp* html/downloads
./templaterender

View File

@ -0,0 +1,76 @@
class CopyToClipboardComponent extends HTMLElement {
static STYLE = `
<style>
.clickable {
cursor: pointer;
}
</style>
<link rel="stylesheet" href="../static/icons/font/bootstrap-icons.css">
`;
static IN_ACTIVE = CopyToClipboardComponent.STYLE + `<div class="clickable"><i class="bi bi-clipboard"></i> copy</div>`;
static ACTIVE = CopyToClipboardComponent.STYLE + `<div class="clickable"><i class="bi bi-clipboard-check"></i> copied</div>`;
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 = "<div class='small ps-5'><copy-to-clipboard from='" + id + "'></copy-to-clipboard></div>";
html += "<p class='font-monospace small ps-5' id='" +id + "' >" + this.innerHTML + "</p>";
console.log("html=" + html);
this.innerHTML = html;
}
}
customElements.define('code-sample', CodeSampleComponent);

2
static/js/custom.js Normal file
View File

@ -0,0 +1,2 @@
import './copy-to-clipboard.js';