large parts of the usage is now dynamic.
Still need to generate instructions for authorized keys. A lot of troubleshooting for the form to cookie persistence.
This commit is contained in:
		
							parent
							
								
									91e1139881
								
							
						
					
					
						commit
						db44a20d5a
					
				| @ -3,11 +3,10 @@ package main | ||||
| import ( | ||||
| 	templates2 "converge/pkg/server/templates" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| ) | ||||
| 
 | ||||
| func pageHandler(w http.ResponseWriter, r *http.Request) { | ||||
| 	username, _ := os.LookupEnv("CONVERGE_USERNAME") | ||||
| 	username := getAgentSshUser() | ||||
| 	access := getConvergeAccess(r, username) | ||||
| 
 | ||||
| 	switch r.URL.Path { | ||||
|  | ||||
| @ -1,9 +1,12 @@ | ||||
| package main | ||||
| 
 | ||||
| import ( | ||||
| 	"converge/pkg/server/templates" | ||||
| 	"log" | ||||
| 	"math/rand" | ||||
| 	"net/http" | ||||
| 	"time" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| ) | ||||
| 
 | ||||
| func generateCLIExammple(w http.ResponseWriter, r *http.Request) { | ||||
| @ -14,11 +17,31 @@ func generateCLIExammple(w http.ResponseWriter, r *http.Request) { | ||||
| 		http.Error(w, "Error parsing form", http.StatusBadRequest) | ||||
| 		return | ||||
| 	} | ||||
| 	remote_shells := r.Form["remote-shell"] | ||||
| 	local_shells := r.Form["local-shhell"] | ||||
| 	ids := r.Form["rendez-vous-id"] | ||||
| 	id := "" | ||||
| 	if len(ids) > 0 { | ||||
| 		id = ids[0] | ||||
| 	} | ||||
| 	if id == "" { | ||||
| 		id = strconv.Itoa(rand.Int() % 1000000) | ||||
| 	} | ||||
| 	remoteShells := r.Form["remote-shell"] | ||||
| 	localShells := r.Form["local-shell"] | ||||
| 	keys := r.FormValue("ssh-keys") | ||||
| 	log.Printf("remote_shells %v", remote_shells) | ||||
| 	log.Printf("local_shells %v", local_shells) | ||||
| 	log.Printf("remote_shells %v", remoteShells) | ||||
| 	log.Printf("local_shells %v", localShells) | ||||
| 	log.Printf("ssh-keys %v", keys) | ||||
| 	w.Write([]byte(time.Now().Format(time.DateTime))) | ||||
| 
 | ||||
| 	access := getConvergeAccess(r, getAgentSshUser()) | ||||
| 
 | ||||
| 	usageInputs := templates.NewUsageInputs(id, remoteShells, localShells) | ||||
| 	err = templates.ShellUsage(access, usageInputs).Render(r.Context(), w) | ||||
| 	if err != nil { | ||||
| 		http.Error(w, err.Error(), 500) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func getAgentSshUser() string { | ||||
| 	username, _ := os.LookupEnv("CONVERGE_USERNAME") | ||||
| 	return username | ||||
| } | ||||
|  | ||||
| @ -39,6 +39,49 @@ templ About() { | ||||
|           When the timeout of a session is near the user is informed about this with messages | ||||
|           in the shell. | ||||
|       </p> | ||||
| 
 | ||||
|       <p>end-to-end encryoption</p> | ||||
|       <p> ssh keys</p> | ||||
|       <p>agent options </p> | ||||
|       <p>client access </p> | ||||
| 
 | ||||
|       <h2>Local clients: using ssh with a proxy command </h2> | ||||
| 
 | ||||
|       <p> | ||||
|         <code>wsproxy</code> is a command that can be used as a proxy command for SSH which performs the connection to the | ||||
|         remote server. This command needs to be downloaded only once (see <a href="downloads.html">downloads</a>). It does not depend on | ||||
|         the converge implementation but only on the websocket standards. Other tools that | ||||
|         provide a mapping of stdio to a websocket can also be used instead of wsproxy. | ||||
|       </p> | ||||
| 
 | ||||
|       <h2>Local clients: using SSH with a local TCP forwarding proxy</h2> | ||||
| 
 | ||||
|       <p> | ||||
|         This option is less convenient than the proxy command because it requires two separate | ||||
|         commands to execute. | ||||
|       </p> | ||||
| 
 | ||||
|       <p> | ||||
|          Local clients can connect using regular ssh and sftp commands through a tunnel that | ||||
|          translates a local TCP port to a websocket connection in converge. See | ||||
|          the <a href="downloads.html">downloads</a> section. | ||||
|          This runs a local client that allows SSH to port 10000 and connects to converge using | ||||
|          a websocket connection. | ||||
|      </p> | ||||
| 
 | ||||
|      <h2>Remote shell usage</h2> | ||||
| 
 | ||||
|      <p> | ||||
|        The agent supports a --shells command-line option by which a comma-separated | ||||
|        list of shells can be prepended to the default search path for shells, e.g. | ||||
|        <code>--shells zsh,csh,sh</code> (linux) or <code>cmd,powershell</code> for | ||||
|        windows. | ||||
|      </p> | ||||
| 
 | ||||
|      <p> | ||||
|        The agent sets a <coder>agentdir</coder> environment variable that points to | ||||
|        the directory where the agent is running. | ||||
|      </p> | ||||
|   </div> | ||||
| } | ||||
| 
 | ||||
|  | ||||
| @ -1 +1,5 @@ | ||||
| package templates | ||||
| 
 | ||||
| const BASH = "*.sh" | ||||
| const CMD = "cmd" | ||||
| const POWERSHELL = "powershell" | ||||
|  | ||||
| @ -2,10 +2,118 @@ package templates | ||||
| 
 | ||||
| import "converge/pkg/models" | ||||
| 
 | ||||
| 
 | ||||
| templ AgentUsage(access models.ConvergeAccess, shells map[string]bool, usageInputs UsageInputs) { | ||||
|     <div> | ||||
|     <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 shells[BASH] { | ||||
|         <pre>{` | ||||
|         `}curl http{access.Secure}://{access.HostPort}/static/agent > agent{` | ||||
|         chmod 755 agent | ||||
|         `}./agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{` | ||||
|         rm -f agent | ||||
|         `}</pre> | ||||
|     } | ||||
|     if shells[CMD] || shells[POWERSHELL]  { | ||||
|         <pre>{` | ||||
|         `}curl http{access.Secure}://{access.HostPort}/static/agent.exe > agent.exe{` | ||||
|         `}agent --id {usageInputs.Id} ws{access.Secure}://{access.HostPort}{` | ||||
|         del agent.exe | ||||
|         `}</pre> | ||||
|     } | ||||
| 
 | ||||
|     <p> | ||||
|         The agent has more command-line options than shown here. | ||||
|         Download the agent and run it without arguments to | ||||
|         see all options. | ||||
|     </p> | ||||
|     <p> | ||||
|         <b>Tip</b>: Run the above command locally in a similar shell (for instance in a | ||||
|         docker container) then try to connect to the shell using the commands below. If that | ||||
|         works, then you are all set. | ||||
|     </p> | ||||
| 
 | ||||
| 
 | ||||
|     <h2>Connecting to the agent</h2> | ||||
| 
 | ||||
|      <pre>{` | ||||
|           `}ssh -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}"  { access.Username }{"@localhost"}   {` | ||||
|           `}sftp -oServerAliveInterval=10 -oProxyCommand="wsproxy ws{access.Secure}://{access.HostPort}/client/{usageInputs.Id}" { access.Username }{"@localhost"}   {` | ||||
|           `}</pre> | ||||
| 
 | ||||
|     <h2>Working with the agent</h2> | ||||
| 
 | ||||
|      if shells[BASH] { | ||||
|         <pre>{` | ||||
|         # cd back to the agent directory | ||||
|         cd $agentdir | ||||
| 
 | ||||
|         # prevent logout when last user exits | ||||
|         touch $agentdir/.hold | ||||
|        `}</pre> | ||||
| 
 | ||||
|     } | ||||
|     if shells[CMD] { | ||||
|         <pre>{` | ||||
|         # cd back to the agent directory | ||||
|         cd %agentdir% | ||||
| 
 | ||||
|         # prevent logout when last user exits | ||||
|         echo > %agentdir%\.hold | ||||
|         `}</pre> | ||||
|     } | ||||
|     if shells[POWERSHELL] { | ||||
|         <pre>{` | ||||
|         # cd back to the agent directory | ||||
|         cd $env:agentdir | ||||
| 
 | ||||
|         # prevent logout when last user exits | ||||
|         $null > $env:agentdir\.hold | ||||
|         `}</pre> | ||||
|     } | ||||
|     if shells[CMD] || shells[POWERSHELL]  { | ||||
|         <p>windows</p> | ||||
|             <p> | ||||
|               NOTE: When running the agent on windows, an exit of the remote session using | ||||
|               exit in powershell or command prompt does not terminate the shell completely. | ||||
|               To terminate the client ssh session must be killed by closing the terminal. | ||||
|               Cleanup of remote processes on the agent appears to work properly despite this | ||||
|               problem. It is just that exit of the windows shell (powershell or command prompt) | ||||
|               is not detected properly. | ||||
|           </p> | ||||
|         } | ||||
|     </div> | ||||
| } | ||||
| 
 | ||||
| templ LocalShellUsage(access models.ConvergeAccess, shells map[string]bool, usageInput UsageInputs) { | ||||
|   <div> | ||||
|   if shells[BASH] { | ||||
|      <p>bash</p> | ||||
|   } | ||||
|   if shells[CMD] { | ||||
|   <p>cmd</p> | ||||
|   } | ||||
|   if shells[POWERSHELL] { | ||||
|   <p>powershell</p> | ||||
|   } | ||||
|   </div> | ||||
| } | ||||
| 
 | ||||
| templ ShellUsage(access models.ConvergeAccess, usageInputs UsageInputs) { | ||||
|   <div> | ||||
|   @AgentUsage(access, usageInputs.RemoteShells, usageInputs) | ||||
|   </div> | ||||
|   @LocalShellUsage(access, usageInputs.LocalShells, usageInputs) | ||||
| } | ||||
| 
 | ||||
| 
 | ||||
| templ Usage(access models.ConvergeAccess) { | ||||
|   <div> | ||||
| 
 | ||||
| 
 | ||||
|       <h1>Usage</h1> | ||||
| 
 | ||||
|       <style> | ||||
| @ -18,7 +126,7 @@ templ Usage(access models.ConvergeAccess) { | ||||
|       <form id="inputs" novalidate | ||||
|             hx-post="/usage" | ||||
|             method="post" | ||||
|             hx-trigger="load,input delay:500ms,change" | ||||
|             hx-trigger="load,input delay:500ms,change,formdataloaded" | ||||
|             hx-target="#example-cli" | ||||
|             hx-on::after-request="saveFormToCookie()"> | ||||
|           <table class="table table-responsive"> | ||||
| @ -35,19 +143,19 @@ templ Usage(access models.ConvergeAccess) { | ||||
|                  </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                   <td class="minimal-width"><label for="remote-shell">remote shells</label></td> | ||||
|                   <td class="minimal-width"><label for="remote-shell">agent environment</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> | ||||
|                       <input checked id="remote-shell-0" name="remote-shell" type="radio" value={BASH}> <label for="remote-shell-0">*.sh</label> | ||||
|                       <input id="remote-shell-1" name="remote-shell" type="radio" value={CMD}> <label for="remote-shell-1">command prompt</label> | ||||
|                       <input id="remote-shell-2" name="remote-shell" type="radio" value={POWERSHELL}> <label for="remote-shell-2">power shell</label> | ||||
|                   </td> | ||||
|               </tr> | ||||
|               <tr> | ||||
|                   <td class="minimal-width"><label for="local-shell">local shell</label></td> | ||||
|                   <td class="minimal-width"><label for="local-shell">local environment</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> | ||||
|                       <input id="checked local-shell-0" name="local-shell" type="radio" value={BASH}> <label for="local-shell-0">*.sh</label> | ||||
|                       <input id="local-shell-1" name="local-shell" type="radio" value={CMD}> <label for="local-shell-1">command prompt</label> | ||||
|                       <input id="local-shell-2" name="local-shell" type="radio" value={POWERSHELL}> <label for="local-shell-2">powershell</label> | ||||
|                   </td> | ||||
|               </tr> | ||||
|           </table> | ||||
| @ -56,159 +164,10 @@ templ Usage(access models.ConvergeAccess) { | ||||
|       <div id="example-cli"> | ||||
|       </div> | ||||
| 
 | ||||
|                       <h1>usage old</h1> | ||||
| 
 | ||||
|                       <h2>Continuous integration jobs</h2> | ||||
| 
 | ||||
|                       <p> | ||||
|                           In a | ||||
|                           continuous integration job, download the agent, chmod it and run it. | ||||
|                       </p> | ||||
|                       <pre>{` | ||||
|            # linux | ||||
|            `}curl http{access.Secure}://{access.HostPort}/static/agent > agent{` | ||||
|            chmod 755 agent | ||||
|            `}./agent --id ID ws{access.Secure}://{access.HostPort}{` | ||||
|            rm -f agent | ||||
| 
 | ||||
|            # windows | ||||
|            `}curl http{access.Secure}://{access.HostPort}/static/agent.exe > agent.exe{` | ||||
|            `}agent --id ID ws{access.Secure}://{access.HostPort}{` | ||||
|            del agent.exe | ||||
|           `}</pre> | ||||
|                       <p> | ||||
|                           Above, ID is a unique id for the job, the so-called rendez-cous ID. This should not conflict with IDs | ||||
|                           used by other agents. The ID is used for a rendez-vous between the end-user on a local system and | ||||
|                           the continuous integration job running on a build agent. If you don't specify an id, a random | ||||
|                           id will be generated. If you specify a duplicate ID, the server will generate a new one andd the | ||||
|                           agent will tell you what id to use. | ||||
|                           Clients can now connect to the Converge server with the ID to establish a connection to | ||||
|                           the CI job through Converge. | ||||
| 
 | ||||
|                           Communication between | ||||
|                           end-user and agent is encrypted using SSH and the rendez-vous server is unable to | ||||
|                           read the contents. The rendez-vous server is nothing more then a glorified bit pipe, | ||||
|                           simply transferring data between end-user SSH client and the agent which runs an | ||||
|                           embedded SSH server. | ||||
|                       </p> | ||||
| 
 | ||||
|                       <p> | ||||
|                           NOTE: When running the agent on windows, an exit of the remote session using | ||||
|                           exit in powershell or command prompt does not terminate the shell completely. | ||||
|                           To terminate the client ssh session must be killed by closing the terminal. | ||||
|                           Cleanup of remote processes on the agent appears to work properly despite this | ||||
|                           problem. It is just that exit of the windows shell (powershell or command prompt) | ||||
|                           is not detected properly. | ||||
|                       </p> | ||||
| 
 | ||||
|                       <p> | ||||
|                           The agent has more command-line options than shown here. | ||||
|                           Download the agent and run it without arguments to | ||||
|                           see all options. | ||||
|                       </p> | ||||
| 
 | ||||
|                       <h2>Local clients: using ssh with a proxy command </h2> | ||||
| 
 | ||||
|                       <p><code>wsproxy</code> is a command that can be used as a proxy command for SSH which performs the connection to the | ||||
|                           remote server. This command needs to be downloaded only once (see <a href="downloads.html">downloads</a>). It does not depend on | ||||
|                           the converge implementation but only on the websocket standards. Other tools that | ||||
|                           provide a mapping of stdio to a websocket can also be used instead of wsproxy. | ||||
|                       </p> | ||||
|                       <p> | ||||
|                           Next step is to run a local SSH or SFTP client: | ||||
|                       </p> | ||||
| 
 | ||||
|                       <pre> | ||||
|                                                                                                                               {` | ||||
|           `}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> | ||||
| 
 | ||||
|                       <p> | ||||
|                       This option is less convenient than the proxy command because it requires two separate | ||||
|                       commands to execute. | ||||
|                       </p> | ||||
| 
 | ||||
|                       <p> | ||||
|                           Local clients can connect using regular ssh and sftp commands through a tunnel that | ||||
|                           translates a local TCP port to a websocket connection in converge. See | ||||
|                           the <a href="downloads.html">downloads</a> section. | ||||
|                           This runs a local client that allows SSH to port 10000 and connects to converge using | ||||
|                           a websocket connection. | ||||
|                       </p> | ||||
| 
 | ||||
| 
 | ||||
|                       <p> | ||||
|                           Next step is to run a local SSH of SFTP client: | ||||
|                       </p> | ||||
| 
 | ||||
|                       <pre>                                                           {` | ||||
|           `}ssh -oServerAliveInterval=10 -p 10000 { access.Username }{"@localhost"}          {` | ||||
|           `}sftp -oServerAliveInterval=10 -oPort=10000 { access.Username }{"@localhost"}     {` | ||||
|           `}</pre> | ||||
| 
 | ||||
|                       <h2>Remote shell usage</h2> | ||||
| 
 | ||||
|                       <p> | ||||
|                       The agent supports a --shells command-line option by which a comma-separated | ||||
|                       list of shells can be prepended to the default search path for shells, e.g. | ||||
|                       <code>--shells zsh,csh,sh</code> (linux) or <code>cmd,powershell</code> for | ||||
|                       windows. | ||||
|                       </p> | ||||
| 
 | ||||
|                       <p> | ||||
|                       The agent sets a <coder>agentdir</coder> environment variable that points to | ||||
|                       the directory where the agent is running. | ||||
|                       </p> | ||||
| 
 | ||||
|                       <h3>Linux</h3> | ||||
| 
 | ||||
|                       <pre>{` | ||||
|             # cd back to the agent directory | ||||
|             cd $agentdir | ||||
| 
 | ||||
|             # prevent logout when last user exits | ||||
|             touch $agentdir/.hold | ||||
|                       `}</pre> | ||||
| 
 | ||||
|                       <pre> | ||||
|                       </pre> | ||||
| 
 | ||||
|                       <h3>Windows Command Prompt</h3> | ||||
| 
 | ||||
|                       <pre>{` | ||||
|             # cd back to the agent directory | ||||
|             cd %agentdir% | ||||
| 
 | ||||
|             # prevent logout when last user exits | ||||
|             echo > %agentdir%\.hold | ||||
|                      `}</pre> | ||||
| 
 | ||||
|                       <h3>Windows Powershell</h3> | ||||
| 
 | ||||
|                       <pre>{` | ||||
|             # cd back to the agent directory | ||||
|             cd $env:agentdir | ||||
| 
 | ||||
|             # prevent logout when last user exits | ||||
|             $null > $env:agentdir\.hold | ||||
|                      `}</pre> | ||||
| 
 | ||||
|                       <h2>Authentication</h2> | ||||
| 
 | ||||
|                       <p> | ||||
|                           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> | ||||
|                       <p> | ||||
|                           Another way to authenticate is through an .authorized_keys file in the | ||||
|                           same directory as where the agent is started. | ||||
| 
 | ||||
|                           This can be setup as follows before starting the agent: | ||||
|                       </p> | ||||
|                       <pre>                                         {` | ||||
|           `}# linux                                                 {` | ||||
|           `}echo "ssh-rsa dkddkdkkk a@b.c" > .authorized_keys       {` | ||||
| @ -250,13 +209,17 @@ templ Usage(access models.ConvergeAccess) { | ||||
|         const formData = new FormData(form); | ||||
|         for (let element of form.elements) { | ||||
|             if (element.id) { | ||||
|                 console.log("Saving " + element.id) | ||||
|                 //console.log("Saving " + element.id) | ||||
|                 if (element.type === 'checkbox') { | ||||
|                     console.log("Checkbox " + element.checked) | ||||
|                     //console.log("Checkbox " + element.checked) | ||||
|                     setCookie(element.id, element.checked ? 'true' : 'false', 7); | ||||
|                 } else if (element.type === 'radio') { | ||||
|                     if (element.checked) { | ||||
|                         //console.log("Set cookie " + element.id + " " + element.value) | ||||
|                         setCookie(element.id, element.value, 7); | ||||
|                     } else { | ||||
|                         //console.log("Set cookie " + element.id + " EMPTY") | ||||
|                         setCookie(element.id, "", 7) | ||||
|                     } | ||||
|                 } else { | ||||
|                     setCookie(element.id, element.value, 7); | ||||
| @ -266,32 +229,57 @@ templ Usage(access models.ConvergeAccess) { | ||||
|     } | ||||
| 
 | ||||
|     function loadFormFromCookie() { | ||||
|         console.log("Load form from cookie") | ||||
|         const form = document.getElementById('inputs'); | ||||
|         if (!form) { | ||||
|             return | ||||
|         } | ||||
|         updated = false | ||||
|         for (let element of form.elements) { | ||||
|             if (element.id) { | ||||
|                 const value = getCookie(element.id); | ||||
|                 console.log("Loading " + element.id + " value: " + value) | ||||
|                 //console.log("Loading " + element.id + " value: " + value) | ||||
|                 if (value !== null) { | ||||
|                     if (element.type === 'checkbox') { | ||||
|                         element.checked = value === 'true'; | ||||
|                         newvalue = value === "true" | ||||
|                         if (element.checked != newvalue) { | ||||
|                             console.log("Setting " + element.id + " checked " + newvalue) | ||||
|                             element.checked = newvalue | ||||
|                             updated = true | ||||
|                         } | ||||
|                     } else if (element.type === 'radio') { | ||||
|                         element.checked = (element.value === value); | ||||
|                         newvalue = element.value === value | ||||
|                         if (element.checked != newvalue) { | ||||
|                             console.log("Setting " + element.id + " selected " + newvalue) | ||||
|                             element.checked = newvalue; | ||||
|                             updated = true | ||||
|                         } | ||||
|                     } else { | ||||
|                         element.value = value; | ||||
|                         if (element.value != value) { | ||||
|                             console.log("Setting " + element.id + " " + element.value + " -> " + value) | ||||
|                             element.value = value; | ||||
|                             updated = true | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|         if (updated) { | ||||
|             console.log("Sending htmx event to trigger form") | ||||
|             htmx.trigger(form, 'formdataloaded') | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     // Save form data to cookie on change | ||||
|     document.getElementById('inputs').addEventListener('change', saveFormToCookie); | ||||
| 
 | ||||
|     document.body.addEventListener('htmx:load', function(event) { | ||||
|       loadFormFromCookie(); | ||||
|       //console.log("htmx:load") | ||||
|       //loadFormFromCookie(); | ||||
|     }); | ||||
| 
 | ||||
|     document.body.addEventListener('htmx:afterSettle', function(event) { | ||||
|       console.log("htmx:afterSettle") | ||||
|       loadFormFromCookie(); | ||||
|     }); | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										23
									
								
								pkg/server/templates/usageinputs.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								pkg/server/templates/usageinputs.go
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| package templates | ||||
| 
 | ||||
| type UsageInputs struct { | ||||
| 	Id           string | ||||
| 	SshKeys      []string | ||||
| 	RemoteShells map[string]bool | ||||
| 	LocalShells  map[string]bool | ||||
| } | ||||
| 
 | ||||
| func NewUsageInputs(id string, remoteShells []string, localShells []string) UsageInputs { | ||||
| 	inputs := UsageInputs{ | ||||
| 		Id:           id, | ||||
| 		RemoteShells: make(map[string]bool), | ||||
| 		LocalShells:  make(map[string]bool), | ||||
| 	} | ||||
| 	for _, remoteShell := range remoteShells { | ||||
| 		inputs.RemoteShells[remoteShell] = true | ||||
| 	} | ||||
| 	for _, localShell := range localShells { | ||||
| 		inputs.LocalShells[localShell] = true | ||||
| 	} | ||||
| 	return inputs | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user