Merge pull request #4486 from ssilvert/kc1250-big-commit
KEYCLOAK-1250: Initial commit for new account mgt.
This commit is contained in:
commit
657c68475d
91 changed files with 8159 additions and 0 deletions
|
@ -49,6 +49,62 @@
|
|||
</resources>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>account2</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.github.eirslett</groupId>
|
||||
<artifactId>frontend-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>yarn install keycloak preview</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<arguments>install --production=false --frozen-lockfile</arguments>
|
||||
<workingDirectory>src/main/resources/theme/keycloak-preview/account/resources</workingDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>compile typescript</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<phase>compile</phase>
|
||||
<configuration>
|
||||
<arguments>run build</arguments>
|
||||
<workingDirectory>src/main/resources/theme/keycloak-preview/account/resources</workingDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>yarn remove dev dependencies production=true</id>
|
||||
<goals>
|
||||
<goal>yarn</goal>
|
||||
</goals>
|
||||
<phase>process-classes</phase>
|
||||
<configuration>
|
||||
<arguments>install --production=true --frozen-lockfile</arguments>
|
||||
<workingDirectory>src/main/resources/theme/keycloak-preview/account/resources</workingDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-clean-plugin</artifactId>
|
||||
<configuration>
|
||||
<filesets>
|
||||
<fileset>
|
||||
<directory>src/main/resources/theme/keycloak-preview/account/resources/node_modules</directory>
|
||||
</fileset>
|
||||
</filesets>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<build>
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
<!DOCTYPE html>
|
||||
<html class="layout-pf-alt layout-pf-alt-fixed">
|
||||
<head>
|
||||
<title>Keycloak Account</title>
|
||||
|
||||
<script>
|
||||
var authUrl = '${authUrl}';
|
||||
var baseUrl = '${baseUrl}';
|
||||
var realm = '${realm}';
|
||||
var resourceUrl = '${resourceUrl}';
|
||||
|
||||
<#if referrer??>
|
||||
var referrer = '${referrer}';
|
||||
var referrer_uri = '${referrer_uri?html}';
|
||||
</#if>
|
||||
|
||||
<#if msg??>
|
||||
var locale = '${locale}';
|
||||
var l18n_msg = JSON.parse('${msg}');
|
||||
</#if>
|
||||
</script>
|
||||
|
||||
<base href="${baseUrl}/">
|
||||
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
|
||||
<link rel="icon" href="${resourceUrl}/app/assets/img/favicon.ico" type="image/x-icon"/>
|
||||
|
||||
<#if properties.styles?has_content>
|
||||
<#list properties.styles?split(' ') as style>
|
||||
<link href="${resourceUrl}/${style}" rel="stylesheet"/>
|
||||
</#list>
|
||||
</#if>
|
||||
|
||||
<link rel="stylesheet" href="${resourceUrl}/styles.css">
|
||||
|
||||
<!--<script src="${authUrl}/js/${resourceVersion}/keycloak.js" type="text/javascript"></script>-->
|
||||
|
||||
<!-- PatternFly -->
|
||||
<!-- iPad retina icon -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="152x152"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-152.png">
|
||||
<!-- iPad retina icon (iOS < 7) -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="144x144"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-144.png">
|
||||
<!-- iPad non-retina icon -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="76x76"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-76.png">
|
||||
<!-- iPad non-retina icon (iOS < 7) -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="72x72"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-72.png">
|
||||
<!-- iPhone 6 Plus icon -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="120x120"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-180.png">
|
||||
<!-- iPhone retina icon (iOS < 7) -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="114x114"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-114.png">
|
||||
<!-- iPhone non-retina icon (iOS < 7) -->
|
||||
<link rel="apple-touch-icon-precomposed" sizes="57x57"
|
||||
href="${resourceUrl}/node_modules/patternfly/dist/img/apple-touch-icon-precomposed-57.png">
|
||||
<link href="${resourceUrl}/node_modules/patternfly/dist/css/patternfly.min.css" rel="stylesheet"
|
||||
media="screen, print">
|
||||
<link href="${resourceUrl}/node_modules/patternfly/dist/css/patternfly-additions.min.css" rel="stylesheet"
|
||||
media="screen, print">
|
||||
<script src="${resourceUrl}/node_modules/jquery/dist/jquery.min.js"></script>
|
||||
<script src="${resourceUrl}/node_modules/bootstrap/dist/js/bootstrap.min.js"></script>
|
||||
<script src="${resourceUrl}/node_modules/jquery-match-height/dist/jquery.matchHeight-min.js"></script>
|
||||
<script src="${resourceUrl}/node_modules/patternfly/dist/js/patternfly.min.js"></script>
|
||||
|
||||
<!-- Polyfill(s) for older browsers -->
|
||||
<script src="${resourceUrl}/node_modules/core-js/client/shim.min.js"></script>
|
||||
|
||||
<#if properties.scripts?has_content>
|
||||
<#list properties.scripts?split(' ') as script>
|
||||
<script type="text/javascript" src="${resourceUrl}/${script}"></script>
|
||||
</#list>
|
||||
</#if>
|
||||
|
||||
<script src="${resourceUrl}/node_modules/zone.js/dist/zone.js"></script>
|
||||
<script src="${resourceUrl}/node_modules/systemjs/dist/system.src.js"></script>
|
||||
|
||||
<script src="${resourceUrl}/systemjs.config.js"></script>
|
||||
<script>
|
||||
System.import('${resourceUrl}/main.js').catch(function (err) {
|
||||
console.error(err);
|
||||
});
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<app-root>
|
||||
<style>
|
||||
.kc-background {
|
||||
background: url('${resourceUrl}/img/keycloak-bg.png') top left no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.logo-centered {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.kc-logo-text {
|
||||
background-image: url("${resourceUrl}/img/keycloak-logo-text.png");
|
||||
background-repeat: no-repeat;
|
||||
width: 250px;
|
||||
height: 38px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<body class="cards-pf kc-background">
|
||||
<div class='logo-centered kc-logo-text'/>
|
||||
</body>
|
||||
</app-root>
|
||||
|
||||
</html>
|
19
themes/src/main/resources/theme/keycloak-preview/account/resources/.gitignore
vendored
Normal file
19
themes/src/main/resources/theme/keycloak-preview/account/resources/.gitignore
vendored
Normal file
|
@ -0,0 +1,19 @@
|
|||
# ignore typescript-generated files
|
||||
*.js
|
||||
*.js.map
|
||||
|
||||
# ignore keycloak.json - we won't need this much longer
|
||||
keycloak.json
|
||||
|
||||
# ignore log files
|
||||
*.log
|
||||
|
||||
# ignore libraries (for now?)
|
||||
node_modules
|
||||
node
|
||||
|
||||
# Don't ignore these
|
||||
!keycloak.js
|
||||
!systemjs-angular-loader.js
|
||||
!systemjs.config.extras.js
|
||||
!systemjs.config.js
|
|
@ -0,0 +1,110 @@
|
|||
/*
|
||||
* 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} from '@angular/core';
|
||||
import {Http, Response, RequestOptionsArgs} from '@angular/http';
|
||||
|
||||
import {ToastNotifier, ToastNotification} from '../top-nav/toast.notifier';
|
||||
import {KeycloakService} from '../keycloak-service/keycloak.service';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AccountServiceClient {
|
||||
|
||||
private accountUrl: string;
|
||||
|
||||
constructor(protected http: Http,
|
||||
protected kcSvc: KeycloakService,
|
||||
protected notifier: ToastNotifier) {
|
||||
this.accountUrl = kcSvc.authServerUrl() + 'realms/' + kcSvc.realm() + '/account';
|
||||
}
|
||||
|
||||
public doGetRequest(endpoint: string,
|
||||
responseHandler: Function,
|
||||
options?: RequestOptionsArgs) {
|
||||
this.http.get(this.accountUrl + endpoint, options)
|
||||
.subscribe((res: Response) => responseHandler(res),
|
||||
(error: Response) => this.handleServiceError(error));
|
||||
}
|
||||
|
||||
public doPostRequest(endpoint: string,
|
||||
responseHandler: Function,
|
||||
options?: RequestOptionsArgs,
|
||||
successMessage?: string) {
|
||||
this.http.post(this.accountUrl + endpoint, options)
|
||||
.subscribe((res: Response) => this.handleAccountUpdated(responseHandler, res, successMessage),
|
||||
(error: Response) => this.handleServiceError(error));
|
||||
}
|
||||
|
||||
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"));
|
||||
responseHandler(res);
|
||||
}
|
||||
|
||||
public doDelete(endpoint: string,
|
||||
responseHandler: Function,
|
||||
options?: RequestOptionsArgs,
|
||||
successMessage?: string) {
|
||||
this.http.delete(this.accountUrl + endpoint, options)
|
||||
.subscribe((res: Response) => this.handleAccountUpdated(responseHandler, res, successMessage),
|
||||
(error: Response) => this.handleServiceError(error));
|
||||
}
|
||||
|
||||
private handleServiceError(response: Response): void {
|
||||
console.log('**** ERROR!!!! ***');
|
||||
console.log(JSON.stringify(response));
|
||||
console.log("response.status=" + response.status);
|
||||
console.log('***************************************')
|
||||
|
||||
if ((response.status === undefined) || (response.status === 401)) {
|
||||
this.kcSvc.logout();
|
||||
return;
|
||||
}
|
||||
|
||||
if (response.status === 403) {
|
||||
// TODO: go to a forbidden page?
|
||||
}
|
||||
|
||||
if (response.status === 404) {
|
||||
// TODO: route to PageNotFoundComponent
|
||||
}
|
||||
|
||||
let message: string = response.status + " " + response.statusText;
|
||||
|
||||
const not500Error: boolean = response.status !== 500;
|
||||
console.log('not500Error=' + not500Error);
|
||||
|
||||
// Unfortunately, errors can be sent back in the response body as
|
||||
// 'errorMessage' or 'error_description'
|
||||
if (not500Error && response.json().hasOwnProperty('errorMessage')) {
|
||||
message = response.json().errorMessage;
|
||||
}
|
||||
|
||||
if (not500Error && response.json().hasOwnProperty('error_description')) {
|
||||
message = response.json().error_description;
|
||||
}
|
||||
|
||||
this.notifier.emit(new ToastNotification(message, "error"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
<app-top-nav></app-top-nav>
|
||||
<app-side-nav></app-side-nav>
|
||||
|
||||
|
||||
<div class="container-fluid container-pf-alt-nav-pf-vertical-alt {{this.contentWidthClass}}"> <!-- collapsed-nav hidden-nav -->
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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, HostListener} from '@angular/core';
|
||||
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
|
||||
import {ResponsivenessService, ContentWidthClass, MenuClickListener} from "./responsiveness-service/responsiveness.service";
|
||||
|
||||
declare const locale: string;
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
templateUrl: './app.component.html',
|
||||
styleUrls: ['./app.component.css']
|
||||
})
|
||||
export class AppComponent implements MenuClickListener {
|
||||
|
||||
private contentWidthClass: ContentWidthClass = this.respSvc.calcSideContentWidthClass();
|
||||
|
||||
constructor(translate: TranslateService, private respSvc: ResponsivenessService) {
|
||||
// this language will be used as a fallback when a translation isn't found in the current language
|
||||
translate.setDefaultLang('en');
|
||||
|
||||
// the lang to use, if the lang isn't available, it will use the current loader to get them
|
||||
translate.use(locale);
|
||||
|
||||
this.respSvc.addMenuClickListener(this);
|
||||
}
|
||||
|
||||
public menuClicked() : void {
|
||||
this.contentWidthClass = this.respSvc.calcSideContentWidthClass();
|
||||
}
|
||||
|
||||
@HostListener('window:resize', ['$event'])
|
||||
private onResize(event: any) {
|
||||
this.contentWidthClass = this.respSvc.calcSideContentWidthClass();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
* 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 { BrowserModule } from '@angular/platform-browser';
|
||||
import { NgModule } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { HttpModule } from '@angular/http';
|
||||
import { RouterModule, Routes } from '@angular/router';
|
||||
import { LocationStrategy, HashLocationStrategy } from '@angular/common';
|
||||
|
||||
import { TranslateLoader } from '@ngx-translate/core';
|
||||
import { TranslateModule } from '@ngx-translate/core';
|
||||
|
||||
import { KeycloakService } from './keycloak-service/keycloak.service';
|
||||
import { KEYCLOAK_HTTP_PROVIDER } from './keycloak-service/keycloak.http';
|
||||
|
||||
import {ResponsivenessService} from './responsiveness-service/responsiveness.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 { AccountPageComponent } from './content/account-page/account-page.component';
|
||||
import { PasswordPageComponent } from './content/password-page/password-page.component';
|
||||
import { PageNotFoundComponent } from './content/page-not-found/page-not-found.component';
|
||||
|
||||
import { AuthenticatorPageComponent } from './content/authenticator-page/authenticator-page.component';
|
||||
|
||||
import { SessionsPageComponent } from './content/sessions-page/sessions-page.component';
|
||||
import { LargeSessionCardComponent } from './content/sessions-page/large-session-card.component';
|
||||
import { SmallSessionCardComponent } from './content/sessions-page/small-session-card.component';
|
||||
|
||||
import { ApplicationsPageComponent } from './content/applications-page/applications-page.component';
|
||||
import { LargeAppCardComponent } from './content/applications-page/large-app-card.component';
|
||||
import { SmallAppCardComponent } from './content/applications-page/small-app-card.component';
|
||||
import { RowAppCardComponent } from './content/applications-page/row-app-card.component';
|
||||
|
||||
import { ToolbarComponent } from './content/widgets/toolbar.component';
|
||||
|
||||
import {OrderbyPipe} from './pipes/orderby.pipe';
|
||||
import {FilterbyPipe} from './pipes/filterby.pipe';
|
||||
|
||||
const routes: Routes = [
|
||||
{ path: 'account', component: AccountPageComponent },
|
||||
{ path: 'password', component: PasswordPageComponent },
|
||||
{ path: 'authenticator', component: AuthenticatorPageComponent },
|
||||
{ path: 'sessions', component: SessionsPageComponent },
|
||||
{ path: 'applications', component: ApplicationsPageComponent },
|
||||
{ path: '', redirectTo: '/account', pathMatch: 'full' },
|
||||
{ path: '**', component: PageNotFoundComponent}
|
||||
];
|
||||
|
||||
const decs = [
|
||||
AppComponent,
|
||||
TopNavComponent,
|
||||
NotificationComponent,
|
||||
SideNavComponent,
|
||||
AccountPageComponent,
|
||||
PasswordPageComponent,
|
||||
PageNotFoundComponent,
|
||||
AuthenticatorPageComponent,
|
||||
SessionsPageComponent,
|
||||
LargeSessionCardComponent,
|
||||
SmallSessionCardComponent,
|
||||
ApplicationsPageComponent,
|
||||
LargeAppCardComponent,
|
||||
SmallAppCardComponent,
|
||||
RowAppCardComponent,
|
||||
ToolbarComponent,
|
||||
OrderbyPipe,
|
||||
FilterbyPipe
|
||||
];
|
||||
|
||||
export const ORIGINAL_INCOMING_URL: Location = window.location;
|
||||
|
||||
@NgModule({
|
||||
declarations: decs,
|
||||
imports: [
|
||||
BrowserModule,
|
||||
FormsModule,
|
||||
HttpModule,
|
||||
TranslateModule.forRoot({
|
||||
loader: {provide: TranslateLoader, useClass: DeclaredVarTranslateLoader}
|
||||
}),
|
||||
RouterModule.forRoot(routes)
|
||||
],
|
||||
providers: [
|
||||
KeycloakService,
|
||||
KEYCLOAK_HTTP_PROVIDER,
|
||||
ResponsivenessService,
|
||||
AccountServiceClient,
|
||||
TranslateUtil,
|
||||
ToastNotifier,
|
||||
{ provide: LocationStrategy, useClass: HashLocationStrategy }
|
||||
],
|
||||
bootstrap: [AppComponent]
|
||||
})
|
||||
export class AppModule { }
|
Binary file not shown.
After Width: | Height: | Size: 627 B |
Binary file not shown.
After Width: | Height: | Size: 5.1 KiB |
|
@ -0,0 +1,67 @@
|
|||
<div class="page-header">
|
||||
<h1>{{'editAccountHtmlTitle' | translate}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-9 content-area">
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
</div>
|
||||
<div class="col-md-2 subtitle">
|
||||
<span class="subtitle"><span class="required">*</span> {{'requiredFields' | translate}}</span>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
|
||||
<form #formGroup="ngForm" (ngSubmit)="saveAccount()" class="form-horizontal">
|
||||
|
||||
<div class="form-group ">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="username" class="control-label">{{'username' | translate}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="text" class="form-control" id="username" name="username" disabled value="{{username}}" >
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group ">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="email" class="control-label">{{'email' | translate }}</label> <span class="required">*</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input type="email" class="form-control" id="email" name="email" required autofocus="" ngModel type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group ">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="firstName" class="control-label">{{'firstName' | translate}}</label> <span class="required">*</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input class="form-control" id="firstName" required name="firstName" ngModel type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group ">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="lastName" class="control-label">{{'lastName' | translate}}</label> <span class="required">*</span>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input class="form-control" id="lastName" required name="lastName" ngModel type="text">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||
<div class="">
|
||||
<button type="submit" [disabled]="!formGroup.valid || !formGroup.dirty" class="btn btn-primary btn-lg" name="submitAction">{{'doSave' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,67 @@
|
|||
/*
|
||||
* 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, ViewChild} from '@angular/core';
|
||||
import {Response} from '@angular/http';
|
||||
import {FormGroup} from '@angular/forms';
|
||||
|
||||
import {AccountServiceClient} from '../../account-service/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-account-page',
|
||||
templateUrl: './account-page.component.html',
|
||||
styleUrls: ['./account-page.component.css']
|
||||
})
|
||||
export class AccountPageComponent implements OnInit {
|
||||
|
||||
@ViewChild('formGroup') private formGroup: FormGroup;
|
||||
|
||||
// using ordinary variable here
|
||||
// disabled fields not working properly with FormGroup
|
||||
// FormGroup.getRawValue() causes page refresh. Not sure why?
|
||||
private username: string;
|
||||
|
||||
constructor(private accountSvc: AccountServiceClient ) {
|
||||
accountSvc.doGetRequest("/", (res: Response) => this.handleGetResponse(res));
|
||||
}
|
||||
|
||||
public saveAccount() {
|
||||
console.log("posting: " + JSON.stringify(this.formGroup.value));
|
||||
this.accountSvc.doPostRequest("/", (res: Response) => this.handlePostResponse(res), this.formGroup.value);
|
||||
this.formGroup.reset(this.formGroup.value);
|
||||
}
|
||||
|
||||
protected handleGetResponse(res: Response) {
|
||||
const response: any = res.json();
|
||||
this.username = response.username;
|
||||
this.formGroup.reset(response);
|
||||
console.log('**** response from account REST API ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('*** formGroup ***');
|
||||
console.log(JSON.stringify(this.formGroup.value));
|
||||
console.log('***************************************');
|
||||
}
|
||||
|
||||
protected handlePostResponse(res: Response) {
|
||||
console.log('**** response from account POST ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('***************************************');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
/*
|
||||
* 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 {Application} from './application';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export abstract class AppCard {
|
||||
|
||||
abstract app: Application;
|
||||
abstract sessions: any[];
|
||||
|
||||
/*
|
||||
protected getName(): string {
|
||||
if (this.app.hasOwnProperty('name')) {
|
||||
return this.translateUtil.translate(this.app.name);
|
||||
}
|
||||
|
||||
return this.app.clientId;
|
||||
}
|
||||
|
||||
protected getDescription (): string {
|
||||
if (!this.app.hasOwnProperty('description')) return null;
|
||||
|
||||
let desc: string = this.app.description;
|
||||
|
||||
if (desc.indexOf("//icon") > -1) {
|
||||
desc = desc.substring(0, desc.indexOf("//icon"));
|
||||
}
|
||||
|
||||
return desc;
|
||||
}*/
|
||||
|
||||
protected isSessionActive() : boolean {
|
||||
for (let session of this.sessions) {
|
||||
for (let client of session.clients) {
|
||||
if (this.app.clientId === client.clientId) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
* 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 {TranslateUtil} from '../../ngx-translate/translate.util';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export class Application {
|
||||
|
||||
constructor(private app: any, private translateUtil: TranslateUtil ) {
|
||||
this.setIcon();
|
||||
}
|
||||
|
||||
private setIcon(): void {
|
||||
this.app.icon = "pficon-key";
|
||||
|
||||
if (!this.app.hasOwnProperty('description')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let desc: string = this.app.description;
|
||||
const iconIndex: number = desc.indexOf("//icon=");
|
||||
if (iconIndex > -1) {
|
||||
this.app.icon = desc.substring(iconIndex + 7, desc.length);
|
||||
}
|
||||
}
|
||||
|
||||
public get clientId():string {
|
||||
return this.app.name;
|
||||
}
|
||||
|
||||
public get name():string {
|
||||
if (this.app.hasOwnProperty('name')) {
|
||||
return this.translateUtil.translate(this.app.name);
|
||||
}
|
||||
|
||||
return this.app.clientId;
|
||||
}
|
||||
|
||||
public get description(): string {
|
||||
if (!this.app.hasOwnProperty('description')) return null;
|
||||
|
||||
let desc: string = this.app.description;
|
||||
|
||||
if (desc.indexOf("//icon") > -1) {
|
||||
desc = desc.substring(0, desc.indexOf("//icon"));
|
||||
}
|
||||
|
||||
return desc;
|
||||
}
|
||||
|
||||
public get icon(): string {
|
||||
return this.app.icon;
|
||||
}
|
||||
|
||||
public get effectiveUrl(): string {
|
||||
return this.app.effectiveUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
/*.card-pf-view .card-pf-top-element .app-icon {
|
||||
display: block;
|
||||
font-size: 46px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
margin: 0 auto;
|
||||
text-align: center;
|
||||
width: 40px;
|
||||
}*/
|
||||
|
||||
/*
|
||||
.col-lg-2 .card-pf-view .card-pf-top-element .card-pf-small-icon-circle {
|
||||
font-size: 23px;
|
||||
height: 54px;
|
||||
line-height: 50px;
|
||||
width: 54px;
|
||||
}*/
|
|
@ -0,0 +1,54 @@
|
|||
|
||||
|
||||
<div class="cards-pf">
|
||||
<toolbar #toolbar [filterProps]="propLabels" [sortProps]="propLabels" [actionButtons]="actionButtons"></toolbar>
|
||||
<div *ngIf="toolbar.activeView === 'LargeCards'" class="container-fluid container-cards-pf">
|
||||
<div class="row row-cards-pf">
|
||||
<large-app-card *ngFor="let app of (applications | filterby:toolbar.filterBy.prop:toolbar.filterText | orderby:toolbar.sortBy.prop:toolbar.isSortAscending )"
|
||||
class="col-xs-12 col-sm-6 col-md-4 col-lg-3"
|
||||
[app]="app"
|
||||
[sessions]="sessions">
|
||||
</large-app-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="toolbar.activeView === 'SmallCards'" class="container-fluid container-cards-pf">
|
||||
<div class="row row-cards-pf">
|
||||
<small-app-card *ngFor="let app of (applications | filterby:toolbar.filterBy.prop:toolbar.filterText | orderby:toolbar.sortBy.prop:toolbar.isSortAscending )"
|
||||
class="col-xs-12 col-sm-3 col-md-2"
|
||||
[app]="app"
|
||||
[sessions]="sessions">
|
||||
</small-app-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="toolbar.activeView === 'List'" class="container-fluid">
|
||||
<div class="list-group list-view-pf list-view-pf-view">
|
||||
<row-app-card *ngFor="let app of (applications | filterby:toolbar.filterBy.prop:toolbar.filterText | orderby:toolbar.sortBy.prop:toolbar.isSortAscending )"
|
||||
class="list-group-item"
|
||||
[app]="app"
|
||||
[sessions]="sessions">
|
||||
</row-app-card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
$(function () {
|
||||
// matchHeight the contents of each .card-pf and then the .card-pf itself
|
||||
$(".row-cards-pf > [class*='col'] > .card-pf > .card-pf-body").matchHeight();
|
||||
});
|
||||
$(document).ready(function () {
|
||||
// Card Single Select
|
||||
$('.card-pf-view-single-select').click(function () {
|
||||
if ($(this).hasClass('active'))
|
||||
{
|
||||
$(this).removeClass('active');
|
||||
} else
|
||||
{
|
||||
$('.card-pf-view-single-select').removeClass('active');
|
||||
$(this).addClass('active');
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* 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} from '@angular/core';
|
||||
import {Response} from '@angular/http';
|
||||
|
||||
import {TranslateUtil} from '../../ngx-translate/translate.util';
|
||||
import {AccountServiceClient} from '../../account-service/account.service';
|
||||
|
||||
import {Application} from './application';
|
||||
|
||||
import {View} from '../widgets/toolbar.component';
|
||||
import {PropertyLabel} from '../widgets/property.label';
|
||||
import {ActionButton} from '../widgets/action.button';
|
||||
import {RefreshButton, Refreshable} from '../widgets/refresh.button';
|
||||
|
||||
declare const resourceUrl: string;
|
||||
|
||||
type SelectableProperty = "name" | "description";
|
||||
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'app-applications-page',
|
||||
templateUrl: 'applications-page.component.html',
|
||||
styleUrls: ['applications-page.component.css']
|
||||
})
|
||||
export class ApplicationsPageComponent implements Refreshable, OnInit {
|
||||
private activeView: View = "LargeCards";
|
||||
|
||||
private resourceUrl: string = resourceUrl;
|
||||
private applications: Application[] = [];
|
||||
private isSortAscending: boolean = true;
|
||||
private sortBy: SelectableProperty = "name";
|
||||
private filterBy: SelectableProperty = "name";
|
||||
private filterText: string = "";
|
||||
|
||||
private propLabels: PropertyLabel[] = [];
|
||||
private actionButtons: ActionButton[] = [];
|
||||
|
||||
private sessions: any[] = [];
|
||||
|
||||
constructor(accountSvc: AccountServiceClient, private translateUtil: TranslateUtil) {
|
||||
this.initPropLabels();
|
||||
this.actionButtons.push(new RefreshButton(accountSvc,"/applications", this));
|
||||
accountSvc.doGetRequest("/applications", (res: Response) => this.refresh(res));
|
||||
accountSvc.doGetRequest("/sessions", (res: Response) => this.handleGetSessionsResponse(res));
|
||||
}
|
||||
|
||||
private initPropLabels(): void {
|
||||
this.propLabels.push({prop: "name", label: "Name"});
|
||||
this.propLabels.push({prop: "description", label: "Description"});
|
||||
}
|
||||
|
||||
public refresh(res: Response) {
|
||||
console.log('**** response from apps REST API ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('*** apps res.json() ***');
|
||||
console.log(JSON.stringify(res.json().applications));
|
||||
console.log('*************************************');
|
||||
|
||||
const newApps: Application[] = [];
|
||||
for (let app of res.json().applications) {
|
||||
newApps.push(new Application(app, this.translateUtil));
|
||||
}
|
||||
|
||||
// reference must change to trigger pipes
|
||||
this.applications = newApps;
|
||||
}
|
||||
|
||||
private handleGetSessionsResponse(res: Response) {
|
||||
console.log('**** response from sessions REST API ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('*** sessions res.json() ***');
|
||||
console.log(JSON.stringify(res.json()));
|
||||
console.log('***************************************');
|
||||
this.sessions = res.json();
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<div class="card-pf card-pf-view card-pf-view-select card-pf-view-single-select">
|
||||
<div class="card-pf-body">
|
||||
<div class="card-pf-top-element">
|
||||
|
||||
<div class="card-pf-heading-kebab">
|
||||
<div class="dropup pull-right dropdown-kebab-pf">
|
||||
<button class="btn btn-link dropdown-toggle" type="button" id="dropupKebabRight3" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropupKebabRight3">
|
||||
<li *ngIf="app.effectiveUrl"><a href="{{app.effectiveUrl}}">Go to Application</a></li>
|
||||
<li><a href="#">Revoke Grant</a></li>
|
||||
<li><a href="#">Logout Session(s)</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span *ngIf="app.icon" class="fa {{app.icon}} card-pf-icon-circle"></span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="card-pf-title text-center">
|
||||
{{app.name}}
|
||||
</h2>
|
||||
<h4 *ngIf="app.description" class="text-center">
|
||||
{{app.description}}
|
||||
</h4>
|
||||
<div *ngIf="isSessionActive()" class="card-pf-items text-center">
|
||||
<div class="card-pf-item">
|
||||
<span class="pficon pficon-screen"></span>
|
||||
<span class="card-pf-item-text">Session Active</span>
|
||||
<span class="fa fa-check"></span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="card-pf-info text-center"><strong>Created On</strong> 2015-03-01 02:00 AM <br/> Never Expires</p>
|
||||
</div>
|
||||
<div class="card-pf-view-checkbox">
|
||||
<input type="checkbox">
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {AppCard} from './app-card';
|
||||
import {Application} from './application';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'large-app-card',
|
||||
templateUrl: 'large-app-card.component.html',
|
||||
styleUrls: ['large-app-card.component.css']
|
||||
})
|
||||
export class LargeAppCardComponent extends AppCard {
|
||||
@Input() app: Application;
|
||||
@Input() sessions: any[];
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
<div class="list-view-pf-actions">
|
||||
<button class="btn btn-default">Action</button>
|
||||
<div class="dropdown pull-right dropdown-kebab-pf">
|
||||
<button class="btn btn-link dropdown-toggle" type="button" id="dropdownKebabRight9" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropdownKebabRight9">
|
||||
<li *ngIf="app.effectiveUrl"><a href="{{app.effectiveUrl}}">Go to Application</a></li>
|
||||
<li><a href="#">Revoke Grant</a></li>
|
||||
<li><a href="#">Logout Session(s)</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="list-view-pf-main-info">
|
||||
<div class="list-view-pf-left">
|
||||
<span class="fa {{app.icon}} list-view-pf-icon-sm"></span>
|
||||
<!--<img *ngIf="app.iconSrc" type="image/svg+xml" src="{{app.iconSrc}}" alt="" width="auto" height="40px"/>
|
||||
<span *ngIf="app.icon" class="fa {{app.icon}} list-view-pf-icon-sm"></span>-->
|
||||
</div>
|
||||
<div class="list-view-pf-body">
|
||||
<div class="list-view-pf-description">
|
||||
<div class="list-group-item-heading">
|
||||
{{app.name}}
|
||||
</div>
|
||||
<div class="list-group-item-text">
|
||||
{{app.description}}
|
||||
</div>
|
||||
</div>
|
||||
<div class="list-view-pf-additional-info">
|
||||
<div class="list-view-pf-additional-info-item">
|
||||
<span class="pficon pficon-screen"></span>
|
||||
<strong>8</strong> Hosts
|
||||
</div>
|
||||
<div class="list-view-pf-additional-info-item">
|
||||
<span class="pficon pficon-cluster"></span>
|
||||
<strong>6</strong> Clusters
|
||||
</div>
|
||||
<div class="list-view-pf-additional-info-item">
|
||||
<span class="pficon pficon-container-node"></span>
|
||||
<strong>10</strong> Nodes
|
||||
</div>
|
||||
<div class="list-view-pf-additional-info-item">
|
||||
<span class="pficon pficon-image"></span>
|
||||
<strong>8</strong> Images
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {AppCard} from './app-card';
|
||||
import {Application} from './application';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'row-app-card',
|
||||
templateUrl: 'row-app-card.component.html',
|
||||
styleUrls: ['row-app-card.component.css']
|
||||
})
|
||||
export class RowAppCardComponent extends AppCard {
|
||||
@Input() app: Application;
|
||||
@Input() sessions: any[];
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<div class="card-pf card-pf-view card-pf-view-xs">
|
||||
<div class="card-pf-body">
|
||||
<div class="card-pf-heading-kebab">
|
||||
<div class="dropdown pull-right dropdown-kebab-pf">
|
||||
<button class="btn btn-link dropdown-toggle" type="button" id="dropupKebabRight2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropupKebabRight2">
|
||||
<li *ngIf="app.effectiveUrl"><a href="{{app.effectiveUrl}}">Go to Application</a></li>
|
||||
<li><a href="#">Revoke Grant</a></li>
|
||||
<li><a href="#">Logout Session(s)</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="card-pf-title">
|
||||
<span class="pficon {{app.icon}}"></span> {{app.name}}
|
||||
</h2>
|
||||
</div>
|
||||
<div class="progress-pf-legend">
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="25" aria-valuemin="0" aria-valuemax="100" style="width: 25%;" data-toggle="tooltip" title="25% Used">
|
||||
<span class="sr-only">25% Used</span>
|
||||
</div>
|
||||
<div class="progress-bar progress-bar-remaining" role="progressbar" aria-valuenow="75" aria-valuemin="0" aria-valuemax="100" style="width: 75%;" data-toggle="tooltip" title="75% Available">
|
||||
<span class="sr-only">75% Available</span>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
// Initialize Tooltip
|
||||
jQuery(document).ready(function () {
|
||||
jQuery('[data-toggle="tooltip"]').tooltip();
|
||||
});
|
||||
</script>
|
||||
|
||||
<p><span class="pficon pficon-warning-triangle-o"></span> <strong>10%</strong> in use</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {AppCard} from './app-card';
|
||||
import {Application} from './application';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'small-app-card',
|
||||
templateUrl: 'small-app-card.component.html',
|
||||
styleUrls: ['small-app-card.component.css']
|
||||
})
|
||||
export class SmallAppCardComponent extends AppCard {
|
||||
@Input() app: Application;
|
||||
@Input() sessions: any[];
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<div class="page-header">
|
||||
<h1>Authenticator</h1>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-9 content-area">
|
||||
|
||||
<ol>
|
||||
<li>
|
||||
<p>Install <a href="https://fedorahosted.org/freeotp/" target="_blank">FreeOTP</a> or Google Authenticator on your device. Both applications are available in <a href="https://play.google.com">Google Play</a> and Apple App Store.</p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Open the application and scan the barcode or enter the key.</p>
|
||||
<p><img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAPYAAAD2AQAAAADNaUdlAAACiElEQVR42u2ZTa6jMBCEG7FgyRF8k+RilkDiYuEmHIGlF4ieqm7+3sxodqP2IiwQ+Eskx+4qlx3Rf16bfPmX/1++ikijYyovGbSb0dp9JKMRr1XwQXVesxTpVUuj09LpmpOBKngW9HoUeeO18Kazbkmamvg6LBhVEcUtyUtyZRz9L69eF3n1Y+L8V8Rt/nH7eH1ipJs/6yOQizdgkB+33/QVyA+nwaja+Mqw/M2f4jj6jwlv1eoTv6RVOFG7sByq4KqL7uSzTtrxl+BjqNn9Gt9Yjg7Dv/G0e33CHyn3e3yDORp8/imiwk/iScSWmyr4lmx96fZ+dH+EieM789n/YO59bc1/3B9Zp0net//E8izef1hjMn+0mu2u+gjn8GprxVN50x8//fTQfzAn+vRMDVCNCIoA84/rqo9gvrYL5lo8H9J6fP4f/hPMj/WvHFWZbc3+4e+xPLvXCEfV/YdKv/wnnLdsHZNaPrQkhuiwFKmEA8F1JpUjHyrXZ3E7r4LDa6CfZK22P7EiMBFVwuVwHexPjpU63/4dzkczRM/XZkcmHZ0r4e7akqzXDLFqcpf3vb6Ecgqa/Yeqt+SvpqlqONY6zj93xdf8D/rMZ7Ec14e7TguJth3YoXQ7bqiBr9ywgxemfjxtrARcl39HcztaQHSgicuRrwfuCbQS7hs6UxKPPjj/limGOriVpnrgZ4gQyl26+ZFfI7ldzF+CTYCwKn18X30d/MxfQBNRy+iQU/fjfDWS8/zIzgfPXae1PfQTzO18MHv+x61VPxO566MGPqilVtM3pb081ucaOHq9n/mLi8xz/Q7mx/mlh8Tm1Pytn2hu+uH5KqO12PkClFQarYN//7/68mr5LwnkYjOKw3BlAAAAAElFTkSuQmCC" alt="Figure: Barcode"></p>
|
||||
<p><span class="code">MVFU 4UKB IYZU 2RBZ NJSW 2MTK NQZT QVBZ</span></p>
|
||||
</li>
|
||||
<li>
|
||||
<p>Enter the one-time code provided by the application and click Save to finish the setup.</p>
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
<hr>
|
||||
|
||||
<form action="http://localhost:8180/auth/realms/master/account/totp" class="form-horizontal" method="post">
|
||||
<input id="stateChecker" name="stateChecker" value="7L6A5K0Mghuc4cm2DBF78rIMI5140AnKc01_q3Pj-4o" type="hidden">
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="totp" class="control-label">One-time code</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input class="form-control" id="totp" name="totp" autocomplete="off" autofocus="" type="text">
|
||||
<input id="totpSecret" name="totpSecret" value="eKNQAF3MD9jem2jl38T9" type="hidden">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||
<div class="">
|
||||
<button type="submit" class="btn btn-primary btn-lg" name="submitAction" value="Save">Save</button>
|
||||
<button type="submit" class="btn btn-default btn-lg" name="submitAction" value="Cancel">Cancel</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-authenticator-page',
|
||||
templateUrl: './authenticator-page.component.html',
|
||||
styleUrls: ['./authenticator-page.component.css']
|
||||
})
|
||||
export class AuthenticatorPageComponent implements OnInit {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
<p>
|
||||
Page Not Found
|
||||
</p>
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-page-not-found',
|
||||
templateUrl: './page-not-found.component.html',
|
||||
styleUrls: ['./page-not-found.component.css']
|
||||
})
|
||||
export class PageNotFoundComponent implements OnInit {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
<div class="page-header">
|
||||
<h1>{{'changePasswordHtmlTitle' | translate}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-10">
|
||||
</div>
|
||||
<div class="col-md-2 subtitle">
|
||||
<span class="subtitle">{{'allFieldsRequired' | translate}}</span>
|
||||
</div>
|
||||
<hr/>
|
||||
</div>
|
||||
|
||||
<form #formGroup="ngForm" (ngSubmit)="changePassword()" class="form-horizontal">
|
||||
<input readonly="" value="this is not a login form" style="display: none;" type="text">
|
||||
<input readonly="" value="this is not a login form" style="display: none;" type="password">
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="password" class="control-label">{{'password' | translate}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input ngModel class="form-control" id="password" name="password" autofocus="" autocomplete="off" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="password-new" class="control-label">{{'passwordNew' | translate}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input ngModel class="form-control" id="newPassword" name="newPassword" autocomplete="off" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div class="col-sm-2 col-md-2">
|
||||
<label for="password-confirm" class="control-label">{{'passwordConfirm' | translate}}</label>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-10 col-md-10">
|
||||
<input ngModel class="form-control" id="confirmation" name="confirmation" autocomplete="off" type="password">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<div id="kc-form-buttons" class="col-md-offset-2 col-md-10 submit">
|
||||
<div class="">
|
||||
<button type="submit" class="btn btn-primary btn-lg" name="submitAction">{{'doSave' | translate}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* 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, ViewChild} from '@angular/core';
|
||||
import {Response} from '@angular/http';
|
||||
import {FormGroup} from '@angular/forms';
|
||||
|
||||
import {AccountServiceClient} from '../../account-service/account.service';
|
||||
|
||||
@Component({
|
||||
selector: 'app-password-page',
|
||||
templateUrl: './password-page.component.html',
|
||||
styleUrls: ['./password-page.component.css']
|
||||
})
|
||||
export class PasswordPageComponent implements OnInit {
|
||||
|
||||
@ViewChild('formGroup') private formGroup: FormGroup;
|
||||
|
||||
constructor(private accountSvc: AccountServiceClient) {
|
||||
}
|
||||
|
||||
public changePassword() {
|
||||
console.log("posting: " + JSON.stringify(this.formGroup.value));
|
||||
this.accountSvc.doPostRequest("/credentials", (res: Response) => this.handlePostResponse(res), this.formGroup.value);
|
||||
}
|
||||
|
||||
protected handlePostResponse(res: Response) {
|
||||
console.log('**** response from account POST ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('***************************************');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
<div class="card-pf card-pf-view card-pf-view-select card-pf-view-single-select">
|
||||
<div class="card-pf-body">
|
||||
<div class="card-pf-top-element">
|
||||
|
||||
<div class="card-pf-heading-kebab">
|
||||
<div class="dropup pull-right dropdown-kebab-pf">
|
||||
<button class="btn btn-link dropdown-toggle" type="button" id="dropupKebabRight3" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropupKebabRight3">
|
||||
<li><a href="#">Logout Session</a></li>
|
||||
<li><a href="#">View Clients</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<span class="fa fa-clock-o card-pf-icon-circle"></span>
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="card-pf-title text-center">
|
||||
<strong>{{'ip' | translate}}</strong> {{session.ipAddress}}
|
||||
</h2>
|
||||
<p class="card-pf-info text-center"><strong>{{'started' | translate}}</strong> {{session.started * 1000 | date:'medium'}}</p>
|
||||
<p class="card-pf-info text-center"><strong>{{'lastAccess' | translate}}</strong> {{session.lastAccess * 1000 | date:'medium'}}</p>
|
||||
<p class="card-pf-info text-center"><strong>{{'expires' | translate}}</strong> {{session.expires * 1000 | date:'medium'}}</p>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {Session} from './session';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'large-session-card',
|
||||
templateUrl: 'large-session-card.component.html',
|
||||
styleUrls: ['large-session-card.component.css']
|
||||
})
|
||||
export class LargeSessionCardComponent {
|
||||
@Input() session: Session;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
<div class="col-sm-9 content-area">
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{'ip' | translate}}</td>
|
||||
<td>{{'started' | translate}}</td>
|
||||
<td>{{'lastAccess' | translate}}</td>
|
||||
<td>{{'expires' | translate}}</td>
|
||||
<td>{{'clients' | translate}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody *ngFor="let session of sessions">
|
||||
<tr>
|
||||
<td>{{session.ipAddress}}</td>
|
||||
<td>{{session.started * 1000 | date:'medium'}}</td>
|
||||
<td>{{session.lastAccess * 1000 | date: 'medium'}}</td>
|
||||
<td>{{session.expires * 1000 | date: 'medium'}}</td>
|
||||
<td>
|
||||
<span *ngFor="let client of session.clients">
|
||||
{{client.clientId}}<br>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<a (click)="logoutAllSessions()" id="logout-all-sessions" href="#/sessions">{{'doLogOutAllSessions' | translate}}</a>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,35 @@
|
|||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
|
||||
import {Session} from './session';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'row-session-card',
|
||||
templateUrl: 'row-session-card.component.html',
|
||||
styleUrls: ['row-session-card.component.css']
|
||||
})
|
||||
export class RowSessionCardComponent {
|
||||
@Input() sessions: Session[];
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export class Session {
|
||||
|
||||
constructor(private session: any) {}
|
||||
|
||||
get ipAddress(): string {
|
||||
return this.session.ipAddress;
|
||||
}
|
||||
|
||||
get started(): number {
|
||||
return this.session.started;
|
||||
}
|
||||
|
||||
get lastAccess(): number {
|
||||
return this.session.lastAccess;
|
||||
}
|
||||
|
||||
get expires(): number {
|
||||
return this.session.expires;
|
||||
}
|
||||
|
||||
get clients(): string[] {
|
||||
return this.session.clients;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
<div class="cards-pf">
|
||||
<toolbar #toolbar [filterProps]="filterLabels" [sortProps]="sortLabels" [actionButtons]="actionButtons"></toolbar>
|
||||
|
||||
<div *ngIf="toolbar.activeView === 'LargeCards'" class="container-fluid container-cards-pf">
|
||||
<div class="row row-cards-pf">
|
||||
<large-session-card *ngFor="let session of (sessions | filterby:toolbar.filterBy.prop:toolbar.filterText | orderby:toolbar.sortBy.prop:toolbar.isSortAscending )"
|
||||
class="col-xs-12 col-sm-6 col-md-4 col-lg-3"
|
||||
[session]="session">
|
||||
</large-session-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="toolbar.activeView === 'SmallCards'" class="container-fluid container-cards-pf">
|
||||
<div class="row row-cards-pf">
|
||||
<small-session-card *ngFor="let session of (sessions | filterby:toolbar.filterBy.prop:toolbar.filterText | orderby:toolbar.sortBy.prop:toolbar.isSortAscending )"
|
||||
class="col-xs-12 col-sm-3 col-md-2"
|
||||
[session]="session">
|
||||
</small-session-card>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div *ngIf="toolbar.activeView === 'List'" class="container-fluid">
|
||||
<div class="page-header">
|
||||
<h1>{{'sessionsHtmlTitle' | translate}}</h1>
|
||||
</div>
|
||||
|
||||
<div class="col-sm-9 content-area">
|
||||
|
||||
<table class="table table-striped table-bordered">
|
||||
<thead>
|
||||
<tr>
|
||||
<td>{{'ip' | translate}}</td>
|
||||
<td>{{'started' | translate}}</td>
|
||||
<td>{{'lastAccess' | translate}}</td>
|
||||
<td>{{'expires' | translate}}</td>
|
||||
<td>{{'clients' | translate}}</td>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody *ngFor="let item of response">
|
||||
<tr>
|
||||
<td>{{item.ipAddress}}</td>
|
||||
<td>{{item.started * 1000 | date:'medium'}}</td>
|
||||
<td>{{item.lastAccess * 1000 | date: 'medium'}}</td>
|
||||
<td>{{item.expires * 1000 | date: 'medium'}}</td>
|
||||
<td>
|
||||
<span *ngFor="let client of item.clients">
|
||||
{{client.clientId}}<br>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
<a (click)="logoutAllSessions()" id="logout-all-sessions" href="#/sessions">{{'doLogOutAllSessions' | translate}}</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
* 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} from '@angular/core';
|
||||
import {Response} from '@angular/http';
|
||||
|
||||
import {AccountServiceClient} from '../../account-service/account.service';
|
||||
import {TranslateUtil} from '../../ngx-translate/translate.util';
|
||||
|
||||
import {View} from '../widgets/toolbar.component';
|
||||
import {PropertyLabel} from '../widgets/property.label';
|
||||
import {ActionButton} from '../widgets/action.button';
|
||||
import {RefreshButton, Refreshable} from '../widgets/refresh.button';
|
||||
|
||||
import {Session} from './session';
|
||||
|
||||
@Component({
|
||||
selector: 'app-sessions-page',
|
||||
templateUrl: './sessions-page.component.html',
|
||||
styleUrls: ['./sessions-page.component.css']
|
||||
})
|
||||
export class SessionsPageComponent implements Refreshable, OnInit {
|
||||
private filterLabels: PropertyLabel[] = [];
|
||||
private sortLabels: PropertyLabel[] = [];
|
||||
|
||||
private response: any[] = [];
|
||||
private sessions: Session[] = [];
|
||||
|
||||
private actionButtons: ActionButton[] = [];
|
||||
|
||||
constructor(private accountSvc: AccountServiceClient, private translateUtil: TranslateUtil ) {
|
||||
this.initPropLabels();
|
||||
this.actionButtons.push(new LogoutAllButton(accountSvc, translateUtil));
|
||||
this.actionButtons.push(new RefreshButton(accountSvc,"/sessions", this));
|
||||
accountSvc.doGetRequest("/sessions", (res: Response) => this.refresh(res));
|
||||
}
|
||||
|
||||
private initPropLabels(): void {
|
||||
this.filterLabels.push({prop: "ipAddress", label: "IP"});
|
||||
|
||||
this.sortLabels.push({prop: "ipAddress", label: "IP"});
|
||||
this.sortLabels.push({prop: "started", label: "Started"});
|
||||
this.sortLabels.push({prop: "lastAccess", label: "Last Access"});
|
||||
this.sortLabels.push({prop: "expires", label: "Expires"});
|
||||
}
|
||||
|
||||
public refresh(res: Response) {
|
||||
console.log('**** response from account REST API ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('*** res.json() ***');
|
||||
console.log(JSON.stringify(res.json()));
|
||||
console.log('***************************************');
|
||||
this.response = res.json();
|
||||
|
||||
const newSessions: Session[] = [];
|
||||
for (let session of res.json()) {
|
||||
newSessions.push(new Session(session));
|
||||
}
|
||||
|
||||
// reference must change to trigger pipes
|
||||
this.sessions = newSessions;
|
||||
}
|
||||
|
||||
private logoutAllSessions() {
|
||||
this.accountSvc.doDelete("/sessions",
|
||||
(res: Response) => this.handleLogoutResponse(res),
|
||||
{params: {current: true}},
|
||||
"Logging out all sessions.");
|
||||
}
|
||||
|
||||
private handleLogoutResponse(res: Response) {
|
||||
console.log('**** response from account DELETE ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('***************************************');
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class LogoutAllButton implements ActionButton {
|
||||
public readonly label: string = "Logout All"; //TODO: localize in constructor
|
||||
public readonly tooltip: string;
|
||||
|
||||
constructor(private accountSvc: AccountServiceClient, translateUtil: TranslateUtil ) {
|
||||
this.tooltip = translateUtil.translate('doLogOutAllSessions');
|
||||
}
|
||||
|
||||
performAction(): void {
|
||||
this.accountSvc.doDelete("/sessions",
|
||||
(res: Response) => this.handleLogoutResponse(res),
|
||||
{params: {current: true}},
|
||||
"Logging out all sessions.");
|
||||
}
|
||||
|
||||
private handleLogoutResponse(res: Response) {
|
||||
console.log('**** response from account DELETE ***');
|
||||
console.log(JSON.stringify(res));
|
||||
console.log('***************************************');
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
<div class="card-pf card-pf-view card-pf-view-xs">
|
||||
<div class="card-pf-body">
|
||||
<div class="card-pf-heading-kebab">
|
||||
<div class="dropdown pull-right dropdown-kebab-pf">
|
||||
<button class="btn btn-link dropdown-toggle" type="button" id="dropupKebabRight2" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu dropdown-menu-right" aria-labelledby="dropupKebabRight2">
|
||||
<li><a href="#">Logout Session</a></li>
|
||||
<li><a href="#">View Clients</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h2 class="card-pf-title">
|
||||
<span class="fa fa-clock-o"></span> {{session.ipAddress}}
|
||||
</h2>
|
||||
<p class="card-pf-info text-center"><strong>{{'started' | translate}}</strong> {{session.started * 1000 | date:'shortTime'}}</p>
|
||||
<p class="card-pf-info text-center"><strong>{{'lastAccess' | translate}}</strong> {{session.lastAccess * 1000 | date:'shortTime'}}</p>
|
||||
<p class="card-pf-info text-center"><strong>{{'expires' | translate}}</strong> {{session.expires * 1000 | date:'shortTime'}}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 {Component, Input} from '@angular/core';
|
||||
import {Session} from './session';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'small-session-card',
|
||||
templateUrl: 'small-session-card.component.html',
|
||||
styleUrls: ['small-session-card.component.css']
|
||||
})
|
||||
export class SmallSessionCardComponent {
|
||||
@Input() session: Session;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 {Icon} from '../../page/icon';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export interface ActionButton {
|
||||
readonly label: string | Icon;
|
||||
readonly tooptip?: string;
|
||||
|
||||
performAction(): void;
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Property name and its human-readable, localized display text.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export interface PropertyLabel {
|
||||
readonly prop: string;
|
||||
readonly label: string;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* 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 {Response} from '@angular/http';
|
||||
|
||||
import {ActionButton} from './action.button';
|
||||
|
||||
import {Icon} from '../../page/icon';
|
||||
|
||||
import {AccountServiceClient} from '../../account-service/account.service';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export class RefreshButton implements ActionButton {
|
||||
|
||||
readonly label:Icon = new Icon('fa', 'refresh');
|
||||
readonly tooltip:string = 'Refresh'; //TODO: localize in constructor
|
||||
|
||||
constructor(private accountSvc: AccountServiceClient,
|
||||
private request: string,
|
||||
private refreshable:Refreshable) {}
|
||||
|
||||
public performAction(): void {
|
||||
this.accountSvc.doGetRequest(this.request, (res: Response) => {
|
||||
this.refreshable.refresh(res);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export interface Refreshable {
|
||||
refresh(response:Response): void;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
* 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 {Component, Input, OnInit} from '@angular/core';
|
||||
|
||||
import {PropertyLabel} from './property.label';
|
||||
import {ActionButton} from './action.button';
|
||||
import {Icon} from '../../page/icon';
|
||||
|
||||
export type View = "LargeCards" | "SmallCards" | "List";
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Component({
|
||||
moduleId: module.id, // need this for styleUrls path to work properly with Systemjs
|
||||
selector: 'toolbar',
|
||||
templateUrl: 'toolbar.html',
|
||||
styleUrls: ['toolbar.css']
|
||||
})
|
||||
export class ToolbarComponent implements OnInit {
|
||||
@Input() filterProps: PropertyLabel[];
|
||||
@Input() sortProps: PropertyLabel[];
|
||||
@Input() actionButtons: ActionButton[];
|
||||
|
||||
// TODO: localize in constructor
|
||||
readonly sortByTooltip: string = "Sort by...";
|
||||
readonly sortAscendingTooltip: string = "Sort Ascending";
|
||||
readonly sortDescendingTooltip: string = "Sort Descending";
|
||||
|
||||
private isSortAscending: boolean = true;
|
||||
private sortBy: PropertyLabel;
|
||||
private filterBy: PropertyLabel;
|
||||
private filterText: string = "";
|
||||
|
||||
public activeView: View = "LargeCards";
|
||||
|
||||
ngOnInit() {
|
||||
if (this.filterProps && this.filterProps.length > 0) {
|
||||
this.filterBy = this.filterProps[0];
|
||||
}
|
||||
|
||||
if (this.sortProps && this.sortProps.length > 0) {
|
||||
this.sortBy = this.sortProps[0];
|
||||
}
|
||||
}
|
||||
|
||||
private changeView(activeView: View) {
|
||||
this.activeView = activeView;
|
||||
}
|
||||
|
||||
private toggleSort() {
|
||||
this.isSortAscending = !this.isSortAscending;
|
||||
}
|
||||
|
||||
private changeSortByProp(prop: PropertyLabel) {
|
||||
this.sortBy = prop;
|
||||
}
|
||||
|
||||
private changeFilterByProp(prop: PropertyLabel) {
|
||||
this.filterBy = prop;
|
||||
this.filterText = "";
|
||||
}
|
||||
|
||||
private selectedFilterClass(prop: PropertyLabel): string {
|
||||
if (this.filterBy === prop) {
|
||||
return "selected";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private selectedSortByClass(prop: PropertyLabel): string {
|
||||
if (this.sortBy === prop) {
|
||||
return "selected";
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
isIconButton(button: ActionButton): boolean {
|
||||
return button.label instanceof Icon;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
<div class="container-fluid">
|
||||
<div class="row toolbar-pf">
|
||||
<div class="col-sm-12">
|
||||
<div class="toolbar-pf-actions">
|
||||
<div *ngIf="filterProps" class="form-group toolbar-pf-filter">
|
||||
<label class="sr-only" for="filter">{{filterBy.label}}</label>
|
||||
<div class="input-group">
|
||||
<div class="input-group-btn">
|
||||
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">{{filterBy.label}} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li *ngFor="let prop of filterProps" (click)="changeFilterByProp(prop)" class="{{selectedFilterClass(prop)}}"><a>{{prop.label}}</a></li>
|
||||
</ul>
|
||||
</div><!-- /btn-group -->
|
||||
<form #filterForm="ngForm">
|
||||
<input [(ngModel)]="filterText" name="filterText" type="text" class="form-control" id="filter" placeholder="Filter By {{filterBy.label}}...">
|
||||
</form>
|
||||
</div><!-- /input-group -->
|
||||
</div>
|
||||
<div *ngIf="sortProps" class="form-group">
|
||||
<div class="dropdown btn-group">
|
||||
<button type="button"
|
||||
class="btn btn-default dropdown-toggle"
|
||||
data-toggle="tooltip"
|
||||
data-toggle="dropdown"
|
||||
data-placement="top"
|
||||
title="{{sortByTooltip}}"
|
||||
aria-haspopup="true"
|
||||
aria-expanded="false">{{sortBy.label}} <span class="caret"></span></button>
|
||||
<ul class="dropdown-menu">
|
||||
<li *ngFor="let prop of sortProps" (click)="changeSortByProp(prop)" class="{{selectedSortByClass(prop)}}"><a>{{prop.label}}</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
<button (click)="toggleSort()" class="btn btn-link" type="button">
|
||||
<span *ngIf="!isSortAscending"
|
||||
class="fa fa-sort-alpha-asc"
|
||||
data-toggle="toltip"
|
||||
data-placement="top"
|
||||
title="{{sortAscendingTooltip}}"></span>
|
||||
<span *ngIf="isSortAscending"
|
||||
class="fa fa-sort-alpha-desc"
|
||||
data-toggle="toltip"
|
||||
data-placement="top"
|
||||
title="{{sortDescendingTooltip}}"></span>
|
||||
</button>
|
||||
</div>
|
||||
<div *ngIf="actionButtons">
|
||||
<span class="form-group" *ngFor="let action of actionButtons">
|
||||
<button *ngIf="!isIconButton(action)"
|
||||
(click)="action.performAction()"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title="{{action.tooltip}}"
|
||||
class="btn btn-default"
|
||||
type="button">{{action.label}}</button>
|
||||
<button *ngIf="isIconButton(action)"
|
||||
(click)="action.performAction()"
|
||||
data-toggle="tooltip"
|
||||
data-placement="top"
|
||||
title="{{action.tooltip}}"
|
||||
class="btn btn-link"
|
||||
type="button">
|
||||
<span class="{{action.label.getClasses()}}"></span>
|
||||
</button>
|
||||
</span>
|
||||
<!--<button class="btn btn-default" type="button">Action</button>
|
||||
<div class="dropdown btn-group dropdown-kebab-pf">
|
||||
<button class="btn btn-link dropdown-toggle" type="button" id="dropdownKebab" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span class="fa fa-ellipsis-v"></span>
|
||||
</button>
|
||||
<ul class="dropdown-menu " aria-labelledby="dropdownKebab">
|
||||
<li><a href="#">Action</a></li>
|
||||
<li><a href="#">Another action</a></li>
|
||||
<li><a href="#">Something else here</a></li>
|
||||
<li role="separator" class="divider"></li>
|
||||
<li><a href="#">Separated link</a></li>
|
||||
</ul>
|
||||
</div>-->
|
||||
|
||||
</div>
|
||||
<div class="toolbar-pf-action-right">
|
||||
<!--<div class="form-group toolbar-pf-find">
|
||||
<button class="btn btn-link btn-find" type="button">
|
||||
<span class="fa fa-search"></span>
|
||||
</button>
|
||||
<div class="find-pf-dropdown-container">
|
||||
<input type="text" class="form-control" id="find" placeholder="Find By Keyword...">
|
||||
<div class="find-pf-buttons">
|
||||
<span class="find-pf-nums">1 of 3</span>
|
||||
<button class="btn btn-link" type="button">
|
||||
<span class="fa fa-angle-up"></span>
|
||||
</button>
|
||||
<button class="btn btn-link" type="button">
|
||||
<span class="fa fa-angle-down"></span>
|
||||
</button>
|
||||
<button class="btn btn-link btn-find-close" type="button">
|
||||
<span class="pficon pficon-close"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>-->
|
||||
<div class="form-group toolbar-pf-view-selector">
|
||||
<button (click)="changeView('LargeCards')" class="btn btn-link "><i class="fa fa-th-large"></i></button>
|
||||
<button (click)="changeView('SmallCards')" class="btn btn-link "><i class="fa fa-th"></i></button>
|
||||
<button (click)="changeView('List')" class="btn btn-link "><i class="fa fa-th-list"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!--<div class="row toolbar-pf-results">
|
||||
<div class="col-sm-12">
|
||||
<h5>40 Results</h5>
|
||||
<p>Active filters:</p>
|
||||
<ul class="list-inline">
|
||||
<li>
|
||||
<span class="label label-info">
|
||||
Name: nameofthething
|
||||
<a href="#"><span class="pficon pficon-close"></span></a>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label label-info">
|
||||
Name: nameofthething
|
||||
<a href="#"><span class="pficon pficon-close"></span></a>
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="label label-info">
|
||||
Name: nameofthething
|
||||
<a href="#"><span class="pficon pficon-close"></span></a>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<p><a href="#">Clear All Filters</a></p>
|
||||
</div><!-- /col --
|
||||
</div>--><!-- /row -->
|
||||
</div><!-- /col -->
|
||||
</div><!-- /row -->
|
||||
</div><!-- /container -->
|
|
@ -0,0 +1 @@
|
|||
!keycloak.js
|
|
@ -0,0 +1,478 @@
|
|||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright 2017 Andy Hanson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
|
||||
* associated documentation files (the "Software"), to deal in the Software without restriction, including
|
||||
* without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
|
||||
* following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all copies or substantial
|
||||
* portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
|
||||
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
*/
|
||||
export as namespace Keycloak;
|
||||
|
||||
export = Keycloak;
|
||||
|
||||
/**
|
||||
* Creates a new Keycloak client instance.
|
||||
* @param config Path to a JSON config file or a plain config object.
|
||||
*/
|
||||
declare function Keycloak(config?: string|{}): Keycloak.KeycloakInstance;
|
||||
|
||||
declare namespace Keycloak {
|
||||
type KeycloakAdapterName = 'cordova'|'default';
|
||||
type KeycloakOnLoad = 'login-required'|'check-sso';
|
||||
type KeycloakResponseMode = 'query'|'fragment';
|
||||
type KeycloakResponseType = 'code'|'id_token token'|'code id_token token';
|
||||
type KeycloakFlow = 'standard'|'implicit'|'hybrid';
|
||||
|
||||
interface KeycloakInitOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
adapter?: KeycloakAdapterName;
|
||||
|
||||
/**
|
||||
* Specifies an action to do on load.
|
||||
*/
|
||||
onLoad?: KeycloakOnLoad;
|
||||
|
||||
/**
|
||||
* Set an initial value for the token.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the refresh token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for the id token (only together with `token` or
|
||||
* `refreshToken`).
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* Set an initial value for skew between local time and Keycloak server in
|
||||
* seconds (only together with `token` or `refreshToken`).
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* Set to enable/disable monitoring login state.
|
||||
* @default true
|
||||
*/
|
||||
checkLoginIframe?: boolean;
|
||||
|
||||
/**
|
||||
* Set the interval to check login state (in seconds).
|
||||
* @default 5
|
||||
*/
|
||||
checkLoginIframeInterval?: boolean;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect response mode to send to Keycloak upon login.
|
||||
* @default fragment After successful authentication Keycloak will redirect
|
||||
* to JavaScript application with OpenID Connect parameters
|
||||
* added in URL fragment. This is generally safer and
|
||||
* recommended over query.
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Set the OpenID Connect flow.
|
||||
* @default standard
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
}
|
||||
|
||||
interface KeycloakLoginOptions {
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
scope?: string;
|
||||
|
||||
/**
|
||||
* Specifies the uri to redirect to after login.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* By default the login screen is displayed if the user is not logged into
|
||||
* Keycloak. To only authenticate to the application if the user is already
|
||||
* logged in and not display the login page if the user is not logged in, set
|
||||
* this option to `'none'`. To always require re-authentication and ignore
|
||||
* SSO, set this option to `'login'`.
|
||||
*/
|
||||
prompt?: 'none'|'login';
|
||||
|
||||
/**
|
||||
* If value is `'register'` then user is redirected to registration page,
|
||||
* otherwise to login page.
|
||||
*/
|
||||
action?: 'register';
|
||||
|
||||
/**
|
||||
* Used just if user is already authenticated. Specifies maximum time since
|
||||
* the authentication of user happened. If user is already authenticated for
|
||||
* longer time than `'maxAge'`, the SSO is ignored and he will need to
|
||||
* authenticate again.
|
||||
*/
|
||||
maxAge?: number;
|
||||
|
||||
/**
|
||||
* Used to pre-fill the username/email field on the login form.
|
||||
*/
|
||||
loginHint?: string;
|
||||
|
||||
/**
|
||||
* Used to tell Keycloak which IDP the user wants to authenticate with.
|
||||
*/
|
||||
idpHint?: string;
|
||||
|
||||
/**
|
||||
* Specifies the desired locale for the UI.
|
||||
*/
|
||||
locale?: string;
|
||||
}
|
||||
|
||||
type KeycloakPromiseCallback<T> = (result: T) => void;
|
||||
|
||||
interface KeycloakPromise<TSuccess, TError> {
|
||||
/**
|
||||
* Function to call if the promised action succeeds.
|
||||
*/
|
||||
success(callback: KeycloakPromiseCallback<TSuccess>): KeycloakPromise<TSuccess, TError>;
|
||||
|
||||
/**
|
||||
* Function to call if the promised action throws an error.
|
||||
*/
|
||||
error(callback: KeycloakPromiseCallback<TError>): KeycloakPromise<TSuccess, TError>;
|
||||
}
|
||||
|
||||
interface KeycloakError {
|
||||
error: string;
|
||||
error_description: string;
|
||||
}
|
||||
|
||||
interface KeycloakAdapter {
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
register(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
redirectUri(options: { redirectUri: string; }, encodeHash: boolean): string;
|
||||
}
|
||||
|
||||
interface KeycloakProfile {
|
||||
id?: string;
|
||||
username?: string;
|
||||
email?: string;
|
||||
firstName?: string;
|
||||
lastName?: string;
|
||||
enabled?: boolean;
|
||||
emailVerified?: boolean;
|
||||
totp?: boolean;
|
||||
createdTimestamp?: number;
|
||||
}
|
||||
|
||||
// export interface KeycloakUserInfo {}
|
||||
|
||||
/**
|
||||
* A client for the Keycloak authentication server.
|
||||
* @see {@link https://keycloak.gitbooks.io/securing-client-applications-guide/content/topics/oidc/javascript-adapter.html|Keycloak JS adapter documentation}
|
||||
*/
|
||||
interface KeycloakInstance {
|
||||
/**
|
||||
* Is true if the user is authenticated, false otherwise.
|
||||
*/
|
||||
authenticated?: boolean;
|
||||
|
||||
/**
|
||||
* The user id.
|
||||
*/
|
||||
subject?: string;
|
||||
|
||||
/**
|
||||
* Response mode passed in init (default value is `'fragment'`).
|
||||
*/
|
||||
responseMode?: KeycloakResponseMode;
|
||||
|
||||
/**
|
||||
* Response type sent to Keycloak with login requests. This is determined
|
||||
* based on the flow value used during initialization, but can be overridden
|
||||
* by setting this value.
|
||||
*/
|
||||
responseType?: KeycloakResponseType;
|
||||
|
||||
/**
|
||||
* Flow passed in init.
|
||||
*/
|
||||
flow?: KeycloakFlow;
|
||||
|
||||
/**
|
||||
* The realm roles associated with the token.
|
||||
*/
|
||||
realmAccess?: { roles: string[] };
|
||||
|
||||
/**
|
||||
* The resource roles associated with the token.
|
||||
*/
|
||||
resourceAccess?: string[];
|
||||
|
||||
/**
|
||||
* The base64 encoded token that can be sent in the Authorization header in
|
||||
* requests to services.
|
||||
*/
|
||||
token?: string;
|
||||
|
||||
/**
|
||||
* The parsed token as a JavaScript object.
|
||||
*/
|
||||
tokenParsed?: {
|
||||
exp?: number;
|
||||
iat?: number;
|
||||
nonce?: string;
|
||||
sub?: string;
|
||||
session_state?: string;
|
||||
realm_access?: { roles: string[] };
|
||||
resource_access?: string[];
|
||||
};
|
||||
|
||||
/**
|
||||
* The base64 encoded refresh token that can be used to retrieve a new token.
|
||||
*/
|
||||
refreshToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed refresh token as a JavaScript object.
|
||||
*/
|
||||
refreshTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The base64 encoded ID token.
|
||||
*/
|
||||
idToken?: string;
|
||||
|
||||
/**
|
||||
* The parsed id token as a JavaScript object.
|
||||
*/
|
||||
idTokenParsed?: { nonce?: string };
|
||||
|
||||
/**
|
||||
* The estimated time difference between the browser time and the Keycloak
|
||||
* server in seconds. This value is just an estimation, but is accurate
|
||||
* enough when determining if a token is expired or not.
|
||||
*/
|
||||
timeSkew?: number;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loginRequired?: boolean;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
authServerUrl?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
realm?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
clientSecret?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
redirectUri?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
sessionId?: string;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
profile?: KeycloakProfile;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
userInfo?: {}; // KeycloakUserInfo;
|
||||
|
||||
/**
|
||||
* Called when the adapter is initialized.
|
||||
*/
|
||||
onReady?(authenticated?: boolean): void;
|
||||
|
||||
/**
|
||||
* Called when a user is successfully authenticated.
|
||||
*/
|
||||
onAuthSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error during authentication.
|
||||
*/
|
||||
onAuthError?(errorData: KeycloakError): void;
|
||||
|
||||
/**
|
||||
* Called when the token is refreshed.
|
||||
*/
|
||||
onAuthRefreshSuccess?(): void;
|
||||
|
||||
/**
|
||||
* Called if there was an error while trying to refresh the token.
|
||||
*/
|
||||
onAuthRefreshError?(): void;
|
||||
|
||||
/**
|
||||
* Called if the user is logged out (will only be called if the session
|
||||
* status iframe is enabled, or in Cordova mode).
|
||||
*/
|
||||
onAuthLogout?(): void;
|
||||
|
||||
/**
|
||||
* Called when the access token is expired. If a refresh token is available
|
||||
* the token can be refreshed with Keycloak#updateToken, or in cases where
|
||||
* it's not (ie. with implicit flow) you can redirect to login screen to
|
||||
* obtain a new access token.
|
||||
*/
|
||||
onTokenExpired?(): void;
|
||||
|
||||
/**
|
||||
* Called to initialize the adapter.
|
||||
* @param initOptions Initialization options.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
init(initOptions: KeycloakInitOptions): KeycloakPromise<boolean, KeycloakError>;
|
||||
|
||||
/**
|
||||
* Redirects to login form.
|
||||
* @param options Login options.
|
||||
*/
|
||||
login(options?: KeycloakLoginOptions): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to logout.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
logout(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to registration form.
|
||||
* @param options Supports same options as Keycloak#login but `action` is
|
||||
* set to `'register'`.
|
||||
*/
|
||||
register(options?: any): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Redirects to the Account Management Console.
|
||||
*/
|
||||
accountManagement(): KeycloakPromise<void, void>;
|
||||
|
||||
/**
|
||||
* Returns the URL to login form.
|
||||
* @param options Supports same options as Keycloak#login.
|
||||
*/
|
||||
createLoginUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to logout the user.
|
||||
* @param options Logout options.
|
||||
* @param options.redirectUri Specifies the uri to redirect to after logout.
|
||||
*/
|
||||
createLogoutUrl(options?: any): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to registration page.
|
||||
* @param options Supports same options as Keycloak#createLoginUrl but
|
||||
* `action` is set to `'register'`.
|
||||
*/
|
||||
createRegisterUrl(options?: KeycloakLoginOptions): string;
|
||||
|
||||
/**
|
||||
* Returns the URL to the Account Management Console.
|
||||
*/
|
||||
createAccountUrl(): string;
|
||||
|
||||
/**
|
||||
* Returns true if the token has less than `minValidity` seconds left before
|
||||
* it expires.
|
||||
* @param minValidity If not specified, `0` is used.
|
||||
*/
|
||||
isTokenExpired(minValidity?: number): boolean;
|
||||
|
||||
/**
|
||||
* If the token expires within `minValidity` seconds, the token is refreshed.
|
||||
* If the session status iframe is enabled, the session status is also
|
||||
* checked.
|
||||
* @returns A promise to set functions that can be invoked if the token is
|
||||
* still valid, or if the token is no longer valid.
|
||||
* @example
|
||||
* ```js
|
||||
* keycloak.updateToken(5).success(function(refreshed) {
|
||||
* if (refreshed) {
|
||||
* alert('Token was successfully refreshed');
|
||||
* } else {
|
||||
* alert('Token is still valid');
|
||||
* }
|
||||
* }).error(function() {
|
||||
* alert('Failed to refresh the token, or the session has expired');
|
||||
* });
|
||||
*/
|
||||
updateToken(minValidity: number): KeycloakPromise<boolean, boolean>;
|
||||
|
||||
/**
|
||||
* Clears authentication state, including tokens. This can be useful if
|
||||
* the application has detected the session was expired, for example if
|
||||
* updating token fails. Invoking this results in Keycloak#onAuthLogout
|
||||
* callback listener being invoked.
|
||||
*/
|
||||
clearToken(): void;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given realm role.
|
||||
* @param role A realm role name.
|
||||
*/
|
||||
hasRealmRole(role: string): boolean;
|
||||
|
||||
/**
|
||||
* Returns true if the token has the given role for the resource.
|
||||
* @param role A role name.
|
||||
* @param resource If not specified, `clientId` is used.
|
||||
*/
|
||||
hasResourceRole(role: string, resource?: string): boolean;
|
||||
|
||||
/**
|
||||
* Loads the user's profile.
|
||||
* @returns A promise to set functions to be invoked on success or error.
|
||||
*/
|
||||
loadUserProfile(): KeycloakPromise<KeycloakProfile, void>;
|
||||
|
||||
/**
|
||||
* @private Undocumented.
|
||||
*/
|
||||
loadUserInfo(): KeycloakPromise<{}, void>;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 {Injectable} from '@angular/core';
|
||||
import {Http, Request, XHRBackend, ConnectionBackend, RequestOptions, RequestOptionsArgs, Response, Headers} from '@angular/http';
|
||||
|
||||
import {KeycloakService} from './keycloak.service';
|
||||
import {Observable} from 'rxjs/Observable';
|
||||
import 'rxjs/add/observable/fromPromise';
|
||||
import 'rxjs/add/operator/concatMap';
|
||||
import 'rxjs/add/operator/map';
|
||||
|
||||
|
||||
/**
|
||||
* This provides a wrapper over the ng2 Http class that insures tokens are refreshed on each request.
|
||||
*/
|
||||
@Injectable()
|
||||
export class KeycloakHttp extends Http {
|
||||
constructor(_backend: ConnectionBackend, _defaultOptions: RequestOptions, private _keycloakService: KeycloakService) {
|
||||
super(_backend, _defaultOptions);
|
||||
}
|
||||
|
||||
request(url: string | Request, options?: RequestOptionsArgs): Observable<Response> {
|
||||
if (!this._keycloakService.authenticated()) return super.request(url, options);
|
||||
|
||||
const tokenPromise: Promise<string> = this._keycloakService.getToken();
|
||||
const tokenObservable: Observable<string> = Observable.fromPromise(tokenPromise);
|
||||
|
||||
if (typeof url === 'string') {
|
||||
return tokenObservable.map(token => {
|
||||
const authOptions = new RequestOptions({headers: new Headers({'Authorization': 'Bearer ' + token})});
|
||||
return new RequestOptions().merge(options).merge(authOptions);
|
||||
}).concatMap(opts => super.request(url, opts));
|
||||
} else if (url instanceof Request) {
|
||||
return tokenObservable.map(token => {
|
||||
url.headers.set('Authorization', 'Bearer ' + token);
|
||||
return url;
|
||||
}).concatMap(request => super.request(request));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function keycloakHttpFactory(backend: XHRBackend, defaultOptions: RequestOptions, keycloakService: KeycloakService) {
|
||||
return new KeycloakHttp(backend, defaultOptions, keycloakService);
|
||||
}
|
||||
|
||||
export const KEYCLOAK_HTTP_PROVIDER = {
|
||||
provide: Http,
|
||||
useFactory: keycloakHttpFactory,
|
||||
deps: [XHRBackend, RequestOptions, KeycloakService]
|
||||
};
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* 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 {Injectable} from '@angular/core';
|
||||
|
||||
// If using a local keycloak.js, uncomment this import. With keycloak.js fetched
|
||||
// from the server, you get a compile-time warning on use of the Keycloak()
|
||||
// method below. I'm not sure how to fix this, but it's certainly cleaner
|
||||
// to get keycloak.js from the server.
|
||||
//
|
||||
import * as Keycloak from './keycloak';
|
||||
|
||||
type KeycloakClient = Keycloak.KeycloakInstance;
|
||||
type InitOptions = Keycloak.KeycloakInitOptions;
|
||||
|
||||
@Injectable()
|
||||
export class KeycloakService {
|
||||
static keycloakAuth: KeycloakClient;
|
||||
|
||||
/**
|
||||
* Configure and initialize the Keycloak adapter.
|
||||
*
|
||||
* @param configOptions Optionally, a path to keycloak.json, or an object containing
|
||||
* url, realm, and clientId.
|
||||
* @param adapterOptions Optional initiaization options. See javascript adapter docs
|
||||
* for details.
|
||||
* @returns {Promise<T>}
|
||||
*/
|
||||
static init(configOptions?: string|{}, initOptions?: InitOptions): Promise<any> {
|
||||
KeycloakService.keycloakAuth = Keycloak(configOptions);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
KeycloakService.keycloakAuth.init(initOptions)
|
||||
.success(() => {
|
||||
resolve();
|
||||
})
|
||||
.error((errorData: any) => {
|
||||
reject(errorData);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
authenticated(): boolean {
|
||||
return KeycloakService.keycloakAuth.authenticated;
|
||||
}
|
||||
|
||||
login() {
|
||||
KeycloakService.keycloakAuth.login();
|
||||
}
|
||||
|
||||
logout() {
|
||||
KeycloakService.keycloakAuth.logout();
|
||||
}
|
||||
|
||||
account() {
|
||||
KeycloakService.keycloakAuth.accountManagement();
|
||||
}
|
||||
|
||||
authServerUrl(): string {
|
||||
return KeycloakService.keycloakAuth.authServerUrl;
|
||||
}
|
||||
|
||||
realm(): string {
|
||||
return KeycloakService.keycloakAuth.realm;
|
||||
}
|
||||
|
||||
getToken(): Promise<string> {
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
if (KeycloakService.keycloakAuth.token) {
|
||||
KeycloakService.keycloakAuth
|
||||
.updateToken(5)
|
||||
.success(() => {
|
||||
resolve(<string>KeycloakService.keycloakAuth.token);
|
||||
})
|
||||
.error(() => {
|
||||
reject('Failed to refresh token');
|
||||
});
|
||||
} else {
|
||||
reject('Not loggen in');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* 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 {Observable} from 'rxjs/Observable';
|
||||
import {TranslateLoader} from '@ngx-translate/core';
|
||||
|
||||
declare const l18n_msg: any;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export class DeclaredVarTranslateLoader implements TranslateLoader {
|
||||
|
||||
getTranslation(lang: string): Observable<any> {
|
||||
return Observable.of(l18n_msg);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* 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} from '@angular/core';
|
||||
import {TranslateService} from '@ngx-translate/core';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Injectable()
|
||||
export class TranslateUtil {
|
||||
constructor(private translator: TranslateService) {
|
||||
}
|
||||
|
||||
public translate(key: string) : string {
|
||||
// remove Freemarker syntax
|
||||
if (key.startsWith('${') && key.endsWith('}')) {
|
||||
key = key.substring(2, key.length - 1);
|
||||
}
|
||||
|
||||
return this.translator.instant(key);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
export type Vendor = "fa" | "pficon";
|
||||
|
||||
export class Icon {
|
||||
constructor(public vendor: Vendor, public name: string) {
|
||||
}
|
||||
|
||||
getClasses(): string {
|
||||
return `${this.vendor} ${this.vendor}-${this.name}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 {TranslateUtil} from '../ngx-translate/translate.util';
|
||||
|
||||
declare const referrer: string;
|
||||
declare const referrer_uri: string;
|
||||
|
||||
/**
|
||||
* Encapsulate referrer logic.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export class Referrer {
|
||||
|
||||
constructor(private translateUtil: TranslateUtil) {}
|
||||
|
||||
public exists(): boolean {
|
||||
return typeof referrer !== "undefined";
|
||||
}
|
||||
|
||||
// return a value suitable for parameterized use with ngx-translate
|
||||
// example {{'backTo' | translate:referrer.getName()}}
|
||||
public getName(): { param_0: string } {
|
||||
return {param_0: this.translateUtil.translate(referrer) };
|
||||
}
|
||||
|
||||
public getUri(): string {
|
||||
return referrer_uri;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* 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;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* 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 { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
* Case insensitive filtering.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Pipe({name: 'filterby'})
|
||||
export class FilterbyPipe implements PipeTransform {
|
||||
transform(objects: any[], property: string, text: string) {
|
||||
if (!property) return objects;
|
||||
if (!text) return objects;
|
||||
|
||||
const transformed: any[] = [];
|
||||
for (let obj of objects) {
|
||||
let propVal:any = obj[property];
|
||||
if (!this.isString(propVal)) {
|
||||
console.error("Can't filter property " + property + ". Its value is not a string.");
|
||||
break;
|
||||
}
|
||||
|
||||
let strPropVal:string = propVal as string;
|
||||
if (strPropVal.toLowerCase().indexOf(text.toLowerCase()) != -1) {
|
||||
transformed.push(obj);
|
||||
}
|
||||
}
|
||||
|
||||
return transformed;
|
||||
}
|
||||
|
||||
private isString(value: any): boolean {
|
||||
return (typeof value == 'string') || (value instanceof String);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Pipe({name: 'orderby'})
|
||||
export class OrderbyPipe implements PipeTransform {
|
||||
transform(objects: any[], property: string, descending: boolean = true) {
|
||||
if (!property) return objects;
|
||||
|
||||
let sorted: any[] = objects.sort((obj1, obj2) => {
|
||||
if (obj1[property] > obj2[property]) return 1;
|
||||
if (obj1[property] < obj2[property]) return -1;
|
||||
return 0;
|
||||
})
|
||||
|
||||
if (descending) {
|
||||
return sorted;
|
||||
} else {
|
||||
return sorted.reverse();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* For this application, we are responsive to three sizes: large, medium, and
|
||||
* small. Note that these do not perfectly correspond to bootstrap device
|
||||
* sizes but are more in line with patternfly.
|
||||
*
|
||||
* When making decisions based on screen size, you should create a single
|
||||
* instance of this class and then test for each size. Do not use several
|
||||
* instances because the screen size could change at any time and you may
|
||||
* get inconsistent results.
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
export class Media {
|
||||
|
||||
private screenWidth: number = window.innerWidth;
|
||||
|
||||
public isLarge(): boolean {
|
||||
return this.screenWidth > 1023;
|
||||
}
|
||||
|
||||
public isMedium(): boolean {
|
||||
return (this.screenWidth < 1023) && (this.screenWidth > 768);
|
||||
}
|
||||
|
||||
public isSmall(): boolean {
|
||||
return this.screenWidth < 769;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* 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 } from "@angular/core";
|
||||
|
||||
import { Media } from "./media";
|
||||
|
||||
export type SideNavClasses = "" | "collapsed" | "hidden" | "hidden show-mobile-nav";
|
||||
export type ContentWidthClass = "" | "collapsed-nav" | "hidden-nav";
|
||||
|
||||
export interface MenuClickListener {
|
||||
menuClicked(): void;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Stan Silvert ssilvert@redhat.com (C) 2017 Red Hat Inc.
|
||||
*/
|
||||
@Injectable()
|
||||
export class ResponsivenessService {
|
||||
|
||||
private menuOn: boolean = false;
|
||||
private menuListeners: MenuClickListener[] = Array<MenuClickListener>();
|
||||
|
||||
public addMenuClickListener(listener: MenuClickListener) {
|
||||
this.menuListeners.push(listener);
|
||||
}
|
||||
|
||||
public menuClicked() : void {
|
||||
this.menuOn = !this.menuOn;
|
||||
|
||||
for (let listener of this.menuListeners) {
|
||||
listener.menuClicked();
|
||||
}
|
||||
}
|
||||
|
||||
public calcSideNavWidthClasses() : SideNavClasses {
|
||||
const media: Media = new Media();
|
||||
|
||||
if (media.isLarge() && !this.menuOn) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (media.isLarge() && this.menuOn) {
|
||||
return "collapsed";
|
||||
}
|
||||
|
||||
if (media.isMedium() && !this.menuOn) {
|
||||
return "collapsed";
|
||||
}
|
||||
|
||||
if (media.isMedium() && this.menuOn) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// media must be small
|
||||
if (!this.menuOn) {
|
||||
return "hidden"
|
||||
}
|
||||
|
||||
return "hidden show-mobile-nav";
|
||||
}
|
||||
|
||||
public calcSideContentWidthClass() : ContentWidthClass {
|
||||
const media: Media = new Media();
|
||||
|
||||
if (media.isLarge() && !this.menuOn) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (media.isLarge() && this.menuOn) {
|
||||
return "collapsed-nav";
|
||||
}
|
||||
|
||||
if (media.isMedium() && !this.menuOn) {
|
||||
return "collapsed-nav";
|
||||
}
|
||||
|
||||
if (media.isMedium() && this.menuOn) {
|
||||
return "";
|
||||
}
|
||||
|
||||
// media must be small
|
||||
if (!this.menuOn) {
|
||||
return "hidden-nav"
|
||||
}
|
||||
|
||||
return "hidden-nav";
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
<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>-->
|
||||
|
|
@ -0,0 +1,109 @@
|
|||
/*
|
||||
* 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";
|
||||
|
||||
@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("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);
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
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() {
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
<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>
|
|
@ -0,0 +1,43 @@
|
|||
/*
|
||||
* 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);
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
* 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";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
|
||||
|
||||
<nav class="navbar navbar-pf-alt">
|
||||
<notification></notification>
|
||||
|
||||
<div class="navbar-header">
|
||||
<button (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.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="dropdownMenu1" data-toggle="dropdown" aria-haspopup="true" aria-expanded="true">
|
||||
<span title="Help" class="fa pficon-help"></span>
|
||||
<span class="caret"></span>
|
||||
</a>
|
||||
<ul class="dropdown-menu" aria-labelledby="dropdownMenu1">
|
||||
<li><a href="#">Help</a></li>
|
||||
<li><a href="#">About</a></li>
|
||||
</ul>
|
||||
</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="#">Preferences</a></li>
|
||||
<li><a href="#" (click)="logout()">{{'doSignOut' | translate}}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
</nav> <!--/.navbar-->
|
|
@ -0,0 +1,53 @@
|
|||
/*
|
||||
* 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} 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 {Referrer} from "../page/referrer";
|
||||
|
||||
declare const resourceUrl: string;
|
||||
declare const referrer: string;
|
||||
declare const referrer_uri: string;
|
||||
|
||||
@Component({
|
||||
selector: 'app-top-nav',
|
||||
templateUrl: './top-nav.component.html',
|
||||
styleUrls: ['./top-nav.component.css']
|
||||
})
|
||||
export class TopNavComponent implements OnInit {
|
||||
|
||||
public resourceUrl: string = resourceUrl;
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
export const environment = {
|
||||
production: true
|
||||
};
|
|
@ -0,0 +1,8 @@
|
|||
// The file contents for the current environment will overwrite these during build.
|
||||
// The build system defaults to the dev environment which uses `environment.ts`, but if you do
|
||||
// `ng build --env=prod` then `environment.prod.ts` will be used instead.
|
||||
// The list of which env maps to which file can be found in `.angular-cli.json`.
|
||||
|
||||
export const environment = {
|
||||
production: false
|
||||
};
|
|
@ -0,0 +1,46 @@
|
|||
/*
|
||||
* 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 { enableProdMode } from '@angular/core';
|
||||
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||
import { platformBrowser } from '@angular/platform-browser';
|
||||
|
||||
import { AppModule } from './app/app.module';
|
||||
//import { environment } from './environments/environment';
|
||||
|
||||
import { KeycloakService } from './app/keycloak-service/keycloak.service';
|
||||
|
||||
//if (environment.production) {
|
||||
// enableProdMode();
|
||||
//}
|
||||
|
||||
declare const authUrl: string;
|
||||
declare const resourceUrl: string;
|
||||
declare const realm: string;
|
||||
|
||||
const noLogin: boolean = false; // convenient for development
|
||||
if (noLogin) {
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
} else {
|
||||
KeycloakService.init(authUrl + '/realms/' + realm + '/account/keycloak.json',
|
||||
{onLoad: 'login-required'})
|
||||
.then(() => {
|
||||
platformBrowserDynamic().bootstrapModule(AppModule);
|
||||
})
|
||||
.catch((e: any) => {
|
||||
console.log('Error in bootstrap: ' + JSON.stringify(e));
|
||||
});
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
{
|
||||
"name": "keycloak-preview",
|
||||
"version": "1.0.0",
|
||||
"description": "keycloak-preview account management written in Angular 2",
|
||||
"scripts": {
|
||||
"build": "tsc -p ./",
|
||||
"build:watch": "tsc -p ./ -w",
|
||||
"build:e2e": "tsc -p e2e/",
|
||||
"serve": "lite-server -c=bs-config.json",
|
||||
"serve:e2e": "lite-server -c=bs-config.e2e.json",
|
||||
"prestart": "npm run build",
|
||||
"start": "concurrently \"npm run build:watch\" \"npm run serve\"",
|
||||
"pree2e": "npm run build:e2e",
|
||||
"e2e": "concurrently \"npm run serve:e2e\" \"npm run protractor\" --kill-others --success first",
|
||||
"preprotractor": "webdriver-manager update",
|
||||
"protractor": "protractor protractor.config.js",
|
||||
"pretest": "npm run build",
|
||||
"test": "concurrently \"npm run build:watch\" \"karma start karma.conf.js\"",
|
||||
"pretest:once": "npm run build",
|
||||
"test:once": "karma start karma.conf.js --single-run",
|
||||
"lint": "tslint ./**/*.ts --exclude ./node_modules/** -t verbose"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Stan Silvert",
|
||||
"license": "Apache 2.0",
|
||||
"dependencies": {
|
||||
"@angular/common": "~4.0.0",
|
||||
"@angular/compiler": "~4.0.0",
|
||||
"@angular/core": "~4.0.0",
|
||||
"@angular/forms": "~4.0.0",
|
||||
"@angular/http": "~4.0.0",
|
||||
"@angular/platform-browser": "~4.0.0",
|
||||
"@angular/platform-browser-dynamic": "~4.0.0",
|
||||
"@angular/router": "~4.0.0",
|
||||
"@ngx-translate/core": "^7.1.0",
|
||||
"core-js": "^2.4.1",
|
||||
"patternfly": "^3.23.2",
|
||||
"rxjs": "^5.4.2",
|
||||
"systemjs": "^0.20.17",
|
||||
"zone.js": "^0.8.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "2.5.36",
|
||||
"@types/node": "^6.0.46",
|
||||
"canonical-path": "0.0.2",
|
||||
"concurrently": "^3.2.0",
|
||||
"jasmine-core": "~2.4.1",
|
||||
"karma": "^1.3.0",
|
||||
"karma-chrome-launcher": "^2.0.0",
|
||||
"karma-cli": "^1.0.1",
|
||||
"karma-jasmine": "^1.0.2",
|
||||
"karma-jasmine-html-reporter": "^0.2.2",
|
||||
"lite-server": "^2.2.2",
|
||||
"lodash": "^4.16.4",
|
||||
"protractor": "~4.0.14",
|
||||
"rimraf": "^2.5.4",
|
||||
"tslint": "^3.15.1",
|
||||
"typescript": "^2.4.2"
|
||||
},
|
||||
"repository": {}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
h1 {
|
||||
color: #369;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
font-size: 250%;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
var templateUrlRegex = /templateUrl\s*:(\s*['"`](.*?)['"`]\s*)/gm;
|
||||
var stylesRegex = /styleUrls *:(\s*\[[^\]]*?\])/g;
|
||||
var stringRegex = /(['`"])((?:[^\\]\\\1|.)*?)\1/g;
|
||||
|
||||
module.exports.translate = function(load){
|
||||
var url = document.createElement('a');
|
||||
url.href = load.address;
|
||||
|
||||
var basePathParts = url.pathname.split('/');
|
||||
|
||||
basePathParts.pop();
|
||||
var basePath = basePathParts.join('/');
|
||||
|
||||
var baseHref = document.createElement('a');
|
||||
baseHref.href = this.baseURL;
|
||||
baseHref = baseHref.pathname;
|
||||
|
||||
basePath = basePath.replace(baseHref, '');
|
||||
|
||||
load.source = load.source
|
||||
.replace(templateUrlRegex, function(match, quote, url){
|
||||
var resolvedUrl = url;
|
||||
|
||||
if (url.startsWith('.')) {
|
||||
resolvedUrl = basePath + url.substr(1);
|
||||
}
|
||||
|
||||
return 'templateUrl: "' + resolvedUrl + '"';
|
||||
})
|
||||
.replace(stylesRegex, function(match, relativeUrls) {
|
||||
var urls = [];
|
||||
|
||||
while ((match = stringRegex.exec(relativeUrls)) !== null) {
|
||||
if (match[2].startsWith('.')) {
|
||||
urls.push('"' + basePath + match[2].substr(1) + '"');
|
||||
} else {
|
||||
urls.push('"' + match[2] + '"');
|
||||
}
|
||||
}
|
||||
|
||||
return "styleUrls: [" + urls.join(', ') + "]";
|
||||
});
|
||||
|
||||
return load;
|
||||
};
|
|
@ -0,0 +1,11 @@
|
|||
/**
|
||||
* Add barrels and stuff
|
||||
* Adjust as necessary for your application needs.
|
||||
*/
|
||||
// (function (global) {
|
||||
// System.config({
|
||||
// packages: {
|
||||
// // add packages here
|
||||
// }
|
||||
// });
|
||||
// })(this);
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* System configuration for Angular samples
|
||||
* Adjust as necessary for your application needs.
|
||||
*/
|
||||
(function (global) {
|
||||
System.config({
|
||||
paths: {
|
||||
// paths serve as alias
|
||||
'npm:': resourceUrl + '/node_modules/'
|
||||
},
|
||||
// map tells the System loader where to look for things
|
||||
map: {
|
||||
// our app is within the app folder
|
||||
'app': resourceUrl + '/app',
|
||||
|
||||
// angular bundles
|
||||
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
|
||||
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
|
||||
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
|
||||
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
|
||||
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
|
||||
'@angular/http': 'npm:@angular/http/bundles/http.umd.js',
|
||||
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
|
||||
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
|
||||
|
||||
// other libraries
|
||||
'@ngx-translate/core': 'npm:@ngx-translate/core/bundles/core.umd.js',
|
||||
'rxjs': 'npm:rxjs',
|
||||
'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
|
||||
},
|
||||
// packages tells the System loader how to load when no filename and/or no extension
|
||||
packages: {
|
||||
app: {
|
||||
defaultExtension: 'js',
|
||||
meta: {
|
||||
'./*.js': {
|
||||
loader: resourceUrl + '/systemjs-angular-loader.js'
|
||||
}
|
||||
}
|
||||
},
|
||||
'@ngx-translate/core': {
|
||||
defaultExtension: 'js'
|
||||
},
|
||||
rxjs: {
|
||||
defaultExtension: 'js'
|
||||
}
|
||||
}
|
||||
});
|
||||
})(this);
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "es5",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"lib": [ "es2015", "dom" ],
|
||||
"noImplicitAny": true,
|
||||
"suppressImplicitAnyIndexErrors": true
|
||||
}
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
{
|
||||
"rules": {
|
||||
"class-name": true,
|
||||
"comment-format": [
|
||||
true,
|
||||
"check-space"
|
||||
],
|
||||
"curly": true,
|
||||
"eofline": true,
|
||||
"forin": true,
|
||||
"indent": [
|
||||
true,
|
||||
"spaces"
|
||||
],
|
||||
"label-position": true,
|
||||
"label-undefined": true,
|
||||
"max-line-length": [
|
||||
true,
|
||||
140
|
||||
],
|
||||
"member-access": false,
|
||||
"member-ordering": [
|
||||
true,
|
||||
"static-before-instance",
|
||||
"variables-before-functions"
|
||||
],
|
||||
"no-arg": true,
|
||||
"no-bitwise": true,
|
||||
"no-console": [
|
||||
true,
|
||||
"debug",
|
||||
"info",
|
||||
"time",
|
||||
"timeEnd",
|
||||
"trace"
|
||||
],
|
||||
"no-construct": true,
|
||||
"no-debugger": true,
|
||||
"no-duplicate-key": true,
|
||||
"no-duplicate-variable": true,
|
||||
"no-empty": false,
|
||||
"no-eval": true,
|
||||
"no-inferrable-types": true,
|
||||
"no-shadowed-variable": true,
|
||||
"no-string-literal": false,
|
||||
"no-switch-case-fall-through": true,
|
||||
"no-trailing-whitespace": true,
|
||||
"no-unused-expression": true,
|
||||
"no-unused-variable": true,
|
||||
"no-unreachable": true,
|
||||
"no-use-before-declare": true,
|
||||
"no-var-keyword": true,
|
||||
"object-literal-sort-keys": false,
|
||||
"one-line": [
|
||||
true,
|
||||
"check-open-brace",
|
||||
"check-catch",
|
||||
"check-else",
|
||||
"check-whitespace"
|
||||
],
|
||||
"quotemark": [
|
||||
true,
|
||||
"single"
|
||||
],
|
||||
"radix": true,
|
||||
"semicolon": [
|
||||
"always"
|
||||
],
|
||||
"triple-equals": [
|
||||
true,
|
||||
"allow-null-check"
|
||||
],
|
||||
"typedef-whitespace": [
|
||||
true,
|
||||
{
|
||||
"call-signature": "nospace",
|
||||
"index-signature": "nospace",
|
||||
"parameter": "nospace",
|
||||
"property-declaration": "nospace",
|
||||
"variable-declaration": "nospace"
|
||||
}
|
||||
],
|
||||
"variable-name": false,
|
||||
"whitespace": [
|
||||
true,
|
||||
"check-branch",
|
||||
"check-decl",
|
||||
"check-operator",
|
||||
"check-separator",
|
||||
"check-type"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load diff
|
@ -0,0 +1,3 @@
|
|||
parent=base
|
||||
import=login/keycloak
|
||||
deprecatedMode=false
|
Loading…
Reference in a new issue