diff --git a/app/routes/settings/auth-keys/auth-key-row.tsx b/app/routes/settings/auth-keys/auth-key-row.tsx
index 93dc44c..1811959 100644
--- a/app/routes/settings/auth-keys/auth-key-row.tsx
+++ b/app/routes/settings/auth-keys/auth-key-row.tsx
@@ -6,23 +6,26 @@ import ExpireAuthKey from "./dialogs/expire-auth-key";
interface Props {
authKey: PreAuthKey;
- user: User;
+ user: User | null;
}
export default function AuthKeyRow({ authKey, user }: Props) {
const createdAt = new Date(authKey.createdAt).toLocaleString();
const expiration = new Date(authKey.expiration).toLocaleString();
+ const isExpired =
+ (authKey.used && !authKey.reusable) || new Date(authKey.expiration) < new Date();
+ const userDisplay = user ? user.name || user.displayName || user.email || user.id : "(Tag Only)";
return (
-
+
- {!((authKey.used && !authKey.reusable) || new Date(authKey.expiration) < new Date()) && (
+ {!isExpired && user && (
diff --git a/app/routes/settings/auth-keys/overview.tsx b/app/routes/settings/auth-keys/overview.tsx
index 26389c7..b3739d6 100644
--- a/app/routes/settings/auth-keys/overview.tsx
+++ b/app/routes/settings/auth-keys/overview.tsx
@@ -8,6 +8,8 @@ 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";
@@ -22,40 +24,63 @@ export async function loader({ request, context }: Route.LoaderArgs) {
const api = context.hsApi.getRuntimeClient(session.api_key);
const users = await api.getUsers();
- const preAuthKeys = await Promise.all(
- filterUsersWithValidIds(users).map(async (user) => {
- try {
- const preAuthKeys = await api.getPreAuthKeys(user.id);
- return {
- success: true,
- user,
- preAuthKeys,
- };
- } catch (error) {
- log.error("api", "GET /v1/preauthkey for %s: %o", user.name, error);
- return {
- success: false,
- user,
- error,
- preAuthKeys: [],
- };
+
+ let keys: { user: User | null; preAuthKeys: PreAuthKey[] }[];
+ let missing: { user: User; error: unknown }[] = [];
+
+ // Try fetching all keys at once (Headscale 0.28+), fall back to per-user
+ let allKeys: PreAuthKey[] | null = null;
+ try {
+ allKeys = await api.getAllPreAuthKeys();
+ } catch {
+ // older versions don't support this endpoint
+ }
+
+ if (allKeys !== null) {
+ const keysByUser = new Map
();
+ for (const key of allKeys) {
+ const userId = key.user?.id ?? null;
+ const existing = keysByUser.get(userId) ?? [];
+ existing.push(key);
+ keysByUser.set(userId, existing);
+ }
+
+ keys = [];
+ const tagOnly = keysByUser.get(null);
+ if (tagOnly?.length) {
+ keys.push({ user: null, preAuthKeys: tagOnly });
+ }
+ for (const user of users) {
+ const userKeys = keysByUser.get(user.id);
+ if (userKeys?.length) {
+ keys.push({ user, preAuthKeys: userKeys });
}
- }),
- );
+ }
+ } else {
+ type FetchResult =
+ | { success: true; user: User; preAuthKeys: PreAuthKey[] }
+ | { success: false; user: User; error: unknown; preAuthKeys: [] };
- const keys = preAuthKeys
- .filter(({ success }) => success)
- .map(({ user, preAuthKeys }) => ({
- user,
- 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 };
+ }
+ }),
+ );
- const missing = preAuthKeys
- .filter(({ success }) => !success)
- .map(({ user, error }) => ({
- user,
- error,
- }));
+ keys = results
+ .filter(({ success }) => success)
+ .map(({ user, preAuthKeys }) => ({ user, preAuthKeys }));
+
+ missing = results
+ .filter((r): r is Extract => !r.success)
+ .map(({ user, error }) => ({ user, error }));
+ }
return {
keys,
@@ -84,7 +109,11 @@ export default function Page({
return true;
}
- return user.id === selectedUser;
+ if (selectedUser === "__headplane_tag_only") {
+ return user === null;
+ }
+
+ return user?.id === selectedUser;
})
.flatMap(({ preAuthKeys }) => preAuthKeys)
.filter((key) => {
@@ -176,9 +205,14 @@ export default function Page({
>
{[
All,
- ...keys.map(({ user }) => (
- {getUserDisplayName(user)}
- )),
+ ...keys
+ .filter((k): k is { user: User; preAuthKeys: PreAuthKey[] } => k.user !== null)
+ .map(({ user }) => (
+ {getUserDisplayName(user)}
+ )),
+ ...(keys.some(({ user }) => user === null)
+ ? [Tag Only]
+ : []),
]}