web/admin: show connected services on user view page, fix styling (#8416)

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
Jens L
2024-02-05 16:38:23 +01:00
committed by GitHub
parent 087d5aa7e7
commit f4b336a974
22 changed files with 178 additions and 117 deletions

View File

@ -0,0 +1,21 @@
import { AKElement } from "@goauthentik/elements/Base";
import { CSSResult } from "lit";
import { property } from "lit/decorators.js";
import PFButton from "@patternfly/patternfly/components/Button/button.css";
import PFForm from "@patternfly/patternfly/components/Form/form.css";
import PFFormControl from "@patternfly/patternfly/components/FormControl/form-control.css";
import PFBase from "@patternfly/patternfly/patternfly-base.css";
export abstract class BaseUserSettings extends AKElement {
@property()
objectId!: string;
@property()
configureUrl?: string;
static get styles(): CSSResult[] {
return [PFBase, PFButton, PFForm, PFFormControl];
}
}

View File

@ -0,0 +1,146 @@
import { renderSourceIcon } from "@goauthentik/app/admin/sources/utils";
import "@goauthentik/app/elements/user/sources/SourceSettingsOAuth";
import "@goauthentik/app/elements/user/sources/SourceSettingsPlex";
import "@goauthentik/app/elements/user/sources/SourceSettingsSAML";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { AKElement } from "@goauthentik/elements/Base";
import "@goauthentik/elements/EmptyState";
import { msg, str } from "@lit/localize";
import { CSSResult, TemplateResult, css, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css";
import { PaginatedUserSourceConnectionList, SourcesApi, UserSetting } from "@goauthentik/api";
@customElement("ak-user-settings-source")
export class UserSourceSettingsPage extends AKElement {
@property({ attribute: false })
sourceSettings?: UserSetting[];
@property({ attribute: false })
connections?: PaginatedUserSourceConnectionList;
@property({ type: Number })
userId?: number;
@property({ type: Boolean })
canConnect = true;
static get styles(): CSSResult[] {
return [
PFDataList,
css`
.pf-c-data-list__cell {
display: flex;
align-items: center;
}
.pf-c-data-list__cell img {
max-width: 48px;
width: 48px;
margin-right: 16px;
}
:host([theme="dark"]) .pf-c-data-list__cell img {
filter: invert(1);
}
.pf-c-data-list__item {
background-color: transparent;
}
`,
];
}
constructor() {
super();
this.addEventListener(EVENT_REFRESH, () => {
this.firstUpdated();
});
}
async firstUpdated(): Promise<void> {
this.sourceSettings = await new SourcesApi(DEFAULT_CONFIG).sourcesAllUserSettingsList();
this.connections = await new SourcesApi(DEFAULT_CONFIG).sourcesUserConnectionsAllList({
user: this.userId,
});
}
renderSourceSettings(source: UserSetting): TemplateResult {
let connectionPk = -1;
if (this.connections) {
const connections = this.connections.results.filter(
(con) => con.source.slug === source.objectUid,
);
if (connections.length > 0) {
connectionPk = connections[0].pk;
} else {
connectionPk = 0;
}
}
switch (source.component) {
case "ak-user-settings-source-oauth":
return html`<ak-user-settings-source-oauth
objectId=${source.objectUid}
title=${source.title}
connectionPk=${connectionPk}
.configureUrl=${this.canConnect ? source.configureUrl : undefined}
>
</ak-user-settings-source-oauth>`;
case "ak-user-settings-source-plex":
return html`<ak-user-settings-source-plex
objectId=${source.objectUid}
title=${source.title}
connectionPk=${connectionPk}
.configureUrl=${this.canConnect ? source.configureUrl : undefined}
>
</ak-user-settings-source-plex>`;
case "ak-user-settings-source-saml":
return html`<ak-user-settings-source-saml
objectId=${source.objectUid}
title=${source.title}
connectionPk=${connectionPk}
.configureUrl=${this.canConnect ? source.configureUrl : undefined}
>
</ak-user-settings-source-saml>`;
default:
return html`<p>
${msg(str`Error: unsupported source settings: ${source.component}`)}
</p>`;
}
}
render(): TemplateResult {
return html` <ul class="pf-c-data-list" role="list">
${this.sourceSettings
? html`
${this.sourceSettings.length < 1
? html`<ak-empty-state
header=${msg("No services available.")}
></ak-empty-state>`
: html`
${this.sourceSettings.map((source) => {
return html`<li class="pf-c-data-list__item">
<div class="pf-c-data-list__item-row">
<div class="pf-c-data-list__item-content">
<div class="pf-c-data-list__cell">
${renderSourceIcon(
source.title,
source.iconUrl,
)}
${source.title}
</div>
<div class="pf-c-data-list__cell">
${this.renderSourceSettings(source)}
</div>
</div>
</div>
</li>`;
})}
`}
`
: html`<ak-empty-state ?loading="${true}" header=${msg("Loading")}>
</ak-empty-state>`}
</ul>`;
}
}

View File

@ -0,0 +1,71 @@
import { BaseUserSettings } from "@goauthentik/app/elements/user/sources/BaseUserSettings";
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages";
import "@goauthentik/elements/Spinner";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { SourcesApi } from "@goauthentik/api";
@customElement("ak-user-settings-source-oauth")
export class SourceSettingsOAuth extends BaseUserSettings {
@property()
title!: string;
@property({ type: Number })
connectionPk = 0;
render(): TemplateResult {
if (this.connectionPk === -1) {
return html`<ak-spinner></ak-spinner>`;
}
if (this.connectionPk > 0) {
return html`<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new SourcesApi(DEFAULT_CONFIG)
.sourcesUserConnectionsOauthDestroy({
id: this.connectionPk,
})
.then(() => {
showMessage({
level: MessageLevel.info,
message: msg("Successfully disconnected source"),
});
})
.catch((exc) => {
showMessage({
level: MessageLevel.error,
message: msg(str`Failed to disconnected source: ${exc}`),
});
})
.finally(() => {
this.parentElement?.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
}}
>
${msg("Disconnect")}
</button>`;
}
if (this.configureUrl) {
return html`<a
class="pf-c-button pf-m-primary"
href="${this.configureUrl}${AndNext(
`/if/user/#/settings;${JSON.stringify({ page: "page-sources" })}`,
)}"
>
${msg("Connect")}
</a>`;
}
return html`${msg("-")}`;
}
}

View File

@ -0,0 +1,87 @@
import { BaseUserSettings } from "@goauthentik/app/elements/user/sources/BaseUserSettings";
import { DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { PlexAPIClient, popupCenterScreen } from "@goauthentik/common/helpers/plex";
import { MessageLevel } from "@goauthentik/common/messages";
import "@goauthentik/elements/Spinner";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { SourcesApi } from "@goauthentik/api";
@customElement("ak-user-settings-source-plex")
export class SourceSettingsPlex extends BaseUserSettings {
@property()
title!: string;
@property({ type: Number })
connectionPk = 0;
async doPlex(): Promise<void> {
const authInfo = await PlexAPIClient.getPin(this.configureUrl || "");
const authWindow = await popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
PlexAPIClient.pinPoll(this.configureUrl || "", authInfo.pin.id).then((token) => {
authWindow?.close();
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemTokenAuthenticatedCreate({
plexTokenRedeemRequest: {
plexToken: token,
},
slug: this.objectId,
});
});
this.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
}
render(): TemplateResult {
if (this.connectionPk === -1) {
return html`<ak-spinner></ak-spinner>`;
}
if (this.connectionPk > 0) {
return html`<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new SourcesApi(DEFAULT_CONFIG)
.sourcesUserConnectionsPlexDestroy({
id: this.connectionPk,
})
.then(() => {
showMessage({
level: MessageLevel.info,
message: msg("Successfully disconnected source"),
});
})
.catch((exc) => {
showMessage({
level: MessageLevel.error,
message: msg(str`Failed to disconnected source: ${exc}`),
});
})
.finally(() => {
this.parentElement?.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
}}
>
${msg("Disconnect")}
</button>`;
}
if (this.configureUrl) {
return html`<button @click=${this.doPlex} class="pf-c-button pf-m-primary">
${msg("Connect")}
</button>`;
}
return html`${msg("-")}`;
}
}

View File

@ -0,0 +1,71 @@
import { BaseUserSettings } from "@goauthentik/app/elements/user/sources/BaseUserSettings";
import { AndNext, DEFAULT_CONFIG } from "@goauthentik/common/api/config";
import { EVENT_REFRESH } from "@goauthentik/common/constants";
import { MessageLevel } from "@goauthentik/common/messages";
import "@goauthentik/elements/Spinner";
import { showMessage } from "@goauthentik/elements/messages/MessageContainer";
import { msg, str } from "@lit/localize";
import { TemplateResult, html } from "lit";
import { customElement, property } from "lit/decorators.js";
import { SourcesApi } from "@goauthentik/api";
@customElement("ak-user-settings-source-saml")
export class SourceSettingsSAML extends BaseUserSettings {
@property()
title!: string;
@property({ type: Number })
connectionPk = 0;
render(): TemplateResult {
if (this.connectionPk === -1) {
return html`<ak-spinner></ak-spinner>`;
}
if (this.connectionPk > 0) {
return html`<button
class="pf-c-button pf-m-danger"
@click=${() => {
return new SourcesApi(DEFAULT_CONFIG)
.sourcesUserConnectionsSamlDestroy({
id: this.connectionPk,
})
.then(() => {
showMessage({
level: MessageLevel.info,
message: msg("Successfully disconnected source"),
});
})
.catch((exc) => {
showMessage({
level: MessageLevel.error,
message: msg(str`Failed to disconnected source: ${exc}`),
});
})
.finally(() => {
this.parentElement?.dispatchEvent(
new CustomEvent(EVENT_REFRESH, {
bubbles: true,
composed: true,
}),
);
});
}}
>
${msg("Disconnect")}
</button>`;
}
if (this.configureUrl) {
return html`<a
class="pf-c-button pf-m-primary"
href="${this.configureUrl}${AndNext(
`/if/user/#/settings;${JSON.stringify({ page: "page-sources" })}`,
)}"
>
${msg("Connect")}
</a>`;
}
return html`${msg("-")}`;
}
}