fix: actually fix type errors

This commit is contained in:
Aarnav Tale
2026-03-16 23:41:09 -04:00
parent 25dc09e025
commit 8f6fe05c83
16 changed files with 101 additions and 163 deletions
+1 -1
View File
@@ -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 {
-54
View File
@@ -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 (
<main className="container mt-4 mb-24 overscroll-contain">
<Outlet />
</main>
);
}
export function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {
return (
<div className="mx-auto my-24 w-fit overscroll-contain">
<ErrorBanner className="max-w-2xl" error={error} />
</div>
);
}
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+1 -1
View File
@@ -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";
+4 -1
View File
@@ -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(),
+1 -1
View File
@@ -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(),
@@ -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";
@@ -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";
@@ -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";
@@ -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";
+39 -40
View File
@@ -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 (
<div className="flex items-center justify-center h-screen">
<Card>
<Card.Title>Enter Username</Card.Title>
<Card.Text className="mb-4">
Enter the username you want to use to connect to{' '}
<Code>{hostname}</Code>
{'. '}
WebSSH follows the Headscale ACLs, so only permitted usernames will be
able to connect.
</Card.Text>
<Input
labelHidden
type="text"
label="Username"
placeholder="Username"
className="mb-2"
onChange={setUsername}
/>
<Button
variant="heavy"
className="w-full"
onPress={() => {
// We can't use the navigate hook here as we need to do a
// full page reload to ensure the SSH connection is established
window.location.href = `${__PREFIX__}/ssh?hostname=${hostname}&username=${username}`;
}}
>
Connect
</Button>
</Card>
</div>
);
return (
<div className="flex h-screen items-center justify-center">
<Card>
<Card.Title>Enter Username</Card.Title>
<Card.Text className="mb-4">
Enter the username you want to use to connect to <Code>{hostname}</Code>
{". "}
WebSSH follows the Headscale ACLs, so only permitted usernames will be able to connect.
</Card.Text>
<Input
labelHidden
type="text"
label="Username"
placeholder="Username"
className="mb-2"
onChange={setUsername}
/>
<Button
variant="heavy"
className="w-full"
onPress={() => {
// We can't use the navigate hook here as we need to do a
// full page reload to ensure the SSH connection is established
window.location.href = `${__PREFIX__}/ssh?hostname=${hostname}&username=${username}`;
}}
>
Connect
</Button>
</Card>
</div>
);
}
+1 -2
View File
@@ -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<HeadplaneConfig["oidc"]>;
@@ -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,
+28 -32
View File
@@ -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<Logger['debug']>) => {},
...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<Logger["debug"]>) => {},
...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"];
}
+19 -24
View File
@@ -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
}
}