web/user: rework user source connection UI
Signed-off-by: Jens Langhammer <jens.langhammer@beryju.org>
This commit is contained in:
		| @ -29,3 +29,4 @@ class UserSettingSerializer(PassiveSerializer): | ||||
|     component = CharField() | ||||
|     title = CharField() | ||||
|     configure_url = CharField(required=False) | ||||
|     icon_url = CharField() | ||||
|  | ||||
| @ -75,14 +75,17 @@ class OAuthSource(Source): | ||||
|         ) | ||||
|  | ||||
|     def ui_user_settings(self) -> Optional[UserSettingSerializer]: | ||||
|         provider_type = self.type | ||||
|         provider = provider_type() | ||||
|         return UserSettingSerializer( | ||||
|             data={ | ||||
|                 "title": f"OAuth {self.name}", | ||||
|                 "title": self.name, | ||||
|                 "component": "ak-user-settings-source-oauth", | ||||
|                 "configure_url": reverse( | ||||
|                     "authentik_sources_oauth:oauth-client-login", | ||||
|                     kwargs={"source_slug": self.slug}, | ||||
|                 ), | ||||
|                 "icon_url": provider.icon_url(), | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @ -80,9 +80,10 @@ class PlexSource(Source): | ||||
|     def ui_user_settings(self) -> Optional[UserSettingSerializer]: | ||||
|         return UserSettingSerializer( | ||||
|             data={ | ||||
|                 "title": f"Plex {self.name}", | ||||
|                 "title": self.name, | ||||
|                 "component": "ak-user-settings-source-plex", | ||||
|                 "configure_url": self.client_id, | ||||
|                 "icon_url": static("authentik/sources/plex.svg"), | ||||
|             } | ||||
|         ) | ||||
|  | ||||
|  | ||||
| @ -31275,8 +31275,11 @@ components: | ||||
|           type: string | ||||
|         configure_url: | ||||
|           type: string | ||||
|         icon_url: | ||||
|           type: string | ||||
|       required: | ||||
|       - component | ||||
|       - icon_url | ||||
|       - object_uid | ||||
|       - title | ||||
|     UserSourceConnection: | ||||
|  | ||||
| @ -339,9 +339,16 @@ html > form > input { | ||||
|         ) !important; | ||||
|     } | ||||
|     /* data list */ | ||||
|     .pf-c-data-list { | ||||
|         border-top-color: var(--ak-dark-background-lighter); | ||||
|     } | ||||
|     .pf-c-data-list__item { | ||||
|         --pf-c-data-list__item--BackgroundColor: var(--ak-dark-background-light); | ||||
|         --pf-c-data-list__item--BackgroundColor: transparent; | ||||
|         --pf-c-data-list__item--BorderBottomColor: var(--ak-dark-background-lighter); | ||||
|         color: var(--ak-dark-foreground); | ||||
|     } | ||||
| } | ||||
|  | ||||
| .pf-c-data-list__item { | ||||
|     background-color: transparent; | ||||
| } | ||||
|  | ||||
| @ -168,13 +168,13 @@ export class IdentificationStage extends BaseStage< | ||||
|         } | ||||
|         return html`<div class="pf-c-login__main-footer-band"> | ||||
|             ${this.challenge.enrollUrl | ||||
|                 ? html` <p class="pf-c-login__main-footer-band-item"> | ||||
|                 ? html`<p class="pf-c-login__main-footer-band-item"> | ||||
|                       ${t`Need an account?`} | ||||
|                       <a id="enroll" href="${this.challenge.enrollUrl}">${t`Sign up.`}</a> | ||||
|                   </p>` | ||||
|                 : html``} | ||||
|             ${this.challenge.recoveryUrl | ||||
|                 ? html` <p class="pf-c-login__main-footer-band-item"> | ||||
|                 ? html`<p class="pf-c-login__main-footer-band-item"> | ||||
|                       <a id="recovery" href="${this.challenge.recoveryUrl}" | ||||
|                           >${t`Forgot username or password?`}</a | ||||
|                       > | ||||
|  | ||||
| @ -988,14 +988,18 @@ msgstr "Connect" | ||||
| msgid "Connect to the LDAP Server on port 389:" | ||||
| msgstr "Connect to the LDAP Server on port 389:" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettings.ts | ||||
| msgid "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials." | ||||
| msgstr "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials." | ||||
|  | ||||
| #: src/user/user-settings/UserSettingsPage.ts | ||||
| msgid "Connected services" | ||||
| msgstr "Connected services" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Connected." | ||||
| msgstr "Connected." | ||||
| #~ msgid "Connected." | ||||
| #~ msgstr "Connected." | ||||
|  | ||||
| #: src/common/ws.ts | ||||
| msgid "Connection error, reconnecting..." | ||||
| @ -3138,8 +3142,8 @@ msgstr "Not configured action" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Not connected." | ||||
| msgstr "Not connected." | ||||
| #~ msgid "Not connected." | ||||
| #~ msgstr "Not connected." | ||||
|  | ||||
| #: src/elements/router/Router404.ts | ||||
| msgid "Not found" | ||||
| @ -4329,8 +4333,8 @@ msgstr "Source linked" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Source {0}" | ||||
| msgstr "Source {0}" | ||||
| #~ msgid "Source {0}" | ||||
| #~ msgstr "Source {0}" | ||||
|  | ||||
| #: src/pages/sources/SourcesListPage.ts | ||||
| msgid "Source(s)" | ||||
|  | ||||
| @ -987,14 +987,18 @@ msgstr "Connecter" | ||||
| msgid "Connect to the LDAP Server on port 389:" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettings.ts | ||||
| msgid "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/UserSettingsPage.ts | ||||
| msgid "Connected services" | ||||
| msgstr "Services connectés" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Connected." | ||||
| msgstr "Connecté." | ||||
| #~ msgid "Connected." | ||||
| #~ msgstr "Connecté." | ||||
|  | ||||
| #: src/common/ws.ts | ||||
| msgid "Connection error, reconnecting..." | ||||
| @ -3117,8 +3121,8 @@ msgstr "Action non configurée" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Not connected." | ||||
| msgstr "Déconnecté." | ||||
| #~ msgid "Not connected." | ||||
| #~ msgstr "Déconnecté." | ||||
|  | ||||
| #: src/elements/router/Router404.ts | ||||
| msgid "Not found" | ||||
| @ -4292,8 +4296,8 @@ msgstr "Source liée" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Source {0}" | ||||
| msgstr "Source {0}" | ||||
| #~ msgid "Source {0}" | ||||
| #~ msgstr "Source {0}" | ||||
|  | ||||
| #: src/pages/sources/SourcesListPage.ts | ||||
| msgid "Source(s)" | ||||
|  | ||||
| @ -982,14 +982,18 @@ msgstr "" | ||||
| msgid "Connect to the LDAP Server on port 389:" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettings.ts | ||||
| msgid "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/UserSettingsPage.ts | ||||
| msgid "Connected services" | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Connected." | ||||
| msgstr "" | ||||
| #~ msgid "Connected." | ||||
| #~ msgstr "" | ||||
|  | ||||
| #: src/common/ws.ts | ||||
| msgid "Connection error, reconnecting..." | ||||
| @ -3128,8 +3132,8 @@ msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Not connected." | ||||
| msgstr "" | ||||
| #~ msgid "Not connected." | ||||
| #~ msgstr "" | ||||
|  | ||||
| #: src/elements/router/Router404.ts | ||||
| msgid "Not found" | ||||
| @ -4319,8 +4323,8 @@ msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Source {0}" | ||||
| msgstr "" | ||||
| #~ msgid "Source {0}" | ||||
| #~ msgstr "" | ||||
|  | ||||
| #: src/pages/sources/SourcesListPage.ts | ||||
| msgid "Source(s)" | ||||
|  | ||||
| @ -978,14 +978,18 @@ msgstr "Bağlan" | ||||
| msgid "Connect to the LDAP Server on port 389:" | ||||
| msgstr "Bağlantı noktası 389 LDAP sunucusuna bağlanın:" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettings.ts | ||||
| msgid "Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials." | ||||
| msgstr "" | ||||
|  | ||||
| #: src/user/user-settings/UserSettingsPage.ts | ||||
| msgid "Connected services" | ||||
| msgstr "Bağlı hizmetler" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Connected." | ||||
| msgstr "Bağlantılı." | ||||
| #~ msgid "Connected." | ||||
| #~ msgstr "Bağlantılı." | ||||
|  | ||||
| #: src/common/ws.ts | ||||
| msgid "Connection error, reconnecting..." | ||||
| @ -3091,8 +3095,8 @@ msgstr "Yapılandırılmış eylem" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Not connected." | ||||
| msgstr "Bağlantılı değil." | ||||
| #~ msgid "Not connected." | ||||
| #~ msgstr "Bağlantılı değil." | ||||
|  | ||||
| #: src/elements/router/Router404.ts | ||||
| msgid "Not found" | ||||
| @ -4253,8 +4257,8 @@ msgstr "Kaynak bağlantılı" | ||||
|  | ||||
| #: src/user/user-settings/sources/SourceSettingsOAuth.ts | ||||
| #: src/user/user-settings/sources/SourceSettingsPlex.ts | ||||
| msgid "Source {0}" | ||||
| msgstr "Kaynak {0}" | ||||
| #~ msgid "Source {0}" | ||||
| #~ msgstr "Kaynak {0}" | ||||
|  | ||||
| #: src/pages/sources/SourcesListPage.ts | ||||
| msgid "Source(s)" | ||||
|  | ||||
| @ -3,7 +3,6 @@ import { property } from "lit/decorators.js"; | ||||
|  | ||||
| import AKGlobal from "../../authentik.css"; | ||||
| import PFButton from "@patternfly/patternfly/components/Button/button.css"; | ||||
| import PFCard from "@patternfly/patternfly/components/Card/card.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"; | ||||
| @ -16,6 +15,6 @@ export abstract class BaseUserSettings extends LitElement { | ||||
|     configureUrl?: string; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFBase, PFCard, PFButton, PFForm, PFFormControl, AKGlobal]; | ||||
|         return [PFBase, PFButton, PFForm, PFFormControl, AKGlobal]; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -1,10 +1,12 @@ | ||||
| import { t } from "@lingui/macro"; | ||||
|  | ||||
| import { CSSResult, LitElement, TemplateResult, html } from "lit"; | ||||
| import { CSSResult, LitElement, TemplateResult, css, html } from "lit"; | ||||
| import { customElement, property } from "lit/decorators.js"; | ||||
| import { until } from "lit/directives/until.js"; | ||||
|  | ||||
| import PFGrid from "@patternfly/patternfly/layouts/Grid/grid.css"; | ||||
| import AKGlobal from "../../../authentik.css"; | ||||
| import PFContent from "@patternfly/patternfly/components/Content/content.css"; | ||||
| import PFDataList from "@patternfly/patternfly/components/DataList/data-list.css"; | ||||
|  | ||||
| import { SourcesApi, UserSetting } from "@goauthentik/api"; | ||||
|  | ||||
| @ -20,7 +22,27 @@ export class UserSourceSettingsPage extends LitElement { | ||||
|     sourceSettings?: Promise<UserSetting[]>; | ||||
|  | ||||
|     static get styles(): CSSResult[] { | ||||
|         return [PFGrid]; | ||||
|         return [ | ||||
|             PFDataList, | ||||
|             PFContent, | ||||
|             AKGlobal, | ||||
|             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; | ||||
|                 } | ||||
|                 @media (prefers-color-scheme: dark) { | ||||
|                     .pf-c-data-list__cell img { | ||||
|                         filter: invert(1); | ||||
|                     } | ||||
|                 } | ||||
|             `, | ||||
|         ]; | ||||
|     } | ||||
|  | ||||
|     constructor() { | ||||
| @ -38,6 +60,7 @@ export class UserSourceSettingsPage extends LitElement { | ||||
|         switch (source.component) { | ||||
|             case "ak-user-settings-source-oauth": | ||||
|                 return html`<ak-user-settings-source-oauth | ||||
|                     class="pf-c-data-list__item-row" | ||||
|                     objectId=${source.objectUid} | ||||
|                     title=${source.title} | ||||
|                     .configureUrl=${source.configureUrl} | ||||
| @ -45,6 +68,7 @@ export class UserSourceSettingsPage extends LitElement { | ||||
|                 </ak-user-settings-source-oauth>`; | ||||
|             case "ak-user-settings-source-plex": | ||||
|                 return html`<ak-user-settings-source-plex | ||||
|                     class="pf-c-data-list__item-row" | ||||
|                     objectId=${source.objectUid} | ||||
|                     title=${source.title} | ||||
|                     .configureUrl=${source.configureUrl} | ||||
| @ -56,22 +80,36 @@ export class UserSourceSettingsPage extends LitElement { | ||||
|     } | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-l-grid pf-m-gutter"> | ||||
|             ${until( | ||||
|                 this.sourceSettings?.then((source) => { | ||||
|                     if (source.length < 1) { | ||||
|                         return html`<ak-empty-state | ||||
|                             header=${t`No services available.`} | ||||
|                         ></ak-empty-state>`; | ||||
|                     } | ||||
|                     return source.map((stage) => { | ||||
|                         return html`<div class="pf-l-grid__item pf-m-6-col pf-m-4-col-on-xl"> | ||||
|                             ${this.renderSourceSettings(stage)} | ||||
|                         </div>`; | ||||
|                     }); | ||||
|                 }), | ||||
|                 html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> </ak-empty-state>`, | ||||
|             )} | ||||
|         </div>`; | ||||
|         return html` <div class="pf-c-content"> | ||||
|                 <p> | ||||
|                     ${t`Connect your user account to the services listed below, to allow you to login using the service instead of traditional credentials.`} | ||||
|                 </p> | ||||
|             </div> | ||||
|             <ul class="pf-c-data-list" role="list"> | ||||
|                 ${until( | ||||
|                     this.sourceSettings?.then((source) => { | ||||
|                         if (source.length < 1) { | ||||
|                             return html`<ak-empty-state | ||||
|                                 header=${t`No services available.`} | ||||
|                             ></ak-empty-state>`; | ||||
|                         } | ||||
|                         return source.map((stage) => { | ||||
|                             return html`<li class="pf-c-data-list__item"> | ||||
|                                 <div class="pf-c-data-list__item-content"> | ||||
|                                     <div class="pf-c-data-list__cell"> | ||||
|                                         <img src="${stage.iconUrl}" /> | ||||
|                                         ${stage.title} | ||||
|                                     </div> | ||||
|                                     <div class="pf-c-data-list__cell"> | ||||
|                                         ${this.renderSourceSettings(stage)} | ||||
|                                     </div> | ||||
|                                 </div> | ||||
|                             </li>`; | ||||
|                         }); | ||||
|                     }), | ||||
|                     html`<ak-empty-state ?loading="${true}" header=${t`Loading`}> | ||||
|                     </ak-empty-state>`, | ||||
|                 )} | ||||
|             </ul>`; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @ -16,13 +16,6 @@ export class SourceSettingsOAuth extends BaseUserSettings { | ||||
|     title!: string; | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-card"> | ||||
|             <div class="pf-c-card__title">${t`Source ${this.title}`}</div> | ||||
|             <div class="pf-c-card__body">${this.renderInner()}</div> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
|     renderInner(): TemplateResult { | ||||
|         return html`${until( | ||||
|             new SourcesApi(DEFAULT_CONFIG) | ||||
|                 .sourcesUserConnectionsOauthList({ | ||||
| @ -30,29 +23,27 @@ export class SourceSettingsOAuth extends BaseUserSettings { | ||||
|                 }) | ||||
|                 .then((connection) => { | ||||
|                     if (connection.results.length > 0) { | ||||
|                         return html`<p>${t`Connected.`}</p> | ||||
|                             <button | ||||
|                                 class="pf-c-button pf-m-danger" | ||||
|                                 @click=${() => { | ||||
|                                     return new SourcesApi( | ||||
|                                         DEFAULT_CONFIG, | ||||
|                                     ).sourcesUserConnectionsOauthDestroy({ | ||||
|                                         id: connection.results[0].pk || 0, | ||||
|                                     }); | ||||
|                                 }} | ||||
|                             > | ||||
|                                 ${t`Disconnect`} | ||||
|                             </button>`; | ||||
|                     } | ||||
|                     return html`<p>${t`Not connected.`}</p> | ||||
|                         <a | ||||
|                             class="pf-c-button pf-m-primary" | ||||
|                             href="${ifDefined(this.configureUrl)}${AndNext( | ||||
|                                 "/if/user/#/settings;page-sources", | ||||
|                             )}" | ||||
|                         return html` <button | ||||
|                             class="pf-c-button pf-m-danger" | ||||
|                             @click=${() => { | ||||
|                                 return new SourcesApi( | ||||
|                                     DEFAULT_CONFIG, | ||||
|                                 ).sourcesUserConnectionsOauthDestroy({ | ||||
|                                     id: connection.results[0].pk || 0, | ||||
|                                 }); | ||||
|                             }} | ||||
|                         > | ||||
|                             ${t`Connect`} | ||||
|                         </a>`; | ||||
|                             ${t`Disconnect`} | ||||
|                         </button>`; | ||||
|                     } | ||||
|                     return html` <a | ||||
|                         class="pf-c-button pf-m-primary" | ||||
|                         href="${ifDefined(this.configureUrl)}${AndNext( | ||||
|                             "/if/user/#/settings;page-sources", | ||||
|                         )}" | ||||
|                     > | ||||
|                         ${t`Connect`} | ||||
|                     </a>`; | ||||
|                 }), | ||||
|         )}`; | ||||
|     } | ||||
|  | ||||
| @ -16,13 +16,6 @@ export class SourceSettingsPlex extends BaseUserSettings { | ||||
|     @property() | ||||
|     title!: string; | ||||
|  | ||||
|     render(): TemplateResult { | ||||
|         return html`<div class="pf-c-card"> | ||||
|             <div class="pf-c-card__title">${t`Source ${this.title}`}</div> | ||||
|             <div class="pf-c-card__body">${this.renderInner()}</div> | ||||
|         </div>`; | ||||
|     } | ||||
|  | ||||
|     async doPlex(): Promise<void> { | ||||
|         const authInfo = await PlexAPIClient.getPin(this.configureUrl || ""); | ||||
|         const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700); | ||||
| @ -43,7 +36,7 @@ export class SourceSettingsPlex extends BaseUserSettings { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     renderInner(): TemplateResult { | ||||
|     render(): TemplateResult { | ||||
|         return html`${until( | ||||
|             new SourcesApi(DEFAULT_CONFIG) | ||||
|                 .sourcesUserConnectionsPlexList({ | ||||
| @ -51,24 +44,22 @@ export class SourceSettingsPlex extends BaseUserSettings { | ||||
|                 }) | ||||
|                 .then((connection) => { | ||||
|                     if (connection.results.length > 0) { | ||||
|                         return html`<p>${t`Connected.`}</p> | ||||
|                             <button | ||||
|                                 class="pf-c-button pf-m-danger" | ||||
|                                 @click=${() => { | ||||
|                                     return new SourcesApi( | ||||
|                                         DEFAULT_CONFIG, | ||||
|                                     ).sourcesUserConnectionsPlexDestroy({ | ||||
|                                         id: connection.results[0].pk || 0, | ||||
|                                     }); | ||||
|                                 }} | ||||
|                             > | ||||
|                                 ${t`Disconnect`} | ||||
|                             </button>`; | ||||
|                     } | ||||
|                     return html`<p>${t`Not connected.`}</p> | ||||
|                         <button @click=${this.doPlex} class="pf-c-button pf-m-primary"> | ||||
|                             ${t`Connect`} | ||||
|                         return html` <button | ||||
|                             class="pf-c-button pf-m-danger" | ||||
|                             @click=${() => { | ||||
|                                 return new SourcesApi( | ||||
|                                     DEFAULT_CONFIG, | ||||
|                                 ).sourcesUserConnectionsPlexDestroy({ | ||||
|                                     id: connection.results[0].pk || 0, | ||||
|                                 }); | ||||
|                             }} | ||||
|                         > | ||||
|                             ${t`Disconnect`} | ||||
|                         </button>`; | ||||
|                     } | ||||
|                     return html` <button @click=${this.doPlex} class="pf-c-button pf-m-primary"> | ||||
|                         ${t`Connect`} | ||||
|                     </button>`; | ||||
|                 }), | ||||
|         )}`; | ||||
|     } | ||||
|  | ||||
		Reference in New Issue
	
	Block a user
	 Jens Langhammer
					Jens Langhammer