web/admin: improve error handling (#11212)
Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
@ -1,5 +1,6 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { parseAPIError } from "@goauthentik/common/errors";
|
||||
import "@goauthentik/components/ak-radio-input";
|
||||
import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
@ -24,7 +25,6 @@ import {
|
||||
type TransactionApplicationRequest,
|
||||
type TransactionApplicationResponse,
|
||||
ValidationError,
|
||||
ValidationErrorFromJSON,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
import BasePanel from "../BasePanel";
|
||||
@ -133,9 +133,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
})
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
.catch(async (resolution: any) => {
|
||||
const errors = (this.errors = ValidationErrorFromJSON(
|
||||
await resolution.response.json(),
|
||||
));
|
||||
const errors = await parseAPIError(resolution);
|
||||
this.dispatchWizardUpdate({
|
||||
update: {
|
||||
...this.wizard,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { parseAPIError } from "@goauthentik/common/errors";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/CodeMirror";
|
||||
import { CodeMirrorMode } from "@goauthentik/elements/CodeMirror";
|
||||
@ -22,7 +23,7 @@ import {
|
||||
PromptTypeEnum,
|
||||
ResponseError,
|
||||
StagesApi,
|
||||
ValidationErrorFromJSON,
|
||||
ValidationError,
|
||||
} from "@goauthentik/api";
|
||||
|
||||
class PreviewStageHost implements StageHost {
|
||||
@ -83,10 +84,8 @@ export class PromptForm extends ModelForm<Prompt, string> {
|
||||
});
|
||||
this.previewError = undefined;
|
||||
} catch (exc) {
|
||||
const errorMessage = ValidationErrorFromJSON(
|
||||
await (exc as ResponseError).response.json(),
|
||||
);
|
||||
this.previewError = errorMessage.nonFieldErrors;
|
||||
const errorMessage = parseAPIError(exc as ResponseError);
|
||||
this.previewError = (errorMessage as ValidationError).nonFieldErrors;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -12,11 +12,17 @@ export class RequestError extends Error {}
|
||||
|
||||
export type APIErrorTypes = ValidationError | GenericError;
|
||||
|
||||
export const HTTP_BAD_REQUEST = 400;
|
||||
export const HTTP_INTERNAL_SERVICE_ERROR = 500;
|
||||
|
||||
export async function parseAPIError(error: Error): Promise<APIErrorTypes> {
|
||||
if (!(error instanceof ResponseError)) {
|
||||
return error;
|
||||
}
|
||||
if (error.response.status < 400 || error.response.status > 499) {
|
||||
if (
|
||||
error.response.status < HTTP_BAD_REQUEST ||
|
||||
error.response.status >= HTTP_INTERNAL_SERVICE_ERROR
|
||||
) {
|
||||
return error;
|
||||
}
|
||||
const body = await error.response.json();
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { parseAPIError } from "@goauthentik/common/errors";
|
||||
import { MessageLevel } from "@goauthentik/common/messages";
|
||||
import { camelToSnake, convertToSlug, dateToUTC } from "@goauthentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
@ -6,6 +7,7 @@ import { HorizontalFormElement } from "@goauthentik/elements/forms/HorizontalFor
|
||||
import { PreventFormSubmit } from "@goauthentik/elements/forms/helpers";
|
||||
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { property, state } from "lit/decorators.js";
|
||||
|
||||
@ -18,7 +20,7 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro
|
||||
import PFSwitch from "@patternfly/patternfly/components/Switch/switch.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { ResponseError, ValidationError, ValidationErrorFromJSON } from "@goauthentik/api";
|
||||
import { ResponseError, ValidationError, instanceOfValidationError } from "@goauthentik/api";
|
||||
|
||||
export class APIError extends Error {
|
||||
constructor(public response: ValidationError) {
|
||||
@ -124,9 +126,6 @@ export function serializeForm<T extends KeyUnknown>(
|
||||
return json as unknown as T;
|
||||
}
|
||||
|
||||
const HTTP_BAD_REQUEST = 400;
|
||||
const HTTP_INTERNAL_SERVICE_ERROR = 500;
|
||||
|
||||
/**
|
||||
* Form
|
||||
*
|
||||
@ -307,18 +306,9 @@ export abstract class Form<T> extends AKElement {
|
||||
return response;
|
||||
} catch (ex) {
|
||||
if (ex instanceof ResponseError) {
|
||||
let msg = ex.response.statusText;
|
||||
if (
|
||||
ex.response.status >= HTTP_BAD_REQUEST &&
|
||||
ex.response.status < HTTP_INTERNAL_SERVICE_ERROR
|
||||
) {
|
||||
const errorMessage = ValidationErrorFromJSON(await ex.response.json());
|
||||
if (!errorMessage) {
|
||||
return errorMessage;
|
||||
}
|
||||
if (errorMessage instanceof Error) {
|
||||
throw errorMessage;
|
||||
}
|
||||
let errorMessage = ex.response.statusText;
|
||||
const error = await parseAPIError(ex);
|
||||
if (instanceOfValidationError(error)) {
|
||||
// assign all input-related errors to their elements
|
||||
const elements =
|
||||
this.shadowRoot?.querySelectorAll<HorizontalFormElement>(
|
||||
@ -330,26 +320,28 @@ export abstract class Form<T> extends AKElement {
|
||||
if (!elementName) {
|
||||
return;
|
||||
}
|
||||
if (camelToSnake(elementName) in errorMessage) {
|
||||
element.errorMessages = errorMessage[camelToSnake(elementName)];
|
||||
if (camelToSnake(elementName) in error) {
|
||||
element.errorMessages = (error as ValidationError)[
|
||||
camelToSnake(elementName)
|
||||
];
|
||||
element.invalid = true;
|
||||
} else {
|
||||
element.errorMessages = [];
|
||||
element.invalid = false;
|
||||
}
|
||||
});
|
||||
if (errorMessage.nonFieldErrors) {
|
||||
this.nonFieldErrors = errorMessage.nonFieldErrors;
|
||||
if ((error as ValidationError).nonFieldErrors) {
|
||||
this.nonFieldErrors = (error as ValidationError).nonFieldErrors;
|
||||
}
|
||||
errorMessage = msg("Invalid update request.");
|
||||
// Only change the message when we have `detail`.
|
||||
// Everything else is handled in the form.
|
||||
if ("detail" in errorMessage) {
|
||||
msg = errorMessage.detail;
|
||||
if ("detail" in (error as ValidationError)) {
|
||||
errorMessage = (error as ValidationError).detail;
|
||||
}
|
||||
}
|
||||
// error is local or not from rest_framework
|
||||
showMessage({
|
||||
message: msg,
|
||||
message: errorMessage,
|
||||
level: MessageLevel.error,
|
||||
});
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/messages/Message";
|
||||
import { APIMessage } from "@goauthentik/elements/messages/Message";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
@ -20,6 +21,9 @@ export function showMessage(message: APIMessage, unique = false): void {
|
||||
if (!container) {
|
||||
throw new SentryIgnoredError("failed to find message container");
|
||||
}
|
||||
if (message.message.trim() === "") {
|
||||
message.message = msg("Error");
|
||||
}
|
||||
container.addMessage(message, unique);
|
||||
container.requestUpdate();
|
||||
}
|
||||
|
Reference in New Issue
Block a user