From 6a94e815f29cd08d0fc3868cf88c59d03cfe5a11 Mon Sep 17 00:00:00 2001 From: Aarnav Tale Date: Thu, 3 Apr 2025 03:00:38 -0400 Subject: [PATCH] feat: improve error returning and parsing logic --- app/components/Error.tsx | 46 ++++++++++++++++++++++++------ app/layouts/dashboard.tsx | 2 +- app/layouts/shell.tsx | 2 +- app/routes.ts | 2 +- app/server/headscale/api-client.ts | 15 +--------- app/server/web/roles.ts | 16 +++++------ app/utils/res.ts | 11 ++++--- 7 files changed, 56 insertions(+), 38 deletions(-) diff --git a/app/components/Error.tsx b/app/components/Error.tsx index 9297f35..a867a7a 100644 --- a/app/components/Error.tsx +++ b/app/components/Error.tsx @@ -1,16 +1,36 @@ import { AlertIcon } from '@primer/octicons-react'; import { isRouteErrorResponse, useRouteError } from 'react-router'; +import ResponseError from '~/server/headscale/api-error'; import cn from '~/utils/cn'; import Card from './Card'; -import Code from './Code'; interface Props { type?: 'full' | 'embedded'; } -function getMessage(error: Error | unknown) { +function getMessage(error: Error | unknown): { + title: string; + message: string; +} { + if (error instanceof ResponseError) { + if (error.responseObject?.message) { + return { + title: 'Headscale Error', + message: String(error.responseObject.message), + }; + } + + return { + title: 'Headscale Error', + message: error.response, + }; + } + if (!(error instanceof Error)) { - return 'An unknown error occurred'; + return { + title: 'Unknown Error', + message: String(error), + }; } let rootError = error; @@ -25,16 +45,22 @@ function getMessage(error: Error | unknown) { // If we are aggregate, concat into a single message if (rootError instanceof AggregateError) { - return rootError.errors.map((error) => error.message).join('\n'); + return { + title: 'Errors', + message: rootError.errors.map((error) => error.message).join('\n'), + }; } - return rootError.message; + return { + title: 'Error', + message: rootError.message, + }; } export function ErrorPopup({ type = 'full' }: Props) { const error = useRouteError(); const routing = isRouteErrorResponse(error); - const message = getMessage(error); + const { title, message } = getMessage(error); return (
- {routing ? error.status : 'Error'} + {routing ? error.status : title}
- - {routing ? error.statusText : {message}} + + {routing ? error.data.message : message}
diff --git a/app/layouts/dashboard.tsx b/app/layouts/dashboard.tsx index 142ae14..7e3b54b 100644 --- a/app/layouts/dashboard.tsx +++ b/app/layouts/dashboard.tsx @@ -3,7 +3,7 @@ import { type LoaderFunctionArgs, redirect } from 'react-router'; import { Outlet, useLoaderData } from 'react-router'; import { ErrorPopup } from '~/components/Error'; import type { LoadContext } from '~/server'; -import { ResponseError } from '~/server/headscale/api-client'; +import ResponseError from '~/server/headscale/api-error'; import cn from '~/utils/cn'; import log from '~/utils/log'; diff --git a/app/layouts/shell.tsx b/app/layouts/shell.tsx index e9ac3fa..8c3d2c3 100644 --- a/app/layouts/shell.tsx +++ b/app/layouts/shell.tsx @@ -1,4 +1,4 @@ -import { BanIcon, CircleCheckIcon } from 'lucide-react'; +import { CircleCheckIcon } from 'lucide-react'; import { LoaderFunctionArgs, Outlet, diff --git a/app/routes.ts b/app/routes.ts index fd56844..1a0adb4 100644 --- a/app/routes.ts +++ b/app/routes.ts @@ -23,7 +23,7 @@ export default [ ]), route('/users', 'routes/users/overview.tsx'), - route('/acls', 'routes/acls/editor.tsx'), + route('/acls', 'routes/acls/overview.tsx'), route('/dns', 'routes/dns/overview.tsx'), ...prefix('/settings', [ diff --git a/app/server/headscale/api-client.ts b/app/server/headscale/api-client.ts index a64346e..3de5638 100644 --- a/app/server/headscale/api-client.ts +++ b/app/server/headscale/api-client.ts @@ -1,6 +1,7 @@ import { readFile } from 'node:fs/promises'; import { Agent, Dispatcher, request } from 'undici'; import log from '~/utils/log'; +import ResponseError from './api-error'; export async function createApiClient(base: string, certPath?: string) { if (!certPath) { @@ -20,20 +21,6 @@ export async function createApiClient(base: string, certPath?: string) { } } -// Represents an error that occurred during a response -// Thrown when status codes are >= 400 -export class ResponseError extends Error { - status: number; - response: string; - - constructor(status: number, response: string) { - super(`Response Error (${status}): ${response}`); - this.name = 'ResponseError'; - this.status = status; - this.response = response; - } -} - export class ApiClient { private agent: Agent; private base: string; diff --git a/app/server/web/roles.ts b/app/server/web/roles.ts index abe99b9..2569cb2 100644 --- a/app/server/web/roles.ts +++ b/app/server/web/roles.ts @@ -3,10 +3,10 @@ export const Capabilities = { // Can access the admin console ui_access: 1 << 0, - // Read tailnet policy file + // Read tailnet policy file (unimplemented) read_policy: 1 << 1, - // Write tailnet policy file + // Write tailnet policy file (unimplemented) write_policy: 1 << 2, // Read network configurations @@ -16,13 +16,13 @@ export const Capabilities = { // make subnet, or allow a node to be an exit node, enable HTTPS write_network: 1 << 4, - // Read feature configuration + // Read feature configuration (unimplemented) read_feature: 1 << 5, - // Write feature configuration, for example, enable Taildrop + // Write feature configuration, for example, enable Taildrop (unimplemented) write_feature: 1 << 6, - // Configure user & group provisioning + // Configure user & group provisioning (unimplemented) configure_iam: 1 << 7, // Read machines, for example, see machine names and status @@ -38,13 +38,13 @@ export const Capabilities = { // approve users, make Admin write_users: 1 << 11, - // Can generate authkeys + // Can generate authkeys (unimplemented) generate_authkeys: 1 << 12, - // Can use any tag (without being tag owner) + // Can use any tag (without being tag owner) (unimplemented) use_tags: 1 << 13, - // Write tailnet name + // Write tailnet name (unimplemented) write_tailnet: 1 << 14, // Owner flag diff --git a/app/utils/res.ts b/app/utils/res.ts index 680c864..d0e1e7a 100644 --- a/app/utils/res.ts +++ b/app/utils/res.ts @@ -19,10 +19,13 @@ export function data400(message: string) { } export function data403(message: string) { - return data({ - success: false, - message, - }); + return data( + { + success: false, + message, + }, + { status: 403 }, + ); } export function data404(message: string) {