Files
authentik/web/src/admin/groups/MemberSelectForm.ts
Teffen Ellis b88d082947 web/a11y: Modals, Command Palette (Merge branch) (#17812)
* Use project relative paths.

* Fix tests.

* Fix types.

* Clean up admin imports.

* Move admin import.

* Remove or replace references to admin.

* Typo fix.

* Flesh out ak-modal, about modal.

* Flesh out lazy modal.

* Fix portal elements not using dialog scope.

* Fix url parameters, wizards.

* Fix invokers, lazy load.

* Fix theming.

* Add placeholders, help.

* Flesh out command palette.

Flesh out styles, command invokers.

Continue clean up.

Allow slotted content.

Flesh out.

* Flesh out edit invoker. Prep groups.

* Fix odd labeling, legacy situations.

* Prepare deprecation of table modal. Clean up serialization.

* Tidy types.

* Port provider select modal.

* Port member select form.

* Flesh out role modal. Fix loading state.

* Port user group form.

* Fix spellcheck.

* Fix dialog detection.

* Revise types.

* Port rac launch modal.

* Remove deprecated table modal.

* Consistent form action placement.

* Consistent casing.

* Consistent alignment.

* Use more appropriate description.

* Flesh out icon. Fix alignment, colors.

* Flesh out user search.

* Consistent save button.

* Clean up labels.

* Reduce warning noise.

* Clean up label.

* Use attribute e2e expects.

* Use directive. Fix lifecycle

* Fix frequent un-memoized entries.

* Fix up closedBy detection.

* Tidy alignment.

* Fix types, composition.

* Fix labels, tests.

* Fix up impersonation, labels.

* Flesh out. Fix refresh after submit.

* Flesh out basic modal test.

* Fix ARIA.

* Flesh out roles test.

* Revise selectors.

* Clean up selectors.

* Fix impersonation labels, form references.

* Fix messages appearing under modals.

* Ensure reason is parsed.

* Flesh out impersonation test.

* Flesh out impersonate test.

* Flesh out application tests. Clean up toolbar header, ARIA.

* Flesh out wizard test.

* Refine weight, order.

* Fix up initial values, selectors.

* Fix tests.

* Fix selector.
2026-03-25 06:07:29 +00:00

125 lines
4.2 KiB
TypeScript

import "#components/ak-status-label";
import "#elements/buttons/SpinnerButton/index";
import { DEFAULT_CONFIG } from "#common/api/config";
import { PaginatedResponse, Table, TableColumn, Timestamp } from "#elements/table/Table";
import { SlottedTemplateResult } from "#elements/types";
import { CoreApi, CoreUsersListRequest, User } from "@goauthentik/api";
import { match } from "ts-pattern";
import { msg } from "@lit/localize";
import { css, html } from "lit";
import { customElement } from "lit/decorators.js";
// Leaving room in the future for a multi-state control if someone somehow needs to filter inactive
// users as well.
type UserListFilter = "active" | "all";
type UserListRequestFilter = Partial<Pick<CoreUsersListRequest, "isActive">>;
@customElement("ak-group-member-select-form")
export class MemberSelectForm extends Table<User> {
static styles = [
...super.styles,
css`
.show-disabled-toggle-group {
margin-inline-start: 0.5rem;
}
[part="toolbar"] {
gap: var(--pf-global--spacer--md);
}
`,
];
public override searchPlaceholder = msg("Search for users by username or display name...");
public override searchLabel = msg("Search Users");
public override label = msg("Select Users");
public overridesupportsQL = true;
public override checkbox = true;
public override checkboxChip = true;
protected override searchEnabled = true;
userListFilter: UserListFilter = "active";
order = "username";
// The `userListRequestFilter` clause is necessary because the back-end for searches is
// tri-state: `isActive: true` will only show active users, `isActive: false` will show only
// inactive users; only when it's _missing_ will you get all users.
async apiEndpoint(): Promise<PaginatedResponse<User>> {
const userListRequestFilter: UserListRequestFilter = match(this.userListFilter)
.with("all", () => ({}))
.with("active", () => ({ isActive: true }))
.exhaustive();
return new CoreApi(DEFAULT_CONFIG).coreUsersList({
...(await this.defaultEndpointConfig()),
...userListRequestFilter,
includeGroups: false,
});
}
protected override rowLabel(item: User): string | null {
return item.username ?? item.name ?? null;
}
protected columns: TableColumn[] = [
[msg("Name"), "username"],
[msg("Active"), "is_active"],
[msg("Last login"), "last_login"],
];
renderToolbarAfter() {
const toggleShowDisabledUsers = () => {
this.userListFilter = this.userListFilter === "all" ? "active" : "all";
this.page = 1;
this.fetch();
};
return html`<div class="pf-c-toolbar__group pf-m-filter-group">
<div class="pf-c-toolbar__item pf-m-search-filter">
<div class="pf-c-input-group show-disabled-toggle-group">
<label class="pf-c-switch">
<input
class="pf-c-switch__input"
type="checkbox"
?checked=${this.userListFilter === "all"}
@change=${toggleShowDisabledUsers}
/>
<span class="pf-c-switch__toggle">
<span class="pf-c-switch__toggle-icon">
<i class="fas fa-check" aria-hidden="true"></i>
</span>
</span>
<span class="pf-c-switch__label">${msg("Show inactive users")}</span>
</label>
</div>
</div>
</div>`;
}
row(item: User): SlottedTemplateResult[] {
return [
html`<div>${item.username}</div>
<small>${item.name}</small>`,
html` <ak-status-label type="warning" ?good=${item.isActive}></ak-status-label>`,
Timestamp(item.lastLogin),
];
}
renderSelectedChip(item: User): SlottedTemplateResult {
return item.username;
}
}
declare global {
interface HTMLElementTagNameMap {
"ak-group-member-select-form": MemberSelectForm;
}
}