Files
authentik/web/src/admin/common/ak-flow-search/FlowSearch.ts
Jens L. 3c0a8f4641 web/admin: improve invalidation flow default & field grouping (#11769)
* web/admin: auto-select provider invalidation flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* new structuring

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix missing ldap unbind flow

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* unrelated: add enter for redirect

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-10-23 13:58:44 +02:00

146 lines
4.5 KiB
TypeScript

import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { AKElement } from "@goauthentik/elements/Base";
import { SearchSelect } from "@goauthentik/elements/forms/SearchSelect";
import "@goauthentik/elements/forms/SearchSelect";
import { CustomListenerElement } from "@goauthentik/elements/utils/eventEmitter";
import { html } from "lit";
import { property, query } from "lit/decorators.js";
import { FlowsApi, FlowsInstancesListDesignationEnum } from "@goauthentik/api";
import type { Flow, FlowsInstancesListRequest } from "@goauthentik/api";
export function renderElement(flow: Flow) {
return RenderFlowOption(flow);
}
export function renderDescription(flow: Flow) {
return html`${flow.slug}`;
}
export function getFlowValue(flow: Flow | undefined): string | undefined {
return flow?.pk;
}
/**
* FlowSearch
*
* A wrapper around SearchSelect that understands the basic semantics of querying about Flows. This
* code eliminates the long blocks of unreadable invocation that were embedded in every provider, as well as in
* sources, brands, and applications.
*
*/
export class FlowSearch<T extends Flow> extends CustomListenerElement(AKElement) {
/**
* The type of flow we're looking for.
*
* @attr
*/
@property({ type: String })
flowType?: FlowsInstancesListDesignationEnum;
/**
* The id of the current flow, if any. For stages where the flow is already defined.
*
* @attr
*/
@property({ type: String })
currentFlow?: string | undefined;
/**
* If true, it is not valid to leave the flow blank.
*
* @attr
*/
@property({ type: Boolean })
required?: boolean = false;
@query("ak-search-select")
search!: SearchSelect<T>;
/**
* When specified and the object instance does not have a flow selected, auto-select the flow with the given slug.
*
* @attr
*/
@property()
defaultFlowSlug?: string;
@property({ type: String })
name: string | null | undefined;
selectedFlow?: T;
get value() {
return this.selectedFlow ? getFlowValue(this.selectedFlow) : null;
}
constructor() {
super();
this.fetchObjects = this.fetchObjects.bind(this);
this.selected = this.selected.bind(this);
this.handleSearchUpdate = this.handleSearchUpdate.bind(this);
}
handleSearchUpdate(ev: CustomEvent) {
ev.stopPropagation();
this.selectedFlow = ev.detail.value;
this.dispatchEvent(new InputEvent("input", { bubbles: true, composed: true }));
}
async fetchObjects(query?: string): Promise<Flow[]> {
const args: FlowsInstancesListRequest = {
ordering: "slug",
designation: this.flowType,
...(query !== undefined ? { search: query } : {}),
};
const flows = await new FlowsApi(DEFAULT_CONFIG).flowsInstancesList(args);
return flows.results;
}
/* This is the most commonly overridden method of this class. About half of the Flow Searches
* use this method, but several have more complex needs, such as relating to the brand, or just
* returning false.
*/
selected(flow: Flow): boolean {
let selected = this.currentFlow === flow.pk;
if (!this.currentFlow && this.defaultFlowSlug && flow.slug === this.defaultFlowSlug) {
selected = true;
}
return selected;
}
connectedCallback() {
super.connectedCallback();
const horizontalContainer = this.closest("ak-form-element-horizontal[name]");
if (!horizontalContainer) {
throw new Error("This search can only be used in a named ak-form-element-horizontal");
}
const name = horizontalContainer.getAttribute("name");
const myName = this.getAttribute("name");
if (name !== null && name !== myName) {
this.setAttribute("name", name);
}
}
render() {
return html`
<ak-search-select
.fetchObjects=${this.fetchObjects}
.selected=${this.selected}
.renderElement=${renderElement}
.renderDescription=${renderDescription}
.value=${getFlowValue}
.name=${this.name}
@ak-change=${this.handleSearchUpdate}
?blankable=${!this.required}
>
</ak-search-select>
`;
}
}
export default FlowSearch;