KEYCLOAK-7857: Fix notifications
This commit is contained in:
parent
d73c4288ae
commit
0844aa8d68
26 changed files with 224 additions and 639 deletions
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
*/
|
||||
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";
|
||||
|
||||
}
|
|
@ -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 <a href="mailto:sthorger@redhat.com">Stian Thorgersen</a>
|
||||
|
@ -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 {
|
||||
|
|
|
@ -18,9 +18,11 @@
|
|||
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';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -103,7 +105,7 @@ export class AccountServiceClient {
|
|||
message = response.json().error_description;
|
||||
}
|
||||
|
||||
this.notifier.emit(new ToastNotification(message, "error"));
|
||||
this.kcNotifySvc.notify(message, NotificationType.DANGER, response.json().params);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -27,23 +27,23 @@
|
|||
<input readonly="" value="this is not a login form" style="display: none;" type="password">
|
||||
<div class="form-group">
|
||||
<label for="password" class="control-label">{{'currentPassword' | translate}}</label><span class="required">*</span>
|
||||
<input ngModel class="form-control" #password id="password" name="currentPassword" autofocus="" autocomplete="off" type="password">
|
||||
<input ngModel required class="form-control" #password id="password" name="currentPassword" autofocus="" autocomplete="off" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password-new" class="control-label">{{'passwordNew' | translate}}</label><span class="required">*</span>
|
||||
<input ngModel class="form-control" id="newPassword" name="newPassword" autocomplete="off" type="password">
|
||||
<input ngModel required class="form-control" id="newPassword" name="newPassword" autocomplete="off" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password-confirm" class="control-label">{{'passwordConfirm' | translate}}</label><span class="required">*</span>
|
||||
<input ngModel class="form-control" id="confirmation" name="confirmation" autocomplete="off" type="password">
|
||||
<input ngModel required class="form-control" id="confirmation" name="confirmation" autocomplete="off" type="password">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div id="kc-form-buttons" class="submit">
|
||||
<div class="">
|
||||
<button type="submit" class="btn btn-primary btn-lg" name="submitAction">{{'doSave' | translate}}</button>
|
||||
<button [disabled]="!formGroup.form.valid" type="submit" class="btn btn-primary btn-lg" name="submitAction">{{'doSave' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -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'];
|
||||
|
|
|
@ -27,13 +27,19 @@ export class TranslateUtil {
|
|||
constructor(private translator: TranslateService) {
|
||||
}
|
||||
|
||||
public translate(key: string) : string {
|
||||
public translate(key: string, params?: Array<any>): 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
<nav class="nav-pf-vertical {{this.sideNavClasses}}"> <!-- {{this.sideNavClasses}} collapsed hidden show-mobile-nav -->
|
||||
<ul class="list-group">
|
||||
<li *ngFor="let item of navItems" class="list-group-item {{item.active}}">
|
||||
<a [routerLink]="[item.link]">
|
||||
<span class="{{item.icon.getClasses()}}" title="{{item.tooltip}}" data-toggle="tooltip" data-placement="right"></span>
|
||||
<span class="list-group-item-value">{{item.displayName}}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li *ngIf="referrer.exists()" class="list-group-item hidden-sm hidden-md hidden-lg">
|
||||
<a href="{{referrer.getUri()}}">
|
||||
<span class="pficon-arrow" title="{{'backTo' | translate:referrer.getName()}}"></span>
|
||||
<span class="list-group-item-value">{{'backTo' | translate:referrer.getName()}}</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item hidden-sm hidden-md hidden-lg">
|
||||
<a href="#" (click)="logout()">
|
||||
<span class="fa fa-sign-out" title="{{'doSignOut' | translate}}"></span>
|
||||
<span class="list-group-item-value">{{'doSignOut' | translate}}</span>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
<!-- <li class="list-group-item active">
|
||||
<a href="#">
|
||||
<span class="pficon pficon-user" title="Dashboard" data-toggle="tooltip" data-placement="right"></span>
|
||||
<span class="list-group-item-value">Personal information</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="#">
|
||||
<span class="fa fa-link" title="My Services" data-toggle="tooltip" data-placement="right"></span>
|
||||
<span class="list-group-item-value">Connected Devices</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="#">
|
||||
<span class="fa fa-cubes" title="My Requests" data-toggle="tooltip" data-placement="right"></span>
|
||||
<span class="list-group-item-value">Applications</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item">
|
||||
<a href="#">
|
||||
<span class="pficon pficon-history" title="My Items" data-toggle="tooltip" data-placement="right"></span>
|
||||
<span class="list-group-item-value">History</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item hidden-sm hidden-md hidden">
|
||||
<a href="#">
|
||||
<span class="pficon pficon-help" title="Help"></span>
|
||||
<span class="list-group-item-value">Help</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item hidden-sm hidden-md hidden-lg">
|
||||
<a href="#">
|
||||
<span class="fa fa-info-circle" title="About"></span>
|
||||
<span class="list-group-item-value">About</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item hidden-sm hidden-md hidden-lg">
|
||||
<a href="#">
|
||||
<span class="pficon pficon-user" title="Preferences"></span>
|
||||
<span class="list-group-item-value">Preferences</span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="list-group-item hidden-sm hidden-md hidden-lg">
|
||||
<a href="#">
|
||||
<span class="fa fa-sign-out" title="Log Out"></span>
|
||||
<span class="list-group-item-value">Log Out</span>
|
||||
</a>
|
||||
</li>-->
|
||||
|
|
@ -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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
<div *ngIf="isVisible" class="toast-pf
|
||||
toast-pf-max-width
|
||||
toast-pf-top-right
|
||||
alert
|
||||
{{notification.alertType}}
|
||||
alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">
|
||||
<span class="pficon pficon-close"></span>
|
||||
</button>
|
||||
<span class="pficon {{notification.icon}}"></span>
|
||||
{{notification.message}}
|
||||
</div>
|
|
@ -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);
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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<ToastNotification> {
|
||||
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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,42 +0,0 @@
|
|||
<!-- Top Nav -->
|
||||
|
||||
<nav class="navbar navbar-pf-alt">
|
||||
<notification></notification>
|
||||
|
||||
<div class="navbar-header">
|
||||
<button *ngIf="keycloakService.authenticated() && showSideNav" (click)="menuClicked()" type="button" class="navbar-toggle">
|
||||
<span class="sr-only">Toggle navigation</span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
<span class="icon-bar"></span>
|
||||
</button>
|
||||
<a href="http://www.keycloak.org" class="navbar-brand">
|
||||
<img class="navbar-brand-icon" type="image/svg+xml" src="{{resourceUrl}}/app/assets/img/keycloak-logo-min.png" alt="" width="auto" height="30px"/>
|
||||
</a>
|
||||
</div>
|
||||
<nav class="collapse navbar-collapse">
|
||||
<ul class="nav navbar-nav">
|
||||
</ul>
|
||||
<ul class="nav navbar-nav navbar-right navbar-iconic">
|
||||
<li *ngIf="referrer.exists()">
|
||||
<a class="nav-item-iconic" href="{{referrer.getUri()}}"><span class="pficon-arrow"></span> {{'backTo' | translate:referrer.getName()}}</a>
|
||||
</li>
|
||||
<li class="dropdown" >
|
||||
<a class="dropdown-toggle nav-item-iconic" id="dropdownMenu2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span title="Username" class="fa pficon-user"></span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu2">
|
||||
<li><a href="#" (click)="logout()">{{'doSignOut' | translate}}</a></li>
|
||||
<li *ngIf="showLocales()" class="dropdown-submenu pull-left">
|
||||
<a class="test" tabindex="-1" href="#">Change language</a>
|
||||
<ul class="dropdown-menu">
|
||||
<li *ngFor="let locale of availableLocales" (click)="changeLocale(locale.locale)"><a tabindex="-1" href="#">{{ locale.label }}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</nav> <!--/.navbar-->
|
|
@ -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<Object>;
|
||||
|
||||
@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<Object> = 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 });
|
||||
}
|
||||
|
||||
}
|
|
@ -34,12 +34,11 @@
|
|||
</div>
|
||||
</pfng-vertical-navigation>
|
||||
|
||||
|
||||
|
||||
<div #contentContainer
|
||||
class="container-fluid container-cards-pf container-pf-nav-pf-vertical example-page-container">
|
||||
<div class="row">
|
||||
<div class="col-sm-12">
|
||||
<inline-notification></inline-notification>
|
||||
<router-outlet></router-outlet>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -27,7 +27,7 @@ export class VerticalNavComponent implements OnInit {
|
|||
navigationItems: NavigationItemConfig[];
|
||||
|
||||
constructor(private keycloakService: KeycloakService,
|
||||
private translateUtil: TranslateUtil, ) {
|
||||
private translateUtil: TranslateUtil ) {
|
||||
this.referrer = new Referrer(translateUtil);
|
||||
}
|
||||
|
||||
|
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue