Skip to content

Commit

Permalink
client/web: add serve/funnel view
Browse files Browse the repository at this point in the history
Adds a new view to the web client for managing serve/funnel.
The view is permissioned by the "serve" and "funnel" grants,
and allows for http/https/tcp proxy and plain text serving.

Updates #10261

Signed-off-by: Sonia Appasamy <sonia@tailscale.com>
  • Loading branch information
soniaappasamy committed Mar 5, 2024
1 parent 7912d76 commit f9b0628
Show file tree
Hide file tree
Showing 22 changed files with 1,451 additions and 156 deletions.
4 changes: 4 additions & 0 deletions client/web/auth.go
Expand Up @@ -281,6 +281,8 @@ const (
capFeatureSSH capFeature = "ssh" // grants peer SSH server management
capFeatureSubnets capFeature = "subnets" // grants peer subnet routes management
capFeatureExitNodes capFeature = "exitnodes" // grants peer ability to advertise-as and use exit nodes
capFeatureServe capFeature = "serve" // grants peer ability to share resources over Tailscale Serve
capFeatureFunnel capFeature = "funnel" // grants peer ability to share resources over Tailscale Funnel
capFeatureAccount capFeature = "account" // grants peer ability to turn on auto updates and log out of node
)

Expand All @@ -292,6 +294,8 @@ var validCaps []capFeature = []capFeature{
capFeatureSSH,
capFeatureSubnets,
capFeatureExitNodes,
capFeatureServe,
capFeatureFunnel,
capFeatureAccount,
}

Expand Down
1 change: 1 addition & 0 deletions client/web/package.json
Expand Up @@ -10,6 +10,7 @@
"dependencies": {
"@radix-ui/react-collapsible": "^1.0.3",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.5",
"@radix-ui/react-popover": "^1.0.6",
"classnames": "^2.3.1",
"react": "^18.2.0",
Expand Down
28 changes: 26 additions & 2 deletions client/web/src/api.ts
Expand Up @@ -3,7 +3,7 @@

import { useCallback } from "react"
import useToaster from "src/hooks/toaster"
import { ExitNode, NodeData, SubnetRoute } from "src/types"
import { ExitNode, NodeData, ServeData, SubnetRoute } from "src/types"
import { assertNever } from "src/utils/util"
import { MutatorOptions, SWRConfiguration, useSWRConfig } from "swr"
import { noExitNode, runAsExitNode } from "./hooks/exit-nodes"
Expand All @@ -20,6 +20,8 @@ type APIType =
| { action: "update-prefs"; data: LocalPrefsData }
| { action: "update-routes"; data: SubnetRoute[] }
| { action: "update-exit-node"; data: ExitNode }
| { action: "patch-serve-item"; data: ServeData } // add or update
| { action: "delete-serve-item"; data: ServeData }

/**
* POST /api/up data
Expand Down Expand Up @@ -239,6 +241,28 @@ export function useAPI() {
.catch(handlePostError("Failed to update exit node"))
}

/**
* "patch-serve-item" handles adding or updating an item in the
* node's serve config.
*/
case "patch-serve-item": {
// todo: report metric?
return apiFetch("/serve/items", "PATCH", t.data).catch(
handlePostError("Failed to update item")
)
}

/**
* "delete-serve-item" handles deleting an item in the node's
* serve config.
*/
case "delete-serve-item": {
// todo: report metric?
return apiFetch("/serve/items", "DELETE", t.data).catch(
handlePostError("Failed to delete item")
)
}

default:
assertNever(t)
}
Expand All @@ -263,7 +287,7 @@ let unraidCsrfToken: string | undefined // required for unraid POST requests (#8
*/
export function apiFetch<T>(
endpoint: string,
method: "GET" | "POST" | "PATCH",
method: "GET" | "POST" | "PATCH" | "DELETE",
body?: any
): Promise<T> {
const urlParams = new URLSearchParams(window.location.search)
Expand Down
4 changes: 2 additions & 2 deletions client/web/src/assets/icons/copy.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions client/web/src/assets/icons/globe.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions client/web/src/assets/icons/home.svg
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion client/web/src/components/address-copy-card.tsx
Expand Up @@ -125,7 +125,7 @@ function AddressRow({
"text-gray-900 group-hover:text-gray-600"
)}
>
<Copy className="w-4 h-4" />
<Copy className="w-4 h-4" stroke="#292828" />
</span>
</button>
</li>
Expand Down
9 changes: 6 additions & 3 deletions client/web/src/components/app.tsx
Expand Up @@ -8,11 +8,12 @@ import DeviceDetailsView from "src/components/views/device-details-view"
import DisconnectedView from "src/components/views/disconnected-view"
import HomeView from "src/components/views/home-view"
import LoginView from "src/components/views/login-view"
import ServeView from "src/components/views/serve-view"
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, canEdit } from "src/hooks/auth"
import { Feature, NodeData, featureDescription } from "src/types"
import { Feature, NodeData, featureLongName } from "src/types"
import Card from "src/ui/card"
import EmptyState from "src/ui/empty-state"
import LoadingDots from "src/ui/loading-dots"
Expand Down Expand Up @@ -70,7 +71,9 @@ function WebClient({
<FeatureRoute path="/ssh" feature="ssh" node={node}>
<SSHView readonly={!canEdit("ssh", auth)} node={node} />
</FeatureRoute>
{/* <Route path="/serve">Share local content</Route> */}
<FeatureRoute path="/serve" feature="serve" node={node}>
<ServeView node={node} auth={auth} />
</FeatureRoute>
<FeatureRoute path="/update" feature="auto-update" node={node}>
<UpdatingView
versionInfo={node.ClientVersion}
Expand Down Expand Up @@ -113,7 +116,7 @@ function FeatureRoute({
{!node.Features[feature] ? (
<Card className="mt-8">
<EmptyState
description={`${featureDescription(
description={`${featureLongName(
feature
)} not available on this device.`}
/>
Expand Down
13 changes: 7 additions & 6 deletions client/web/src/components/views/home-view.tsx
Expand Up @@ -122,12 +122,13 @@ export default function HomeView({
}
/>
)}
{/* TODO(sonia,will): hiding unimplemented settings pages until implemented */}
{/* <SettingsCard
link="/serve"
title="Share local content"
body="Share local ports, services, and content to your Tailscale network or to the broader internet."
/> */}
{node.Features["serve"] && (
<SettingsCard
link="/serve"
title="Share local content"
body="Share local ports, services, and content to your Tailscale network or to the broader internet."
/>
)}
</div>
</div>
)
Expand Down

0 comments on commit f9b0628

Please sign in to comment.