2022-06-07 21:24:22 +00:00
|
|
|
// Copyright (c) 2022 Tailscale Inc & AUTHORS All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
|
|
|
|
|
|
|
import { Terminal } from "xterm"
|
|
|
|
|
2022-07-26 22:45:52 +00:00
|
|
|
export function showSSHPeers(peers: IPNNetMapPeerNode[], ipn: IPN) {
|
|
|
|
const peersNode = document.getElementById("peers") as HTMLDivElement
|
2022-06-07 21:24:22 +00:00
|
|
|
peersNode.innerHTML = ""
|
|
|
|
|
|
|
|
const sshPeers = peers.filter((p) => p.tailscaleSSHEnabled)
|
|
|
|
if (!sshPeers.length) {
|
|
|
|
peersNode.textContent = "No machines have Tailscale SSH installed."
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const peer of sshPeers) {
|
|
|
|
const peerNode = document.createElement("div")
|
2022-07-26 00:51:06 +00:00
|
|
|
peerNode.className = "flex justify-between p-0.5 hover:bg-gray-100"
|
2022-06-07 21:24:22 +00:00
|
|
|
const nameNode = document.createElement("div")
|
2022-07-26 00:51:06 +00:00
|
|
|
nameNode.className = "font-mono"
|
2022-06-07 21:24:22 +00:00
|
|
|
nameNode.textContent = peer.name
|
|
|
|
peerNode.appendChild(nameNode)
|
|
|
|
|
|
|
|
const sshButtonNode = document.createElement("button")
|
2022-07-26 00:51:06 +00:00
|
|
|
sshButtonNode.className =
|
|
|
|
"py-1 px-2 rounded bg-green-500 border-green-500 text-white hover:bg-green-600 hover:border-green-600"
|
2022-06-07 21:24:22 +00:00
|
|
|
sshButtonNode.addEventListener("click", function () {
|
|
|
|
ssh(peer.name, ipn)
|
|
|
|
})
|
|
|
|
sshButtonNode.textContent = "SSH"
|
|
|
|
peerNode.appendChild(sshButtonNode)
|
|
|
|
|
|
|
|
peersNode.appendChild(peerNode)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export function hideSSHPeers() {
|
2022-07-26 22:45:52 +00:00
|
|
|
const peersNode = document.getElementById("peers") as HTMLDivElement
|
2022-06-07 21:24:22 +00:00
|
|
|
peersNode.innerHTML = ""
|
|
|
|
}
|
|
|
|
|
2022-07-26 22:45:52 +00:00
|
|
|
function ssh(hostname: string, ipn: IPN) {
|
2022-06-07 21:24:22 +00:00
|
|
|
const termContainerNode = document.createElement("div")
|
2022-07-26 00:51:06 +00:00
|
|
|
termContainerNode.className = "p-3"
|
2022-06-07 21:24:22 +00:00
|
|
|
document.body.appendChild(termContainerNode)
|
|
|
|
|
|
|
|
const term = new Terminal({
|
|
|
|
cursorBlink: true,
|
|
|
|
})
|
|
|
|
term.open(termContainerNode)
|
|
|
|
|
|
|
|
// Cancel wheel events from scrolling the page if the terminal has scrollback
|
|
|
|
termContainerNode.addEventListener("wheel", (e) => {
|
|
|
|
if (term.buffer.active.baseY > 0) {
|
|
|
|
e.preventDefault()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2022-07-26 22:45:52 +00:00
|
|
|
let onDataHook: ((data: string) => void) | undefined
|
2022-06-07 21:24:22 +00:00
|
|
|
term.onData((e) => {
|
|
|
|
onDataHook?.(e)
|
|
|
|
})
|
|
|
|
|
|
|
|
term.focus()
|
|
|
|
|
|
|
|
ipn.ssh(
|
|
|
|
hostname,
|
|
|
|
(input) => term.write(input),
|
|
|
|
(hook) => (onDataHook = hook),
|
|
|
|
term.rows,
|
|
|
|
term.cols,
|
|
|
|
() => {
|
|
|
|
term.dispose()
|
|
|
|
termContainerNode.remove()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|