mirror of
https://github.com/tailscale/tailscale.git
synced 2025-10-09 08:01:31 +00:00
client/web: show features based on platform support
Hiding/disabling UI features when not available on the running client. Updates #10261 Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
This commit is contained in:

committed by
Sonia Appasamy

parent
7d61b827e8
commit
7a4ba609d9
@@ -11,7 +11,11 @@ import SSHView from "src/components/views/ssh-view"
|
||||
import SubnetRouterView from "src/components/views/subnet-router-view"
|
||||
import { UpdatingView } from "src/components/views/updating-view"
|
||||
import useAuth, { AuthResponse } from "src/hooks/auth"
|
||||
import useNodeData, { NodeData } from "src/hooks/node-data"
|
||||
import useNodeData, {
|
||||
Feature,
|
||||
featureDescription,
|
||||
NodeData,
|
||||
} from "src/hooks/node-data"
|
||||
import { Link, Route, Router, Switch, useLocation } from "wouter"
|
||||
|
||||
export default function App() {
|
||||
@@ -63,27 +67,27 @@ function WebClient({
|
||||
<Route path="/details">
|
||||
<DeviceDetailsView readonly={!auth.canManageNode} node={data} />
|
||||
</Route>
|
||||
<Route path="/subnets">
|
||||
<FeatureRoute path="/subnets" feature="advertise-routes" node={data}>
|
||||
<SubnetRouterView
|
||||
readonly={!auth.canManageNode}
|
||||
node={data}
|
||||
nodeUpdaters={nodeUpdaters}
|
||||
/>
|
||||
</Route>
|
||||
<Route path="/ssh">
|
||||
</FeatureRoute>
|
||||
<FeatureRoute path="/ssh" feature="ssh" node={data}>
|
||||
<SSHView
|
||||
readonly={!auth.canManageNode}
|
||||
node={data}
|
||||
nodeUpdaters={nodeUpdaters}
|
||||
/>
|
||||
</Route>
|
||||
</FeatureRoute>
|
||||
<Route path="/serve">{/* TODO */}Share local content</Route>
|
||||
<Route path="/update">
|
||||
<FeatureRoute path="/update" feature="auto-update" node={data}>
|
||||
<UpdatingView
|
||||
versionInfo={data.ClientVersion}
|
||||
currentVersion={data.IPNVersion}
|
||||
/>
|
||||
</Route>
|
||||
</FeatureRoute>
|
||||
<Route>
|
||||
<h2 className="mt-8">Page not found</h2>
|
||||
</Route>
|
||||
@@ -93,6 +97,36 @@ function WebClient({
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* FeatureRoute renders a Route component,
|
||||
* but only displays the child view if the specified feature is
|
||||
* available for use on this node's platform. If not available,
|
||||
* a not allowed view is rendered instead.
|
||||
*/
|
||||
function FeatureRoute({
|
||||
path,
|
||||
node,
|
||||
feature,
|
||||
children,
|
||||
}: {
|
||||
path: string
|
||||
node: NodeData // TODO: once we have swr, just call useNodeData within FeatureView
|
||||
feature: Feature
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Route path={path}>
|
||||
{!node.Features[feature] ? (
|
||||
<h2 className="mt-8">
|
||||
{featureDescription(feature)} not available on this device.
|
||||
</h2>
|
||||
) : (
|
||||
children
|
||||
)}
|
||||
</Route>
|
||||
)
|
||||
}
|
||||
|
||||
function Header({
|
||||
node,
|
||||
auth,
|
||||
|
@@ -175,7 +175,7 @@ function ExitNodeSelectorInner({
|
||||
onSelect: (node: ExitNode) => void
|
||||
}) {
|
||||
const [filter, setFilter] = useState<string>("")
|
||||
const { data: exitNodes } = useExitNodes(node.TailnetName, filter)
|
||||
const { data: exitNodes } = useExitNodes(node, filter)
|
||||
const listRef = useRef<HTMLDivElement>(null)
|
||||
|
||||
const hasNodes = useMemo(
|
||||
|
@@ -50,9 +50,10 @@ export default function DeviceDetailsView({
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{node.ClientVersion &&
|
||||
!node.ClientVersion.RunningLatest &&
|
||||
!readonly && (
|
||||
{node.Features["auto-update"] &&
|
||||
!readonly &&
|
||||
node.ClientVersion &&
|
||||
!node.ClientVersion.RunningLatest && (
|
||||
<UpdateAvailableNotification details={node.ClientVersion} />
|
||||
)}
|
||||
<div className="-mx-5 card">
|
||||
|
@@ -33,37 +33,44 @@ export default function HomeView({
|
||||
</div>
|
||||
<p className="text-gray-800 text-lg leading-[25.20px]">{node.IP}</p>
|
||||
</div>
|
||||
<ExitNodeSelector
|
||||
className="mb-5"
|
||||
node={node}
|
||||
nodeUpdaters={nodeUpdaters}
|
||||
disabled={readonly}
|
||||
/>
|
||||
{(node.Features["advertise-exit-node"] ||
|
||||
node.Features["use-exit-node"]) && (
|
||||
<ExitNodeSelector
|
||||
className="mb-5"
|
||||
node={node}
|
||||
nodeUpdaters={nodeUpdaters}
|
||||
disabled={readonly}
|
||||
/>
|
||||
)}
|
||||
<Link className="text-blue-500 font-medium leading-snug" to="/details">
|
||||
View device details →
|
||||
</Link>
|
||||
</div>
|
||||
<h2 className="mb-3">Settings</h2>
|
||||
<SettingsCard
|
||||
link="/subnets"
|
||||
className="mb-3"
|
||||
title="Subnet router"
|
||||
body="Add devices to your tailnet without installing Tailscale on them."
|
||||
/>
|
||||
<SettingsCard
|
||||
link="/ssh"
|
||||
className="mb-3"
|
||||
title="Tailscale SSH server"
|
||||
body="Run a Tailscale SSH server on this device and allow other devices in your tailnet to SSH into it."
|
||||
badge={
|
||||
node.RunningSSHServer
|
||||
? {
|
||||
text: "Running",
|
||||
icon: <div className="w-2 h-2 bg-emerald-500 rounded-full" />,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
{node.Features["advertise-routes"] && (
|
||||
<SettingsCard
|
||||
link="/subnets"
|
||||
className="mb-3"
|
||||
title="Subnet router"
|
||||
body="Add devices to your tailnet without installing Tailscale on them."
|
||||
/>
|
||||
)}
|
||||
{node.Features["ssh"] && (
|
||||
<SettingsCard
|
||||
link="/ssh"
|
||||
className="mb-3"
|
||||
title="Tailscale SSH server"
|
||||
body="Run a Tailscale SSH server on this device and allow other devices in your tailnet to SSH into it."
|
||||
badge={
|
||||
node.RunningSSHServer
|
||||
? {
|
||||
text: "Running",
|
||||
icon: <div className="w-2 h-2 bg-emerald-500 rounded-full" />,
|
||||
}
|
||||
: undefined
|
||||
}
|
||||
/>
|
||||
)}
|
||||
{/* TODO(sonia,will): hiding unimplemented settings pages until implemented */}
|
||||
{/* <SettingsCard
|
||||
link="/serve"
|
||||
|
Reference in New Issue
Block a user