// Copyright (c) Tailscale Inc & AUTHORS // SPDX-License-Identifier: BSD-3-Clause import { render, Component } from "preact" 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: "NoState" } #goPanicTimeout?: number render() { const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state let goPanicDisplay if (goPanicError) { goPanicDisplay = ( ) } let urlDisplay if (browseToURL) { urlDisplay = } let machineAuthInstructions if (ipnState === "NeedsMachineAuth") { machineAuthInstructions = (
An administrator needs to approve this device.
) } const lockedOut = netMap?.lockedOut let lockedOutInstructions if (lockedOut) { lockedOutInstructions = (

This instance of Tailscale Connect needs to be signed, due to {" "}tailnet lock{" "} being enabled on this domain.

Run the following command on a device with a trusted tailnet lock key:

tailscale lock sign {netMap.self.nodeKey}

) } let ssh if (ipn && ipnState === "Running" && netMap && !lockedOut) { ssh = } return ( <>
{goPanicDisplay}
{urlDisplay} {machineAuthInstructions} {lockedOutInstructions} {ssh}
) } 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 === "NeedsLogin") { ipn?.login() } else if (["Running", "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) => { if (this.state.ipnState === "Running") { // Ignore URL requests if we're already running -- it's most likely an // SSH check mode trigger and we already linkify the displayed URL // in the terminal. return } 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 { return new Promise((resolve) => { render( (app ? resolve(app) : undefined)} />, document.body ) }) }