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:
parent
51be117200
commit
1b1f8f2167
@ -51,9 +51,21 @@ func main() {
|
|||||||
return templates2.UsageTab(access)
|
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, "fullindex.html", fullindex)
|
||||||
render(dir, "index.html", templates2.AboutTab)
|
render(dir, "index.html", templates2.AboutTab)
|
||||||
render(dir, "usage.html", usage)
|
render(dir, "usage.html", usage)
|
||||||
|
render(dir, "shellusage.html", shellUsage)
|
||||||
render(dir, "downloads.html", templates2.Downloads)
|
render(dir, "downloads.html", templates2.Downloads)
|
||||||
|
|
||||||
render(dir, "sessions-none.html", func() templ.Component {
|
render(dir, "sessions-none.html", func() templ.Component {
|
||||||
|
@ -16,10 +16,13 @@ templ BasePage(tab int) {
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="stylesheet" href="../static/css/bootstrap.min.css"
|
<link rel="stylesheet" href="../static/css/bootstrap.min.css"
|
||||||
crossorigin="anonymous">
|
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">
|
<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.1.9.12.js"></script>
|
||||||
<script src="../static/js/htmx.ws.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>
|
<title>Converge</title>
|
||||||
</head>
|
</head>
|
||||||
|
@ -91,4 +91,4 @@ templ SessionsTab(state *models.State, loc *time.Location) {
|
|||||||
@BasePage(4) {
|
@BasePage(4) {
|
||||||
@Sessions(state, loc)
|
@Sessions(state, loc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,32 +13,31 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
<h2>Downloading and running the agent</h2>
|
<h2>Downloading and running the agent</h2>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This is what you run on a remote server, typically in your continuous integration job.
|
This is what you run on a remote server, typically in your continuous integration job.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
if usageInputs.RemoteShells[BASH] {
|
if usageInputs.RemoteShells[BASH] {
|
||||||
<pre>{addSshKeys(BASH, usageInputs.SshKeys)}
|
<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{`
|
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent > agent <br/>
|
||||||
chmod 755 agent
|
chmod 755 agent <br/>
|
||||||
`}./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
|
./agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}<br/>
|
||||||
rm -f agent
|
rm -f agent <br/>
|
||||||
`}</pre>
|
</code-sample>
|
||||||
}
|
}
|
||||||
if usageInputs.RemoteShells[CMD] {
|
if usageInputs.RemoteShells[CMD] {
|
||||||
<pre>{addSshKeys(CMD, usageInputs.SshKeys)}
|
<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{`
|
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe <br/>
|
||||||
`}agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
|
agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
|
||||||
del agent.exe
|
del agent.exe <br/>
|
||||||
`}</pre>
|
</code-sample>
|
||||||
}
|
}
|
||||||
if usageInputs.RemoteShells[POWERSHELL] {
|
if usageInputs.RemoteShells[POWERSHELL] {
|
||||||
<pre>{addSshKeys(POWERSHELL, usageInputs.SshKeys)}
|
<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{`
|
curl --fail-with-body http{access.Secure}://{access.BaseUrl}/downloads/agent.exe > agent.exe <br/>
|
||||||
`}agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl}{`
|
agent --id {usageInputs.Id} ws{access.Secure}://{access.BaseUrl} <br/>
|
||||||
del agent.exe
|
del agent.exe <br/>
|
||||||
`}</pre>
|
</code-sample>
|
||||||
}
|
}
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
@ -61,10 +60,13 @@ templ AgentUsage(access models.ConvergeAccess, usageInputs UsageInputs) {
|
|||||||
by the same user that started the agent.
|
by the same user that started the agent.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<pre>{`
|
<code-sample id="ssh-connect">
|
||||||
`}ssh -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"} {`
|
</code-sample>
|
||||||
`}</pre>
|
<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
|
<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
|
<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
|
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.
|
to the webserver. Then you can start an ssh client that connects to the local tcp port.
|
||||||
</p>
|
</p>
|
||||||
<pre>{`
|
<code-sample id="tcptows-ssh">ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}</code-sample>
|
||||||
`}ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {`
|
<code-sample id="tcptows-sftp">sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}</code-sample>
|
||||||
`}sftp -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {`
|
|
||||||
`}</pre>
|
|
||||||
|
|
||||||
<p>This requires the <code>tcptows</code> utility which is available in the
|
<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
|
<a href="downloads.html">downloads</a> section. The utility must be started beforehand
|
||||||
using:
|
using:
|
||||||
</p>
|
</p>
|
||||||
<pre>{`
|
<code-sample id="tcptows-ssh">tcptows 10000 ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id}</code-sample>
|
||||||
`}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id} {`
|
|
||||||
`}tcptows ws{access.Secure}://{access.BaseUrl}/client/{usageInputs.Id} {`
|
|
||||||
`}</pre>
|
|
||||||
|
|
||||||
<h2>Working with the agent</h2>
|
<h2>Working with the agent</h2>
|
||||||
|
|
||||||
if usageInputs.RemoteShells[BASH] {
|
if usageInputs.RemoteShells[BASH] {
|
||||||
<pre>{`
|
<code-sammple id="session-bash">
|
||||||
# cd back to the agent directory
|
# cd back to the agent directory <br/>
|
||||||
cd $agentdir
|
cd $agentdir <br/>
|
||||||
|
<br/>
|
||||||
# prevent logout when last user exits
|
# prevent logout when last user exits <br/>
|
||||||
touch $agentdir/.hold
|
touch $agentdir/.hold<br/>
|
||||||
`}</pre>
|
</code-sammple>
|
||||||
|
|
||||||
}
|
}
|
||||||
if usageInputs.RemoteShells[CMD] {
|
if usageInputs.RemoteShells[CMD] {
|
||||||
<pre>{`
|
<code-sample id="session-cmd">
|
||||||
# cd back to the agent directory
|
# cd back to the agent directory <br/>
|
||||||
cd %agentdir%
|
cd %agentdir% <br/>
|
||||||
|
<br/>
|
||||||
# prevent logout when last user exits
|
# prevent logout when last user exits <br/>
|
||||||
echo > %agentdir%\.hold
|
echo > %agentdir%\.hold <br/>
|
||||||
`}</pre>
|
</code-sample>
|
||||||
}
|
}
|
||||||
if usageInputs.RemoteShells[POWERSHELL] {
|
if usageInputs.RemoteShells[POWERSHELL] {
|
||||||
<pre>{`
|
<code-sample id="session-powershell">
|
||||||
# cd back to the agent directory
|
# cd back to the agent directory <br/>
|
||||||
cd $env:agentdir
|
cd $env:agentdir <br/>
|
||||||
|
<br/>
|
||||||
# prevent logout when last user exits
|
# prevent logout when last user exits <br/>
|
||||||
$null > $env:agentdir\.hold
|
$null > $env:agentdir\.hold <br/>
|
||||||
`}</pre>
|
</code-sample>
|
||||||
}
|
}
|
||||||
if usageInputs.RemoteShells[CMD] || usageInputs.RemoteShells[POWERSHELL] {
|
if usageInputs.RemoteShells[CMD] || usageInputs.RemoteShells[POWERSHELL] {
|
||||||
<p>
|
<p>
|
||||||
@ -145,6 +142,7 @@ templ Usage(access models.ConvergeAccess) {
|
|||||||
<div>
|
<div>
|
||||||
<h1>Usage</h1>
|
<h1>Usage</h1>
|
||||||
|
|
||||||
|
<!-- TODO Investigate how to do this properly with bootstrap -->
|
||||||
<style>
|
<style>
|
||||||
.minimal-width {
|
.minimal-width {
|
||||||
width: 1%;
|
width: 1%;
|
||||||
@ -308,3 +306,10 @@ templ UsageTab(access models.ConvergeAccess) {
|
|||||||
@Usage(access)
|
@Usage(access)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
templ ShellUsageTab(access models.ConvergeAccess, usageInputs UsageInputs) {
|
||||||
|
@BasePage(2) {
|
||||||
|
@ShellUsage(access, usageInputs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -40,7 +40,7 @@ func addSshKeys(shell string, keys []string) string {
|
|||||||
if index == 0 {
|
if index == 0 {
|
||||||
operator = ">"
|
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)
|
operator)
|
||||||
}
|
}
|
||||||
return res + " "
|
return res + " "
|
||||||
|
@ -5,10 +5,11 @@ make
|
|||||||
cd bin
|
cd bin
|
||||||
|
|
||||||
mkdir -p html
|
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/css/ html/static/css/
|
||||||
rsync -av ../static/js/ html/static/js/
|
rsync -av ../static/js/ html/static/js/
|
||||||
|
rsync -av ../static/icons/ html/static/icons/
|
||||||
mkdir -p html/docs
|
mkdir -p html/docs
|
||||||
rsync -av agent* wsproxy* wstotcp* html/downloads
|
rsync -av agent* wsproxy* wstotcp* html/downloads
|
||||||
./templaterender
|
./templaterender
|
||||||
|
76
static/js/copy-to-clipboard.js
Normal file
76
static/js/copy-to-clipboard.js
Normal 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
2
static/js/custom.js
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
|
||||||
|
import './copy-to-clipboard.js';
|
Loading…
Reference in New Issue
Block a user