client/web: add visual indication for exit node pending approval (#10532)

Add visual indication when running as an exit node prior to receiving
admin approval.

Updates https://github.com/tailscale/tailscale/issues/10261

Signed-off-by: Mario Minardi <mario@tailscale.com>
Co-authored-by: Sonia Appasamy <sonia@tailscale.com>
pull/10557/head
Mario Minardi 12 months ago committed by GitHub
parent e9f203d747
commit 763b9daa84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -92,7 +92,8 @@ export function useAPI() {
<MutateDataType, FetchDataType = any>(
key: string,
fetch: Promise<FetchDataType>,
optimisticData: (current: MutateDataType) => MutateDataType
optimisticData: (current: MutateDataType) => MutateDataType,
revalidate?: boolean // optionally specify whether to run final revalidation (step 3)
): Promise<FetchDataType | undefined> => {
const options: MutatorOptions = {
/**
@ -105,6 +106,7 @@ export function useAPI() {
*/
populateCache: false,
optimisticData,
revalidate: revalidate,
}
return mutate(key, fetch, options)
},
@ -226,8 +228,12 @@ export function useAPI() {
...old,
UsingExitNode: Boolean(body.UseExitNode) ? t.data : undefined,
AdvertisingExitNode: Boolean(body.AdvertiseExitNode),
AdvertisingExitNodeApproved: Boolean(body.AdvertiseExitNode)
? true // gets updated in revalidation
: old.AdvertisingExitNodeApproved,
}
}
},
false // skip final revalidation
)
.then(() => metrics.forEach((m) => incrementMetric(m)))
.catch(handlePostError("Failed to update exit node"))

@ -14,6 +14,7 @@ import useExitNodes, {
import { ExitNode, NodeData } from "src/types"
import Popover from "src/ui/popover"
import SearchInput from "src/ui/search-input"
import { useSWRConfig } from "swr"
export default function ExitNodeSelector({
className,
@ -27,7 +28,14 @@ export default function ExitNodeSelector({
const api = useAPI()
const [open, setOpen] = useState<boolean>(false)
const [selected, setSelected] = useState<ExitNode>(toSelectedExitNode(node))
const [pending, setPending] = useState<boolean>(false)
const { mutate } = useSWRConfig() // allows for global mutation
useEffect(() => setSelected(toSelectedExitNode(node)), [node])
useEffect(() => {
setPending(
node.AdvertisingExitNode && node.AdvertisingExitNodeApproved === false
)
}, [node])
const handleSelect = useCallback(
(n: ExitNode) => {
@ -35,9 +43,18 @@ export default function ExitNodeSelector({
if (n.ID === selected.ID) {
return // no update
}
// Eager clear of pending state to avoid UI oddities
if (n.ID !== runAsExitNode.ID) {
setPending(false)
}
api({ action: "update-exit-node", data: n })
// refresh data after short timeout to pick up any pending approval updates
setTimeout(() => {
mutate("/data")
}, 1000)
},
[api, selected]
[api, mutate, selected.ID]
)
const [
@ -52,7 +69,7 @@ export default function ExitNodeSelector({
selected.ID !== noExitNode.ID && selected.ID !== runAsExitNode.ID,
!selected.Online,
],
[selected]
[selected.ID, selected.Online]
)
return (
@ -61,6 +78,7 @@ export default function ExitNodeSelector({
"rounded-md",
{
"bg-red-600": offline,
"bg-yellow-400": pending,
},
className
)}
@ -160,6 +178,12 @@ export default function ExitNodeSelector({
blocked until you disable the exit node or select a different one.
</p>
)}
{pending && (
<p className="text-white p-3">
Pending approval to run as exit node. This device won't be usable as
an exit node until then.
</p>
)}
</div>
)
}

@ -15,6 +15,7 @@ export type NodeData = {
KeyExpired: boolean
UsingExitNode?: ExitNode
AdvertisingExitNode: boolean
AdvertisingExitNodeApproved: boolean
AdvertisedRoutes?: SubnetRoute[]
TUNMode: boolean
IsSynology: boolean

@ -591,6 +591,7 @@ type nodeData struct {
UsingExitNode *exitNode
AdvertisingExitNode bool
AdvertisingExitNodeApproved bool // whether running this node as an exit node has been approved by an admin
AdvertisedRoutes []subnetRoute // excludes exit node routes
RunningSSHServer bool
@ -693,6 +694,8 @@ func (s *Server) serveGetNodeData(w http.ResponseWriter, r *http.Request) {
return p == route
})
}
data.AdvertisingExitNodeApproved = routeApproved(exitNodeRouteV4) || routeApproved(exitNodeRouteV6)
for _, r := range prefs.AdvertiseRoutes {
if r == exitNodeRouteV4 || r == exitNodeRouteV6 {
data.AdvertisingExitNode = true

Loading…
Cancel
Save