diff --git a/cmd/converge/converge.go b/cmd/converge/converge.go index bae64aa..5c4edf4 100644 --- a/cmd/converge/converge.go +++ b/cmd/converge/converge.go @@ -161,6 +161,9 @@ func main() { http.FileServer(http.Dir(downloadDir)))) http.HandleFunc("/", catchAllHandler) + // create usage generator + http.HandleFunc("/usage", generateCLIExammple) + // Start HTTP server fmt.Println("Rendez-vous server listening on :8000") log.Fatal(http.ListenAndServe(":8000", nil)) diff --git a/cmd/converge/convergeaccess.go b/cmd/converge/convergeaccess.go new file mode 100644 index 0000000..f69407f --- /dev/null +++ b/cmd/converge/convergeaccess.go @@ -0,0 +1,36 @@ +package main + +import ( + "converge/pkg/models" + "converge/pkg/server/converge" + "net/http" + "strings" +) + +func getConvergeAccess(r *http.Request, sshRemoteUser string) models.ConvergeAccess { + secure := "" + if r.TLS == nil { + secure = "" + } else { + secure = "s" + } + for _, header := range []string{"X-Forwarded-Proto", "X-Scheme", "X-Forwarded-Scheme"} { + values := r.Header.Values(header) + for _, value := range values { + if strings.ToLower(value) == "https" { + secure = "s" + } + } + } + + location, err := converge.GetUserLocation(r) + if err != nil { + panic(err) + } + return models.ConvergeAccess{ + Secure: secure, + HostPort: r.Host, + Location: location, + Username: sshRemoteUser, + } +} diff --git a/cmd/converge/pagehandler.go b/cmd/converge/pagehandler.go index e0909cc..bdc256e 100644 --- a/cmd/converge/pagehandler.go +++ b/cmd/converge/pagehandler.go @@ -1,34 +1,14 @@ package main import ( - "converge/pkg/server/converge" templates2 "converge/pkg/server/templates" "net/http" "os" - "strings" ) func pageHandler(w http.ResponseWriter, r *http.Request) { - secure := "" - if r.TLS == nil { - secure = "" - } else { - secure = "s" - } - for _, header := range []string{"X-Forwarded-Proto", "X-Scheme", "X-Forwarded-Scheme"} { - values := r.Header.Values(header) - for _, value := range values { - if strings.ToLower(value) == "https" { - secure = "s" - } - } - } username, _ := os.LookupEnv("CONVERGE_USERNAME") - - location, err := converge.GetUserLocation(r) - if err != nil { - panic(err) - } + access := getConvergeAccess(r, username) switch r.URL.Path { case "": @@ -37,12 +17,13 @@ func pageHandler(w http.ResponseWriter, r *http.Request) { fallthrough case "index.html": templates2.AboutTab().Render(r.Context(), w) + // TODO use contexts later. case "usage.html": - templates2.UsageTab(secure, r.Host, username).Render(r.Context(), w) + templates2.UsageTab(access).Render(r.Context(), w) case "downloads.html": templates2.DownloadsTab().Render(r.Context(), w) case "sessions.html": - templates2.SessionsTab(nil, location).Render(r.Context(), w) + templates2.SessionsTab(nil, access.Location).Render(r.Context(), w) default: http.NotFound(w, r) } diff --git a/cmd/converge/usage.go b/cmd/converge/usage.go new file mode 100644 index 0000000..63304ff --- /dev/null +++ b/cmd/converge/usage.go @@ -0,0 +1,24 @@ +package main + +import ( + "log" + "net/http" + "time" +) + +func generateCLIExammple(w http.ResponseWriter, r *http.Request) { + log.Println("usage: got ", r.URL.Path) + + err := r.ParseForm() + if err != nil { + http.Error(w, "Error parsing form", http.StatusBadRequest) + return + } + remote_shells := r.Form["remote-shell"] + local_shells := r.Form["local-shhell"] + keys := r.FormValue("ssh-keys") + log.Printf("remote_shells %v", remote_shells) + log.Printf("local_shells %v", local_shells) + log.Printf("ssh-keys %v", keys) + w.Write([]byte(time.Now().Format(time.DateTime))) +} diff --git a/cmd/templaterender/render.go b/cmd/templaterender/render.go index 01bffee..3ea7024 100644 --- a/cmd/templaterender/render.go +++ b/cmd/templaterender/render.go @@ -37,11 +37,18 @@ func main() { panic(err) } + access := models.ConvergeAccess{ + Secure: "s", + HostPort: "example.com", + Location: netherlands, + Username: "converge", + } + fullindex := func() templ.Component { - return templates2.Index("s", "example.com", "converge") + return templates2.Index(access) } usage := func() templ.Component { - return templates2.UsageTab("s", "example.com", "converge") + return templates2.UsageTab(access) } render(dir, "fullindex.html", fullindex) diff --git a/pkg/models/convergeaccess.go b/pkg/models/convergeaccess.go new file mode 100644 index 0000000..007c321 --- /dev/null +++ b/pkg/models/convergeaccess.go @@ -0,0 +1,11 @@ +package models + +import "time" + +type ConvergeAccess struct { + // 's" when secure, "" otherwise + Secure string + HostPort string + Location *time.Location + Username string +} diff --git a/pkg/server/templates/index.templ b/pkg/server/templates/index.templ index 8914d90..947e97e 100644 --- a/pkg/server/templates/index.templ +++ b/pkg/server/templates/index.templ @@ -1,9 +1,12 @@ package templates -templ Index(secure string, host string, username string) { +import "converge/pkg/models" + + +templ Index(access models.ConvergeAccess) { @BasePage(0) { @About() - @Usage(secure, host, username) + @Usage(access) @Downloads() } } diff --git a/pkg/server/templates/usage.templ b/pkg/server/templates/usage.templ index 252f746..4fe25d8 100644 --- a/pkg/server/templates/usage.templ +++ b/pkg/server/templates/usage.templ @@ -1,8 +1,62 @@ package templates -templ Usage(secure string, host string, username string) { +import "converge/pkg/models" + +templ Usage(access models.ConvergeAccess) { <div> - <h1>usage</h1> + + + <h1>Usage</h1> + + <style> + .minimal-width { + width: 1%; + white-space: nowrap; + } + </style> + + <form id="inputs" novalidate + hx-post="/usage" + method="post" + hx-trigger="load,input delay:500ms,change" + hx-target="#example-cli" + hx-on::after-request="saveFormToCookie()"> + <table class="table table-responsive"> + <tr> + <td class="minimal-width"><label for="rendez-vous-id">rendez-vous id</label></td> + <td> + <input id="rendez-vous-id" class="form-control" name="rendez-vous-id" type="text" maxlength="40"></input> + </td> + </tr> + <tr> + <td class="minimal-width"><label for="ssh-keys">public ssh keys</label></td> + <td> + <textarea id="ssh-keys" class="form-control autogrow" name="ssh-keys" rows="5"></textarea> + </td> + </tr> + <tr> + <td class="minimal-width"><label for="remote-shell">remote shells</label></td> + <td> + <input checked id="remote-shell-0" name="remote-shell" type="checkbox" value="*sh"> <label for="remote-shell-0">*.sh</label> + <input id="remote-shell-1" name="remote-shell" type="checkbox" value="cmd"> <label for="remote-shell-1">cmd.exe</label> + <input id="remote-shell-2" name="remote-shell" type="checkbox" value="cmd"> <label for="remote-shell-2">powerpoint.exe</label> + </td> + </tr> + <tr> + <td class="minimal-width"><label for="local-shell">local shell</label></td> + <td> + <input id="checked local-shell-0" name="local-shell" type="checkbox" value="*sh"> <label for="local-shell-0">*.sh</label> + <input id="local-shell-1" name="local-shell" type="checkbox" value="cmd"> <label for="local-shell-1">cmd.exe</label> + <input id="local-shell-2" name="local-shell" type="checkbox" value="cmd"> <label for="local-shell-2">powerpoint.exe</label> + </td> + </tr> + </table> + </form> + + <div id="example-cli"> + </div> + + <h1>usage old</h1> <h2>Continuous integration jobs</h2> @@ -12,14 +66,14 @@ templ Usage(secure string, host string, username string) { </p> <pre>{` # linux - `}curl http{secure}://{host}/static/agent > agent{` + `}curl http{access.Secure}://{access.HostPort}/static/agent > agent{` chmod 755 agent - `}./agent --id ID ws{secure}://{host}{` + `}./agent --id ID ws{access.Secure}://{access.HostPort}{` rm -f agent # windows - `}curl http{secure}://{host}/static/agent.exe > agent.exe{` - `}agent --id ID ws{secure}://{host}{` + `}curl http{access.Secure}://{access.HostPort}/static/agent.exe > agent.exe{` + `}agent --id ID ws{access.Secure}://{access.HostPort}{` del agent.exe `}</pre> <p> @@ -66,8 +120,8 @@ templ Usage(secure string, host string, username string) { <pre> {` - `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{secure}://{host}/client/ID" { username }{"@localhost"} {` - `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{secure}://{host}/client/ID" { username }{"@localhost"} {` + `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/ID" { access.Username }{"@localhost"} {` + `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/ID" { access.Username }{"@localhost"} {` `}</pre> <h2>Local clients: using SSH with a local TCP forwarding proxy</h2> @@ -91,8 +145,8 @@ templ Usage(secure string, host string, username string) { </p> <pre> {` - `}ssh -oServerAliveInterval=10 -p 10000 { username }{"@localhost"} {` - `}sftp -oServerAliveInterval=10 -oPort=10000 { username }{"@localhost"} {` + `}ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"} {` + `}sftp -oServerAliveInterval=10 -oPort=10000 { access.Username }{"@localhost"} {` `}</pre> <h2>Remote shell usage</h2> @@ -145,7 +199,7 @@ templ Usage(secure string, host string, username string) { <h2>Authentication</h2> <p> - The <code>{ username }</code> user above is configured in the + The <code>{ access.Username }</code> user above is configured in the Converge server and is communicated to the agent when the agent is started as well as the password. </p> @@ -168,13 +222,91 @@ templ Usage(secure string, host string, username string) { Note that on windows you should not used quotes. </p> + + <script> + function setCookie(name, value, days) { + let expires = ""; + if (days) { + const date = new Date(); + date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000)); + expires = "; expires=" + date.toUTCString(); + } + document.cookie = name + "=" + encodeURIComponent(value) + expires + "; path=/"; + } + + function getCookie(name) { + const nameEQ = name + "="; + const ca = document.cookie.split(';'); + for(let i=0; i < ca.length; i++) { + let c = ca[i]; + while (c.charAt(0)==' ') c = c.substring(1,c.length); + if (c.indexOf(nameEQ) == 0) return decodeURIComponent(c.substring(nameEQ.length,c.length)); + } + return null; + } + + function saveFormToCookie() { + const form = document.getElementById('inputs'); + const formData = new FormData(form); + for (let element of form.elements) { + if (element.id) { + console.log("Saving " + element.id) + if (element.type === 'checkbox') { + console.log("Checkbox " + element.checked) + setCookie(element.id, element.checked ? 'true' : 'false', 7); + } else if (element.type === 'radio') { + if (element.checked) { + setCookie(element.id, element.value, 7); + } + } else { + setCookie(element.id, element.value, 7); + } + } + } + } + + function loadFormFromCookie() { + const form = document.getElementById('inputs'); + for (let element of form.elements) { + if (element.id) { + const value = getCookie(element.id); + console.log("Loading " + element.id + " value: " + value) + if (value !== null) { + if (element.type === 'checkbox') { + element.checked = value === 'true'; + } else if (element.type === 'radio') { + element.checked = (element.value === value); + } else { + element.value = value; + } + } + } + } + } + + // Save form data to cookie on change + document.getElementById('inputs').addEventListener('change', saveFormToCookie); + + document.body.addEventListener('htmx:load', function(event) { + loadFormFromCookie(); + }); + + document.body.addEventListener('htmx:afterSettle', function(event) { + loadFormFromCookie(); + }); + + // when hx-boost=false + // Load form data from cookie on page load + //document.addEventListener('DOMContentLoaded', loadFormFromCookie); + </script> + </div> } -templ UsageTab(secure string, host string, username string) { +templ UsageTab(access models.ConvergeAccess) { @BasePage(2) { - @Usage(secure, host, username) + @Usage(access) } }