You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tailscale/cmd/tsconnect/src/app.tsx

125 lines
3.2 KiB
TypeScript

// 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 { render, Component } from "preact"
import { IPNState } from "./wasm_js"
import { URLDisplay } from "./url-display"
import { Header } from "./header"
import { GoPanicDisplay } from "./go-panic-display"
import { SSH } from "./ssh"
type AppState = {
ipn?: IPN
ipnState: IPNState
netMap?: IPNNetMap
browseToURL?: string
goPanicError?: string
}
class App extends Component<{}, AppState> {
state: AppState = { ipnState: IPNState.NoState }
#goPanicTimeout?: number
render() {
const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state
let goPanicDisplay
if (goPanicError) {
goPanicDisplay = (
<GoPanicDisplay error={goPanicError} dismiss={this.clearGoPanic} />
)
}
let urlDisplay
if (browseToURL) {
urlDisplay = <URLDisplay url={browseToURL} />
}
let machineAuthInstructions
if (ipnState === IPNState.NeedsMachineAuth) {
machineAuthInstructions = (
<div class="container mx-auto px-4 text-center">
An administrator needs to authorize this device.
</div>
)
}
let ssh
if (ipn && ipnState === IPNState.Running && netMap) {
ssh = <SSH netMap={netMap} ipn={ipn} />
}
return (
<>
<Header state={ipnState} ipn={ipn} />
{goPanicDisplay}
<div class="flex-grow flex flex-col justify-center overflow-hidden">
{urlDisplay}
{machineAuthInstructions}
{ssh}
</div>
</>
)
}
runWithIPN(ipn: IPN) {
this.setState({ ipn }, () => {
ipn.run({
notifyState: this.handleIPNState,
notifyNetMap: this.handleNetMap,
notifyBrowseToURL: this.handleBrowseToURL,
notifyPanicRecover: this.handleGoPanic,
})
})
}
handleIPNState = (state: IPNState) => {
const { ipn } = this.state
this.setState({ ipnState: state })
if (state == IPNState.NeedsLogin) {
ipn?.login()
} else if ([IPNState.Running, IPNState.NeedsMachineAuth].includes(state)) {
this.setState({ browseToURL: undefined })
}
}
handleNetMap = (netMapStr: string) => {
const netMap = JSON.parse(netMapStr) as IPNNetMap
if (DEBUG) {
console.log("Received net map: " + JSON.stringify(netMap, null, 2))
}
this.setState({ netMap })
}
handleBrowseToURL = (url: string) => {
this.setState({ browseToURL: url })
}
handleGoPanic = (error: string) => {
if (DEBUG) {
console.error("Go panic", error)
}
this.setState({ goPanicError: error })
if (this.#goPanicTimeout) {
window.clearTimeout(this.#goPanicTimeout)
}
this.#goPanicTimeout = window.setTimeout(this.clearGoPanic, 10000)
}
clearGoPanic = () => {
window.clearTimeout(this.#goPanicTimeout)
this.#goPanicTimeout = undefined
this.setState({ goPanicError: undefined })
}
}
export function renderApp(): Promise<App> {
return new Promise((resolve) => {
render(
<App ref={(app) => (app ? resolve(app) : undefined)} />,
document.body
)
})
}