web: A better fix

This fix creates a new property of Table, 'clearOnRefresh', which
automatically empties the `selectedElements` list when an EVENT_REFRESH
event completes.  Set this flag on any table that uses the
`selectedElements` list for bulk deletion; this ensures that stale
data in the `selectedElements` list will not persist and interfere
with future deletion events.
This commit is contained in:
Ken Sternberg 2024-01-10 12:00:06 -08:00
parent 7bb30bea55
commit 078fe8ef06
40 changed files with 52 additions and 10 deletions

View file

@ -43,6 +43,7 @@ export class ApplicationListPage extends TablePage<Application> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -58,6 +58,7 @@ export class BlueprintListPage extends TablePage<BlueprintInstance> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -29,6 +29,7 @@ import {
export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;

View file

@ -35,6 +35,7 @@ import {
@customElement("ak-enterprise-license-list")
export class EnterpriseLicenseListPage extends TablePage<License> {
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;

View file

@ -27,6 +27,7 @@ import {
export class RuleListPage extends TablePage<NotificationRule> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;

View file

@ -38,6 +38,7 @@ export class TransportListPage extends TablePage<NotificationTransport> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -21,6 +21,7 @@ import { FlowStageBinding, FlowsApi } from "@goauthentik/api";
export class BoundStagesList extends Table<FlowStageBinding> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
@property()
target?: string;

View file

@ -37,6 +37,7 @@ export class FlowListPage extends TablePage<Flow> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "slug";

View file

@ -19,6 +19,7 @@ import { CoreApi, Group } from "@goauthentik/api";
@customElement("ak-group-list")
export class GroupListPage extends TablePage<Group> {
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;
}

View file

@ -87,6 +87,7 @@ export class RelatedGroupAdd extends Form<{ groups: string[] }> {
@customElement("ak-group-related-list")
export class RelatedGroupList extends Table<Group> {
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;
}

View file

@ -113,6 +113,7 @@ export class RelatedUserAdd extends Form<{ users: number[] }> {
export class RelatedUserList extends WithTenantConfig(WithCapabilitiesConfig(Table<User>)) {
expandable = true;
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;

View file

@ -107,6 +107,7 @@ export class OutpostListPage extends TablePage<Outpost> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -39,6 +39,7 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
}
checkbox = true;
clearOnRefresh = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<ServiceConnection>> {
const connections = await new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList(

View file

@ -3,7 +3,6 @@ import "@goauthentik/admin/policies/PolicyBindingForm";
import "@goauthentik/admin/policies/PolicyWizard";
import "@goauthentik/admin/users/UserForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { uiConfig } from "@goauthentik/common/ui/config";
import "@goauthentik/components/ak-status-label";
import { PFSize } from "@goauthentik/elements/Spinner";
@ -30,13 +29,7 @@ export class BoundPoliciesList extends Table<PolicyBinding> {
policyOnly = false;
checkbox = true;
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.selectedElements = [];
});
}
clearOnRefresh = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<PolicyBinding>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesBindingsList({

View file

@ -44,6 +44,7 @@ export class PolicyListPage extends TablePage<Policy> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -41,6 +41,7 @@ export class ReputationListPage extends TablePage<Reputation> {
order = "identifier";
checkbox = true;
clearOnRefresh = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<Reputation>> {
return new PoliciesApi(DEFAULT_CONFIG).policiesReputationScoresList({

View file

@ -41,6 +41,7 @@ export class PropertyMappingListPage extends TablePage<PropertyMapping> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -40,6 +40,7 @@ export class ProviderListPage extends TablePage<Provider> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -27,6 +27,7 @@ import {
export class EndpointListPage extends Table<Endpoint> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;

View file

@ -21,6 +21,7 @@ import { RbacApi, Role } from "@goauthentik/api";
@customElement("ak-role-list")
export class RoleListPage extends TablePage<Role> {
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;
}

View file

@ -21,6 +21,7 @@ export class RolePermissionGlobalTable extends Table<Permission> {
}
checkbox = true;
clearOnRefresh = true;
order = "content_type__app_label,content_type__model";

View file

@ -20,6 +20,7 @@ export class RolePermissionObjectTable extends Table<ExtraRoleObjectPermission>
}
checkbox = true;
clearOnRefresh = true;
apiEndpoint(page: number): Promise<PaginatedResponse<ExtraRoleObjectPermission>> {
return new RbacApi(DEFAULT_CONFIG).rbacPermissionsRolesList({

View file

@ -39,6 +39,7 @@ export class SourceListPage extends TablePage<Source> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -55,6 +55,7 @@ export class StageListPage extends TablePage<Stage> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -51,6 +51,7 @@ export class InvitationListPage extends TablePage<Invitation> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "expires";

View file

@ -33,6 +33,7 @@ export class PromptListPage extends TablePage<Prompt> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "name";

View file

@ -34,6 +34,7 @@ export class TenantListPage extends TablePage<Tenant> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "domain";

View file

@ -42,6 +42,7 @@ export class TokenListPage extends TablePage<Token> {
}
checkbox = true;
clearOnRefresh = true;
@property()
order = "expires";

View file

@ -19,6 +19,7 @@ export class UserAssignedGlobalPermissionsTable extends Table<Permission> {
userId?: number;
checkbox = true;
clearOnRefresh = true;
apiEndpoint(page: number): Promise<PaginatedResponse<Permission>> {
return new RbacApi(DEFAULT_CONFIG).rbacPermissionsList({

View file

@ -16,6 +16,7 @@ export class UserAssignedObjectPermissionsTable extends Table<ExtraUserObjectPer
userId?: number;
checkbox = true;
clearOnRefresh = true;
apiEndpoint(page: number): Promise<PaginatedResponse<ExtraUserObjectPermission>> {
return new RbacApi(DEFAULT_CONFIG).rbacPermissionsUsersList({

View file

@ -16,6 +16,7 @@ export class UserDeviceTable extends Table<Device> {
userId?: number;
checkbox = true;
clearOnRefresh = true;
async apiEndpoint(): Promise<PaginatedResponse<Device>> {
return new AuthenticatorsApi(DEFAULT_CONFIG)

View file

@ -94,6 +94,7 @@ const recoveryButtonStyles = css`
export class UserListPage extends WithTenantConfig(WithCapabilitiesConfig(TablePage<User>)) {
expandable = true;
checkbox = true;
clearOnRefresh = true;
searchEnabled(): boolean {
return true;

View file

@ -34,6 +34,7 @@ export class UserOAuthRefreshList extends Table<TokenModel> {
}
checkbox = true;
clearOnRefresh = true;
order = "-expires";
columns(): TableColumn[] {

View file

@ -29,6 +29,7 @@ export class RoleAssignedObjectPermissionTable extends Table<RoleAssignedObjectP
modelPermissions?: PaginatedPermissionList;
checkbox = true;
clearOnRefresh = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<RoleAssignedObjectPermission>> {
const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByRolesList({

View file

@ -29,6 +29,7 @@ export class UserAssignedObjectPermissionTable extends Table<UserAssignedObjectP
modelPermissions?: PaginatedPermissionList;
checkbox = true;
clearOnRefresh = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<UserAssignedObjectPermission>> {
const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersList({

View file

@ -119,6 +119,14 @@ export abstract class Table<T> extends AKElement implements TableLike {
@property({ type: Number })
page = getURLParam("tablePage", 1);
/** @prop
*
* Set if your `selectedElements` use of the selection box is to enable bulk-delete, so that
* stale data is cleared out when the API returns a new list minus the deleted entries.
*/
@property({ attribute: "clear-on-refresh", type: Boolean, reflect: true })
clearOnRefresh = false;
@property({ type: String })
order?: string;
@ -178,8 +186,11 @@ export abstract class Table<T> extends AKElement implements TableLike {
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.fetch();
this.addEventListener(EVENT_REFRESH, async () => {
await this.fetch();
if (this.clearOnRefresh) {
this.selectedElements = [];
}
});
}

View file

@ -25,6 +25,7 @@ export class AuthenticatedSessionList extends Table<AuthenticatedSession> {
}
checkbox = true;
clearOnRefresh = true;
order = "-expires";
columns(): TableColumn[] {

View file

@ -25,6 +25,7 @@ export class UserConsentList extends Table<UserConsent> {
}
checkbox = true;
clearOnRefresh = true;
order = "-expires";
columns(): TableColumn[] {

View file

@ -25,6 +25,7 @@ export class MFADevicesPage extends Table<Device> {
userSettings?: UserSetting[];
checkbox = true;
clearOnRefresh = true;
async apiEndpoint(): Promise<PaginatedResponse<Device>> {
const devices = await new AuthenticatorsApi(DEFAULT_CONFIG).authenticatorsAllList();

View file

@ -29,6 +29,7 @@ export class UserTokenList extends Table<Token> {
expandable = true;
checkbox = true;
clearOnRefresh = true;
@property()
order = "expires";