web: remove more until (#5057)

* more cleanup

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

* don't dynamically import duo form

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

* migrate more

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

* fix import

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

* properly send evens when tab isn't switched

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

* fix loop on tabs

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

* migrate more

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

* don't bubble tab events

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

* remove most other uses of until()

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

* cleanup user settings

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

* only use stale for issues

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

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L 2023-03-23 23:16:26 +01:00 committed by GitHub
parent af7189953c
commit b3dd87bbab
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 699 additions and 744 deletions

1
.github/stale.yml vendored
View File

@ -16,3 +16,4 @@ markComment: >
This issue has been automatically marked as stale because it has not had This issue has been automatically marked as stale because it has not had
recent activity. It will be closed if no further activity occurs. Thank you recent activity. It will be closed if no further activity occurs. Thank you
for your contributions. for your contributions.
only: issues

View File

@ -1,3 +1,4 @@
import { AdminInterface } from "@goauthentik/admin/AdminInterface";
import "@goauthentik/admin/admin-overview/TopApplicationsTable"; import "@goauthentik/admin/admin-overview/TopApplicationsTable";
import "@goauthentik/admin/admin-overview/cards/AdminStatusCard"; import "@goauthentik/admin/admin-overview/cards/AdminStatusCard";
import "@goauthentik/admin/admin-overview/cards/RecentEventsCard"; import "@goauthentik/admin/admin-overview/cards/RecentEventsCard";
@ -8,8 +9,7 @@ import "@goauthentik/admin/admin-overview/charts/AdminLoginAuthorizeChart";
import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart"; import "@goauthentik/admin/admin-overview/charts/OutpostStatusChart";
import "@goauthentik/admin/admin-overview/charts/SyncStatusChart"; import "@goauthentik/admin/admin-overview/charts/SyncStatusChart";
import { VERSION } from "@goauthentik/common/constants"; import { VERSION } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/PageHeader";
import "@goauthentik/elements/cards/AggregatePromiseCard"; import "@goauthentik/elements/cards/AggregatePromiseCard";
import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
@ -17,15 +17,13 @@ import { paramURL } from "@goauthentik/elements/router/RouterOutlet";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFList from "@patternfly/patternfly/components/List/list.css"; import PFList from "@patternfly/patternfly/components/List/list.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import { SessionUser } from "@goauthentik/api";
export function versionFamily(): string { export function versionFamily(): string {
const parts = VERSION.split("."); const parts = VERSION.split(".");
parts.pop(); parts.pop();
@ -58,17 +56,11 @@ export class AdminOverviewPage extends AKElement {
]; ];
} }
@state()
user?: SessionUser;
async firstUpdated(): Promise<void> {
this.user = await me();
}
render(): TemplateResult { render(): TemplateResult {
let name = this.user?.user.username; const user = rootInterface<AdminInterface>()?.user;
if (this.user?.user.name) { let name = user?.user.username;
name = this.user.user.name; if (user?.user.name) {
name = user.user.name;
} }
return html`<ak-page-header icon="" header="" description=${t`General system status`}> return html`<ak-page-header icon="" header="" description=${t`General system status`}>
<span slot="header"> ${t`Welcome, ${name}.`} </span> <span slot="header"> ${t`Welcome, ${name}.`} </span>

View File

@ -18,13 +18,12 @@ import { t } from "@lingui/macro";
import { CSSResult } from "lit"; import { CSSResult } from "lit";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { Outpost, OutpostTypeEnum, OutpostsApi } from "@goauthentik/api"; import { Outpost, OutpostHealth, OutpostTypeEnum, OutpostsApi } from "@goauthentik/api";
export function TypeToLabel(type?: OutpostTypeEnum): string { export function TypeToLabel(type?: OutpostTypeEnum): string {
if (!type) return ""; if (!type) return "";
@ -56,14 +55,31 @@ export class OutpostListPage extends TablePage<Outpost> {
searchEnabled(): boolean { searchEnabled(): boolean {
return true; return true;
} }
async apiEndpoint(page: number): Promise<PaginatedResponse<Outpost>> { async apiEndpoint(page: number): Promise<PaginatedResponse<Outpost>> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList({ const outposts = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList({
ordering: this.order, ordering: this.order,
page: page, page: page,
pageSize: (await uiConfig()).pagination.perPage, pageSize: (await uiConfig()).pagination.perPage,
search: this.search || "", search: this.search || "",
}); });
Promise.all(
outposts.results.map((outpost) => {
return new OutpostsApi(DEFAULT_CONFIG)
.outpostsInstancesHealthList({
uuid: outpost.pk,
})
.then((health) => {
this.health[outpost.pk] = health;
});
}),
);
return outposts;
} }
@state()
health: { [key: string]: OutpostHealth[] } = {};
columns(): TableColumn[] { columns(): TableColumn[] {
return [ return [
new TableColumn(t`Name`, "name"), new TableColumn(t`Name`, "name"),
@ -136,25 +152,15 @@ export class OutpostListPage extends TablePage<Outpost> {
${t`Detailed health (one instance per column, data is cached so may be out of date)`} ${t`Detailed health (one instance per column, data is cached so may be out of date)`}
</h3> </h3>
<dl class="pf-c-description-list pf-m-3-col-on-lg"> <dl class="pf-c-description-list pf-m-3-col-on-lg">
${until( ${this.health[item.pk].map((h) => {
new OutpostsApi(DEFAULT_CONFIG) return html`<div class="pf-c-description-list__group">
.outpostsInstancesHealthList({ <dd class="pf-c-description-list__description">
uuid: item.pk, <div class="pf-c-description-list__text">
}) <ak-outpost-health .outpostHealth=${h}></ak-outpost-health>
.then((health) => { </div>
return health.map((h) => { </dd>
return html` <div class="pf-c-description-list__group"> </div>`;
<dd class="pf-c-description-list__description"> })}
<div class="pf-c-description-list__text">
<ak-outpost-health
.outpostHealth=${h}
></ak-outpost-health>
</div>
</dd>
</div>`;
});
}),
)}
</dl> </dl>
</div> </div>
</td>`; </td>`;

View File

@ -16,11 +16,10 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { OutpostsApi, ServiceConnection } from "@goauthentik/api"; import { OutpostsApi, ServiceConnection, ServiceConnectionState } from "@goauthentik/api";
@customElement("ak-outpost-service-connection-list") @customElement("ak-outpost-service-connection-list")
export class OutpostServiceConnectionListPage extends TablePage<ServiceConnection> { export class OutpostServiceConnectionListPage extends TablePage<ServiceConnection> {
@ -40,14 +39,31 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
checkbox = true; checkbox = true;
async apiEndpoint(page: number): Promise<PaginatedResponse<ServiceConnection>> { async apiEndpoint(page: number): Promise<PaginatedResponse<ServiceConnection>> {
return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList({ const connections = await new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList(
ordering: this.order, {
page: page, ordering: this.order,
pageSize: (await uiConfig()).pagination.perPage, page: page,
search: this.search || "", pageSize: (await uiConfig()).pagination.perPage,
}); search: this.search || "",
},
);
Promise.all(
connections.results.map((connection) => {
return new OutpostsApi(DEFAULT_CONFIG)
.outpostsServiceConnectionsAllStateRetrieve({
uuid: connection.pk,
})
.then((state) => {
this.state[connection.pk] = state;
});
}),
);
return connections;
} }
@state()
state: { [key: string]: ServiceConnectionState } = {};
columns(): TableColumn[] { columns(): TableColumn[] {
return [ return [
new TableColumn(t`Name`, "name"), new TableColumn(t`Name`, "name"),
@ -62,27 +78,16 @@ export class OutpostServiceConnectionListPage extends TablePage<ServiceConnectio
order = "name"; order = "name";
row(item: ServiceConnection): TemplateResult[] { row(item: ServiceConnection): TemplateResult[] {
const itemState = this.state[item.pk];
return [ return [
html`${item.name}`, html`${item.name}`,
html`${item.verboseName}`, html`${item.verboseName}`,
html`<ak-label color=${item.local ? PFColor.Grey : PFColor.Green}> html`<ak-label color=${item.local ? PFColor.Grey : PFColor.Green}>
${item.local ? t`Yes` : t`No`} ${item.local ? t`Yes` : t`No`}
</ak-label>`, </ak-label>`,
html`${until( html`${itemState.healthy
new OutpostsApi(DEFAULT_CONFIG) ? html`<ak-label color=${PFColor.Green}>${ifDefined(itemState.version)}</ak-label>`
.outpostsServiceConnectionsAllStateRetrieve({ : html`<ak-label color=${PFColor.Red}>${t`Unhealthy`}</ak-label>`}`,
uuid: item.pk || "",
})
.then((state) => {
if (state.healthy) {
return html`<ak-label color=${PFColor.Green}
>${ifDefined(state.version)}</ak-label
>`;
}
return html`<ak-label color=${PFColor.Red}>${t`Unhealthy`}</ak-label>`;
}),
html`<ak-spinner></ak-spinner>`,
)}`,
html` <ak-forms-modal> html` <ak-forms-modal>
<span slot="submit"> ${t`Update`} </span> <span slot="submit"> ${t`Update`} </span>
<span slot="header"> ${t`Update ${item.verboseName}`} </span> <span slot="header"> ${t`Update ${item.verboseName}`} </span>

View File

@ -6,6 +6,7 @@ import { convertToTitle } from "@goauthentik/common/utils";
import MDProviderOAuth2 from "@goauthentik/docs/providers/oauth2/index.md"; import MDProviderOAuth2 from "@goauthentik/docs/providers/oauth2/index.md";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/EmptyState";
import "@goauthentik/elements/Markdown"; import "@goauthentik/elements/Markdown";
import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/ModalButton";
@ -15,8 +16,7 @@ import "@goauthentik/elements/events/ObjectChangelog";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -29,31 +29,35 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { OAuth2Provider, OAuth2ProviderSetupURLs, ProvidersApi } from "@goauthentik/api"; import {
OAuth2Provider,
OAuth2ProviderSetupURLs,
PropertyMappingPreview,
ProvidersApi,
} from "@goauthentik/api";
@customElement("ak-provider-oauth2-view") @customElement("ak-provider-oauth2-view")
export class OAuth2ProviderViewPage extends AKElement { export class OAuth2ProviderViewPage extends AKElement {
@property({ type: Number }) @property({ type: Number })
set providerID(value: number) { set providerID(value: number) {
const api = new ProvidersApi(DEFAULT_CONFIG); new ProvidersApi(DEFAULT_CONFIG)
api.providersOauth2Retrieve({ .providersOauth2Retrieve({
id: value, id: value,
}).then((prov) => { })
this.provider = prov; .then((prov) => {
}); this.provider = prov;
api.providersOauth2SetupUrlsRetrieve({ });
id: value,
}).then((prov) => {
this.providerUrls = prov;
});
} }
@property({ attribute: false }) @property({ attribute: false })
provider?: OAuth2Provider; provider?: OAuth2Provider;
@property({ attribute: false }) @state()
providerUrls?: OAuth2ProviderSetupURLs; providerUrls?: OAuth2ProviderSetupURLs;
@state()
preview?: PropertyMappingPreview;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
PFBase, PFBase,
@ -82,10 +86,32 @@ export class OAuth2ProviderViewPage extends AKElement {
return html``; return html``;
} }
return html` <ak-tabs> return html` <ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}"> <section
slot="page-overview"
data-tab-title="${t`Overview`}"
@activate=${() => {
new ProvidersApi(DEFAULT_CONFIG)
.providersOauth2SetupUrlsRetrieve({
id: this.provider?.pk || 0,
})
.then((prov) => {
this.providerUrls = prov;
});
}}
>
${this.renderTabOverview()} ${this.renderTabOverview()}
</section> </section>
<section slot="page-preview" data-tab-title="${t`Preview`}"> <section
slot="page-preview"
data-tab-title="${t`Preview`}"
@activate=${() => {
new ProvidersApi(DEFAULT_CONFIG)
.providersOauth2PreviewUserRetrieve({
id: this.provider?.pk || 0,
})
.then((preview) => (this.preview = preview));
}}
>
${this.renderTabPreview()} ${this.renderTabPreview()}
</section> </section>
<section <section
@ -318,15 +344,9 @@ export class OAuth2ProviderViewPage extends AKElement {
${t`Example JWT payload (for currently authenticated user)`} ${t`Example JWT payload (for currently authenticated user)`}
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
${until( ${this.preview
new ProvidersApi(DEFAULT_CONFIG) ? html`<pre>${JSON.stringify(this.preview?.preview, null, 4)}</pre>`
.providersOauth2PreviewUserRetrieve({ : html` <ak-empty-state ?loading=${true}></ak-empty-state> `}
id: this.provider?.pk,
})
.then((data) => {
return html`<pre>${JSON.stringify(data.preview, null, 4)}</pre>`;
}),
)}
</div> </div>
</div> </div>
</div>`; </div>`;

View File

@ -5,6 +5,7 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import "@goauthentik/elements/EmptyState";
import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/ModalButton"; import "@goauthentik/elements/buttons/ModalButton";
@ -15,9 +16,8 @@ import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -31,7 +31,13 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { CryptoApi, ProvidersApi, SAMLProvider } from "@goauthentik/api"; import {
CertificateKeyPair,
CryptoApi,
ProvidersApi,
SAMLMetadata,
SAMLProvider,
} from "@goauthentik/api";
interface SAMLPreviewAttribute { interface SAMLPreviewAttribute {
attributes: { attributes: {
@ -54,12 +60,40 @@ export class SAMLProviderViewPage extends AKElement {
.providersSamlRetrieve({ .providersSamlRetrieve({
id: value, id: value,
}) })
.then((prov) => (this.provider = prov)); .then((prov) => {
this.provider = prov;
if (prov.signingKp) {
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsRetrieve({
kpUuid: prov.signingKp,
})
.then((kp) => (this.signer = kp));
}
if (prov.verificationKp) {
new CryptoApi(DEFAULT_CONFIG)
.cryptoCertificatekeypairsRetrieve({
kpUuid: prov.verificationKp,
})
.then((kp) => (this.verifier = kp));
}
});
} }
@property({ attribute: false }) @property({ attribute: false })
provider?: SAMLProvider; provider?: SAMLProvider;
@state()
preview?: SAMLPreviewAttribute;
@state()
metadata?: SAMLMetadata;
@state()
signer?: CertificateKeyPair;
@state()
verifier?: CertificateKeyPair;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
PFBase, PFBase,
@ -84,7 +118,7 @@ export class SAMLProviderViewPage extends AKElement {
}); });
} }
async renderRelatedObjects(): Promise<TemplateResult> { renderRelatedObjects(): TemplateResult {
const relatedObjects = []; const relatedObjects = [];
if (this.provider?.assignedApplicationName) { if (this.provider?.assignedApplicationName) {
relatedObjects.push(html`<div class="pf-c-description-list__group"> relatedObjects.push(html`<div class="pf-c-description-list__group">
@ -122,10 +156,7 @@ export class SAMLProviderViewPage extends AKElement {
</dd> </dd>
</div>`); </div>`);
} }
if (this.provider?.signingKp) { if (this.signer) {
const kp = await new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRetrieve({
kpUuid: this.provider.signingKp,
});
relatedObjects.push(html`<div class="pf-c-description-list__group"> relatedObjects.push(html`<div class="pf-c-description-list__group">
<dt class="pf-c-description-list__term"> <dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text" <span class="pf-c-description-list__text"
@ -134,7 +165,9 @@ export class SAMLProviderViewPage extends AKElement {
</dt> </dt>
<dd class="pf-c-description-list__description"> <dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text"> <div class="pf-c-description-list__text">
<a class="pf-c-button pf-m-primary" href=${kp.certificateDownloadUrl} <a
class="pf-c-button pf-m-primary"
href=${this.signer.certificateDownloadUrl}
>${t`Download`}</a >${t`Download`}</a
> >
</div> </div>
@ -160,7 +193,19 @@ export class SAMLProviderViewPage extends AKElement {
${this.renderTabOverview()} ${this.renderTabOverview()}
</section> </section>
${this.renderTabMetadata()} ${this.renderTabMetadata()}
<section slot="page-preview" data-tab-title="${t`Preview`}"> <section
slot="page-preview"
data-tab-title="${t`Preview`}"
@activate=${() => {
new ProvidersApi(DEFAULT_CONFIG)
.providersSamlPreviewUserRetrieve({
id: this.provider?.pk || 0,
})
.then((preview) => {
this.preview = preview.preview as SAMLPreviewAttribute;
});
}}
>
${this.renderTabPreview()} ${this.renderTabPreview()}
</section> </section>
<section <section
@ -264,7 +309,7 @@ export class SAMLProviderViewPage extends AKElement {
</ak-forms-modal> </ak-forms-modal>
</div> </div>
</div> </div>
${until(this.renderRelatedObjects())} ${this.renderRelatedObjects()}
${ ${
this.provider.assignedApplicationName this.provider.assignedApplicationName
? html` <div class="pf-c-card pf-l-grid__item pf-m-12-col"> ? html` <div class="pf-c-card pf-l-grid__item pf-m-12-col">
@ -364,7 +409,17 @@ export class SAMLProviderViewPage extends AKElement {
} }
return html` return html`
${this.provider.assignedApplicationName ${this.provider.assignedApplicationName
? html` <section slot="page-metadata" data-tab-title="${t`Metadata`}"> ? html` <section
slot="page-metadata"
data-tab-title="${t`Metadata`}"
@activate=${() => {
new ProvidersApi(DEFAULT_CONFIG)
.providersSamlMetadataRetrieve({
id: this.provider?.pk || 0,
})
.then((metadata) => (this.metadata = metadata));
}}
>
<div <div
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter" class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
> >
@ -399,19 +454,11 @@ export class SAMLProviderViewPage extends AKElement {
</ak-action-button> </ak-action-button>
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
${until( <ak-codemirror
new ProvidersApi(DEFAULT_CONFIG) mode="xml"
.providersSamlMetadataRetrieve({ ?readOnly=${true}
id: this.provider.pk || 0, value="${ifDefined(this.metadata?.metadata)}"
}) ></ak-codemirror>
.then((m) => {
return html`<ak-codemirror
mode="xml"
?readOnly=${true}
value="${ifDefined(m.metadata)}"
></ak-codemirror>`;
}),
)}
</div> </div>
</div> </div>
</div> </div>
@ -421,65 +468,50 @@ export class SAMLProviderViewPage extends AKElement {
} }
renderTabPreview(): TemplateResult { renderTabPreview(): TemplateResult {
if (!this.provider) { if (!this.preview) {
return html``; return html`<ak-empty-state ?loading=${true}></ak-empty-state>`;
} }
return html` <div return html` <div
class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter" class="pf-c-page__main-section pf-m-no-padding-mobile pf-l-grid pf-m-gutter"
> >
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__title">${t`Example SAML attributes`}</div> <div class="pf-c-card__title">${t`Example SAML attributes`}</div>
${until( <div class="pf-c-card__body">
new ProvidersApi(DEFAULT_CONFIG) <dl class="pf-c-description-list pf-m-2-col-on-lg">
.providersSamlPreviewUserRetrieve({ <div class="pf-c-description-list__group">
id: this.provider?.pk, <dt class="pf-c-description-list__term">
}) <span class="pf-c-description-list__text"
.then((data) => { >${t`NameID attribute`}</span
const d = data.preview as SAMLPreviewAttribute; >
return html` </dt>
<div class="pf-c-card__body"> <dd class="pf-c-description-list__description">
<dl class="pf-c-description-list pf-m-2-col-on-lg"> <div class="pf-c-description-list__text">
<div class="pf-c-description-list__group"> ${this.preview?.nameID}
<dt class="pf-c-description-list__term">
<span class="pf-c-description-list__text"
>${t`NameID attribute`}</span
>
</dt>
<dd class="pf-c-description-list__description">
<div class="pf-c-description-list__text">
${d.nameID}
</div>
</dd>
</div>
</dl>
</div> </div>
<div class="pf-c-card__body"> </dd>
<dl class="pf-c-description-list pf-m-2-col-on-lg"> </div>
${d.attributes.map((attr) => { </dl>
return html` <div class="pf-c-description-list__group"> </div>
<dt class="pf-c-description-list__term"> <div class="pf-c-card__body">
<span class="pf-c-description-list__text" <dl class="pf-c-description-list pf-m-2-col-on-lg">
>${attr.Name}</span ${this.preview?.attributes.map((attr) => {
> return html` <div class="pf-c-description-list__group">
</dt> <dt class="pf-c-description-list__term">
<dd class="pf-c-description-list__description"> <span class="pf-c-description-list__text">${attr.Name}</span>
<div class="pf-c-description-list__text"> </dt>
<ul class="pf-c-list"> <dd class="pf-c-description-list__description">
${attr.Value.map((value) => { <div class="pf-c-description-list__text">
return html` <ul class="pf-c-list">
<li><pre>${value}</pre></li> ${attr.Value.map((value) => {
`; return html` <li><pre>${value}</pre></li> `;
})} })}
</ul> </ul>
</div> </div>
</dd> </dd>
</div>`; </div>`;
})} })}
</dl> </dl>
</div> </div>
`;
}),
)}
</div> </div>
</div>`; </div>`;
} }

View File

@ -1,7 +1,6 @@
import "@goauthentik/admin/providers/scim/SCIMProviderForm"; import "@goauthentik/admin/providers/scim/SCIMProviderForm";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users";
import MDSCIMProvider from "@goauthentik/docs/providers/scim/index.md"; import MDSCIMProvider from "@goauthentik/docs/providers/scim/index.md";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Markdown"; import "@goauthentik/elements/Markdown";
@ -14,7 +13,6 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFBanner from "@patternfly/patternfly/components/Banner/banner.css"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -29,7 +27,7 @@ import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css"; import PFStack from "@patternfly/patternfly/layouts/Stack/stack.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { ProvidersApi, SCIMProvider, SessionUser } from "@goauthentik/api"; import { ProvidersApi, SCIMProvider, Task } from "@goauthentik/api";
@customElement("ak-provider-scim-view") @customElement("ak-provider-scim-view")
export class SCIMProviderViewPage extends AKElement { export class SCIMProviderViewPage extends AKElement {
@ -51,7 +49,7 @@ export class SCIMProviderViewPage extends AKElement {
provider?: SCIMProvider; provider?: SCIMProvider;
@state() @state()
me?: SessionUser; syncState?: Task;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [ return [
@ -76,9 +74,6 @@ export class SCIMProviderViewPage extends AKElement {
if (!this.provider?.pk) return; if (!this.provider?.pk) return;
this.providerID = this.provider?.pk; this.providerID = this.provider?.pk;
}); });
me().then((user) => {
this.me = user;
});
} }
render(): TemplateResult { render(): TemplateResult {
@ -86,7 +81,22 @@ export class SCIMProviderViewPage extends AKElement {
return html``; return html``;
} }
return html` <ak-tabs> return html` <ak-tabs>
<section slot="page-overview" data-tab-title="${t`Overview`}"> <section
slot="page-overview"
data-tab-title="${t`Overview`}"
@activate=${() => {
new ProvidersApi(DEFAULT_CONFIG)
.providersScimSyncStatusRetrieve({
id: this.provider?.pk || 0,
})
.then((state) => {
this.syncState = state;
})
.catch(() => {
this.syncState = undefined;
});
}}
>
${this.renderTabOverview()} ${this.renderTabOverview()}
</section> </section>
<section <section
@ -158,23 +168,13 @@ export class SCIMProviderViewPage extends AKElement {
<p>${t`Sync status`}</p> <p>${t`Sync status`}</p>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
${until( ${this.syncState
new ProvidersApi(DEFAULT_CONFIG) ? html` <ul class="pf-c-list">
.providersScimSyncStatusRetrieve({ ${this.syncState.messages.map((m) => {
id: this.provider.pk, return html`<li>${m}</li>`;
}) })}
.then((task) => { </ul>`
return html` <ul class="pf-c-list"> : html` ${t`Sync not run yet.`} `}
${task.messages.map((m) => {
return html`<li>${m}</li>`;
})}
</ul>`;
})
.catch(() => {
return html`${t`Sync not run yet.`}`;
}),
"loading",
)}
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">

View File

@ -12,8 +12,7 @@ import "@goauthentik/elements/forms/ModalForm";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css";
@ -24,7 +23,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { LDAPSource, SourcesApi, TaskStatusEnum } from "@goauthentik/api"; import { LDAPSource, SourcesApi, Task, TaskStatusEnum } from "@goauthentik/api";
@customElement("ak-source-ldap-view") @customElement("ak-source-ldap-view")
export class LDAPSourceViewPage extends AKElement { export class LDAPSourceViewPage extends AKElement {
@ -42,6 +41,9 @@ export class LDAPSourceViewPage extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
source!: LDAPSource; source!: LDAPSource;
@state()
syncState: Task[] = [];
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFPage, PFButton, PFGrid, PFContent, PFCard, PFDescriptionList, PFList]; return [PFBase, PFPage, PFButton, PFGrid, PFContent, PFCard, PFDescriptionList, PFList];
} }
@ -63,6 +65,15 @@ export class LDAPSourceViewPage extends AKElement {
slot="page-overview" slot="page-overview"
data-tab-title="${t`Overview`}" data-tab-title="${t`Overview`}"
class="pf-c-page__main-section pf-m-no-padding-mobile" class="pf-c-page__main-section pf-m-no-padding-mobile"
@activate=${() => {
new SourcesApi(DEFAULT_CONFIG)
.sourcesLdapSyncStatusList({
slug: this.source.slug,
})
.then((state) => {
this.syncState = state;
});
}}
> >
<div class="pf-l-grid pf-m-gutter"> <div class="pf-l-grid pf-m-gutter">
<div class="pf-c-card pf-l-grid__item pf-m-12-col"> <div class="pf-c-card pf-l-grid__item pf-m-12-col">
@ -123,39 +134,31 @@ export class LDAPSourceViewPage extends AKElement {
<p>${t`Sync status`}</p> <p>${t`Sync status`}</p>
</div> </div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
${until( ${this.syncState.length < 1
new SourcesApi(DEFAULT_CONFIG) ? html`<p>${t`Not synced yet.`}</p>`
.sourcesLdapSyncStatusList({ : html`
slug: this.source.slug, <ul class="pf-c-list">
}) ${this.syncState.map((task) => {
.then((tasks) => { let header = "";
if (tasks.length < 1) { if (task.status === TaskStatusEnum.Warning) {
return html`<p>${t`Not synced yet.`}</p>`; header = t`Task finished with warnings`;
} } else if (task.status === TaskStatusEnum.Error) {
return html`<ul class="pf-c-list"> header = t`Task finished with errors`;
${tasks.map((task) => { } else {
let header = ""; header = t`Last sync: ${task.taskFinishTimestamp.toLocaleString()}`;
if (task.status === TaskStatusEnum.Warning) { }
header = t`Task finished with warnings`; return html`<li>
} else if (task.status === TaskStatusEnum.Error) { <p>${task.taskName}</p>
header = t`Task finished with errors`; <ul class="pf-c-list">
} else { <li>${header}</li>
header = t`Last sync: ${task.taskFinishTimestamp.toLocaleString()}`; ${task.messages.map((m) => {
} return html`<li>${m}</li>`;
return html`<li> })}
<p>${task.taskName}</p> </ul>
<ul class="pf-c-list"> </li> `;
<li>${header}</li> })}
${task.messages.map((m) => { </ul>
return html`<li>${m}</li>`; `}
})}
</ul>
</li> `;
})}
</ul>`;
}),
"loading",
)}
</div> </div>
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-action-button <ak-action-button

View File

@ -12,9 +12,8 @@ import "@goauthentik/elements/forms/ModalForm";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css";
@ -24,7 +23,7 @@ import PFPage from "@patternfly/patternfly/components/Page/page.css";
import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css"; import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { SAMLSource, SourcesApi } from "@goauthentik/api"; import { SAMLMetadata, SAMLSource, SourcesApi } from "@goauthentik/api";
@customElement("ak-source-saml-view") @customElement("ak-source-saml-view")
export class SAMLSourceViewPage extends AKElement { export class SAMLSourceViewPage extends AKElement {
@ -42,6 +41,9 @@ export class SAMLSourceViewPage extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
source?: SAMLSource; source?: SAMLSource;
@state()
metadata?: SAMLMetadata;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFPage, PFGrid, PFButton, PFContent, PFCard, PFDescriptionList]; return [PFBase, PFPage, PFGrid, PFButton, PFContent, PFCard, PFDescriptionList];
} }
@ -152,35 +154,34 @@ export class SAMLSourceViewPage extends AKElement {
slot="page-metadata" slot="page-metadata"
data-tab-title="${t`Metadata`}" data-tab-title="${t`Metadata`}"
class="pf-c-page__main-section pf-m-no-padding-mobile" class="pf-c-page__main-section pf-m-no-padding-mobile"
@activate=${() => {
new SourcesApi(DEFAULT_CONFIG)
.sourcesSamlMetadataRetrieve({
slug: this.source?.slug || "",
})
.then((metadata) => {
this.metadata = metadata;
});
}}
> >
<div class="pf-l-grid pf-m-gutter"> <div class="pf-l-grid pf-m-gutter">
<div class="pf-c-card pf-l-grid__item pf-m-12-col"> <div class="pf-c-card pf-l-grid__item pf-m-12-col">
${until( <div class="pf-c-card__body">
new SourcesApi(DEFAULT_CONFIG) <ak-codemirror
.sourcesSamlMetadataRetrieve({ mode="xml"
slug: this.source.slug, ?readOnly=${true}
}) value="${ifDefined(this.metadata?.metadata)}"
.then((m) => { ></ak-codemirror>
return html` </div>
<div class="pf-c-card__body"> <div class="pf-c-card__footer">
<ak-codemirror <a
mode="xml" class="pf-c-button pf-m-primary"
?readOnly=${true} target="_blank"
value="${ifDefined(m.metadata)}" href=${ifDefined(this.metadata?.downloadUrl)}
></ak-codemirror> >
</div> ${t`Download`}
<div class="pf-c-card__footer"> </a>
<a </div>
class="pf-c-button pf-m-primary"
target="_blank"
href=${ifDefined(m.downloadUrl)}
>
${t`Download`}
</a>
</div>
`;
}),
)}
</div> </div>
</div> </div>
</section> </section>

View File

@ -1,5 +1,6 @@
import "@goauthentik/admin/stages/StageWizard"; import "@goauthentik/admin/stages/StageWizard";
import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm"; import "@goauthentik/admin/stages/authenticator_duo/AuthenticatorDuoStageForm";
import "@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm";
import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm"; import "@goauthentik/admin/stages/authenticator_sms/AuthenticatorSMSStageForm";
import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm"; import "@goauthentik/admin/stages/authenticator_static/AuthenticatorStaticStageForm";
import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm"; import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm";
@ -33,7 +34,6 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { Stage, StagesApi } from "@goauthentik/api"; import { Stage, StagesApi } from "@goauthentik/api";
@ -100,20 +100,24 @@ export class StageListPage extends TablePage<Stage> {
</ak-forms-delete-bulk>`; </ak-forms-delete-bulk>`;
} }
async renderStageActions(stage: Stage): Promise<TemplateResult> { renderStageActions(stage: Stage): TemplateResult {
if (stage.component === "ak-stage-authenticator-duo-form") { switch (stage.component) {
await import("@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm"); case "ak-stage-authenticator-duo-form":
return html`<ak-forms-modal> return html`<ak-forms-modal>
<span slot="submit">${t`Import`}</span> <span slot="submit">${t`Import`}</span>
<span slot="header">${t`Import Duo device`}</span> <span slot="header">${t`Import Duo device`}</span>
<ak-stage-authenticator-duo-device-import-form slot="form" .instancePk=${stage.pk}> <ak-stage-authenticator-duo-device-import-form
</ak-stage-authenticator-duo-device-import-form> slot="form"
<button slot="trigger" class="pf-c-button pf-m-plain"> .instancePk=${stage.pk}
<i class="fas fa-file-import"></i> >
</button> </ak-stage-authenticator-duo-device-import-form>
</ak-forms-modal>`; <button slot="trigger" class="pf-c-button pf-m-plain">
<i class="fas fa-file-import"></i>
</button>
</ak-forms-modal>`;
default:
return html``;
} }
return html``;
} }
row(item: Stage): TemplateResult[] { row(item: Stage): TemplateResult[] {
@ -144,7 +148,7 @@ export class StageListPage extends TablePage<Stage> {
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${until(this.renderStageActions(item))}`, ${this.renderStageActions(item)}`,
]; ];
} }

View File

@ -3,10 +3,11 @@ import "@goauthentik/admin/users/UserActiveForm";
import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserForm";
import "@goauthentik/admin/users/UserPasswordForm"; import "@goauthentik/admin/users/UserPasswordForm";
import "@goauthentik/admin/users/UserResetEmailForm"; import "@goauthentik/admin/users/UserResetEmailForm";
import { DEFAULT_CONFIG, config, tenant } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import { PFColor } from "@goauthentik/elements/Label"; import { PFColor } from "@goauthentik/elements/Label";
import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/ActionButton";
import "@goauthentik/elements/buttons/Dropdown"; import "@goauthentik/elements/buttons/Dropdown";
@ -25,7 +26,6 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property, state } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
@ -189,19 +189,16 @@ export class RelatedUserList extends Table<User> {
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${until( ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.Impersonate)
config().then((config) => { ? html`
if (config.capabilities.includes(CapabilitiesEnum.Impersonate)) { <a
return html`<a class="pf-c-button pf-m-tertiary"
class="pf-c-button pf-m-tertiary" href="${`/-/impersonation/${item.pk}/`}"
href="${`/-/impersonation/${item.pk}/`}" >
> ${t`Impersonate`}
${t`Impersonate`} </a>
</a>`; `
} : html``}`,
return html``;
}),
)}`,
]; ];
} }
@ -266,70 +263,61 @@ export class RelatedUserList extends Table<User> {
${t`Set password`} ${t`Set password`}
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${until( ${rootInterface()?.tenant?.flowRecovery
tenant().then((tenant) => { ? html`
if (!tenant.flowRecovery) { <ak-action-button
return html` class="pf-m-secondary"
<p> .apiRequest=${() => {
${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`} return new CoreApi(DEFAULT_CONFIG)
</p> .coreUsersRecoveryRetrieve({
`; id: item.pk,
} })
return html` .then((rec) => {
<ak-action-button showMessage({
class="pf-m-secondary" level: MessageLevel.success,
.apiRequest=${() => { message: t`Successfully generated recovery link`,
return new CoreApi(DEFAULT_CONFIG) description: rec.link,
.coreUsersRecoveryRetrieve({ });
id: item.pk || 0, })
}) .catch((ex: ResponseError) => {
.then((rec) => { ex.response.json().then(() => {
showMessage({ showMessage({
level: MessageLevel.success, level: MessageLevel.error,
message: t`Successfully generated recovery link`, message: t`No recovery flow is configured.`,
description: rec.link, });
}); });
}) });
.catch((ex: ResponseError) => { }}
ex.response.json().then(() => { >
showMessage({ ${t`Copy recovery link`}
level: MessageLevel.error, </ak-action-button>
message: t`No recovery flow is configured.`, ${item.email
}); ? html`<ak-forms-modal
}); .closeAfterSuccessfulSubmit=${false}
}); >
}} <span slot="submit"> ${t`Send link`} </span>
> <span slot="header">
${t`Copy recovery link`} ${t`Send recovery link to user`}
</ak-action-button> </span>
${item.email <ak-user-reset-email-form
? html`<ak-forms-modal slot="form"
.closeAfterSuccessfulSubmit=${false} .user=${item}
> >
<span slot="submit"> </ak-user-reset-email-form>
${t`Send link`} <button
</span> slot="trigger"
<span slot="header"> class="pf-c-button pf-m-secondary"
${t`Send recovery link to user`} >
</span> ${t`Email recovery link`}
<ak-user-reset-email-form </button>
slot="form" </ak-forms-modal>`
.user=${item} : html`<span
> >${t`Recovery link cannot be emailed, user has no email address saved.`}</span
</ak-user-reset-email-form> >`}
<button `
slot="trigger" : html` <p>
class="pf-c-button pf-m-secondary" ${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`}
> </p>`}
${t`Email recovery link`}
</button>
</ak-forms-modal>`
: html`<span
>${t`Recovery link cannot be emailed, user has no email address saved.`}</span
>`}
`;
}),
)}
</div> </div>
</dd> </dd>
</div> </div>

View File

@ -1,13 +1,14 @@
import { AdminInterface } from "@goauthentik/admin/AdminInterface";
import "@goauthentik/admin/users/ServiceAccountForm"; import "@goauthentik/admin/users/ServiceAccountForm";
import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserActiveForm";
import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserForm";
import "@goauthentik/admin/users/UserPasswordForm"; import "@goauthentik/admin/users/UserPasswordForm";
import "@goauthentik/admin/users/UserResetEmailForm"; import "@goauthentik/admin/users/UserResetEmailForm";
import { DEFAULT_CONFIG, config, tenant } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { uiConfig } from "@goauthentik/common/ui/config"; import { uiConfig } from "@goauthentik/common/ui/config";
import { me } from "@goauthentik/common/users";
import { first } from "@goauthentik/common/utils"; import { first } from "@goauthentik/common/utils";
import { rootInterface } from "@goauthentik/elements/Base";
import { PFColor } from "@goauthentik/elements/Label"; import { PFColor } from "@goauthentik/elements/Label";
import { PFSize } from "@goauthentik/elements/Spinner"; import { PFSize } from "@goauthentik/elements/Spinner";
import "@goauthentik/elements/TreeView"; import "@goauthentik/elements/TreeView";
@ -23,14 +24,13 @@ import { TablePage } from "@goauthentik/elements/table/TablePage";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property, state } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; import PFAlert from "@patternfly/patternfly/components/Alert/alert.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; import PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css";
import { CapabilitiesEnum, CoreApi, ResponseError, User } from "@goauthentik/api"; import { CapabilitiesEnum, CoreApi, ResponseError, User, UserPath } from "@goauthentik/api";
@customElement("ak-user-list") @customElement("ak-user-list")
export class UserListPage extends TablePage<User> { export class UserListPage extends TablePage<User> {
@ -56,18 +56,25 @@ export class UserListPage extends TablePage<User> {
@property() @property()
activePath = getURLParam<string>("path", "/"); activePath = getURLParam<string>("path", "/");
@state()
userPaths?: UserPath;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return super.styles.concat(PFDescriptionList, PFCard, PFAlert); return super.styles.concat(PFDescriptionList, PFCard, PFAlert);
} }
async apiEndpoint(page: number): Promise<PaginatedResponse<User>> { async apiEndpoint(page: number): Promise<PaginatedResponse<User>> {
return new CoreApi(DEFAULT_CONFIG).coreUsersList({ const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList({
ordering: this.order, ordering: this.order,
page: page, page: page,
pageSize: (await uiConfig()).pagination.perPage, pageSize: (await uiConfig()).pagination.perPage,
search: this.search || "", search: this.search || "",
pathStartswith: getURLParam("path", ""), pathStartswith: getURLParam("path", ""),
}); });
this.userPaths = await new CoreApi(DEFAULT_CONFIG).coreUsersPathsRetrieve({
search: this.search,
});
return users;
} }
columns(): TableColumn[] { columns(): TableColumn[] {
@ -81,6 +88,10 @@ export class UserListPage extends TablePage<User> {
renderToolbarSelected(): TemplateResult { renderToolbarSelected(): TemplateResult {
const disabled = this.selectedElements.length < 1; const disabled = this.selectedElements.length < 1;
const currentUser = rootInterface<AdminInterface>()?.user;
const shouldShowWarning = this.selectedElements.find((el) => {
return el.pk === currentUser?.user.pk || el.pk == currentUser?.original?.pk;
});
return html`<ak-forms-delete-bulk return html`<ak-forms-delete-bulk
objectLabel=${t`User(s)`} objectLabel=${t`User(s)`}
.objects=${this.selectedElements} .objects=${this.selectedElements}
@ -102,28 +113,18 @@ export class UserListPage extends TablePage<User> {
}); });
}} }}
> >
${until( ${shouldShowWarning
me().then((user) => { ? html`<div slot="notice" class="pf-c-form__alert">
const shouldShowWarning = this.selectedElements.find((el) => { <div class="pf-c-alert pf-m-inline pf-m-warning">
return el.pk === user.user.pk || el.pk == user.original?.pk; <div class="pf-c-alert__icon">
}); <i class="fas fa-exclamation-circle"></i>
if (shouldShowWarning) { </div>
return html` <h4 class="pf-c-alert__title">
<div slot="notice" class="pf-c-form__alert"> ${t`Warning: You're about to delete the user you're logged in as (${shouldShowWarning.username}). Proceed at your own risk.`}
<div class="pf-c-alert pf-m-inline pf-m-warning"> </h4>
<div class="pf-c-alert__icon"> </div>
<i class="fas fa-exclamation-circle"></i> </div>`
</div> : html``}
<h4 class="pf-c-alert__title">
${t`Warning: You're about to delete the user you're logged in as (${shouldShowWarning.username}). Proceed at your own risk.`}
</h4>
</div>
</div>
`;
}
return html``;
}),
)}
<button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger"> <button ?disabled=${disabled} slot="trigger" class="pf-c-button pf-m-danger">
${t`Delete`} ${t`Delete`}
</button> </button>
@ -148,19 +149,16 @@ export class UserListPage extends TablePage<User> {
<i class="fas fa-edit"></i> <i class="fas fa-edit"></i>
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${until( ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.Impersonate)
config().then((config) => { ? html`
if (config.capabilities.includes(CapabilitiesEnum.Impersonate)) { <a
return html`<a class="pf-c-button pf-m-tertiary"
class="pf-c-button pf-m-tertiary" href="${`/-/impersonation/${item.pk}/`}"
href="${`/-/impersonation/${item.pk}/`}" >
> ${t`Impersonate`}
${t`Impersonate`} </a>
</a>`; `
} : html``}`,
return html``;
}),
)}`,
]; ];
} }
@ -194,7 +192,7 @@ export class UserListPage extends TablePage<User> {
return new CoreApi( return new CoreApi(
DEFAULT_CONFIG, DEFAULT_CONFIG,
).coreUsersPartialUpdate({ ).coreUsersPartialUpdate({
id: item.pk || 0, id: item.pk,
patchedUserRequest: { patchedUserRequest: {
isActive: !item.isActive, isActive: !item.isActive,
}, },
@ -225,70 +223,61 @@ export class UserListPage extends TablePage<User> {
${t`Set password`} ${t`Set password`}
</button> </button>
</ak-forms-modal> </ak-forms-modal>
${until( ${rootInterface()?.tenant?.flowRecovery
tenant().then((tenant) => { ? html`
if (!tenant.flowRecovery) { <ak-action-button
return html` class="pf-m-secondary"
<p> .apiRequest=${() => {
${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`} return new CoreApi(DEFAULT_CONFIG)
</p> .coreUsersRecoveryRetrieve({
`; id: item.pk,
} })
return html` .then((rec) => {
<ak-action-button showMessage({
class="pf-m-secondary" level: MessageLevel.success,
.apiRequest=${() => { message: t`Successfully generated recovery link`,
return new CoreApi(DEFAULT_CONFIG) description: rec.link,
.coreUsersRecoveryRetrieve({ });
id: item.pk || 0, })
}) .catch((ex: ResponseError) => {
.then((rec) => { ex.response.json().then(() => {
showMessage({ showMessage({
level: MessageLevel.success, level: MessageLevel.error,
message: t`Successfully generated recovery link`, message: t`No recovery flow is configured.`,
description: rec.link, });
}); });
}) });
.catch((ex: ResponseError) => { }}
ex.response.json().then(() => { >
showMessage({ ${t`Copy recovery link`}
level: MessageLevel.error, </ak-action-button>
message: t`No recovery flow is configured.`, ${item.email
}); ? html`<ak-forms-modal
}); .closeAfterSuccessfulSubmit=${false}
}); >
}} <span slot="submit"> ${t`Send link`} </span>
> <span slot="header">
${t`Copy recovery link`} ${t`Send recovery link to user`}
</ak-action-button> </span>
${item.email <ak-user-reset-email-form
? html`<ak-forms-modal slot="form"
.closeAfterSuccessfulSubmit=${false} .user=${item}
> >
<span slot="submit"> </ak-user-reset-email-form>
${t`Send link`} <button
</span> slot="trigger"
<span slot="header"> class="pf-c-button pf-m-secondary"
${t`Send recovery link to user`} >
</span> ${t`Email recovery link`}
<ak-user-reset-email-form </button>
slot="form" </ak-forms-modal>`
.user=${item} : html`<span
> >${t`Recovery link cannot be emailed, user has no email address saved.`}</span
</ak-user-reset-email-form> >`}
<button `
slot="trigger" : html` <p>
class="pf-c-button pf-m-secondary" ${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`}
> </p>`}
${t`Email recovery link`}
</button>
</ak-forms-modal>`
: html`<span
>${t`Recovery link cannot be emailed, user has no email address saved.`}</span
>`}
`;
}),
)}
</div> </div>
</dd> </dd>
</div> </div>
@ -323,18 +312,10 @@ export class UserListPage extends TablePage<User> {
<div class="pf-c-card"> <div class="pf-c-card">
<div class="pf-c-card__title">${t`User folders`}</div> <div class="pf-c-card__title">${t`User folders`}</div>
<div class="pf-c-card__body"> <div class="pf-c-card__body">
${until( <ak-treeview
new CoreApi(DEFAULT_CONFIG) .items=${this.userPaths?.paths || []}
.coreUsersPathsRetrieve({ activePath=${this.activePath}
search: this.search, ></ak-treeview>
})
.then((paths) => {
return html`<ak-treeview
.items=${paths.paths}
activePath=${this.activePath}
></ak-treeview>`;
}),
)}
</div> </div>
</div> </div>
</div>`; </div>`;

View File

@ -3,10 +3,10 @@ import "@goauthentik/admin/users/UserActiveForm";
import "@goauthentik/admin/users/UserChart"; import "@goauthentik/admin/users/UserChart";
import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserForm";
import "@goauthentik/admin/users/UserPasswordForm"; import "@goauthentik/admin/users/UserPasswordForm";
import { DEFAULT_CONFIG, config } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/CodeMirror"; import "@goauthentik/elements/CodeMirror";
import { PFColor } from "@goauthentik/elements/Label"; import { PFColor } from "@goauthentik/elements/Label";
import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/PageHeader";
@ -27,7 +27,6 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, html } from "lit"; import { CSSResult, TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css";
@ -197,21 +196,18 @@ export class UserViewPage extends AKElement {
</button> </button>
</ak-forms-modal> </ak-forms-modal>
</div> </div>
${until( ${rootInterface()?.config?.capabilities.includes(
config().then((config) => { CapabilitiesEnum.Impersonate,
if (config.capabilities.includes(CapabilitiesEnum.Impersonate)) { )
return html` <div class="pf-c-card__footer"> ? html`
<a <a
class="pf-c-button pf-m-tertiary" class="pf-c-button pf-m-tertiary"
href="${`/-/impersonation/${this.user?.pk}/`}" href="${`/-/impersonation/${this.user?.pk}/`}"
> >
${t`Impersonate`} ${t`Impersonate`}
</a> </a>
</div>`; `
} : html``}
return html``;
}),
)}
<div class="pf-c-card__footer"> <div class="pf-c-card__footer">
<ak-user-active-form <ak-user-active-form
.obj=${this.user} .obj=${this.user}

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { import {
EVENT_API_DRAWER_TOGGLE, EVENT_API_DRAWER_TOGGLE,
EVENT_NOTIFICATION_DRAWER_TOGGLE, EVENT_NOTIFICATION_DRAWER_TOGGLE,
@ -8,7 +8,7 @@ import {
} from "@goauthentik/common/constants"; } from "@goauthentik/common/constants";
import { currentInterface } from "@goauthentik/common/sentry"; import { currentInterface } from "@goauthentik/common/sentry";
import { me } from "@goauthentik/common/users"; import { me } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
@ -35,17 +35,16 @@ export class PageHeader extends AKElement {
@property() @property()
set header(value: string) { set header(value: string) {
tenant().then((tenant) => { const tenant = rootInterface()?.tenant;
const currentIf = currentInterface(); const currentIf = currentInterface();
let title = tenant.brandingTitle || TITLE_DEFAULT; let title = tenant?.brandingTitle || TITLE_DEFAULT;
if (currentIf === "admin") { if (currentIf === "admin") {
title = `${t`Admin`} - ${title}`; title = `${t`Admin`} - ${title}`;
} }
if (value !== "") { if (value !== "") {
title = `${value} - ${title}`; title = `${value} - ${title}`;
} }
document.title = title; document.title = title;
});
this._header = value; this._header = value;
} }

View File

@ -73,12 +73,8 @@ export class Tabs extends AKElement {
updateURLParams(params); updateURLParams(params);
const page = this.querySelector(`[slot='${this.currentPage}']`); const page = this.querySelector(`[slot='${this.currentPage}']`);
if (!page) return; if (!page) return;
page.dispatchEvent( page.dispatchEvent(new CustomEvent(EVENT_REFRESH));
new CustomEvent(EVENT_REFRESH, { page.dispatchEvent(new CustomEvent("activate"));
bubbles: true,
composed: true,
}),
);
} }
renderTab(page: Element): TemplateResult { renderTab(page: Element): TemplateResult {
@ -94,10 +90,10 @@ export class Tabs extends AKElement {
const pages = Array.from(this.querySelectorAll(":scope > [slot^='page-']")); const pages = Array.from(this.querySelectorAll(":scope > [slot^='page-']"));
if (window.location.hash.includes(ROUTE_SEPARATOR)) { if (window.location.hash.includes(ROUTE_SEPARATOR)) {
const params = getURLParams(); const params = getURLParams();
if (this.pageIdentifier in params) { if (this.pageIdentifier in params && !this.currentPage) {
if (this.querySelector(`[slot='${params[this.pageIdentifier]}']`) !== null) { if (this.querySelector(`[slot='${params[this.pageIdentifier]}']`) !== null) {
// To update the URL to match with the current slot // To update the URL to match with the current slot
this.currentPage = params[this.pageIdentifier] as string; this.onClick(params[this.pageIdentifier] as string);
} }
} }
} }

View File

@ -11,13 +11,12 @@ export class AggregatePromiseCard extends AggregateCard {
@property({ attribute: false }) @property({ attribute: false })
promise?: Promise<Record<string, unknown>>; promise?: Promise<Record<string, unknown>>;
promiseProxy(): Promise<TemplateResult> { async promiseProxy(): Promise<TemplateResult> {
if (!this.promise) { if (!this.promise) {
return new Promise<TemplateResult>(() => html``); return html``;
} }
return this.promise.then((s) => { const value = await this.promise;
return html`<i class="fa fa-check-circle"></i>&nbsp;${s.toString()}`; return html`<i class="fa fa-check-circle"></i>&nbsp;${value.toString()}`;
});
} }
renderInner(): TemplateResult { renderInner(): TemplateResult {

View File

@ -4,7 +4,6 @@ import { AKElement } from "@goauthentik/elements/Base";
import { CSSResult, css } from "lit"; import { CSSResult, css } from "lit";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFNav from "@patternfly/patternfly/components/Nav/nav.css"; import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css";
@ -72,9 +71,6 @@ export class SidebarItem extends AKElement {
@property() @property()
path?: string; path?: string;
@property({ attribute: false })
condition: () => Promise<boolean> = async () => true;
activeMatchers: RegExp[] = []; activeMatchers: RegExp[] = [];
@property({ type: Boolean }) @property({ type: Boolean })
@ -145,16 +141,10 @@ export class SidebarItem extends AKElement {
} }
render(): TemplateResult { render(): TemplateResult {
return html`${until(this.renderInner())}`; return this.renderInner();
} }
async renderInner(): Promise<TemplateResult> { renderInner(): TemplateResult {
if (this.condition) {
const result = await this.condition();
if (!result) {
return html``;
}
}
if (this.childItems.length > 0) { if (this.childItems.length > 0) {
return html`<li return html`<li
class="pf-c-nav__item ${this.expanded ? "pf-m-expandable pf-m-expanded" : ""}" class="pf-c-nav__item ${this.expanded ? "pf-m-expandable pf-m-expanded" : ""}"

View File

@ -1,10 +1,9 @@
import { me } from "@goauthentik/common/users"; import { AdminInterface } from "@goauthentik/admin/AdminInterface";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement } from "lit/decorators.js"; import { customElement } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import PFNav from "@patternfly/patternfly/components/Nav/nav.css"; import PFNav from "@patternfly/patternfly/components/Nav/nav.css";
@ -34,18 +33,12 @@ export class SidebarUser extends AKElement {
} }
render(): TemplateResult { render(): TemplateResult {
const me = rootInterface<AdminInterface>()?.user;
return html` return html`
<a href="/if/user/#/settings" class="pf-c-nav__link user-avatar" id="user-settings"> <a href="/if/user/#/settings" class="pf-c-nav__link user-avatar" id="user-settings">
${until( ${me
me().then((u) => { ? html`<img class="pf-c-avatar" src="${ifDefined(me.user.avatar)}" alt="" />`
return html`<img : html``}
class="pf-c-avatar"
src="${ifDefined(u.user.avatar)}"
alt=""
/>`;
}),
html``,
)}
</a> </a>
<a href="/flows/-/default/invalidation/" class="pf-c-nav__link user-logout" id="logout"> <a href="/flows/-/default/invalidation/" class="pf-c-nav__link user-logout" id="logout">
<i class="fas fa-sign-out-alt" aria-hidden="true"></i> <i class="fas fa-sign-out-alt" aria-hidden="true"></i>

View File

@ -1,4 +1,4 @@
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { import {
EVENT_FLOW_ADVANCE, EVENT_FLOW_ADVANCE,
EVENT_FLOW_INSPECTOR_TOGGLE, EVENT_FLOW_INSPECTOR_TOGGLE,
@ -64,13 +64,11 @@ export class FlowExecutor extends Interface implements StageHost {
); );
window.location.assign((value as RedirectChallenge).to); window.location.assign((value as RedirectChallenge).to);
} }
tenant().then((tenant) => { if (value?.flowInfo?.title) {
if (value?.flowInfo?.title) { document.title = `${value.flowInfo?.title} - ${this.tenant?.brandingTitle}`;
document.title = `${value.flowInfo?.title} - ${tenant.brandingTitle}`; } else {
} else { document.title = this.tenant?.brandingTitle || TITLE_DEFAULT;
document.title = tenant.brandingTitle || TITLE_DEFAULT; }
}
});
this.requestUpdate(); this.requestUpdate();
} }
@ -527,15 +525,13 @@ export class FlowExecutor extends Interface implements StageHost {
<footer class="pf-c-login__footer"> <footer class="pf-c-login__footer">
<p></p> <p></p>
<ul class="pf-c-list pf-m-inline"> <ul class="pf-c-list pf-m-inline">
${until( ${this.tenant?.uiFooterLinks?.map((link) => {
this.tenant?.uiFooterLinks?.map((link) => { return html`<li>
return html`<li> <a href="${link.href || ""}"
<a href="${link.href || ""}" >${link.name}</a
>${link.name}</a >
> </li>`;
</li>`; })}
}),
)}
<li> <li>
<a <a
href="https://goauthentik.io?utm_source=authentik&amp;utm_medium=flow" href="https://goauthentik.io?utm_source=authentik&amp;utm_medium=flow"

View File

@ -40,6 +40,7 @@ export class AuthenticatorValidateStage
set loading(value: boolean) { set loading(value: boolean) {
this.host.loading = value; this.host.loading = value;
} }
get loading(): boolean { get loading(): boolean {
return this.host.loading; return this.host.loading;
} }

View File

@ -1,14 +1,12 @@
import { uiConfig } from "@goauthentik/common/ui/config";
import { me } from "@goauthentik/common/users";
import { truncateWords } from "@goauthentik/common/utils"; import { truncateWords } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { UserInterface } from "@goauthentik/user/UserInterface";
import { t } from "@lingui/macro"; import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css"; import PFAvatar from "@patternfly/patternfly/components/Avatar/avatar.css";
import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFButton from "@patternfly/patternfly/components/Button/button.css";
@ -80,6 +78,7 @@ export class LibraryApplication extends AKElement {
if (!this.application) { if (!this.application) {
return html`<ak-spinner></ak-spinner>`; return html`<ak-spinner></ak-spinner>`;
} }
const me = rootInterface<UserInterface>()?.me;
return html` <div return html` <div
class="pf-c-card pf-m-hoverable pf-m-compact ${this.selected class="pf-c-card pf-m-hoverable pf-m-compact ${this.selected
? "pf-m-selectable pf-m-selected" ? "pf-m-selectable pf-m-selected"
@ -93,24 +92,16 @@ export class LibraryApplication extends AKElement {
> >
${this.renderIcon()} ${this.renderIcon()}
</a> </a>
${until( ${rootInterface()?.uiConfig?.enabledFeatures.applicationEdit && me?.user.isSuperuser
uiConfig().then((config) => { ? html`
if (!config.enabledFeatures.applicationEdit) { <a
return html``; class="pf-c-button pf-m-control pf-m-small"
} href="/if/admin/#/core/applications/${this.application?.slug}"
return me().then((u) => { >
if (!u.user.isSuperuser) return html``; <i class="fas fa-pencil-alt"></i>
return html` </a>
<a `
class="pf-c-button pf-m-control pf-m-small" : html``}
href="/if/admin/#/core/applications/${this.application?.slug}"
>
<i class="fas fa-pencil-alt"></i>
</a>
`;
});
}),
)}
</div> </div>
<div class="pf-c-card__title"> <div class="pf-c-card__title">
<p> <p>

View File

@ -1,7 +1,7 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { LayoutType, UIConfig, uiConfig } from "@goauthentik/common/ui/config"; import { LayoutType } from "@goauthentik/common/ui/config";
import { groupBy } from "@goauthentik/common/utils"; import { groupBy } from "@goauthentik/common/utils";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/EmptyState";
import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch"; import { getURLParam, updateURLParams } from "@goauthentik/elements/router/RouteMatch";
import { PaginatedResponse } from "@goauthentik/elements/table/Table"; import { PaginatedResponse } from "@goauthentik/elements/table/Table";
@ -12,7 +12,7 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js"; import { ifDefined } from "lit/directives/if-defined.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css"; import PFEmptyState from "@patternfly/patternfly/components/EmptyState/empty-state.css";
@ -130,10 +130,11 @@ export class LibraryPage extends AKElement {
return groupBy(this.filterApps(), (app) => app.group || ""); return groupBy(this.filterApps(), (app) => app.group || "");
} }
renderApps(config: UIConfig): TemplateResult { renderApps(): TemplateResult {
let groupClass = ""; let groupClass = "";
let groupGrid = ""; let groupGrid = "";
switch (config.layout.type) { const uiConfig = rootInterface()?.uiConfig;
switch (uiConfig?.layout.type) {
case LayoutType.row: case LayoutType.row:
groupClass = "pf-m-12-col"; groupClass = "pf-m-12-col";
groupGrid = groupGrid =
@ -161,7 +162,7 @@ export class LibraryPage extends AKElement {
return html`<ak-library-app return html`<ak-library-app
class="pf-l-grid__item" class="pf-l-grid__item"
.application=${app} .application=${app}
background=${config.theme.cardBackground} background=${ifDefined(uiConfig?.theme.cardBackground)}
?selected=${app.slug === this.selectedApp?.slug} ?selected=${app.slug === this.selectedApp?.slug}
></ak-library-app>`; ></ak-library-app>`;
})} })}
@ -172,57 +173,48 @@ export class LibraryPage extends AKElement {
} }
render(): TemplateResult { render(): TemplateResult {
return html`${until( return html`<main role="main" class="pf-c-page__main" tabindex="-1" id="main-content">
uiConfig().then((config) => { <div class="pf-c-content header">
return html`<main <h1>${t`My applications`}</h1>
role="main" ${rootInterface()?.uiConfig?.enabledFeatures.search
class="pf-c-page__main" ? html`<input
tabindex="-1" @input=${(ev: InputEvent) => {
id="main-content" this.query = (ev.target as HTMLInputElement).value;
> updateURLParams({
<div class="pf-c-content header"> search: this.query,
<h1>${t`My applications`}</h1> });
${config.enabledFeatures.search if (!this.fuse) return;
? html`<input const apps = this.fuse.search(this.query);
@input=${(ev: InputEvent) => { if (apps.length < 1) return;
this.query = (ev.target as HTMLInputElement).value; this.selectedApp = apps[0].item;
updateURLParams({ }}
search: this.query, @keydown=${(ev: KeyboardEvent) => {
}); if (ev.key === "Enter" && this.selectedApp?.launchUrl) {
if (!this.fuse) return; window.location.assign(this.selectedApp.launchUrl);
const apps = this.fuse.search(this.query); } else if (ev.key === "Escape") {
if (apps.length < 1) return; (ev.target as HTMLInputElement).value = "";
this.selectedApp = apps[0].item; this.query = "";
}} updateURLParams({
@keydown=${(ev: KeyboardEvent) => { search: this.query,
if (ev.key === "Enter" && this.selectedApp?.launchUrl) { });
window.location.assign(this.selectedApp.launchUrl); this.selectedApp = undefined;
} else if (ev.key === "Escape") { }
(ev.target as HTMLInputElement).value = ""; }}
this.query = ""; type="text"
updateURLParams({ class="pf-u-display-none pf-u-display-block-on-md"
search: this.query, autofocus
}); placeholder=${t`Search...`}
this.selectedApp = undefined; />`
} : html``}
}} </div>
type="text" <section class="pf-c-page__main-section">
class="pf-u-display-none pf-u-display-block-on-md" ${loading(
autofocus this.apps,
placeholder=${t`Search...`} html`${this.filterApps().length > 0
/>` ? this.renderApps()
: html``} : this.renderEmptyState()}`,
</div> )}
<section class="pf-c-page__main-section"> </section>
${loading( </main>`;
this.apps,
html`${this.filterApps().length > 0
? this.renderApps(config)
: this.renderEmptyState()}`,
)}
</section>
</main>`;
}),
)}`;
} }
} }

View File

@ -1,10 +1,10 @@
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { me } from "@goauthentik/common/users"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/Tabs";
import "@goauthentik/elements/user/SessionList"; import "@goauthentik/elements/user/SessionList";
import "@goauthentik/elements/user/UserConsentList"; import "@goauthentik/elements/user/UserConsentList";
import { UserInterface } from "@goauthentik/user/UserInterface";
import "@goauthentik/user/user-settings/details/UserPassword"; import "@goauthentik/user/user-settings/details/UserPassword";
import "@goauthentik/user/user-settings/details/UserSettingsFlowExecutor"; import "@goauthentik/user/user-settings/details/UserSettingsFlowExecutor";
import "@goauthentik/user/user-settings/mfa/MFADevicesPage"; import "@goauthentik/user/user-settings/mfa/MFADevicesPage";
@ -16,7 +16,6 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, state } from "lit/decorators.js"; import { customElement, state } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import PFCard from "@patternfly/patternfly/components/Card/card.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
@ -62,7 +61,7 @@ export class UserSettingsPage extends AKElement {
} }
@state() @state()
userSettings!: Promise<UserSetting[]>; userSettings?: UserSetting[];
constructor() { constructor() {
super(); super();
@ -71,11 +70,14 @@ export class UserSettingsPage extends AKElement {
}); });
} }
firstUpdated(): void { async firstUpdated(): Promise<void> {
this.userSettings = new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList(); this.userSettings = await new StagesApi(DEFAULT_CONFIG).stagesAllUserSettingsList();
} }
render(): TemplateResult { render(): TemplateResult {
const pwStage = this.userSettings?.filter(
(stage) => stage.component === "ak-user-settings-password",
);
return html`<div class="pf-c-page"> return html`<div class="pf-c-page">
<main role="main" class="pf-c-page__main" tabindex="-1"> <main role="main" class="pf-c-page__main" tabindex="-1">
<ak-tabs ?vertical="${true}"> <ak-tabs ?vertical="${true}">
@ -89,20 +91,11 @@ export class UserSettingsPage extends AKElement {
<ak-user-settings-flow-executor></ak-user-settings-flow-executor> <ak-user-settings-flow-executor></ak-user-settings-flow-executor>
</div> </div>
<div class="pf-l-stack__item"> <div class="pf-l-stack__item">
${until( ${pwStage
this.userSettings?.then((settings) => { ? html`<ak-user-settings-password
const pwStage = settings.filter( configureUrl=${ifDefined(pwStage[0].configureUrl)}
(stage) => ></ak-user-settings-password>`
stage.component === "ak-user-settings-password", : html``}
);
if (pwStage.length > 0) {
return html`<ak-user-settings-password
configureUrl=${ifDefined(pwStage[0].configureUrl)}
></ak-user-settings-password>`;
}
return html``;
}),
)}
</div> </div>
</div> </div>
</section> </section>
@ -111,26 +104,20 @@ export class UserSettingsPage extends AKElement {
data-tab-title="${t`Sessions`}" data-tab-title="${t`Sessions`}"
class="pf-c-page__main-section pf-m-no-padding-mobile" class="pf-c-page__main-section pf-m-no-padding-mobile"
> >
${until( <ak-user-session-list
me().then((u) => { targetUser=${ifDefined(
return html`<ak-user-session-list rootInterface<UserInterface>()?.me?.user.username,
targetUser=${u.user.username} )}
></ak-user-session-list>`; ></ak-user-session-list>
}),
)}
</section> </section>
<section <section
slot="page-consents" slot="page-consents"
data-tab-title="${t`Consent`}" data-tab-title="${t`Consent`}"
class="pf-c-page__main-section pf-m-no-padding-mobile" class="pf-c-page__main-section pf-m-no-padding-mobile"
> >
${until( <ak-user-consent-list
me().then((u) => { userId=${ifDefined(rootInterface<UserInterface>()?.me?.user.pk)}
return html`<ak-user-consent-list ></ak-user-consent-list>
userId=${u.user.pk}
></ak-user-consent-list>`;
}),
)}
</section> </section>
<section <section
slot="page-mfa" slot="page-mfa"

View File

@ -1,8 +1,8 @@
import { DEFAULT_CONFIG, tenant } from "@goauthentik/common/api/config"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages"; import { MessageLevel } from "@goauthentik/common/messages";
import { refreshMe } from "@goauthentik/common/users"; import { refreshMe } from "@goauthentik/common/users";
import { AKElement } from "@goauthentik/elements/Base"; import { AKElement, rootInterface } from "@goauthentik/elements/Base";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { StageHost } from "@goauthentik/flow/stages/base"; import { StageHost } from "@goauthentik/flow/stages/base";
import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage"; import "@goauthentik/user/user-settings/details/stages/prompt/PromptStage";
@ -22,7 +22,6 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
import { import {
ChallengeChoices, ChallengeChoices,
ChallengeTypes, ChallengeTypes,
CurrentTenant,
FlowChallengeResponseRequest, FlowChallengeResponseRequest,
FlowErrorChallenge, FlowErrorChallenge,
FlowsApi, FlowsApi,
@ -51,18 +50,10 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost {
@property({ type: Boolean }) @property({ type: Boolean })
loading = false; loading = false;
@property({ attribute: false })
tenant!: CurrentTenant;
static get styles(): CSSResult[] { static get styles(): CSSResult[] {
return [PFBase, PFCard, PFPage, PFButton, PFContent]; return [PFBase, PFCard, PFPage, PFButton, PFContent];
} }
constructor() {
super();
tenant().then((tenant) => (this.tenant = tenant));
}
submit(payload?: FlowChallengeResponseRequest): Promise<boolean> { submit(payload?: FlowChallengeResponseRequest): Promise<boolean> {
if (!payload) return Promise.reject(); if (!payload) return Promise.reject();
if (!this.challenge) return Promise.reject(); if (!this.challenge) return Promise.reject();
@ -93,13 +84,12 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost {
} }
firstUpdated(): void { firstUpdated(): void {
tenant().then((tenant) => { const tenant = rootInterface()?.tenant;
this.flowSlug = tenant.flowUserSettings; this.flowSlug = tenant?.flowUserSettings;
if (!this.flowSlug) { if (!this.flowSlug) {
return; return;
} }
this.nextChallenge(); this.nextChallenge();
});
} }
async nextChallenge(): Promise<void> { async nextChallenge(): Promise<void> {

View File

@ -13,7 +13,6 @@ import { t } from "@lingui/macro";
import { TemplateResult, html } from "lit"; import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { ifDefined } from "lit/directives/if-defined.js"; import { ifDefined } from "lit/directives/if-defined.js";
import { until } from "lit/directives/until.js";
import { AuthenticatorsApi, Device, UserSetting } from "@goauthentik/api"; import { AuthenticatorsApi, Device, UserSetting } from "@goauthentik/api";
@ -47,7 +46,7 @@ export function deviceTypeName(device: Device): string {
@customElement("ak-user-settings-mfa") @customElement("ak-user-settings-mfa")
export class MFADevicesPage extends Table<Device> { export class MFADevicesPage extends Table<Device> {
@property({ attribute: false }) @property({ attribute: false })
userSettings?: Promise<UserSetting[]>; userSettings?: UserSetting[];
checkbox = true; checkbox = true;
@ -70,41 +69,32 @@ export class MFADevicesPage extends Table<Device> {
} }
renderToolbar(): TemplateResult { renderToolbar(): TemplateResult {
const settings = (this.userSettings || []).filter((stage) => {
if (stage.component === "ak-user-settings-password") {
return false;
}
return stage.configureUrl;
});
return html`<ak-dropdown class="pf-c-dropdown"> return html`<ak-dropdown class="pf-c-dropdown">
<button class="pf-m-primary pf-c-dropdown__toggle" type="button"> <button class="pf-m-primary pf-c-dropdown__toggle" type="button">
<span class="pf-c-dropdown__toggle-text">${t`Enroll`}</span> <span class="pf-c-dropdown__toggle-text">${t`Enroll`}</span>
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i> <i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button> </button>
<ul class="pf-c-dropdown__menu" hidden> <ul class="pf-c-dropdown__menu" hidden>
${until( ${settings.map((stage) => {
this.userSettings?.then((stages) => { return html`<li>
return stages <a
.filter((stage) => { href="${ifDefined(stage.configureUrl)}${AndNext(
if (stage.component === "ak-user-settings-password") { `/if/user/#/settings;${JSON.stringify({
return false; page: "page-mfa",
} })}`,
return stage.configureUrl; )}"
}) class="pf-c-dropdown__menu-item"
.map((stage) => { >
return html`<li> ${stageToAuthenticatorName(stage)}
<a </a>
href="${ifDefined(stage.configureUrl)}${AndNext( </li>`;
`/if/user/#/settings;${JSON.stringify({ })}
page: "page-mfa",
})}`,
)}"
class="pf-c-dropdown__menu-item"
>
${stageToAuthenticatorName(stage)}
</a>
</li>`;
});
}),
html`<ak-empty-state
?loading="${true}"
header=${t`Loading`}
></ak-empty-state>`,
)}
</ul> </ul>
</ak-dropdown> </ak-dropdown>
${super.renderToolbar()}`; ${super.renderToolbar()}`;

View File

@ -12,7 +12,6 @@ import { t } from "@lingui/macro";
import { CSSResult, TemplateResult, css, html } from "lit"; import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js"; import { customElement, property } from "lit/decorators.js";
import { until } from "lit/directives/until.js";
import PFContent from "@patternfly/patternfly/components/Content/content.css"; import PFContent from "@patternfly/patternfly/components/Content/content.css";
import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css"; import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";
@ -22,7 +21,7 @@ import { PaginatedUserSourceConnectionList, SourcesApi, UserSetting } from "@goa
@customElement("ak-user-settings-source") @customElement("ak-user-settings-source")
export class UserSourceSettingsPage extends AKElement { export class UserSourceSettingsPage extends AKElement {
@property({ attribute: false }) @property({ attribute: false })
sourceSettings?: Promise<UserSetting[]>; sourceSettings?: UserSetting[];
@property({ attribute: false }) @property({ attribute: false })
connections?: PaginatedUserSourceConnectionList; connections?: PaginatedUserSourceConnectionList;
@ -57,7 +56,7 @@ export class UserSourceSettingsPage extends AKElement {
async firstUpdated(): Promise<void> { async firstUpdated(): Promise<void> {
const user = await me(); const user = await me();
this.sourceSettings = new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList(); this.sourceSettings = await new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList();
this.connections = await new SourcesApi(DEFAULT_CONFIG).sourcesUserConnectionsAllList({ this.connections = await new SourcesApi(DEFAULT_CONFIG).sourcesUserConnectionsAllList({
user: user.user.pk, user: user.user.pk,
}); });
@ -115,30 +114,33 @@ export class UserSourceSettingsPage extends AKElement {
</p> </p>
</div> </div>
<ul class="pf-c-data-list" role="list"> <ul class="pf-c-data-list" role="list">
${until( ${this.sourceSettings
this.sourceSettings?.then((source) => { ? html`
if (source.length < 1) { ${this.sourceSettings.length < 1
return html`<ak-empty-state ? html`<ak-empty-state
header=${t`No services available.`} header=${t`No services available.`}
></ak-empty-state>`; ></ak-empty-state>`
} : html`
return source.map((source) => { ${this.sourceSettings.map((source) => {
return html`<li class="pf-c-data-list__item"> return html`<li class="pf-c-data-list__item">
<div class="pf-c-data-list__item-content"> <div class="pf-c-data-list__item-content">
<div class="pf-c-data-list__cell"> <div class="pf-c-data-list__cell">
${renderSourceIcon(source.title, source.iconUrl)} ${renderSourceIcon(
${source.title} source.title,
</div> source.iconUrl,
<div class="pf-c-data-list__cell"> )}
${this.renderSourceSettings(source)} ${source.title}
</div> </div>
</div> <div class="pf-c-data-list__cell">
</li>`; ${this.renderSourceSettings(source)}
}); </div>
}), </div>
html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </li>`;
</ak-empty-state>`, })}
)} `}
`
: html`<ak-empty-state ?loading="${true}" header=${t`Loading`}>
</ak-empty-state>`}
</ul>`; </ul>`;
} }
} }