client/web: handle offline exit nodes

If the currently selected exit node is offline, render the exit node
selector in red with an error message. Update exit nodes in the dropdown
to indicate if they are offline, and don't allow them to be selected.

This also updates some older color values to use the new colors.

Updates #10261

Signed-off-by: Will Norris <will@tailscale.com>
pull/10471/head
Will Norris 11 months ago committed by Will Norris
parent b144391c06
commit f5989f317f

@ -46,11 +46,13 @@ export default function ExitNodeSelector({
none, // not using exit nodes
advertising, // advertising as exit node
using, // using another exit node
offline, // selected exit node node is offline
] = useMemo(
() => [
selected.ID === noExitNode.ID,
selected.ID === runAsExitNode.ID,
selected.ID !== noExitNode.ID && selected.ID !== runAsExitNode.ID,
!selected.Online,
],
[selected]
)
@ -74,23 +76,31 @@ export default function ExitNodeSelector({
>
<div
className={cx(
"p-1.5 rounded-md border flex items-stretch gap-1.5",
"rounded-md",
{
"border-gray-200": none,
"bg-amber-600 border-amber-600": advertising,
"bg-blue-500 border-blue-500": using,
"bg-red-600": offline,
},
className
)}
>
<div
className={cx("p-1.5 rounded-md border flex items-stretch gap-1.5", {
"border-gray-200": none,
"bg-yellow-300 border-yellow-300": advertising && !offline,
"bg-blue-500 border-blue-500": using && !offline,
"bg-red-500 border-red-500": offline,
})}
>
<button
className={cx("flex-1 px-2 py-1.5 rounded-[1px]", {
"bg-white": none,
"hover:bg-gray-100": none && !disabled,
"bg-orange-600": advertising,
"hover:bg-orange-400": advertising && !disabled,
"bg-blue-500": using,
"hover:bg-blue-400": using && !disabled,
"bg-yellow-300": advertising && !offline,
"hover:bg-yellow-200": advertising && !offline && !disabled,
"bg-blue-500": using && !offline,
"hover:bg-blue-400": using && !offline && !disabled,
"bg-red-500": offline,
"hover:bg-red-400": offline && !disabled,
})}
onClick={() => setOpen(!open)}
disabled={disabled}
@ -101,7 +111,7 @@ export default function ExitNodeSelector({
{ "bg-opacity-70 text-white": advertising || using }
)}
>
Exit node
Exit node{offline && " offline"}
</p>
<div className="flex items-center">
<p
@ -131,8 +141,9 @@ export default function ExitNodeSelector({
{!disabled && (advertising || using) && (
<button
className={cx("px-3 py-2 rounded-sm text-white", {
"bg-orange-400": advertising,
"bg-blue-400": using,
"bg-yellow-200": advertising && !offline,
"bg-blue-400": using && !offline,
"bg-red-400": offline,
})}
onClick={(e) => {
e.preventDefault()
@ -144,6 +155,14 @@ export default function ExitNodeSelector({
</button>
)}
</div>
{offline && (
<p className="text-white p-3">
The selected exit node is currently offline. Your internet traffic
is blocked until you disable the exit node or select a different
one.
</p>
)}
</div>
</Popover>
)
}
@ -254,10 +273,16 @@ function ExitNodeSelectorItem({
return (
<button
key={node.ID}
className="w-full px-4 py-2 flex justify-between items-center cursor-pointer hover:bg-gray-100"
className={cx(
"w-full px-4 py-2 flex justify-between items-center cursor-pointer hover:bg-gray-100",
{
"text-gray-400 cursor-not-allowed": !node.Online,
}
)}
onClick={onSelect}
disabled={!node.Online}
>
<div>
<div className="w-full">
{node.Location && (
<>
<CountryFlag code={node.Location.CountryCode} />{" "}
@ -265,7 +290,8 @@ function ExitNodeSelectorItem({
)}
<span className="leading-snug">{node.Name}</span>
</div>
{isSelected && <Check />}
{node.Online || <span className="leading-snug">Offline</span>}
{isSelected && <Check className="ml-1" />}
</button>
)
}

@ -222,8 +222,10 @@ export function trimDNSSuffix(s: string, tailnetDNSName: string): string {
return s
}
export const noExitNode: ExitNode = { ID: "NONE", Name: "None" }
// Neither of these are really "online", but setting this makes them selectable.
export const noExitNode: ExitNode = { ID: "NONE", Name: "None", Online: true }
export const runAsExitNode: ExitNode = {
ID: "RUNNING",
Name: "Run as exit node…",
Online: true,
}

@ -738,6 +738,7 @@ func (s *Server) serveGetExitNodes(w http.ResponseWriter, r *http.Request) {
ID: ps.ID,
Name: ps.DNSName,
Location: ps.Location,
Online: ps.Online,
})
}
writeJSON(w, exitNodes)

Loading…
Cancel
Save