From f4a2811afb3546a51654b29a1a92a83cfe98e44a Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Sat, 7 Mar 2026 15:12:50 -0500 Subject: [PATCH] chore: general cleanup/pr improvements --- app/layouts/shell.tsx | 3 +- app/routes/auth/pending-approval.tsx | 9 - .../machines/components/machine-row.tsx | 8 +- app/routes/machines/dialogs/move.tsx | 8 +- app/routes/machines/dialogs/new.tsx | 160 +++++++++--------- app/routes/machines/machine-actions.ts | 5 +- app/routes/machines/machine.tsx | 12 +- app/routes/settings/auth-keys/actions.ts | 42 ++--- .../settings/auth-keys/auth-key-row.tsx | 6 +- .../auth-keys/dialogs/add-auth-key.tsx | 8 +- app/routes/settings/auth-keys/overview.tsx | 28 +-- app/utils/user.ts | 6 - 12 files changed, 127 insertions(+), 168 deletions(-) diff --git a/app/layouts/shell.tsx b/app/layouts/shell.tsx index 90f4264..67861ec 100644 --- a/app/layouts/shell.tsx +++ b/app/layouts/shell.tsx @@ -47,9 +47,8 @@ export async function loader({ request, context }: Route.LoaderArgs) { configAvailable: context.hs.readable(), debug: context.config.debug, user: session.user, - uiAccess: check, access: { - ui: await context.sessions.check(request, Capabilities.ui_access), + ui: check, dns: await context.sessions.check(request, Capabilities.read_network), users: await context.sessions.check(request, Capabilities.read_users), policy: await context.sessions.check(request, Capabilities.read_policy), diff --git a/app/routes/auth/pending-approval.tsx b/app/routes/auth/pending-approval.tsx index b7031bf..fb5aaf0 100644 --- a/app/routes/auth/pending-approval.tsx +++ b/app/routes/auth/pending-approval.tsx @@ -1,10 +1,8 @@ -import { eq } from "drizzle-orm"; import { ClockIcon, LogOut, RefreshCw, UserCheck } from "lucide-react"; import { Form, redirect } from "react-router"; import Button from "~/components/Button"; import Card from "~/components/Card"; -import { users } from "~/server/db/schema"; import { Capabilities } from "~/server/web/roles"; import toast from "~/utils/toast"; @@ -24,18 +22,11 @@ export async function loader({ request, context }: Route.LoaderArgs) { return redirect("/machines"); } - const [user] = await context.db - .select() - .from(users) - .where(eq(users.sub, session.user.subject)) - .limit(1); - const url = context.config.headscale.public_url ?? context.config.headscale.url; return { user: session.user, url, - exists: !!user, }; } catch { return redirect("/login", { diff --git a/app/routes/machines/components/machine-row.tsx b/app/routes/machines/components/machine-row.tsx index d748404..7931524 100644 --- a/app/routes/machines/components/machine-row.tsx +++ b/app/routes/machines/components/machine-row.tsx @@ -2,8 +2,6 @@ import { ChevronDown, Copy } from "lucide-react"; import { useMemo } from "react"; import { Link } from "react-router"; -import type { User } from "~/types"; - import Chip from "~/components/Chip"; import Menu from "~/components/Menu"; import StatusCircle from "~/components/StatusCircle"; @@ -12,11 +10,13 @@ import { ExpiryTag } from "~/components/tags/Expiry"; import { HeadplaneAgentTag } from "~/components/tags/HeadplaneAgent"; import { SubnetTag } from "~/components/tags/Subnet"; import { TailscaleSSHTag } from "~/components/tags/TailscaleSSH"; +import type { User } from "~/types"; import cn from "~/utils/cn"; import * as hinfo from "~/utils/host-info"; import { PopulatedNode } from "~/utils/node-info"; import { formatTimeDelta } from "~/utils/time"; import toast from "~/utils/toast"; +import { getUserDisplayName } from "~/utils/user"; import MenuOptions from "./menu"; @@ -63,9 +63,7 @@ export default function MachineRow({ {node.givenName}

- {node.user - ? node.user.name || node.user.displayName || node.user.email || node.user.id - : "Tag-owned"} + {node.user ? getUserDisplayName(node.user) : "Tag-owned"}

{mapTagsToComponents(node, uiTags)} diff --git a/app/routes/machines/dialogs/move.tsx b/app/routes/machines/dialogs/move.tsx index 0cc41f6..bd852b1 100644 --- a/app/routes/machines/dialogs/move.tsx +++ b/app/routes/machines/dialogs/move.tsx @@ -1,9 +1,9 @@ import { Key, useState } from "react"; -import type { Machine, User } from "~/types"; - import Dialog from "~/components/Dialog"; import Select from "~/components/Select"; +import type { Machine, User } from "~/types"; +import { getUserDisplayName } from "~/utils/user"; interface MoveProps { machine: Machine; @@ -34,9 +34,7 @@ export default function Move({ machine, users, isOpen, setIsOpen }: MoveProps) { placeholder="Select a user" > {users.map((user) => ( - - {user.name || user.displayName || user.email || user.id} - + {getUserDisplayName(user)} ))} diff --git a/app/routes/machines/dialogs/new.tsx b/app/routes/machines/dialogs/new.tsx index bb9bddb..2960388 100644 --- a/app/routes/machines/dialogs/new.tsx +++ b/app/routes/machines/dialogs/new.tsx @@ -1,92 +1,86 @@ -import { Computer, FileKey2 } from 'lucide-react'; -import { useState } from 'react'; -import { useNavigate } from 'react-router'; -import Code from '~/components/Code'; -import Dialog from '~/components/Dialog'; -import Input from '~/components/Input'; -import Menu from '~/components/Menu'; -import Select from '~/components/Select'; -import type { User } from '~/types'; +import { Computer, FileKey2 } from "lucide-react"; +import { useState } from "react"; +import { useNavigate } from "react-router"; + +import Code from "~/components/Code"; +import Dialog from "~/components/Dialog"; +import Input from "~/components/Input"; +import Menu from "~/components/Menu"; +import Select from "~/components/Select"; +import type { User } from "~/types"; +import { getUserDisplayName } from "~/utils/user"; export interface NewMachineProps { - server: string; - users: User[]; - isDisabled?: boolean; - disabledKeys?: string[]; + server: string; + users: User[]; + isDisabled?: boolean; + disabledKeys?: string[]; } export default function NewMachine(data: NewMachineProps) { - const [pushDialog, setPushDialog] = useState(false); - const [mkey, setMkey] = useState(''); - const navigate = useNavigate(); + const [pushDialog, setPushDialog] = useState(false); + const [mkey, setMkey] = useState(""); + const navigate = useNavigate(); - const isMkeyInvalid = mkey.length > 0 && mkey.length !== 24; + const isMkeyInvalid = mkey.length > 0 && mkey.length !== 24; - return ( - <> - - - Register Machine Key - - The machine key is given when you run{' '} - tailscale up --login-server={data.server} on - your device. - - - - - - - - Add Device - { - if (key === 'register') { - setPushDialog(true); - return; - } + return ( + <> + + + Register Machine Key + + The machine key is given when you run{" "} + tailscale up --login-server={data.server} on your device. + + + + + + + + Add Device + { + if (key === "register") { + setPushDialog(true); + return; + } - if (key === 'pre-auth') { - navigate('/settings/auth-keys'); - } - }} - > - - -
- - Register Machine Key -
-
- -
- - Generate Pre-auth Key -
-
-
-
-
- - ); + if (key === "pre-auth") { + navigate("/settings/auth-keys"); + } + }} + > + + +
+ + Register Machine Key +
+
+ +
+ + Generate Pre-auth Key +
+
+
+
+
+ + ); } diff --git a/app/routes/machines/machine-actions.ts b/app/routes/machines/machine-actions.ts index 8e80ad0..8952ec1 100644 --- a/app/routes/machines/machine-actions.ts +++ b/app/routes/machines/machine-actions.ts @@ -60,9 +60,10 @@ export async function machineAction({ request, context }: Route.ActionArgs) { }); } - // Tag-only nodes (Headscale 0.28+) have no user, so we rely on role-based permissions + // Tag-only nodes (Headscale 0.28+) have no user — only role-based permissions apply const nodeOwnerId = node.user?.providerId?.split("/").pop(); - if (nodeOwnerId !== session.user.subject && !check) { + const isOwner = nodeOwnerId !== undefined && nodeOwnerId === session.user.subject; + if (!isOwner && !check) { throw data("You do not have permission to act on this machine", { status: 403, }); diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index f1eff1c..79d3018 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -12,9 +12,9 @@ import Tooltip from "~/components/Tooltip"; import cn from "~/utils/cn"; import { getOSInfo, getTSVersion } from "~/utils/host-info"; import { mapNodes, sortNodeTags } from "~/utils/node-info"; +import { getUserDisplayName } from "~/utils/user"; import type { Route } from "./+types/machine"; - import { mapTagsToComponents, uiTagsForNode } from "./components/machine-row"; import MenuOptions from "./components/menu"; import Routes from "./dialogs/routes"; @@ -109,9 +109,7 @@ export default function Page({
- {node.user - ? node.user.name || node.user.displayName || node.user.email || node.user.id - : "Tag-owned"} + {node.user ? getUserDisplayName(node.user) : "Tag-owned"}
@@ -243,11 +241,7 @@ export default function Page({
diff --git a/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx b/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx index 5505bed..e9420f3 100644 --- a/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx +++ b/app/routes/settings/auth-keys/dialogs/add-auth-key.tsx @@ -1,8 +1,6 @@ import { Key, useEffect, useRef, useState } from "react"; import { useFetcher } from "react-router"; -import type { User } from "~/types"; - import Button from "~/components/Button"; import Code from "~/components/Code"; import Dialog from "~/components/Dialog"; @@ -11,7 +9,9 @@ import Link from "~/components/Link"; import NumberInput from "~/components/NumberInput"; import Select from "~/components/Select"; import Switch from "~/components/Switch"; +import type { User } from "~/types"; import toast from "~/utils/toast"; +import { getUserDisplayName } from "~/utils/user"; interface AddAuthKeyProps { users: User[]; @@ -152,9 +152,7 @@ export default function AddAuthKey({ selectedKey={userId} > {availableUsers.map((user) => ( - - {user.name || user.displayName || user.email || user.id} - + {getUserDisplayName(user)} ))} )} diff --git a/app/routes/settings/auth-keys/overview.tsx b/app/routes/settings/auth-keys/overview.tsx index 375b47d..301ce92 100644 --- a/app/routes/settings/auth-keys/overview.tsx +++ b/app/routes/settings/auth-keys/overview.tsx @@ -2,20 +2,18 @@ import { FileKey2 } from "lucide-react"; import { useMemo, useState } from "react"; import { Link as RemixLink } from "react-router"; -import type { PreAuthKey } from "~/types"; -import type { User } from "~/types/User"; - import Code from "~/components/Code"; import Link from "~/components/Link"; import Notice from "~/components/Notice"; import Select from "~/components/Select"; import TableList from "~/components/TableList"; import { Capabilities } from "~/server/web/roles"; +import type { PreAuthKey } from "~/types"; +import type { User } from "~/types/User"; import log from "~/utils/log"; -import { filterUsersWithValidIds, getUserDisplayName } from "~/utils/user"; +import { getUserDisplayName } from "~/utils/user"; import type { Route } from "./+types/overview"; - import { authKeysAction } from "./actions"; import AuthKeyRow from "./auth-key-row"; import AddAuthKey from "./dialogs/add-auth-key"; @@ -63,15 +61,17 @@ export async function loader({ request, context }: Route.LoaderArgs) { | { success: false; user: User; error: unknown; preAuthKeys: [] }; const results: FetchResult[] = await Promise.all( - filterUsersWithValidIds(users).map(async (user) => { - try { - const preAuthKeys = await api.getPreAuthKeys(user.id); - return { success: true as const, user, preAuthKeys }; - } catch (error) { - log.error("api", "GET /v1/preauthkey for %s: %o", user.name, error); - return { success: false as const, user, error, preAuthKeys: [] as const }; - } - }), + users + .filter((u) => u.id?.length > 0) + .map(async (user) => { + try { + const preAuthKeys = await api.getPreAuthKeys(user.id); + return { success: true as const, user, preAuthKeys }; + } catch (error) { + log.error("api", "GET /v1/preauthkey for %s: %o", user.name, error); + return { success: false as const, user, error, preAuthKeys: [] as const }; + } + }), ); keys = results diff --git a/app/utils/user.ts b/app/utils/user.ts index e79adfe..3a8b333 100644 --- a/app/utils/user.ts +++ b/app/utils/user.ts @@ -1,11 +1,5 @@ import type { User } from "~/types/User"; -// Filter users with valid IDs (OIDC users may not have a name) -export function filterUsersWithValidIds(users: User[]): User[] { - return users.filter((user) => user.id?.length > 0); -} - -// Get display name with fallback: name -> displayName -> email -> id export function getUserDisplayName(user: User): string { return user.name || user.displayName || user.email || user.id; }