From 8f6fe05c8320dcdf8f86c6dea0960b8bfaf523cc Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Mon, 16 Mar 2026 23:41:09 -0400 Subject: [PATCH] fix: actually fix type errors --- app/components/page-error.tsx | 2 +- app/layouts/dashboard.tsx | 54 ------------- app/routes/dns/components/rename-tailnet.tsx | 2 +- app/routes/dns/components/toggle-magic.tsx | 2 +- app/routes/dns/dialogs/add-ns.tsx | 2 +- app/routes/dns/dialogs/add-record.tsx | 2 +- app/routes/machines/machine.tsx | 5 +- app/routes/machines/overview.tsx | 2 +- .../auth-keys/dialogs/expire-auth-key.tsx | 2 +- .../restrictions/dialogs/add-domain.tsx | 2 +- .../restrictions/dialogs/add-group.tsx | 2 +- .../restrictions/dialogs/add-user.tsx | 2 +- app/routes/ssh/user-prompt.tsx | 79 +++++++++---------- app/server/web/oidc-connector.ts | 3 +- app/utils/log.ts | 60 +++++++------- tsconfig.json | 43 +++++----- 16 files changed, 101 insertions(+), 163 deletions(-) delete mode 100644 app/layouts/dashboard.tsx diff --git a/app/components/page-error.tsx b/app/components/page-error.tsx index 315b9c6..265f75e 100644 --- a/app/components/page-error.tsx +++ b/app/components/page-error.tsx @@ -4,7 +4,7 @@ import { isRouteErrorResponse, useRevalidator } from "react-router"; import { isConnectionError } from "~/server/headscale/api/error-client"; import cn from "~/utils/cn"; -import Button from "./Button"; +import Button from "./button"; import { ErrorBanner } from "./error-banner"; interface PageErrorProps { diff --git a/app/layouts/dashboard.tsx b/app/layouts/dashboard.tsx deleted file mode 100644 index f07728b..0000000 --- a/app/layouts/dashboard.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Outlet, redirect } from "react-router"; - -import { ErrorBanner } from "~/components/error-banner"; -import { pruneEphemeralNodes } from "~/server/db/pruner"; -import { isDataUnauthorizedError } from "~/server/headscale/api/error-client"; -import log from "~/utils/log"; - -import type { Route } from "./+types/dashboard"; - -export async function loader({ request, context, ...rest }: Route.LoaderArgs) { - const principal = await context.auth.require(request); - const apiKey = context.auth.getHeadscaleApiKey(principal, context.oidc?.apiKey); - const api = context.hsApi.getRuntimeClient(apiKey); - - // MARK: The session should stay valid if Headscale isn't healthy - const healthy = await api.isHealthy(); - if (healthy) { - try { - await api.getApiKeys(); - await pruneEphemeralNodes({ context, request, ...rest }); - } catch (error) { - if (isDataUnauthorizedError(error)) { - const displayName = - principal.kind === "oidc" ? principal.profile.name : principal.displayName; - log.warn("auth", "Logging out %s due to expired API key", displayName); - return redirect("/login", { - headers: { - "Set-Cookie": await context.auth.destroySession(request), - }, - }); - } - } - } - - return { - healthy, - }; -} - -export default function Layout() { - return ( -
- -
- ); -} - -export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) { - return ( -
- -
- ); -} diff --git a/app/routes/dns/components/rename-tailnet.tsx b/app/routes/dns/components/rename-tailnet.tsx index 524194c..8a08d20 100644 --- a/app/routes/dns/components/rename-tailnet.tsx +++ b/app/routes/dns/components/rename-tailnet.tsx @@ -1,4 +1,4 @@ -import Button from "~/components/Button"; +import Button from "~/components/button"; import Code from "~/components/Code"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Input from "~/components/Input"; diff --git a/app/routes/dns/components/toggle-magic.tsx b/app/routes/dns/components/toggle-magic.tsx index 6177229..c7fe3e4 100644 --- a/app/routes/dns/components/toggle-magic.tsx +++ b/app/routes/dns/components/toggle-magic.tsx @@ -1,4 +1,4 @@ -import Button from "~/components/Button"; +import Button from "~/components/button"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Text from "~/components/Text"; import Title from "~/components/Title"; diff --git a/app/routes/dns/dialogs/add-ns.tsx b/app/routes/dns/dialogs/add-ns.tsx index f5256b5..00832a9 100644 --- a/app/routes/dns/dialogs/add-ns.tsx +++ b/app/routes/dns/dialogs/add-ns.tsx @@ -1,7 +1,7 @@ import { Split } from "lucide-react"; import { useMemo, useState } from "react"; -import Button from "~/components/Button"; +import Button from "~/components/button"; import Chip from "~/components/Chip"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Input from "~/components/Input"; diff --git a/app/routes/dns/dialogs/add-record.tsx b/app/routes/dns/dialogs/add-record.tsx index 55f8ed9..e3def7b 100644 --- a/app/routes/dns/dialogs/add-record.tsx +++ b/app/routes/dns/dialogs/add-record.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; -import Button from "~/components/Button"; +import Button from "~/components/button"; import Code from "~/components/Code"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Input from "~/components/Input"; diff --git a/app/routes/machines/machine.tsx b/app/routes/machines/machine.tsx index 0df5995..386cd99 100644 --- a/app/routes/machines/machine.tsx +++ b/app/routes/machines/machine.tsx @@ -48,11 +48,14 @@ export async function loader({ request, params, context }: Route.LoaderArgs) { const nodes = nodesSnap.data; const users = usersSnap.data; const node = nodes.find((node) => node.id === params.id); + if (node == null) { + throw data(null, { status: 404 }); + } const lookup = await context.agents?.lookup([node.nodeKey]); const [enhancedNode] = mapNodes([node], lookup); const tags = [...node.tags].toSorted(); - const supportsNodeOwnerChange = !context.hsApi.clientHelpers.isAtleast("0.28.0-beta.1"); + const supportsNodeOwnerChange = !context.hsApi.clientHelpers.isAtleast("0.28.0"); return { agent: context.agents?.agentID(), diff --git a/app/routes/machines/overview.tsx b/app/routes/machines/overview.tsx index faff4d3..94d44d5 100644 --- a/app/routes/machines/overview.tsx +++ b/app/routes/machines/overview.tsx @@ -46,7 +46,7 @@ export async function loader({ request, context }: Route.LoaderArgs) { const stats = await context.agents?.lookup(nodes.map((node) => node.nodeKey)); const populatedNodes = mapNodes(nodes, stats); - const supportsNodeOwnerChange = !context.hsApi.clientHelpers.isAtleast("0.28.0-beta.1"); + const supportsNodeOwnerChange = !context.hsApi.clientHelpers.isAtleast("0.28.0"); return { agent: context.agents?.agentID(), diff --git a/app/routes/settings/auth-keys/dialogs/expire-auth-key.tsx b/app/routes/settings/auth-keys/dialogs/expire-auth-key.tsx index b19a44f..b8a2792 100644 --- a/app/routes/settings/auth-keys/dialogs/expire-auth-key.tsx +++ b/app/routes/settings/auth-keys/dialogs/expire-auth-key.tsx @@ -1,4 +1,4 @@ -import Button from "~/components/Button"; +import Button from "~/components/button"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Text from "~/components/Text"; import Title from "~/components/Title"; diff --git a/app/routes/settings/restrictions/dialogs/add-domain.tsx b/app/routes/settings/restrictions/dialogs/add-domain.tsx index e3f9eb5..8c9ddf5 100644 --- a/app/routes/settings/restrictions/dialogs/add-domain.tsx +++ b/app/routes/settings/restrictions/dialogs/add-domain.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; -import Button from "~/components/Button"; +import Button from "~/components/button"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Input from "~/components/Input"; import Text from "~/components/Text"; diff --git a/app/routes/settings/restrictions/dialogs/add-group.tsx b/app/routes/settings/restrictions/dialogs/add-group.tsx index 82d0e05..78985b2 100644 --- a/app/routes/settings/restrictions/dialogs/add-group.tsx +++ b/app/routes/settings/restrictions/dialogs/add-group.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; -import Button from "~/components/Button"; +import Button from "~/components/button"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Input from "~/components/Input"; import Text from "~/components/Text"; diff --git a/app/routes/settings/restrictions/dialogs/add-user.tsx b/app/routes/settings/restrictions/dialogs/add-user.tsx index 039d420..f2ac2f1 100644 --- a/app/routes/settings/restrictions/dialogs/add-user.tsx +++ b/app/routes/settings/restrictions/dialogs/add-user.tsx @@ -1,6 +1,6 @@ import { useMemo, useState } from "react"; -import Button from "~/components/Button"; +import Button from "~/components/button"; import Dialog, { DialogPanel } from "~/components/Dialog"; import Input from "~/components/Input"; import Text from "~/components/Text"; diff --git a/app/routes/ssh/user-prompt.tsx b/app/routes/ssh/user-prompt.tsx index bbf160d..d8a2e4b 100644 --- a/app/routes/ssh/user-prompt.tsx +++ b/app/routes/ssh/user-prompt.tsx @@ -1,47 +1,46 @@ -import { useState } from 'react'; -import Button from '~/components/Button'; -import Card from '~/components/Card'; -import Code from '~/components/Code'; -import Input from '~/components/Input'; +import { useState } from "react"; + +import Button from "~/components/button"; +import Card from "~/components/Card"; +import Code from "~/components/Code"; +import Input from "~/components/Input"; interface UserPromptProps { - hostname: string; + hostname: string; } export default function UserPrompt({ hostname }: UserPromptProps) { - const [username, setUsername] = useState(''); + const [username, setUsername] = useState(""); - return ( -
- - Enter Username - - Enter the username you want to use to connect to{' '} - {hostname} - {'. '} - WebSSH follows the Headscale ACLs, so only permitted usernames will be - able to connect. - - - - -
- ); + return ( +
+ + Enter Username + + Enter the username you want to use to connect to {hostname} + {". "} + WebSSH follows the Headscale ACLs, so only permitted usernames will be able to connect. + + + + +
+ ); } diff --git a/app/server/web/oidc-connector.ts b/app/server/web/oidc-connector.ts index 3b6de7b..39707ca 100644 --- a/app/server/web/oidc-connector.ts +++ b/app/server/web/oidc-connector.ts @@ -4,7 +4,6 @@ import log from "~/utils/log"; import type { HeadplaneConfig } from "../config/config-schema"; import type { RuntimeApiClient } from "../headscale/api/endpoints"; - import { isDataUnauthorizedError } from "../headscale/api/error-client"; export type OidcConfig = NonNullable; @@ -228,7 +227,7 @@ async function discoveryCoalesce( } } } catch { - log.warn("oidc", "Failed to reach OIDC provider for discovery, will retry on next request"); + log.warn("auth", "Failed to reach OIDC provider for discovery, will retry on next request"); discoveryFailed = true; metadata = { issuer: config.issuer, diff --git a/app/utils/log.ts b/app/utils/log.ts index ffd88a3..9704143 100644 --- a/app/utils/log.ts +++ b/app/utils/log.ts @@ -3,46 +3,42 @@ // is static and logger is later modified in `app/server/index.ts` to // disable debug logging if the `HEADPLANE_DEBUG_LOG` specifies as such. -const levels = ['info', 'warn', 'error', 'debug'] as const; -type Category = 'server' | 'config' | 'agent' | 'api' | 'auth'; +const levels = ["info", "warn", "error", "debug"] as const; +type Category = "server" | "config" | "agent" | "api" | "auth" | "sse"; -export interface Logger - extends Record< - (typeof levels)[number], - (category: Category, message: string, ...args: unknown[]) => void - > { - debugEnabled: boolean; +export interface Logger extends Record< + (typeof levels)[number], + (category: Category, message: string, ...args: unknown[]) => void +> { + debugEnabled: boolean; } const logLevels = getLogLevels(); export default { - debugEnabled: logLevels.includes('debug'), - debug: (..._: Parameters) => {}, - ...Object.fromEntries( - logLevels.map((level) => [ - level, - (category: Category, message: string, ...args: unknown[]) => { - const date = new Date().toISOString(); - console.log( - `${date} [${category}] ${level.toUpperCase()}: ${message}`, - ...args, - ); - }, - ]), - ), + debugEnabled: logLevels.includes("debug"), + debug: (..._: Parameters) => {}, + ...Object.fromEntries( + logLevels.map((level) => [ + level, + (category: Category, message: string, ...args: unknown[]) => { + const date = new Date().toISOString(); + console.log(`${date} [${category}] ${level.toUpperCase()}: ${message}`, ...args); + }, + ]), + ), } as Logger; function getLogLevels() { - const debugLog = process.env.HEADPLANE_DEBUG_LOG; - if (debugLog == null) { - return ['info', 'warn', 'error']; - } + const debugLog = process.env.HEADPLANE_DEBUG_LOG; + if (debugLog == null) { + return ["info", "warn", "error"]; + } - const normalized = debugLog.trim().toLowerCase(); - const truthyValues = ['1', 'true', 'yes', 'on']; - if (!truthyValues.includes(normalized)) { - return ['info', 'warn', 'error']; - } + const normalized = debugLog.trim().toLowerCase(); + const truthyValues = ["1", "true", "yes", "on"]; + if (!truthyValues.includes(normalized)) { + return ["info", "warn", "error"]; + } - return ['info', 'warn', 'error', 'debug']; + return ["info", "warn", "error", "debug"]; } diff --git a/tsconfig.json b/tsconfig.json index 4ae965a..5b85c73 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,26 +1,21 @@ { - "include": [ - "**/*", - "**/.server/**/*", - "**/.client/**/*", - ".react-router/types/**/*" - ], - "compilerOptions": { - "lib": ["DOM", "DOM.Iterable", "ES2022"], - "types": ["node", "vite/client"], - "target": "ES2022", - "module": "ESNext", - "moduleResolution": "bundler", - "jsx": "react-jsx", - "rootDirs": [".", "./.react-router/types"], - "paths": { - "~/*": ["./app/*"] - }, - "esModuleInterop": true, - "verbatimModuleSyntax": false, - "noEmit": true, - "resolveJsonModule": true, - "skipLibCheck": true, - "strict": true - } + "include": ["**/*", "**/.server/**/*", "**/.client/**/*", ".react-router/types/**/*"], + "compilerOptions": { + "lib": ["DOM", "DOM.Iterable", "ES2023"], + "types": ["node", "vite/client"], + "target": "ES2022", + "module": "ESNext", + "moduleResolution": "bundler", + "jsx": "react-jsx", + "rootDirs": [".", "./.react-router/types"], + "paths": { + "~/*": ["./app/*"] + }, + "esModuleInterop": true, + "verbatimModuleSyntax": false, + "noEmit": true, + "resolveJsonModule": true, + "skipLibCheck": true, + "strict": true + } }