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.
159 lines
4.7 KiB
TypeScript
159 lines
4.7 KiB
TypeScript
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";
|
|
|
|
export interface AdminStatus {
|
|
icon: string;
|
|
message?: TemplateResult;
|
|
}
|
|
|
|
/**
|
|
* Abstract base class for admin status cards with robust state management
|
|
*
|
|
* @template T - Type of the primary data value used in the card
|
|
*/
|
|
export abstract class AdminStatusCard<T> extends AggregateCard {
|
|
// Current data value state
|
|
@state()
|
|
value?: T;
|
|
|
|
// Current status state derived from value
|
|
@state()
|
|
protected status?: AdminStatus;
|
|
|
|
// Current error state if any request fails
|
|
@state()
|
|
protected error?: APIError;
|
|
|
|
// Abstract methods to be implemented by subclasses
|
|
abstract getPrimaryValue(): Promise<T>;
|
|
abstract getStatus(value: T): Promise<AdminStatus>;
|
|
|
|
constructor() {
|
|
super();
|
|
// Proper binding for event handler
|
|
this.fetchData = this.fetchData.bind(this);
|
|
// Register refresh event listener
|
|
this.addEventListener(EVENT_REFRESH, this.fetchData);
|
|
}
|
|
|
|
// Lifecycle method: Called when component is added to DOM
|
|
connectedCallback(): void {
|
|
super.connectedCallback();
|
|
// Initial data fetch
|
|
this.fetchData();
|
|
}
|
|
|
|
/**
|
|
* Fetch primary data and handle errors
|
|
*/
|
|
private fetchData() {
|
|
this.getPrimaryValue()
|
|
.then((value: T) => {
|
|
this.value = value; // Triggers shouldUpdate
|
|
this.error = undefined;
|
|
})
|
|
.catch(async (error: unknown) => {
|
|
this.status = undefined;
|
|
this.error = await parseAPIResponseError(error);
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Lit lifecycle method: Determine if component should update
|
|
*
|
|
* @param changed - Map of changed properties
|
|
* @returns boolean indicating if update should proceed
|
|
*/
|
|
shouldUpdate(changed: PropertyValues<this>) {
|
|
if (changed.has("value") && this.value !== undefined) {
|
|
// When value changes, fetch new status
|
|
this.getStatus(this.value)
|
|
.then((status) => {
|
|
this.status = status;
|
|
this.error = undefined;
|
|
})
|
|
.catch(async (error: unknown) => {
|
|
this.status = undefined;
|
|
this.error = await parseAPIResponseError(error);
|
|
});
|
|
|
|
// Prevent immediate re-render if only value changed
|
|
if (changed.size === 1) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Render the primary value display
|
|
*
|
|
* @returns TemplateResult displaying the value
|
|
*/
|
|
protected renderValue(): TemplateResult {
|
|
return html`${this.value}`;
|
|
}
|
|
|
|
/**
|
|
* Render status state
|
|
*
|
|
* @param status - AdminStatus object containing icon and message
|
|
* @returns TemplateResult for status display
|
|
*/
|
|
private renderStatus(status: AdminStatus): TemplateResult {
|
|
return html`
|
|
<p><i class="${status.icon}"></i> ${this.renderValue()}</p>
|
|
${status.message ? html`<p class="subtext">${status.message}</p>` : nothing}
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Render error state
|
|
*
|
|
* @param error - Error message to display
|
|
* @returns TemplateResult for error display
|
|
*/
|
|
private renderError(error: string): TemplateResult {
|
|
return html`
|
|
<p><i class="fa fa-times"></i> ${msg("Failed to fetch")}</p>
|
|
<p class="subtext">${error}</p>
|
|
`;
|
|
}
|
|
|
|
/**
|
|
* Render loading state
|
|
*
|
|
* @returns TemplateResult for loading spinner
|
|
*/
|
|
private renderLoading(): TemplateResult {
|
|
return html`<ak-spinner size="${PFSize.Large}"></ak-spinner>`;
|
|
}
|
|
|
|
/**
|
|
* Main render method that selects appropriate state display
|
|
*
|
|
* @returns TemplateResult for current component state
|
|
*/
|
|
renderInner(): TemplateResult {
|
|
return html`
|
|
<p class="center-value">
|
|
${
|
|
this.status
|
|
? this.renderStatus(this.status) // Status available
|
|
: this.error
|
|
? this.renderError(pluckErrorDetail(this.error)) // Error state
|
|
: this.renderLoading() // Loading state
|
|
}
|
|
</p>
|
|
`;
|
|
}
|
|
}
|