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