From 0844aa8d682415b1f9723d69576f8b1ea45b9f64 Mon Sep 17 00:00:00 2001 From: "ssilvert@win.redhat.com" Date: Mon, 23 Jul 2018 16:04:59 -0400 Subject: [PATCH] KEYCLOAK-7857: Fix notifications --- .../idm/ErrorRepresentation.java | 9 + .../org/keycloak/services/ErrorResponse.java | 5 + .../account/AccountCredentialResource.java | 10 +- .../resources/account/AccountRestService.java | 11 +- .../services/resources/account/Errors.java | 30 --- .../account/AccountRestServiceTest.java | 7 +- .../app/account-service/account.service.ts | 14 +- .../account/resources/app/app.module.ts | 21 +- .../password-page.component.html | 8 +- .../password-page/password-page.component.ts | 25 +- .../app/ngx-translate/translate.util.ts | 10 +- .../inline-notification.component.css | 29 +++ .../resources/app/page/side-nav-item.ts | 33 --- .../app/side-nav/side-nav.component.css | 0 .../app/side-nav/side-nav.component.html | 71 ------ .../app/side-nav/side-nav.component.ts | 115 ---------- .../app/top-nav/notification.component.css | 0 .../app/top-nav/notification.component.html | 12 - .../app/top-nav/notification.component.ts | 43 ---- .../resources/app/top-nav/toast.notifier.ts | 75 ------ .../app/top-nav/top-nav.component.css | 0 .../app/top-nav/top-nav.component.html | 42 ---- .../app/top-nav/top-nav.component.ts | 69 ------ .../vertical-nav/vertical-nav.component.html | 3 +- .../vertical-nav/vertical-nav.component.ts | 214 +++++++++--------- .../account/resources/systemjs.config.js | 7 +- 26 files changed, 224 insertions(+), 639 deletions(-) delete mode 100644 services/src/main/java/org/keycloak/services/resources/account/Errors.java create mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/notification/inline-notification.component.css delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/page/side-nav-item.ts delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.css delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.html delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.ts delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.css delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.html delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.ts delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/toast.notifier.ts delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.css delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html delete mode 100644 themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.ts diff --git a/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java index f9c9071a39..64f3484889 100644 --- a/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/ErrorRepresentation.java @@ -22,6 +22,7 @@ package org.keycloak.representations.idm; */ public class ErrorRepresentation { private String errorMessage; + private Object[] params; public ErrorRepresentation() { } @@ -33,4 +34,12 @@ public class ErrorRepresentation { public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } + + public Object[] getParams() { + return this.params; + } + + public void setParams(Object[] params) { + this.params = params; + } } diff --git a/services/src/main/java/org/keycloak/services/ErrorResponse.java b/services/src/main/java/org/keycloak/services/ErrorResponse.java index cb1ad54c1f..492541fe23 100755 --- a/services/src/main/java/org/keycloak/services/ErrorResponse.java +++ b/services/src/main/java/org/keycloak/services/ErrorResponse.java @@ -32,8 +32,13 @@ public class ErrorResponse { } public static Response error(String message, Response.Status status) { + return ErrorResponse.error(message, null, status); + } + + public static Response error(String message, Object[] params, Response.Status status) { ErrorRepresentation error = new ErrorRepresentation(); error.setErrorMessage(message); + error.setParams(params); return Response.status(status).entity(error).type(MediaType.APPLICATION_JSON).build(); } diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java b/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java index 3af53c5771..125de8b481 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountCredentialResource.java @@ -20,6 +20,8 @@ import javax.ws.rs.POST; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.Response; +import org.keycloak.models.ModelException; +import org.keycloak.services.messages.Messages; public class AccountCredentialResource { @@ -62,10 +64,14 @@ public class AccountCredentialResource { UserCredentialModel cred = UserCredentialModel.password(update.getCurrentPassword()); if (!session.userCredentialManager().isValid(realm, user, cred)) { event.error(org.keycloak.events.Errors.INVALID_USER_CREDENTIALS); - return ErrorResponse.error(Errors.INVALID_CREDENTIALS, Response.Status.BAD_REQUEST); + return ErrorResponse.error(Messages.INVALID_PASSWORD_EXISTING, Response.Status.BAD_REQUEST); } - session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(update.getNewPassword(), false)); + try { + session.userCredentialManager().updateCredential(realm, user, UserCredentialModel.password(update.getNewPassword(), false)); + } catch (ModelException e) { + return ErrorResponse.error(e.getMessage(), e.getParameters(), Response.Status.BAD_REQUEST); + } return Response.ok().build(); } diff --git a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java index 32d88de642..48e55eec0e 100755 --- a/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/AccountRestService.java @@ -54,6 +54,7 @@ import javax.ws.rs.core.UriInfo; import java.util.LinkedList; import java.util.List; import java.util.Map; +import org.keycloak.services.messages.Messages; /** * @author Stian Thorgersen @@ -146,27 +147,27 @@ public class AccountRestService { if (usernameChanged) { UserModel existing = session.users().getUserByUsername(userRep.getUsername(), realm); if (existing != null) { - return ErrorResponse.exists(Errors.USERNAME_EXISTS); + return ErrorResponse.exists(Messages.USERNAME_EXISTS); } user.setUsername(userRep.getUsername()); } } else if (usernameChanged) { - return ErrorResponse.error(Errors.READ_ONLY_USERNAME, Response.Status.BAD_REQUEST); + return ErrorResponse.error(Messages.READ_ONLY_USERNAME, Response.Status.BAD_REQUEST); } boolean emailChanged = userRep.getEmail() != null && !userRep.getEmail().equals(user.getEmail()); if (emailChanged && !realm.isDuplicateEmailsAllowed()) { UserModel existing = session.users().getUserByEmail(userRep.getEmail(), realm); if (existing != null) { - return ErrorResponse.exists(Errors.EMAIL_EXISTS); + return ErrorResponse.exists(Messages.EMAIL_EXISTS); } } if (realm.isRegistrationEmailAsUsername() && !realm.isDuplicateEmailsAllowed()) { UserModel existing = session.users().getUserByUsername(userRep.getEmail(), realm); if (existing != null) { - return ErrorResponse.exists(Errors.USERNAME_EXISTS); + return ErrorResponse.exists(Messages.USERNAME_EXISTS); } } @@ -200,7 +201,7 @@ public class AccountRestService { return Cors.add(request, Response.ok()).auth().allowedOrigins(auth.getToken()).build(); } catch (ReadOnlyException e) { - return ErrorResponse.error(Errors.READ_ONLY_USER, Response.Status.BAD_REQUEST); + return ErrorResponse.error(Messages.READ_ONLY_USER, Response.Status.BAD_REQUEST); } } diff --git a/services/src/main/java/org/keycloak/services/resources/account/Errors.java b/services/src/main/java/org/keycloak/services/resources/account/Errors.java deleted file mode 100644 index ad890b1838..0000000000 --- a/services/src/main/java/org/keycloak/services/resources/account/Errors.java +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2016 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.keycloak.services.resources.account; - -/** - * @author Stian Thorgersen - */ -public class Errors { - - public static final String USERNAME_EXISTS = "username_exists"; - public static final String EMAIL_EXISTS = "email_exists"; - public static final String READ_ONLY_USER = "user_read_only"; - public static final String READ_ONLY_USERNAME = "username_read_only"; - public static final String INVALID_CREDENTIALS = "invalid_credentials"; - -} diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java index 6dbade1fd2..52ffe3e577 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/AccountRestServiceTest.java @@ -41,6 +41,7 @@ import java.util.List; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.junit.Assert.*; +import org.keycloak.services.messages.Messages; /** * @author Stian Thorgersen @@ -119,7 +120,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { assertEquals("bobby@localhost", user.getEmail()); user.setEmail("john-doh@localhost"); - updateError(user, 409, "email_exists"); + updateError(user, 409, Messages.EMAIL_EXISTS); user.setEmail("test-user@localhost"); user = updateAndGet(user); @@ -131,7 +132,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { assertEquals("updatedusername", user.getUsername()); user.setUsername("john-doh@localhost"); - updateError(user, 409, "username_exists"); + updateError(user, 409, Messages.USERNAME_EXISTS); user.setUsername("test-user@localhost"); user = updateAndGet(user); @@ -142,7 +143,7 @@ public class AccountRestServiceTest extends AbstractTestRealmKeycloakTest { adminClient.realm("test").update(realmRep); user.setUsername("updatedUsername2"); - updateError(user, 400, "username_read_only"); + updateError(user, 400, Messages.READ_ONLY_USERNAME); } private UserRepresentation updateAndGet(UserRepresentation user) throws IOException { diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts index 860408d981..f13d921706 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/account-service/account.service.ts @@ -18,8 +18,10 @@ import {Injectable} from '@angular/core'; import {Http, Response, RequestOptionsArgs} from '@angular/http'; -import {ToastNotifier, ToastNotification} from '../top-nav/toast.notifier'; +import {KeycloakNotificationService} from '../notification/keycloak-notification.service'; import {KeycloakService} from '../keycloak-service/keycloak.service'; + +import {NotificationType} from 'patternfly-ng/notification'; /** * @@ -31,8 +33,8 @@ export class AccountServiceClient { private accountUrl: string; constructor(protected http: Http, - protected kcSvc: KeycloakService, - protected notifier: ToastNotifier) { + protected kcSvc: KeycloakService, + protected kcNotifySvc: KeycloakNotificationService) { this.accountUrl = kcSvc.authServerUrl() + 'realms/' + kcSvc.realm() + '/account'; } @@ -56,7 +58,7 @@ export class AccountServiceClient { private handleAccountUpdated(responseHandler: Function, res: Response, successMessage?: string) { let message: string = "Your account has been updated."; if (successMessage) message = successMessage; - this.notifier.emit(new ToastNotification(message, "success")); + this.kcNotifySvc.notify(message, NotificationType.SUCCESS); responseHandler(res); } @@ -102,8 +104,8 @@ export class AccountServiceClient { if (not500Error && response.json().hasOwnProperty('error_description')) { message = response.json().error_description; } - - this.notifier.emit(new ToastNotification(message, "error")); + + this.kcNotifySvc.notify(message, NotificationType.DANGER, response.json().params); } } diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts index 82a2eec6a9..ce17228898 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/app.module.ts @@ -28,30 +28,28 @@ import { KeycloakService } from './keycloak-service/keycloak.service'; import { KEYCLOAK_HTTP_PROVIDER } from './keycloak-service/keycloak.http'; import {KeycloakGuard} from './keycloak-service/keycloak.guard'; -import {ResponsivenessService} from './responsiveness-service/responsiveness.service' +import {ResponsivenessService} from './responsiveness-service/responsiveness.service'; +import {KeycloakNotificationService} from './notification/keycloak-notification.service'; import { AccountServiceClient } from './account-service/account.service'; import {TranslateUtil} from './ngx-translate/translate.util'; import { DeclaredVarTranslateLoader } from './ngx-translate/declared.var.translate.loader'; import { AppComponent } from './app.component'; -import { TopNavComponent } from './top-nav/top-nav.component'; -import { NotificationComponent } from './top-nav/notification.component'; -import { ToastNotifier } from './top-nav/toast.notifier'; -import { SideNavComponent } from './side-nav/side-nav.component'; import {VerticalNavComponent} from './vertical-nav/vertical-nav.component'; +import {InlineNotification} from './notification/inline-notification-component'; + +import { VerticalNavigationModule } from 'patternfly-ng/navigation'; +import {InlineNotificationModule} from 'patternfly-ng/notification/inline-notification'; -import { NavigationModule } from 'patternfly-ng/navigation'; /* Routing Module */ import { AppRoutingModule } from './app-routing.module'; const decs = [ AppComponent, - TopNavComponent, - NotificationComponent, - SideNavComponent, VerticalNavComponent, + InlineNotification, ]; export const ORIGINAL_INCOMING_URL: Location = window.location; @@ -62,7 +60,8 @@ export const ORIGINAL_INCOMING_URL: Location = window.location; BrowserModule, FormsModule, HttpModule, - NavigationModule, + VerticalNavigationModule, + InlineNotificationModule, TranslateModule.forRoot({ loader: {provide: TranslateLoader, useClass: DeclaredVarTranslateLoader} }), @@ -73,9 +72,9 @@ export const ORIGINAL_INCOMING_URL: Location = window.location; KeycloakGuard, KEYCLOAK_HTTP_PROVIDER, ResponsivenessService, + KeycloakNotificationService, AccountServiceClient, TranslateUtil, - ToastNotifier, { provide: LocationStrategy, useClass: HashLocationStrategy } ], bootstrap: [AppComponent] diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.html index c10d780836..399c2c17d7 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.html +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.html @@ -27,23 +27,23 @@
* - +
* - +
* - +
- +
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.ts index 177298f7cf..dc2684b5df 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/content/password-page/password-page.component.ts @@ -18,7 +18,10 @@ import {Component, OnInit, ViewChild, Renderer2} from '@angular/core'; import {Response} from '@angular/http'; import {FormGroup} from '@angular/forms'; +import {NotificationType} from 'patternfly-ng/notification'; + import {AccountServiceClient} from '../../account-service/account.service'; +import {KeycloakNotificationService} from '../../notification/keycloak-notification.service'; @Component({ selector: 'app-password-page', @@ -30,18 +33,34 @@ export class PasswordPageComponent implements OnInit { @ViewChild('formGroup') private formGroup: FormGroup; private lastPasswordUpdate: number; - constructor(private accountSvc: AccountServiceClient, private renderer: Renderer2) { + constructor(private accountSvc: AccountServiceClient, + private renderer: Renderer2, + protected kcNotifySvc: KeycloakNotificationService,) { this.accountSvc.doGetRequest("/credentials/password", (res: Response) => this.handleGetResponse(res)); } public changePassword() { console.log("posting: " + JSON.stringify(this.formGroup.value)); + if (!this.confirmationMatches()) return; this.accountSvc.doPostRequest("/credentials/password", (res: Response) => this.handlePostResponse(res), this.formGroup.value); this.renderer.selectRootElement('#password').focus(); } + private confirmationMatches(): boolean { + const newPassword: string = this.formGroup.value['newPassword']; + const confirmation: string = this.formGroup.value['confirmation']; + + const matches: boolean = newPassword === confirmation; + + if (!matches) { + this.kcNotifySvc.notify('notMatchPasswordMessage', NotificationType.DANGER) + } + + return matches; + } + protected handlePostResponse(res: Response) { - console.log('**** response from account POST ***'); + console.log('**** response from password POST ***'); console.log(JSON.stringify(res)); console.log('***************************************'); this.formGroup.reset(); @@ -49,7 +68,7 @@ export class PasswordPageComponent implements OnInit { } protected handleGetResponse(res: Response) { - console.log('**** response from account POST ***'); + console.log('**** response from password GET ***'); console.log(JSON.stringify(res)); console.log('***************************************'); this.lastPasswordUpdate = res.json()['lastUpdate']; diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/ngx-translate/translate.util.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/ngx-translate/translate.util.ts index 947c2a3bad..ad833c2437 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/ngx-translate/translate.util.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/ngx-translate/translate.util.ts @@ -27,13 +27,19 @@ export class TranslateUtil { constructor(private translator: TranslateService) { } - public translate(key: string) : string { + public translate(key: string, params?: Array): string { // remove Freemarker syntax if (key.startsWith('${') && key.endsWith('}')) { key = key.substring(2, key.length - 1); } - return this.translator.instant(key); + const ngTranslateParams = {}; + for (let i in params) { + let paramName: string = 'param_' + i; + ngTranslateParams[paramName] = params[i]; + } + + return this.translator.instant(key, ngTranslateParams); } } diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/notification/inline-notification.component.css b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/notification/inline-notification.component.css new file mode 100644 index 0000000000..18a47652f5 --- /dev/null +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/notification/inline-notification.component.css @@ -0,0 +1,29 @@ +.faux-layout { + position: fixed; + top: 37px; + bottom: 0; + left: 0; + right: 0; + background-color: #f5f5f5; + padding-top: 15px; + z-index: 1100; +} +.example-page-container.container-fluid { + position: fixed; + top: 37px; + bottom: 0; + left: 0; + right: 0; + background-color: #f5f5f5; + padding-top: 15px; +} + +.hide-vertical-nav { + margin-top: 15px; + margin-left: 30px; +} + +.navbar-brand-txt { + line-height: 34px; +} + diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/page/side-nav-item.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/page/side-nav-item.ts deleted file mode 100644 index 3cfdb3b2ca..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/page/side-nav-item.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Icon} from "./icon"; - -export type Active = "active" | ""; - -export class SideNavItem { - - constructor(public displayName: string, - public link: string, - public tooltip?: string, - public icon?: Icon, - public active?: Active) { - } - - setActive(active: Active) { - this.active = active; - } -} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.css b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.html deleted file mode 100644 index 616817e24f..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.html +++ /dev/null @@ -1,71 +0,0 @@ - - - diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.ts deleted file mode 100644 index 3ccca2eb70..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/side-nav/side-nav.component.ts +++ /dev/null @@ -1,115 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import {Component, OnInit, HostListener} from '@angular/core'; -import {Router, NavigationEnd} from '@angular/router'; -import {KeycloakService} from '../keycloak-service/keycloak.service'; -import {TranslateUtil} from '../ngx-translate/translate.util'; -import {SideNavItem, Active} from '../page/side-nav-item'; -import {Icon} from '../page/icon'; -import {ResponsivenessService, SideNavClasses, MenuClickListener} from "../responsiveness-service/responsiveness.service"; -import {Media} from "../responsiveness-service/media"; -import {Referrer} from "../page/referrer"; - -declare const baseUrl: string; - -@Component({ - selector: 'app-side-nav', - templateUrl: './side-nav.component.html', - styleUrls: ['./side-nav.component.css'] -}) -export class SideNavComponent implements OnInit, MenuClickListener { - - private referrer: Referrer; - private sideNavClasses: SideNavClasses = this.respSvc.calcSideNavWidthClasses(); - private isFirstRouterEvent: boolean = true; - - public navItems: SideNavItem[]; - - constructor(private router: Router, - private translateUtil: TranslateUtil, - private respSvc: ResponsivenessService, - private keycloakService: KeycloakService) { - this.referrer = new Referrer(translateUtil); - this.navItems = [ - this.makeSideNavItem("account", new Icon("pficon", "user"), "active"), - this.makeSideNavItem("password", new Icon("pficon", "key")), - this.makeSideNavItem("authenticator", new Icon("pficon", "cloud-security")), - this.makeSideNavItem("device-activity", new Icon("fa", "desktop")), - this.makeSideNavItem("sessions", new Icon("fa", "clock-o")), - this.makeSideNavItem("applications", new Icon("fa", "th")) - ]; - - this.router.events.subscribe(value => { - if (value instanceof NavigationEnd) { - const navEnd = value as NavigationEnd; - this.setActive(navEnd.url); - - const media: Media = new Media(); - if (media.isSmall() && !this.isFirstRouterEvent) { - this.respSvc.menuClicked(); - } - - this.isFirstRouterEvent = false; - } - }); - - this.respSvc.addMenuClickListener(this); - - // direct navigation such as '#/password' - this.setActive(window.location.hash.substring(1)); - } - - // use itemName for translate key, link, and tooltip - private makeSideNavItem(itemName: string, icon: Icon, active?: Active): SideNavItem { - const localizedName: string = this.translateUtil.translate(itemName); - - return new SideNavItem(localizedName, itemName, localizedName, icon, active); - } - - private logout() { - this.keycloakService.logout(baseUrl); - } - - public menuClicked(): void { - this.sideNavClasses = this.respSvc.calcSideNavWidthClasses(); - } - - @HostListener('window:resize', ['$event']) - private onResize(event: any) { - this.sideNavClasses = this.respSvc.calcSideNavWidthClasses(); - } - - setActive(url: string) { - for (let navItem of this.navItems) { - if (("/" + navItem.link) === url) { - navItem.setActive("active"); - } else { - navItem.setActive(""); - } - } - - if ("/" === url) { - this.navItems[0].setActive("active"); - } - } - - ngOnInit() { - - } - -} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.css b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.html deleted file mode 100644 index 7b71d119ea..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.html +++ /dev/null @@ -1,12 +0,0 @@ -
- - - {{notification.message}} -
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.ts deleted file mode 100644 index be57d83ce9..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/notification.component.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Component} from '@angular/core'; - -import {ToastNotifier, ToastNotification} from './toast.notifier'; - -@Component({ - selector: 'notification', - templateUrl: './notification.component.html', - styleUrls: ['./notification.component.css'] -}) -export class NotificationComponent { - private isVisible: boolean = false; - private notification: ToastNotification = new ToastNotification(""); - - constructor(toastNotifier: ToastNotifier) { - - toastNotifier.subscribe((notification: ToastNotification) => { - this.notification = notification; - this.isVisible = true; - setTimeout(() => { - this.isVisible = false; - }, 8000); - }) - - } -} - - diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/toast.notifier.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/toast.notifier.ts deleted file mode 100644 index ea275ec3f3..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/toast.notifier.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2017 Red Hat Inc. and/or its affiliates and other contributors - * as indicated by the @author tags. All rights reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ - - import {Injectable, EventEmitter} from '@angular/core'; - - /** - * - * @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc. - */ -@Injectable() -export class ToastNotifier extends EventEmitter { - constructor() { - super(); - } -} - -type ToastIcon = "pficon-ok" | - "pficon-info" | - "pficon-warning-triangle-o" | - "pficon-error-circle-o"; - -type ToastAlertType = "alert-success" | - "alert-info" | - "alert-warning" | - "alert-danger"; - -export type MessageType = "success" | - "info" | - "warning" | - "error"; - -export class ToastNotification { - public alertType: ToastAlertType = "alert-success"; - public icon: ToastIcon = "pficon-ok"; - - constructor(public message: string, messageType?: MessageType) { - switch (messageType) { - case "info": { - this.alertType = "alert-info"; - this.icon = "pficon-info"; - break; - } - case "warning": { - this.alertType = "alert-warning"; - this.icon = "pficon-warning-triangle-o"; - break; - } - case "error": { - this.alertType = "alert-danger"; - this.icon = "pficon-error-circle-o"; - break; - } - default: { - this.alertType = "alert-success"; - this.icon = "pficon-ok"; - } - } - } -} - - diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.css b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.css deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html deleted file mode 100644 index ba256f483b..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.html +++ /dev/null @@ -1,42 +0,0 @@ - - - diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.ts deleted file mode 100644 index 4458aa3e38..0000000000 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/top-nav/top-nav.component.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2017 Red Hat, Inc. and/or its affiliates - * and other contributors as indicated by the @author tags. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -import {Component, OnInit, Input} from '@angular/core'; -import {TranslateUtil} from '../ngx-translate/translate.util'; -import {KeycloakService} from '../keycloak-service/keycloak.service'; -import {ResponsivenessService} from "../responsiveness-service/responsiveness.service"; -import {Features} from '../page/features'; -import {Referrer} from "../page/referrer"; - -declare const resourceUrl: string; -declare const baseUrl: string; -declare const referrer: string; -declare const referrer_uri: string; -declare const features: Features; -declare const availableLocales: Array; - -@Component({ - selector: 'app-top-nav', - templateUrl: './top-nav.component.html', - styleUrls: ['./top-nav.component.css'] -}) -export class TopNavComponent implements OnInit { - @Input() showSideNav: String; - - public resourceUrl: string = resourceUrl; - public availableLocales: Array = availableLocales; - - private referrer: Referrer; - - constructor(private keycloakService: KeycloakService, - translateUtil: TranslateUtil, - private respSvc: ResponsivenessService) { - this.referrer = new Referrer(translateUtil); - } - - private menuClicked(): void { - this.respSvc.menuClicked(); - } - - ngOnInit() { - } - - private logout() { - this.keycloakService.logout(baseUrl); - } - - private showLocales(): boolean { - return features.isInternationalizationEnabled && (this.availableLocales.length > 1); - } - - private changeLocale(newLocale: string) { - this.keycloakService.login({kcLocale: newLocale }); - } - -} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.html b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.html index e11d9605bc..6bc27d136f 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.html +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.html @@ -34,12 +34,11 @@ - -
+
diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.ts b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.ts index 3ff0d5be53..62a23f263a 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.ts +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/app/vertical-nav/vertical-nav.component.ts @@ -1,107 +1,107 @@ -import {Component, OnInit, ViewEncapsulation, ViewChild} from '@angular/core'; -import {NavigationItemConfig, VerticalNavigationComponent} from 'patternfly-ng/navigation'; -import {TranslateUtil} from '../ngx-translate/translate.util'; -import {KeycloakService} from '../keycloak-service/keycloak.service'; -import {Features} from '../page/features'; -import {Referrer} from "../page/referrer"; - -declare const baseUrl: string; -declare const resourceUrl: string; -declare const features: Features; -declare const availableLocales: Array; - -@Component({ - encapsulation: ViewEncapsulation.None, - selector: 'vertical-nav', - styleUrls: ['./vertical-nav.component.css'], - templateUrl: './vertical-nav.component.html' -}) -export class VerticalNavComponent implements OnInit { - @ViewChild('pfVerticalNav') pfVerticalNav: VerticalNavigationComponent; - - public resourceUrl: string = resourceUrl; - public availableLocales: Array = availableLocales; - - private referrer: Referrer; - - navigationItems: NavigationItemConfig[]; - - constructor(private keycloakService: KeycloakService, - private translateUtil: TranslateUtil, ) { - this.referrer = new Referrer(translateUtil); - } - - ngOnInit(): void { - this.navigationItems = [ - { - title: this.translateUtil.translate('personalInfoHtmlTitle'), - iconStyleClass: 'fa fa-user-circle', - url: 'account', - mobileItem: false - }, - { - title: this.translateUtil.translate('accountSecurityTitle'), - iconStyleClass: 'fa fa-shield', - children: this.makeSecurityChildren(), - }, - { - title: this.translateUtil.translate('applicationsHtmlTitle'), - iconStyleClass: 'fa fa-th', - url: 'applications', - } - ]; - - if (features.isMyResourcesEnabled) { - this.navigationItems.push( - { - title: this.translateUtil.translate('myResources'), - iconStyleClass: 'fa fa-file-o', - url: 'my-resources', - } - ); - } - } - - private makeSecurityChildren(): Array { - const children: Array = [ - { - title: this.translateUtil.translate('changePasswordHtmlTitle'), - iconStyleClass: 'fa fa-shield', - url: 'password', - }, - { - title: this.translateUtil.translate('authenticatorTitle'), - iconStyleClass: 'fa fa-shield', - url: 'authenticator', - }, - { - title: this.translateUtil.translate('device-activity'), - iconStyleClass: 'fa fa-shield', - url: 'device-activity', - } - ]; - - if (features.isLinkedAccountsEnabled) { - children.push({ - title: this.translateUtil.translate('linkedAccountsHtmlTitle'), - iconStyleClass: 'fa fa-shield', - url: 'linked-accounts', - }); - }; - - return children; - } - - private logout() { - this.keycloakService.logout(baseUrl); - } - - private isShowLocales(): boolean { - return features.isInternationalizationEnabled && (this.availableLocales.length > 1); - } - - private changeLocale(newLocale: string) { - this.keycloakService.login({kcLocale: newLocale}); - } - -} \ No newline at end of file +import {Component, OnInit, ViewEncapsulation, ViewChild} from '@angular/core'; +import {NavigationItemConfig, VerticalNavigationComponent} from 'patternfly-ng/navigation'; +import {TranslateUtil} from '../ngx-translate/translate.util'; +import {KeycloakService} from '../keycloak-service/keycloak.service'; +import {Features} from '../page/features'; +import {Referrer} from "../page/referrer"; + +declare const baseUrl: string; +declare const resourceUrl: string; +declare const features: Features; +declare const availableLocales: Array; + +@Component({ + encapsulation: ViewEncapsulation.None, + selector: 'vertical-nav', + styleUrls: ['./vertical-nav.component.css'], + templateUrl: './vertical-nav.component.html' +}) +export class VerticalNavComponent implements OnInit { + @ViewChild('pfVerticalNav') pfVerticalNav: VerticalNavigationComponent; + + public resourceUrl: string = resourceUrl; + public availableLocales: Array = availableLocales; + + private referrer: Referrer; + + navigationItems: NavigationItemConfig[]; + + constructor(private keycloakService: KeycloakService, + private translateUtil: TranslateUtil ) { + this.referrer = new Referrer(translateUtil); + } + + ngOnInit(): void { + this.navigationItems = [ + { + title: this.translateUtil.translate('personalInfoHtmlTitle'), + iconStyleClass: 'fa fa-user-circle', + url: 'account', + mobileItem: false + }, + { + title: this.translateUtil.translate('accountSecurityTitle'), + iconStyleClass: 'fa fa-shield', + children: this.makeSecurityChildren(), + }, + { + title: this.translateUtil.translate('applicationsHtmlTitle'), + iconStyleClass: 'fa fa-th', + url: 'applications', + } + ]; + + if (features.isMyResourcesEnabled) { + this.navigationItems.push( + { + title: this.translateUtil.translate('myResources'), + iconStyleClass: 'fa fa-file-o', + url: 'my-resources', + } + ); + } + } + + private makeSecurityChildren(): Array { + const children: Array = [ + { + title: this.translateUtil.translate('changePasswordHtmlTitle'), + iconStyleClass: 'fa fa-shield', + url: 'password', + }, + { + title: this.translateUtil.translate('authenticatorTitle'), + iconStyleClass: 'fa fa-shield', + url: 'authenticator', + }, + { + title: this.translateUtil.translate('device-activity'), + iconStyleClass: 'fa fa-shield', + url: 'device-activity', + } + ]; + + if (features.isLinkedAccountsEnabled) { + children.push({ + title: this.translateUtil.translate('linkedAccountsHtmlTitle'), + iconStyleClass: 'fa fa-shield', + url: 'linked-accounts', + }); + }; + + return children; + } + + private logout() { + this.keycloakService.logout(baseUrl); + } + + private isShowLocales(): boolean { + return features.isInternationalizationEnabled && (this.availableLocales.length > 1); + } + + private changeLocale(newLocale: string) { + this.keycloakService.login({kcLocale: newLocale}); + } + +} diff --git a/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js b/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js index 60683b9f0a..4b0ae949a4 100644 --- a/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js +++ b/themes/src/main/resources/theme/keycloak-preview/account/resources/systemjs.config.js @@ -31,6 +31,9 @@ // patternfly-ng 'patternfly-ng/navigation': 'npm:patternfly-ng/bundles/patternfly-ng.umd.min.js', 'patternfly-ng/utilities': 'npm:patternfly-ng/bundles/patternfly-ng.umd.min.js', + 'patternfly-ng/notification': 'npm:patternfly-ng/bundles/patternfly-ng.umd.min.js', + 'patternfly-ng/notification/inline-notification': 'npm:patternfly-ng/bundles/patternfly-ng.umd.min.js', + 'patternfly-ng/notification/notification-service': 'npm:patternfly-ng/bundles/patternfly-ng.umd.min.js', // unused patternfly-ng dependencies 'angular-tree-component': '@empty', @@ -44,10 +47,6 @@ 'ngx-bootstrap/dropdown': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', 'ngx-bootstrap/popover': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', 'ngx-bootstrap/tooltip': 'npm:ngx-bootstrap/bundles/ngx-bootstrap.umd.min.js', - - // patternfly-ng currently requires us to install transpiler. Need to get rid of this. - 'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js', - 'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js' }, bundles: {