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:
		| @ -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> ${error}</p> | ||||
|             <p class="subtext">${msg("Failed to fetch")}</p> | ||||
|             <p><i class="fa fa-times"></i> ${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> | ||||
|  | ||||
| @ -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> | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Teffen Ellis
					Teffen Ellis