feat: Basic support for custom logo, css, and favicon
MODIFY
README.md
README.md
@@ -28,7 +28,7 @@ The developer portal requires following realm roles in keycloak to allow users a
| devportaluser | The role to get access as developer/client to use managed apis |
| apiadmin | The role to get access as admin to share apis with developers via 'Administration' menu entry |
-The [apiman manager](https://github.com/apiman/apiman) delivers also keycloak groups to assign the above roles:
+These roles are assigned to users via following groups:
| Keycloak groups | Description |
|--------------------------|------------------------------------------------------------------------------------------------|
@@ -36,7 +36,8 @@ The [apiman manager](https://github.com/apiman/apiman) delivers also keycloak gr
| API-Mgmt-Devportal-Users | The group to get access as developer/client |
| API-Mgmt-Administrators | The group to get access as admin to share apis with developers via 'Administration' menu entry |
-**Note:** We prefer assigning users to the groups, not assigning the roles directly to the users. Just for comfort reasons, if at anytime more roles will introduced for an application like the developer portal. If this is the case you can simply change the group settings instead assigning each user the new roles.
+**Note:** You have to assign the groups to the user instead of assigning the roles directly to the users!
+You can find an example [keycloak-realm](https://github.com/apiman/apiman/blob/master/distro/data/src/main/resources/data/apiman-realm-for-keycloak.json) in the manager project to set up these groups in keycloak.
## Get the code
@@ -56,56 +57,45 @@ At any time, you can pull changes from the upstream and merge them onto your mai
The general idea is to keep your 'main' branch in-sync with the 'upstream/main'.
-## Running the Developer Portal as docker container
-
-### Build docker image locally
+## Running the Developer Portal
To build the docker image locally you have to run the following command or use Intellij run configurations:
-`docker build -t apiman-devportal:latest .`
-
-### Start with docker-compose
-
-You can start the docker container with
-`docker-compose up`
+```bash
+docker build -t apiman-devportal:latest .
+docker-compose up
+```
This command uses the existing [docker-compose.yml file](docker-compose.yml) with configured container environment variables.
You may need to change these environment variables for your production setup. See configuration in the next chapter.
-### Docker container environment variables
-
-To ensure that the developer portal docker container can start correctly you have to configure some environment variables.
-
-| Variable | Example | Description |
-|----------------------|-------------------------------|---------------------------------------------------------------------------|
-| API_MGMT_UI_REST_URL | https://localhost:8443/apiman | the address of the [apiman manager](https://github.com/apiman/apiman) ui |
-| KEYCLOAK_AUTH_URL | https://localhost:8443/auth | the address of the keycloak server |
-| KEYCLOAK_REALM | apiman | the realm name for your apiman installation configured in keycloak |
-
### Technical background:
On container start the script [start-nginx-with-devportal.sh](docker/start-nginx-with-devportal.sh) uses the environment variable values to configure the angular app environment settings for your apiman installation.
You can find these angular app settings in [environment.prod.ts](/src/environments/environment.prod.ts).
+If you want to start the Developer Portal Angular App directly without docker (e.g. for local development) then you may have to modify the angular [environment.ts](/src/environments/environment.ts) file to match your local machine / docker environment.
The idea behind this mechanism is from this nice [blog entry](https://blog.codecentric.de/en/2019/03/docker-angular-dockerize-app-easily/).
-## Further Documentation
+## Environment Variables for local development and docker
-For more information please check out our [documentation](https://doc.scheer-pas.com/display/APIMGMNT/Developer+Portal).
-
-Please open a new [github issue](https://github.com/apiman/apiman-developer-portal/issues) for documentation changes.
+In the angular environment the variables have a slightly different name.
-## Modifications for local development on the angular app
+| Docker Environment | Angular Environment | Example | Description | Required |
+|----------------------|---------------------|---------------------------------|--------------------------------------------------------------------------|----------|
+| API_MGMT_UI_REST_URL | apiMgmtUiRestUrl | https://localhost:8443/apiman | the address of the [apiman manager](https://github.com/apiman/apiman) ui | yes |
+| KEYCLOAK_AUTH_URL | keycloakAuthUrl | https://localhost:8443/auth | the address of the keycloak server | yes |
+| KEYCLOAK_REALM | apiMgmtRealm | apiman | the realm name for your apiman installation configured in keycloak | yes |
+| LOGO_FILE_URL | logoUrl | https://localhost:8443/logo.png | an optional url to a custom logo | optional |
+| FAVICON_FILE_URL | - | https://localhost:8443/fav.ico | an optional url to a custom favicon | optional |
+| CSS_FILE_URL | - | https://localhost:8443/my.css | an optional url to a custom css url | optional |
-If you want to start the Developer Portal Angular App directly without docker (e.g. for local development) then you may have to modify the angular [environment.ts](/src/environments/environment.ts) file to match your local machine / docker environment.
+## Further Documentation
-In the angular environment the variables have a slightly different name but the description is the same as in the last chapter:
+For more information please check out our [documentation](https://doc.scheer-pas.com/display/APIMGMNT/Developer+Portal).
+Please open a new [github issue](https://github.com/apiman/apiman-developer-portal/issues) for documentation changes.
-| Docker Environment | Angular Environment | Example | Description |
-|----------------------|---------------------|-------------------------------|-----------------------------------------------------------------------------|
-| API_MGMT_UI_REST_URL | apiMgmtUiRestUrl | https://localhost:8443/apiman | the address of the [apiman manager](https://github.com/apiman/apiman) ui |
-| KEYCLOAK_AUTH_URL | keycloakAuthUrl | https://localhost:8443/auth | the address of the keycloak server |
-| KEYCLOAK_REALM | apiMgmtRealm | apiman | the realm name for your apiman installation configured in keycloak |
+## Local development notes
After these changes you can run `ng serve` for a dev server. Navigate to `http://localhost:80/`. The app will automatically reload if you change any of the source files.

MODIFY
docker/start-nginx-with-devportal.sh
docker/start-nginx-with-devportal.sh
#!/bin/sh
+# Check if a prefix exists
+if [ -n "$SYSTEM_PREFIX" ]
+then
+ # If a prefix is set we will replace the base path in index.html before starting the application
+ # Replace existing base tag with base tag containing system prefix src e.g. "/pas/devportal/"
+ sed -i "s|<base.*>|<base href=\"/$SYSTEM_PREFIX/devportal/\">|g" "/usr/share/nginx/html/index.html"
+fi
+
+
+# Check if custom css is needed
+if [ -n "$CSS_FILE_URL" ]
+then
+ printf "Using custom css url: %s\n" "$CSS_FILE_URL"
+ sed -i "s|<!--CustomCSS-->|<!--CSS-->\n <link rel=\"stylesheet\" href=\"$CSS_FILE_URL\">|g" "/usr/share/nginx/html/index.html"
+fi
+
+
+# Check if custom favicon is needed
+if [ -n "$FAVICON_FILE_URL" ]
+then
+ printf "Using custom favicon url: %s\n" "$FAVICON_FILE_URL"
+fi
+sed -i "s|FAVICON_FILE_URL|${FAVICON_FILE_URL:-favicon.ico}|g" "/usr/share/nginx/html/index.html"
+
+
+# Set the default logo
+if [ -n "$LOGO_FILE_URL" ]
+then
+ printf "Using custom img url: %s\n" "$LOGO_FILE_URL"
+else
+ export LOGO_FILE_URL="assets/logo-header.png"
+fi
+
+
# replace all occurences of placeholders with environment variables
# see https://blog.codecentric.de/en/2019/03/docker-angular-dockerize-app-easily/
for mainFileName in /usr/share/nginx/html/main*.js
do
- envsubst "\$KEYCLOAK_REALM \$API_MGMT_UI_REST_URL \$KEYCLOAK_AUTH_URL" < "$mainFileName" > main.tmp
+ envsubst "\$KEYCLOAK_REALM \$API_MGMT_UI_REST_URL \$KEYCLOAK_AUTH_URL \$LOGO_FILE_URL" < "$mainFileName" > main.tmp
mv main.tmp "${mainFileName}"
done
-# Check if a prefix exists
-if [ -n "$SYSTEM_PREFIX" ]
-then
- # If a prefix is set we will replace the base path in index.html before starting the application
- # Replace existing base tag with base tag containing system prefix src e.g. "/pas/devportal/"
- sed -i "s/<base.*>/<base href=\"\/$SYSTEM_PREFIX\/devportal\/\">/g" "/usr/share/nginx/html/index.html"
-fi
-
-echo "Starting devportal"
+printf "Starting developer portal:\n"
# start nginx
nginx -g 'daemon off;'

MODIFY
src/app/app.module.ts
src/app/app.module.ts
@@ -103,18 +103,6 @@ import {TokenService} from './services/token.service';
useFactory: initializer,
multi: true,
deps: [KeycloakService, TokenService]
- },
- {
- provide: 'API_MGMT_UI_REST_URL',
- useValue: environment.apiMgmtUiRestUrl
- },
- {
- provide: 'KEYCLOAK_AUTH_URL',
- useValue: environment.keycloakAuthUrl
- },
- {
- provide: 'API_MGTM_REALM',
- useValue: environment.apiMgmtRealm
}
],
bootstrap: [AppComponent]

MODIFY
src/app/components/admin/services/admin.service.ts
src/app/components/admin/services/admin.service.ts
* limitations under the License.
*/
-import {Inject, Injectable} from '@angular/core';
import {from, Observable, of} from 'rxjs';
import {mergeMap} from 'rxjs/operators';
import {ClientSearchResult, Developer} from '../../../services/api-data.service';
import {HttpClient} from '@angular/common/http';
import {KeycloakInteractionService} from './keycloak-interaction.service';
+import {environment} from '../../../../environments/environment';
+import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class AdminService {
+ private apiMgmtUiRestUrl: string = environment.apiMgmtUiRestUrl;
+
/**
* Constructor of Admin Service
* @param http Http Client
@@ -33,8 +36,7 @@ export class AdminService {
* @param apiMgmtUiRestUrl Api Management UI REST Url
*/
constructor(private http: HttpClient,
- private keycloak: KeycloakInteractionService, @Inject('API_MGMT_UI_REST_URL')
- private apiMgmtUiRestUrl: string) {
+ private keycloak: KeycloakInteractionService) {
}
/**

MODIFY
src/app/components/admin/services/keycloak-interaction.service.ts
src/app/components/admin/services/keycloak-interaction.service.ts
* limitations under the License.
*/
-import {Inject, Injectable} from '@angular/core';
import {KeycloakService} from 'keycloak-angular';
import KcAdminClient from 'keycloak-admin';
import {TokenService} from '../../../services/token.service';
import UserRepresentation from 'keycloak-admin/lib/defs/userRepresentation';
import GroupRepresentation from 'keycloak-admin/lib/defs/groupRepresentation';
+import {environment} from '../../../../environments/environment';
+import {Injectable} from '@angular/core';
@Injectable({
providedIn: 'root'
@@ -28,19 +29,16 @@ export class KeycloakInteractionService {
private kcAdminClient: KcAdminClient;
-
/**
* Constructor of Keycloak Interaction Service
* @param keycloak the keycloak service
* @param keycloakRestUrl the keycloak rest url
*/
constructor(private keycloak: KeycloakService,
- private tokenService: TokenService,
- @Inject('KEYCLOAK_AUTH_URL') private keycloakRestUrl: string,
- @Inject('API_MGTM_REALM') private apiMgmtRealm: string) {
+ private tokenService: TokenService) {
this.kcAdminClient = new KcAdminClient({
- baseUrl: this.keycloakRestUrl,
- realmName: this.apiMgmtRealm
+ baseUrl: environment.keycloakAuthUrl,
+ realmName: environment.apiMgmtRealm,
});
// set initial token
const token = keycloak.getKeycloakInstance().token;

MODIFY
src/app/components/app-header/header.component.html
src/app/components/app-header/header.component.html
-->
<nav>
- <img routerLink="/" class="logo clickable" src="assets/logo-header.png">
+ <img routerLink="/" src="{{ logo }}" class="logo clickable" alt="Logo">
+
<div class="rightMenu">
<div class="floatRight">
<mat-icon id="header-icon">desktop_windows</mat-icon>

MODIFY
src/app/components/app-header/header.component.ts
src/app/components/app-header/header.component.ts
import {Component} from '@angular/core';
import {KeycloakService} from 'keycloak-angular';
+import {environment} from '../../../environments/environment';
@Component({
selector: 'app-app-header',
@@ -25,9 +26,9 @@ import {KeycloakService} from 'keycloak-angular';
export class HeaderComponent {
public user: string = this.keycloak.getKeycloakInstance().profile.username;
+ public logo: string = environment.logoUrl;
- constructor(private keycloak: KeycloakService) {
- }
+ constructor(private keycloak: KeycloakService) {}
/**
* Logout a user and clear the session tokens

MODIFY
src/app/components/developer/api-list/api-list.component.ts
src/app/components/developer/api-list/api-list.component.ts
* limitations under the License.
*/
-import {Component, Inject, OnInit} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
import {forkJoin, from, Observable, Subject} from 'rxjs';
import {concatMap, map, mergeAll, mergeMap, switchMap, toArray} from 'rxjs/operators';
import {ApiDataService, ApiVersion, Client, Contract} from '../../../services/api-data.service';
@@ -25,6 +25,7 @@ import {animate, state, style, transition, trigger} from '@angular/animations';
import {Sort} from '@angular/material/sort';
import {KeycloakService} from 'keycloak-angular';
import {formatDate} from '@angular/common';
+import {environment} from '../../../../environments/environment';
export interface ApiListElement {
id: string;
@@ -72,6 +73,8 @@ class GatewayEndpoint {
export class ApiListComponent implements OnInit {
+ private apiMgmtUiRestUrl: string = environment.apiMgmtUiRestUrl;
+
columnHeaders: string[] = ['api', 'publicAPI', 'apiVersion', 'tryApi', 'options'];
expandedElements: Array<ApiListElement> = [];
@@ -94,8 +97,7 @@ export class ApiListComponent implements OnInit {
private apiDataService: ApiDataService,
private toasterService: ToasterService,
private loadingSpinnerService: SpinnerService,
- private keycloak: KeycloakService,
- @Inject('API_MGMT_UI_REST_URL') private apiMgmtUiRestUrl: string) {
+ private keycloak: KeycloakService) {
}
/**

MODIFY
src/app/components/swagger/swagger.component.ts
src/app/components/swagger/swagger.component.ts
* limitations under the License.
*/
-import {Component, Inject, OnInit} from '@angular/core';
+import {Component, OnInit} from '@angular/core';
import {ActivatedRoute} from '@angular/router';
+import {environment} from '../../../environments/environment';
declare const SwaggerUIBundle: any;
@@ -26,7 +27,10 @@ declare const SwaggerUIBundle: any;
})
export class SwaggerComponent implements OnInit {
- constructor(@Inject('API_MGMT_UI_REST_URL') private apiMgmtUiRestUrl: string, private route: ActivatedRoute) { }
+ private apiMgmtUiRestUrl: string = environment.apiMgmtUiRestUrl;
+
+ constructor(private route: ActivatedRoute) {
+ }
/**
* Load the swagger definition and display it with the swagger ui bundle library on component initialization

MODIFY
src/app/services/api-data.service.spec.ts
src/app/services/api-data.service.spec.ts
import {TestBed} from '@angular/core/testing';
import {ApiDataService} from './api-data.service';
-import {KeycloakService} from 'keycloak-angular';
import {HttpClient} from '@angular/common/http';
describe('ApiDataService', () => {
const httpClient = jasmine.createSpy('HttpClient');
beforeEach(() => TestBed.configureTestingModule({
- providers: [
- { provide: HttpClient, useValue: httpClient},
- { provide: 'API_MGMT_UI_REST_URL', useValue: 'https://dummyURL.com/apiman' }
- ]
+ providers: [{
+ provide: HttpClient, useValue: httpClient
+ }]
}));
it('should be created', () => {
- const service: ApiDataService = TestBed.get(ApiDataService);
+ const service: ApiDataService = TestBed.inject(ApiDataService);
expect(service).toBeTruthy();
});
});

MODIFY
src/app/services/api-data.service.ts
src/app/services/api-data.service.ts
* limitations under the License.
*/
-import {Inject, Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';
+import {environment} from '../../environments/environment';
+import {Injectable} from '@angular/core';
/**
* Api Version
@@ -183,13 +184,13 @@ export interface GatewayEndpoint {
*/
export class ApiDataService {
+ private apiMgmtUiRestUrl: string = environment.apiMgmtUiRestUrl;
+
/**
* Constructor
* @param http The http client
- * @param apiMgmtUiRestUrl The Api Mgmt UI REST url
*/
- constructor(private http: HttpClient, @Inject('API_MGMT_UI_REST_URL') private apiMgmtUiRestUrl: string) {
- }
+ constructor(private http: HttpClient) {}
/**
* Get all public apis

MODIFY
src/environments/environment.prod.ts
src/environments/environment.prod.ts
@@ -18,5 +18,6 @@ export const environment = {
production: true,
apiMgmtRealm: '${KEYCLOAK_REALM}',
apiMgmtUiRestUrl: '${API_MGMT_UI_REST_URL}',
- keycloakAuthUrl: '${KEYCLOAK_AUTH_URL}'
+ keycloakAuthUrl: '${KEYCLOAK_AUTH_URL}',
+ logoUrl: '${LOGO_FILE_URL}'
};

MODIFY
src/environments/environment.ts
src/environments/environment.ts
@@ -22,7 +22,8 @@ export const environment = {
production: false,
apiMgmtRealm: '[keycloakRealm]',
apiMgmtUiRestUrl: 'https://[apimanUiRestEndpoint]:[port]/apiman',
- keycloakAuthUrl: 'https://[keycloakEndpoint]:[port]/auth'
+ keycloakAuthUrl: 'https://[keycloakEndpoint]:[port]/auth',
+ logoUrl: 'assets/logo-header.png'
};
/*

MODIFY
src/index.html
src/index.html
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
- <link rel="icon" type="image/x-icon" href="favicon.ico">
+ <link rel="icon" type="image/x-icon" href="FAVICON_FILE_URL">
+ <!--CustomCSS-->
</head>
<body>
<app-root></app-root>

feat: Basic support for custom logo, css, and favicon