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 { AlertIcon } from '@primer/octicons-react';
|
||||||
import { isRouteErrorResponse, useRouteError } from 'react-router';
|
import { isRouteErrorResponse, useRouteError } from 'react-router';
|
||||||
|
import ResponseError from '~/server/headscale/api-error';
|
||||||
import cn from '~/utils/cn';
|
import cn from '~/utils/cn';
|
||||||
import Card from './Card';
|
import Card from './Card';
|
||||||
import Code from './Code';
|
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
type?: 'full' | 'embedded';
|
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)) {
|
if (!(error instanceof Error)) {
|
||||||
return 'An unknown error occurred';
|
return {
|
||||||
|
title: 'Unknown Error',
|
||||||
|
message: String(error),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let rootError = error;
|
let rootError = error;
|
||||||
@ -25,16 +45,22 @@ function getMessage(error: Error | unknown) {
|
|||||||
|
|
||||||
// If we are aggregate, concat into a single message
|
// If we are aggregate, concat into a single message
|
||||||
if (rootError instanceof AggregateError) {
|
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) {
|
export function ErrorPopup({ type = 'full' }: Props) {
|
||||||
const error = useRouteError();
|
const error = useRouteError();
|
||||||
const routing = isRouteErrorResponse(error);
|
const routing = isRouteErrorResponse(error);
|
||||||
const message = getMessage(error);
|
const { title, message } = getMessage(error);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
@ -48,12 +74,14 @@ export function ErrorPopup({ type = 'full' }: Props) {
|
|||||||
<Card>
|
<Card>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Card.Title className="text-3xl mb-0">
|
<Card.Title className="text-3xl mb-0">
|
||||||
{routing ? error.status : 'Error'}
|
{routing ? error.status : title}
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<AlertIcon className="w-12 h-12 text-red-500" />
|
<AlertIcon className="w-12 h-12 text-red-500" />
|
||||||
</div>
|
</div>
|
||||||
<Card.Text className="mt-4 text-lg">
|
<Card.Text
|
||||||
{routing ? error.statusText : <Code>{message}</Code>}
|
className={cn('mt-4 text-lg', routing ? 'font-normal' : 'font-mono')}
|
||||||
|
>
|
||||||
|
{routing ? error.data.message : message}
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -3,7 +3,7 @@ import { type LoaderFunctionArgs, redirect } from 'react-router';
|
|||||||
import { Outlet, useLoaderData } from 'react-router';
|
import { Outlet, useLoaderData } from 'react-router';
|
||||||
import { ErrorPopup } from '~/components/Error';
|
import { ErrorPopup } from '~/components/Error';
|
||||||
import type { LoadContext } from '~/server';
|
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 cn from '~/utils/cn';
|
||||||
import log from '~/utils/log';
|
import log from '~/utils/log';
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { BanIcon, CircleCheckIcon } from 'lucide-react';
|
import { CircleCheckIcon } from 'lucide-react';
|
||||||
import {
|
import {
|
||||||
LoaderFunctionArgs,
|
LoaderFunctionArgs,
|
||||||
Outlet,
|
Outlet,
|
||||||
|
|||||||
@ -23,7 +23,7 @@ export default [
|
|||||||
]),
|
]),
|
||||||
|
|
||||||
route('/users', 'routes/users/overview.tsx'),
|
route('/users', 'routes/users/overview.tsx'),
|
||||||
route('/acls', 'routes/acls/editor.tsx'),
|
route('/acls', 'routes/acls/overview.tsx'),
|
||||||
route('/dns', 'routes/dns/overview.tsx'),
|
route('/dns', 'routes/dns/overview.tsx'),
|
||||||
|
|
||||||
...prefix('/settings', [
|
...prefix('/settings', [
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import { readFile } from 'node:fs/promises';
|
import { readFile } from 'node:fs/promises';
|
||||||
import { Agent, Dispatcher, request } from 'undici';
|
import { Agent, Dispatcher, request } from 'undici';
|
||||||
import log from '~/utils/log';
|
import log from '~/utils/log';
|
||||||
|
import ResponseError from './api-error';
|
||||||
|
|
||||||
export async function createApiClient(base: string, certPath?: string) {
|
export async function createApiClient(base: string, certPath?: string) {
|
||||||
if (!certPath) {
|
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 {
|
export class ApiClient {
|
||||||
private agent: Agent;
|
private agent: Agent;
|
||||||
private base: string;
|
private base: string;
|
||||||
|
|||||||
@ -3,10 +3,10 @@ export const Capabilities = {
|
|||||||
// Can access the admin console
|
// Can access the admin console
|
||||||
ui_access: 1 << 0,
|
ui_access: 1 << 0,
|
||||||
|
|
||||||
// Read tailnet policy file
|
// Read tailnet policy file (unimplemented)
|
||||||
read_policy: 1 << 1,
|
read_policy: 1 << 1,
|
||||||
|
|
||||||
// Write tailnet policy file
|
// Write tailnet policy file (unimplemented)
|
||||||
write_policy: 1 << 2,
|
write_policy: 1 << 2,
|
||||||
|
|
||||||
// Read network configurations
|
// Read network configurations
|
||||||
@ -16,13 +16,13 @@ export const Capabilities = {
|
|||||||
// make subnet, or allow a node to be an exit node, enable HTTPS
|
// make subnet, or allow a node to be an exit node, enable HTTPS
|
||||||
write_network: 1 << 4,
|
write_network: 1 << 4,
|
||||||
|
|
||||||
// Read feature configuration
|
// Read feature configuration (unimplemented)
|
||||||
read_feature: 1 << 5,
|
read_feature: 1 << 5,
|
||||||
|
|
||||||
// Write feature configuration, for example, enable Taildrop
|
// Write feature configuration, for example, enable Taildrop (unimplemented)
|
||||||
write_feature: 1 << 6,
|
write_feature: 1 << 6,
|
||||||
|
|
||||||
// Configure user & group provisioning
|
// Configure user & group provisioning (unimplemented)
|
||||||
configure_iam: 1 << 7,
|
configure_iam: 1 << 7,
|
||||||
|
|
||||||
// Read machines, for example, see machine names and status
|
// Read machines, for example, see machine names and status
|
||||||
@ -38,13 +38,13 @@ export const Capabilities = {
|
|||||||
// approve users, make Admin
|
// approve users, make Admin
|
||||||
write_users: 1 << 11,
|
write_users: 1 << 11,
|
||||||
|
|
||||||
// Can generate authkeys
|
// Can generate authkeys (unimplemented)
|
||||||
generate_authkeys: 1 << 12,
|
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,
|
use_tags: 1 << 13,
|
||||||
|
|
||||||
// Write tailnet name
|
// Write tailnet name (unimplemented)
|
||||||
write_tailnet: 1 << 14,
|
write_tailnet: 1 << 14,
|
||||||
|
|
||||||
// Owner flag
|
// Owner flag
|
||||||
|
|||||||
@ -19,10 +19,13 @@ export function data400(message: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function data403(message: string) {
|
export function data403(message: string) {
|
||||||
return data({
|
return data(
|
||||||
success: false,
|
{
|
||||||
message,
|
success: false,
|
||||||
});
|
message,
|
||||||
|
},
|
||||||
|
{ status: 403 },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function data404(message: string) {
|
export function data404(message: string) {
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user