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 (
- <>
-
-
+ >
+ );
}
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;
}