import { Icon } from "@iconify/react"; import { ArrowRight } from "lucide-react"; import { useEffect } from "react"; import { NavLink } from "react-router"; import Button from "~/components/Button"; import Card from "~/components/Card"; import Link from "~/components/Link"; import Options from "~/components/Options"; import StatusCircle from "~/components/StatusCircle"; import { Machine } from "~/types"; import cn from "~/utils/cn"; import { useLiveData } from "~/utils/live-data"; import log from "~/utils/log"; import toast from "~/utils/toast"; import type { Route } from "./+types/onboarding"; export async function loader({ request, context }: Route.LoaderArgs) { const session = await context.sessions.auth(request); // Try to determine the OS split between Linux, Windows, macOS, iOS, and Android // We need to convert this to a known value to return it to the client so we can // automatically tab to the correct download button. const userAgent = request.headers.get("user-agent"); const os = userAgent?.match(/(Linux|Windows|Mac OS X|iPhone|iPad|Android)/); let osValue = "linux"; switch (os?.[0]) { case "Windows": osValue = "windows"; break; case "Mac OS X": osValue = "macos"; break; case "iPhone": case "iPad": osValue = "ios"; break; case "Android": osValue = "android"; break; default: osValue = "linux"; break; } const api = context.hsApi.getRuntimeClient(session.api_key); let firstMachine: Machine | undefined; try { const nodes = await api.getNodes(); const node = nodes.find((n) => { // Tag-only nodes have no user if (!n.user || n.user.provider !== "oidc") { return false; } // For some reason, headscale makes providerID a url where the // last component is the subject, so we need to strip that out const subject = n.user.providerId?.split("/").pop(); if (!subject) { return false; } if (subject !== session.user.subject) { return false; } return true; }); firstMachine = node; } catch (e) { // If we cannot lookup nodes, we cannot proceed log.debug("api", "Failed to lookup nodes %o", e); } return { user: session.user, osValue, firstMachine, }; } export default function Page({ loaderData: { user, osValue, firstMachine }, }: Route.ComponentProps) { const { pause, resume } = useLiveData(); useEffect(() => { if (firstMachine) { pause(); } else { resume(); } }, [firstMachine]); const subject = user.email ? ( <> as {user.email} > ) : ( "with your OIDC provider" ); return (
Click this button to copy the command.{" "} View script source
Requires Windows 10 or later.
Requires macOS Big Sur 11.0 or later.
You can also download Tailscale on the{" "}
macOS App Store
{"."}
Requires iOS 15 or later.
Requires Android 8 or later.
{firstMachine.givenName}
{firstMachine.name}
IP Addresses
{firstMachine.ipAddresses.map((ip) => ({ip}
))}Waiting for your first device...