web: Normalize client-side error handling (#13595)

web: Clean up error handling. Prep for permission checks.

- Add clearer reporting for API and network errors.
- Tidy error checking.
- Partial type safety for events.
This commit is contained in:
Teffen Ellis
2025-04-07 19:50:41 +02:00
committed by GitHub
parent e93b2a1a75
commit 363d655378
53 changed files with 901 additions and 493 deletions

View File

@ -1,13 +1,16 @@
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { PFSize } from "@goauthentik/common/enums.js";
import {
APIError,
parseAPIResponseError,
pluckErrorDetail,
} from "@goauthentik/common/errors/network";
import { AggregateCard } from "@goauthentik/elements/cards/AggregateCard";
import { msg } from "@lit/localize";
import { PropertyValues, TemplateResult, html, nothing } from "lit";
import { state } from "lit/decorators.js";
import { ResponseError } from "@goauthentik/api";
export interface AdminStatus {
icon: string;
message?: TemplateResult;
@ -29,7 +32,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
// Current error state if any request fails
@state()
protected error?: string;
protected error?: APIError;
// Abstract methods to be implemented by subclasses
abstract getPrimaryValue(): Promise<T>;
@ -59,9 +62,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
this.value = value; // Triggers shouldUpdate
this.error = undefined;
})
.catch((err: ResponseError) => {
.catch(async (error: unknown) => {
this.status = undefined;
this.error = err?.response?.statusText ?? msg("Unknown error");
this.error = await parseAPIResponseError(error);
});
}
@ -79,9 +82,9 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
this.status = status;
this.error = undefined;
})
.catch((err: ResponseError) => {
.catch(async (error: unknown) => {
this.status = undefined;
this.error = err?.response?.statusText ?? msg("Unknown error");
this.error = await parseAPIResponseError(error);
});
// Prevent immediate re-render if only value changed
@ -120,8 +123,8 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
*/
private renderError(error: string): TemplateResult {
return html`
<p><i class="fa fa-times"></i>&nbsp;${error}</p>
<p class="subtext">${msg("Failed to fetch")}</p>
<p><i class="fa fa-times"></i>&nbsp;${msg("Failed to fetch")}</p>
<p class="subtext">${error}</p>
`;
}
@ -146,7 +149,7 @@ export abstract class AdminStatusCard<T> extends AggregateCard {
this.status
? this.renderStatus(this.status) // Status available
: this.error
? this.renderError(this.error) // Error state
? this.renderError(pluckErrorDetail(this.error)) // Error state
: this.renderLoading() // Loading state
}
</p>

View File

@ -10,6 +10,7 @@ import "@goauthentik/elements/buttons/ModalButton";
import "@goauthentik/elements/buttons/SpinnerButton";
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
import { Table, TableColumn } from "@goauthentik/elements/table/Table";
import { SlottedTemplateResult } from "@goauthentik/elements/types";
import { msg } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
@ -68,7 +69,7 @@ export class RecentEventsCard extends Table<Event> {
</div>`;
}
row(item: EventWithContext): TemplateResult[] {
row(item: EventWithContext): SlottedTemplateResult[] {
return [
html`<div><a href="${`#/events/log/${item.pk}`}">${actionToLabel(item.action)}</a></div>
<small>${item.app}</small>`,
@ -81,7 +82,11 @@ export class RecentEventsCard extends Table<Event> {
];
}
renderEmpty(): TemplateResult {
renderEmpty(inner?: SlottedTemplateResult): TemplateResult {
if (this.error) {
return super.renderEmpty(inner);
}
return super.renderEmpty(
html`<ak-empty-state header=${msg("No Events found.")}>
<div slot="body">${msg("No matching events could be found.")}</div>