mirror of
https://github.com/tailscale/tailscale.git
synced 2025-04-03 23:05:50 +00:00
150 lines
4.5 KiB
TypeScript
150 lines
4.5 KiB
TypeScript
![]() |
import cx from "classnames"
|
|||
|
import React, { useCallback, useState } from "react"
|
|||
|
import { AuthResponse, AuthType } from "src/hooks/auth"
|
|||
|
import { NodeData } from "src/hooks/node-data"
|
|||
|
import { ReactComponent as ChevronDown } from "src/icons/chevron-down.svg"
|
|||
|
import { ReactComponent as Eye } from "src/icons/eye.svg"
|
|||
|
import { ReactComponent as User } from "src/icons/user.svg"
|
|||
|
import Popover from "src/ui/popover"
|
|||
|
import ProfilePic from "src/ui/profile-pic"
|
|||
|
|
|||
|
export default function LoginToggle({
|
|||
|
node,
|
|||
|
auth,
|
|||
|
newSession,
|
|||
|
}: {
|
|||
|
node: NodeData
|
|||
|
auth: AuthResponse
|
|||
|
newSession: () => Promise<void>
|
|||
|
}) {
|
|||
|
const [open, setOpen] = useState<boolean>(false)
|
|||
|
|
|||
|
return (
|
|||
|
<Popover
|
|||
|
className="p-3 bg-white rounded-lg shadow flex flex-col gap-2 max-w-[317px]"
|
|||
|
content={
|
|||
|
<LoginPopoverContent node={node} auth={auth} newSession={newSession} />
|
|||
|
}
|
|||
|
side="bottom"
|
|||
|
align="end"
|
|||
|
open={open}
|
|||
|
onOpenChange={setOpen}
|
|||
|
asChild
|
|||
|
>
|
|||
|
{!auth.canManageNode ? (
|
|||
|
<button
|
|||
|
className={cx(
|
|||
|
"pl-3 py-1 bg-zinc-800 rounded-full flex justify-start items-center",
|
|||
|
{ "pr-1": auth.viewerIdentity, "pr-3": !auth.viewerIdentity }
|
|||
|
)}
|
|||
|
onClick={() => setOpen(!open)}
|
|||
|
>
|
|||
|
<Eye />
|
|||
|
<div className="text-white leading-snug ml-2 mr-1">Viewing</div>
|
|||
|
<ChevronDown className="stroke-white w-[15px] h-[15px]" />
|
|||
|
{auth.viewerIdentity && (
|
|||
|
<ProfilePic
|
|||
|
className="ml-2"
|
|||
|
size="medium"
|
|||
|
url={auth.viewerIdentity.profilePicUrl}
|
|||
|
/>
|
|||
|
)}
|
|||
|
</button>
|
|||
|
) : (
|
|||
|
<div
|
|||
|
className={cx(
|
|||
|
"w-[34px] h-[34px] p-1 rounded-full items-center inline-flex",
|
|||
|
{
|
|||
|
"bg-transparent": !open,
|
|||
|
"bg-neutral-300": open,
|
|||
|
}
|
|||
|
)}
|
|||
|
>
|
|||
|
<button onClick={() => setOpen(!open)}>
|
|||
|
<ProfilePic
|
|||
|
size="medium"
|
|||
|
url={auth.viewerIdentity?.profilePicUrl}
|
|||
|
/>
|
|||
|
</button>
|
|||
|
</div>
|
|||
|
)}
|
|||
|
</Popover>
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
function LoginPopoverContent({
|
|||
|
node,
|
|||
|
auth,
|
|||
|
newSession,
|
|||
|
}: {
|
|||
|
node: NodeData
|
|||
|
auth: AuthResponse
|
|||
|
newSession: () => Promise<void>
|
|||
|
}) {
|
|||
|
const handleSignInClick = useCallback(() => {
|
|||
|
if (auth.viewerIdentity) {
|
|||
|
newSession()
|
|||
|
} else {
|
|||
|
// Must be connected over Tailscale to log in.
|
|||
|
// If not already connected, reroute to the Tailscale IP
|
|||
|
// before sending user through check mode.
|
|||
|
window.location.href = `http://${node.IP}:5252/?check=now`
|
|||
|
}
|
|||
|
}, [node.IP, auth.viewerIdentity, newSession])
|
|||
|
|
|||
|
return (
|
|||
|
<>
|
|||
|
<div className="text-black text-sm font-medium leading-tight">
|
|||
|
{!auth.canManageNode ? "Viewing" : "Managing"}
|
|||
|
{auth.viewerIdentity && ` as ${auth.viewerIdentity.loginName}`}
|
|||
|
</div>
|
|||
|
{!auth.canManageNode &&
|
|||
|
(!auth.viewerIdentity || auth.authNeeded == AuthType.tailscale ? (
|
|||
|
<>
|
|||
|
<p className="text-neutral-500 text-xs">
|
|||
|
{auth.viewerIdentity ? (
|
|||
|
<>
|
|||
|
To make changes, sign in to confirm your identity. This extra
|
|||
|
step helps us keep your device secure.
|
|||
|
</>
|
|||
|
) : (
|
|||
|
<>
|
|||
|
You can see most of this device's details. To make changes,
|
|||
|
you need to sign in.
|
|||
|
</>
|
|||
|
)}
|
|||
|
</p>
|
|||
|
<button
|
|||
|
className={cx(
|
|||
|
"w-full px-3 py-2 bg-indigo-500 rounded shadow text-center text-white text-sm font-medium mt-2",
|
|||
|
{ "mb-2": auth.viewerIdentity }
|
|||
|
)}
|
|||
|
onClick={handleSignInClick}
|
|||
|
>
|
|||
|
{auth.viewerIdentity ? "Sign in to confirm identity" : "Sign in"}
|
|||
|
</button>
|
|||
|
</>
|
|||
|
) : (
|
|||
|
<p className="text-neutral-500 text-xs">
|
|||
|
You don’t have permission to make changes to this device, but you
|
|||
|
can view most of its details.
|
|||
|
</p>
|
|||
|
))}
|
|||
|
{auth.viewerIdentity && (
|
|||
|
<>
|
|||
|
<hr />
|
|||
|
<div className="flex items-center">
|
|||
|
<User className="flex-shrink-0" />
|
|||
|
<p className="text-neutral-500 text-xs ml-2">
|
|||
|
We recognize you because you are accessing this page from{" "}
|
|||
|
<span className="font-medium">
|
|||
|
{auth.viewerIdentity.nodeName || auth.viewerIdentity.nodeIP}
|
|||
|
</span>
|
|||
|
</p>
|
|||
|
</div>
|
|||
|
</>
|
|||
|
)}
|
|||
|
</>
|
|||
|
)
|
|||
|
}
|