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; checkbox = true;
clearOnRefresh = true;
@property() @property()
order = "name"; order = "name";

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -29,6 +29,7 @@ export class UserAssignedObjectPermissionTable extends Table<UserAssignedObjectP
modelPermissions?: PaginatedPermissionList; modelPermissions?: PaginatedPermissionList;
checkbox = true; checkbox = true;
clearOnRefresh = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<UserAssignedObjectPermission>> { async apiEndpoint(page: number): Promise<PaginatedResponse<UserAssignedObjectPermission>> {
const perms = await new RbacApi(DEFAULT_CONFIG).rbacPermissionsAssignedByUsersList({ 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 }) @property({ type: Number })
page = getURLParam("tablePage", 1); 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 }) @property({ type: String })
order?: string; order?: string;
@ -178,8 +186,11 @@ export abstract class Table<T> extends AKElement implements TableLike {
constructor() { constructor() {
super(); super();
this.addEventListener(EVENT_REFRESH, () => { this.addEventListener(EVENT_REFRESH, async () => {
this.fetch(); await this.fetch();
if (this.clearOnRefresh) {
this.selectedElements = [];
}
}); });
} }

View File

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

View File

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

View File

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

View File

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