feat: improve error returning and parsing logic
This commit is contained in:
parent
234020eec5
commit
6a94e815f2
@ -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 (
|
||||
<div
|
||||
@ -48,12 +74,14 @@ export function ErrorPopup({ type = 'full' }: Props) {
|
||||
<Card>
|
||||
<div className="flex items-center justify-between">
|
||||
<Card.Title className="text-3xl mb-0">
|
||||
{routing ? error.status : 'Error'}
|
||||
{routing ? error.status : title}
|
||||
</Card.Title>
|
||||
<AlertIcon className="w-12 h-12 text-red-500" />
|
||||
</div>
|
||||
<Card.Text className="mt-4 text-lg">
|
||||
{routing ? error.statusText : <Code>{message}</Code>}
|
||||
<Card.Text
|
||||
className={cn('mt-4 text-lg', routing ? 'font-normal' : 'font-mono')}
|
||||
>
|
||||
{routing ? error.data.message : message}
|
||||
</Card.Text>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { BanIcon, CircleCheckIcon } from 'lucide-react';
|
||||
import { CircleCheckIcon } from 'lucide-react';
|
||||
import {
|
||||
LoaderFunctionArgs,
|
||||
Outlet,
|
||||
|
||||
@ -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', [
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -19,10 +19,13 @@ export function data400(message: string) {
|
||||
}
|
||||
|
||||
export function data403(message: string) {
|
||||
return data({
|
||||
return data(
|
||||
{
|
||||
success: false,
|
||||
message,
|
||||
});
|
||||
},
|
||||
{ status: 403 },
|
||||
);
|
||||
}
|
||||
|
||||
export function data404(message: string) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user