diff --git a/keycloak-d4science-theme/CHANGELOG.md b/keycloak-d4science-theme/CHANGELOG.md index 44b7f48..63430ff 100644 --- a/keycloak-d4science-theme/CHANGELOG.md +++ b/keycloak-d4science-theme/CHANGELOG.md @@ -1,6 +1,17 @@ +# Changelog for "keycloak-d4science-themes" + +All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). -# Changelog for "keycloak-d4science-theme" +## Master branch - 2022-02-15 +- Updated d4science themes for Keycloak v16 +- Reimplemented d4science.v2 account ui for Keycloak v16 + +## Master branch +- First release + +[#19518] Implemented the Keycloak "d4science" login parametric template. + +Each new gateway that does not implement its own specific login template should use by default this base "d4science" template. However, it is possible to set some parameters in `/themes//login/themes.properties` file to do some customizations such as logo, background, colors, and some links (see for example parameters of dev4.d4science.org and next.d4science.org themes). -## [v2.0.0-SNAPSHOT] diff --git a/keycloak-d4science-theme/README.md b/keycloak-d4science-theme/README.md index 685d6d1..b4f8645 100644 --- a/keycloak-d4science-theme/README.md +++ b/keycloak-d4science-theme/README.md @@ -1,52 +1,71 @@ -# Keycloak D4Science Theme +# Keycloak D4Science Themes -**Keycloak D4Science Theme** repository collects the implementations of base D4Science theme and a set of specific per gateway themes implementations. +**Keycloak D4Science Themes** repository collects the implementations of base D4Science theme and a set of specific per gateway themes implementations. -Each Keycloak theme is made of a set of [Freemarker](https://freemarker.apache.org/) templates. +Each Keycloak theme is made of a set of [Freemarker](https://freemarker.apache.org/) templates. Themes of Keycloak account section is implemented in [React](https://reactjs.org/). ## Structure of the project -The themes sources are contained in `src/main/resources` folder. +The themes are in `src/main/resources/themes` subfolder. +The `util` subfolder contains some utility test scripts to create Keycloak clients that uses a specific theme. ## Built With -* [Maven](https://maven.apache.org/) - Dependency Management +[Node.js](https://nodejs.org/). See [Keycloak src project](https://github.com/keycloak/keycloak/tree/main/themes/src/main/resources/theme/keycloak.v2/account/src) ## Documentation -To build the theme JAR it is sufficient to type - - mvn clean package - For details see [Theme section](https://www.keycloak.org/docs/latest/server_development/#_themes) of Keycloak developer docs. As specified in [Deploying Themes](https://www.keycloak.org/docs/latest/server_development/#deploying-themes) section of the documentation, *themes can be deployed to Keycloak by copying the theme directory to themes or it can be deployed as an archive.* -During the development phase, themes caching in Keycloak configuration file has to be disabled. To do this edit `standalone.xml`. For `theme` set `staticMaxAge` to `-1` and both `cacheTemplates` and `cacheThemes` to `false`. +In the development phase, we suggest to disable themes caching in Keycloak configuration file. To do this edit `standalone.xml`. For `theme` set `staticMaxAge` to `-1` and both `cacheTemplates` and `cacheThemes` to `false`. + +To test theme in a Keycloak instance you can clone this repo somewhere and create a symbolic link for each theme you want to deploy: + + mkdir /opt/git + cd /opt/git + git clone https://code-repo.d4science.org/gCubeSystem/d4science-keycloak-themes.git + + cd /opt/keycloak/themes + ln -s /opt/git/d4science-keycloak-themes/src/themes/d4science/ + ln -s /opt/git/d4science-keycloak-themes/src/themes/d4science.v2/ + + ln -s /opt/git/d4science-keycloak-themes/src/themes/dev4.d4science.org/ + ln -s /opt/git/d4science-keycloak-themes/src/themes/next.d4science.org/ + ... + +Note: `d4science` and `d4science.v2` are the base D4Science themes, that are **required** by all `\*.d4science.org` per gateway specific themes. + +If you are already logged-in in Keycloak administration console you have to logout and then re-login. At that you can set one of the new themes per Keycloak Realm or for a specific Keycloak Client in the realm. ## Change log -See [CHANGELOG.md](CHANGELOG.md). +See [CHANGELOG.md](CHANGELOG.md) file for details + +See [Releases](https://code-repo.d4science.org/gCubeSystem/d4science-keycloak-themes/releases). ## Authors -* **Vincenzo Cestone** ([Nubisware S.r.l.](http://www.nubisware.com)) for themes preparing/testing. -* **Mauro Mugnaini** ([Nubisware S.r.l.](http://www.nubisware.com)) for archive building/maven automation. - -## How to Cite this Software -[Intentionally left blank] +* **Vincenzo Cestone** ([Nubisware S.r.l.](http://www.nubisware.com)) +* **Mauro Mugnaini** ([Nubisware S.r.l.](http://www.nubisware.com)) ## License This project is licensed under the EUPL V.1.1 License - see the [LICENSE.md](LICENSE.md) file for details. + ## About the gCube Framework This software is part of the [gCubeFramework](https://www.gcube-system.org/ "gCubeFramework"): an open-source software toolkit used for building and operating Hybrid Data Infrastructures enabling the dynamic deployment of Virtual Research Environments by favouring the realisation of reuse oriented policies. -The projects leading to this software have received funding from a series of European Union programmes see [FUNDING.md](FUNDING.md) +The projects leading to this software have received funding from a series of European Union programmes including: -## Acknowledgments -[Intentionally left blank] \ No newline at end of file +- the Sixth Framework Programme for Research and Technological Development + - DILIGENT (grant no. 004260); +- the Seventh Framework Programme for research, technological development and demonstration + - D4Science (grant no. 212488), D4Science-II (grant no.239019), ENVRI (grant no. 283465), EUBrazilOpenBio (grant no. 288754), iMarine(grant no. 283644); +- the H2020 research and innovation programme + - BlueBRIDGE (grant no. 675680), EGIEngage (grant no. 654142), ENVRIplus (grant no. 654182), Parthenos (grant no. 654119), SoBigData (grant no. 654024),DESIRA (grant no. 818194), ARIADNEplus (grant no. 823914), RISIS2 (grant no. 824091), PerformFish (grant no. 727610), AGINFRAplus (grant no. 731001); diff --git a/keycloak-d4science-theme/src/main/resources/META-INF/keycloak-themes.json b/keycloak-d4science-theme/src/main/resources/META-INF/keycloak-themes.json index 73c3b69..5faab6c 100644 --- a/keycloak-d4science-theme/src/main/resources/META-INF/keycloak-themes.json +++ b/keycloak-d4science-theme/src/main/resources/META-INF/keycloak-themes.json @@ -45,6 +45,12 @@ "login" ] }, + { + "name": "d4science.v2", + "types": [ + "account" + ] + }, { "name": "dante.d4science.org", "types": [ diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/messages/messages_en.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/messages/messages_en.properties new file mode 100644 index 0000000..1a4d950 --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/messages/messages_en.properties @@ -0,0 +1,24 @@ +accountManagementWelcomeMessage=Welcome to Keycloak Extended Account Console + +personalBasicInfoHtmlTitle=Basic information +accountExtraInfoHtmlTitleHome=Profile settings +accountExtraSubMessageHome=Handle extra profile settings +accountExtraInfoHtmlTitle=Profile settings +accountExtraSubMessage=Handle profile: add/update your avatar, delete your account + +avatarLabel=Avatar +uploadLabel=Select image file +dragdropInfo=Drag and drop an image file or upload one +browseButton=Browse +clearButton=Clear +avatarInfo=A 100x100px size is suggested. Images exceding 250x250px will be resized to 250px width or height mantaining their ratio. The maximum file size permitted is 1MB. +avatarUpdatedMessage=Avatar successfully updated +error-noAvatarFound=No avatar found on server + +deleteAccount=Delete Account +deleteAccountInfoMessage=Deleting your account will disable your profile and remove your name and photo you''ve shared on D4Science gateway(s). Some information may still be visible to others, such as your name in the posts and private messages you sent.
All files and folders you created of your workspace will be removed. +deleteAccountDialogHeader=Confirm account delete +deleteAccountWarningMessage=Clicking on the "Confirm" button is an undoable operation, your account will be removed and you'll be automatically logged out from all your sessions. +deleteAccountConfirmMessage=
Do you really want to remove your account? NOTE: This action is irreversible! +doDeleteConfirm=Confirm +accountDeletedMessage=Your account has been deleted \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/messages/messages_it.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/messages/messages_it.properties new file mode 100644 index 0000000..fa487a9 --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/messages/messages_it.properties @@ -0,0 +1,24 @@ +accountManagementWelcomeMessage=Benvenuto nella gestione estesa degli account di Keycloak + +personalBasicInfoHtmlTitle=Informazioni base +accountExtraInfoHtmlTitleHome=Impostazioni profilo +accountExtraSubMessageHome=Gestisce ulteriori impostazioni associate al profilo +accountExtraInfoHtmlTitle=Impostazioni del profilo +accountExtraSubMessage=Gestisce il profilo: aggiunta/modifica dell''avatar, cancellazione dell''account + +avatarLabel=Avatar +uploadLabel=Seleziona un''immagine +dragdropInfo=Trascina qui un file immagine o selezionalo +browseButton=Seleziona +clearButton=Cancella +avatarInfo=Si consiglia una dimensione di 100x100px. Le immagini che eccedono 250x250px saranno ridimensionate a 250px di larghezza o altezza mantenendo il loro rapporto. La massima dimensione consentita del file \u00e8 di 1MB. +avatarUpdatedMessage=Avatar aggiornato con successo +error-noAvatarFound=Avatar non trovato sul server + +deleteAccount=Delete Account +deleteAccountInfoMessage=La cancellazione del proprio account disabiliter\u00e0 il profilo e rimuover\u00e0 il nome e le foto condivise sul/sui gateway D4Science. Alcune informazioni potrebbero risultare ancora visibili agli altri utenti, come il nome nei post e nei messaggi privati inviati.
Tutti i file e le cartelle create nel workspace personale saranno rimosse. +deleteAccountDialogHeader=Conferma cancellazione account +deleteAccountWarningMessage=Cliccando sul bottone "Conferma" si avvier\u00e0 un''operazione non annullabile, l''account personale sar\u00e0 rimosso e saranno terminate tutte le sessioni aperte nei vari siti. +deleteAccountConfirmMessage=
Si vuole veramente cancellare il proprio account? NOTA BENE: Questa azione \u00e8 irreversibile! +doDeleteConfirm=Conferma +accountDeletedMessage=L''account \u00e8 stato cancellato \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content.json b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content.json new file mode 100644 index 0000000..b0a864e --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content.json @@ -0,0 +1,74 @@ +[ + { + "id": "personal-info", + "path": "personal-info", + "icon": "pf-icon-user", + "label": "personalInfoHtmlTitle", + "descriptionLabel": "personalInfoIntroMessage", + "content" : [ + { + "id": "personal-info-base", + "path": "personal-info-base", + "label": "personalBasicInfoHtmlTitle", + "modulePath": "/content/account-page/AccountPage.js", + "componentName": "AccountPage" + }, + { + "id": "account-extra", + "path": "account-extra", + "label": "accountExtraInfoHtmlTitleHome", + "modulePath": "/content/d4science-page/AccountExtraPage.js", + "componentName": "AccountExtraPage" + } + ] + }, + { + "id": "security", + "icon": "pf-icon-security", + "label": "Account Security", + "descriptionLabel": "accountSecurityIntroMessage", + "content": [ + { + "id": "signingin", + "path": "security/signingin", + "label": "signingIn", + "modulePath": "/content/signingin-page/SigningInPage.js", + "componentName": "SigningInPage" + }, + { + "id": "device-activity", + "path": "security/device-activity", + "label": "device-activity", + "modulePath": "/content/device-activity-page/DeviceActivityPage.js", + "componentName": "DeviceActivityPage" + }, + { + "id": "linked-accounts", + "path": "security/linked-accounts", + "label": "linkedAccountsHtmlTitle", + "modulePath": "/content/linked-accounts-page/LinkedAccountsPage.js", + "componentName": "LinkedAccountsPage", + "hidden": "!features.isLinkedAccountsEnabled" + } + ] + }, + { + "id": "applications", + "icon": "pf-icon-applications", + "path": "applications", + "label": "applications", + "descriptionLabel": "applicationsIntroMessage", + "modulePath": "/content/applications-page/ApplicationsPage.js", + "componentName": "ApplicationsPage" + }, + { + "id": "resources", + "icon": "pf-icon-repository", + "path": "resources", + "label": "resources", + "descriptionLabel": "resourceIntroMessage", + "modulePath": "/content/my-resources-page/MyResourcesPage.js", + "componentName": "MyResourcesPage", + "hidden": "!features.isMyResourcesEnabled" + } +] \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AccountExtraPage.js b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AccountExtraPage.js new file mode 100644 index 0000000..5e40f62 --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AccountExtraPage.js @@ -0,0 +1,102 @@ +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import * as React from "../../../../common/keycloak/web_modules/react.js"; +import { Button, Grid, GridItem, Expandable, Modal } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js"; +import { AccountServiceContext } from "../../account-service/AccountServiceContext.js"; +import { Msg } from "../../widgets/Msg.js"; +import { ContentPage } from "../ContentPage.js"; +import { ContentAlert } from "../ContentAlert.js"; +import { AvatarForm } from "./AvatarForm.js"; +export class AccountExtraPage extends React.Component { + constructor(props) { + super(props); + + _defineProperty(this, "context", void 0); + + _defineProperty(this, "handleModalToggle", open => { + this.setState({ + isModalOpen: open + }); + }); + + _defineProperty(this, "modalConfirmDelete", event => { + const accountUrl = this.context["accountUrl"]; + const deleteUrl = accountUrl + "-delete/request-delete"; + this.context.doPost(deleteUrl, {}).then(() => { + ContentAlert.success('accountDeletedMessage'); + window.location.reload(); + }); + this.setState({ + isModalOpen: false + }); + }); + + this.state = { + isModalOpen: false + }; + } + + render() { + const accountUrl = this.context["accountUrl"]; + return React.createElement(ContentPage, { + title: "accountExtraInfoHtmlTitle", + introMessage: "accountExtraSubMessage" + }, React.createElement(AvatarForm, { + accountUrl: accountUrl + }), React.createElement("div", { + id: "delete-account", + style: { + marginTop: "30px" + } + }, React.createElement(Expandable, { + toggleText: Msg.localize('deleteAccount') + }, React.createElement(Grid, { + gutter: "sm" + }, React.createElement(GridItem, { + span: 8 + }, React.createElement("p", { + dangerouslySetInnerHTML: { + __html: Msg.localize('deleteAccountInfoMessage') + } + })), React.createElement(GridItem, { + span: 4 + }, React.createElement(Button, { + id: "delete-account-btn", + variant: "danger", + onClick: e => this.handleModalToggle(true), + className: "delete-button" + }, React.createElement(Msg, { + msgKey: "doDelete" + }))))), React.createElement(Modal, { + width: '50%', + title: Msg.localize('deleteAccountDialogHeader'), + isOpen: this.state.isModalOpen, + onClose: () => this.handleModalToggle(false), + actions: [React.createElement(Button, { + key: "confirm", + variant: "danger", + onClick: this.modalConfirmDelete + }, React.createElement(Msg, { + msgKey: "doDeleteConfirm" + })), React.createElement(Button, { + key: "cancel", + variant: "secondary", + onClick: e => this.handleModalToggle(false) + }, React.createElement(Msg, { + msgKey: "doCancel" + }))] + }, React.createElement("div", { + dangerouslySetInnerHTML: { + __html: Msg.localize('deleteAccountWarningMessage') + } + }), React.createElement("div", { + dangerouslySetInnerHTML: { + __html: Msg.localize('deleteAccountConfirmMessage') + } + })))); + } + +} + +_defineProperty(AccountExtraPage, "contextType", AccountServiceContext); +//# sourceMappingURL=AccountExtraPage.js.map \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AccountExtraPage.js.map b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AccountExtraPage.js.map new file mode 100644 index 0000000..639ca5b --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AccountExtraPage.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/app/content/d4science-page/AccountExtraPage.tsx"],"names":["React","Button","Grid","GridItem","Expandable","Modal","AccountServiceContext","Msg","ContentPage","ContentAlert","AvatarForm","AccountExtraPage","Component","constructor","props","open","setState","isModalOpen","event","accountUrl","context","deleteUrl","doPost","then","success","window","location","reload","state","render","marginTop","localize","__html","e","handleModalToggle","modalConfirmDelete"],"mappings":";;AAAA,OAAO,KAAKA,KAAZ;AAEA,SAASC,MAAT,EAAiBC,IAAjB,EAAuBC,QAAvB,EAAiCC,UAAjC,EAA6CC,KAA7C;AACA,SAASC,qBAAT;AACA,SAASC,GAAT;AACA,SAASC,WAAT;AACA,SAASC,YAAT;AACA,SAASC,UAAT;AASA,OAAO,MAAMC,gBAAN,SAA+BX,KAAK,CAACY,SAArC,CAA6F;AAIhGC,EAAAA,WAAW,CAACC,KAAD,EAAa;AACpB,UAAMA,KAAN;;AADoB;;AAAA,+CAKKC,IAAD,IAAmB;AAC3C,WAAKC,QAAL,CAAc;AAAEC,QAAAA,WAAW,EAAEF;AAAf,OAAd;AACH,KAPuB;;AAAA,gDASMG,KAAD,IAAgB;AACzC,YAAMC,UAAU,GAAG,KAAKC,OAAL,CAAc,YAAd,CAAnB;AACA,YAAMC,SAAS,GAAGF,UAAU,GAAG,wBAA/B;AACA,WAAKC,OAAL,CAAcE,MAAd,CAA2BD,SAA3B,EAAsC,EAAtC,EACKE,IADL,CACU,MAAM;AACRd,QAAAA,YAAY,CAACe,OAAb,CAAqB,uBAArB;AACAC,QAAAA,MAAM,CAACC,QAAP,CAAgBC,MAAhB;AACH,OAJL;AAKA,WAAKX,QAAL,CAAc;AAAEC,QAAAA,WAAW,EAAE;AAAf,OAAd;AACH,KAlBuB;;AAEpB,SAAKW,KAAL,GAAa;AAAEX,MAAAA,WAAW,EAAE;AAAf,KAAb;AACH;;AAiBMY,EAAAA,MAAM,GAAoB;AAC7B,UAAMV,UAAU,GAAG,KAAKC,OAAL,CAAc,YAAd,CAAnB;AAEA,WACI,oBAAC,WAAD;AAAa,MAAA,KAAK,EAAC,2BAAnB;AACI,MAAA,YAAY,EAAC;AADjB,OAGI,oBAAC,UAAD;AAAY,MAAA,UAAU,EAAED;AAAxB,MAHJ,EAKI;AAAK,MAAA,EAAE,EAAC,gBAAR;AAAyB,MAAA,KAAK,EAAE;AAACW,QAAAA,SAAS,EAAC;AAAX;AAAhC,OACI,oBAAC,UAAD;AAAY,MAAA,UAAU,EAAEvB,GAAG,CAACwB,QAAJ,CAAa,eAAb;AAAxB,OACI,oBAAC,IAAD;AAAM,MAAA,MAAM,EAAE;AAAd,OACI,oBAAC,QAAD;AAAU,MAAA,IAAI,EAAE;AAAhB,OACI;AAAG,MAAA,uBAAuB,EAAE;AAAEC,QAAAA,MAAM,EAAEzB,GAAG,CAACwB,QAAJ,CAAa,0BAAb;AAAV;AAA5B,MADJ,CADJ,EAII,oBAAC,QAAD;AAAU,MAAA,IAAI,EAAE;AAAhB,OACI,oBAAC,MAAD;AAAQ,MAAA,EAAE,EAAC,oBAAX;AAAgC,MAAA,OAAO,EAAC,QAAxC;AACI,MAAA,OAAO,EAAGE,CAAD,IAAO,KAAKC,iBAAL,CAAuB,IAAvB,CADpB;AACkD,MAAA,SAAS,EAAC;AAD5D,OAGI,oBAAC,GAAD;AAAK,MAAA,MAAM,EAAC;AAAZ,MAHJ,CADJ,CAJJ,CADJ,CADJ,EAgBI,oBAAC,KAAD;AACI,MAAA,KAAK,EAAE,KADX;AAEI,MAAA,KAAK,EAAE3B,GAAG,CAACwB,QAAJ,CAAa,2BAAb,CAFX;AAGI,MAAA,MAAM,EAAE,KAAKH,KAAL,CAAWX,WAHvB;AAII,MAAA,OAAO,EAAE,MAAM,KAAKiB,iBAAL,CAAuB,KAAvB,CAJnB;AAKI,MAAA,OAAO,EAAE,CACL,oBAAC,MAAD;AAAQ,QAAA,GAAG,EAAC,SAAZ;AAAsB,QAAA,OAAO,EAAC,QAA9B;AAAuC,QAAA,OAAO,EAAE,KAAKC;AAArD,SACI,oBAAC,GAAD;AAAK,QAAA,MAAM,EAAC;AAAZ,QADJ,CADK,EAIL,oBAAC,MAAD;AAAQ,QAAA,GAAG,EAAC,QAAZ;AAAqB,QAAA,OAAO,EAAC,WAA7B;AAAyC,QAAA,OAAO,EAAGF,CAAD,IAAO,KAAKC,iBAAL,CAAuB,KAAvB;AAAzD,SACI,oBAAC,GAAD;AAAK,QAAA,MAAM,EAAC;AAAZ,QADJ,CAJK;AALb,OAcI;AAAK,MAAA,uBAAuB,EAAE;AAAEF,QAAAA,MAAM,EAAEzB,GAAG,CAACwB,QAAJ,CAAa,6BAAb;AAAV;AAA9B,MAdJ,EAeI;AAAK,MAAA,uBAAuB,EAAE;AAAEC,QAAAA,MAAM,EAAEzB,GAAG,CAACwB,QAAJ,CAAa,6BAAb;AAAV;AAA9B,MAfJ,CAhBJ,CALJ,CADJ;AA2CH;;AAtE+F;;gBAAvFpB,gB,iBACYL,qB","sourcesContent":["import * as React from 'react';\r\n\r\nimport { Button, Grid, GridItem, Expandable, Modal, Form } from '@patternfly/react-core';\r\nimport { AccountServiceContext } from '../../account-service/AccountServiceContext';\r\nimport { Msg } from '../../widgets/Msg';\r\nimport { ContentPage } from '../ContentPage';\r\nimport { ContentAlert } from '../ContentAlert';\r\nimport { AvatarForm } from './AvatarForm';\r\n\r\ninterface AccountExtraPageProps {\r\n}\r\n\r\ninterface AccountExtraPageState {\r\n isModalOpen: boolean;\r\n}\r\n\r\nexport class AccountExtraPage extends React.Component {\r\n static contextType = AccountServiceContext;\r\n context: React.ContextType;\r\n\r\n constructor(props: any) {\r\n super(props)\r\n this.state = { isModalOpen: false }\r\n }\r\n\r\n private handleModalToggle = (open: boolean) => {\r\n this.setState({ isModalOpen: open })\r\n }\r\n\r\n private modalConfirmDelete = (event: any) => {\r\n const accountUrl = this.context![\"accountUrl\"]\r\n const deleteUrl = accountUrl + \"-delete/request-delete\"\r\n this.context!.doPost(deleteUrl, {})\r\n .then(() => {\r\n ContentAlert.success('accountDeletedMessage')\r\n window.location.reload();\r\n })\r\n this.setState({ isModalOpen: false })\r\n }\r\n\r\n public render(): React.ReactNode {\r\n const accountUrl = this.context![\"accountUrl\"]\r\n\r\n return (\r\n \r\n \r\n\r\n
\r\n \r\n \r\n \r\n

\r\n \r\n \r\n \r\n \r\n \r\n \r\n\r\n this.handleModalToggle(false)}\r\n actions={[\r\n ,\r\n \r\n ]}\r\n >\r\n

\r\n
\r\n \r\n
\r\n\r\n \r\n )\r\n }\r\n}"],"file":"AccountExtraPage.js"} \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AvatarForm.js b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AvatarForm.js new file mode 100644 index 0000000..61546b1 --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AvatarForm.js @@ -0,0 +1,194 @@ +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +import * as React from "../../../../common/keycloak/web_modules/react.js"; +import { Form, FormGroup, ActionGroup, FileUpload, Avatar, Button, Tooltip } from "../../../../common/keycloak/web_modules/@patternfly/react-core.js"; +import { OutlinedQuestionCircleIcon } from "../../../../common/keycloak/web_modules/@patternfly/react-icons.js"; +import { AccountServiceContext } from "../../account-service/AccountServiceContext.js"; +import { ContentAlert } from "../ContentAlert.js"; +import { Msg } from "../../widgets/Msg.js"; +export class AvatarForm extends React.Component { + constructor(props, context) { + super(props); + + _defineProperty(this, "context", void 0); + + _defineProperty(this, "handleFileInputChange", void 0); + + _defineProperty(this, "imageScale", (imgData, callback) => { + var img = new Image(); + img.src = imgData; + + img.onload = event => { + var canvas = document.createElement("canvas"); + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0); + var MAX_WIDTH = 250; + var MAX_HEIGHT = 250; + var width = img.width; + var height = img.height; + + if (width > height) { + if (width > MAX_WIDTH) { + height *= MAX_WIDTH / width; + width = MAX_WIDTH; + } + } else { + if (height > MAX_HEIGHT) { + width *= MAX_HEIGHT / height; + height = MAX_HEIGHT; + } + } + + canvas.width = width; + canvas.height = height; + ctx = canvas.getContext("2d"); + ctx.drawImage(img, 0, 0, width, height); + canvas.toBlob(callback); + }; + }); + + _defineProperty(this, "handleSubmit", event => { + event.preventDefault(); + const form = event.target; + var formData = new FormData(form); + formData.append("image", this.state.imageBlob); + var xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = () => { + if (xhr.readyState == 4) { + if (200 <= xhr.status && xhr.status <= 204) { + ContentAlert.success('avatarUpdatedMessage'); + } else { + ContentAlert.danger(xhr.response); + } // force reload avatar + + + this.setState({ + errors: { + avatar: "" + }, + imageBlob: null, + filename: "", + avatarSrc: this.state.avatarUrl + }); + } + }; + + xhr.open(form.method, form.action, true); + xhr.send(formData); + }); + + _defineProperty(this, "handleError", event => { + this.setState({ + errors: { + avatar: Msg.localize('error-noAvatarFound') + }, + avatarSrc: "" + }); + }); + + this.context = context; + var currentAvatar = props.accountUrl + "-avatar"; + this.state = { + errors: { + avatar: '' + }, + imageBlob: null, + filename: "", + avatarUrl: currentAvatar, + avatarSrc: currentAvatar, + noAvatarSrc: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMiwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNiAzNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzYgMzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRjBGMEYwO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRDJEMkQyO30KCS5zdDJ7ZmlsbDojQjhCQkJFO30KCS5zdDN7ZmlsbDojRDJEMkQyO30KPC9zdHlsZT4KPHJlY3QgY2xhc3M9InN0MCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjM2Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNy43LDIwLjFjLTMuNSwwLTYuNC0yLjktNi40LTYuNHMyLjktNi40LDYuNC02LjRzNi40LDIuOSw2LjQsNi40UzIxLjMsMjAuMSwxNy43LDIwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4zLDM2bDAtNi43Yy0yLDAuNC0yLjksMS40LTMuMSwzLjVMMTAuMSwzNkgxMy4zeiIvPgo8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTAuMSwzNmwwLjEtMy4yYzAuMi0yLjEsMS4xLTMuMSwzLjEtMy41bDAsNi43aDkuNGwwLTYuN2MyLDAuNCwyLjksMS40LDMuMSwzLjVsMC4xLDMuMmg0LjcKCWMtMC40LTMuOS0xLjMtOS0yLjktMTFjLTEuMS0xLjQtMi4zLTIuMi0zLjUtMi42cy0xLjgtMC42LTYuMy0wLjZzLTYuMSwwLjctNi4xLDAuN2MtMS4yLDAuNC0yLjQsMS4yLTMuNCwyLjYKCUM2LjcsMjcsNS44LDMyLjIsNS40LDM2SDEwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yNS45LDM2bC0wLjEtMy4yYy0wLjItMi4xLTEuMS0zLjEtMy4xLTMuNWwwLDYuN0gyNS45eiIvPgo8L3N2Zz4=" + }; + var reader = new FileReader(); + + reader.onloadend = function (event) { + var imgData = String(event.target.result); + this.imageScale(imgData, blob => { + this.setState({ + imageBlob: blob, + avatarSrc: URL.createObjectURL(blob) + }); + }); + }.bind(this); + + this.handleFileInputChange = (file, filename) => { + if (filename != "") { + this.setState({ + filename: filename + }); + reader.readAsDataURL(file); + } else { + this.setState({ + imageBlob: null, + filename: "", + avatarSrc: currentAvatar + }); + } + }; + } + + render() { + const { + filename, + avatarUrl, + avatarSrc, + noAvatarSrc + } = this.state; + const avatarStyle = { + objectFit: 'cover', + width: '150px', + height: '150px', + border: '1px solid lightgray', + boxShadow: 'lightgray 6px 3px 10px 2px' + }; + return React.createElement(Form, { + id: "avatarForm", + method: "post", + isHorizontal: true, + action: avatarUrl, + encType: "multipart/form-data", + onSubmit: event => this.handleSubmit(event) + }, React.createElement(FormGroup, { + label: Msg.localize('avatarLabel'), + fieldId: "avatar-current-or-preview", + helperTextInvalid: this.state.errors.avatar, + isValid: this.state.errors.avatar === '' + }, avatarSrc !== "" ? React.createElement(Avatar, { + src: avatarSrc, + style: avatarStyle, + alt: "Avatar image preview", + onError: this.handleError + }) : React.createElement(Avatar, { + src: noAvatarSrc, + style: avatarStyle, + alt: "No avatar found" + })), React.createElement(FormGroup, { + fieldId: "avatar-upload", + label: React.createElement("span", null, React.createElement(Msg, { + msgKey: "uploadLabel" + }), ' ', React.createElement(Tooltip, { + content: React.createElement(Msg, { + msgKey: "avatarInfo" + }) + }, React.createElement(OutlinedQuestionCircleIcon, null))) + }, React.createElement(FileUpload, { + id: "simple-file", + filename: filename, + filenamePlaceholder: Msg.localize('dragdropInfo'), + browseButtonText: Msg.localize('browseButton'), + clearButtonText: Msg.localize('clearButton'), + onChange: this.handleFileInputChange + })), React.createElement(ActionGroup, null, React.createElement(Button, { + id: "save-btn", + type: "submit", + variant: "primary", + isDisabled: filename === "" + }, React.createElement(Msg, { + msgKey: "doSave" + })))); + } + +} + +_defineProperty(AvatarForm, "contextType", AccountServiceContext); +//# sourceMappingURL=AvatarForm.js.map \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AvatarForm.js.map b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AvatarForm.js.map new file mode 100644 index 0000000..bc91dba --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/content/d4science-page/AvatarForm.js.map @@ -0,0 +1 @@ +{"version":3,"sources":["../../../src/app/content/d4science-page/AvatarForm.tsx"],"names":["React","Form","FormGroup","ActionGroup","FileUpload","Avatar","Button","Tooltip","OutlinedQuestionCircleIcon","AccountServiceContext","ContentAlert","Msg","AvatarForm","Component","constructor","props","context","imgData","callback","img","Image","src","onload","event","canvas","document","createElement","ctx","getContext","drawImage","MAX_WIDTH","MAX_HEIGHT","width","height","toBlob","preventDefault","form","target","formData","FormData","append","state","imageBlob","xhr","XMLHttpRequest","onreadystatechange","readyState","status","success","danger","response","setState","errors","avatar","filename","avatarSrc","avatarUrl","open","method","action","send","localize","currentAvatar","accountUrl","noAvatarSrc","reader","FileReader","onloadend","String","result","imageScale","blob","URL","createObjectURL","bind","handleFileInputChange","file","readAsDataURL","render","avatarStyle","objectFit","border","boxShadow","handleSubmit","handleError"],"mappings":"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","sourcesContent":["import * as React from 'react';\r\nimport * as CSS from 'csstype';\r\n\r\nimport { Form, FormGroup, ActionGroup, FileUpload, Avatar, Button, Tooltip } from \"@patternfly/react-core\";\r\nimport { OutlinedQuestionCircleIcon } from '@patternfly/react-icons';\r\nimport { AccountServiceContext } from '../../account-service/AccountServiceContext';\r\nimport { ContentAlert } from '../ContentAlert';\r\nimport { Msg } from '../../widgets/Msg';\r\n\r\n\r\ninterface AvatarFormProps {\r\n accountUrl: string;\r\n}\r\n\r\ninterface AvatarFormState {\r\n errors: any;\r\n imageBlob: any;\r\n filename: string;\r\n avatarUrl: string;\r\n avatarSrc: string;\r\n noAvatarSrc: string;\r\n}\r\n\r\nexport class AvatarForm extends React.Component {\r\n static contextType = AccountServiceContext;\r\n context: React.ContextType;\r\n private handleFileInputChange: any;\r\n\r\n constructor(props: AvatarFormProps, context: React.ContextType) {\r\n super(props);\r\n this.context = context;\r\n\r\n var currentAvatar = props.accountUrl + \"-avatar\"\r\n this.state = {\r\n errors: {avatar: ''},\r\n imageBlob: null,\r\n filename: \"\",\r\n avatarUrl: currentAvatar,\r\n avatarSrc: currentAvatar,\r\n noAvatarSrc: \"data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMiwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNiAzNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzYgMzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRjBGMEYwO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRDJEMkQyO30KCS5zdDJ7ZmlsbDojQjhCQkJFO30KCS5zdDN7ZmlsbDojRDJEMkQyO30KPC9zdHlsZT4KPHJlY3QgY2xhc3M9InN0MCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjM2Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNy43LDIwLjFjLTMuNSwwLTYuNC0yLjktNi40LTYuNHMyLjktNi40LDYuNC02LjRzNi40LDIuOSw2LjQsNi40UzIxLjMsMjAuMSwxNy43LDIwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4zLDM2bDAtNi43Yy0yLDAuNC0yLjksMS40LTMuMSwzLjVMMTAuMSwzNkgxMy4zeiIvPgo8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTAuMSwzNmwwLjEtMy4yYzAuMi0yLjEsMS4xLTMuMSwzLjEtMy41bDAsNi43aDkuNGwwLTYuN2MyLDAuNCwyLjksMS40LDMuMSwzLjVsMC4xLDMuMmg0LjcKCWMtMC40LTMuOS0xLjMtOS0yLjktMTFjLTEuMS0xLjQtMi4zLTIuMi0zLjUtMi42cy0xLjgtMC42LTYuMy0wLjZzLTYuMSwwLjctNi4xLDAuN2MtMS4yLDAuNC0yLjQsMS4yLTMuNCwyLjYKCUM2LjcsMjcsNS44LDMyLjIsNS40LDM2SDEwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yNS45LDM2bC0wLjEtMy4yYy0wLjItMi4xLTEuMS0zLjEtMy4xLTMuNWwwLDYuN0gyNS45eiIvPgo8L3N2Zz4=\"\r\n }\r\n\r\n var reader = new FileReader()\r\n reader.onloadend = function (event: any) {\r\n var imgData = String(event.target!.result)\r\n this.imageScale(imgData, (blob: Blob) => {\r\n this.setState({\r\n imageBlob: blob,\r\n avatarSrc: URL.createObjectURL(blob)\r\n })\r\n })\r\n }.bind(this)\r\n \r\n this.handleFileInputChange = (file: File, filename: string) => {\r\n if (filename != \"\") {\r\n this.setState({ filename: filename })\r\n reader.readAsDataURL(file)\r\n } else {\r\n this.setState({\r\n imageBlob: null,\r\n filename: \"\",\r\n avatarSrc: currentAvatar\r\n })\r\n }\r\n }\r\n }\r\n\r\n private imageScale = (imgData: string, callback: any) => {\r\n var img = new Image()\r\n img.src = imgData\r\n img.onload = (event: Event) => {\r\n var canvas = document.createElement(\"canvas\")\r\n var ctx = canvas.getContext(\"2d\")\r\n ctx!.drawImage(img, 0, 0)\r\n\r\n var MAX_WIDTH = 250\r\n var MAX_HEIGHT = 250\r\n var width = img.width\r\n var height = img.height\r\n\r\n if (width > height) {\r\n if (width > MAX_WIDTH) {\r\n height *= MAX_WIDTH / width\r\n width = MAX_WIDTH\r\n }\r\n } else {\r\n if (height > MAX_HEIGHT) {\r\n width *= MAX_HEIGHT / height\r\n height = MAX_HEIGHT\r\n }\r\n }\r\n canvas.width = width\r\n canvas.height = height\r\n ctx = canvas.getContext(\"2d\")\r\n ctx!.drawImage(img, 0, 0, width, height)\r\n canvas.toBlob(callback)\r\n }\r\n }\r\n\r\n private handleSubmit = (event: React.FormEvent): void => {\r\n event.preventDefault()\r\n const form = event.target as HTMLFormElement\r\n var formData = new FormData(form)\r\n formData.append(\"image\", this.state.imageBlob)\r\n var xhr = new XMLHttpRequest();\r\n xhr.onreadystatechange = () => {\r\n if (xhr.readyState == 4) {\r\n if (200 <= xhr.status && xhr.status <= 204) {\r\n ContentAlert.success('avatarUpdatedMessage')\r\n } else {\r\n ContentAlert.danger(xhr.response)\r\n }\r\n // force reload avatar\r\n this.setState({\r\n errors: {avatar: \"\"},\r\n imageBlob: null,\r\n filename: \"\",\r\n avatarSrc: this.state.avatarUrl\r\n })\r\n }\r\n }\r\n xhr.open(form.method, form.action, true);\r\n xhr.send(formData);\r\n }\r\n\r\n private handleError = (event: any) => {\r\n this.setState({\r\n errors: {avatar: Msg.localize('error-noAvatarFound')},\r\n avatarSrc: \"\"\r\n })\r\n }\r\n\r\n render() {\r\n const { filename, avatarUrl, avatarSrc, noAvatarSrc } = this.state\r\n const avatarStyle: CSS.Properties = {\r\n objectFit: 'cover',\r\n width: '150px', height: '150px',\r\n border: '1px solid lightgray',\r\n boxShadow: 'lightgray 6px 3px 10px 2px'\r\n }\r\n return (\r\n
this.handleSubmit(event)}\r\n >\r\n \r\n { avatarSrc !== \"\"\r\n ? \r\n : \r\n }\r\n \r\n\r\n \r\n \r\n {' '}\r\n }>\r\n \r\n \r\n }\r\n >\r\n \r\n\r\n \r\n\r\n \r\n \r\n \r\n \r\n \r\n \r\n )\r\n }\r\n}\r\n"],"file":"AvatarForm.js"} \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/css/styles.css b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/css/styles.css new file mode 100644 index 0000000..fe1a59e --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/css/styles.css @@ -0,0 +1,23 @@ +body { + /* --pf-global--FontFamily--sans-serif: Comic Sans MS; */ + /* --pf-global--FontFamily--heading--sans-serif: Comic Sans MS; */ + + --pf-global--BackgroundColor--dark-100: #303030; + + --pf-global--Color--100: #151515; + +} + +.pf-c-nav__list .pf-c-nav__link { + --pf-c-nav__list-link--Color: #303030; + --pf-c-nav__list-link--m-current--Color: #151515; +} + +.pf-c-nav__simple-list .pf-c-nav__link { + --pf-c-nav__simple-list-link--Color: #303030; + --pf-c-nav__simple-list-link--m-current--Color: #151515; +} + +.brand { + height: 50px !important; +} \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/public/d4science-logo.png b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/public/d4science-logo.png new file mode 100644 index 0000000..6005bae Binary files /dev/null and b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/public/d4science-logo.png differ diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/public/favicon.ico b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/public/favicon.ico new file mode 100644 index 0000000..718bf5d Binary files /dev/null and b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/resources/public/favicon.ico differ diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AccountExtraPage.tsx b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AccountExtraPage.tsx new file mode 100644 index 0000000..1880c02 --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AccountExtraPage.tsx @@ -0,0 +1,88 @@ +import * as React from 'react'; + +import { Button, Grid, GridItem, Expandable, Modal, Form } from '@patternfly/react-core'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { Msg } from '../../widgets/Msg'; +import { ContentPage } from '../ContentPage'; +import { ContentAlert } from '../ContentAlert'; +import { AvatarForm } from './AvatarForm'; + +interface AccountExtraPageProps { +} + +interface AccountExtraPageState { + isModalOpen: boolean; +} + +export class AccountExtraPage extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + + constructor(props: any) { + super(props) + this.state = { isModalOpen: false } + } + + private handleModalToggle = (open: boolean) => { + this.setState({ isModalOpen: open }) + } + + private modalConfirmDelete = (event: any) => { + const accountUrl = this.context!["accountUrl"] + const deleteUrl = accountUrl + "-delete/request-delete" + this.context!.doPost(deleteUrl, {}) + .then(() => { + ContentAlert.success('accountDeletedMessage') + window.location.reload(); + }) + this.setState({ isModalOpen: false }) + } + + public render(): React.ReactNode { + const accountUrl = this.context!["accountUrl"] + + return ( + + + +
+ + + +

+ + + + + + + + this.handleModalToggle(false)} + actions={[ + , + + ]} + > +

+
+ +
+ + + ) + } +} \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AvatarForm.tsx b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AvatarForm.tsx new file mode 100644 index 0000000..3be175f --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/src/app/content/d4science-page/AvatarForm.tsx @@ -0,0 +1,190 @@ +import * as React from 'react'; +import * as CSS from 'csstype'; + +import { Form, FormGroup, ActionGroup, FileUpload, Avatar, Button, Tooltip } from "@patternfly/react-core"; +import { OutlinedQuestionCircleIcon } from '@patternfly/react-icons'; +import { AccountServiceContext } from '../../account-service/AccountServiceContext'; +import { ContentAlert } from '../ContentAlert'; +import { Msg } from '../../widgets/Msg'; + + +interface AvatarFormProps { + accountUrl: string; +} + +interface AvatarFormState { + errors: any; + imageBlob: any; + filename: string; + avatarUrl: string; + avatarSrc: string; + noAvatarSrc: string; +} + +export class AvatarForm extends React.Component { + static contextType = AccountServiceContext; + context: React.ContextType; + private handleFileInputChange: any; + + constructor(props: AvatarFormProps, context: React.ContextType) { + super(props); + this.context = context; + + var currentAvatar = props.accountUrl + "-avatar" + this.state = { + errors: {avatar: ''}, + imageBlob: null, + filename: "", + avatarUrl: currentAvatar, + avatarSrc: currentAvatar, + noAvatarSrc: "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4KPCEtLSBHZW5lcmF0b3I6IEFkb2JlIElsbHVzdHJhdG9yIDI0LjAuMiwgU1ZHIEV4cG9ydCBQbHVnLUluIC4gU1ZHIFZlcnNpb246IDYuMDAgQnVpbGQgMCkgIC0tPgo8c3ZnIHZlcnNpb249IjEuMSIgaWQ9IkxheWVyXzEiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIHg9IjBweCIgeT0iMHB4IgoJIHZpZXdCb3g9IjAgMCAzNiAzNiIgc3R5bGU9ImVuYWJsZS1iYWNrZ3JvdW5kOm5ldyAwIDAgMzYgMzY7IiB4bWw6c3BhY2U9InByZXNlcnZlIj4KPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCS5zdDB7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRjBGMEYwO30KCS5zdDF7ZmlsbC1ydWxlOmV2ZW5vZGQ7Y2xpcC1ydWxlOmV2ZW5vZGQ7ZmlsbDojRDJEMkQyO30KCS5zdDJ7ZmlsbDojQjhCQkJFO30KCS5zdDN7ZmlsbDojRDJEMkQyO30KPC9zdHlsZT4KPHJlY3QgY2xhc3M9InN0MCIgd2lkdGg9IjM2IiBoZWlnaHQ9IjM2Ii8+CjxwYXRoIGNsYXNzPSJzdDEiIGQ9Ik0xNy43LDIwLjFjLTMuNSwwLTYuNC0yLjktNi40LTYuNHMyLjktNi40LDYuNC02LjRzNi40LDIuOSw2LjQsNi40UzIxLjMsMjAuMSwxNy43LDIwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0xMy4zLDM2bDAtNi43Yy0yLDAuNC0yLjksMS40LTMuMSwzLjVMMTAuMSwzNkgxMy4zeiIvPgo8cGF0aCBjbGFzcz0ic3QzIiBkPSJNMTAuMSwzNmwwLjEtMy4yYzAuMi0yLjEsMS4xLTMuMSwzLjEtMy41bDAsNi43aDkuNGwwLTYuN2MyLDAuNCwyLjksMS40LDMuMSwzLjVsMC4xLDMuMmg0LjcKCWMtMC40LTMuOS0xLjMtOS0yLjktMTFjLTEuMS0xLjQtMi4zLTIuMi0zLjUtMi42cy0xLjgtMC42LTYuMy0wLjZzLTYuMSwwLjctNi4xLDAuN2MtMS4yLDAuNC0yLjQsMS4yLTMuNCwyLjYKCUM2LjcsMjcsNS44LDMyLjIsNS40LDM2SDEwLjF6Ii8+CjxwYXRoIGNsYXNzPSJzdDIiIGQ9Ik0yNS45LDM2bC0wLjEtMy4yYy0wLjItMi4xLTEuMS0zLjEtMy4xLTMuNWwwLDYuN0gyNS45eiIvPgo8L3N2Zz4=" + } + + var reader = new FileReader() + reader.onloadend = function (event: any) { + var imgData = String(event.target!.result) + this.imageScale(imgData, (blob: Blob) => { + this.setState({ + imageBlob: blob, + avatarSrc: URL.createObjectURL(blob) + }) + }) + }.bind(this) + + this.handleFileInputChange = (file: File, filename: string) => { + if (filename != "") { + this.setState({ filename: filename }) + reader.readAsDataURL(file) + } else { + this.setState({ + imageBlob: null, + filename: "", + avatarSrc: currentAvatar + }) + } + } + } + + private imageScale = (imgData: string, callback: any) => { + var img = new Image() + img.src = imgData + img.onload = (event: Event) => { + var canvas = document.createElement("canvas") + var ctx = canvas.getContext("2d") + ctx!.drawImage(img, 0, 0) + + var MAX_WIDTH = 250 + var MAX_HEIGHT = 250 + var width = img.width + var height = img.height + + if (width > height) { + if (width > MAX_WIDTH) { + height *= MAX_WIDTH / width + width = MAX_WIDTH + } + } else { + if (height > MAX_HEIGHT) { + width *= MAX_HEIGHT / height + height = MAX_HEIGHT + } + } + canvas.width = width + canvas.height = height + ctx = canvas.getContext("2d") + ctx!.drawImage(img, 0, 0, width, height) + canvas.toBlob(callback) + } + } + + private handleSubmit = (event: React.FormEvent): void => { + event.preventDefault() + const form = event.target as HTMLFormElement + var formData = new FormData(form) + formData.append("image", this.state.imageBlob) + var xhr = new XMLHttpRequest(); + xhr.onreadystatechange = () => { + if (xhr.readyState == 4) { + if (200 <= xhr.status && xhr.status <= 204) { + ContentAlert.success('avatarUpdatedMessage') + } else { + ContentAlert.danger(xhr.response) + } + // force reload avatar + this.setState({ + errors: {avatar: ""}, + imageBlob: null, + filename: "", + avatarSrc: this.state.avatarUrl + }) + } + } + xhr.open(form.method, form.action, true); + xhr.send(formData); + } + + private handleError = (event: any) => { + this.setState({ + errors: {avatar: Msg.localize('error-noAvatarFound')}, + avatarSrc: "" + }) + } + + render() { + const { filename, avatarUrl, avatarSrc, noAvatarSrc } = this.state + const avatarStyle: CSS.Properties = { + objectFit: 'cover', + width: '150px', height: '150px', + border: '1px solid lightgray', + boxShadow: 'lightgray 6px 3px 10px 2px' + } + return ( +
this.handleSubmit(event)} + > + + { avatarSrc !== "" + ? + : + } + + + + + {' '} + }> + + + } + > + + + + + + + +
+ ) + } +} diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/theme.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/theme.properties new file mode 100644 index 0000000..c402614 --- /dev/null +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science.v2/account/theme.properties @@ -0,0 +1,19 @@ +# This theme will inherit everything from its parent unless +# it is overridden in the current theme. +parent=keycloak.v2 + +# Look at the styles.css file to see examples of using PatternFly's CSS variables +# for modifying look and feel. +styles=css/styles.css + +# This is the logo in upper lefthand corner. +# It must be a relative path. +logo=/public/d4science-logo.png + +# This is the link followed when clicking on the logo. +# It can be any valid URL, including an external site. +logoUrl=./ + +# This is the icon for the account console. +# It must be a relative path. +favIcon=/public/favicon.ico \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/account/account.ftl b/keycloak-d4science-theme/src/main/resources/theme/d4science/account/account.ftl deleted file mode 100644 index bf90d13..0000000 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/account/account.ftl +++ /dev/null @@ -1,191 +0,0 @@ -<#import "template.ftl" as layout> -<@layout.mainLayout active='account' bodyClass='user'; section> - -
-
-

${msg("editAccountHtmlTitle")}

-
-
- * ${msg("requiredFields")} -
-
- -
- - - - <#if !realm.registrationEmailAsUsername> -
-
- <#if realm.editUsernameAllowed>* -
- -
- disabled="disabled" value="${(account.username!'')}"/> -
-
- - -
-
- * -
- -
- -
-
- -
-
- * -
- -
- -
-
- -
-
- * -
- -
- -
-
- -
-
-
- <#if url.referrerURI??>${kcSanitize(msg("backToApplication")?no_esc)} - - -
-
-
-
- -
-
-

${msg("changeAvatarHtmlTitle")}

-
-
- - <#assign avatarUrl = url.accountUrl?replace("^(.*)(/account/?)(\\?(.*))?$", "$1/avatar-provider/?account&$4", 'r') /> -
- - ${msg("avatarFileSizeMessage")}';this.replaceWith(div)" /> - - - - - - - -
-
-
- -
-
-
- - - -
- - -
-
-

${msg("deleteAccountHtmlTitle")}

-
-
- - <#assign deleteUrl = url.accountUrl?replace("^(.*)(/account/?)(\\?(.*))?$", "$1/delete-account/delete?$4", 'r') /> -
- - - -
-
${msg("deleteAccountMessage")}
-
-
-
${msg("deleteAccountWarningTitle")}
-
${msg("deleteAccountWarningMessage")}
-
-
-
-
- -
-
-
-
- - diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/account/theme.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science/account/theme.properties index 9ed413c..512d633 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/account/theme.properties +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/account/theme.properties @@ -1 +1 @@ -parent=keycloak.v2 \ No newline at end of file +parent=keycloak \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/admin/theme.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science/admin/theme.properties index a292171..512d633 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/admin/theme.properties +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/admin/theme.properties @@ -1,2 +1 @@ -parent=keycloak -scripts=js/user-avatar.js \ No newline at end of file +parent=keycloak \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/email/theme.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science/email/theme.properties index 2d72a63..ee3f1d1 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/email/theme.properties +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/email/theme.properties @@ -1 +1 @@ -parent=keycloak.v2 +parent=keycloak diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/login-update-profile.ftl b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/login-update-profile.ftl index 39e1e75..6faf8db 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/login-update-profile.ftl +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/login-update-profile.ftl @@ -1,43 +1,82 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout; section> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('username','email','firstName','lastName'); section> <#if section = "header"> ${msg("loginProfileTitle")} <#elseif section = "form">
<#if user.editUsernameAllowed> -
+
- + + + + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + +
-
+
- + + + <#if messagesPerField.existsError('email')> + + ${kcSanitize(messagesPerField.get('email'))?no_esc} + +
-
+
- + + + <#if messagesPerField.existsError('firstName')> + + ${kcSanitize(messagesPerField.get('firstName'))?no_esc} + +
-
+
- + + + <#if messagesPerField.existsError('lastName')> + + ${kcSanitize(messagesPerField.get('lastName'))?no_esc} + +
@@ -47,15 +86,45 @@
+
<#if isAppInitiatedAction??> - + <#else> - +
+ + + diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/messages/messages_it.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/messages/messages_it.properties index 5358c85..cdd32ce 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/messages/messages_it.properties +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/messages/messages_it.properties @@ -1,3 +1,7 @@ +loginAccountTitle=Accedi al tuo account + confirmLinkIdpReviewProfile=Rivedi le informazioni del nuovo profilo -usernameValidityMsg=Sono consentiti solo lettere, numeri e punti \ No newline at end of file +usernameValidityMsg=Sono consentiti solo lettere, numeri e punti + +identity-provider-login-label=Oppure accedi con \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/register.ftl b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/register.ftl index ab7c662..ffa4a17 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/register.ftl +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/register.ftl @@ -1,73 +1,131 @@ <#import "template.ftl" as layout> -<@layout.registrationLayout; section> +<@layout.registrationLayout displayMessage=!messagesPerField.existsError('firstName','lastName','email','username','password','password-confirm'); section> <#if section = "header"> ${msg("registerTitle")} <#elseif section = "form">
-
+
- + + + <#if messagesPerField.existsError('firstName')> + + ${kcSanitize(messagesPerField.get('firstName'))?no_esc} + +
-
+
- + + + <#if messagesPerField.existsError('lastName')> + + ${kcSanitize(messagesPerField.get('lastName'))?no_esc} + +
-
+
- + + + <#if messagesPerField.existsError('email')> + + ${kcSanitize(messagesPerField.get('email'))?no_esc} + +
- <#if !realm.registrationEmailAsUsername> -
-
- + <#if !realm.registrationEmailAsUsername> +
+
+ +
+
+ + + + <#if messagesPerField.existsError('username')> + + ${kcSanitize(messagesPerField.get('username'))?no_esc} + + +
-
- -
-
- + <#if passwordRequired??> -
-
- -
-
- -
-
+
+
+ +
+
+ -
-
- + <#if messagesPerField.existsError('password')> + + ${kcSanitize(messagesPerField.get('password'))?no_esc} + + +
-
- + +
+
+ +
+
+ + + <#if messagesPerField.existsError('password-confirm')> + + ${kcSanitize(messagesPerField.get('password-confirm'))?no_esc} + + +
-
<#if recaptchaRequired??> -
-
-
+
+
+
+
-
@@ -78,21 +136,25 @@
- + +
+ - + \ No newline at end of file diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/resources/css/d4science.css b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/resources/css/d4science.css index bcee5dc..f392ac5 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/resources/css/d4science.css +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/resources/css/d4science.css @@ -1,568 +1,29 @@ .login-pf body { - display: flex; - flex-direction: column; - height: 100vh; + background: url("../img/color-triangles.png") no-repeat center center fixed; + background-size: cover; + height: 100%; } -#content { - flex: 1 0 auto; - display: flex; - flex-direction: column; - justify-content: center; +.login-pf-page { + padding-top: 30px; + padding-bottom: 30px; } -.alert-error { - background-color: #ffffff; - border-color: #cc0000; - color: #333333; -} - -#kc-locale ul { - display: none; - position: absolute; - background-color: #fff; - list-style: none; - right: 0; - top: 20px; - min-width: 100px; - padding: 2px 0; - border: solid 1px #bbb; -} - -#kc-locale:hover ul { - display: block; - margin: 0; -} - -#kc-locale ul li a { - display: block; - padding: 5px 14px; - color: #000 !important; - text-decoration: none; - line-height: 20px; -} - -#kc-locale ul li a:hover { - color: #4d5258; - background-color: #d4edfa; -} - -#kc-locale-dropdown a { - color: #4d5258; - background: 0 0; - padding: 0 15px 0 0; - font-weight: 300; -} - -#kc-locale-dropdown a:hover { - text-decoration: none; -} - -a#kc-current-locale-link { - display: block; - padding: 0 5px; -} - -/* a#kc-current-locale-link:hover { - background-color: rgba(0,0,0,0.2); -} */ - -a#kc-current-locale-link::after { - content: "\2c5"; - margin-left: 4px; -} - -.login-pf .container { - padding-top: 40px; -} - -.login-pf a:hover { - color: #0099d3; -} - -#kc-logo { - width: 100%; -} - -#kc-logo-wrapper { - background-image: url(../img/keycloak-logo-2.png); - background-repeat: no-repeat; - height: 63px; - width: 300px; - margin: 62px auto 0; -} - -div.kc-logo-text { - background-image: url(../img/keycloak-logo-text.png); - background-repeat: no-repeat; - height: 63px; - width: 300px; - margin: 0 auto; -} - -div.kc-logo-text span { - display: none; -} - -#kc-header { - color: #ededed; - overflow: visible; - white-space: nowrap; -} - -#kc-header-wrapper { - font-size: 29px; - text-transform: uppercase; - letter-spacing: 3px; - line-height: 1.2em; - padding: 62px 10px 20px; - white-space: normal; -} - -#kc-content { - width: 100%; -} - -#kc-attempted-username{ - font-size: 20px; - font-family:inherit; - font-weight: normal; - padding-right:10px; -} - -#kc-username{ - text-align: center; -} - -#kc-webauthn-settings-form{ - padding-top:8px; -} - -/* #kc-content-wrapper { - overflow-y: hidden; -} */ - -/* #kc-info { - padding-bottom: 200px; - margin-bottom: -200px; -} */ - -#kc-info-wrapper { - font-size: 13px; -} - -#kc-form-options span { - display: block; -} - -#kc-form-options .checkbox { - margin-top: 0; - color: #72767b; -} - -#kc-terms-text { - margin-bottom: 20px; -} - -#kc-registration { - margin-bottom: 15px; -} - -/* TOTP */ - -.subtitle { - text-align: right; - margin-top: 30px; - color: #909090; -} - -.required { - color: #CB2915; -} - -ol#kc-totp-settings { - margin: 0; - padding-left: 20px; -} - -ul#kc-totp-supported-apps { - margin-bottom: 10px; -} - -#kc-totp-secret-qr-code { - max-width:150px; - max-height:150px; -} - -#kc-totp-secret-key { - background-color: #fff; - color: #333333; - font-size: 16px; - padding: 10px 0; -} - -/* OAuth */ - -#kc-oauth h3 { - margin-top: 0; -} - -#kc-oauth ul { - list-style: none; - padding: 0; - margin: 0; -} - -#kc-oauth ul li { - border-top: 1px solid rgba(255, 255, 255, 0.1); - font-size: 12px; - padding: 10px 0; -} - -#kc-oauth ul li:first-of-type { - border-top: 0; -} - -#kc-oauth .kc-role { - display: inline-block; - width: 50%; -} - -/* Code */ -#kc-code textarea { - width: 100%; - height: 8em; -} - -/* Social */ - -#kc-social-providers ul { - padding: 0; -} - -#kc-social-providers li { - display: block; -} - -#kc-social-providers li:first-of-type { - margin-top: 0; -} - -.kc-login-tooltip{ - position:relative; - display: inline-block; -} - -.kc-login-tooltip .kc-tooltip-text{ - top:-3px; - left:160%; - background-color: black; - visibility: hidden; - color: #fff; - - min-width:130px; - text-align: center; - border-radius: 2px; - box-shadow:0 1px 8px rgba(0,0,0,0.6); - padding: 5px; - - position: absolute; - opacity:0; - transition:opacity 0.5s; -} - -/* Show tooltip */ -.kc-login-tooltip:hover .kc-tooltip-text { - visibility: visible; - opacity:0.7; -} - -/* Arrow for tooltip */ -.kc-login-tooltip .kc-tooltip-text::after { - content: " "; - position: absolute; - top: 15px; - right: 100%; - margin-top: -5px; - border-width: 5px; - border-style: solid; - border-color: transparent black transparent transparent; -} - -.zocial, -a.zocial { - width: 100%; - font-weight: normal; - font-size: 14px; - text-shadow: none; - border: 0; - background: #f5f5f5; - color: #72767b; - border-radius: 0; - white-space: normal; -} -.zocial:before { - border-right: 0; - margin-right: 0; -} -.zocial span:before { - padding: 7px 10px; - font-size: 14px; -} -.zocial:hover { - background: #ededed !important; -} - -.zocial.facebook, -.zocial.github, -.zocial.google, -.zocial.microsoft, -.zocial.stackoverflow, -.zocial.linkedin, -.zocial.twitter { - background-image: none; - border: 0; - - box-shadow: none; - text-shadow: none; -} - -/* Copy of zocial windows classes to be used for microsoft's social provider button */ -.zocial.microsoft:before{ content: "\f15d"; } -.zocial.stackoverflow:before{ color: inherit; } - -.zocial.oidc:before{ - content: " "; - background: url(../img/academic.png); - height: 25px; - width: 25px; - background-size: contain; - background-repeat: no-repeat; - margin: 3px 3px 3px 7px; -} -.zocial.google:before{ - content: " "; - background: url(../img/google.png); - height: 16px; - width: 16px; - background-size: contain; - background-repeat: no-repeat; - margin: 6px 8px 8px 8px; -} - -@media (min-width: 768px) { - #kc-container-wrapper { - position: absolute; - width: 100%; - } - - .login-pf .container { - padding-right: 80px; - } - - #kc-locale { - position: relative; - text-align: right; - z-index: 9999; - } -} - -@media (max-width: 767px) { - - .login-pf body { - background: white; - } - - #kc-header { - padding-left: 15px; - padding-right: 15px; - float: none; - text-align: left; - } - - #kc-header-wrapper { - font-size: 16px; - font-weight: bold; - padding: 20px 60px 0 0; - color: #72767b; - letter-spacing: 0; - } - - div.kc-logo-text { - margin: 0; - width: 150px; - height: 32px; - background-size: 100%; - } - - #kc-form { - float: none; - } - - #kc-info-wrapper { - border-top: 1px solid rgba(255, 255, 255, 0.1); - margin-top: 15px; - padding-top: 15px; - padding-left: 0px; - padding-right: 15px; - } - - #kc-social-providers li { - display: block; - margin-right: 5px; - } - - .login-pf .container { - padding-top: 15px; - padding-bottom: 15px; - } - - #kc-locale { - position: absolute; - width: 200px; - top: 20px; - right: 20px; - text-align: right; - z-index: 9999; - } - - #kc-logo-wrapper { - background-size: 100px 21px; - height: 21px; - width: 100px; - margin: 20px 0 0 20px; - } - -} - -@media (min-height: 646px) { - #kc-container-wrapper { - bottom: 12%; - } -} - -@media (max-height: 645px) { - #kc-container-wrapper { - padding-top: 50px; - top: 20%; - } -} - -.card-pf form.form-actions .btn { - float: right; - margin-left: 10px; -} - -#kc-form-buttons { - margin-top: 40px; -} - -.login-pf-page .login-pf-brand { - margin-top: 20px; - max-width: 360px; - width: 40%; -} - -.card-pf { - background: #fff; - margin: 0 auto; - padding: 0 20px; - max-width: 500px; - border-top: 0; - box-shadow: 0 0 0; -} - -.login-pf-page .card-pf{ - padding: 20px 20px 20px 20px; -} - -/*tablet*/ -@media (max-width: 840px) { - .login-pf-page .card-pf{ - max-width: none; - margin-left: 20px; - margin-right: 20px; - padding: 20px 20px 20px 20px; - } -} -@media (max-width: 767px) { - .login-pf-page .card-pf{ - max-width: none; - margin-left: 0; - margin-right: 0; - padding-top: 0; - } - .card-pf.login-pf-accounts{ - max-width: none; - } -} - -.login-pf-page .login-pf-signup { - font-size: 15px; - color: #72767b; -} -#kc-content-wrapper .row { - margin-left: 0; - margin-right: 0; -} - -@media (min-width: 768px) { - .login-pf-page .login-pf-social-section:first-of-type { - padding-right: 39px; - border-right: 1px solid #d1d1d1; - margin-right: -1px; - } - .login-pf-page .login-pf-social-section:last-of-type { - padding-left: 40px; - } - .login-pf-page .login-pf-social-section .login-pf-social-link:last-of-type { +#kc-info { margin-bottom: 0; - } - .login-pf-page .login-pf-social-double-col .login-pf-social-link { - -ms-flex-preferred-size: 100%; - flex-basis: 100%; - } } -.login-pf-page .login-pf-social-link { - margin-bottom: 20px; -} -.login-pf-page .login-pf-social-link a { - padding: 2px 0; +.card-d4s-wide { + max-width: 750px; } -.login-pf-page.login-pf-page-accounts { - margin-left: auto; - margin-right: auto; +.d4s-terms-text { + max-height: 40vh; + overflow-y: auto; + padding: 0 20px 10px 0; } -.login-pf-page .btn-primary { - margin-top: 0; -} - -.login-pf-page .list-view-pf .list-group-item { - border-bottom: 1px solid #ededed; -} - -.login-pf-page .list-view-pf-description { - width: 100%; -} - -.login-pf-page .card-pf{ - margin-bottom: 10px; -} - -#kc-form-login div.form-group:last-of-type, -#kc-register-form div.form-group:last-of-type, -#kc-update-profile-form div.form-group:last-of-type { - margin-bottom: 0px; -} - -#kc-back { - margin-top: 5px; -} - -form#kc-select-back-form div.login-pf-social-section { - padding-left: 0px; - border-left: 0px; +.d4s-terms-info { + font-weight: bold; + font-variant: small-caps; } diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/template.ftl b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/template.ftl index 35bbdb7..765ce11 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/template.ftl +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/template.ftl @@ -1,19 +1,25 @@ -<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false displayWide=false showAnotherWayIfPresent=true> +<#macro registrationLayout bodyClass="" displayInfo=false displayMessage=true displayRequiredFields=false showAnotherWayIfPresent=true displayWide=false> - + <#if properties.meta?has_content> <#list properties.meta?split(' ') as meta> + <#if properties.titleTag?has_content>${properties.titleTag}<#else>${msg("loginTitle",(realm.displayName!''))}</#if> + <#if properties.stylesCommon?has_content> + <#list properties.stylesCommon?split(' ') as style> + + + <#if properties.styles?has_content> <#list properties.styles?split(' ') as style> @@ -31,80 +37,82 @@ - -
- -
- + +
+ - -
+
- - -
- <#if realm.internationalizationEnabled && locale.supported?size gt 1> -
-
-
- ${locale.current} -
    - <#list locale.supported as l> -
  • ${l.label}
  • - -
-
-
+ +
+ - - <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> - <#if displayRequiredFields> -
-
- * ${msg("requiredFields")} -
-
-

<#nested "header">

+ <#if properties.infrastructureLogo?has_content && properties.infrastructureLogo='yes'> +
+ + D4Science Infrastructure + +
+ +
+ +
+ <#if realm.internationalizationEnabled && locale.supported?size gt 1> +
+
+
+ ${locale.current} +
    + <#list locale.supported as l> +
  • + ${l.label} +
  • + +
+
- <#else> -

<#nested "header">

- <#else> - <#if displayRequiredFields> -
-
- * ${msg("requiredFields")} + <#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())> + <#if displayRequiredFields> +
+
+ * ${msg("requiredFields")} +
+
+

<#nested "header">

+
-
- <#nested "show-username"> -
-
+ <#else> +

<#nested "header">

+ + <#else> + <#if displayRequiredFields> +
+
+ * ${msg("requiredFields")} +
+
+ <#nested "show-username"> +
-
- <#else> - <#nested "show-username"> -
-
-
+
- <#-- App-initiated actions should not see warning messages about the need to complete the action --> - <#-- during login. --> - <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)> -
- <#if message.type = 'success'> - <#if message.type = 'warning'> - <#if message.type = 'error'> - <#if message.type = 'info'> - -
- +
+
- <#nested "form"> + <#-- App-initiated actions should not see warning messages about the need to complete the action --> + <#-- during login. --> + <#if displayMessage && message?has_content && (message.type != 'warning' || !isAppInitiatedAction??)> +
+
+ <#if message.type = 'success'> + <#if message.type = 'warning'> + <#if message.type = 'error'> + <#if message.type = 'info'> +
+ ${kcSanitize(message.summary)?no_esc} +
+ - <#if auth?has_content && auth.showTryAnotherWayLink() && showAnotherWayIfPresent> -
class="${properties.kcContentWrapperClass!}"> -
class="${properties.kcFormSocialAccountContentClass!} ${properties.kcFormSocialAccountClass!}"> - -
-
- + + <#nested "form"> - <#if displayInfo> -
-
- <#nested "info"> -
-
- -
+ <#if auth?has_content && auth.showTryAnotherWayLink() && showAnotherWayIfPresent> +
+ +
+ -
- -
-
- Terms of Use | - Cookies Policy | - Privacy Policy | - <#if properties.linkProject?has_content>${properties.descrProject!} -
- <#if properties.ECLogo?has_content && properties.ECLogo='yes'> -
- <#if properties.footerRow?has_content>
${kcSanitize(properties.footerRow)?no_esc}
-
- - ${properties.ECLogoAlt!} - + <#if displayInfo> +
+
+ <#nested "info"> +
+
+
-
- <#else> - <#if properties.footerRow?has_content>
${kcSanitize(properties.footerRow)?no_esc}
- -
+
-
-
+ +
+
+ Terms of Use | + Cookies Policy | + Privacy Policy | + <#if properties.linkProject?has_content>${properties.descrProject!} +
+ <#if properties.ECLogo?has_content && properties.ECLogo='yes'> +
+ <#if properties.footerRow?has_content>
${kcSanitize(properties.footerRow)?no_esc}
+
+ + ${properties.ECLogoAlt!} + +
+
+ <#else> + <#if properties.footerRow?has_content>
${kcSanitize(properties.footerRow)?no_esc}
+ +
-
+
+
diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/terms.ftl b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/terms.ftl index c6c5ccd..cb1a212 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/terms.ftl +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/terms.ftl @@ -3,24 +3,27 @@ <#if section = "header"> ${msg("termsTitle")} <#elseif section = "form"> -
- <#include "terms.html" parse=false> -
-

${msg("termsAcceptMsg")}

-
- - -
-
- + } + diff --git a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/theme.properties b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/theme.properties index 88411ea..e5644c1 100644 --- a/keycloak-d4science-theme/src/main/resources/theme/d4science/login/theme.properties +++ b/keycloak-d4science-theme/src/main/resources/theme/d4science/login/theme.properties @@ -1,7 +1,9 @@ parent=keycloak import=common/keycloak -styles=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/zocial/zocial.css css/d4science.css +#styles=node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/zocial/zocial.css css/d4science.css +styles=css/login.css css/tile.css css/d4science.css +#stylesCommon=web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/pficon/pficon.css #titleTag= #favicon=https://services.d4science.org/favicon.ico @@ -31,4 +33,5 @@ ECLogoAlt=EU H2020 programme footerRow=Project has received funding from the European Union's Horizon programme ... -#kcLogoIdP-eosc-oidc= \ No newline at end of file +kcFormCardWideClass=card-d4s-wide +kcLogoIdP-eosc-oidc=fa fa-university diff --git a/keycloak-d4science-theme/util/keycloak-themes.json b/keycloak-d4science-theme/util/keycloak-themes.json new file mode 100644 index 0000000..5183f12 --- /dev/null +++ b/keycloak-d4science-theme/util/keycloak-themes.json @@ -0,0 +1,6 @@ +{ + "themes": [{ + "name" : "${theme-name}", + "types": [ "login" ] + }] +} \ No newline at end of file diff --git a/keycloak-d4science-theme/util/xquery/create-client.xq b/keycloak-d4science-theme/util/xquery/create-client.xq new file mode 100644 index 0000000..d866512 --- /dev/null +++ b/keycloak-d4science-theme/util/xquery/create-client.xq @@ -0,0 +1,33 @@ + +import module namespace c = 'urn:nubisware:keycloak:clients' at 'keycloak-clients.xqm'; +import module namespace gw = 'urn:nubisware:d4science:gateways' at 'd4s-gateways.xqm'; + + +declare function local:build-clientId($gateway) { + replace($gateway, 'https://', '') +}; + +declare function local:build-client-def($gateway) { + let $clientId := local:build-clientId($gateway) + let $baseUrl := $gateway || '/' + let $redirectUri := $gateway || '/*' + let $login_theme := "d4science" + return map { + "clientId" : $clientId, + "baseUrl" : $baseUrl, + "redirectUri" : $redirectUri, + "login_theme" : $login_theme + } +}; + +let $dummy := '' + +(: delete all clients + gw:list() ! c:delete-client(local:build-clientId(.)) +:) + +(: add all clients + gw:list() ! c:create-client(local:build-client-def(.)) +:) + +return gw:list() ! c:create-client(local:build-client-def(.)) \ No newline at end of file diff --git a/keycloak-d4science-theme/util/xquery/d4s-gateways.xqm b/keycloak-d4science-theme/util/xquery/d4s-gateways.xqm new file mode 100644 index 0000000..3506702 --- /dev/null +++ b/keycloak-d4science-theme/util/xquery/d4s-gateways.xqm @@ -0,0 +1,10 @@ +module namespace gw = 'urn:nubisware:d4science:gateways'; + +declare variable $gw:url := 'https://services.d4science.org/thematic-gateways'; + + +declare function gw:list() { + let $req := + return http:send-request($req, $gw:url)//a[@class='entry-link']/@href/data() ! replace(., '/explore', '') +}; + diff --git a/keycloak-d4science-theme/util/xquery/gateways.xq b/keycloak-d4science-theme/util/xquery/gateways.xq new file mode 100644 index 0000000..5f63503 --- /dev/null +++ b/keycloak-d4science-theme/util/xquery/gateways.xq @@ -0,0 +1,2 @@ + +http:send-request()//a[@class='entry-link']/@href/data() ! replace(., '/explore', '') \ No newline at end of file diff --git a/keycloak-d4science-theme/util/xquery/keycloak-clients.xqm b/keycloak-d4science-theme/util/xquery/keycloak-clients.xqm new file mode 100644 index 0000000..2c6ddb7 --- /dev/null +++ b/keycloak-d4science-theme/util/xquery/keycloak-clients.xqm @@ -0,0 +1,91 @@ +module namespace c = 'urn:nubisware:keycloak:clients'; + +declare variable $c:keycloak-url := 'http://localhost:8080'; +declare variable $c:token-url := '/auth/realms/master/protocol/openid-connect/token'; +declare variable $c:clients-url := '/auth/admin/realms/test/clients'; + + +declare function c:get-token() { + let $url := $c:keycloak-url || $c:token-url + + let $form-params := map { + 'grant_type': 'password', + 'client_id': 'admin-cli', + 'username': 'admin', + 'password': 'admin' + } + + let $body := substring(web:create-url('', $form-params), 2) + + let $http-req := + + + + + let $token := http:send-request($http-req, $url, $body)[2]//access__token/data() + return $token +}; + + +declare function c:create-client($params) { + let $client-def := ``[ + { + "clientId": "`{$params?clientId}`", + "baseUrl": "`{$params?baseUrl}`", + "enabled": true, + "clientAuthenticatorType": "client-secret", + "redirectUris": [ + "`{$params?redirectUri}`" + ], + "webOrigins": [ + "/*" + ], + "standardFlowEnabled": true, + "directAccessGrantsEnabled": true, + "publicClient": true, + "protocol": "openid-connect", + "attributes": { + "login_theme": "`{$params?login_theme}`" + }, + "fullScopeAllowed": true + } + ]`` + + let $token := c:get-token() + let $url := $c:keycloak-url || $c:clients-url + + let $http-req := + + + + + + let $resp := http:send-request($http-req, $url, $client-def) + + return $params?clientId || ' : ' || $resp[1]/@status/data() || ' ' || $resp[1]/@message/data() +}; + + +declare function c:delete-client($clientId) { + let $token := c:get-token() + let $url := $c:keycloak-url || $c:clients-url + + let $geturl := web:create-url($url, map { "clientId" : $clientId }) + let $http-req := + + + + + let $resp := http:send-request($http-req, $geturl) + let $id := $resp[2]/json/_[clientId=$clientId]/id/data() + + return if ($id) then + let $delurl := $url || '/' || $id + let $http-req := + + + + + let $resp := http:send-request($http-req, $delurl) + return $clientId || ' : ' || $resp[1]/@status/data() || ' ' || $resp[1]/@message/data() +}; \ No newline at end of file diff --git a/keycloak-d4science-theme/util/xquery/scraper.xq b/keycloak-d4science-theme/util/xquery/scraper.xq new file mode 100644 index 0000000..f170133 --- /dev/null +++ b/keycloak-d4science-theme/util/xquery/scraper.xq @@ -0,0 +1,94 @@ + +declare variable $url := 'https://services.d4science.org/thematic-gateways'; + + +declare function local:list() { + let $req := + return http:send-request($req, $url)//a[@class='entry-link']/@href/data() ! replace(., '/explore', '') +}; + +declare %basex:inline function local:get-title($page){ + substring-before($page/html/head/title, " -") +}; + +declare %basex:inline function local:get-favicon-url($page){ + $page/html/head/link[@rel="Shortcut Icon"]/string(@href) +}; + +declare %basex:inline function local:get-background-image($page){ + () +}; + +declare %basex:inline function local:get-logo-url($page){ + $page/html/body//h1[contains(@class, "site-title")]//img/string(@src) +}; + +declare %basex:inline function local:get-logo-alt($page){ + $page/html/body//h1[contains(@class, "site-title")]//img/string(@alt) +}; + +declare %basex:inline function local:get-infrastructure-logo($page){ + if(exists($page/html/body//div[contains(@class,"poweredBy-link")]//img)) then "yes" else "no" +}; + +declare %basex:inline function local:get-terms-url($page){ + $page/html/body//footer//a[@href = "/terms-of-use"]/@href +}; + +declare %basex:inline function local:get-cookiepolicy-url($page){ + $page/html/body//footer//a[@href = "/cookie-policy"]/@href +}; + +declare %basex:inline function local:get-privacypolicy-url($page){ + $page/html/body//footer//a[text() = "Privacy Policy"]/@href +}; + +declare %basex:inline function local:get-project-url($page){ + $page/html/body//footer/div[contains(@class, "custom-footer-container")]/a[last()]/@href +}; + +declare %basex:inline function local:get-project-description($page){ + () +}; + +declare %basex:inline function local:get-ec-logo($page){ + if(exists( + $page/html/body//footer/div[not(contains(@class, "custom-footer-container"))]/a[@href = "http://ec.europa.eu/programmes/horizon2020/"])) + then "yes" else "no" +}; + +declare %basex:inline function local:get-footer($page){ + string-join($page/html/body/footer/div/div//text() ! replace(., "'", """), "
") +}; + +declare %basex:inline function local:to-ansible-call($params as map(*)){ + string-join(("./keycloak-action.sh inject-theme", + map:keys($params) ! ("-e '" || . || "="" || $params(.) || ""'") + ), " ") +}; + +declare %basex:inline function local:transform($page-addr){ + let $page := html:parse(fetch:text($page-addr)) + return + local:to-ansible-call( + map{ + "theme" : substring-after($page-addr, "://"), + "title_tag" : local:get-title($page), + "favicon_url" : local:get-favicon-url($page), + "logo_url" : $page-addr || local:get-logo-url($page), + "logo_alt" : local:get-logo-alt($page), + "infrastructure_logo" : local:get-infrastructure-logo($page), + "background_image" : local:get-background-image($page), + "terms_url" : $page-addr || local:get-terms-url($page), + "cookie_policy_url" : $page-addr || local:get-cookiepolicy-url($page), + "privacy_policy_url" : "" || local:get-privacypolicy-url($page), + "project_url" : "" || local:get-project-url($page), + "project_description" : local:get-project-description($page), + "EC_logo" : local:get-ec-logo($page), + "footer" : local:get-footer($page) + } + ) +}; + +for $page-addr in local:list() +return xquery:fork-join(function(){ local:transform($page-addr)})