Skip to content

Commit 7930042

Browse files
committed
Add mtx sample
1 parent 67af8bb commit 7930042

File tree

15 files changed

+393
-1
lines changed

15 files changed

+393
-1
lines changed

README.md

+2
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,8 @@ For information about how to upload a react-based application to the HTML5 Appli
5454
5555
- [Basic App stored on HTML5 Application Repository, using XSUAA service, and destination service](standalone-approuter-html5-runtime-mta-hello-world)
5656
57+
- [Multi-tenant SAP Fiori app on HTML5 Application Repository](standalone-mtx-approuter)
58+
5759
- [SAP Fiori app integrated with SAP Cloud Portal service](standalone-portal-mta)
5860
5961
- [SAP Fiori app integrated with SAP Cloud Portal service and using UI5 flexibility service for key users](standalone-portal-keyuser-mta)

standalone-approuter-html5-runtime/readme.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# On Standalone Application Router using HTML5 Application Repository
1+
# On Standalone Application Router using HTML5 Application Repository
22

33
## Diagram
44

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<!DOCTYPE HTML>
2+
<html>
3+
4+
<head>
5+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
6+
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8" />
7+
<link rel="shortcut icon"
8+
href="https://static.community.services.sap/com-hdr/v7/453.190.7/shared-ui/1dx-assets/images/favicon.png"
9+
type="image/x-icon">
10+
<title>MTX Guestbook</title>
11+
<script src="https://sapui5.hana.ondemand.com/resources/sap-ui-core.js" id="sap-ui-bootstrap" data-sap-ui-libs="sap.m"
12+
data-sap-ui-theme="sap_fiori_3_dark">
13+
</script>
14+
15+
<style>
16+
.sapMPageEnableScrolling {
17+
padding: 35px;
18+
}
19+
</style>
20+
<script>
21+
22+
const guestbook = new sap.ui.model.json.JSONModel({})
23+
24+
fetch("/guestbook").then(async (res) => {
25+
if (res.ok) {
26+
const data = await res.json();
27+
guestbook.setData(data);
28+
}
29+
});
30+
31+
new sap.m.App({
32+
pages: new sap.m.Page({
33+
title: {
34+
path: "/tenant",
35+
formatter: tenant => `Multi-tenant Guestbook (${tenant})`
36+
},
37+
headerContent: new sap.m.Button({
38+
icon: "sap-icon://log",
39+
tooltip: {
40+
path: "/user",
41+
formatter: user => `Logout ${user}`
42+
},
43+
press: function () {
44+
window.location.replace("/logout");
45+
}
46+
}),
47+
content: [
48+
new sap.m.List({
49+
showSeparators: "Inner",
50+
items: {
51+
path: "/entries",
52+
template: new sap.m.FeedListItem({
53+
showIcon: false,
54+
sender: "{author}",
55+
timestamp: {
56+
path: "timestamp",
57+
formatter: ts => new Date(ts)
58+
},
59+
text: "{content}",
60+
convertLinksToAnchorTags: "All"
61+
})
62+
}
63+
}),
64+
new sap.m.FeedInput({
65+
showIcon: false,
66+
enabled: {
67+
path: "/canWrite",
68+
formatter: scope => !!scope
69+
},
70+
post: async function (oEvent) {
71+
const input = oEvent.getParameter("value");
72+
const csrfRes = await fetch("/guestbook", {
73+
method: "HEAD",
74+
headers: { "x-csrf-token": "fetch" }
75+
});
76+
const res = await fetch(`/guestbook?content=${input}`, {
77+
method: 'POST',
78+
headers: { "x-csrf-token": csrfRes.headers.get("x-csrf-token") }
79+
});
80+
const newData = await res.json();
81+
guestbook.setData(newData);
82+
}
83+
})
84+
]
85+
})
86+
}).setModel(guestbook)
87+
.placeAt("uiArea");
88+
</script>
89+
</head>
90+
91+
<body class="sapUiBody">
92+
<div id="uiArea"></div>
93+
</body>
94+
95+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<html>
2+
<head>
3+
<title>Logout</title>
4+
</head>
5+
<body>
6+
<h1>Logout</h1>
7+
<p>You are now logged out!</p>
8+
<p>You may <a href="index.html">return to the application</a></p>
9+
</body>
10+
</html>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"_version": "1.12.0",
3+
"sap.app": {
4+
"id": "mtx-guestbook",
5+
"type": "application",
6+
"applicationVersion": {
7+
"version": "1.0.0"
8+
}
9+
},
10+
"sap.cloud": {
11+
"service": "cloud.service"
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"name": "html5module",
3+
"version": "0.0.1",
4+
"scripts": {
5+
"build": "npm run clean && npm run zip",
6+
"zip": "npx bestzip HTML5Module-content.zip *",
7+
"clean": "npx rimraf HTML5Module-content.zip dist"
8+
}
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"routes": []
3+
}

standalone-mtx-approuter/diagram.png

2.61 MB
Loading

standalone-mtx-approuter/mta.yaml

+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
ID: mtx-guestbook
2+
_schema-version: "2.1"
3+
version: 1.0.0
4+
5+
parameters:
6+
appname: mtx-guestbook
7+
subdomain: <subdomain of the provider subaccount>
8+
9+
modules:
10+
- name: mtx-approuter
11+
type: approuter.nodejs
12+
path: router
13+
parameters:
14+
routes:
15+
- route: https://${subdomain}-${appname}.${default-domain}
16+
- route: https://<subdomain of the consumer subaccount>-${appname}.${default-domain}
17+
disk-quota: 256M
18+
memory: 256M
19+
host: ${appname}
20+
domain: ${default-domain}
21+
requires:
22+
- name: html5-rt
23+
- name: uaa
24+
- name: saas-registry
25+
properties:
26+
TENANT_HOST_PATTERN: "^(.*)-${appname}.${default-domain}"
27+
- name: html5_deployer
28+
type: com.sap.application.content
29+
path: .
30+
requires:
31+
- name: html5-host
32+
parameters:
33+
content-target: true
34+
build-parameters:
35+
build-result: resources
36+
requires:
37+
- artifacts:
38+
- HTML5Module-content.zip
39+
name: HTML5Module
40+
target-path: resources/
41+
- name: HTML5Module
42+
type: html5
43+
path: HTML5Module
44+
build-parameters:
45+
builder: custom
46+
commands:
47+
- npm run build
48+
supported-platforms: []
49+
resources:
50+
- name: html5-host
51+
type: org.cloudfoundry.managed-service
52+
parameters:
53+
service: html5-apps-repo
54+
service-plan: app-host
55+
service-name: ${appname}-html5-host
56+
- name: html5-rt
57+
parameters:
58+
service: html5-apps-repo
59+
service-plan: app-runtime
60+
service-name: ${appname}-html5-rt
61+
type: org.cloudfoundry.managed-service
62+
- name: uaa
63+
type: org.cloudfoundry.managed-service
64+
parameters:
65+
service: xsuaa
66+
service-plan: application
67+
service-name: ${appname}-uaa
68+
config:
69+
xsappname: ${appname}
70+
tenant-mode: shared
71+
scopes:
72+
- name: $XSAPPNAME.Read
73+
description: Read permission
74+
- name: $XSAPPNAME.Write
75+
description: Write permission
76+
- name: $XSAPPNAME.Callback
77+
description: With this scope set, the callbacks for tenant onboarding, offboarding and getDependencies can be called.
78+
grant-as-authority-to-apps:
79+
- $XSAPPNAME(application,sap-provisioning,tenant-onboarding)
80+
foreign-scope-references:
81+
- uaa.user
82+
role-templates:
83+
- name: Reader
84+
description: Can read
85+
scope-references:
86+
- $XSAPPNAME.Read
87+
- name: Author
88+
description: Can read and write
89+
scope-references:
90+
- $XSAPPNAME.Read
91+
- $XSAPPNAME.Write
92+
- name: saas-registry
93+
type: org.cloudfoundry.managed-service
94+
parameters:
95+
service: saas-registry
96+
service-plan: application
97+
service-name: ${appname}-saas-registry
98+
config:
99+
xsappname: ${appname}
100+
appName: ${appname}
101+
displayName: Guestbook
102+
description: A guestbook app to explain the concepts of Multitenancy
103+
category: Custom Apps
104+
appUrls:
105+
onSubscription: https://${subdomain}-${appname}.${default-domain}/callback/v1.0/tenants/{tenantId}
106+
getDependencies: https://${subdomain}-${appname}.${default-domain}/callback/v1.0/dependencies

standalone-mtx-approuter/package.json

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "mtx-approuter",
3+
"description": "Node.js based application router service for html5-apps",
4+
"dependencies": {
5+
"mbt": "^1.1.1"
6+
},
7+
"scripts": {
8+
"build": "mbt build"
9+
},
10+
"version": "1.0.0"
11+
}

standalone-mtx-approuter/readme.md

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Multi-tenant Standalone Application Router using HTML5 Application Repository
2+
3+
## Diagram
4+
5+
![diagram](diagram.png)
6+
7+
## Description
8+
The image above shows the guestbook application that you will learn to build in this post. The idea of this guestbook is quite simple; each tenant will have its own guestbook that is available under a unique URL. The application comes with two role templates - reader and author. Readers can see all existing entries of that tenant, and authors can also add new entries. For more details, please refer to this [blog post](INSERT HERE).
9+
10+
11+
To keep things simple, we won't add any persistence layer. We will use a standard JSON object in our extended approuter to temporarily save some data. Consequently, all data is lost once the application restarts, but this is fine for our simple demo. If you would like to persist the data, please look at the [multitenancy guide from CAP](https://cap.cloud.sap/docs/guides/multitenancy).
12+
13+
14+
## Download and Installation
15+
1. Download the source code:
16+
```
17+
git clone https://github.com/SAP-samples/multi-cloud-html5-apps-samples
18+
cd multi-cloud-html5-apps-samples/standalone-mtx-approuter
19+
```
20+
2. Build the project:
21+
```
22+
npm install
23+
npm run build
24+
```
25+
3. Deploy the project:
26+
```
27+
cf deploy mta_archives/mtx-guestbook_1.0.0.mtar
28+
```
29+
30+
If the deployment has been successful, you find the URL of the application router in the console output or you can print it on Unix-based systems with `cf app mtx-approuter | awk '/^routes/ { print "https://"$2"/" }'`. It probably has the following structure: <https://[subaccount-id]-mtx-guestbook.cfapps.eu10.hana.ondemand.com>.
31+
32+
33+
## Configuration
34+
35+
Before deploying the project, you need to to replace the subdomain placeholders in the `mta.yaml` yaml file. If you want to subscribe multiple tenants from multiple subaccount, add new routes for them as well. For more details, please refer to this [blog post](INSERT HERE).
36+
37+
## Check the Result
38+
39+
### List the Deployed HTML5 App
40+
41+
```
42+
$ cf html5-list
43+
Getting list of HTML5 applications in org 9f10ed8atrial / space dev as [email protected]...
44+
OK
45+
46+
name version app-host-id service instance visibility last changed
47+
mtxguestbook 1.0.0 78619acc-1181-4c75-a3d9-7ca25ed91790 mtx-guestbook-html5-host public Tue, 20 Apr 2021 14:09:55 GMT
48+
```
49+
50+
### List the Deployed MTA
51+
52+
```
53+
$ cf mta cf mta mtx-guestbook
54+
Showing health and status for multi-target app cf mta mtx-guestbook in org 9f10ed8atrial / space dev as [email protected]...
55+
OK
56+
Version: 1.0.0
57+
58+
Apps:
59+
name requested state instances memory disk urls
60+
mtx-approuter started 1/1 256M 256M <provider>-mtx-guestbook.cfapps.eu10.hana.ondemand.com, <consumer>-mtx-guestbook.cfapps.eu10.hana.ondemand.com
61+
62+
Services:
63+
name service plan bound apps last operation
64+
mtx-guestbook-html5-host html5-apps-repo app-host create succeeded
65+
mtx-guestbook-html5-rt html5-apps-repo app-runtime mtx-approuter create succeeded
66+
mtx-guestbook-saas-registry saas-registry application mtx-approuter create succeeded
67+
mtx-guestbook-uaa xsuaa application mtx-approuter create succeeded
68+
```
69+
70+
### Check the Web App
71+
72+
Access the URL to view the web app. You are directed to a sign-on page before you can see the web app that display your name.
73+
74+
![webapp](result.png)

standalone-mtx-approuter/result.png

172 KB
Loading
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
const approuter = require('@sap/approuter');
2+
3+
const ar = approuter(),
4+
entries = {};
5+
6+
ar.beforeRequestHandler.use('/guestbook', function myMiddleware(req, res) {
7+
if (req.isUnauthenticated()) {
8+
res.statusCode = 401;
9+
res.end("Unauthorized");
10+
return;
11+
}
12+
const canRead = req.user.scopes.find((scope => scope.includes("Read")));
13+
if (!canRead) {
14+
res.statusCode = 401;
15+
res.end("Unauthorized");
16+
return;
17+
}
18+
const canWrite = req.user.scopes.find((scope => scope.includes("Write")));
19+
const tenant = req.user.tenant;
20+
if (req.method === "POST" && canWrite) { // not the best permission check but ok for demo
21+
if (!entries[tenant]) {
22+
entries[tenant] = [];
23+
}
24+
entries[tenant].push({
25+
content: req.query.content,
26+
author: req.user.name,
27+
timestamp: new Date()
28+
})
29+
}
30+
res.end(JSON.stringify({
31+
tenant,
32+
canWrite,
33+
user: req.user.name,
34+
entries: entries[tenant]
35+
}));
36+
});
37+
ar.start();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "appouter",
3+
"description": "Node.js based application router service for html5-apps",
4+
"dependencies": {
5+
"@sap/approuter": "^10.2.0"
6+
},
7+
"scripts": {
8+
"start": "node extended-server.js"
9+
}
10+
}

0 commit comments

Comments
 (0)