|
|
|
@ -4,43 +4,46 @@
|
|
|
|
|
|
|
|
|
|
import { Terminal } from "xterm"
|
|
|
|
|
|
|
|
|
|
export function showSSHPeers(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
|
|
|
|
const peersNode = document.getElementById("peers") as HTMLDivElement
|
|
|
|
|
peersNode.innerHTML = ""
|
|
|
|
|
export function showSSHForm(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
|
|
|
|
const formNode = document.getElementById("ssh-form") as HTMLDivElement
|
|
|
|
|
const noSSHNode = document.getElementById("no-ssh") as HTMLDivElement
|
|
|
|
|
|
|
|
|
|
const sshPeers = peers.filter((p) => p.tailscaleSSHEnabled)
|
|
|
|
|
if (!sshPeers.length) {
|
|
|
|
|
peersNode.textContent = "No machines have Tailscale SSH installed."
|
|
|
|
|
const sshPeers = peers.filter(
|
|
|
|
|
(p) => p.tailscaleSSHEnabled && p.online !== false
|
|
|
|
|
)
|
|
|
|
|
if (sshPeers.length == 0) {
|
|
|
|
|
formNode.classList.add("hidden")
|
|
|
|
|
noSSHNode.classList.remove("hidden")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
sshPeers.sort((a, b) => a.name.localeCompare(b.name))
|
|
|
|
|
|
|
|
|
|
for (const peer of sshPeers) {
|
|
|
|
|
const peerNode = document.createElement("div")
|
|
|
|
|
peerNode.className = "flex justify-between p-0.5 hover:bg-gray-100"
|
|
|
|
|
const nameNode = document.createElement("div")
|
|
|
|
|
nameNode.className = "font-mono"
|
|
|
|
|
nameNode.textContent = peer.name
|
|
|
|
|
peerNode.appendChild(nameNode)
|
|
|
|
|
|
|
|
|
|
const sshButtonNode = document.createElement("button")
|
|
|
|
|
sshButtonNode.className =
|
|
|
|
|
"py-1 px-2 rounded bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
|
|
|
|
|
sshButtonNode.addEventListener("click", function () {
|
|
|
|
|
ssh(peer.name, ipn)
|
|
|
|
|
})
|
|
|
|
|
sshButtonNode.textContent = "SSH"
|
|
|
|
|
peerNode.appendChild(sshButtonNode)
|
|
|
|
|
const selectNode = formNode.querySelector("select")!
|
|
|
|
|
selectNode.innerHTML = ""
|
|
|
|
|
for (const p of sshPeers) {
|
|
|
|
|
const option = document.createElement("option")
|
|
|
|
|
option.textContent = p.name.split(".")[0]
|
|
|
|
|
option.value = p.name
|
|
|
|
|
selectNode.appendChild(option)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
peersNode.appendChild(peerNode)
|
|
|
|
|
const usernameNode = formNode.querySelector(".username") as HTMLInputElement
|
|
|
|
|
formNode.onsubmit = (e) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
const hostname = selectNode.value
|
|
|
|
|
ssh(hostname, usernameNode.value, ipn)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
noSSHNode.classList.add("hidden")
|
|
|
|
|
formNode.classList.remove("hidden")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export function hideSSHPeers() {
|
|
|
|
|
const peersNode = document.getElementById("peers") as HTMLDivElement
|
|
|
|
|
peersNode.innerHTML = ""
|
|
|
|
|
export function hideSSHForm() {
|
|
|
|
|
const formNode = document.getElementById("ssh-form") as HTMLDivElement
|
|
|
|
|
formNode.classList.add("hidden")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function ssh(hostname: string, ipn: IPN) {
|
|
|
|
|
function ssh(hostname: string, username: string, ipn: IPN) {
|
|
|
|
|
const termContainerNode = document.createElement("div")
|
|
|
|
|
termContainerNode.className = "p-3"
|
|
|
|
|
document.body.appendChild(termContainerNode)
|
|
|
|
@ -64,15 +67,14 @@ function ssh(hostname: string, ipn: IPN) {
|
|
|
|
|
|
|
|
|
|
term.focus()
|
|
|
|
|
|
|
|
|
|
ipn.ssh(
|
|
|
|
|
hostname,
|
|
|
|
|
(input) => term.write(input),
|
|
|
|
|
(hook) => (onDataHook = hook),
|
|
|
|
|
term.rows,
|
|
|
|
|
term.cols,
|
|
|
|
|
() => {
|
|
|
|
|
ipn.ssh(hostname, username, {
|
|
|
|
|
writeFn: (input) => term.write(input),
|
|
|
|
|
setReadFn: (hook) => (onDataHook = hook),
|
|
|
|
|
rows: term.rows,
|
|
|
|
|
cols: term.cols,
|
|
|
|
|
onDone: () => {
|
|
|
|
|
term.dispose()
|
|
|
|
|
termContainerNode.remove()
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|