Skip to content
This repository has been archived by the owner on Mar 28, 2022. It is now read-only.

Commit

Permalink
chore(todo): support local mindsphere development mode
Browse files Browse the repository at this point in the history
provide support for executing local angular dev server connected
to the mindsphere remote services for local development
  • Loading branch information
dlouzan committed Dec 4, 2018
1 parent ce6510a commit 72cb937
Show file tree
Hide file tree
Showing 21 changed files with 271 additions and 33 deletions.
8 changes: 4 additions & 4 deletions .gitlab-ci.yml
Expand Up @@ -41,8 +41,8 @@ test:
- yarn
- yarn commitlint
- yarn lint
- yarn build --no-progress --prod
- yarn build --no-progress
- yarn build:prod --no-progress
- yarn build:dev --no-progress
- yarn test --no-progress --code-coverage
- yarn --cwd server
- yarn --cwd devops/devopsadmin
Expand All @@ -68,7 +68,7 @@ mindsphere-dev:
script:
- cf target -o "$CF_ORG" -s "$CF_SPACE_DEV"
- yarn
- yarn build --no-progress --prod
- yarn build:prod --no-progress
- cd server
- yarn
- cf push todo-dev --no-start
Expand All @@ -93,7 +93,7 @@ mindsphere-stage:
script:
- cf target -o "$CF_ORG" -s "$CF_SPACE_STAGE"
- yarn
- yarn build --no-progress --prod
- yarn build:prod --no-progress
- cd server
- yarn
- cf push todo-stage --no-start
Expand Down
66 changes: 59 additions & 7 deletions README.md
Expand Up @@ -15,8 +15,10 @@ The demo consists of:
- a simple todo app using the MEAN (MongoDB, Express.js, Angular, Node.js) stack
- Angular App (root folder)
- [Backend](server)
- local Angular dev server setup that proxies requests to MindSphere,
allowing local development
- a devops admin backend that provides access to prometheus and grafana
- [devopsadmin app](devops/devopsadmin/)
- [devopsadmin app](devops/devopsadmin)
- [Prometheus on CloudFoundry](devops/prometheus)
- [Grafana on CloudFoundry](devops/grafana)

Expand All @@ -42,15 +44,64 @@ The following environment variables are recognized by the todo backend:
| `JWKS_URI` | JWKS endpoint, contains key used for validating auth tokens | only on MindSphere deploy | *empty* |
| `JWT_ISSUER` | Expected issuer of the token to be found in the JWT `iss` field | only on MindSphere deploy | *empty* |

### Local run
### Local Development

This project includes support for running the web interface in local
development mode connected to MindSphere. In order to reach the MindSphere
APIs you need to provide user credentials for your user.

The local Angular development server is setup to use a local proxy based on
WebPack that forwards api requests:

- `/api/**` will be forwarded to `https://gateway.eu1.mindsphere.io`
This applies to all [MindSphere API calls](https://developer.mindsphere.io/apis/index.html).
You can check the [MindSphere service source](src/app/mindsphere.service.ts)
for a sample.
- `/v1/**` will be forwarded to `http://localhost:3000`
This applies to all local Node.js todo backend server API calls. You can
start the backend locally from the `server/` directory.

To be able to reach the MindSphere APIs from your local environment you need
to setup authentication credentials for the MindSphere `/api/**` endpoints.
Please note the next steps are only needed if you call directly MindSphere
APIs from your frontend. They are not needed to interact with the local
todo API backend.

1. As a first one-time step you need to register your application in the
MindSphere Developer Cockpit by following the [official documentation](https://developer.mindsphere.io/howto/howto-cf-running-app.html#configure-the-application-via-the-developer-cockpit)
- Create the application
- Register endpoints
- **IMPORTANT** Configure the [application Roles & Scopes](https://developer.mindsphere.io/howto/howto-cf-running-app.html#configure-the-application-roles-scopes)
Your application will only have access to the MindSphere APIs that are
configured in this step
- Register the application
1. Access your new application with a web browser and authenticate. On
successful authentication the MindSphere gateway will setup some session
cookies. Use the browser developer tools to copy the cookies `SESSION`
and `XSRF-TOKEN`
1. Create a file `src/environments/.environment.ts` (notice the dot in the
name) with the same contents as `src/environments/environment.ts`. This
file will be ignored by git
1. In this file set the variables `xsrfTokenHeader` and `sessionCookie`
to the values copied before
1. These [credentials will be valid](https://developer.mindsphere.io/concepts/concept-gateway-url-schemas.html#restrictions)
for a maximum of 12 hours and have an inactivity timeout of 30 minutes.
When they expire, you can execute the same flow again by logging in to
MindSphere

Then start the local todo backend and Angular dev server. You will be able
to enjoy live reload of changes done in the source code of the Angular
app:

```sh
# Start mongodb server
docker run -p 27017:27017 mongo
# Build static angular ap and start nodejs server
yarn
yarn build --prod
cd server

# Start nodejs backend
yarn --cwd server
yarn --cwd server start

# Start Angular dev server
yarn
yarn start
```
Expand Down Expand Up @@ -86,7 +137,8 @@ bind the services:
```sh
# Build static angular app
yarn
yarn build --no-progress --prod
yarn build:prod --no-progress

# Push nodejs server
cd server
yarn
Expand Down
14 changes: 13 additions & 1 deletion angular.json
Expand Up @@ -28,6 +28,14 @@
"scripts": []
},
"configurations": {
"mdsplocal": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/.environment.mdsplocal.ts"
}
]
},
"production": {
"fileReplacements": [
{
Expand All @@ -50,9 +58,13 @@
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "todo:build"
"browserTarget": "todo:build",
"proxyConfig": "proxy.conf.js"
},
"configurations": {
"mdsplocal": {
"browserTarget": "todo:build:mdsplocal"
},
"production": {
"browserTarget": "todo:build:production"
}
Expand Down
8 changes: 6 additions & 2 deletions package.json
Expand Up @@ -14,8 +14,10 @@
"license": "MIT",
"scripts": {
"ng": "ng",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"start": "ng serve --configuration=mdsplocal",
"build": "ng build --configuration=mdsplocal",
"build:dev": "ng build",
"build:prod": "ng build --configuration=production",
"test": "ng test",
"eclint": "eclint check $(git ls-files)",
"lint": "ng lint && markdownlint --ignore '**/node_modules/**' . && yarn eclint",
Expand All @@ -42,6 +44,7 @@
"bootstrap": "^4.1.3",
"core-js": "^2.5.4",
"font-awesome": "^4.7.0",
"ngx-cookie-service": "^2.0.2",
"rxjs": "^6.3.3",
"zone.js": "~0.8.26"
},
Expand All @@ -58,6 +61,7 @@
"chromedriver": "^2.41.0",
"codelyzer": "^4.5.0",
"eclint": "^2.8.0",
"https-proxy-agent": "^2.2.1",
"jasmine-core": "~2.99.1",
"jasmine-reporters": "^2.3.2",
"jasmine-spec-reporter": "~4.2.1",
Expand Down
39 changes: 39 additions & 0 deletions proxy.conf.js
@@ -0,0 +1,39 @@
var HttpsProxyAgent = require('https-proxy-agent');
var proxyConfig = [
{
context: '/api',
target: 'https://gateway.eu1.mindsphere.io',
secure: true,
changeOrigin: true,
logLevel: 'debug'
},
{
context: '/v1',
target: 'http://localhost:3000',
secure: false,
changeOrigin: true,
logLevel: 'debug'
}
];

function setupForCorporateProxy(proxyConfig) {
console.log('Checking corporate proxy settings');
var proxyServer = process.env.http_proxy || process.env.HTTP_PROXY;
if (proxyServer) {
var agent = new HttpsProxyAgent(proxyServer);
console.log('Using corporate proxy server: ' + proxyServer);
proxyConfig.forEach(function(entry) {
// Do not proxy requests targeted to localhost
if (entry.target.search(/localhost|127\.0\.0\.1/i) < 0) {
entry.agent = agent;
} else {
console.log('Ignoring proxy for localhost target entry:', entry);
}
});
} else {
console.log('No proxy detected, using direct connection');
}
return proxyConfig;
}

module.exports = setupForCorporateProxy(proxyConfig);
6 changes: 0 additions & 6 deletions proxy.conf.json

This file was deleted.

10 changes: 9 additions & 1 deletion src/app/app.component.spec.ts
Expand Up @@ -3,12 +3,15 @@ import { TestBed, async } from '@angular/core/testing';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule } from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';

import { AppComponent } from './app.component';
import { TodosComponent } from './todos/todos.component';
import { UserInfoComponent } from './userinfo/userinfo.component';
import { TodoServiceMock } from './todo.service.mock';
import { TodoService } from './todo.service';
import { MindSphereService } from './mindsphere.service';
import { MindSphereServiceMock } from './mindsphere.service.mock';

describe('AppComponent', () => {
beforeEach(async(() => {
Expand All @@ -25,20 +28,25 @@ describe('AppComponent', () => {
UserInfoComponent
],
providers: [
{ provide: TodoService, useClass: TodoServiceMock }
{ provide: TodoService, useClass: TodoServiceMock },
{ provide: MindSphereService, useClass: MindSphereServiceMock },
CookieService
],
}).compileComponents();
}));

it('should create the app', async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app).toBeTruthy();
}));

it(`should have as title 'todo'`, async(() => {
const fixture = TestBed.createComponent(AppComponent);
const app = fixture.debugElement.componentInstance;
expect(app.title).toEqual('TODO');
}));

it('should render title in a h1 tag', async(() => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
Expand Down
3 changes: 2 additions & 1 deletion src/app/app.module.ts
Expand Up @@ -7,11 +7,11 @@ import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { FormsModule } from '@angular/forms';
import { HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';

import { AppComponent } from './app.component';
import { TodosComponent } from './todos/todos.component';
import { UserInfoComponent } from './userinfo/userinfo.component';

import { AuthInterceptor } from './interceptors/auth.interceptor';

@NgModule({
Expand All @@ -27,6 +27,7 @@ import { AuthInterceptor } from './interceptors/auth.interceptor';
],
providers: [
{ provide: HTTP_INTERCEPTORS, useClass: AuthInterceptor, multi: true },
CookieService
],
bootstrap: [AppComponent]
})
Expand Down
25 changes: 25 additions & 0 deletions src/app/mindsphere.service.mock.ts
@@ -0,0 +1,25 @@
/*
Copyright Siemens AG 2018
SPDX-License-Identifier: MIT
*/

import { Injectable } from '@angular/core';
import { Observable , of} from 'rxjs';
import { TenantInfo } from './tenantinfo';

@Injectable()
export class MindSphereServiceMock {

getTenantInfo(): Observable<TenantInfo> {
const TENANT_INFO: TenantInfo = {
prefix: 'mytenant',
name: 'mytenant',
displayName: 'mytenant',
type: 'DEVELOPER',
companyName: 'My Company',
allowedToCreateSubtenant: true,
ETag: 1
};
return of(TENANT_INFO);
}
}
45 changes: 45 additions & 0 deletions src/app/mindsphere.service.ts
@@ -0,0 +1,45 @@
/*
Copyright Siemens AG 2018
SPDX-License-Identifier: MIT
*/

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { CookieService } from 'ngx-cookie-service';
import { Observable } from 'rxjs';
import { TenantInfo } from './tenantinfo';
import { environment } from '../environments/environment';
import { tap } from 'rxjs/operators';

@Injectable({ providedIn: 'root' })
export class MindSphereService {

static readonly TENANTMGMT_TENANTINFO_URL = './api/tenantmanagement/v4/tenantInfo';
mdspXsrfTokenHeader: string;

constructor(private http: HttpClient, private cookieService: CookieService) {
this.mdspXsrfTokenHeader = this.cookieService.get('XSRF-TOKEN');

if (! environment.production) {
console.log('MindSphere dev mode, setting custom xsrf header and session cookie');

this.mdspXsrfTokenHeader = environment.mdsp.xsrfTokenHeader;
this.cookieService.set('SESSION', environment.mdsp.sessionCookie);
}
}

getTenantInfo(): Observable<TenantInfo> {
return this.http.get<TenantInfo>(
MindSphereService.TENANTMGMT_TENANTINFO_URL,
{
headers: new HttpHeaders({
'x-xsrf-token': this.mdspXsrfTokenHeader,
'accept': 'application/json',
'content-type': 'application/json'
})
})
.pipe(tap(response => {
console.log('tenantInfo:', response);
}));
}
}
20 changes: 20 additions & 0 deletions src/app/tenantinfo.ts
@@ -0,0 +1,20 @@
/*
Copyright Siemens AG 2018
SPDX-License-Identifier: MIT
*/

type TenantType =
| 'DEVELOPER'
| 'USER'
| 'OPERATOR';

export class TenantInfo {
ETag: any;
allowedToCreateSubtenant: boolean;
companyName: string;
country?: string;
displayName: string;
name: string;
prefix: string;
type: TenantType;
}
4 changes: 2 additions & 2 deletions src/app/todo.ts
Expand Up @@ -4,6 +4,6 @@ SPDX-License-Identifier: MIT
*/

export class Todo {
_id?: string;
title: string;
_id?: string;
title: string;
}
1 change: 1 addition & 0 deletions src/app/todos/todos.component.html
Expand Up @@ -17,3 +17,4 @@ <h1 class="form-control">{{todo.title}}</h1>
</div>
</div>
</div>
<hr>

0 comments on commit 72cb937

Please sign in to comment.