-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Reduces the amount of boilerplate to render the UI and makes it easier to respond to state changes (e.g. machine getting authorized, netmap changing, etc.) Preact adds ~13K to our bundle size (5K after Brotli) thus is a neglibible size contribution. We mitigate the delay in rendering the UI by having a static placeholder in the HTML. Required bumping the esbuild version to pick up evanw/esbuild#2349, which makes it easier to support Preact's JSX code generation. Fixes #5137 Fixes #5273 Signed-off-by: Mihai Parparita <mihai@tailscale.com>
- Loading branch information
Showing
19 changed files
with
407 additions
and
320 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
// 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 | ||
) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
// 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. | ||
|
||
export function GoPanicDisplay({ | ||
error, | ||
dismiss, | ||
}: { | ||
error: string | ||
dismiss: () => void | ||
}) { | ||
return ( | ||
<div | ||
class="rounded bg-red-500 p-2 absolute top-2 right-2 text-white font-bold text-right cursor-pointer" | ||
onClick={dismiss} | ||
> | ||
Tailscale has encountered an error. | ||
<div class="text-sm font-normal">Click to reload</div> | ||
</div> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
// 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 { IPNState } from "./wasm_js" | ||
|
||
export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) { | ||
const stateText = STATE_LABELS[state] | ||
|
||
let logoutButton | ||
if (state === IPNState.Running) { | ||
logoutButton = ( | ||
<button | ||
class="button bg-gray-500 border-gray-500 text-white hover:bg-gray-600 hover:border-gray-600 ml-2 font-bold" | ||
onClick={() => ipn?.logout()} | ||
> | ||
Logout | ||
</button> | ||
) | ||
} | ||
return ( | ||
<div class="bg-gray-100 border-b border-gray-200 pt-4 pb-2"> | ||
<header class="container mx-auto px-4 flex flex-row items-center"> | ||
<h1 class="text-3xl font-bold grow">Tailscale Connect</h1> | ||
<div class="text-gray-600">{stateText}</div> | ||
{logoutButton} | ||
</header> | ||
</div> | ||
) | ||
} | ||
|
||
const STATE_LABELS = { | ||
[IPNState.NoState]: "Initializing…", | ||
[IPNState.InUseOtherUser]: "In-use by another user", | ||
[IPNState.NeedsLogin]: "Needs login", | ||
[IPNState.NeedsMachineAuth]: "Needs authorization", | ||
[IPNState.Stopped]: "Stopped", | ||
[IPNState.Starting]: "Starting…", | ||
[IPNState.Running]: "Running", | ||
} as const |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.