diff --git a/.github/stale.yml b/.github/stale.yml index 545f958d2..ee67af42a 100644 --- a/.github/stale.yml +++ b/.github/stale.yml @@ -16,3 +16,4 @@ markComment: > 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 for your contributions. +only: issues diff --git a/web/src/admin/admin-overview/AdminOverviewPage.ts b/web/src/admin/admin-overview/AdminOverviewPage.ts index 41b19a4e1..db05badb5 100644 --- a/web/src/admin/admin-overview/AdminOverviewPage.ts +++ b/web/src/admin/admin-overview/AdminOverviewPage.ts @@ -1,3 +1,4 @@ +import { AdminInterface } from "@goauthentik/admin/AdminInterface"; import "@goauthentik/admin/admin-overview/TopApplicationsTable"; import "@goauthentik/admin/admin-overview/cards/AdminStatusCard"; 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/SyncStatusChart"; import { VERSION } from "@goauthentik/common/constants"; -import { me } from "@goauthentik/common/users"; -import { AKElement } from "@goauthentik/elements/Base"; +import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/PageHeader"; import "@goauthentik/elements/cards/AggregatePromiseCard"; import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; @@ -17,15 +17,13 @@ import { paramURL } from "@goauthentik/elements/router/RouterOutlet"; import { t } from "@lingui/macro"; 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 PFList from "@patternfly/patternfly/components/List/list.css"; import PFPage from "@patternfly/patternfly/components/Page/page.css"; import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; -import { SessionUser } from "@goauthentik/api"; - export function versionFamily(): string { const parts = VERSION.split("."); parts.pop(); @@ -58,17 +56,11 @@ export class AdminOverviewPage extends AKElement { ]; } - @state() - user?: SessionUser; - - async firstUpdated(): Promise { - this.user = await me(); - } - render(): TemplateResult { - let name = this.user?.user.username; - if (this.user?.user.name) { - name = this.user.user.name; + const user = rootInterface()?.user; + let name = user?.user.username; + if (user?.user.name) { + name = user.user.name; } return html` ${t`Welcome, ${name}.`} diff --git a/web/src/admin/outposts/OutpostListPage.ts b/web/src/admin/outposts/OutpostListPage.ts index b273dc414..4d7c6a053 100644 --- a/web/src/admin/outposts/OutpostListPage.ts +++ b/web/src/admin/outposts/OutpostListPage.ts @@ -18,13 +18,12 @@ import { t } from "@lingui/macro"; import { CSSResult } 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 { until } from "lit/directives/until.js"; 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 { if (!type) return ""; @@ -56,14 +55,31 @@ export class OutpostListPage extends TablePage { searchEnabled(): boolean { return true; } + async apiEndpoint(page: number): Promise> { - return new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList({ + const outposts = await new OutpostsApi(DEFAULT_CONFIG).outpostsInstancesList({ ordering: this.order, page: page, pageSize: (await uiConfig()).pagination.perPage, 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[] { return [ new TableColumn(t`Name`, "name"), @@ -136,25 +152,15 @@ export class OutpostListPage extends TablePage { ${t`Detailed health (one instance per column, data is cached so may be out of date)`}
- ${until( - new OutpostsApi(DEFAULT_CONFIG) - .outpostsInstancesHealthList({ - uuid: item.pk, - }) - .then((health) => { - return health.map((h) => { - return html`
-
-
- -
-
-
`; - }); - }), - )} + ${this.health[item.pk].map((h) => { + return html`
+
+
+ +
+
+
`; + })}
`; diff --git a/web/src/admin/outposts/ServiceConnectionListPage.ts b/web/src/admin/outposts/ServiceConnectionListPage.ts index 6225ff822..f40dfe37f 100644 --- a/web/src/admin/outposts/ServiceConnectionListPage.ts +++ b/web/src/admin/outposts/ServiceConnectionListPage.ts @@ -16,11 +16,10 @@ import { TablePage } from "@goauthentik/elements/table/TablePage"; import { t } from "@lingui/macro"; 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 { 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") export class OutpostServiceConnectionListPage extends TablePage { @@ -40,14 +39,31 @@ export class OutpostServiceConnectionListPage extends TablePage> { - return new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList({ - ordering: this.order, - page: page, - pageSize: (await uiConfig()).pagination.perPage, - search: this.search || "", - }); + const connections = await new OutpostsApi(DEFAULT_CONFIG).outpostsServiceConnectionsAllList( + { + ordering: this.order, + page: page, + 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[] { return [ new TableColumn(t`Name`, "name"), @@ -62,27 +78,16 @@ export class OutpostServiceConnectionListPage extends TablePage ${item.local ? t`Yes` : t`No`} `, - html`${until( - new OutpostsApi(DEFAULT_CONFIG) - .outpostsServiceConnectionsAllStateRetrieve({ - uuid: item.pk || "", - }) - .then((state) => { - if (state.healthy) { - return html`${ifDefined(state.version)}`; - } - return html`${t`Unhealthy`}`; - }), - html``, - )}`, + html`${itemState.healthy + ? html`${ifDefined(itemState.version)}` + : html`${t`Unhealthy`}`}`, html` ${t`Update`} ${t`Update ${item.verboseName}`} diff --git a/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts b/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts index 19a92a3b3..9f80a518b 100644 --- a/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts +++ b/web/src/admin/providers/oauth2/OAuth2ProviderViewPage.ts @@ -6,6 +6,7 @@ import { convertToTitle } from "@goauthentik/common/utils"; import MDProviderOAuth2 from "@goauthentik/docs/providers/oauth2/index.md"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/Markdown"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ModalButton"; @@ -15,8 +16,7 @@ import "@goauthentik/elements/events/ObjectChangelog"; import { t } from "@lingui/macro"; import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; -import { until } from "lit/directives/until.js"; +import { customElement, property, state } from "lit/decorators.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.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 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") export class OAuth2ProviderViewPage extends AKElement { @property({ type: Number }) set providerID(value: number) { - const api = new ProvidersApi(DEFAULT_CONFIG); - api.providersOauth2Retrieve({ - id: value, - }).then((prov) => { - this.provider = prov; - }); - api.providersOauth2SetupUrlsRetrieve({ - id: value, - }).then((prov) => { - this.providerUrls = prov; - }); + new ProvidersApi(DEFAULT_CONFIG) + .providersOauth2Retrieve({ + id: value, + }) + .then((prov) => { + this.provider = prov; + }); } @property({ attribute: false }) provider?: OAuth2Provider; - @property({ attribute: false }) + @state() providerUrls?: OAuth2ProviderSetupURLs; + @state() + preview?: PropertyMappingPreview; + static get styles(): CSSResult[] { return [ PFBase, @@ -82,10 +86,32 @@ export class OAuth2ProviderViewPage extends AKElement { return html``; } return html` -
+
{ + new ProvidersApi(DEFAULT_CONFIG) + .providersOauth2SetupUrlsRetrieve({ + id: this.provider?.pk || 0, + }) + .then((prov) => { + this.providerUrls = prov; + }); + }} + > ${this.renderTabOverview()}
-
+
{ + new ProvidersApi(DEFAULT_CONFIG) + .providersOauth2PreviewUserRetrieve({ + id: this.provider?.pk || 0, + }) + .then((preview) => (this.preview = preview)); + }} + > ${this.renderTabPreview()}
- ${until( - new ProvidersApi(DEFAULT_CONFIG) - .providersOauth2PreviewUserRetrieve({ - id: this.provider?.pk, - }) - .then((data) => { - return html`
${JSON.stringify(data.preview, null, 4)}
`; - }), - )} + ${this.preview + ? html`
${JSON.stringify(this.preview?.preview, null, 4)}
` + : html` `}
`; diff --git a/web/src/admin/providers/saml/SAMLProviderViewPage.ts b/web/src/admin/providers/saml/SAMLProviderViewPage.ts index 90255729f..9d8e98711 100644 --- a/web/src/admin/providers/saml/SAMLProviderViewPage.ts +++ b/web/src/admin/providers/saml/SAMLProviderViewPage.ts @@ -5,6 +5,7 @@ import { EVENT_REFRESH } from "@goauthentik/common/constants"; import { MessageLevel } from "@goauthentik/common/messages"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/CodeMirror"; +import "@goauthentik/elements/EmptyState"; import "@goauthentik/elements/Tabs"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/ModalButton"; @@ -15,9 +16,8 @@ import { showMessage } from "@goauthentik/elements/messages/MessageContainer"; import { t } from "@lingui/macro"; 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 { until } from "lit/directives/until.js"; import PFBanner from "@patternfly/patternfly/components/Banner/banner.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 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 { attributes: { @@ -54,12 +60,40 @@ export class SAMLProviderViewPage extends AKElement { .providersSamlRetrieve({ 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 }) provider?: SAMLProvider; + @state() + preview?: SAMLPreviewAttribute; + + @state() + metadata?: SAMLMetadata; + + @state() + signer?: CertificateKeyPair; + + @state() + verifier?: CertificateKeyPair; + static get styles(): CSSResult[] { return [ PFBase, @@ -84,7 +118,7 @@ export class SAMLProviderViewPage extends AKElement { }); } - async renderRelatedObjects(): Promise { + renderRelatedObjects(): TemplateResult { const relatedObjects = []; if (this.provider?.assignedApplicationName) { relatedObjects.push(html`
@@ -122,10 +156,7 @@ export class SAMLProviderViewPage extends AKElement {
`); } - if (this.provider?.signingKp) { - const kp = await new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRetrieve({ - kpUuid: this.provider.signingKp, - }); + if (this.signer) { relatedObjects.push(html`
@@ -160,7 +193,19 @@ export class SAMLProviderViewPage extends AKElement { ${this.renderTabOverview()}
${this.renderTabMetadata()} -
+
{ + new ProvidersApi(DEFAULT_CONFIG) + .providersSamlPreviewUserRetrieve({ + id: this.provider?.pk || 0, + }) + .then((preview) => { + this.preview = preview.preview as SAMLPreviewAttribute; + }); + }} + > ${this.renderTabPreview()}
- ${until(this.renderRelatedObjects())} + ${this.renderRelatedObjects()} ${ this.provider.assignedApplicationName ? html`
@@ -364,7 +409,17 @@ export class SAMLProviderViewPage extends AKElement { } return html` ${this.provider.assignedApplicationName - ? html`
+ ? html`
{ + new ProvidersApi(DEFAULT_CONFIG) + .providersSamlMetadataRetrieve({ + id: this.provider?.pk || 0, + }) + .then((metadata) => (this.metadata = metadata)); + }} + >
@@ -399,19 +454,11 @@ export class SAMLProviderViewPage extends AKElement {
@@ -421,65 +468,50 @@ export class SAMLProviderViewPage extends AKElement { } renderTabPreview(): TemplateResult { - if (!this.provider) { - return html``; + if (!this.preview) { + return html``; } return html`
${t`Example SAML attributes`}
- ${until( - new ProvidersApi(DEFAULT_CONFIG) - .providersSamlPreviewUserRetrieve({ - id: this.provider?.pk, - }) - .then((data) => { - const d = data.preview as SAMLPreviewAttribute; - return html` -
-
-
-
- ${t`NameID attribute`} -
-
-
- ${d.nameID} -
-
-
-
+
+
+
+
+ ${t`NameID attribute`} +
+
+
+ ${this.preview?.nameID}
-
-
- ${d.attributes.map((attr) => { - return html`
-
- ${attr.Name} -
-
-
-
    - ${attr.Value.map((value) => { - return html` -
  • ${value}
  • - `; - })} -
-
-
-
`; - })} -
-
- `; - }), - )} +
+
+
+
+
+
+ ${this.preview?.attributes.map((attr) => { + return html`
+
+ ${attr.Name} +
+
+
+
    + ${attr.Value.map((value) => { + return html`
  • ${value}
  • `; + })} +
+
+
+
`; + })} +
+
`; } diff --git a/web/src/admin/providers/scim/SCIMProviderViewPage.ts b/web/src/admin/providers/scim/SCIMProviderViewPage.ts index ab118d4be..441b39950 100644 --- a/web/src/admin/providers/scim/SCIMProviderViewPage.ts +++ b/web/src/admin/providers/scim/SCIMProviderViewPage.ts @@ -1,7 +1,6 @@ import "@goauthentik/admin/providers/scim/SCIMProviderForm"; import { DEFAULT_CONFIG } from "@goauthentik/common/api/config"; import { EVENT_REFRESH } from "@goauthentik/common/constants"; -import { me } from "@goauthentik/common/users"; import MDSCIMProvider from "@goauthentik/docs/providers/scim/index.md"; import { AKElement } from "@goauthentik/elements/Base"; import "@goauthentik/elements/Markdown"; @@ -14,7 +13,6 @@ import { t } from "@lingui/macro"; import { CSSResult, TemplateResult, html } from "lit"; 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 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 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") export class SCIMProviderViewPage extends AKElement { @@ -51,7 +49,7 @@ export class SCIMProviderViewPage extends AKElement { provider?: SCIMProvider; @state() - me?: SessionUser; + syncState?: Task; static get styles(): CSSResult[] { return [ @@ -76,9 +74,6 @@ export class SCIMProviderViewPage extends AKElement { if (!this.provider?.pk) return; this.providerID = this.provider?.pk; }); - me().then((user) => { - this.me = user; - }); } render(): TemplateResult { @@ -86,7 +81,22 @@ export class SCIMProviderViewPage extends AKElement { return html``; } return html` -
+
{ + new ProvidersApi(DEFAULT_CONFIG) + .providersScimSyncStatusRetrieve({ + id: this.provider?.pk || 0, + }) + .then((state) => { + this.syncState = state; + }) + .catch(() => { + this.syncState = undefined; + }); + }} + > ${this.renderTabOverview()}
${t`Sync status`}

- ${until( - new ProvidersApi(DEFAULT_CONFIG) - .providersScimSyncStatusRetrieve({ - id: this.provider.pk, - }) - .then((task) => { - return html`
    - ${task.messages.map((m) => { - return html`
  • ${m}
  • `; - })} -
`; - }) - .catch(() => { - return html`${t`Sync not run yet.`}`; - }), - "loading", - )} + ${this.syncState + ? html`
    + ${this.syncState.messages.map((m) => { + return html`
  • ${m}
  • `; + })} +
` + : html` ${t`Sync not run yet.`} `}
diff --git a/web/src/admin/stages/StageListPage.ts b/web/src/admin/stages/StageListPage.ts index bc8e08f60..92aa94ea2 100644 --- a/web/src/admin/stages/StageListPage.ts +++ b/web/src/admin/stages/StageListPage.ts @@ -1,5 +1,6 @@ import "@goauthentik/admin/stages/StageWizard"; 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_static/AuthenticatorStaticStageForm"; import "@goauthentik/admin/stages/authenticator_totp/AuthenticatorTOTPStageForm"; @@ -33,7 +34,6 @@ import { t } from "@lingui/macro"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { until } from "lit/directives/until.js"; import { Stage, StagesApi } from "@goauthentik/api"; @@ -100,20 +100,24 @@ export class StageListPage extends TablePage { `; } - async renderStageActions(stage: Stage): Promise { - if (stage.component === "ak-stage-authenticator-duo-form") { - await import("@goauthentik/admin/stages/authenticator_duo/DuoDeviceImportForm"); - return html` - ${t`Import`} - ${t`Import Duo device`} - - - - `; + renderStageActions(stage: Stage): TemplateResult { + switch (stage.component) { + case "ak-stage-authenticator-duo-form": + return html` + ${t`Import`} + ${t`Import Duo device`} + + + + `; + default: + return html``; } - return html``; } row(item: Stage): TemplateResult[] { @@ -144,7 +148,7 @@ export class StageListPage extends TablePage { - ${until(this.renderStageActions(item))}`, + ${this.renderStageActions(item)}`, ]; } diff --git a/web/src/admin/users/RelatedUserList.ts b/web/src/admin/users/RelatedUserList.ts index 3778f241d..57687c7f7 100644 --- a/web/src/admin/users/RelatedUserList.ts +++ b/web/src/admin/users/RelatedUserList.ts @@ -3,10 +3,11 @@ import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; 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 { uiConfig } from "@goauthentik/common/ui/config"; import { first } from "@goauthentik/common/utils"; +import { rootInterface } from "@goauthentik/elements/Base"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/buttons/ActionButton"; import "@goauthentik/elements/buttons/Dropdown"; @@ -25,7 +26,6 @@ import { t } from "@lingui/macro"; import { CSSResult, TemplateResult, html } from "lit"; import { customElement, property, state } from "lit/decorators.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 PFDescriptionList from "@patternfly/patternfly/components/DescriptionList/description-list.css"; @@ -189,19 +189,16 @@ export class RelatedUserList extends Table { - ${until( - config().then((config) => { - if (config.capabilities.includes(CapabilitiesEnum.Impersonate)) { - return html` - ${t`Impersonate`} - `; - } - return html``; - }), - )}`, + ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.Impersonate) + ? html` + + ${t`Impersonate`} + + ` + : html``}`, ]; } @@ -266,70 +263,61 @@ export class RelatedUserList extends Table { ${t`Set password`} - ${until( - tenant().then((tenant) => { - if (!tenant.flowRecovery) { - return html` -

- ${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`} -

- `; - } - return html` - { - return new CoreApi(DEFAULT_CONFIG) - .coreUsersRecoveryRetrieve({ - id: item.pk || 0, - }) - .then((rec) => { - showMessage({ - level: MessageLevel.success, - message: t`Successfully generated recovery link`, - description: rec.link, - }); - }) - .catch((ex: ResponseError) => { - ex.response.json().then(() => { - showMessage({ - level: MessageLevel.error, - message: t`No recovery flow is configured.`, - }); - }); - }); - }} - > - ${t`Copy recovery link`} - - ${item.email - ? html` - - ${t`Send link`} - - - ${t`Send recovery link to user`} - - - - - ` - : html`${t`Recovery link cannot be emailed, user has no email address saved.`}`} - `; - }), - )} + ${rootInterface()?.tenant?.flowRecovery + ? html` + { + return new CoreApi(DEFAULT_CONFIG) + .coreUsersRecoveryRetrieve({ + id: item.pk, + }) + .then((rec) => { + showMessage({ + level: MessageLevel.success, + message: t`Successfully generated recovery link`, + description: rec.link, + }); + }) + .catch((ex: ResponseError) => { + ex.response.json().then(() => { + showMessage({ + level: MessageLevel.error, + message: t`No recovery flow is configured.`, + }); + }); + }); + }} + > + ${t`Copy recovery link`} + + ${item.email + ? html` + ${t`Send link`} + + ${t`Send recovery link to user`} + + + + + ` + : html`${t`Recovery link cannot be emailed, user has no email address saved.`}`} + ` + : html`

+ ${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`} +

`} diff --git a/web/src/admin/users/UserListPage.ts b/web/src/admin/users/UserListPage.ts index 4185291db..c19acbdee 100644 --- a/web/src/admin/users/UserListPage.ts +++ b/web/src/admin/users/UserListPage.ts @@ -1,13 +1,14 @@ +import { AdminInterface } from "@goauthentik/admin/AdminInterface"; import "@goauthentik/admin/users/ServiceAccountForm"; import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserForm"; import "@goauthentik/admin/users/UserPasswordForm"; 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 { uiConfig } from "@goauthentik/common/ui/config"; -import { me } from "@goauthentik/common/users"; import { first } from "@goauthentik/common/utils"; +import { rootInterface } from "@goauthentik/elements/Base"; import { PFColor } from "@goauthentik/elements/Label"; import { PFSize } from "@goauthentik/elements/Spinner"; import "@goauthentik/elements/TreeView"; @@ -23,14 +24,13 @@ import { TablePage } from "@goauthentik/elements/table/TablePage"; import { t } from "@lingui/macro"; import { CSSResult, TemplateResult, html } from "lit"; -import { customElement, property } from "lit/decorators.js"; -import { until } from "lit/directives/until.js"; +import { customElement, property, state } from "lit/decorators.js"; import PFAlert from "@patternfly/patternfly/components/Alert/alert.css"; import PFCard from "@patternfly/patternfly/components/Card/card.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") export class UserListPage extends TablePage { @@ -56,18 +56,25 @@ export class UserListPage extends TablePage { @property() activePath = getURLParam("path", "/"); + @state() + userPaths?: UserPath; + static get styles(): CSSResult[] { return super.styles.concat(PFDescriptionList, PFCard, PFAlert); } async apiEndpoint(page: number): Promise> { - return new CoreApi(DEFAULT_CONFIG).coreUsersList({ + const users = await new CoreApi(DEFAULT_CONFIG).coreUsersList({ ordering: this.order, page: page, pageSize: (await uiConfig()).pagination.perPage, search: this.search || "", pathStartswith: getURLParam("path", ""), }); + this.userPaths = await new CoreApi(DEFAULT_CONFIG).coreUsersPathsRetrieve({ + search: this.search, + }); + return users; } columns(): TableColumn[] { @@ -81,6 +88,10 @@ export class UserListPage extends TablePage { renderToolbarSelected(): TemplateResult { const disabled = this.selectedElements.length < 1; + const currentUser = rootInterface()?.user; + const shouldShowWarning = this.selectedElements.find((el) => { + return el.pk === currentUser?.user.pk || el.pk == currentUser?.original?.pk; + }); return html` { }); }} > - ${until( - me().then((user) => { - const shouldShowWarning = this.selectedElements.find((el) => { - return el.pk === user.user.pk || el.pk == user.original?.pk; - }); - if (shouldShowWarning) { - return html` -
-
-
- -
-

- ${t`Warning: You're about to delete the user you're logged in as (${shouldShowWarning.username}). Proceed at your own risk.`} -

-
-
- `; - } - return html``; - }), - )} + ${shouldShowWarning + ? html`
+
+
+ +
+

+ ${t`Warning: You're about to delete the user you're logged in as (${shouldShowWarning.username}). Proceed at your own risk.`} +

+
+
` + : html``} @@ -148,19 +149,16 @@ export class UserListPage extends TablePage { - ${until( - config().then((config) => { - if (config.capabilities.includes(CapabilitiesEnum.Impersonate)) { - return html` - ${t`Impersonate`} - `; - } - return html``; - }), - )}`, + ${rootInterface()?.config?.capabilities.includes(CapabilitiesEnum.Impersonate) + ? html` + + ${t`Impersonate`} + + ` + : html``}`, ]; } @@ -194,7 +192,7 @@ export class UserListPage extends TablePage { return new CoreApi( DEFAULT_CONFIG, ).coreUsersPartialUpdate({ - id: item.pk || 0, + id: item.pk, patchedUserRequest: { isActive: !item.isActive, }, @@ -225,70 +223,61 @@ export class UserListPage extends TablePage { ${t`Set password`} - ${until( - tenant().then((tenant) => { - if (!tenant.flowRecovery) { - return html` -

- ${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`} -

- `; - } - return html` - { - return new CoreApi(DEFAULT_CONFIG) - .coreUsersRecoveryRetrieve({ - id: item.pk || 0, - }) - .then((rec) => { - showMessage({ - level: MessageLevel.success, - message: t`Successfully generated recovery link`, - description: rec.link, - }); - }) - .catch((ex: ResponseError) => { - ex.response.json().then(() => { - showMessage({ - level: MessageLevel.error, - message: t`No recovery flow is configured.`, - }); - }); - }); - }} - > - ${t`Copy recovery link`} - - ${item.email - ? html` - - ${t`Send link`} - - - ${t`Send recovery link to user`} - - - - - ` - : html`${t`Recovery link cannot be emailed, user has no email address saved.`}`} - `; - }), - )} + ${rootInterface()?.tenant?.flowRecovery + ? html` + { + return new CoreApi(DEFAULT_CONFIG) + .coreUsersRecoveryRetrieve({ + id: item.pk, + }) + .then((rec) => { + showMessage({ + level: MessageLevel.success, + message: t`Successfully generated recovery link`, + description: rec.link, + }); + }) + .catch((ex: ResponseError) => { + ex.response.json().then(() => { + showMessage({ + level: MessageLevel.error, + message: t`No recovery flow is configured.`, + }); + }); + }); + }} + > + ${t`Copy recovery link`} + + ${item.email + ? html` + ${t`Send link`} + + ${t`Send recovery link to user`} + + + + + ` + : html`${t`Recovery link cannot be emailed, user has no email address saved.`}`} + ` + : html`

+ ${t`To let a user directly reset a their password, configure a recovery flow on the currently active tenant.`} +

`} @@ -323,18 +312,10 @@ export class UserListPage extends TablePage {
${t`User folders`}
- ${until( - new CoreApi(DEFAULT_CONFIG) - .coreUsersPathsRetrieve({ - search: this.search, - }) - .then((paths) => { - return html``; - }), - )} +
`; diff --git a/web/src/admin/users/UserViewPage.ts b/web/src/admin/users/UserViewPage.ts index c73dfb858..5924ad6f1 100644 --- a/web/src/admin/users/UserViewPage.ts +++ b/web/src/admin/users/UserViewPage.ts @@ -3,10 +3,10 @@ import "@goauthentik/admin/users/UserActiveForm"; import "@goauthentik/admin/users/UserChart"; import "@goauthentik/admin/users/UserForm"; 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 { MessageLevel } from "@goauthentik/common/messages"; -import { AKElement } from "@goauthentik/elements/Base"; +import { AKElement, rootInterface } from "@goauthentik/elements/Base"; import "@goauthentik/elements/CodeMirror"; import { PFColor } from "@goauthentik/elements/Label"; import "@goauthentik/elements/PageHeader"; @@ -27,7 +27,6 @@ import { t } from "@lingui/macro"; import { CSSResult, TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { until } from "lit/directives/until.js"; import PFButton from "@patternfly/patternfly/components/Button/button.css"; import PFCard from "@patternfly/patternfly/components/Card/card.css"; @@ -197,21 +196,18 @@ export class UserViewPage extends AKElement { - ${until( - config().then((config) => { - if (config.capabilities.includes(CapabilitiesEnum.Impersonate)) { - return html` `; - } - return html``; - }), - )} + ${rootInterface()?.config?.capabilities.includes( + CapabilitiesEnum.Impersonate, + ) + ? html` + + ${t`Impersonate`} + + ` + : html``}
@@ -111,26 +104,20 @@ export class UserSettingsPage extends AKElement { data-tab-title="${t`Sessions`}" class="pf-c-page__main-section pf-m-no-padding-mobile" > - ${until( - me().then((u) => { - return html``; - }), - )} + ()?.me?.user.username, + )} + >
- ${until( - me().then((u) => { - return html``; - }), - )} + ()?.me?.user.pk)} + >
(this.tenant = tenant)); - } - submit(payload?: FlowChallengeResponseRequest): Promise { if (!payload) return Promise.reject(); if (!this.challenge) return Promise.reject(); @@ -93,13 +84,12 @@ export class UserSettingsFlowExecutor extends AKElement implements StageHost { } firstUpdated(): void { - tenant().then((tenant) => { - this.flowSlug = tenant.flowUserSettings; - if (!this.flowSlug) { - return; - } - this.nextChallenge(); - }); + const tenant = rootInterface()?.tenant; + this.flowSlug = tenant?.flowUserSettings; + if (!this.flowSlug) { + return; + } + this.nextChallenge(); } async nextChallenge(): Promise { diff --git a/web/src/user/user-settings/mfa/MFADevicesPage.ts b/web/src/user/user-settings/mfa/MFADevicesPage.ts index 8e69a545d..cebeb170a 100644 --- a/web/src/user/user-settings/mfa/MFADevicesPage.ts +++ b/web/src/user/user-settings/mfa/MFADevicesPage.ts @@ -13,7 +13,6 @@ import { t } from "@lingui/macro"; import { TemplateResult, html } from "lit"; import { customElement, property } from "lit/decorators.js"; import { ifDefined } from "lit/directives/if-defined.js"; -import { until } from "lit/directives/until.js"; import { AuthenticatorsApi, Device, UserSetting } from "@goauthentik/api"; @@ -47,7 +46,7 @@ export function deviceTypeName(device: Device): string { @customElement("ak-user-settings-mfa") export class MFADevicesPage extends Table { @property({ attribute: false }) - userSettings?: Promise; + userSettings?: UserSetting[]; checkbox = true; @@ -70,41 +69,32 @@ export class MFADevicesPage extends Table { } renderToolbar(): TemplateResult { + const settings = (this.userSettings || []).filter((stage) => { + if (stage.component === "ak-user-settings-password") { + return false; + } + return stage.configureUrl; + }); return html` ${super.renderToolbar()}`; diff --git a/web/src/user/user-settings/sources/SourceSettings.ts b/web/src/user/user-settings/sources/SourceSettings.ts index 128f5887c..e4b55bcc2 100644 --- a/web/src/user/user-settings/sources/SourceSettings.ts +++ b/web/src/user/user-settings/sources/SourceSettings.ts @@ -12,7 +12,6 @@ import { t } from "@lingui/macro"; import { CSSResult, TemplateResult, css, html } from "lit"; import { customElement, property } from "lit/decorators.js"; -import { until } from "lit/directives/until.js"; import PFContent from "@patternfly/patternfly/components/Content/content.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") export class UserSourceSettingsPage extends AKElement { @property({ attribute: false }) - sourceSettings?: Promise; + sourceSettings?: UserSetting[]; @property({ attribute: false }) connections?: PaginatedUserSourceConnectionList; @@ -57,7 +56,7 @@ export class UserSourceSettingsPage extends AKElement { async firstUpdated(): Promise { 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({ user: user.user.pk, }); @@ -115,30 +114,33 @@ export class UserSourceSettingsPage extends AKElement {

    - ${until( - this.sourceSettings?.then((source) => { - if (source.length < 1) { - return html``; - } - return source.map((source) => { - return html`
  • -
    -
    - ${renderSourceIcon(source.title, source.iconUrl)} - ${source.title} -
    -
    - ${this.renderSourceSettings(source)} -
    -
    -
  • `; - }); - }), - html` - `, - )} + ${this.sourceSettings + ? html` + ${this.sourceSettings.length < 1 + ? html`` + : html` + ${this.sourceSettings.map((source) => { + return html`
  • +
    +
    + ${renderSourceIcon( + source.title, + source.iconUrl, + )} + ${source.title} +
    +
    + ${this.renderSourceSettings(source)} +
    +
    +
  • `; + })} + `} + ` + : html` + `}
`; } }