web/admin: migrate flows to web forms

Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
Jens Langhammer 2021-03-29 22:24:46 +02:00
parent 3a2f285a87
commit e96d2fa666
5 changed files with 205 additions and 26 deletions

View File

@ -24,10 +24,6 @@ export class AdminURLManager {
return `/administration/outpost_service_connections/${rest}`; return `/administration/outpost_service_connections/${rest}`;
} }
static flows(rest: string): string {
return `/administration/flows/${rest}`;
}
static stages(rest: string): string { static stages(rest: string): string {
return `/administration/stages/${rest}`; return `/administration/stages/${rest}`;
} }

View File

@ -63,7 +63,7 @@ export class ApplicationForm extends Form<Application> {
${Array.from(m).map(([group, providers]) => { ${Array.from(m).map(([group, providers]) => {
return html`<optgroup label=${group}> return html`<optgroup label=${group}>
${providers.map(p => { ${providers.map(p => {
const selected = (this.application?.provider?.pk === p.pk) || (this.provider === p.pk) const selected = (this.application?.provider?.pk === p.pk) || (this.provider === p.pk);
return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>${p.name}</option>`; return html`<option ?selected=${selected} value=${ifDefined(p.pk)}>${p.name}</option>`;
})} })}
</optgroup>`; </optgroup>`;

View File

@ -0,0 +1,115 @@
import { Flow, FlowDesignationEnum, FlowsApi } from "authentik-api";
import { gettext } from "django";
import { customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../api/Config";
import { Form } from "../../elements/forms/Form";
import { ifDefined } from "lit-html/directives/if-defined";
import "../../elements/forms/HorizontalFormElement";
@customElement("ak-flow-form")
export class FlowForm extends Form<Flow> {
@property({attribute: false})
flow?: Flow;
getSuccessMessage(): string {
if (this.flow) {
return gettext("Successfully updated flow.");
} else {
return gettext("Successfully created flow.");
}
}
send = (data: Flow): Promise<void | Flow> => {
let writeOp: Promise<Flow>;
if (this.flow) {
writeOp = new FlowsApi(DEFAULT_CONFIG).flowsInstancesUpdate({
slug: this.flow.slug,
data: data
});
} else {
writeOp = new FlowsApi(DEFAULT_CONFIG).flowsInstancesCreate({
data: data
});
}
const background = this.getFormFile();
if (background) {
return writeOp.then(flow => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesSetBackground({
slug: flow.slug,
file: background
});
});
}
return writeOp;
};
renderDesignations(): TemplateResult {
return html`
<option value=${FlowDesignationEnum.Authentication} ?selected=${this.flow?.designation === FlowDesignationEnum.Authentication}>
${gettext("Authentication")}
</option>
<option value=${FlowDesignationEnum.Authorization} ?selected=${this.flow?.designation === FlowDesignationEnum.Authorization}>
${gettext("Authorization")}
</option>
<option value=${FlowDesignationEnum.Enrollment} ?selected=${this.flow?.designation === FlowDesignationEnum.Enrollment}>
${gettext("Enrollment")}
</option>
<option value=${FlowDesignationEnum.Invalidation} ?selected=${this.flow?.designation === FlowDesignationEnum.Invalidation}>
${gettext("Invalidation")}
</option>
<option value=${FlowDesignationEnum.Recovery} ?selected=${this.flow?.designation === FlowDesignationEnum.Recovery}>
${gettext("Recovery")}
</option>
<option value=${FlowDesignationEnum.StageConfiguration} ?selected=${this.flow?.designation === FlowDesignationEnum.StageConfiguration}>
${gettext("Stage Configuration")}
</option>
<option value=${FlowDesignationEnum.Unenrollment} ?selected=${this.flow?.designation === FlowDesignationEnum.Unenrollment}>
${gettext("Unenrollment")}
</option>
`;
}
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${gettext("Name")}
?required=${true}
name="name">
<input type="text" value="${ifDefined(this.flow?.name)}" class="pf-c-form-control" required>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Title")}
?required=${true}
name="title">
<input type="text" value="${ifDefined(this.flow?.title)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Shown as the Title in Flow pages.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Name")}
?required=${true}
name="slug">
<input type="text" value="${ifDefined(this.flow?.slug)}" class="pf-c-form-control" required>
<p class="pf-c-form__helper-text">${gettext("Visible in the URL.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Designation")}
?required=${true}
name="designation">
<select class="pf-c-form-control">
<option value="" ?selected=${this.flow?.designation === undefined}>---------</option>
${this.renderDesignations()}
</select>
<p class="pf-c-form__helper-text">${gettext("Decides what this Flow is used for. For example, the Authentication flow is redirect to when an un-authenticated user visits authentik.")}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
label=${gettext("Background")}
name="background">
<input type="file" value="${ifDefined(this.flow?.background)}" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${gettext("Background shown during execution.")}</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -0,0 +1,38 @@
import { Flow, FlowsApi } from "authentik-api";
import { gettext } from "django";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
import { DEFAULT_CONFIG } from "../../api/Config";
import { Form } from "../../elements/forms/Form";
import "../../elements/forms/HorizontalFormElement";
@customElement("ak-flow-import-form")
export class FlowImportForm extends Form<Flow> {
getSuccessMessage(): string {
return gettext("Successfully imported flow.");
}
// eslint-disable-next-line
send = (data: Flow): Promise<void> => {
const file = this.getFormFile();
if (!file) {
throw new Error("No form data");
}
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesImportFlow({
file: file
});
};
renderForm(): TemplateResult {
return html`<form class="pf-c-form pf-m-horizontal">
<ak-form-element-horizontal
label=${gettext("Flow")}
name="flow">
<input type="file" value="" class="pf-c-form-control">
<p class="pf-c-form__helper-text">${gettext("Background shown during execution.")}</p>
</ak-form-element-horizontal>
</form>`;
}
}

View File

@ -6,11 +6,13 @@ import { TablePage } from "../../elements/table/TablePage";
import "../../elements/buttons/ModalButton"; import "../../elements/buttons/ModalButton";
import "../../elements/buttons/SpinnerButton"; import "../../elements/buttons/SpinnerButton";
import "../../elements/forms/DeleteForm"; import "../../elements/forms/DeleteForm";
import "../../elements/forms/ModalForm";
import "./FlowForm";
import "./FlowImportForm";
import { TableColumn } from "../../elements/table/Table"; import { TableColumn } from "../../elements/table/Table";
import { PAGE_SIZE } from "../../constants"; import { PAGE_SIZE } from "../../constants";
import { Flow, FlowsApi } from "authentik-api"; import { Flow, FlowsApi } from "authentik-api";
import { DEFAULT_CONFIG } from "../../api/Config"; import { DEFAULT_CONFIG } from "../../api/Config";
import { AdminURLManager } from "../../api/legacy";
@customElement("ak-flow-list") @customElement("ak-flow-list")
export class FlowListPage extends TablePage<Flow> { export class FlowListPage extends TablePage<Flow> {
@ -60,48 +62,76 @@ export class FlowListPage extends TablePage<Flow> {
html`${Array.from(item.stages || []).length}`, html`${Array.from(item.stages || []).length}`,
html`${Array.from(item.policies || []).length}`, html`${Array.from(item.policies || []).length}`,
html` html`
<ak-modal-button href="${AdminURLManager.flows(`${item.pk}/update/`)}"> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-secondary"> <span slot="submit">
${gettext("Update")}
</span>
<span slot="header">
${gettext("Update Flow")}
</span>
<ak-flow-form slot="form" .flow=${item}>
</ak-flow-form>
<button slot="trigger" class="pf-c-button pf-m-secondary">
${gettext("Edit")} ${gettext("Edit")}
</ak-spinner-button> </button>
<div slot="modal"></div> </ak-forms-modal>
</ak-modal-button>
<ak-forms-delete <ak-forms-delete
.obj=${item} .obj=${item}
objectLabel=${gettext("Flow")} objectLabel=${gettext("Flow")}
.delete=${() => { .delete=${() => {
return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDelete({ return new FlowsApi(DEFAULT_CONFIG).flowsInstancesDelete({
slug: item.slug || "" slug: item.slug
}); });
}}> }}>
<button slot="trigger" class="pf-c-button pf-m-danger"> <button slot="trigger" class="pf-c-button pf-m-danger">
${gettext("Delete")} ${gettext("Delete")}
</button> </button>
</ak-forms-delete> </ak-forms-delete>
<a class="pf-c-button pf-m-secondary ak-root-link" href="${AdminURLManager.flows(`${item.pk}/execute/?next=/%23${window.location.href}`)}"> <button
class="pf-c-button pf-m-secondary ak-root-link"
@click=${() => {
new FlowsApi(DEFAULT_CONFIG).flowsInstancesExecute({
slug: item.slug
}).then(link => {
window.location.assign(`${link.link}?next=/%23${window.location.href}`);
});
}}>
${gettext("Execute")} ${gettext("Execute")}
</a> </button>
<a class="pf-c-button pf-m-secondary ak-root-link" href="${`${DEFAULT_CONFIG.basePath}/flows/instances/${item.slug}/export/`}"> <a class="pf-c-button pf-m-secondary ak-root-link" href="${`${DEFAULT_CONFIG.basePath}/flows/instances/${item.slug}/export/`}">
${gettext("Export")} ${gettext("Export")}
</a> </a>`,
`,
]; ];
} }
renderToolbar(): TemplateResult { renderToolbar(): TemplateResult {
return html` return html`
<ak-modal-button href=${AdminURLManager.flows("create/")}> <ak-forms-modal>
<ak-spinner-button slot="trigger" class="pf-m-primary"> <span slot="submit">
${gettext("Create")} ${gettext("Create")}
</ak-spinner-button> </span>
<div slot="modal"></div> <span slot="header">
</ak-modal-button> ${gettext("Create Flow")}
<ak-modal-button href=${AdminURLManager.flows("import/")}> </span>
<ak-spinner-button slot="trigger" class="pf-m-secondary"> <ak-flow-form slot="form">
</ak-flow-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Create")}
</button>
</ak-forms-modal>
<ak-forms-modal>
<span slot="submit">
${gettext("Import")} ${gettext("Import")}
</ak-spinner-button> </span>
<div slot="modal"></div> <span slot="header">
</ak-modal-button> ${gettext("Import Flow")}
</span>
<ak-flow-import-form slot="form">
</ak-flow-import-form>
<button slot="trigger" class="pf-c-button pf-m-primary">
${gettext("Import")}
</button>
</ak-forms-modal>
${super.renderToolbar()} ${super.renderToolbar()}
`; `;
} }