web/admin: rework initial wizard pages and add grid layout (#9668)
* remove @goauthentik/authentik as TS path Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial implementation Signed-off-by: Jens Langhammer <jens@goauthentik.io> * oh yeah Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format earlier changes Signed-off-by: Jens Langhammer <jens@goauthentik.io> * support plain alert Signed-off-by: Jens Langhammer <jens@goauthentik.io> * initial attempt at dedupe Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make it a base class Signed-off-by: Jens Langhammer <jens@goauthentik.io> * migrate all wizards Signed-off-by: Jens Langhammer <jens@goauthentik.io> * create type create mixin to dedupe more, add icon to source create Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add ldap icon Signed-off-by: Jens Langhammer <jens@goauthentik.io> * Optimised images with calibre/image-actions * match inverting we should probably replace all icons with coloured ones so we don't need to invert them...I guess Signed-off-by: Jens Langhammer <jens@goauthentik.io> * format Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make everything more explicit Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add icons to provider Signed-off-by: Jens Langhammer <jens@goauthentik.io> * add remaining provider icons Signed-off-by: Jens Langhammer <jens@goauthentik.io> * rework to not use inheritance Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix unrelated typo Signed-off-by: Jens Langhammer <jens@goauthentik.io> * make app wizard use grid layout Signed-off-by: Jens Langhammer <jens@goauthentik.io> * keep wizard height consistent Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix tests Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: authentik-automation[bot] <135050075+authentik-automation[bot]@users.noreply.github.com>
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import { PaginatedResponse } from "@goauthentik/authentik/elements/table/Table";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKChart } from "@goauthentik/elements/charts/Chart";
|
||||
import "@goauthentik/elements/forms/ConfirmationForm";
|
||||
import { PaginatedResponse } from "@goauthentik/elements/table/Table";
|
||||
import { ChartData, ChartOptions } from "chart.js";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
|
@ -25,167 +25,152 @@ type ModelConverter = (provider: OneOfProvider) => ModelRequest;
|
||||
type ProviderNoteProvider = () => TemplateResult | undefined;
|
||||
type ProviderNote = ProviderNoteProvider | undefined;
|
||||
|
||||
/**
|
||||
* There's an internal key and an API key because "Proxy" has three different subtypes.
|
||||
*/
|
||||
// prettier-ignore
|
||||
type ProviderType = [
|
||||
string, // internal key used by the wizard to distinguish between providers
|
||||
string, // Name of the provider
|
||||
string, // Description
|
||||
ProviderRenderer, // Function that returns the provider's wizard panel as a TemplateResult
|
||||
ProviderModelEnumType, // key used by the API to distinguish between providers
|
||||
ModelConverter, // Handler that takes a generic provider and returns one specifically typed to its panel
|
||||
ProviderNote?,
|
||||
];
|
||||
|
||||
export type LocalTypeCreate = TypeCreate & {
|
||||
formName: string;
|
||||
modelName: ProviderModelEnumType;
|
||||
converter: ModelConverter;
|
||||
note?: ProviderNote;
|
||||
renderer: ProviderRenderer;
|
||||
};
|
||||
|
||||
// prettier-ignore
|
||||
const _providerModelsTable: ProviderType[] = [
|
||||
[
|
||||
"oauth2provider",
|
||||
msg("OAuth2/OIDC (Open Authorization/OpenID Connect)"),
|
||||
msg("Modern applications, APIs and Single-page applications."),
|
||||
() =>
|
||||
export const providerModelsList: LocalTypeCreate[] = [
|
||||
{
|
||||
formName: "oauth2provider",
|
||||
name: msg("OAuth2/OIDC (Open Authorization/OpenID Connect)"),
|
||||
description: msg("Modern applications, APIs and Single-page applications."),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-oauth></ak-application-wizard-authentication-by-oauth>`,
|
||||
ProviderModelEnum.Oauth2Oauth2provider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.Oauth2Oauth2provider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.Oauth2Oauth2provider,
|
||||
...(provider as OAuth2ProviderRequest),
|
||||
}),
|
||||
],
|
||||
[
|
||||
"ldapprovider",
|
||||
msg("LDAP (Lightweight Directory Access Protocol)"),
|
||||
msg("Provide an LDAP interface for applications and users to authenticate against."),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/openidconnect.svg",
|
||||
},
|
||||
{
|
||||
formName: "ldapprovider",
|
||||
name: msg("LDAP (Lightweight Directory Access Protocol)"),
|
||||
description: msg(
|
||||
"Provide an LDAP interface for applications and users to authenticate against.",
|
||||
),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-ldap></ak-application-wizard-authentication-by-ldap>`,
|
||||
ProviderModelEnum.LdapLdapprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.LdapLdapprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.LdapLdapprovider,
|
||||
...(provider as LDAPProviderRequest),
|
||||
}),
|
||||
],
|
||||
[
|
||||
"proxyprovider-proxy",
|
||||
msg("Transparent Reverse Proxy"),
|
||||
msg("For transparent reverse proxies with required authentication"),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/ldap.png",
|
||||
},
|
||||
{
|
||||
formName: "proxyprovider-proxy",
|
||||
name: msg("Transparent Reverse Proxy"),
|
||||
description: msg("For transparent reverse proxies with required authentication"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-reverse-proxy></ak-application-wizard-authentication-for-reverse-proxy>`,
|
||||
ProviderModelEnum.ProxyProxyprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
||||
...(provider as ProxyProviderRequest),
|
||||
mode: ProxyMode.Proxy,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"proxyprovider-forwardsingle",
|
||||
msg("Forward Auth (Single Application)"),
|
||||
msg("For nginx's auth_request or traefik's forwardAuth"),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
||||
},
|
||||
{
|
||||
formName: "proxyprovider-forwardsingle",
|
||||
name: msg("Forward Auth (Single Application)"),
|
||||
description: msg("For nginx's auth_request or traefik's forwardAuth"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-single-forward-proxy></ak-application-wizard-authentication-for-single-forward-proxy>`,
|
||||
ProviderModelEnum.ProxyProxyprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
||||
...(provider as ProxyProviderRequest),
|
||||
mode: ProxyMode.ForwardSingle,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"proxyprovider-forwarddomain",
|
||||
msg("Forward Auth (Domain Level)"),
|
||||
msg("For nginx's auth_request or traefik's forwardAuth per root domain"),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
||||
},
|
||||
{
|
||||
formName: "proxyprovider-forwarddomain",
|
||||
name: msg("Forward Auth (Domain Level)"),
|
||||
description: msg("For nginx's auth_request or traefik's forwardAuth per root domain"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-forward-proxy-domain></ak-application-wizard-authentication-for-forward-proxy-domain>`,
|
||||
ProviderModelEnum.ProxyProxyprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.ProxyProxyprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ProxyProxyprovider,
|
||||
...(provider as ProxyProviderRequest),
|
||||
mode: ProxyMode.ForwardDomain,
|
||||
}),
|
||||
],
|
||||
[
|
||||
"racprovider",
|
||||
msg("Remote Access Provider"),
|
||||
msg("Remotely access computers/servers via RDP/SSH/VNC"),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/proxy.svg",
|
||||
},
|
||||
{
|
||||
formName: "racprovider",
|
||||
name: msg("Remote Access Provider"),
|
||||
description: msg("Remotely access computers/servers via RDP/SSH/VNC"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-for-rac></ak-application-wizard-authentication-for-rac>`,
|
||||
ProviderModelEnum.RacRacprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.RacRacprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.RacRacprovider,
|
||||
...(provider as RACProviderRequest),
|
||||
}),
|
||||
() => html`<ak-license-notice></ak-license-notice>`
|
||||
],
|
||||
[
|
||||
"samlprovider",
|
||||
msg("SAML (Security Assertion Markup Language)"),
|
||||
msg("Configure SAML provider manually"),
|
||||
() =>
|
||||
note: () => html`<ak-license-notice></ak-license-notice>`,
|
||||
requiresEnterprise: true,
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/rac.svg",
|
||||
},
|
||||
{
|
||||
formName: "samlprovider",
|
||||
name: msg("SAML (Security Assertion Markup Language)"),
|
||||
description: msg("Configure SAML provider manually"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-saml-configuration></ak-application-wizard-authentication-by-saml-configuration>`,
|
||||
ProviderModelEnum.SamlSamlprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.SamlSamlprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.SamlSamlprovider,
|
||||
...(provider as SAMLProviderRequest),
|
||||
}),
|
||||
],
|
||||
[
|
||||
"radiusprovider",
|
||||
msg("RADIUS (Remote Authentication Dial-In User Service)"),
|
||||
msg("Configure RADIUS provider manually"),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/saml.png",
|
||||
},
|
||||
{
|
||||
formName: "radiusprovider",
|
||||
name: msg("RADIUS (Remote Authentication Dial-In User Service)"),
|
||||
description: msg("Configure RADIUS provider manually"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-radius></ak-application-wizard-authentication-by-radius>`,
|
||||
ProviderModelEnum.RadiusRadiusprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.RadiusRadiusprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.RadiusRadiusprovider,
|
||||
...(provider as RadiusProviderRequest),
|
||||
}),
|
||||
],
|
||||
[
|
||||
"scimprovider",
|
||||
msg("SCIM (System for Cross-domain Identity Management)"),
|
||||
msg("Configure SCIM provider manually"),
|
||||
() =>
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/radius.svg",
|
||||
},
|
||||
{
|
||||
formName: "scimprovider",
|
||||
name: msg("SCIM (System for Cross-domain Identity Management)"),
|
||||
description: msg("Configure SCIM provider manually"),
|
||||
renderer: () =>
|
||||
html`<ak-application-wizard-authentication-by-scim></ak-application-wizard-authentication-by-scim>`,
|
||||
ProviderModelEnum.ScimScimprovider,
|
||||
(provider: OneOfProvider) => ({
|
||||
modelName: ProviderModelEnum.ScimScimprovider,
|
||||
converter: (provider: OneOfProvider) => ({
|
||||
providerModel: ProviderModelEnum.ScimScimprovider,
|
||||
...(provider as SCIMProviderRequest),
|
||||
}),
|
||||
],
|
||||
component: "",
|
||||
iconUrl: "/static/authentik/sources/scim.png",
|
||||
},
|
||||
];
|
||||
|
||||
function mapProviders([
|
||||
formName,
|
||||
name,
|
||||
description,
|
||||
_,
|
||||
modelName,
|
||||
converter,
|
||||
note,
|
||||
]: ProviderType): LocalTypeCreate {
|
||||
return {
|
||||
formName,
|
||||
name,
|
||||
description,
|
||||
component: "",
|
||||
modelName,
|
||||
converter,
|
||||
note,
|
||||
};
|
||||
}
|
||||
|
||||
export const providerModelsList = _providerModelsTable.map(mapProviders);
|
||||
|
||||
export const providerRendererList = new Map<string, ProviderRenderer>(
|
||||
_providerModelsTable.map(([modelName, _0, _1, renderer]) => [modelName, renderer]),
|
||||
providerModelsList.map((tc) => [tc.formName, tc.renderer]),
|
||||
);
|
||||
|
||||
export default providerModelsList;
|
||||
|
@ -3,63 +3,41 @@ import "@goauthentik/components/ak-switch-input";
|
||||
import "@goauthentik/components/ak-text-input";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { html, nothing } from "lit";
|
||||
import { map } from "lit/directives/map.js";
|
||||
import { html } from "lit";
|
||||
|
||||
import BasePanel from "../BasePanel";
|
||||
import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices";
|
||||
import type { LocalTypeCreate } from "./ak-application-wizard-authentication-method-choice.choices";
|
||||
import providerModelsList from "./ak-application-wizard-authentication-method-choice.choices";
|
||||
|
||||
@customElement("ak-application-wizard-authentication-method-choice")
|
||||
export class ApplicationWizardAuthenticationMethodChoice extends WithLicenseSummary(BasePanel) {
|
||||
constructor() {
|
||||
super();
|
||||
this.handleChoice = this.handleChoice.bind(this);
|
||||
this.renderProvider = this.renderProvider.bind(this);
|
||||
}
|
||||
|
||||
handleChoice(ev: InputEvent) {
|
||||
const target = ev.target as HTMLInputElement;
|
||||
this.dispatchWizardUpdate({
|
||||
update: {
|
||||
...this.wizard,
|
||||
providerModel: target.value,
|
||||
errors: {},
|
||||
},
|
||||
status: this.valid ? "valid" : "invalid",
|
||||
});
|
||||
}
|
||||
|
||||
renderProvider(type: LocalTypeCreate) {
|
||||
const method = this.wizard.providerModel;
|
||||
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id="provider-${type.formName}"
|
||||
?disabled=${type.formName === "racprovider" && !this.hasEnterpriseLicense}
|
||||
value=${type.formName}
|
||||
?checked=${type.formName === method}
|
||||
@change=${this.handleChoice}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for="provider-${type.formName}">${type.name}</label>
|
||||
<span class="pf-c-radio__description"
|
||||
>${type.description}${type.note ? type.note() : nothing}</span
|
||||
>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
render() {
|
||||
const selectedTypes = providerModelsList.filter(
|
||||
(t) => t.formName === this.wizard.providerModel,
|
||||
);
|
||||
return providerModelsList.length > 0
|
||||
? html`<form class="pf-c-form pf-m-horizontal">
|
||||
${map(providerModelsList, this.renderProvider)}
|
||||
<ak-wizard-page-type-create
|
||||
.types=${providerModelsList}
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
.selectedType=${selectedTypes.length > 0 ? selectedTypes[0] : undefined}
|
||||
@select=${(ev: CustomEvent<LocalTypeCreate>) => {
|
||||
this.dispatchWizardUpdate({
|
||||
update: {
|
||||
...this.wizard,
|
||||
providerModel: ev.detail.formName,
|
||||
errors: {},
|
||||
},
|
||||
status: this.valid ? "valid" : "invalid",
|
||||
});
|
||||
}}
|
||||
></ak-wizard-page-type-create>
|
||||
</form>`
|
||||
: html`<ak-empty-state loading header=${msg("Loading")}></ak-empty-state>`;
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, state } from "@lit/reactive-element/decorators.js";
|
||||
import { TemplateResult, css, html, nothing } from "lit";
|
||||
import { PropertyValues, TemplateResult, css, html, nothing } from "lit";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
|
||||
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
|
||||
@ -94,8 +94,7 @@ export class ApplicationWizardCommitApplication extends BasePanel {
|
||||
|
||||
response?: TransactionApplicationResponse;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
willUpdate(_changedProperties: Map<string, any>) {
|
||||
willUpdate(_changedProperties: PropertyValues<this>) {
|
||||
if (this.commitState === idleState) {
|
||||
this.response = undefined;
|
||||
this.commitState = runningState;
|
||||
|
@ -12,8 +12,6 @@ import "./radius/ak-application-wizard-authentication-by-radius";
|
||||
import "./saml/ak-application-wizard-authentication-by-saml-configuration";
|
||||
import "./scim/ak-application-wizard-authentication-by-scim";
|
||||
|
||||
// prettier-ignore
|
||||
|
||||
@customElement("ak-application-wizard-authentication-method")
|
||||
export class ApplicationWizardApplicationDetails extends BasePanel {
|
||||
render() {
|
||||
|
@ -9,15 +9,14 @@ import { customElement, property } from "lit/decorators.js";
|
||||
@customElement("ak-license-notice")
|
||||
export class AkLicenceNotice extends WithLicenseSummary(AKElement) {
|
||||
@property()
|
||||
notice = msg("This feature requires an enterprise license.");
|
||||
notice = msg("Enterprise only");
|
||||
|
||||
render() {
|
||||
return this.hasEnterpriseLicense
|
||||
? nothing
|
||||
: html`
|
||||
<ak-alert class="pf-c-radio__description" inline>
|
||||
${this.notice}
|
||||
<a href="#/enterprise/licenses">${msg("Learn more")}</a>
|
||||
<ak-alert class="pf-c-radio__description" inline plain>
|
||||
<a href="#/enterprise/licenses">${this.notice}</a>
|
||||
</ak-alert>
|
||||
`;
|
||||
}
|
||||
|
@ -4,73 +4,24 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import type { Wizard } from "@goauthentik/elements/wizard/Wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { OutpostsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-service-connection-wizard-initial")
|
||||
export class InitialServiceConnectionWizardPage extends WizardPage {
|
||||
@property({ attribute: false })
|
||||
connectionTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFButton, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>("input[type=radio]")
|
||||
.forEach((radio) => {
|
||||
if (radio.checked) {
|
||||
radio.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.connectionTypes.map((type) => {
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
this.host.steps = [
|
||||
"initial",
|
||||
`type-${type.component}-${type.modelName}`,
|
||||
];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-service-connection-wizard")
|
||||
export class ServiceConnectionWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, PFRadio];
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property()
|
||||
@ -79,6 +30,9 @@ export class ServiceConnectionWizard extends AKElement {
|
||||
@property({ attribute: false })
|
||||
connectionTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllTypesList().then((types) => {
|
||||
this.connectionTypes = types;
|
||||
@ -92,11 +46,19 @@ export class ServiceConnectionWizard extends AKElement {
|
||||
header=${msg("New outpost integration")}
|
||||
description=${msg("Create a new outpost integration.")}
|
||||
>
|
||||
<ak-service-connection-wizard-initial
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.connectionTypes=${this.connectionTypes}
|
||||
.types=${this.connectionTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
this.wizard.steps = [
|
||||
"initial",
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
];
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-service-connection-wizard-initial>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.connectionTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -10,80 +10,24 @@ import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import type { Wizard } from "@goauthentik/elements/wizard/Wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { PoliciesApi, Policy, PolicyBinding, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-policy-wizard-initial")
|
||||
export class InitialPolicyWizardPage extends WizardPage {
|
||||
@property({ attribute: false })
|
||||
policyTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFButton, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>("input[type=radio]")
|
||||
.forEach((radio) => {
|
||||
if (radio.checked) {
|
||||
radio.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.policyTypes.map((type) => {
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
const idx = this.host.steps.indexOf("initial") + 1;
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.host.steps = this.host.steps.filter(
|
||||
(step) => !step.startsWith("type-"),
|
||||
);
|
||||
this.host.steps.splice(
|
||||
idx,
|
||||
0,
|
||||
`type-${type.component}-${type.modelName}`,
|
||||
);
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-policy-wizard")
|
||||
export class PolicyWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, PFRadio];
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property()
|
||||
@ -98,6 +42,9 @@ export class PolicyWizard extends AKElement {
|
||||
@property({ attribute: false })
|
||||
policyTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new PoliciesApi(DEFAULT_CONFIG).policiesAllTypesList().then((types) => {
|
||||
this.policyTypes = types;
|
||||
@ -111,8 +58,26 @@ export class PolicyWizard extends AKElement {
|
||||
header=${msg("New policy")}
|
||||
description=${msg("Create a new policy.")}
|
||||
>
|
||||
<ak-policy-wizard-initial slot="initial" .policyTypes=${this.policyTypes}>
|
||||
</ak-policy-wizard-initial>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.policyTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.wizard.steps = this.wizard.steps.filter(
|
||||
(step) => !step.startsWith("type-"),
|
||||
);
|
||||
this.wizard.steps.splice(
|
||||
idx,
|
||||
0,
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
);
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.policyTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -1,4 +1,3 @@
|
||||
import "@goauthentik/admin/common/ak-license-notice";
|
||||
import "@goauthentik/admin/property-mappings/PropertyMappingLDAPForm";
|
||||
import "@goauthentik/admin/property-mappings/PropertyMappingNotification";
|
||||
import "@goauthentik/admin/property-mappings/PropertyMappingRACForm";
|
||||
@ -6,90 +5,35 @@ import "@goauthentik/admin/property-mappings/PropertyMappingSAMLForm";
|
||||
import "@goauthentik/admin/property-mappings/PropertyMappingScopeForm";
|
||||
import "@goauthentik/admin/property-mappings/PropertyMappingTestForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/Alert";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import type { Wizard } from "@goauthentik/elements/wizard/Wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { TemplateResult, html } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { PropertymappingsApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-property-mapping-wizard-initial")
|
||||
export class InitialPropertyMappingWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
@property({ attribute: false })
|
||||
mappingTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles() {
|
||||
return [PFBase, PFForm, PFButton, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>("input[type=radio]")
|
||||
.forEach((radio) => {
|
||||
if (radio.checked) {
|
||||
radio.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.mappingTypes.map((type) => {
|
||||
const requiresEnteprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
this.host.steps = [
|
||||
"initial",
|
||||
`type-${type.component}-${type.modelName}`,
|
||||
];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
?disabled=${type.requiresEnterprise ? this.hasEnterpriseLicense : false}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description"
|
||||
>${type.description}
|
||||
${requiresEnteprise
|
||||
? html`<ak-license-notice></ak-license-notice>`
|
||||
: nothing}</span
|
||||
>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-property-mapping-wizard")
|
||||
export class PropertyMappingWizard extends AKElement {
|
||||
static get styles() {
|
||||
return [PFBase, PFButton, PFRadio];
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
mappingTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.mappingTypes = await new PropertymappingsApi(
|
||||
DEFAULT_CONFIG,
|
||||
@ -103,11 +47,19 @@ export class PropertyMappingWizard extends AKElement {
|
||||
header=${msg("New property mapping")}
|
||||
description=${msg("Create a new property mapping.")}
|
||||
>
|
||||
<ak-property-mapping-wizard-initial
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.mappingTypes=${this.mappingTypes}
|
||||
.types=${this.mappingTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
this.wizard.steps = [
|
||||
"initial",
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
];
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-property-mapping-wizard-initial>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.mappingTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -5,103 +5,28 @@ import "@goauthentik/admin/providers/proxy/ProxyProviderForm";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderForm";
|
||||
import "@goauthentik/admin/providers/saml/SAMLProviderImportForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import "@goauthentik/elements/Alert";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import type { Wizard } from "@goauthentik/elements/wizard/Wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html, nothing } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFHint from "@patternfly/patternfly/components/Hint/hint.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { ProvidersApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-provider-wizard-initial")
|
||||
export class InitialProviderWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
@property({ attribute: false })
|
||||
providerTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFHint, PFButton, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>("input[type=radio]")
|
||||
.forEach((radio) => {
|
||||
if (radio.checked) {
|
||||
radio.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
renderHint(): TemplateResult {
|
||||
return html`<div class="pf-c-hint">
|
||||
<div class="pf-c-hint__title">${msg("Try the new application wizard")}</div>
|
||||
<div class="pf-c-hint__body">
|
||||
${msg(
|
||||
"The new application wizard greatly simplifies the steps required to create applications and providers.",
|
||||
)}
|
||||
</div>
|
||||
<div class="pf-c-hint__footer">
|
||||
<a
|
||||
class="pf-c-button pf-m-link pf-m-inline"
|
||||
href=${paramURL("/core/applications", {
|
||||
createForm: true,
|
||||
})}
|
||||
>${msg("Try it now")}</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<br />`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.providerTypes.map((type) => {
|
||||
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${type.component}
|
||||
@change=${() => {
|
||||
this.host.steps = ["initial", `type-${type.component}`];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
?disabled=${requiresEnterprise}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${type.component}>${type.name}</label>
|
||||
<span class="pf-c-radio__description"
|
||||
>${type.description}
|
||||
${requiresEnterprise
|
||||
? html`<ak-license-notice></ak-license-notice>`
|
||||
: nothing}
|
||||
</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-provider-wizard")
|
||||
export class ProviderWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, PFRadio];
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property()
|
||||
@ -115,6 +40,9 @@ export class ProviderWizard extends AKElement {
|
||||
return Promise.resolve();
|
||||
};
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
async firstUpdated(): Promise<void> {
|
||||
this.providerTypes = await new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList();
|
||||
}
|
||||
@ -129,8 +57,17 @@ export class ProviderWizard extends AKElement {
|
||||
return this.finalHandler();
|
||||
}}
|
||||
>
|
||||
<ak-provider-wizard-initial slot="initial" .providerTypes=${this.providerTypes}>
|
||||
</ak-provider-wizard-initial>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
.types=${this.providerTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
this.wizard.steps = ["initial", `type-${ev.detail.component}`];
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.providerTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -7,78 +7,32 @@ import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import { TypeCreateWizardPageLayouts } from "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import type { Wizard } from "@goauthentik/elements/wizard/Wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { SourcesApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-source-wizard-initial")
|
||||
export class InitialSourceWizardPage extends WizardPage {
|
||||
@property({ attribute: false })
|
||||
sourceTypes: TypeCreate[] = [];
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFButton, PFRadio];
|
||||
}
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>("input[type=radio]")
|
||||
.forEach((radio) => {
|
||||
if (radio.checked) {
|
||||
radio.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.sourceTypes.map((type) => {
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
this.host.steps = [
|
||||
"initial",
|
||||
`type-${type.component}-${type.modelName}`,
|
||||
];
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description">${type.description}</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-source-wizard")
|
||||
export class SourceWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, PFRadio];
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property({ attribute: false })
|
||||
sourceTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new SourcesApi(DEFAULT_CONFIG).sourcesAllTypesList().then((types) => {
|
||||
this.sourceTypes = types;
|
||||
@ -92,8 +46,20 @@ export class SourceWizard extends AKElement {
|
||||
header=${msg("New source")}
|
||||
description=${msg("Create a new source.")}
|
||||
>
|
||||
<ak-source-wizard-initial slot="initial" .sourceTypes=${this.sourceTypes}>
|
||||
</ak-source-wizard-initial>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.sourceTypes}
|
||||
layout=${TypeCreateWizardPageLayouts.grid}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
this.wizard.steps = [
|
||||
"initial",
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
];
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.sourceTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { placeholderHelperText } from "@goauthentik/admin/helperText";
|
||||
import { BaseSourceForm } from "@goauthentik/admin/sources/BaseSourceForm";
|
||||
import { placeholderHelperText } from "@goauthentik/authentik/admin/helperText";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/FormGroup";
|
||||
|
@ -22,89 +22,27 @@ import "@goauthentik/admin/stages/user_logout/UserLogoutStageForm";
|
||||
import "@goauthentik/admin/stages/user_write/UserWriteStageForm";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import "@goauthentik/elements/forms/ProxyForm";
|
||||
import "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import { FormWizardPage } from "@goauthentik/elements/wizard/FormWizardPage";
|
||||
import "@goauthentik/elements/wizard/TypeCreateWizardPage";
|
||||
import "@goauthentik/elements/wizard/Wizard";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
import { Wizard } from "@goauthentik/elements/wizard/Wizard";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { customElement } from "@lit/reactive-element/decorators/custom-element.js";
|
||||
import { CSSResult, TemplateResult, html, nothing } from "lit";
|
||||
import { property } from "lit/decorators.js";
|
||||
import { CSSResult, TemplateResult, html } from "lit";
|
||||
import { property, query } from "lit/decorators.js";
|
||||
|
||||
import PFButton from "@patternfly/patternfly/components/Button/button.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { FlowStageBinding, Stage, StagesApi, TypeCreate } from "@goauthentik/api";
|
||||
|
||||
@customElement("ak-stage-wizard-initial")
|
||||
export class InitialStageWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
@property({ attribute: false })
|
||||
stageTypes: TypeCreate[] = [];
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFForm, PFButton, PFRadio];
|
||||
}
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
this.shadowRoot
|
||||
?.querySelectorAll<HTMLInputElement>("input[type=radio]")
|
||||
.forEach((radio) => {
|
||||
if (radio.checked) {
|
||||
radio.dispatchEvent(new CustomEvent("change"));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.stageTypes.map((type) => {
|
||||
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
const idx = this.host.steps.indexOf("initial") + 1;
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.host.steps = this.host.steps.filter(
|
||||
(step) => !step.startsWith("type-"),
|
||||
);
|
||||
this.host.steps.splice(
|
||||
idx,
|
||||
0,
|
||||
`type-${type.component}-${type.modelName}`,
|
||||
);
|
||||
this.host.isValid = true;
|
||||
}}
|
||||
?disabled=${requiresEnterprise}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description">${type.description}${
|
||||
requiresEnterprise ? html`<ak-license-notice></ak-license-notice>` : nothing
|
||||
}</span>
|
||||
</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
}
|
||||
|
||||
@customElement("ak-stage-wizard")
|
||||
export class StageWizard extends AKElement {
|
||||
static get styles(): CSSResult[] {
|
||||
return [PFBase, PFButton, PFRadio];
|
||||
return [PFBase, PFButton];
|
||||
}
|
||||
|
||||
@property()
|
||||
@ -119,6 +57,9 @@ export class StageWizard extends AKElement {
|
||||
@property({ attribute: false })
|
||||
stageTypes: TypeCreate[] = [];
|
||||
|
||||
@query("ak-wizard")
|
||||
wizard?: Wizard;
|
||||
|
||||
firstUpdated(): void {
|
||||
new StagesApi(DEFAULT_CONFIG).stagesAllTypesList().then((types) => {
|
||||
this.stageTypes = types;
|
||||
@ -132,8 +73,26 @@ export class StageWizard extends AKElement {
|
||||
header=${msg("New stage")}
|
||||
description=${msg("Create a new stage.")}
|
||||
>
|
||||
<ak-stage-wizard-initial slot="initial" .stageTypes=${this.stageTypes}>
|
||||
</ak-stage-wizard-initial>
|
||||
<ak-wizard-page-type-create
|
||||
slot="initial"
|
||||
.types=${this.stageTypes}
|
||||
@select=${(ev: CustomEvent<TypeCreate>) => {
|
||||
if (!this.wizard) return;
|
||||
const idx = this.wizard.steps.indexOf("initial") + 1;
|
||||
// Exclude all current steps starting with type-,
|
||||
// this happens when the user selects a type and then goes back
|
||||
this.wizard.steps = this.wizard.steps.filter(
|
||||
(step) => !step.startsWith("type-"),
|
||||
);
|
||||
this.wizard.steps.splice(
|
||||
idx,
|
||||
0,
|
||||
`type-${ev.detail.component}-${ev.detail.modelName}`,
|
||||
);
|
||||
this.wizard.isValid = true;
|
||||
}}
|
||||
>
|
||||
</ak-wizard-page-type-create>
|
||||
${this.stageTypes.map((type) => {
|
||||
return html`
|
||||
<ak-wizard-page-form
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { RenderFlowOption } from "@goauthentik/admin/flows/utils";
|
||||
import { BaseStageForm } from "@goauthentik/admin/stages/BaseStageForm";
|
||||
import { deviceTypeRestrictionPair } from "@goauthentik/admin/stages/authenticator_webauthn/utils";
|
||||
import { DataProvision } from "@goauthentik/authentik/elements/ak-dual-select/types";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { first } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/ak-dual-select/ak-dual-select-provider";
|
||||
import { DataProvision } from "@goauthentik/elements/ak-dual-select/types";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import "@goauthentik/elements/forms/Radio";
|
||||
import "@goauthentik/elements/forms/SearchSelect";
|
||||
|
@ -3,7 +3,7 @@ import { CustomEmitterElement } from "@goauthentik/elements/utils/eventEmitter";
|
||||
|
||||
import { msg } from "@lit/localize";
|
||||
import { customElement, property, query } from "@lit/reactive-element/decorators.js";
|
||||
import { TemplateResult, html, nothing } from "lit";
|
||||
import { TemplateResult, css, html, nothing } from "lit";
|
||||
import { classMap } from "lit/directives/class-map.js";
|
||||
import { map } from "lit/directives/map.js";
|
||||
|
||||
@ -31,7 +31,15 @@ import { type WizardButton, WizardStepLabel } from "./types";
|
||||
@customElement("ak-wizard-frame")
|
||||
export class AkWizardFrame extends CustomEmitterElement(ModalButton) {
|
||||
static get styles() {
|
||||
return [...super.styles, PFWizard];
|
||||
return [
|
||||
...super.styles,
|
||||
PFWizard,
|
||||
css`
|
||||
.pf-c-modal-box {
|
||||
height: 75%;
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -84,7 +92,7 @@ export class AkWizardFrame extends CustomEmitterElement(ModalButton) {
|
||||
${this.renderHeader()}
|
||||
<div class="pf-c-wizard__outer-wrap">
|
||||
<div class="pf-c-wizard__inner-wrap">
|
||||
${this.renderNavigation()}
|
||||
${this.renderNavigation()}
|
||||
${this.renderMainSection()}
|
||||
</div>
|
||||
${this.renderFooter()}
|
||||
|
@ -17,6 +17,8 @@ export enum Level {
|
||||
export class Alert extends AKElement {
|
||||
@property({ type: Boolean })
|
||||
inline = false;
|
||||
@property({ type: Boolean })
|
||||
plain = false;
|
||||
|
||||
@property()
|
||||
level: Level = Level.Warning;
|
||||
@ -26,7 +28,11 @@ export class Alert extends AKElement {
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<div class="pf-c-alert ${this.inline ? "pf-m-inline" : ""} ${this.level}">
|
||||
return html`<div
|
||||
class="pf-c-alert ${this.inline ? "pf-m-inline" : ""} ${this.plain
|
||||
? "pf-m-plain"
|
||||
: ""} ${this.level}"
|
||||
>
|
||||
<div class="pf-c-alert__icon">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
</div>
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { authentikBrandContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { globalAK } from "@goauthentik/common/global";
|
||||
import { authentikConfigContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/authentik/common/constants";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { EVENT_REFRESH_ENTERPRISE } from "@goauthentik/common/constants";
|
||||
import { authentikEnterpriseContext } from "@goauthentik/elements/AuthentikContexts";
|
||||
import type { ReactiveElementHost } from "@goauthentik/elements/types.js";
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { EVENT_REFRESH } from "@goauthentik/authentik/common/constants";
|
||||
import { getRelativeTime } from "@goauthentik/authentik/common/utils";
|
||||
import { AKElement } from "@goauthentik/authentik/elements/Base";
|
||||
import { EVENT_REFRESH } from "@goauthentik/common/constants";
|
||||
import { getRelativeTime } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/components/ak-status-label";
|
||||
import { AKElement } from "@goauthentik/elements/Base";
|
||||
import "@goauthentik/elements/EmptyState";
|
||||
import "@goauthentik/elements/events/LogViewer";
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { sourceLocale, targetLocales } from "@goauthentik/authentik/locale-codes";
|
||||
|
||||
import { configureLocalization } from "@lit/localize";
|
||||
|
||||
import { sourceLocale, targetLocales } from "../../locale-codes";
|
||||
import { getBestMatchLocale } from "./helpers";
|
||||
|
||||
type LocaleGetter = ReturnType<typeof configureLocalization>["getLocale"];
|
||||
|
146
web/src/elements/wizard/TypeCreateWizardPage.ts
Normal file
146
web/src/elements/wizard/TypeCreateWizardPage.ts
Normal file
@ -0,0 +1,146 @@
|
||||
import "@goauthentik/admin/common/ak-license-notice";
|
||||
import { WithLicenseSummary } from "@goauthentik/elements/Interface/licenseSummaryProvider";
|
||||
import { WizardPage } from "@goauthentik/elements/wizard/WizardPage";
|
||||
|
||||
import { msg, str } from "@lit/localize";
|
||||
import { CSSResult, TemplateResult, css, html, nothing } from "lit";
|
||||
import { customElement, property } from "lit/decorators.js";
|
||||
|
||||
import PFCard from "@patternfly/patternfly/components/Card/card.css";
|
||||
import PFForm from "@patternfly/patternfly/components/Form/form.css";
|
||||
import PFRadio from "@patternfly/patternfly/components/Radio/radio.css";
|
||||
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
|
||||
import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
|
||||
import { TypeCreate } from "@goauthentik/api";
|
||||
|
||||
export enum TypeCreateWizardPageLayouts {
|
||||
list = "list",
|
||||
grid = "grid",
|
||||
}
|
||||
|
||||
@customElement("ak-wizard-page-type-create")
|
||||
export class TypeCreateWizardPage extends WithLicenseSummary(WizardPage) {
|
||||
@property({ attribute: false })
|
||||
types: TypeCreate[] = [];
|
||||
|
||||
@property({ attribute: false })
|
||||
selectedType?: TypeCreate;
|
||||
|
||||
@property({ type: String })
|
||||
layout: TypeCreateWizardPageLayouts = TypeCreateWizardPageLayouts.list;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
PFBase,
|
||||
PFForm,
|
||||
PFGrid,
|
||||
PFRadio,
|
||||
PFCard,
|
||||
css`
|
||||
.pf-c-card__header-main img {
|
||||
max-height: 2em;
|
||||
min-height: 2em;
|
||||
}
|
||||
:host([theme="dark"]) .pf-c-card__header-main img {
|
||||
filter: invert(1);
|
||||
}
|
||||
`,
|
||||
];
|
||||
}
|
||||
|
||||
sidebarLabel = () => msg("Select type");
|
||||
|
||||
activeCallback: () => Promise<void> = async () => {
|
||||
this.host.isValid = false;
|
||||
if (this.selectedType) {
|
||||
this.selectDispatch(this.selectedType);
|
||||
}
|
||||
};
|
||||
|
||||
private selectDispatch(type: TypeCreate) {
|
||||
this.dispatchEvent(
|
||||
new CustomEvent("select", {
|
||||
detail: type,
|
||||
bubbles: true,
|
||||
composed: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
renderGrid(): TemplateResult {
|
||||
return html`<div class="pf-l-grid pf-m-gutter">
|
||||
${this.types.map((type, idx) => {
|
||||
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div
|
||||
class="pf-l-grid__item pf-m-3-col pf-c-card ${requiresEnterprise
|
||||
? "pf-m-non-selectable-raised"
|
||||
: "pf-m-selectable-raised"} ${this.selectedType == type
|
||||
? "pf-m-selected-raised"
|
||||
: ""}"
|
||||
tabindex=${idx}
|
||||
@click=${() => {
|
||||
if (requiresEnterprise) {
|
||||
return;
|
||||
}
|
||||
this.selectDispatch(type);
|
||||
this.selectedType = type;
|
||||
}}
|
||||
>
|
||||
${type.iconUrl
|
||||
? html`<div class="pf-c-card__header">
|
||||
<div class="pf-c-card__header-main">
|
||||
<img src=${type.iconUrl} alt=${msg(str`${type.name} Icon`)} />
|
||||
</div>
|
||||
</div>`
|
||||
: nothing}
|
||||
<div class="pf-c-card__title">${type.name}</div>
|
||||
<div class="pf-c-card__body">${type.description}</div>
|
||||
${requiresEnterprise
|
||||
? html`<div class="pf-c-card__footer">
|
||||
<ak-license-notice></ak-license-notice>
|
||||
</div> `
|
||||
: nothing}
|
||||
</div>`;
|
||||
})}
|
||||
</div>`;
|
||||
}
|
||||
|
||||
renderList(): TemplateResult {
|
||||
return html`<form class="pf-c-form pf-m-horizontal">
|
||||
${this.types.map((type) => {
|
||||
const requiresEnterprise = type.requiresEnterprise && !this.hasEnterpriseLicense;
|
||||
return html`<div class="pf-c-radio">
|
||||
<input
|
||||
class="pf-c-radio__input"
|
||||
type="radio"
|
||||
name="type"
|
||||
id=${`${type.component}-${type.modelName}`}
|
||||
@change=${() => {
|
||||
this.selectDispatch(type);
|
||||
}}
|
||||
?disabled=${requiresEnterprise}
|
||||
/>
|
||||
<label class="pf-c-radio__label" for=${`${type.component}-${type.modelName}`}
|
||||
>${type.name}</label
|
||||
>
|
||||
<span class="pf-c-radio__description"
|
||||
>${type.description}
|
||||
${requiresEnterprise
|
||||
? html`<ak-license-notice></ak-license-notice>`
|
||||
: nothing}
|
||||
</span>
|
||||
</div>`;
|
||||
})}
|
||||
</form>`;
|
||||
}
|
||||
|
||||
render(): TemplateResult {
|
||||
switch (this.layout) {
|
||||
case TypeCreateWizardPageLayouts.grid:
|
||||
return this.renderGrid();
|
||||
case TypeCreateWizardPageLayouts.list:
|
||||
return this.renderList();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,5 +1,5 @@
|
||||
import { dateTimeLocal } from "@goauthentik/authentik/common/utils";
|
||||
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
|
||||
import { dateTimeLocal } from "@goauthentik/common/utils";
|
||||
import "@goauthentik/elements/forms/HorizontalFormElement";
|
||||
import { ModelForm } from "@goauthentik/elements/forms/ModelForm";
|
||||
|
||||
|
Reference in New Issue
Block a user