├── .bluemix ├── deploy.json ├── icon.svg ├── pipeline.yml ├── toolchain.svg └── toolchain.yml ├── .gitignore ├── .travis.yml ├── LICENSE ├── README.md ├── docker-compose.yaml ├── docs ├── cloudfoundry.md ├── istio.md ├── kubernetes.md └── openwhisk.md ├── flightassist-weather ├── Dockerfile.alpine ├── Dockerfile.phusion ├── LICENSE ├── Makefile ├── Procfile ├── README.md ├── manifest.yml ├── requirements.txt └── src │ └── app.py ├── flightassist.yaml ├── flightassist_serverless.yaml ├── images ├── paas-containers.png ├── plans.png └── status.png ├── ingress.yaml ├── main_application ├── .dockerignore ├── Dockerfile.local ├── conversation.js ├── flightassist.js ├── flightstats.js ├── manifest.yml ├── package.json ├── project.json ├── public │ ├── css │ │ ├── conversation.css │ │ ├── flightassist.css │ │ ├── font-awesome.min.css │ │ ├── ie8.css │ │ ├── images │ │ │ ├── arrow.svg │ │ │ ├── banner.svg │ │ │ ├── loader.gif │ │ │ ├── overlay.png │ │ │ ├── poptrox-closer.svg │ │ │ └── poptrox-nav.svg │ │ └── main.css │ ├── fonts │ │ ├── FontAwesome.otf │ │ ├── bold-italic │ │ │ ├── h-n-bold-italic.eot │ │ │ ├── h-n-bold-italic.ttf │ │ │ ├── h-n-bold-italic.woff │ │ │ └── h-n-bold-italic.woff2 │ │ ├── bold │ │ │ ├── h-n-bold.eot │ │ │ ├── h-n-bold.ttf │ │ │ ├── h-n-bold.woff │ │ │ └── h-n-bold.woff2 │ │ ├── fontawesome-webfont.eot │ │ ├── fontawesome-webfont.svg │ │ ├── fontawesome-webfont.ttf │ │ ├── fontawesome-webfont.woff │ │ ├── fontawesome-webfont.woff2 │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ ├── glyphicons-halflings-regular.woff2 │ │ ├── ibm-glyph.svg │ │ ├── ibm-glyphs.eot │ │ ├── ibm-glyphs.ttf │ │ ├── ibm-glyphs.woff │ │ ├── ibm-icons.eot │ │ ├── ibm-icons.svg │ │ ├── ibm-icons.ttf │ │ ├── ibm-icons.woff │ │ ├── light-italic │ │ │ ├── h-n-light-italic.eot │ │ │ ├── h-n-light-italic.ttf │ │ │ ├── h-n-light-italic.woff │ │ │ └── h-n-light-italic.woff2 │ │ ├── light │ │ │ ├── h-n-light.eot │ │ │ ├── h-n-light.ttf │ │ │ ├── h-n-light.woff │ │ │ └── h-n-light.woff2 │ │ ├── medium-italic │ │ │ ├── h-n-medium-italic.eot │ │ │ ├── h-n-medium-italic.ttf │ │ │ ├── h-n-medium-italic.woff │ │ │ └── h-n-medium-italic.woff2 │ │ ├── medium │ │ │ ├── h-n-medium.eot │ │ │ ├── h-n-medium.ttf │ │ │ ├── h-n-medium.woff │ │ │ └── h-n-medium.woff2 │ │ ├── roman-italic │ │ │ ├── h-n-roman-italic.eot │ │ │ ├── h-n-roman-italic.ttf │ │ │ ├── h-n-roman-italic.woff │ │ │ └── h-n-roman-italic.woff2 │ │ └── roman │ │ │ ├── h-n-roman.eot │ │ │ ├── h-n-roman.ttf │ │ │ ├── h-n-roman.woff │ │ │ └── h-n-roman.woff2 │ ├── images │ │ ├── bg-alt.jpg │ │ ├── bg.jpg │ │ ├── fulls │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ ├── 04.jpg │ │ │ ├── 05.jpg │ │ │ ├── 06.jpg │ │ │ ├── 07.jpg │ │ │ └── 08.jpg │ │ ├── marker_image.png │ │ ├── pic01.jpg │ │ ├── pic02.jpg │ │ ├── throbber.gif │ │ └── thumbs │ │ │ ├── 01.jpg │ │ │ ├── 02.jpg │ │ │ ├── 03.jpg │ │ │ ├── 04.jpg │ │ │ ├── 05.jpg │ │ │ ├── 06.jpg │ │ │ ├── 07.jpg │ │ │ └── 08.jpg │ └── js │ │ ├── api.js │ │ ├── common.js │ │ ├── conversation.js │ │ ├── global.js │ │ ├── ie │ │ ├── PIE.htc │ │ ├── backgroundsize.min.htc │ │ ├── html5shiv.js │ │ └── respond.min.js │ │ ├── jquery.min.js │ │ ├── jquery.scrolly.min.js │ │ ├── main.js │ │ ├── mustache.js │ │ ├── skel.min.js │ │ ├── trips.js │ │ └── util.js ├── restcall.js ├── routes │ ├── index.js │ └── tripdata.js ├── tripit.js ├── views │ ├── conversation.ejs │ ├── index.ejs │ └── trips.ejs └── weather.js ├── ratelimit.yaml ├── scripts ├── bx_login.sh ├── deploy.sh ├── install.sh └── install_bx.sh └── secret.yaml /.bluemix/deploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json-schema.org/draft-04/schema#", 3 | "title": "Sample Deploy Stage", 4 | "description": "sample toolchain", 5 | "longDescription": "The Delivery Pipeline automates continuous deployment.", 6 | "type": "object", 7 | "properties": { 8 | "prod-region": { 9 | "description": "The bluemix region", 10 | "type": "string" 11 | }, 12 | "prod-organization": { 13 | "description": "The bluemix org", 14 | "type": "string" 15 | }, 16 | "prod-space": { 17 | "description": "The bluemix space", 18 | "type": "string" 19 | }, 20 | "bluemix-user": { 21 | "description": "Your Bluemix user ID", 22 | "type": "string" 23 | }, 24 | "bluemix-password": { 25 | "description": "Your Bluemix Password", 26 | "type": "string" 27 | }, 28 | "bluemix-api-key": { 29 | "description": "Required for **Federated ID** since Federated ID can't login with Bluemix user and password via Bluemix CLI. You can obtain your API_KEY via https://console.ng.bluemix.net/iam/#/apikeys by clicking **Create API key** (Each API key only can be viewed once).", 30 | "type": "string" 31 | }, 32 | "bluemix-cluster-account": { 33 | "description": "The GUID of the Bluemix account where you created the cluster. Retrieve it with [bx iam accounts].", 34 | "type": "string" 35 | }, 36 | "bluemix-cluster-name": { 37 | "description": "Your cluster name. Retrieve it with [bx cs clusters].", 38 | "type": "string" 39 | }, 40 | "flightstats-app-id": { 41 | "description": "Your Flightstats Application ID. You can obtain one via https://developer.flightstats.com/signup", 42 | "type": "string" 43 | }, 44 | "flightstats-app-key":{ 45 | "description": "Your Flightstats Application Key", 46 | "type": "string" 47 | }, 48 | "tripit-api-key":{ 49 | "description": "Your TripIt API Key, You can obtain one via https://www.tripit.com/developer/create", 50 | "type": "string" 51 | }, 52 | "tripit-api-secret":{ 53 | "description": "Your TripIt API Secret", 54 | "type": "string" 55 | }, 56 | "openwhisk-auth":{ 57 | "description": "Enter your OpenWhisk Auth to enable serverless for Bluemix Cluster. Else, leave it blank to use microservices on Bluemix Cluster", 58 | "type": "string" 59 | } 60 | }, 61 | "required": ["prod-region", "prod-organization", "prod-space", "bluemix-cluster-account", "bluemix-cluster-name", "flightstats-app-id", "flightstats-app-key", "tripit-api-key", "tripit-api-secret","openwhisk-auth"], 62 | "anyOf": [ 63 | { 64 | "required": ["bluemix-user", "bluemix-password"] 65 | }, 66 | { 67 | "required": ["bluemix-api-key"] 68 | } 69 | ], 70 | "form": [ 71 | { 72 | "type": "validator", 73 | "url": "/devops/setup/bm-helper/helper.html" 74 | }, 75 | { 76 | "type": "text", 77 | "readonly": false, 78 | "title": "Bluemix User ID", 79 | "key": "bluemix-user" 80 | },{ 81 | "type": "password", 82 | "readonly": false, 83 | "title": "Bluemix Password", 84 | "key": "bluemix-password" 85 | },{ 86 | "type": "password", 87 | "readonly": false, 88 | "title": "Bluemix API Key (Optional)", 89 | "key": "bluemix-api-key" 90 | },{ 91 | "type": "password", 92 | "readonly": false, 93 | "title": "Bluemix Cluster Account ID", 94 | "key": "bluemix-cluster-account" 95 | },{ 96 | "type": "text", 97 | "readonly": false, 98 | "title": "Bluemix Cluster Name", 99 | "key": "bluemix-cluster-name" 100 | },{ 101 | "type": "text", 102 | "readonly": false, 103 | "title": "Flightstats APP ID", 104 | "key": "flightstats-app-id" 105 | },{ 106 | "type": "password", 107 | "readonly": false, 108 | "title": "Flightstats APP Key", 109 | "key": "flightstats-app-key" 110 | },{ 111 | "type": "text", 112 | "readonly": false, 113 | "title": "TripIt API Key", 114 | "key": "tripit-api-key" 115 | },{ 116 | "type": "password", 117 | "readonly": false, 118 | "title": "TripIt API Secret", 119 | "key": "tripit-api-secret" 120 | },{ 121 | "type": "password", 122 | "readonly": false, 123 | "title": "OpenWhisk Auth", 124 | "key": "openwhisk-auth" 125 | }, 126 | { 127 | "type": "table", 128 | "columnCount": 4, 129 | "widths": ["15%", "28%", "28%", "28%"], 130 | "items": [ 131 | { 132 | "type": "label", 133 | "title": "" 134 | }, 135 | { 136 | "type": "label", 137 | "title": "Region" 138 | }, 139 | { 140 | "type": "label", 141 | "title": "Organization" 142 | }, 143 | { 144 | "type": "label", 145 | "title": "Space" 146 | }, 147 | { 148 | "type": "label", 149 | "title": "Deployment Configuration" 150 | }, 151 | { 152 | "type": "select", 153 | "key": "prod-region" 154 | }, 155 | { 156 | "type": "select", 157 | "key": "prod-organization" 158 | }, 159 | { 160 | "type": "select", 161 | "key": "prod-space", 162 | "readonly": false 163 | } 164 | ] 165 | } 166 | ] 167 | } 168 | -------------------------------------------------------------------------------- /.bluemix/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /.bluemix/pipeline.yml: -------------------------------------------------------------------------------- 1 | --- 2 | stages: 3 | - name: BUILD 4 | inputs: 5 | - type: git 6 | branch: master 7 | service: ${SAMPLE_REPO} 8 | triggers: 9 | - type: commit 10 | jobs: 11 | - name: Build 12 | type: builder 13 | artifact_dir: '' 14 | build_type: shell 15 | script: |- 16 | #!/bin/bash 17 | bash -n *.sh 18 | - name: Cloud Foundry Deploy 19 | inputs: 20 | - type: job 21 | stage: BUILD 22 | job: Build 23 | triggers: 24 | - type: stage 25 | properties: 26 | - name: FLIGHTSTATS_APP_ID 27 | type: secure 28 | value: ${FLIGHTSTATS_APP_ID} 29 | - name: FLIGHTSTATS_APP_KEY 30 | type: secure 31 | value: ${FLIGHTSTATS_APP_KEY} 32 | - name: TRIPIT_API_KEY 33 | type: secure 34 | value: ${TRIPIT_API_KEY} 35 | - name: TRIPIT_API_SECRET 36 | type: secure 37 | value: ${TRIPIT_API_SECRET} 38 | jobs: 39 | - name: Cloud Foundry Deploy 40 | type: deployer 41 | target: 42 | region_id: ${PROD_REGION_ID} 43 | organization: ${PROD_ORG_NAME} 44 | space: ${PROD_SPACE_NAME} 45 | application: ${CF_APP_NAME} 46 | script: | 47 | #!/bin/bash 48 | cf create-service cloudantNoSQLDB Lite mycloudant 49 | cf create-service weatherinsights Free-v2 myweatherinsights 50 | cf push "${CF_APP}" 51 | cf set-env "${CF_APP}" FLIGHTSTATS_APP_ID "${FLIGHTSTATS_APP_ID}" 52 | cf set-env "${CF_APP}" FLIGHTSTATS_APP_KEY "${FLIGHTSTATS_APP_KEY}" 53 | cf set-env "${CF_APP}" TRIPIT_API_KEY "${TRIPIT_API_KEY}" 54 | cf set-env "${CF_APP}" TRIPIT_API_SECRET "${TRIPIT_API_SECRET}" 55 | cf set-env "${CF_APP}" BASE_URL https://"${CF_APP}".mybluemix.net/ 56 | cf restage "${CF_APP}" 57 | # View logs 58 | # cf logs "${CF_APP}" --recent 59 | - name: Kubernetes Deploy 60 | inputs: 61 | - type: job 62 | stage: BUILD 63 | job: Build 64 | triggers: 65 | - type: stage 66 | properties: 67 | - name: BLUEMIX_USER 68 | type: text 69 | value: ${BLUEMIX_USER} 70 | - name: BLUEMIX_PASSWORD 71 | type: secure 72 | value: ${BLUEMIX_PASSWORD} 73 | - name: BLUEMIX_ACCOUNT 74 | type: secure 75 | value: ${BLUEMIX_ACCOUNT} 76 | - name: CLUSTER_NAME 77 | type: text 78 | value: ${CLUSTER_NAME} 79 | - name: API_KEY 80 | type: secure 81 | value: ${API_KEY} 82 | - name: FLIGHTSTATS_APP_ID 83 | type: secure 84 | value: ${FLIGHTSTATS_APP_ID} 85 | - name: FLIGHTSTATS_APP_KEY 86 | type: secure 87 | value: ${FLIGHTSTATS_APP_KEY} 88 | - name: TRIPIT_API_KEY 89 | type: secure 90 | value: ${TRIPIT_API_KEY} 91 | - name: TRIPIT_API_SECRET 92 | type: secure 93 | value: ${TRIPIT_API_SECRET} 94 | - name: OPENWHISK_AUTH 95 | type: secure 96 | value: ${OPENWHISK_AUTH} 97 | jobs: 98 | - name: Kubernetes Deploy 99 | type: deployer 100 | target: 101 | region_id: ${PROD_REGION_ID} 102 | organization: ${PROD_ORG_NAME} 103 | space: ${PROD_SPACE_NAME} 104 | application: ${CF_APP_NAME} 105 | script: | 106 | #!/bin/bash 107 | . ./scripts/install_bx.sh 108 | ./scripts/bx_login.sh 109 | ./scripts/deploy.sh 110 | hooks: 111 | - enabled: true 112 | label: null 113 | ssl_enabled: false 114 | url: https://devops-api-integration.stage1.ng.bluemix.net/v1/messaging/webhook/publish 115 | -------------------------------------------------------------------------------- /.bluemix/toolchain.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: "Deploy FlightAssist sample to Bluemix" 3 | description: "Toolchain to deploy FlightAssist sample to Bluemix" 4 | version: 0.1 5 | image: data:image/svg+xml;base64,$file(toolchain.svg,base64) 6 | icon: data:image/svg+xml;base64,$file(icon.svg,base64) 7 | required: 8 | - deploy 9 | - sample-repo 10 | 11 | # Github repos 12 | sample-repo: 13 | service_id: githubpublic 14 | parameters: 15 | repo_name: "{{name}}" 16 | repo_url: https://github.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms 17 | type: clone 18 | has_issues: false 19 | 20 | # Pipelines 21 | sample-build: 22 | service_id: pipeline 23 | parameters: 24 | name: "{{name}}" 25 | ui-pipeline: true 26 | configuration: 27 | content: $file(pipeline.yml) 28 | env: 29 | SAMPLE_REPO: "sample-repo" 30 | CF_APP_NAME: "{{deploy.parameters.prod-app-name}}" 31 | PROD_SPACE_NAME: "{{deploy.parameters.prod-space}}" 32 | PROD_ORG_NAME: "{{deploy.parameters.prod-organization}}" 33 | PROD_REGION_ID: "{{deploy.parameters.prod-region}}" 34 | BLUEMIX_USER: "{{deploy.parameters.bluemix-user}}" 35 | BLUEMIX_PASSWORD: "{{deploy.parameters.bluemix-password}}" 36 | API_KEY: "{{deploy.parameters.bluemix-api-key}}" 37 | BLUEMIX_ACCOUNT: "{{deploy.parameters.bluemix-cluster-account}}" 38 | CLUSTER_NAME: "{{deploy.parameters.bluemix-cluster-name}}" 39 | FLIGHTSTATS_APP_ID: "{{deploy.parameters.flightstats-app-id}}" 40 | FLIGHTSTATS_APP_KEY: "{{deploy.parameters.flightstats-app-key}}" 41 | TRIPIT_API_KEY: "{{deploy.parameters.tripit-api-key}}" 42 | TRIPIT_API_SECRET: "{{deploy.parameters.tripit-api-secret}}" 43 | OPENWHISK_AUTH: "{{deploy.parameters.openwhisk-auth}}" 44 | execute: true 45 | services: ["sample-repo"] 46 | hidden: [form, description] 47 | 48 | #Deployment 49 | deploy: 50 | schema: 51 | $ref: deploy.json 52 | service-category: pipeline 53 | parameters: 54 | prod-app-name: "{{sample-repo.parameters.repo_name}}" 55 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | .env 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional REPL history 38 | .node_repl_history 39 | 40 | # VS Code metadata 41 | .vscode 42 | # local test 43 | testtripit 44 | 45 | #get rid of ds_store 46 | .DS_Store 47 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: bash 2 | script: true 3 | sudo: required 4 | before_install: 5 | - bash scripts/install.sh 6 | group: stable 7 | dist: precise 8 | os: linux 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 IBM Corporation 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://travis-ci.org/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms.svg?branch=master)](https://travis-ci.org/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms) 2 | 3 | # Navigate application deployment options with Cloud Foundry, Kubernetes, OpenWhisk and Istio 4 | 5 | PaaS platforms like Cloud Foundry, container orchestrators like Kubernetes, Serverless platforms like OpenWhisk and Service-mesh like Istio are all great technologies to deploy and manage your microservices on. Common wisdom says there is no such thing as too many choices, but abundance of choices can lead to analysis paralysis. In this code, we look at deployment experience the different platforms provide, and what do we gain and lose by choosing one vs another. 6 | 7 | We start with a sample Node.js monolithic application, Flightassist, factor it into two microservices, and then use it for demonstrating and comparing various deployment technologies. A set of trade-offs and comparisions can be made between these deployment models, and this application provides a basis for those discussions. 8 | 9 | ![architecure-diagram](images/paas-containers.png) 10 | 11 | ## Included Components 12 | 13 | - [Cloud Foundry](https://www.cloudfoundry.org) 14 | - [Kubernetes Clusters](https://console.ng.bluemix.net/docs/containers/cs_ov.html#cs_ov) 15 | - [Istio](https://istio.io) 16 | - [OpenWhisk](https://www.ibm.com/cloud-computing/bluemix/openwhisk) 17 | - [Cloudant NoSQL Database](https://cloudant.com) 18 | - [Insights for Weather](https://console.ng.bluemix.net/docs/services/Weather/weather_overview.html#about_weather) 19 | - [TripIt Developer API](https://www.tripit.com/developer) 20 | - [FlightStats Developer API](https://developer.flightstats.com) 21 | 22 | ## Prerequisites 23 | 24 | Register and obtain the keys for [FlightStats Developer API](https://developer.flightstats.com/signup) and [TripIt Developer API](https://www.tripit.com/developer/create) to query flight status. 25 | 26 | When signing up for a FlightStats developer key, note that there is a review process that may take 24 hours or more to get your application credentials activated for a 30-day trial with the API. 27 | 28 | ## Deploy using DevOps Toolchain 29 | 30 | Click the button to deploy your app and fill in all the variables from **Delivery Pipeline**. For Further instructions, please follow the [Toolchain instructions](https://github.com/IBM/container-journey-template/blob/master/Toolchain_Instructions_new.md). 31 | 32 | [![Create Toolchain](https://github.com/IBM/container-journey-template/blob/master/images/button.png)](https://console.ng.bluemix.net/devops/setup/deploy/) 33 | 34 | ### Toolchain Scenario One: Monolithic Application 35 | You should see a link under the Cloud Foundry Deploy stage and that's where your application is hosting. 36 | 37 | ### Toolchain Scenario Two: Microservices on Kubernetes Clusters, with or without Serverless capabilities 38 | 39 | If you want to deploy with microservices, please leave the **OpenWhisk Auth** variable blank on **Delivery Pipeline**. 40 | Otherwise, fill in the **OpenWhisk Auth** variable to enable serverless for your Flightassist. 41 | 42 | Then, click **View logs and history** under Kubernetes Deploy stage in your pipeline to access your application via the URL link at the end of your logs. 43 | 44 | # Steps 45 | 46 | ## Part A: Deploy, test and factor monolithic application into microservices: 47 | 48 | 1. [Provision application services - Cloudant Database and Insights for Weather Service](#1-create-your-cloudant-database-and-insights-for-weather-service) 49 | 2. [Deploy monolithic application](#2-deploy-monolithic-flightassist-application-using-cloud-foundry) 50 | 3. [Factor monolithic application into microservices and test](#3-factor-monolithic-application-into-microservices-and-test) 51 | 52 | ## Part B: Deploy microservices leveraging: 53 | 54 | 4. [Cloud Foundry](docs/cloudfoundry.md) 55 | 5. [Kubernetes Cluster](docs/kubernetes.md) 56 | 6. [Istio](docs/istio.md) 57 | 7. [OpenWhisk](docs/openwhisk.md) 58 | 59 | After you deployed Flightassist using any platform, you can go to [How to Use Flightassist](#how-to-use-flightassist) and start testing your application. 60 | 61 | # 1. Create your Cloudant Database and Insights for Weather Service 62 | 63 | First, clone and get in our repository to obtain the necessary files and scripts for building this example. 64 | 65 | ```bash 66 | git clone https://github.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms.git && cd Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms 67 | ``` 68 | 69 | Since we need to create services using the command line, we need to install [Bluemix CLI](http://clis.ng.bluemix.net/ui/home.html) before proceeding to the following steps. 70 | 71 | We will use Bluemix's [The Cloudant NoSQL database service](https://console.ng.bluemix.net/catalog/services/cloudant-nosql-db?env_id=ibm:yp:us-south) and [Insights for Weather service](https://console.ng.bluemix.net/catalog/services/weather-company-data?env_id=ibm:yp:us-south) for our database and weather data. Therefore, run the following commands to create cloudant and Insights for Weather service. 72 | 73 | > For this example, we recommend you name your services to *mycloudant* and *myweatherinsights*. 74 | 75 | ```bash 76 | bx service create cloudantNoSQLDB Lite mycloudant 77 | bx service create weatherinsights Free-v2 myweatherinsights 78 | ``` 79 | 80 | Before moving on, the demo application is missing code to create the databases used to cache API responses in your newly created Cloudant instance. You can run the following commands with your cloudant URL to create the databases. 81 | 82 | ```bash 83 | bx service key-create mycloudant {service key} #You can put any name for your {service key} 84 | bx service key-show mycloudant {service key} #This will output your cloudant credential, "url" is Your cloudant URL 85 | curl -k -X PUT {your-cloudantURL}/trips 86 | curl -k -X PUT {your-cloudantURL}/weather 87 | curl -k -X PUT {your-cloudantURL}/connections 88 | ``` 89 | # 2. Deploy monolithic Flightassist application using Cloud Foundry 90 | 91 | In this scenario, we will deploy Flightassist as a monolithic application and host it on Cloud Foundry. 92 | 93 | First, type the following commands to push your application with your own unique application name. 94 | 95 | ```bash 96 | bx app push {your_unique_app_name} -f main_application/manifest.yml 97 | ``` 98 | > Note: If you want to use `cf` commands, please install [cloudfoundry CLI](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html) and run `cf push {your_unique_app_name} -f main_application/manifest.yml` 99 | 100 | Now, go to https://console.ng.bluemix.net/dashboard/apps and select your application. Click the *Runtime* settings for your application and add these four environment variables to set up external credentials to the TripIt and FlightStats services: 101 | - `FLIGHTSTATS_APP_ID` : application ID assigned by FlightStats 102 | - `FLIGHTSTATS_APP_KEY` : application key assigned by FlightStats 103 | - `TRIPIT_API_KEY` : API key assigned by TripIt 104 | - `TRIPIT_API_SECRET` : API secret assigned by TripIt 105 | - `BASE_URL`: your URL for accessing your application. e.g. https://{app_name}.mybluemix.net/ 106 | 107 | Your application should restart automatically but can be done manually as well 108 | in the UI. With the service bindings and added environment variables, the 109 | application should be operational at the hostname route you selected for your CF 110 | application. 111 | 112 | Congratulations, now you can learn about [How to Use Flightassist](#how-to-use-flightassist) and start testing your application. 113 | 114 | # 3. Factor monolithic application into microservices and test 115 | 116 | To factor the application into microservices, we add a python microservice to the picture. Instead of directly accessing the apis from Node app, the python program will serve as a proxy to query. This step locally tests the app with the microservice and associated docker container images which are created. 117 | 118 | First, install [Docker CLI](https://www.docker.com/community-edition#/download). 119 | 120 | Next, edit the `docker-compose.yaml` file and add your credentials for **FLIGHTSTATS_APP_ID**, **FLIGHTSTATS_APP_KEY**, **TRIPIT_API_KEY**,**TRIPIT_API_SECRET**,**CLOUDANT_URL**, and **WEATHER_URL**. Create a key for the weather service and then run the following commands to view the service urls. 121 | 122 | ```bash 123 | bx service key-create myweatherinsights {service key} #You can put any name for your {service key} 124 | bx service keys {service_name} #This will output all your service keys for the {service_name} 125 | bx service key-show {service_name} {service key} #This will output your service credential, "url" is Your service URL 126 | ``` 127 | 128 | Then, run the following commands to build your docker images and run Docker Compose. 129 | 130 | ```bash 131 | docker build -f main_application/Dockerfile.local -t flightassist main_application 132 | docker build -f flightassist-weather/Dockerfile.alpine -t weather-service flightassist-weather 133 | docker-compose up 134 | ``` 135 | Now, your FlightAssist application should be running on http://localhost:3000/ 136 | 137 | # Deploy leveraging different scenarios 138 | 139 | Now you know to deploy your application and microservices on your local host. So let's deploy your application and microservice on the cloud with these 4 different scenarios. 140 | 141 | 1. [Cloud Foundry](docs/cloudfoundry.md) 142 | 2. [Kubernetes Cluster](docs/kubernetes.md) 143 | 3. [Istio](docs/istio.md) 144 | 4. [OpenWhisk](docs/openwhisk.md) 145 | 146 | 147 | # How to Use Flightassist 148 | 149 | To try out Flightassist, you need to [add a trip on TripIt](https://www.tripit.com/trip/create). Then, add at least one flight for your trip with a departure within the next 24 hours. In your plan, please fill in your confirmation number or airline with flight number. 150 | 151 | ![Tripit plan](images/plans.png) 152 | 153 | Once you added a new plan and you have your Flightassist running, open your Flightassist and click **Authenticate with TripIt** to login to Flightassist. 154 | 155 | Now you can see the most recent flight status and weather for all your flights within 24 hours. 156 | 157 | ![Flightassist status](images/status.png) 158 | 159 | # Reference 160 | 161 | This project is based on this [flightassist](https://github.com/estesp/flightassist) example. 162 | 163 | [Phil Estes](https://github.com/estesp) and [Lin Sun](https://github.com/linsun) are the main contributors for the [flightassist](https://github.com/estesp/flightassist) example. 164 | 165 | # License 166 | [Apache 2.0](LICENSE) 167 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3.1" 2 | 3 | services: 4 | 5 | flightassist: 6 | image: flightassist:latest 7 | environment: 8 | - DEVMODE=true 9 | - DEV_URL=http://localhost:3000/ 10 | - DEPLOY=compose 11 | - FORCE_FLIGHT_VIEW=true 12 | - USE_WEATHER_SERVICE=true 13 | - FLIGHTSTATS_APP_ID= 14 | - FLIGHTSTATS_APP_KEY= 15 | - TRIPIT_API_KEY= 16 | - TRIPIT_API_SECRET= 17 | - CLOUDANT_URL= 18 | ports: 19 | - "3000:3000" 20 | networks: 21 | - fassist-front 22 | - fassist-back 23 | depends_on: 24 | - weather-service 25 | 26 | weather-service: 27 | image: weather-service:latest 28 | networks: 29 | - fassist-back 30 | ports: 31 | - "5000:5000" 32 | environment: 33 | - WEATHER_URL= 34 | - DEPLOY=compose 35 | 36 | networks: 37 | fassist-back: 38 | fassist-front: 39 | -------------------------------------------------------------------------------- /docs/cloudfoundry.md: -------------------------------------------------------------------------------- 1 | # Deploy Flightassist microservices on Cloud Foundry 2 | 3 | Make sure you have both developer accounts mentioned in prerequisites. Also make sure you have cloudant and weatherinsights services created as listed in [step 1](https://github.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms#1-create-your-cloudant-database-and-insights-for-weather-service). 4 | 5 | In this scenario, we take the Flightassist which is factored to use the weather microservice. Since Cloud Foundry apps (warden containers) are not allowed to talk privately, they need to communicate via public route. 6 | 7 | We first push the python microservice. 8 | ``` 9 | bx app push {your_unique_proxy_name} -f flightassist-weather/manifest.yml 10 | ``` 11 | > Note: If you want to use `cf` commands, please install [cloudfoundry CLI](https://docs.cloudfoundry.org/cf-cli/install-go-cli.html) and replace all the `bx app push` command with `cf push` 12 | 13 | **make sure you pick a unique name for the app.** 14 | This will bring up the first app we need. 15 | The output should look like: 16 | ``` 17 | requested state: started 18 | instances: 1/1 19 | usage: 256M x 1 instances 20 | urls: {proxy_name}.mybluemix.net 21 | last uploaded: Thu Jun 8 21:36:15 UTC 2017 22 | stack: unknown 23 | buildpack: python_buildpack 24 | ``` 25 | And we need the **urls** for next step. 26 | Now we will push the second app, but **without starting** it. 27 | ``` 28 | bx app push {your_unique_app_name} -f main_application/manifest.yml --no-start 29 | ``` 30 | **make sure you pick a unique name for the app, too.** 31 | 32 | Now we inject the environment variables as in monolithic deployment: 33 | - `FLIGHTSTATS_APP_ID` : application ID assigned by FlightStats 34 | - `FLIGHTSTATS_APP_KEY` : application key assigned by FlightStats 35 | - `TRIPIT_API_KEY` : API key assigned by TripIt 36 | - `TRIPIT_API_SECRET` : API secret assigned by TripIt 37 | - `BASE_URL`: You URL for accessing your application. In the format **https://**{name2}**.mybluemix.net/** 38 | 39 | Plus, a couple more since we have two apps: 40 | - `USE_WEATHER_SERVICE`: true 41 | - `MICROSERVICE_URL`: {proxy_name}.mybluemix.net 42 | 43 | Now we start the 2nd app: 44 | `bx app start {app_name}` 45 | 46 | You can now test the apps by going to http://{app_name}.mybluemix.net 47 | 48 | 49 | ## Takeaway points 50 | To push an app, we simply use `bx app push` or `cf push` command. There is no container image or repository involved. Cloud Foundry has wide inventories of build packs to support different programming languages. If you run `cf marketplace`, you can find the huge list of services provided by Bluemix that can easily be consumed by your application. When pushing multiple microservices that need to communicate to each other, however, the experience is not so smooth. Another common alternative to creating public routes and sharing information through environment variables as used in this example, is to bind a message queue service for communication. 51 | 52 | # Code Structure 53 | 54 | | File | Description | 55 | | ---------------------------------------- | ---------------------------------------- | 56 | | [flightassist.js](../main_application/flightassist.js) | Main application, start the express web server and calling the major AJAX functions| 57 | | All JavaScript files (main_application/*.js) | The implementation of the flightstats, tripIt, and weather information, shared by all deployment options | 58 | | [app.py](../flightassist-weather/scr/app.py) | Weather Microservice, query and sent weather information to the main application | 59 | | [Procfile and requirements.txt](../flightassist-weather/)| Description of the microservice to be deployed | 60 | | [package.json](../main_application/package.json) | List the packages required by the application | 61 | | [manifest.yml](../main_application/manifest.yml) | Description of the application to be deployed | 62 | -------------------------------------------------------------------------------- /docs/istio.md: -------------------------------------------------------------------------------- 1 | # Enable Flightassist microservices with Istio service-mesh capabilities 2 | 3 | Istio is an open platform that provides a uniform way to connect, manage, and secure microservices. Istio is the result of a joint collaboration between IBM, Google and Lyft as a means to support traffic flow management, access policy enforcement and the telemetry data aggregation between microservices, all without requiring changes to the code 4 | 5 | ## 1. Setup your Kubernetes environment and images. 6 | 7 | First, follow the [Kubernetes Cluster Tutorial](https://github.com/IBM/container-journey-template) to create your own cluster on Bluemix. 8 | 9 | Then, install the container registry plugin for Bluemix CLI. 10 | 11 | ```bash 12 | bx plugin install container-registry -r Bluemix 13 | ``` 14 | Next, build your own docker images and push them to your own bluemix container registry. 15 | > You may skip these steps through deploying the secrets if you performed the Flightassist microservices deployment on Kubernetes scenario 16 | 17 | > Replace `` with your own namespace, you can view your namespace by running `bx cr namespaces` 18 | > 19 | > If you have an unauthorized error, run `bx cr login` to authorized your container-registry. 20 | 21 | ```bash 22 | docker build -f main_application/Dockerfile.local -t registry.ng.bluemix.net//flightassist main_application 23 | docker build -f flightassist-weather/Dockerfile.alpine -t registry.ng.bluemix.net//weather-service flightassist-weather 24 | docker push registry.ng.bluemix.net//flightassist 25 | docker push registry.ng.bluemix.net//weather-service 26 | ``` 27 | 28 | Then, you need to run the following commands to bind your Cloudant and Weather Insights services to your clusters. 29 | 30 | ```bash 31 | bx cs cluster-service-bind {your-cluster-name} default mycloudant 32 | bx cs cluster-service-bind {your-cluster-name} default myweatherinsights 33 | ``` 34 | Next, create secret to give FlightStats and TripIt API credentials for Flightassist. Modify the secret.yaml file with **flightstats-app-id**, **flightstats-app-key**, **tripit-api-key**, and **tripit-api-secret**. 35 | 36 | Now, run the following commands to deploy the secret 37 | 38 | ```bash 39 | kubectl create -f secret.yaml 40 | ``` 41 | 42 | ## 2. Installing Istio in your Cluster 43 | 44 | ### Download the Istio source 45 | 1. Download the latest Istio release for your OS: [Istio releases](https://github.com/istio/istio/releases) 46 | 2. Extract and go to the root directory. 47 | 3. Copy the `istioctl` bin to your local bin 48 | ```bash 49 | $ cp bin/istioctl /usr/local/bin 50 | ## example for macOS 51 | ``` 52 | 53 | ### Grant Permissions 54 | 1. Run the following command to check if your cluster has RBAC 55 | ```bash 56 | $ kubectl api-versions | grep rbac 57 | ``` 58 | 2. Grant permissions based on the version of your RBAC 59 | * If you have an **alpha** version, run: 60 | 61 | ```bash 62 | $ kubectl apply -f install/kubernetes/istio-rbac-alpha.yaml 63 | ``` 64 | 65 | * If you have a **beta** version, run: 66 | 67 | ```bash 68 | $ kubectl apply -f install/kubernetes/istio-rbac-beta.yaml 69 | ``` 70 | 71 | * If **your cluster has no RBAC** enabled, proceed to installing the **Control Plane**. 72 | 73 | ### Install the [Istio Control Plane](https://istio.io/docs/concepts/what-is-istio/overview.html#architecture) in your cluster 74 | ```bash 75 | kubectl apply -f install/kubernetes/istio.yaml 76 | cd .. 77 | ``` 78 | You should now have the Istio Control Plane running in Pods of your Cluster. 79 | ```bash 80 | $ kubectl get pods 81 | NAME READY STATUS RESTARTS 82 | istio-egress-3850639395-30d1v 1/1 Running 0 83 | istio-ingress-4068702052-2st6r 1/1 Running 0 84 | istio-manager-251184572-x9dd4 2/2 Running 0 85 | istio-mixer-2499357295-kn4vq 1/1 Running 0 86 | ``` 87 | 88 | ## 3. Inject Istio Envoys on Flightassist. 89 | 90 | > Note : if you performed the Flightassist microservices deployment on Kubernetes scenario you need to delete the previous services and deployments. 91 | > 92 | > ```bash 93 | > kubectl delete -f flightassist.yaml 94 | > ``` 95 | 96 | First, grab your isito-ingress IP:Port. 97 | 98 | ```bash 99 | echo $(kubectl get po -l istio=ingress -o jsonpath={.items[0].status.hostIP}):$(kubectl get svc istio-ingress -o jsonpath={.spec.ports[0].nodePort}) 100 | ``` 101 | 102 | Then, edit the `flightassist.yaml` and replace the `````` with your own namespace. You can obtain your namespace by running `bx cr namespace`. Also replace `` with `http://` 103 | > You also can remove `type:NodePort` on *flightassist-service* because we will access our application via isito-ingress. 104 | 105 | Next, deploy ingress to connect the microservices and inject Istio envoys on Flightassist and Weather Microservice. 106 | 107 | ```bash 108 | kubectl create -f ingress.yaml 109 | kubectl create -f <(istioctl kube-inject -f flightassist.yaml --includeIPRanges=172.30.0.0/16,172.20.0.0/16) 110 | ``` 111 | 112 | Congratulations, now your Flightassist application should be running on `http://`. You can go to [How to Use Flightassist](https://github.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms#how-to-use-flightassist) and start testing your application. 113 | 114 | 115 | ## 4. Exporing additional features on Istio. 116 | 117 | One feature provided by Istio is rate limits. Rate limits can limit the number of accesses from users and prevent your website from getting abused. 118 | 119 | To enable this, run 120 | 121 | ```bash 122 | istioctl mixer rule create global flightassist-service.default.svc.cluster.local -f ratelimit.yaml 123 | ``` 124 | 125 | Now, your rate limit is 50 requests per 10 seconds. Since the flightassist website will use 15-20 requests per visit, you should see your Mixer returns a `RESOURCE_EXHAUSTED` after you refresh your site 3 to 4 times. 126 | 127 | You can learn about other additional features on Istio by clicking [here](https://istio.io/docs/tasks/index.html). 128 | 129 | ## Takeaway points 130 | 131 | Istio is a service-mesh for microservices deployment and managing your application traffic. Istio provides an easy way to create this service mesh by deploying a control plane and injecting sidecars, an extended version of the Envoy proxy, in the same Pod as your microservice. In addition to the proxy feature we tested in the example, it also provides rich layer-7 routing, circuit breakers, policy enforcement and telemetry recording/reporting functions. This gives end users much better control over canary deployments, and not worrying about issues like service discovery etc. In addition, there is a focus from monitoring and trace collection from an application and microservices perspective, which a native container orchestration experience would not provide. However, Istio on its own is not sufficient, and it has to reside on a platform. 132 | 133 | 134 | # Code Structure 135 | 136 | | File | Description | 137 | | ---------------------------------------- | ---------------------------------------- | 138 | | [flightassist.js](../main_application/flightassist.js) | Main application, start the express web server and calling the major AJAX functions| 139 | | All JavaScript files (main_application/*.js) | The implementation of the flightstats and tripIt information, shared by all deployment options | 140 | | [app.py](../flightassist-weather/scr/app.py) | Weather Microservice, query and sent weather information to the main application | 141 | | [package.json](../main_application/package.json) | List the packages required by the application | 142 | | [Dockerfile.local](../main_application/Dockerfile.local) and [Dockerfile.alpine](../flightassist-weather/Dockerfile.alpine) | Description of the Docker image | 143 | | [flightassist.yaml](../flightassist.yaml) and [secret.yaml](../secret.yaml)| Specification file for the deployment of the service and secret in Kubernetes | 144 | | [ingress.yaml](../ingress.yaml)| Specification file for adding flightassist's endpoint to istio-ingress| 145 | | [ratelimit.yaml](../ratelimit.yaml) | Specification file for creating rate limits with Istio Mixer| 146 | -------------------------------------------------------------------------------- /docs/kubernetes.md: -------------------------------------------------------------------------------- 1 | # Deploy Flightassist microservices on Kubernetes Cluster 2 | 3 | In this scenario, we use the Flightassist microservices version which runs in two containers. We will run Flightassist as our main application with weather-service as our microservice to query the weather data. Then, we will host those containers using Kubernetes. 4 | 5 | First, follow the [Kubernetes Cluster Tutorial](https://github.com/IBM/container-journey-template) to create your own cluster on Bluemix. 6 | 7 | Then, install the container registry plugin for Bluemix CLI. 8 | 9 | ```bash 10 | bx plugin install container-registry -r Bluemix 11 | ``` 12 | Next, build your own docker images and push them to your own bluemix container registry. 13 | 14 | > Replace `` with your own namespace, you can view your namespace by running `bx cr namespaces` 15 | > 16 | > If you have an unauthorized error, run `bx cr login` to authorized your container-registry. 17 | 18 | ```bash 19 | docker build -f main_application/Dockerfile.local -t registry.ng.bluemix.net//flightassist main_application 20 | docker build -f flightassist-weather/Dockerfile.alpine -t registry.ng.bluemix.net//weather-service flightassist-weather 21 | docker push registry.ng.bluemix.net//flightassist 22 | docker push registry.ng.bluemix.net//weather-service 23 | ``` 24 | 25 | Then, you need to run the following commands to bind your Cloudant and Weather Insights services to your clusters. 26 | 27 | ```bash 28 | bx cs cluster-service-bind {your-cluster-name} default mycloudant 29 | bx cs cluster-service-bind {your-cluster-name} default myweatherinsights 30 | ``` 31 | 32 | Next, create secret to give FlightStats and TripIt API credentials for Flightassist. Modify the secret.yaml file with **flightstats-app-id**, **flightstats-app-key**, **tripit-api-key**, and **tripit-api-secret**. 33 | 34 | Then, edit the `flightassist.yaml` and replace the `````` with your own namespace. You can obtain your namespace by running `bx cr namespace`. Also replace `` with your node ip and nodeport (e.g. 169.47.237.139:30080). You can obtain your IP by running `kubectl get nodes` and your nodeport is 30080. 35 | 36 | > Note : If you deployed any servers or deployment from other scenarios, you need to delete them before you proceed to the following steps. 37 | > 38 | > ```bash 39 | > kubectl delete -f flightassist.yaml 40 | > ``` 41 | 42 | Lastly, run the following commands to deploy the secret and deployment. 43 | 44 | ```bash 45 | kubectl create -f secret.yaml 46 | kubectl create -f flightassist.yaml 47 | ``` 48 | 49 | Congratulations, now your Flightassist application should be running on `http://:30080`. You can go to [How to Use Flightassist](https://github.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms#how-to-use-flightassist) and start testing your application. 50 | 51 | ## Takeaway points 52 | Kubernetes is a powerful container orchestration tool, with container networking features built in. In this example we get a taste of the logical concept of clusters, creating a deployment and service binding. As a developer, you need to handle building container images and managing images in repositories. This gives user full control over the underlying OS, lends itself to reuse and portability across multiple container orchestration platforms. Service registration and loadbalancing features also lend itself to a microservices deployment. Though for a typical developer, the experience is infrastructure centric, requires knowledge about Kubernetes fundamentals, and the simplicity of deploying applications and binding services at a PaaS level are a bit lost. 53 | 54 | 55 | # Code Structure 56 | 57 | | File | Description | 58 | | ---------------------------------------- | ---------------------------------------- | 59 | | [flightassist.js](../main_application/flightassist.js) | Main application, start the express web server and calling the major AJAX functions| 60 | | All JavaScript files (main_application/*.js) | The implementation of the flightstats and tripIt information, shared by all deployment options | 61 | | [app.py](../flightassist-weather/scr/app.py) | Weather Microservice, query and sent weather information to the main application | 62 | | [package.json](../main_application/package.json) | List the packages required by the application | 63 | | [Dockerfile.local](../main_application/Dockerfile.local) and [Dockerfile.alpine](../flightassist-weather/Dockerfile.alpine) | Description of the Docker image | 64 | | [flightassist.yaml](../flightassist.yaml) and [secret.yaml](../secret.yaml)| Specification file for the deployment of the service and secret in Kubernetes | 65 | -------------------------------------------------------------------------------- /docs/openwhisk.md: -------------------------------------------------------------------------------- 1 | # Deploy Flightassist leveraging OpenWhisk functions 2 | 3 | In this scenario, we will deploy Flightassist to use a function that queries the necessary weather data. This shows how you could implement simple microservices with OpenWhisk. This approach replaces containers with OpenWhisk actions to save space and runtime memory in your cluster. Thanks to OpenWhisk built-in system packages, which includes a weather API, we don't even have to write our own action to implement the weather forecast lookup. 4 | 5 | First, follow the [Kubernetes Cluster Tutorial](https://github.com/IBM/container-journey-template) to create your own cluster on Bluemix. 6 | 7 | Then, install the container registry plugin for Bluemix CLI. 8 | 9 | ```bash 10 | bx plugin install container-registry -r Bluemix 11 | ``` 12 | Next, build your own docker images and push them to your own bluemix container registry. 13 | > You may skip these steps through deploying the secrets if you performed the Flightassist microservices deployment on Kubernetes or Istio scenarios 14 | 15 | > Replace `` with your own namespace, you can view your namespace by running `bx cr namespaces` 16 | > 17 | > If you have an unauthorized error, run `bx cr login` to authorized your container-registry. 18 | 19 | ```bash 20 | docker build -f main_application/Dockerfile.local -t registry.ng.bluemix.net//flightassist main_application 21 | docker build -f flightassist-weather/Dockerfile.alpine -t registry.ng.bluemix.net//weather-service flightassist-weather 22 | docker push registry.ng.bluemix.net//flightassist 23 | docker push registry.ng.bluemix.net//weather-service 24 | ``` 25 | 26 | Then, you need to run the following commands to bind your Cloudant and Weather Insights services to your clusters. 27 | 28 | ```bash 29 | bx cs cluster-service-bind {your-cluster-name} default mycloudant 30 | bx cs cluster-service-bind {your-cluster-name} default myweatherinsights 31 | ``` 32 | Next, create secret to give FlightStats and TripIt API credentials for Flightassist. Modify the secret.yaml file with **flightstats-app-id**, **flightstats-app-key**, **tripit-api-key**, and **tripit-api-secret**. 33 | 34 | Now, run the following commands to deploy the secret 35 | 36 | ```bash 37 | kubectl create -f secret.yaml 38 | ``` 39 | 40 | > Note : you need to delete all the services and deployments from any previous scenarios. 41 | > 42 | > ```bash 43 | > kubectl delete -f flightassist.yaml 44 | > ``` 45 | 46 | Then, install [OpenWhisk CLI](https://console.ng.bluemix.net/openwhisk/learn/cli) and Mark down its credentials. 47 | 48 | Next, edit `flightassist_serverless.yaml` and replace the `` with your own namespace, `` with your node ip and nodeport, and `` with your OpenWhisk authentication. You can run `wsk property get --auth | awk '{print $3}'` to view your OpenWhisk authentication. 49 | 50 | Now, let's deploy the new Flightassist app with serverless capability 51 | 52 | ```bash 53 | kubectl create -f flightassist_serverless.yaml 54 | ``` 55 | 56 | Congratulations, now your Flightassist application should be running on `http://:30080`. Also, you can learn about [How to Use Flightassist](https://github.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms#how-to-use-flightassist) and start testing your application. 57 | 58 | ## Takeaway points 59 | In this example, the weather microservice in the Flightassist app is a perfect use case to convert it into a function on OpenWhisk. It is an event-triggered app, a stateless app, there are no sessions to manage, don’t require persistence, and performs a simple logical step. Many microservices, which don’t need to be long running applications or tasks can easily, be converted into serverless functions. This essentially gives you benefits like scaling etc., as well as cost benefits that you pay for infrastructure only when the application is used. 60 | 61 | # Code Structure 62 | 63 | | File | Description | 64 | | ---------------------------------------- | ---------------------------------------- | 65 | | [flightassist.js](../main_application/flightassist.js) | Main application, start the express web server and calling the major AJAX functions| 66 | | [weather.js](../main_application/weather.js) | Trigger actions in OpenWhisk to get the weather information | 67 | | All JavaScript files (main_application/*.js) | The implementation of the flightstats and tripIt information, shared by all deployment options | 68 | | [package.json](../main_application/package.json) | List the packages required by the application | 69 | | [Dockerfile.local](../main_application/Dockerfile.local) | Description of the Docker image | 70 | | [flightassist_serverless.yaml](../flightassist_serverless.yaml) and [secret.yaml](../secret.yaml)| Specification file for the deployment of the service and secret in Kubernetes | 71 | -------------------------------------------------------------------------------- /flightassist-weather/Dockerfile.alpine: -------------------------------------------------------------------------------- 1 | FROM python:2.7-alpine 2 | 3 | RUN pip install --upgrade pip 4 | RUN pip install -U setuptools 5 | RUN pip install Flask Flask-Cache requests 6 | 7 | # application source code including static files, templates, etc 8 | ADD src /app/src 9 | 10 | EXPOSE 5000 11 | ENTRYPOINT ["python", "-u", "/app/src/app.py"] 12 | -------------------------------------------------------------------------------- /flightassist-weather/Dockerfile.phusion: -------------------------------------------------------------------------------- 1 | FROM phusion/baseimage 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | RUN apt-get update -qq && \ 5 | apt-get install -y -q --no-install-recommends \ 6 | python2.7 python-pip build-essential python-dev 7 | 8 | RUN pip install --upgrade pip 9 | RUN pip install -U setuptools 10 | RUN pip install Flask Flask-Cache requests 11 | 12 | # application source code including static files, templates, etc 13 | ADD src /app/src 14 | 15 | EXPOSE 5000 16 | 17 | ENTRYPOINT ["python", "-u", "/app/src/app.py"] 18 | -------------------------------------------------------------------------------- /flightassist-weather/Makefile: -------------------------------------------------------------------------------- 1 | # Simple Makefile to perform simple build/deploy steps for 2 | # the Flightassist demo application code 3 | 4 | .PHONY: localimage localdeploy 5 | 6 | localimage: Dockerfile.alpine 7 | docker build -f Dockerfile.alpine -t weather-service:v1 . 8 | 9 | localdeploy: localimage 10 | source .env && docker rm -f /weather-service && docker run --name weather-service -p 80:5000 \ 11 | -e WEATHER_URL=$WEATHER_URL weather-service:v1 12 | -------------------------------------------------------------------------------- /flightassist-weather/Procfile: -------------------------------------------------------------------------------- 1 | web: python src/app.py -------------------------------------------------------------------------------- /flightassist-weather/README.md: -------------------------------------------------------------------------------- 1 | # flightassist-weather 2 | Weather micro-service for the flightassist demo application, written in 3 | python. This micro-service interacts with the [Weather Company weather 4 | data](https://console.ng.bluemix.net/catalog/services/weather-company-data/) 5 | service to obtain the weather data for a given location. The microservice can 6 | run locally as Docker container or in a container service, for example, use `make localdeploy` to deploy the microservice locally. Once it is deployed, you may interact with the weather service using curl: 7 | 8 | curl -X GET 'http://localhost:80/weather/41.788136/-87.740871' 9 | 10 | When running the microservice in a container service such as swarm or 11 | kubernetes, you will replace localhost with the service name (e.g. 12 | weather-service) to lookup the service. 13 | -------------------------------------------------------------------------------- /flightassist-weather/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | applications: 3 | - name: filght-weather 4 | memory: 256M 5 | command: python src/app.py 6 | domain: mybluemix.net 7 | buildpack: python_buildpack 8 | path: . 9 | services: 10 | - myweatherinsights 11 | env: 12 | DEPLOY: cloudfoundry 13 | declared-services: 14 | - myweatherinsights: 15 | label: weatherinsights 16 | plan: Free-v2 -------------------------------------------------------------------------------- /flightassist-weather/requirements.txt: -------------------------------------------------------------------------------- 1 | Flask 2 | gunicorn 3 | requests -------------------------------------------------------------------------------- /flightassist-weather/src/app.py: -------------------------------------------------------------------------------- 1 | import os, json, requests 2 | from flask import Flask, Response, abort, request 3 | from datetime import datetime 4 | import logging 5 | from logging import StreamHandler 6 | #import urllib3 7 | 8 | #urllib3.disable_warnings() 9 | # Define the base logger 10 | logging.getLogger("weather-service").setLevel(logging.DEBUG) 11 | log = logging.getLogger("weather-service") 12 | stream_handler = StreamHandler() 13 | stream_formatter = logging.Formatter('[%(asctime)s] [%(thread)d] [%(module)s : %(lineno)d] [%(levelname)s] %(message)s') 14 | stream_handler.setFormatter(stream_formatter) 15 | log.addHandler(stream_handler) 16 | 17 | # Flask config 18 | app = Flask(__name__, static_url_path='') 19 | app.config['PROPAGATE_EXCEPTIONS'] = True 20 | 21 | # other global variables 22 | deploy_mode = os.environ['DEPLOY'] 23 | WEATHER_EP = '' 24 | 25 | ''' 26 | This is the analyzer API that accepts GET data as describes below: 27 | GET /weather// 28 | ''' 29 | @app.route('/weather//', methods=['GET']) 30 | def get_weather(lat, lon): 31 | 32 | weather_service_ep = WEATHER_EP + '/api/weather/v1/geocode/' + lat + '/' + lon + '/forecast/daily/3day.json' 33 | log.info(weather_service_ep) 34 | r = requests.get(weather_service_ep, headers={'Content-type': 'application/json'}) 35 | if r.status_code != 200: 36 | log.error("FAILED retrieve weather information msg: '%s', code: '%s'", r.json(), r.status_code) 37 | return None, r.status_code 38 | 39 | log.info("response json: '%s'", r.json()) 40 | return json.dumps(r.json()), r.status_code 41 | 42 | 43 | if __name__ == '__main__': 44 | # construct weather_ep from env var or vcap_services 45 | PORT = os.getenv('VCAP_APP_PORT', '5000') 46 | log.info("deploy mode is: '%s'", deploy_mode) 47 | 48 | if deploy_mode == 'swarm': 49 | with open('/run/secrets/weather_url', 'r') as url_secret: 50 | WEATHER_EP=url_secret.read().replace('\n', '') 51 | elif deploy_mode == 'kubernetes': 52 | with open('/run/secrets/service-bind/binding', 'r') as url_secret: 53 | data = json.loads(url_secret.read()) 54 | WEATHER_EP=data['url'] 55 | elif deploy_mode == 'cloudfoundry': 56 | data = json.loads(os.environ['VCAP_SERVICES']) 57 | WEATHER_EP = data['weatherinsights'][0]['credentials']['url'] 58 | else: 59 | WEATHER_EP = os.environ['WEATHER_URL'] 60 | 61 | log.info("Starting flightassist weather-service") 62 | app.run(host='0.0.0.0', port=int(PORT)) 63 | -------------------------------------------------------------------------------- /flightassist.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: weather-service 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | name: weather-service 10 | labels: 11 | run: weather-service 12 | spec: 13 | containers: 14 | - name: weather-service 15 | #change to your namespace, obtain your name space via `bx cr namespaces` 16 | image: "registry.ng.bluemix.net//weather-service" 17 | imagePullPolicy: IfNotPresent 18 | env: 19 | - name: DEPLOY 20 | value: "kubernetes" 21 | volumeMounts: 22 | - mountPath: /run/secrets/service-bind 23 | name: weather-service-bind-volume 24 | volumes: 25 | - name: weather-service-bind-volume 26 | secret: 27 | defaultMode: 420 28 | secretName: binding-myweatherinsights 29 | --- 30 | apiVersion: v1 31 | kind: Service 32 | metadata: 33 | name: weather-service 34 | labels: 35 | name: weather-service 36 | run: weather-service 37 | spec: 38 | selector: 39 | run: weather-service 40 | ports: 41 | - protocol: TCP 42 | port: 5000 43 | name: http 44 | --- 45 | apiVersion: extensions/v1beta1 46 | kind: Deployment 47 | metadata: 48 | name: flightassist-service 49 | spec: 50 | replicas: 1 51 | template: 52 | metadata: 53 | name: flightassist-service 54 | labels: 55 | run: flightassist-service 56 | spec: 57 | containers: 58 | - name: flightassist-service 59 | #change to your namespace, obtain your name space via `bx cr namespaces` 60 | image: "registry.ng.bluemix.net//flightassist" 61 | imagePullPolicy: IfNotPresent 62 | env: 63 | - name: DEPLOY 64 | value: "kubernetes" 65 | - name: USE_WEATHER_SERVICE 66 | value: "true" 67 | - name: BASE_URL 68 | #replace to your `ip:nodeport`. You can obtain your ip via `kubectl get nodes` and your nodeport is 30080. 69 | value: "http:///" 70 | - name: FORCE_FLIGHT_VIEW 71 | value: "true" 72 | volumeMounts: 73 | - mountPath: /opt/service-bind 74 | name: service-bind-volume2 75 | - mountPath: /run/secrets 76 | name: mysecret-volume 77 | volumes: 78 | - name: service-bind-volume2 79 | secret: 80 | defaultMode: 420 81 | secretName: binding-mycloudant 82 | - name: mysecret-volume 83 | secret: 84 | defaultMode: 420 85 | secretName: mysecret 86 | --- 87 | apiVersion: v1 88 | kind: Service 89 | metadata: 90 | name: flightassist-service 91 | labels: 92 | run: flightassist-service 93 | name: flightassist-service 94 | spec: 95 | # You can remove type: NodePort for istio deployment. 96 | type: NodePort 97 | selector: 98 | run: flightassist-service 99 | ports: 100 | - protocol: TCP 101 | port: 3000 102 | nodePort: 30080 103 | name: http 104 | -------------------------------------------------------------------------------- /flightassist_serverless.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Deployment 3 | metadata: 4 | name: flightassist-service 5 | spec: 6 | replicas: 1 7 | template: 8 | metadata: 9 | name: flightassist-service 10 | labels: 11 | run: flightassist-service 12 | spec: 13 | containers: 14 | - name: flightassist-service 15 | #change to your namespace, obtain your name space via `bx cr namespaces` 16 | image: "registry.ng.bluemix.net//flightassist" 17 | imagePullPolicy: IfNotPresent 18 | env: 19 | - name: DEPLOY 20 | value: "kubernetes" 21 | - name: USE_WEATHER_SERVICE 22 | value: "false" 23 | - name: USE_WEATHER_SERVERLESS 24 | value: "true" 25 | - name: OPENWHISK_AUTH 26 | #replace to your actual OpenWhisk Authendication. 27 | value: 28 | - name: BASE_URL 29 | #replace to your `ip:nodeport`. You can obtain your ip via `kubectl get nodes` and your nodeport is 30080. 30 | value: "http:///" 31 | - name: FORCE_FLIGHT_VIEW 32 | value: "true" 33 | volumeMounts: 34 | - mountPath: /opt/service-bind 35 | name: service-bind-volume2 36 | - mountPath: /run/secrets 37 | name: mysecret-volume 38 | - mountPath: /opt/service-bind2 39 | name: weather-service-bind-volume 40 | volumes: 41 | - name: service-bind-volume2 42 | secret: 43 | defaultMode: 420 44 | secretName: binding-mycloudant 45 | - name: weather-service-bind-volume 46 | secret: 47 | defaultMode: 420 48 | secretName: binding-myweatherinsights 49 | - name: mysecret-volume 50 | secret: 51 | defaultMode: 420 52 | secretName: mysecret 53 | --- 54 | apiVersion: v1 55 | kind: Service 56 | metadata: 57 | name: flightassist-service 58 | labels: 59 | run: flightassist-service 60 | spec: 61 | type: NodePort 62 | selector: 63 | run: flightassist-service 64 | ports: 65 | - protocol: TCP 66 | port: 3000 67 | nodePort: 30080 -------------------------------------------------------------------------------- /images/paas-containers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/images/paas-containers.png -------------------------------------------------------------------------------- /images/plans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/images/plans.png -------------------------------------------------------------------------------- /images/status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/images/status.png -------------------------------------------------------------------------------- /ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: extensions/v1beta1 2 | kind: Ingress 3 | metadata: 4 | name: gateway 5 | annotations: 6 | kubernetes.io/ingress.class: "istio" 7 | spec: 8 | backend: 9 | serviceName: flightassist-service 10 | servicePort: 3000 11 | rules: 12 | - http: 13 | paths: 14 | - path: / 15 | backend: 16 | serviceName: flightassist-service 17 | servicePort: 3000 18 | -------------------------------------------------------------------------------- /main_application/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .vscode 3 | .env 4 | dot-env 5 | node_modules 6 | -------------------------------------------------------------------------------- /main_application/Dockerfile.local: -------------------------------------------------------------------------------- 1 | FROM node:6 2 | 3 | # Create app directory 4 | RUN mkdir -p /usr/src/app 5 | WORKDIR /usr/src/app 6 | 7 | # Install app dependencies, assuming npm is already installed on the image 8 | COPY package.json /usr/src/app/ 9 | RUN npm install 10 | 11 | #app binding to 3000 12 | EXPOSE 3000 13 | 14 | # Bundle app source code inside the docker image 15 | COPY . /usr/src/app 16 | 17 | CMD [ "node", "flightassist.js" ] 18 | -------------------------------------------------------------------------------- /main_application/conversation.js: -------------------------------------------------------------------------------- 1 | // communicate with Watson conversation micro-service 2 | 3 | var restcall = require('./restcall.js'); 4 | var url = require('url'); 5 | 6 | module.exports = { 7 | // requires input in the query string: 8 | // - context = context information 9 | // - message = textual message 10 | Message: function(req, resp) { 11 | // send/receive message from the Watson conversation service 12 | if (process.env.DEVMODE === "true" && process.env.DEPLOY !== "swarm") { 13 | host = "localhost"; 14 | } else { 15 | host = "conversation-service"; 16 | } 17 | 18 | var options = { 19 | host: host, 20 | port: 6000, 21 | path: "/message/", 22 | method: "POST", 23 | rejectUnauthorized: false 24 | }; 25 | 26 | var postdata = { 27 | "context": req.body.context, 28 | "input": req.body.input, 29 | }; 30 | 31 | //send the request to the Conversation microservice API 32 | restcall.post(options, false, postdata, function(respData) { 33 | // response data will be sent back to our query 34 | console.log("sending JSON conversation response: " + JSON.stringify(respData)); 35 | resp.send(respData); 36 | }); 37 | 38 | } 39 | }; -------------------------------------------------------------------------------- /main_application/flightassist.js: -------------------------------------------------------------------------------- 1 | /* jshint node:true */ 2 | 3 | /** 4 | * Module dependencies. 5 | */ 6 | var express = require('express'), 7 | routes = require('./routes'), 8 | session = require("express-session"), 9 | http = require('http'), 10 | path = require('path'), 11 | fs = require('fs'), 12 | bodyParser = require('body-parser'); 13 | 14 | 15 | var app = express(); 16 | var conversationMode = false; 17 | if (process.env.CONVERSATION_MODE === "true") { 18 | conversationMode = true; 19 | } 20 | 21 | // if deploying to a different route, update this variable: 22 | var baseURL = process.env.BASE_URL; 23 | if (process.env.DEVMODE === "true") { 24 | baseURL = process.env.DEV_URL; 25 | } 26 | 27 | if (process.env.DEPLOY === "swarm") { 28 | // credentials are stored in secrets files instead of 29 | // environment variables; read each one and load them 30 | // into our application environment 31 | console.log("Swarm deploy mode is detected; collecting credentials from secrets"); 32 | var basePath = "/run/secrets/"; 33 | var list = ["flightstats_app_key", "weather_url", "tripit_api_secret", "tripit_api_key", 34 | "flightstats_app_id", "cloudant_url" 35 | ]; 36 | for (var i = 0; i < list.length; i++) { 37 | var contents = fs.readFileSync(basePath + list[i], "utf8"); 38 | contents = contents.replace(/[\n\r]/g, ''); //remove trailing newline 39 | global[list[i]] = contents; 40 | } 41 | } else if (process.env.DEPLOY === "kubernetes") { 42 | console.log("Kubernetes deploy mode is detected; collecting credentials from secrets"); 43 | var basePath = "/run/secrets/"; 44 | var list = ["flightstats-app-id", "flightstats-app-key", 45 | "tripit-api-key", "tripit-api-secret" 46 | ]; 47 | for (var i = 0; i < list.length; i++) { 48 | var contents = fs.readFileSync(basePath + list[i], "base64"); 49 | contents = contents.replace(/[\n\r]/g, ''); //remove trailing newline 50 | var key = list[i].replace(/[-]/g, '_'); // replace - with _ as - is not allowed in var name 51 | global[key] = contents; 52 | } 53 | baseURL = process.env.BASE_URL; 54 | } 55 | // all environments 56 | app.set('port', process.env.PORT || 3000); 57 | app.set('views', __dirname + '/views'); 58 | app.set('view engine', 'ejs'); 59 | app.use(express.static(path.join(__dirname, 'public'))); 60 | app.use(session({ secret: 'wilson dog ball', proxy: true, resave: true, saveUninitialized: true })); 61 | app.use(bodyParser.json()); // support json encoded bodies 62 | 63 | app.get('/', routes.index); 64 | 65 | app.get("/authorize", function(req, res) { 66 | tripit.authorize(baseURL + "flights", req.query.mobile, res); 67 | }); 68 | 69 | var tripdata = require('./routes/tripdata.js'), 70 | tripit = require('./tripit.js'), 71 | flightstats = require('./flightstats.js'), 72 | weather = require('./weather.js'), 73 | conversation = require('./conversation.js'); 74 | 75 | app.get("/flights", function(req, res) { 76 | var respData = {}; 77 | // will store the access tokens in the session 78 | tripit.getAccessTokens(req).then(function(results) { 79 | req.session.oauth_access_token = results[0]; 80 | req.session.oauth_access_token_secret = results[1]; 81 | var accessToken = results[0]; 82 | var accessTokenSecret = results[1]; 83 | console.log("acquired OATH access tokens"); 84 | 85 | // access TripIt trip data using our authenticated access information 86 | console.log("Request profile data for the authenticated TripIt user.."); 87 | tripit.getProfileData(accessToken, accessTokenSecret).then(function(results) { 88 | var profile = JSON.parse(results[0]); 89 | respData.name = profile.Profile.screen_name; 90 | respData.company = profile.Profile.company; 91 | respData.photo = profile.Profile.photo_url; 92 | respData.home = profile.Profile.home_city; 93 | console.log("Received profile info for " + respData.name + ". Rendering response.."); 94 | req.session.user = respData.name; 95 | // we have our static (older) view of travel details, or the 96 | // Watson powered "conversation" mode 97 | if (conversationMode === true) { 98 | res.render("conversation", respData); 99 | } else { 100 | res.render("trips", respData); 101 | } 102 | }, function(error) { 103 | console.log(error); 104 | respData.message = "Could not retrieve TripIt profile, error: " + error.data; 105 | respData.no_data = "error"; 106 | res.render("trips", respData); 107 | }); 108 | }, function(error) { 109 | console.log("Error getting authorization tokens: " + error.data); 110 | respData.message = "OAUTH login to TripIt failed with: " + error.data; 111 | respData.no_data = "error"; 112 | res.render("trips", respData); 113 | return; 114 | }); 115 | }); 116 | 117 | // wrap all AJAX-used methods to do an XHR check to limit use from outside of 118 | // our application: 119 | app.use("/i/\*", function(req, res, next) { 120 | if (req.xhr === true) { 121 | next(); 122 | } else { 123 | // reject API calls not from our application 124 | console.log("Non-Xhr request to API from: " + req.hostname + " (IP: " + req.ip + ")\nHeaders: " + 125 | "%j\nQuery: %j", req.headers, req.query); 126 | res.status(403).send("API access forbidden."); 127 | } 128 | }); 129 | 130 | // called via AJAX method to query user's trip data; return current flights 131 | app.get("/i/tripdata", tripdata.getFlights); 132 | 133 | // endpoints for flightstats API lookups: 134 | app.get("/i/flightinfo", flightstats.getFlightInfo); 135 | app.get("/i/conninfo", flightstats.getConnections); 136 | 137 | // weather endpoint 138 | app.get("/i/weather", weather.getThreeDayForecast); 139 | 140 | // Watson conversation service endpoint 141 | app.post("/conversation", conversation.Message); 142 | 143 | var server = http.createServer(app).listen(app.get('port'), function() { 144 | console.log('FlightAssist server listening on port ' + app.get('port')); 145 | }); 146 | 147 | // handle signals properly for when running without init/shell in container: 148 | 149 | // quit on ctrl-c when running docker in terminal 150 | process.on('SIGINT', function onSigint() { 151 | console.info('Got SIGINT (aka ctrl-c in docker). Graceful shutdown ', new Date().toISOString()); 152 | shutdown(); 153 | }); 154 | 155 | // quit properly on docker stop 156 | process.on('SIGTERM', function onSigterm() { 157 | console.info('Got SIGTERM (docker container stop). Graceful shutdown ', new Date().toISOString()); 158 | shutdown(); 159 | }); 160 | 161 | // shut down server 162 | function shutdown() { 163 | process.exit(); 164 | } -------------------------------------------------------------------------------- /main_application/flightstats.js: -------------------------------------------------------------------------------- 1 | /* Retrieve data from FlightStats API */ 2 | var FlightStatsAPI = require("flightstats"), 3 | Cloudant = require('cloudant'), 4 | fs = require('fs'); 5 | 6 | var flightStatsAppId = ""; 7 | var flightStatsAppKey = ""; 8 | if (process.env.DEPLOY === "swarm" || process.env.DEPLOY === "kubernetes") { 9 | flightStatsAppId = global.flightstats_app_id; 10 | flightStatsAppKey = global.flightstats_app_key; 11 | } else { 12 | flightStatsAppId = process.env.FLIGHTSTATS_APP_ID; 13 | flightStatsAppKey = process.env.FLIGHTSTATS_APP_KEY; 14 | } 15 | 16 | // cloudant credentials URL 17 | var cURL = ""; 18 | if (process.env.DEVMODE === "true") { 19 | if (process.env.DEPLOY === "swarm") { 20 | cURL = global.cloudant_url; 21 | } else { 22 | cURL = process.env.CLOUDANT_URL; 23 | } 24 | } else if (process.env.DEPLOY === "kubernetes") { 25 | console.log("kubernetes deploy mode is detected") 26 | var binding = JSON.parse(fs.readFileSync('/opt/service-bind/binding', 'utf8')); 27 | cURL = binding.url 28 | } else { 29 | var vcap_services = JSON.parse(process.env.VCAP_SERVICES); 30 | cURL = vcap_services.cloudantNoSQLDB[0].credentials.url; 31 | } 32 | 33 | var cloudant = Cloudant({ url: cURL, plugin: 'promises' }); 34 | var flightstats = new FlightStatsAPI({ 35 | appId: flightStatsAppId, 36 | apiKey: flightStatsAppKey 37 | }); 38 | 39 | module.exports = { 40 | // requires input in the query string: 41 | // - date = YYYY-MM-DD (departure date) 42 | // - airline = fs code (e.g. AA = American Airlines) 43 | // - flightnum = flight number 44 | // - airport = arrival airport code (e.g. SFO) 45 | getFlightInfo: function(req, resp) { 46 | // retrieve flight 47 | var opts = { 48 | date: new Date(Date.parse(req.query.date)), 49 | airlineCode: req.query.airline, 50 | flightNumber: req.query.flightnum, 51 | airport: req.query.airport, 52 | }; 53 | flightstats.lookup(opts, function(err, data) { 54 | if (err) { 55 | console.log("error looking up flight status: " + err); 56 | resp.send(err); 57 | return; 58 | } 59 | console.log("sending flight lookup response for %j%j", req.query.airline, req.query.flightnum); 60 | resp.send(data); 61 | }); 62 | }, 63 | getConnections: function(req, resp) { 64 | // get connecting flights options 65 | // requires the following in the query string: 66 | // - date = YYYY-MM-DD HH:MM:SS (time to start search) 67 | // - depairport = departing airport code (e.g. CHO) 68 | // - arrairport = arrival airport code (e.g. SFO) 69 | // - numhours = number of hours to search from start 70 | // - results = number of results to return 71 | 72 | // Look up cache first for connections data.. 73 | var fingerprint = req.query.date + "_" + req.query.depairport + "_" + req.query.arrairport + 74 | "_" + req.query.numhours + "_" + req.query.results; 75 | getCachedData(fingerprint).then(function(data) { 76 | var now = Date.now(); 77 | if ((now - data.cachetime) > 120 * 60 * 1000) { 78 | // data older than 2 hours; don't use cache 79 | console.log("Expiring cached connections data for " + fingerprint); 80 | data.expired = true; 81 | } 82 | return data; 83 | }).catch(function(err) { 84 | console.log("[getCachedFlightConnectionsData] Cloudant lookup error/empty: " + err); 85 | }).then(function(data) { 86 | if (!isEmpty(data) && !data.expired) { 87 | // use cached connections data 88 | console.log("using cached connecting flight data for " + fingerprint); 89 | resp.send(data); 90 | return; 91 | } 92 | // no cache or cache expired; query connection data 93 | var opts = { 94 | date: new Date(Date.parse(req.query.date)), 95 | departureAirport: req.query.depairport, 96 | arrivalAirport: req.query.arrairport, 97 | numHours: req.query.numhours, 98 | maxResults: req.query.results, 99 | }; 100 | flightstats.firstFlightOut(opts, function(err, newData) { 101 | if (err) { 102 | console.log("error looking up flight connections: " + err); 103 | resp.send(err); 104 | return; 105 | } 106 | if (!isEmpty(data)) { 107 | //set the rev ID so cache update works 108 | newData._rev = data._rev; 109 | } 110 | // cache this data in cloudant with the current epoch ms 111 | var currentEpochms = Date.now(); 112 | newData.cachetime = currentEpochms; 113 | newData._id = fingerprint; 114 | cacheConnectionsData(newData); 115 | console.log("sending flight connections JSON response for %j - %j on %j", req.query.depairport, req.query.arrairport, req.query.date); 116 | resp.send(newData); 117 | }); 118 | }); 119 | } 120 | }; 121 | 122 | function getCachedData(fingerprint) { 123 | // query cloudant to see if we have cached any connections data for this query combination 124 | var connDB = cloudant.db.use("connections"); 125 | return connDB.get(fingerprint); 126 | } 127 | 128 | function cacheConnectionsData(connData) { 129 | var connDB = cloudant.db.use("connections"); 130 | connDB.insert(connData, function(err, data) { 131 | if (err) { 132 | console.log("Error on connections DB insert: " + err); 133 | } 134 | }); 135 | } 136 | 137 | function isEmpty(obj) { 138 | if (obj === undefined) { 139 | return true; 140 | } 141 | return Object.keys(obj).length === 0; 142 | } 143 | -------------------------------------------------------------------------------- /main_application/manifest.yml: -------------------------------------------------------------------------------- 1 | --- 2 | declared-services: 3 | - mycloudant: 4 | label: cloudantNoSQLDB 5 | plan: Lite 6 | - myweatherinsights: 7 | label: weatherinsights 8 | plan: Free-v2 9 | 10 | applications: 11 | - name: FlightAssist 12 | path: . 13 | domain: mybluemix.net 14 | instances: 1 15 | memory: 512M 16 | services: 17 | - mycloudant 18 | - myweatherinsights 19 | env: 20 | DEPLOY: cloudfoundry 21 | -------------------------------------------------------------------------------- /main_application/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "FlightAssist", 3 | "version": "0.0.1", 4 | "description": "FlightAssist using TripIt and FlightAware APIs on Bluemix", 5 | "scripts": { 6 | "start": "node flightassist.js" 7 | }, 8 | "dependencies": { 9 | "express": "4.12.x", 10 | "cfenv": "1.0.x", 11 | "body-parser": "*", 12 | "tripit-node": "*", 13 | "ejs": "*", 14 | "cloudant": "*", 15 | "express-session": "*", 16 | "flightstats": "*" 17 | }, 18 | "repository": {} 19 | } 20 | -------------------------------------------------------------------------------- /main_application/project.json: -------------------------------------------------------------------------------- 1 | {"Name":"FlightAssist on bluemix.net"} 2 | -------------------------------------------------------------------------------- /main_application/public/css/conversation.css: -------------------------------------------------------------------------------- 1 | * { 2 | -webkit-box-sizing: border-box; 3 | -moz-box-sizing: border-box; 4 | box-sizing: border-box; 5 | } 6 | 7 | body { 8 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 9 | margin: 0; 10 | padding: 0; 11 | } 12 | 13 | div { 14 | word-wrap: break-word; 15 | line-height: 1.25rem; 16 | } 17 | 18 | #view-change-button { 19 | display: inline-block; 20 | position: absolute; 21 | width: 3.125rem; 22 | height: 3.125rem; 23 | border-radius: 1.5625rem; 24 | background: #AB72F8; 25 | top: 0.3125rem; 26 | right: 0.3125rem; 27 | line-height: 3.125rem; 28 | vertical-align: middle; 29 | } 30 | 31 | #view-change-button img { 32 | display: none; 33 | width: 100%; 34 | height: 100%; 35 | vertical-align: middle; 36 | } 37 | 38 | #view-change-button:not(.full) .not-full { 39 | display: inline-block; 40 | } 41 | 42 | #view-change-button.full .full { 43 | display: inline-block; 44 | } 45 | 46 | #contentParent { 47 | height: 100%; 48 | } 49 | 50 | .responsive-columns-wrapper { 51 | display: -ms-flexbox; 52 | display: -webkit-flex; 53 | display: flex; 54 | flex-direction: row; 55 | -ms-display: flex; 56 | -ms-flex-direction: row; 57 | } 58 | 59 | .responsive-column { 60 | -webkit-flex: 1; 61 | -ms-flex: 1; 62 | flex: 1; 63 | overflow: auto; 64 | } 65 | 66 | #chat-column-holder { 67 | text-align: center; 68 | } 69 | 70 | .chat-column { 71 | height: 100%; 72 | padding: 0.9375rem 0 0.625rem 0; 73 | margin: auto; 74 | text-align: left; 75 | max-width: 25rem; 76 | min-width: 9.375rem; 77 | } 78 | 79 | #scrollingChat { 80 | margin: 0.75rem; 81 | overflow-y: auto; 82 | overflow-x: hidden; 83 | height: calc(100% - 4rem); 84 | } 85 | 86 | .message-inner { 87 | opacity: 0; 88 | margin-top: 0.9375rem; 89 | -webkit-transition-property: opacity, margin-top; 90 | -webkit-transition-duration: 0.75s; 91 | -webkit-transition-timing-function: ease-in; 92 | -moz-transition-property: opacity, margin-top; 93 | -moz-transition-duration: 0.75s; 94 | -moz-transition-timing-function: ease-in; 95 | -o-transition-property: opacity, margin-top; 96 | -o-transition-duration: 0.75s; 97 | -o-transition-timing-function: ease-in; 98 | -ms-transition-property: opacity, margin-top; 99 | -ms-transition-duration: 0.75s; 100 | -ms-transition-timing-function: ease-in; 101 | transition-property: opacity, margin-top; 102 | transition-duration: 0.75s; 103 | transition-timing-function: ease-in; 104 | } 105 | 106 | .load .message-inner { 107 | opacity: 1; 108 | margin-top: 0.3125rem; 109 | } 110 | 111 | .from-user { 112 | text-align: right; 113 | } 114 | 115 | .from-user .message-inner { 116 | position: relative; 117 | font-size: 1rem; 118 | color: #fff; 119 | letter-spacing: 0.015rem; 120 | line-height: 1.3125rem; 121 | background: #00B4A0; 122 | border-radius: 1.25rem; 123 | border-bottom-right-radius: 0; 124 | text-align: left; 125 | display: inline-block; 126 | margin-left: 2.5rem; 127 | min-width: 2.5rem; 128 | } 129 | 130 | .from-user .message-inner p { 131 | margin: 0.3125rem; 132 | padding: 0 0.9375rem; 133 | } 134 | 135 | .from-user .message-inner:before, .from-user .message-inner:after { 136 | content: ""; 137 | position: absolute; 138 | } 139 | 140 | 141 | .from-user .message-inner:before { 142 | z-index: -2; 143 | bottom: -0.375rem; 144 | right: 0; 145 | height: 0.375rem; 146 | width: 0.5rem; 147 | background: #1cb3a0; 148 | } 149 | 150 | .from-user .message-inner:after { 151 | z-index: -1; 152 | bottom: -0.5rem; 153 | right: 0; 154 | height: 0.5rem; 155 | width: 0.5rem; 156 | background: #fff; 157 | border-top-right-radius: 1.25rem; 158 | } 159 | 160 | .from-watson .message-inner { 161 | position: relative; 162 | border-radius: 1.5625rem; 163 | font-size: 1rem; 164 | color: #B5B5B5; 165 | letter-spacing: 0.015rem; 166 | line-height: 1.3125rem; 167 | } 168 | 169 | .from-watson.latest .message-inner { 170 | color: #323232; 171 | } 172 | 173 | .from-watson p { 174 | margin: 0.3125rem; 175 | padding: 0 1.25rem; 176 | } 177 | 178 | .from-watson.latest.top p:before { 179 | content: "."; 180 | color: #9855D4; 181 | background-image: url("/images/marker_image.png"); 182 | background-size: 0.3125rem 1.3125rem; 183 | position: absolute; 184 | z-index: 2; 185 | left: 0.4375rem; 186 | width: 0.3125rem; 187 | height: 1.3125rem; 188 | line-height: 1.3125rem; 189 | } 190 | 191 | #textInput { 192 | border: none; 193 | outline: none; 194 | background: transparent; 195 | font-size: 1rem; 196 | color: #323232; 197 | letter-spacing: 0.015rem; 198 | line-height: 1.3125rem; 199 | height: 2.5rem; 200 | max-width: 100%; 201 | padding-left: 0.125rem; 202 | margin-bottom: -0.125rem; 203 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 204 | 205 | } 206 | 207 | #textInput.underline { 208 | border-bottom: 2px solid #00B4A0; 209 | } 210 | 211 | ::-webkit-input-placeholder { 212 | color: #B5B5B5; 213 | } 214 | 215 | ::-moz-placeholder { 216 | color: #B5B5B5; 217 | opacity: 1; 218 | } 219 | 220 | input:-moz-placeholder { 221 | color: #B5B5B5; 222 | opacity: 1; 223 | } 224 | 225 | :-ms-input-placeholder { 226 | color: #B5B5B5; 227 | } 228 | 229 | ::-ms-clear { 230 | display: none; 231 | } 232 | 233 | .inputOutline { 234 | display: block; 235 | border-bottom: 0.0625rem solid #aeaeae; 236 | margin-left: 0.5rem; 237 | margin-right: 0.5rem; 238 | } 239 | 240 | #textInputDummy { 241 | position:absolute; 242 | white-space:pre; 243 | top: 0; 244 | left: -1000%; 245 | opacity: 0; 246 | } 247 | 248 | #payload-column { 249 | font-family: Monaco, monospace; 250 | font-size: 0.75rem; 251 | letter-spacing: 0; 252 | line-height: 1.125rem; 253 | background-color: #23292A; 254 | color: #fff; 255 | overflow-x: auto; 256 | 257 | width: 45%; 258 | max-width: 32.0625rem; 259 | min-width: 29.6875rem; 260 | } 261 | 262 | #payload-column.full { 263 | width: 100%; 264 | max-width: none; 265 | min-width: initial; 266 | } 267 | 268 | #payload-column .header-text, #payload-column #payload-initial-message { 269 | font-family: Helvetica Neue for IBM, Helvetica Neue, Helvetica, Arial, sans-serif; 270 | font-size: 1.125rem; 271 | color: #9E9E9E; 272 | letter-spacing: 0.01875rem; 273 | padding: 0.5rem; 274 | padding-left: 2.5rem; 275 | background: #383D3E; 276 | } 277 | 278 | .hide { 279 | display: none; 280 | } 281 | 282 | .payload .line-numbers, .payload .payload-text { 283 | padding: 0.5rem; 284 | } 285 | 286 | .line-numbers { 287 | width: 2rem; 288 | color: #898989; 289 | text-align: right; 290 | } 291 | 292 | pre { 293 | margin: 0; 294 | word-wrap: normal; 295 | } 296 | 297 | .string { 298 | color: #54EED0; 299 | } 300 | 301 | .boolean, .null, .number { 302 | color: #CE8EFF; 303 | } 304 | 305 | .key { 306 | color: #66B7FF; 307 | } 308 | 309 | html{ 310 | font-size: 16px; 311 | } 312 | 313 | @media only screen and (max-width: 1000px) { 314 | html { 315 | font-size: 15px; 316 | } 317 | } 318 | 319 | @media only screen and (max-width: 600px) { 320 | html { 321 | font-size: 14px; 322 | } 323 | 324 | .chat-column { 325 | padding-top: 4rem; 326 | } 327 | 328 | #payload-column { 329 | width: 0; 330 | max-width: none; 331 | min-width: initial; 332 | } 333 | } 334 | 335 | /* IBM Design fonts https://github.ibm.com/Design/fonts */ 336 | @font-face { 337 | font-family: 'Helvetica Neue for IBM'; 338 | src: url('../fonts/light/h-n-light.eot?') format('eot'), 339 | url('../fonts/light/h-n-light.woff2') format('woff2'), 340 | url('../fonts/light/h-n-light.woff') format('woff'), 341 | url('../fonts/light/h-n-light.ttf') format('truetype'); 342 | font-weight: 300; 343 | font-style: normal; 344 | } 345 | @font-face { 346 | font-family: 'Helvetica Neue for IBM'; 347 | src: url('../fonts/light-italic/h-n-light-italic.eot?') format('eot'), 348 | url('../fonts/light-italic/h-n-light-italic.woff2') format('woff2'), 349 | url('../fonts/light-italic/h-n-light-italic.woff') format('woff'), 350 | url('../fonts/light-italic/h-n-light-italic.ttf') format('truetype'); 351 | font-weight: 300; 352 | font-style: italic; 353 | } 354 | @font-face { 355 | font-family: 'Helvetica Neue for IBM'; 356 | src: url('../fonts/roman/h-n-roman.eot?') format('eot'), 357 | url('../fonts/roman/h-n-roman.woff2') format('woff2'), 358 | url('../fonts/roman/h-n-roman.woff') format('woff'), 359 | url('../fonts/roman/h-n-roman.ttf') format('truetype'); 360 | font-weight: 400; 361 | font-style: normal; 362 | } 363 | @font-face { 364 | font-family: 'Helvetica Neue for IBM'; 365 | src: url('../fonts/roman-italic/h-n-roman-italic.eot?') format('eot'), 366 | url('../fonts/roman-italic/h-n-roman-italic.woff2') format('woff2'), 367 | url('../fonts/roman-italic/h-n-roman-italic.woff') format('woff'), 368 | url('../fonts/roman-italic/h-n-roman-italic.ttf') format('truetype'); 369 | font-weight: 400; 370 | font-style: italic; 371 | } 372 | @font-face { 373 | font-family: 'Helvetica Neue for IBM'; 374 | src: url('../fonts/medium/h-n-medium.eot?') format('eot'), 375 | url('../fonts/medium/h-n-medium.woff2') format('woff2'), 376 | url('../fonts/medium/h-n-medium.woff') format('woff'), 377 | url('../fonts/medium/h-n-medium.ttf') format('truetype'); 378 | font-weight: 500; 379 | font-style: normal; 380 | } 381 | @font-face { 382 | font-family: 'Helvetica Neue for IBM'; 383 | src: url('../fonts/medium-italic/h-n-medium-italic.eot?') format('eot'), 384 | url('../fonts/medium-italic/h-n-medium-italic.woff2') format('woff2'), 385 | url('../fonts/medium-italic/h-n-medium-italic.woff') format('woff'), 386 | url('../fonts/medium-italic/h-n-medium-italic.ttf') format('truetype'); 387 | font-weight: 500; 388 | font-style: italic; 389 | } 390 | @font-face { 391 | font-family: 'Helvetica Neue for IBM'; 392 | src: url('../fonts/bold/h-n-bold.eot?') format('eot'), 393 | url('../fonts/bold/h-n-bold.woff2') format('woff2'), 394 | url('../fonts/bold/h-n-bold.woff') format('woff'), 395 | url('../fonts/bold/h-n-bold.ttf') format('truetype'); 396 | font-weight: 700; 397 | font-style: normal; 398 | } 399 | @font-face { 400 | font-family: 'Helvetica Neue for IBM'; 401 | src: url('../fonts/bold-italic/h-n-bold-italic.eot?') format('eot'), 402 | url('../fonts/bold-italic/h-n-bold-italic.woff2') format('woff2'), 403 | url('../fonts/bold-italic/h-n-bold-italic.woff') format('woff'), 404 | url('../fonts/bold-italic/h-n-bold-italic.ttf') format('truetype'); 405 | font-weight: 700; 406 | font-style: italic; 407 | } 408 | 409 | /* IBM Icons */ 410 | @font-face { 411 | font-family: 'ibm-icons'; 412 | src:url('../fonts/ibm-icons.eot?ytcz1z') format('eot'), 413 | url('../fonts/ibm-icons.eot?ytcz1z#iefix') format('embedded-opentype'), 414 | url('../fonts/ibm-icons.ttf?ytcz1z') format('truetype'), 415 | url('../fonts/ibm-icons.woff?ytcz1z') format('woff'), 416 | url('../fonts/ibm-icons.svg?ytcz1z#ibm-icons') format('svg'); 417 | font-weight: normal; 418 | font-style: normal; 419 | } 420 | 421 | /* IBM glyphs */ 422 | @font-face { 423 | font-family: 'ibm-glyph'; 424 | src:url('../fonts/ibm-glyphs.eot?1b8643') format('eot'), 425 | url('../fonts/ibm-glyphs.eot?1b8643#iefix') format('embedded-opentype'), 426 | url('../fonts/ibm-glyphs.ttf?1b8643') format('truetype'), 427 | url('../fonts/ibm-glyphs.woff?1b8643') format('woff'), 428 | url('../fonts/ibm-glyphs.svg?1b8643#ibm-glyph') format('svg'); 429 | font-weight: normal; 430 | font-style: normal; 431 | } 432 | -------------------------------------------------------------------------------- /main_application/public/css/flightassist.css: -------------------------------------------------------------------------------- 1 | .throbber-div { 2 | display: none; 3 | height: 48px; 4 | position: relative; 5 | } 6 | 7 | img.ajax-loader { 8 | position: absolute; 9 | left: 50%; 10 | top: 50%; 11 | margin-left: -16px; 12 | margin-top: -16px; 13 | display: block; 14 | } 15 | 16 | .resultsHeader { 17 | padding: 10px; 18 | margin-left: 20px; 19 | font-size: 85%; 20 | } 21 | 22 | .tripName { 23 | font-weight: 600; 24 | font-size: 95%; 25 | color: rgb(58, 84, 76); 26 | } 27 | 28 | .tripDestination { 29 | font-weight: 600; 30 | font-size: 95%; 31 | color: rgb(58, 84, 76); 32 | } 33 | 34 | .resultsContainer { 35 | text-align: left; 36 | width: 80%; 37 | margin-left: 2%; 38 | } 39 | 40 | .flightresults { 41 | background: #efefef; 42 | opacity: 0.8; 43 | } 44 | 45 | .flightid { 46 | font-size: 110%; 47 | width: 50%; 48 | background: #3f3f3f; 49 | color: white; 50 | font-weight: bold; 51 | padding: 2px; 52 | padding-left: 15px; 53 | border-top: 1px solid #bbbbbb; 54 | border-right: 1px solid #cccccc; 55 | } 56 | 57 | .flighttime { 58 | font-size: 95%; 59 | font-weight: 300; 60 | font-style: italic; 61 | color: #dddddd; 62 | } 63 | 64 | .airport { 65 | margin: 10px; 66 | padding: 25px; 67 | } 68 | 69 | .airportDetails { 70 | padding-right: 15px; 71 | padding-left: 5px; 72 | width: 30%; 73 | } 74 | 75 | 76 | /* Mobile */ 77 | 78 | @media screen and (max-width: 800px) { 79 | .resultsContainer { 80 | width: 100%; 81 | margin-left: 1%; 82 | } 83 | .airportDetails { 84 | padding-right: 10px; 85 | padding-left: 3px; 86 | width: 25%; 87 | } 88 | } 89 | 90 | .airportCode { 91 | font-size: 170%; 92 | font-weight: bold; 93 | color: #558a26; 94 | } 95 | 96 | .airportLoc { 97 | font-size: 80%; 98 | font-weight: 300; 99 | font-style: italic; 100 | color: #9d9d9d; 101 | } 102 | 103 | .flightInfo { 104 | text-align: top; 105 | margin-top: 2px; 106 | padding-left: 10px; 107 | border-left: 1px solid #cdcdcd; 108 | } 109 | 110 | .fieldTitle { 111 | font-weight: bold; 112 | } 113 | 114 | .flightTime, 115 | .gateInfo { 116 | color: #5d5d5d; 117 | font-weight: 400; 118 | } 119 | 120 | .flightstatInfo { 121 | display: none; 122 | } 123 | 124 | .weatherInfo { 125 | display: none; 126 | } 127 | 128 | .weatherCity { 129 | font-weight: 500; 130 | } 131 | 132 | .weatherData { 133 | color: #0033cd; 134 | font-size: 80%; 135 | padding: 3px; 136 | margin: 0px; 137 | background: #cccccc; 138 | opacity: 0.9; 139 | } 140 | 141 | .statusDetail { 142 | color: #002266; 143 | font-size: 70%; 144 | padding: 2px; 145 | margin: 0px; 146 | background: #999999; 147 | border-top: 1px solid #555555; 148 | opacity: 0.9; 149 | } 150 | 151 | .flightStatsStatus { 152 | font-weight: 500; 153 | } 154 | 155 | .altoption { 156 | margin-top: 30px; 157 | padding-left: 3px; 158 | border-top: 2px solid #5d5d5d; 159 | border-bottom: 2px solid #5d5d5d; 160 | background: #3f3f3f; 161 | } 162 | 163 | .airportId, 164 | .airportFrom, 165 | .airportTo { 166 | font-weight: 500; 167 | } 168 | 169 | .optionitem { 170 | padding-left: 0px; 171 | background: #cccccc; 172 | color: #3f3f3f; 173 | opacity: 0.7; 174 | } 175 | 176 | .altflightTime { 177 | font-size: 70%; 178 | margin-left: 15px; 179 | padding: 2px; 180 | } -------------------------------------------------------------------------------- /main_application/public/css/ie8.css: -------------------------------------------------------------------------------- 1 | /* 2 | Overflow by HTML5 UP 3 | html5up.net | @ajlkn 4 | Free for personal and commercial use under the CCA 3.0 license (html5up.net/license) 5 | */ 6 | 7 | /* Basic */ 8 | 9 | body { 10 | background-image: url("../images/bg.jpg"); 11 | background-repeat: no-repeat; 12 | background-size: cover; 13 | background-position: auto; 14 | background-attachment: scroll; 15 | -ms-behavior: url("/js/ie/backgroundsize.min.htc"); 16 | } 17 | 18 | /* Section/Article */ 19 | 20 | section > .last-child, 21 | article > .last-child, 22 | section.last-child, 23 | article.last-child { 24 | margin-bottom: 0; 25 | } 26 | 27 | /* Button */ 28 | 29 | input[type="button"], 30 | input[type="submit"], 31 | input[type="reset"], 32 | button, 33 | .button { 34 | position: relative; 35 | -ms-behavior: url("/js/ie/PIE.htc"); 36 | } 37 | 38 | input[type="button"].style2:hover, 39 | input[type="submit"].style2:hover, 40 | input[type="reset"].style2:hover, 41 | button.style2:hover, 42 | .button.style2:hover { 43 | color: #35b88f !important; 44 | } 45 | 46 | input[type="button"].style3, 47 | input[type="submit"].style3, 48 | input[type="reset"].style3, 49 | button.style3, 50 | .button.style3 { 51 | background: #fefefe; 52 | border: solid 1px #dad9d9; 53 | } 54 | 55 | /* Footer */ 56 | 57 | #footer .icons { 58 | position: relative; 59 | -ms-behavior: url("/js/ie/PIE.htc"); 60 | } 61 | 62 | /* Header */ 63 | 64 | #header header p { 65 | border-top: solid 1px #888; 66 | } 67 | -------------------------------------------------------------------------------- /main_application/public/css/images/arrow.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /main_application/public/css/images/banner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /main_application/public/css/images/loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/css/images/loader.gif -------------------------------------------------------------------------------- /main_application/public/css/images/overlay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/css/images/overlay.png -------------------------------------------------------------------------------- /main_application/public/css/images/poptrox-closer.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /main_application/public/css/images/poptrox-nav.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /main_application/public/fonts/FontAwesome.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/FontAwesome.otf -------------------------------------------------------------------------------- /main_application/public/fonts/bold-italic/h-n-bold-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold-italic/h-n-bold-italic.eot -------------------------------------------------------------------------------- /main_application/public/fonts/bold-italic/h-n-bold-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold-italic/h-n-bold-italic.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/bold-italic/h-n-bold-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold-italic/h-n-bold-italic.woff -------------------------------------------------------------------------------- /main_application/public/fonts/bold-italic/h-n-bold-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold-italic/h-n-bold-italic.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/bold/h-n-bold.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold/h-n-bold.eot -------------------------------------------------------------------------------- /main_application/public/fonts/bold/h-n-bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold/h-n-bold.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/bold/h-n-bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold/h-n-bold.woff -------------------------------------------------------------------------------- /main_application/public/fonts/bold/h-n-bold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/bold/h-n-bold.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/fontawesome-webfont.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/fontawesome-webfont.eot -------------------------------------------------------------------------------- /main_application/public/fonts/fontawesome-webfont.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/fontawesome-webfont.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/fontawesome-webfont.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/fontawesome-webfont.woff -------------------------------------------------------------------------------- /main_application/public/fonts/fontawesome-webfont.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/fontawesome-webfont.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /main_application/public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /main_application/public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/ibm-glyphs.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/ibm-glyphs.eot -------------------------------------------------------------------------------- /main_application/public/fonts/ibm-glyphs.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/ibm-glyphs.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/ibm-glyphs.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/ibm-glyphs.woff -------------------------------------------------------------------------------- /main_application/public/fonts/ibm-icons.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/ibm-icons.eot -------------------------------------------------------------------------------- /main_application/public/fonts/ibm-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/ibm-icons.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/ibm-icons.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/ibm-icons.woff -------------------------------------------------------------------------------- /main_application/public/fonts/light-italic/h-n-light-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light-italic/h-n-light-italic.eot -------------------------------------------------------------------------------- /main_application/public/fonts/light-italic/h-n-light-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light-italic/h-n-light-italic.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/light-italic/h-n-light-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light-italic/h-n-light-italic.woff -------------------------------------------------------------------------------- /main_application/public/fonts/light-italic/h-n-light-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light-italic/h-n-light-italic.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/light/h-n-light.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light/h-n-light.eot -------------------------------------------------------------------------------- /main_application/public/fonts/light/h-n-light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light/h-n-light.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/light/h-n-light.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light/h-n-light.woff -------------------------------------------------------------------------------- /main_application/public/fonts/light/h-n-light.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/light/h-n-light.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/medium-italic/h-n-medium-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium-italic/h-n-medium-italic.eot -------------------------------------------------------------------------------- /main_application/public/fonts/medium-italic/h-n-medium-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium-italic/h-n-medium-italic.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/medium-italic/h-n-medium-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium-italic/h-n-medium-italic.woff -------------------------------------------------------------------------------- /main_application/public/fonts/medium-italic/h-n-medium-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium-italic/h-n-medium-italic.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/medium/h-n-medium.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium/h-n-medium.eot -------------------------------------------------------------------------------- /main_application/public/fonts/medium/h-n-medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium/h-n-medium.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/medium/h-n-medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium/h-n-medium.woff -------------------------------------------------------------------------------- /main_application/public/fonts/medium/h-n-medium.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/medium/h-n-medium.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/roman-italic/h-n-roman-italic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman-italic/h-n-roman-italic.eot -------------------------------------------------------------------------------- /main_application/public/fonts/roman-italic/h-n-roman-italic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman-italic/h-n-roman-italic.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/roman-italic/h-n-roman-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman-italic/h-n-roman-italic.woff -------------------------------------------------------------------------------- /main_application/public/fonts/roman-italic/h-n-roman-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman-italic/h-n-roman-italic.woff2 -------------------------------------------------------------------------------- /main_application/public/fonts/roman/h-n-roman.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman/h-n-roman.eot -------------------------------------------------------------------------------- /main_application/public/fonts/roman/h-n-roman.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman/h-n-roman.ttf -------------------------------------------------------------------------------- /main_application/public/fonts/roman/h-n-roman.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman/h-n-roman.woff -------------------------------------------------------------------------------- /main_application/public/fonts/roman/h-n-roman.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/fonts/roman/h-n-roman.woff2 -------------------------------------------------------------------------------- /main_application/public/images/bg-alt.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/bg-alt.jpg -------------------------------------------------------------------------------- /main_application/public/images/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/bg.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/01.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/02.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/03.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/04.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/05.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/06.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/07.jpg -------------------------------------------------------------------------------- /main_application/public/images/fulls/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/fulls/08.jpg -------------------------------------------------------------------------------- /main_application/public/images/marker_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/marker_image.png -------------------------------------------------------------------------------- /main_application/public/images/pic01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/pic01.jpg -------------------------------------------------------------------------------- /main_application/public/images/pic02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/pic02.jpg -------------------------------------------------------------------------------- /main_application/public/images/throbber.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/throbber.gif -------------------------------------------------------------------------------- /main_application/public/images/thumbs/01.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/01.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/02.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/02.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/03.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/03.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/04.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/04.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/05.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/05.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/06.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/06.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/07.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/07.jpg -------------------------------------------------------------------------------- /main_application/public/images/thumbs/08.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IBM/Microservices-deployment-with-PaaS-Containers-and-Serverless-Platforms/e85e30059e689ea90b1dd413323c8e80a02cee8a/main_application/public/images/thumbs/08.jpg -------------------------------------------------------------------------------- /main_application/public/js/api.js: -------------------------------------------------------------------------------- 1 | // The Api module is designed to handle all interactions with the server 2 | 3 | var Api = (function() { 4 | var requestPayload; 5 | var responsePayload; 6 | var messageEndpoint = '/conversation'; 7 | 8 | // Publicly accessible methods defined 9 | return { 10 | sendRequest: sendRequest, 11 | 12 | // The request/response getters/setters are defined here to prevent internal methods 13 | // from calling the methods without any of the callbacks that are added elsewhere. 14 | getRequestPayload: function() { 15 | return requestPayload; 16 | }, 17 | setRequestPayload: function(newPayloadStr) { 18 | requestPayload = JSON.parse(newPayloadStr); 19 | }, 20 | getResponsePayload: function() { 21 | return responsePayload; 22 | }, 23 | setResponsePayload: function(newPayloadStr) { 24 | responsePayload = JSON.parse(newPayloadStr); 25 | } 26 | }; 27 | 28 | // Send a message request to the server 29 | function sendRequest(text, context) { 30 | // Build request payload 31 | var payloadToWatson = {}; 32 | if (text) { 33 | payloadToWatson.input = { 34 | text: text 35 | }; 36 | } 37 | if (context) { 38 | payloadToWatson.context = context; 39 | } 40 | 41 | // Built http request 42 | var http = new XMLHttpRequest(); 43 | http.open('POST', messageEndpoint, true); 44 | http.setRequestHeader('Content-type', 'application/json'); 45 | http.onreadystatechange = function() { 46 | if (http.readyState === 4 && http.status === 200 && http.responseText) { 47 | Api.setResponsePayload(http.responseText); 48 | } 49 | }; 50 | 51 | var params = JSON.stringify(payloadToWatson); 52 | // Stored in variable (publicly visible through Api.getRequestPayload) 53 | // to be used throughout the application 54 | if (Object.getOwnPropertyNames(payloadToWatson).length !== 0) { 55 | Api.setRequestPayload(params); 56 | } 57 | 58 | // Send request 59 | http.send(params); 60 | } 61 | }()); -------------------------------------------------------------------------------- /main_application/public/js/common.js: -------------------------------------------------------------------------------- 1 | // The Common module is designed as an auxiliary module 2 | // to hold functions that are used in multiple other modules 3 | /* eslint no-unused-vars: "off" */ 4 | 5 | var Common = (function() { 6 | // Publicly accessible methods defined 7 | return { 8 | buildDomElement: buildDomElementFromJson, 9 | fireEvent: fireEvent, 10 | listForEach: listForEach 11 | }; 12 | 13 | // Take in JSON object and build a DOM element out of it 14 | // (Limited in scope, cannot necessarily create arbitrary DOM elements) 15 | // JSON Example: 16 | // { 17 | // "tagName": "div", 18 | // "text": "Hello World!", 19 | // "className": ["aClass", "bClass"], 20 | // "attributes": [{ 21 | // "name": "onclick", 22 | // "value": "alert("Hi there!")" 23 | // }], 24 | // "children: [{other similarly structured JSON objects...}, {...}] 25 | // } 26 | function buildDomElementFromJson(domJson) { 27 | // Create a DOM element with the given tag name 28 | var element = document.createElement(domJson.tagName); 29 | 30 | // Fill the "content" of the element 31 | if (domJson.text) { 32 | element.innerHTML = domJson.text; 33 | } else if (domJson.html) { 34 | element.insertAdjacentHTML('beforeend', domJson.html); 35 | } 36 | 37 | // Add classes to the element 38 | if (domJson.classNames) { 39 | for (var i = 0; i < domJson.classNames.length; i++) { 40 | element.classList.add(domJson.classNames[i]); 41 | } 42 | } 43 | // Add attributes to the element 44 | if (domJson.attributes) { 45 | for (var j = 0; j < domJson.attributes.length; j++) { 46 | var currentAttribute = domJson.attributes[j]; 47 | element.setAttribute(currentAttribute.name, currentAttribute.value); 48 | } 49 | } 50 | // Add children elements to the element 51 | if (domJson.children) { 52 | for (var k = 0; k < domJson.children.length; k++) { 53 | var currentChild = domJson.children[k]; 54 | element.appendChild(buildDomElementFromJson(currentChild)); 55 | } 56 | } 57 | return element; 58 | } 59 | 60 | // Trigger an event to fire 61 | function fireEvent(element, event) { 62 | var evt; 63 | if (document.createEventObject) { 64 | // dispatch for IE 65 | evt = document.createEventObject(); 66 | return element.fireEvent('on' + event, evt); 67 | } 68 | // otherwise, dispatch for Firefox, Chrome + others 69 | evt = document.createEvent('HTMLEvents'); 70 | evt.initEvent(event, true, true); // event type,bubbling,cancelable 71 | return !element.dispatchEvent(evt); 72 | } 73 | 74 | // A function that runs a for each loop on a List, running the callback function for each one 75 | function listForEach(list, callback) { 76 | for (var i = 0; i < list.length; i++) { 77 | callback.call(null, list[i]); 78 | } 79 | } 80 | }()); 81 | -------------------------------------------------------------------------------- /main_application/public/js/conversation.js: -------------------------------------------------------------------------------- 1 | // The ConversationPanel module is designed to handle 2 | // all display and behaviors of the conversation column of the app. 3 | /* eslint no-unused-vars: "off" */ 4 | /* global Api: true, Common: true*/ 5 | 6 | var ConversationPanel = (function() { 7 | var settings = { 8 | selectors: { 9 | chatBox: '#scrollingChat', 10 | fromUser: '.from-user', 11 | fromWatson: '.from-watson', 12 | latest: '.latest' 13 | }, 14 | authorTypes: { 15 | user: 'user', 16 | watson: 'watson' 17 | } 18 | }; 19 | 20 | // Publicly accessible methods defined 21 | return { 22 | init: init, 23 | inputKeyDown: inputKeyDown 24 | }; 25 | 26 | // Initialize the module 27 | function init() { 28 | chatUpdateSetup(); 29 | Api.sendRequest('', null); 30 | setupInputBox(); 31 | } 32 | // Set up callbacks on payload setters in Api module 33 | // This causes the displayMessage function to be called when messages are sent / received 34 | function chatUpdateSetup() { 35 | var currentRequestPayloadSetter = Api.setRequestPayload; 36 | Api.setRequestPayload = function(newPayloadStr) { 37 | currentRequestPayloadSetter.call(Api, newPayloadStr); 38 | displayMessage(JSON.parse(newPayloadStr), settings.authorTypes.user); 39 | }; 40 | 41 | var currentResponsePayloadSetter = Api.setResponsePayload; 42 | Api.setResponsePayload = function(newPayloadStr) { 43 | currentResponsePayloadSetter.call(Api, newPayloadStr); 44 | var message = handleResponseMessage(JSON.parse(newPayloadStr)); 45 | displayMessage(message, settings.authorTypes.watson); 46 | }; 47 | } 48 | 49 | // Set up the input box to underline text as it is typed 50 | // This is done by creating a hidden dummy version of the input box that 51 | // is used to determine what the width of the input text should be. 52 | // This value is then used to set the new width of the visible input box. 53 | function setupInputBox() { 54 | var input = document.getElementById('textInput'); 55 | var dummy = document.getElementById('textInputDummy'); 56 | var minFontSize = 14; 57 | var maxFontSize = 16; 58 | var minPadding = 4; 59 | var maxPadding = 6; 60 | 61 | // If no dummy input box exists, create one 62 | if (dummy === null) { 63 | var dummyJson = { 64 | 'tagName': 'div', 65 | 'attributes': [{ 66 | 'name': 'id', 67 | 'value': 'textInputDummy' 68 | }] 69 | }; 70 | 71 | dummy = Common.buildDomElement(dummyJson); 72 | document.body.appendChild(dummy); 73 | } 74 | 75 | function adjustInput() { 76 | if (input.value === '') { 77 | // If the input box is empty, remove the underline 78 | input.classList.remove('underline'); 79 | input.setAttribute('style', 'width:' + '100%'); 80 | input.style.width = '100%'; 81 | } else { 82 | // otherwise, adjust the dummy text to match, and then set the width of 83 | // the visible input box to match it (thus extending the underline) 84 | input.classList.add('underline'); 85 | var txtNode = document.createTextNode(input.value); 86 | ['font-size', 'font-style', 'font-weight', 'font-family', 'line-height', 87 | 'text-transform', 'letter-spacing' 88 | ].forEach(function(index) { 89 | dummy.style[index] = window.getComputedStyle(input, null).getPropertyValue(index); 90 | }); 91 | dummy.textContent = txtNode.textContent; 92 | 93 | var padding = 0; 94 | var htmlElem = document.getElementsByTagName('html')[0]; 95 | var currentFontSize = parseInt(window.getComputedStyle(htmlElem, null).getPropertyValue('font-size'), 10); 96 | if (currentFontSize) { 97 | padding = Math.floor((currentFontSize - minFontSize) / (maxFontSize - minFontSize) * 98 | (maxPadding - minPadding) + minPadding); 99 | } else { 100 | padding = maxPadding; 101 | } 102 | 103 | var widthValue = (dummy.offsetWidth + padding) + 'px'; 104 | input.setAttribute('style', 'width:' + widthValue); 105 | input.style.width = widthValue; 106 | } 107 | } 108 | 109 | // Any time the input changes, or the window resizes, adjust the size of the input box 110 | input.addEventListener('input', adjustInput); 111 | window.addEventListener('resize', adjustInput); 112 | 113 | // Trigger the input event once to set up the input box and dummy element 114 | Common.fireEvent(input, 'input'); 115 | } 116 | 117 | // Display a user or Watson message that has just been sent/received 118 | function displayMessage(newPayload, typeValue) { 119 | var isUser = isUserMessage(typeValue); 120 | var textExists = (newPayload.input && newPayload.input.text) || 121 | (newPayload.output && newPayload.output.text); 122 | if (isUser !== null && textExists) { 123 | // Create new message DOM element 124 | var messageDivs = buildMessageDomElements(newPayload, isUser); 125 | var chatBoxElement = document.querySelector(settings.selectors.chatBox); 126 | var previousLatest = chatBoxElement.querySelectorAll((isUser ? 127 | settings.selectors.fromUser : settings.selectors.fromWatson) + 128 | settings.selectors.latest); 129 | // Previous "latest" message is no longer the most recent 130 | if (previousLatest) { 131 | Common.listForEach(previousLatest, function(element) { 132 | element.classList.remove('latest'); 133 | }); 134 | } 135 | 136 | messageDivs.forEach(function(currentDiv) { 137 | chatBoxElement.appendChild(currentDiv); 138 | // Class to start fade in animation 139 | currentDiv.classList.add('load'); 140 | }); 141 | // Move chat to the most recent messages when new messages are added 142 | scrollToChatBottom(); 143 | } 144 | } 145 | 146 | // Checks if the given typeValue matches with the user "name", the Watson "name", or neither 147 | // Returns true if user, false if Watson, and null if neither 148 | // Used to keep track of whether a message was from the user or Watson 149 | function isUserMessage(typeValue) { 150 | if (typeValue === settings.authorTypes.user) { 151 | return true; 152 | } else if (typeValue === settings.authorTypes.watson) { 153 | return false; 154 | } 155 | return null; 156 | } 157 | 158 | // Constructs new DOM element from a message payload 159 | function buildMessageDomElements(newPayload, isUser) { 160 | var textArray = isUser ? newPayload.input.text : newPayload.output.text; 161 | if (Object.prototype.toString.call(textArray) !== '[object Array]') { 162 | textArray = [textArray]; 163 | } 164 | var messageArray = []; 165 | 166 | textArray.forEach(function(currentText) { 167 | if (currentText) { 168 | var messageJson = { 169 | //
170 | 'tagName': 'div', 171 | 'classNames': ['segments'], 172 | 'children': [{ 173 | //
174 | 'tagName': 'div', 175 | 'classNames': [(isUser ? 'from-user' : 'from-watson'), 'latest', ((messageArray.length === 0) ? 'top' : 'sub')], 176 | 'children': [{ 177 | //
178 | 'tagName': 'div', 179 | 'classNames': ['message-inner'], 180 | 'children': [{ 181 | //

{messageText}

182 | 'tagName': 'p', 183 | 'text': currentText 184 | }] 185 | }] 186 | }] 187 | }; 188 | messageArray.push(Common.buildDomElement(messageJson)); 189 | } 190 | }); 191 | 192 | return messageArray; 193 | } 194 | 195 | // Scroll to the bottom of the chat window (to the most recent messages) 196 | // Note: this method will bring the most recent user message into view, 197 | // even if the most recent message is from Watson. 198 | // This is done so that the "context" of the conversation is maintained in the view, 199 | // even if the Watson message is long. 200 | function scrollToChatBottom() { 201 | var scrollingChat = document.querySelector('#scrollingChat'); 202 | 203 | // Scroll to the latest message sent by the user 204 | var scrollEl = scrollingChat.querySelector(settings.selectors.fromUser + 205 | settings.selectors.latest); 206 | if (scrollEl) { 207 | scrollingChat.scrollTop = scrollEl.offsetTop; 208 | } 209 | } 210 | 211 | // Handles the submission of input 212 | function inputKeyDown(event, inputBox) { 213 | // Submit on enter key, dis-allowing blank messages 214 | if (event.keyCode === 13 && inputBox.value) { 215 | // Retrieve the context from the previous server response 216 | var context; 217 | var latestResponse = Api.getResponsePayload(); 218 | if (latestResponse) { 219 | context = latestResponse.context; 220 | } 221 | 222 | // Send the user message 223 | Api.sendRequest(inputBox.value, context); 224 | 225 | // Clear input box for further messages 226 | inputBox.value = ''; 227 | Common.fireEvent(inputBox, 'input'); 228 | } 229 | } 230 | }()); -------------------------------------------------------------------------------- /main_application/public/js/global.js: -------------------------------------------------------------------------------- 1 | /* global ConversationPanel: true */ 2 | /* eslint no-unused-vars: "off" */ 3 | 4 | // Other JS files required to be loaded first: apis.js, conversation.js 5 | (function() { 6 | // Initialize all modules 7 | ConversationPanel.init(); 8 | })(); 9 | 10 | // nasty globals for state 11 | var tripRetrieved = false; 12 | var alternatesRetrieved = false; 13 | var problemState = {}; 14 | 15 | // this function handles responses from the Watson conversation service 16 | // and optionally uses intents and e 17 | function handleResponseMessage(incomingMsg) { 18 | var response = incomingMsg; 19 | for (var i = 0; i < response.intents.length; i++) { 20 | var intent = response.intents[i]; 21 | var handled = false; 22 | switch (intent.intent) { 23 | case "when": 24 | if (intent.confidence > 0.5) { 25 | response = handleWhen(response); 26 | handled = true; 27 | } else { 28 | // not enough confidence to guess "when"..ask for more 29 | response.output.text = "Can you rephrase what you are looking for?"; 30 | } 31 | break; 32 | case "alternate": 33 | if (intent.confidence > 0.5) { 34 | response = handleAlternate(response); 35 | handled = true; 36 | break; 37 | } else { 38 | // not enough confidence to guess "when"..ask for more 39 | response.output.text = "Can you rephrase what you are looking for?"; 40 | } 41 | break; 42 | case "problem": 43 | if (intent.confidence > 0.5) { 44 | response = handleProblem(intent, response); 45 | handled = true; 46 | break; 47 | } else { 48 | // not enough confidence to guess "when"..ask for more 49 | response.output.text = "Can you rephrase what you are looking for?"; 50 | } 51 | break; 52 | } 53 | if (handled) { 54 | break; 55 | } 56 | } 57 | return response; 58 | } 59 | 60 | function handleWhen(response) { 61 | if (tripRetrieved === true) { 62 | $('#flight-alternates').css("display", "none"); 63 | $('#flight-results').css("display", "block"); 64 | } else { 65 | parseTripsForFlights(false, false); 66 | tripRetrieved = true; 67 | } 68 | response.output.text = "I've loaded your next trip details for you."; 69 | return response; 70 | } 71 | 72 | function handleAlternate(response) { 73 | if (!tripRetrieved) { 74 | parseTripsForFlights(false, true); 75 | tripRetrieved = true; 76 | } 77 | $('#flight-results').css("display", "none"); 78 | if (alternatesRetrieved) { 79 | $('#flight-alternates').css("display", "block"); 80 | } else { 81 | $('#flight-alternates').trigger('instantiate'); 82 | alternatesRetrieved = true; 83 | } 84 | response.output.text = "I've loaded alternate flights between your origin and destination for you."; 85 | return response; 86 | } 87 | 88 | function handleProblem(intent, response) { 89 | // first check if the conversation service has a response (to clarify information): 90 | if (response.output.text.length > 0) { 91 | problemState = response; 92 | return response; 93 | } 94 | var airport = ""; 95 | if (response.entities.length > 0) { 96 | if (response.entities[0].entity === "Airport") { 97 | // user has responded with an airport: 98 | airport = response.entities[0].value; // this will be the official entity name 99 | } 100 | } 101 | if (airport !== "") { 102 | var childDiv = $("#flight-alternates").children("div"); 103 | response.output.text = "Let me see if I can help you get from " + airport + " to your destination via an alternate flight."; 104 | // BIG FIXME: Due to time constraints, for our DockerCon demo with the 105 | // conversation service, we hardcoded Austin and the date to show a 106 | // lookup specific to a story we were telling about Lin's canceled flight 107 | // FIX: actually parse the known trip JSON to find where the traveler is 108 | // going and use the date of "now" or that original flight date/time to 109 | // do the alternate search. 110 | outputAlternateFlights(childDiv[0], airportCode(airport), "AUS", "2017-04-18T13:00:00-05:00", 12, 15); 111 | } else { 112 | response.output.text = "I see you are having flight problems."; 113 | } 114 | return response; 115 | } 116 | 117 | function airportCode(cityEntity) { 118 | switch (cityEntity) { 119 | case "Dallas": 120 | return "DFW"; 121 | case "Austin": 122 | return "AUS"; 123 | case "Chicago": 124 | return "ORD"; 125 | case "Raleigh": 126 | return "RDU"; 127 | case "Charlotte": 128 | return "CLT"; 129 | case "Charlottesville": 130 | return "CHO"; 131 | default: 132 | return "Unknown"; 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /main_application/public/js/ie/backgroundsize.min.htc: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /main_application/public/js/ie/html5shiv.js: -------------------------------------------------------------------------------- 1 | /* 2 | HTML5 Shiv v3.6.2 | @afarkas @jdalton @jon_neal @rem | MIT/GPL2 Licensed 3 | */ 4 | (function(l,f){function m(){var a=e.elements;return"string"==typeof a?a.split(" "):a}function i(a){var b=n[a[o]];b||(b={},h++,a[o]=h,n[h]=b);return b}function p(a,b,c){b||(b=f);if(g)return b.createElement(a);c||(c=i(b));b=c.cache[a]?c.cache[a].cloneNode():r.test(a)?(c.cache[a]=c.createElem(a)).cloneNode():c.createElem(a);return b.canHaveChildren&&!s.test(a)?c.frag.appendChild(b):b}function t(a,b){if(!b.cache)b.cache={},b.createElem=a.createElement,b.createFrag=a.createDocumentFragment,b.frag=b.createFrag(); 5 | a.createElement=function(c){return!e.shivMethods?b.createElem(c):p(c,a,b)};a.createDocumentFragment=Function("h,f","return function(){var n=f.cloneNode(),c=n.createElement;h.shivMethods&&("+m().join().replace(/\w+/g,function(a){b.createElem(a);b.frag.createElement(a);return'c("'+a+'")'})+");return n}")(e,b.frag)}function q(a){a||(a=f);var b=i(a);if(e.shivCSS&&!j&&!b.hasCSS){var c,d=a;c=d.createElement("p");d=d.getElementsByTagName("head")[0]||d.documentElement;c.innerHTML="x"; 6 | c=d.insertBefore(c.lastChild,d.firstChild);b.hasCSS=!!c}g||t(a,b);return a}var k=l.html5||{},s=/^<|^(?:button|map|select|textarea|object|iframe|option|optgroup)$/i,r=/^(?:a|b|code|div|fieldset|h1|h2|h3|h4|h5|h6|i|label|li|ol|p|q|span|strong|style|table|tbody|td|th|tr|ul)$/i,j,o="_html5shiv",h=0,n={},g;(function(){try{var a=f.createElement("a");a.innerHTML="";j="hidden"in a;var b;if(!(b=1==a.childNodes.length)){f.createElement("a");var c=f.createDocumentFragment();b="undefined"==typeof c.cloneNode|| 7 | "undefined"==typeof c.createDocumentFragment||"undefined"==typeof c.createElement}g=b}catch(d){g=j=!0}})();var e={elements:k.elements||"abbr article aside audio bdi canvas data datalist details figcaption figure footer header hgroup main mark meter nav output progress section summary time video",version:"3.6.2",shivCSS:!1!==k.shivCSS,supportsUnknownElements:g,shivMethods:!1!==k.shivMethods,type:"default",shivDocument:q,createElement:p,createDocumentFragment:function(a,b){a||(a=f);if(g)return a.createDocumentFragment(); 8 | for(var b=b||i(a),c=b.frag.cloneNode(),d=0,e=m(),h=e.length;d #mq-test-1 { width: 42px; }',c.insertBefore(e,d),b=42===f.offsetWidth,c.removeChild(e),{matches:b,media:a}}}(a.document)}(this),function(a){"use strict";function b(){v(!0)}var c={};a.respond=c,c.update=function(){};var d=[],e=function(){var b=!1;try{b=new a.XMLHttpRequest}catch(c){b=new a.ActiveXObject("Microsoft.XMLHTTP")}return function(){return b}}(),f=function(a,b){var c=e();c&&(c.open("GET",a,!0),c.onreadystatechange=function(){4!==c.readyState||200!==c.status&&304!==c.status||b(c.responseText)},4!==c.readyState&&c.send(null))},g=function(a){return a.replace(c.regex.minmaxwh,"").match(c.regex.other)};if(c.ajax=f,c.queue=d,c.unsupportedmq=g,c.regex={media:/@media[^\{]+\{([^\{\}]*\{[^\}\{]*\})+/gi,keyframes:/@(?:\-(?:o|moz|webkit)\-)?keyframes[^\{]+\{(?:[^\{\}]*\{[^\}\{]*\})+[^\}]*\}/gi,comments:/\/\*[^*]*\*+([^/][^*]*\*+)*\//gi,urls:/(url\()['"]?([^\/\)'"][^:\)'"]+)['"]?(\))/g,findStyles:/@media *([^\{]+)\{([\S\s]+?)$/,only:/(only\s+)?([a-zA-Z]+)\s?/,minw:/\(\s*min\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,maxw:/\(\s*max\-width\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/,minmaxwh:/\(\s*m(in|ax)\-(height|width)\s*:\s*(\s*[0-9\.]+)(px|em)\s*\)/gi,other:/\([^\)]*\)/g},c.mediaQueriesSupported=a.matchMedia&&null!==a.matchMedia("only all")&&a.matchMedia("only all").matches,!c.mediaQueriesSupported){var h,i,j,k=a.document,l=k.documentElement,m=[],n=[],o=[],p={},q=30,r=k.getElementsByTagName("head")[0]||l,s=k.getElementsByTagName("base")[0],t=r.getElementsByTagName("link"),u=function(){var a,b=k.createElement("div"),c=k.body,d=l.style.fontSize,e=c&&c.style.fontSize,f=!1;return b.style.cssText="position:absolute;font-size:1em;width:1em",c||(c=f=k.createElement("body"),c.style.background="none"),l.style.fontSize="100%",c.style.fontSize="100%",c.appendChild(b),f&&l.insertBefore(c,l.firstChild),a=b.offsetWidth,f?l.removeChild(c):c.removeChild(b),l.style.fontSize=d,e&&(c.style.fontSize=e),a=j=parseFloat(a)},v=function(b){var c="clientWidth",d=l[c],e="CSS1Compat"===k.compatMode&&d||k.body[c]||d,f={},g=t[t.length-1],p=(new Date).getTime();if(b&&h&&q>p-h)return a.clearTimeout(i),i=a.setTimeout(v,q),void 0;h=p;for(var s in m)if(m.hasOwnProperty(s)){var w=m[s],x=w.minw,y=w.maxw,z=null===x,A=null===y,B="em";x&&(x=parseFloat(x)*(x.indexOf(B)>-1?j||u():1)),y&&(y=parseFloat(y)*(y.indexOf(B)>-1?j||u():1)),w.hasquery&&(z&&A||!(z||e>=x)||!(A||y>=e))||(f[w.media]||(f[w.media]=[]),f[w.media].push(n[w.rules]))}for(var C in o)o.hasOwnProperty(C)&&o[C]&&o[C].parentNode===r&&r.removeChild(o[C]);o.length=0;for(var D in f)if(f.hasOwnProperty(D)){var E=k.createElement("style"),F=f[D].join("\n");E.type="text/css",E.media=D,r.insertBefore(E,g.nextSibling),E.styleSheet?E.styleSheet.cssText=F:E.appendChild(k.createTextNode(F)),o.push(E)}},w=function(a,b,d){var e=a.replace(c.regex.comments,"").replace(c.regex.keyframes,"").match(c.regex.media),f=e&&e.length||0;b=b.substring(0,b.lastIndexOf("/"));var h=function(a){return a.replace(c.regex.urls,"$1"+b+"$2$3")},i=!f&&d;b.length&&(b+="/"),i&&(f=1);for(var j=0;f>j;j++){var k,l,o,p;i?(k=d,n.push(h(a))):(k=e[j].match(c.regex.findStyles)&&RegExp.$1,n.push(RegExp.$2&&h(RegExp.$2))),o=k.split(","),p=o.length;for(var q=0;p>q;q++)l=o[q],g(l)||m.push({media:l.split("(")[0].match(c.regex.only)&&RegExp.$2||"all",rules:n.length-1,hasquery:l.indexOf("(")>-1,minw:l.match(c.regex.minw)&&parseFloat(RegExp.$1)+(RegExp.$2||""),maxw:l.match(c.regex.maxw)&&parseFloat(RegExp.$1)+(RegExp.$2||"")})}v()},x=function(){if(d.length){var b=d.shift();f(b.href,function(c){w(c,b.href,b.media),p[b.href]=!0,a.setTimeout(function(){x()},0)})}},y=function(){for(var b=0;b1){for(o=0;o 0) { 80 | 81 | var $header_header = $header.find('header'); 82 | 83 | $window 84 | .on('resize.overflow_fsh', function() { 85 | 86 | if (skel.breakpoint('mobile').active) 87 | $header.css('padding', ''); 88 | else { 89 | 90 | var p = Math.max(192, ($window.height() - $header_header.outerHeight()) / 2); 91 | $header.css('padding', p + 'px 0 ' + p + 'px 0'); 92 | 93 | } 94 | 95 | }) 96 | .trigger('resize.overflow_fsh'); 97 | 98 | $window.load(function() { 99 | $window.trigger('resize.overflow_fsh'); 100 | }); 101 | 102 | } 103 | 104 | } 105 | 106 | // Parallax background. 107 | 108 | // Disable parallax on IE (smooth scrolling is jerky), and on mobile platforms (= better performance). 109 | if (skel.vars.browser == 'ie' 110 | || skel.vars.mobile) 111 | settings.parallax = false; 112 | 113 | if (settings.parallax) { 114 | 115 | var $dummy = $(), $bg; 116 | 117 | $window 118 | .on('scroll.overflow_parallax', function() { 119 | 120 | // Adjust background position. 121 | $bg.css('background-position', 'center ' + (-1 * (parseInt($window.scrollTop()) / settings.parallaxFactor)) + 'px'); 122 | 123 | }) 124 | .on('resize.overflow_parallax', function() { 125 | 126 | // If we're in a situation where we need to temporarily disable parallax, do so. 127 | if (!skel.breakpoint('wide').active 128 | || skel.breakpoint('narrow').active) { 129 | 130 | $body.css('background-position', ''); 131 | $bg = $dummy; 132 | 133 | } 134 | 135 | // Otherwise, continue as normal. 136 | else 137 | $bg = $body; 138 | 139 | // Trigger scroll handler. 140 | $window.triggerHandler('scroll.overflow_parallax'); 141 | 142 | }) 143 | .trigger('resize.overflow_parallax'); 144 | } 145 | }); 146 | 147 | })(jQuery); 148 | -------------------------------------------------------------------------------- /main_application/public/js/skel.min.js: -------------------------------------------------------------------------------- 1 | /* skel.js v3.0.1 | (c) skel.io | MIT licensed */ 2 | var skel=function(){"use strict";var t={breakpointIds:null,events:{},isInit:!1,obj:{attachments:{},breakpoints:{},head:null,states:{}},sd:"/",state:null,stateHandlers:{},stateId:"",vars:{},DOMReady:null,indexOf:null,isArray:null,iterate:null,matchesMedia:null,extend:function(e,n){t.iterate(n,function(i){t.isArray(n[i])?(t.isArray(e[i])||(e[i]=[]),t.extend(e[i],n[i])):"object"==typeof n[i]?("object"!=typeof e[i]&&(e[i]={}),t.extend(e[i],n[i])):e[i]=n[i]})},newStyle:function(t){var e=document.createElement("style");return e.type="text/css",e.innerHTML=t,e},_canUse:null,canUse:function(e){t._canUse||(t._canUse=document.createElement("div"));var n=t._canUse.style,i=e.charAt(0).toUpperCase()+e.slice(1);return e in n||"Moz"+i in n||"Webkit"+i in n||"O"+i in n||"ms"+i in n},on:function(e,n){var i=e.split(/[\s]+/);return t.iterate(i,function(e){var a=i[e];if(t.isInit){if("init"==a)return void n();if("change"==a)n();else{var r=a.charAt(0);if("+"==r||"!"==r){var o=a.substring(1);if(o in t.obj.breakpoints)if("+"==r&&t.obj.breakpoints[o].active)n();else if("!"==r&&!t.obj.breakpoints[o].active)return void n()}}}t.events[a]||(t.events[a]=[]),t.events[a].push(n)}),t},trigger:function(e){return t.events[e]&&0!=t.events[e].length?(t.iterate(t.events[e],function(n){t.events[e][n]()}),t):void 0},breakpoint:function(e){return t.obj.breakpoints[e]},breakpoints:function(e){function n(t,e){this.name=this.id=t,this.media=e,this.active=!1,this.wasActive=!1}return n.prototype.matches=function(){return t.matchesMedia(this.media)},n.prototype.sync=function(){this.wasActive=this.active,this.active=this.matches()},t.iterate(e,function(i){t.obj.breakpoints[i]=new n(i,e[i])}),window.setTimeout(function(){t.poll()},0),t},addStateHandler:function(e,n){t.stateHandlers[e]=n},callStateHandler:function(e){var n=t.stateHandlers[e]();t.iterate(n,function(e){t.state.attachments.push(n[e])})},changeState:function(e){t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].sync()}),t.vars.lastStateId=t.stateId,t.stateId=e,t.breakpointIds=t.stateId===t.sd?[]:t.stateId.substring(1).split(t.sd),t.obj.states[t.stateId]?t.state=t.obj.states[t.stateId]:(t.obj.states[t.stateId]={attachments:[]},t.state=t.obj.states[t.stateId],t.iterate(t.stateHandlers,t.callStateHandler)),t.detachAll(t.state.attachments),t.attachAll(t.state.attachments),t.vars.stateId=t.stateId,t.vars.state=t.state,t.trigger("change"),t.iterate(t.obj.breakpoints,function(e){t.obj.breakpoints[e].active?t.obj.breakpoints[e].wasActive||t.trigger("+"+e):t.obj.breakpoints[e].wasActive&&t.trigger("-"+e)})},generateStateConfig:function(e,n){var i={};return t.extend(i,e),t.iterate(t.breakpointIds,function(e){t.extend(i,n[t.breakpointIds[e]])}),i},getStateId:function(){var e="";return t.iterate(t.obj.breakpoints,function(n){var i=t.obj.breakpoints[n];i.matches()&&(e+=t.sd+i.id)}),e},poll:function(){var e="";e=t.getStateId(),""===e&&(e=t.sd),e!==t.stateId&&t.changeState(e)},_attach:null,attach:function(e){var n=t.obj.head,i=e.element;return i.parentNode&&i.parentNode.tagName?!1:(t._attach||(t._attach=n.firstChild),n.insertBefore(i,t._attach.nextSibling),e.permanent&&(t._attach=i),!0)},attachAll:function(e){var n=[];t.iterate(e,function(t){n[e[t].priority]||(n[e[t].priority]=[]),n[e[t].priority].push(e[t])}),n.reverse(),t.iterate(n,function(e){t.iterate(n[e],function(i){t.attach(n[e][i])})})},detach:function(t){var e=t.element;return t.permanent||!e.parentNode||e.parentNode&&!e.parentNode.tagName?!1:(e.parentNode.removeChild(e),!0)},detachAll:function(e){var n={};t.iterate(e,function(t){n[e[t].id]=!0}),t.iterate(t.obj.attachments,function(e){e in n||t.detach(t.obj.attachments[e])})},attachment:function(e){return e in t.obj.attachments?t.obj.attachments[e]:null},newAttachment:function(e,n,i,a){return t.obj.attachments[e]={id:e,element:n,priority:i,permanent:a}},init:function(){t.initMethods(),t.initVars(),t.initEvents(),t.obj.head=document.getElementsByTagName("head")[0],t.isInit=!0,t.trigger("init")},initEvents:function(){t.on("resize",function(){t.poll()}),t.on("orientationChange",function(){t.poll()}),t.DOMReady(function(){t.trigger("ready")}),window.onload&&t.on("load",window.onload),window.onload=function(){t.trigger("load")},window.onresize&&t.on("resize",window.onresize),window.onresize=function(){t.trigger("resize")},window.onorientationchange&&t.on("orientationChange",window.onorientationchange),window.onorientationchange=function(){t.trigger("orientationChange")}},initMethods:function(){document.addEventListener?!function(e,n){t.DOMReady=n()}("domready",function(){function t(t){for(r=1;t=n.shift();)t()}var e,n=[],i=document,a="DOMContentLoaded",r=/^loaded|^c/.test(i.readyState);return i.addEventListener(a,e=function(){i.removeEventListener(a,e),t()}),function(t){r?t():n.push(t)}}):!function(e,n){t.DOMReady=n()}("domready",function(t){function e(t){for(h=1;t=i.shift();)t()}var n,i=[],a=!1,r=document,o=r.documentElement,s=o.doScroll,c="DOMContentLoaded",d="addEventListener",u="onreadystatechange",l="readyState",f=s?/^loaded|^c/:/^loaded|c/,h=f.test(r[l]);return r[d]&&r[d](c,n=function(){r.removeEventListener(c,n,a),e()},a),s&&r.attachEvent(u,n=function(){/^c/.test(r[l])&&(r.detachEvent(u,n),e())}),t=s?function(e){self!=top?h?e():i.push(e):function(){try{o.doScroll("left")}catch(n){return setTimeout(function(){t(e)},50)}e()}()}:function(t){h?t():i.push(t)}}),Array.prototype.indexOf?t.indexOf=function(t,e){return t.indexOf(e)}:t.indexOf=function(t,e){if("string"==typeof t)return t.indexOf(e);var n,i,a=e?e:0;if(!this)throw new TypeError;if(i=this.length,0===i||a>=i)return-1;for(0>a&&(a=i-Math.abs(a)),n=a;i>n;n++)if(this[n]===t)return n;return-1},Array.isArray?t.isArray=function(t){return Array.isArray(t)}:t.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)},Object.keys?t.iterate=function(t,e){if(!t)return[];var n,i=Object.keys(t);for(n=0;i[n]&&e(i[n],t[i[n]])!==!1;n++);}:t.iterate=function(t,e){if(!t)return[];var n;for(n in t)if(Object.prototype.hasOwnProperty.call(t,n)&&e(n,t[n])===!1)break},window.matchMedia?t.matchesMedia=function(t){return""==t?!0:window.matchMedia(t).matches}:window.styleMedia||window.media?t.matchesMedia=function(t){if(""==t)return!0;var e=window.styleMedia||window.media;return e.matchMedium(t||"all")}:window.getComputedStyle?t.matchesMedia=function(t){if(""==t)return!0;var e=document.createElement("style"),n=document.getElementsByTagName("script")[0],i=null;e.type="text/css",e.id="matchmediajs-test",n.parentNode.insertBefore(e,n),i="getComputedStyle"in window&&window.getComputedStyle(e,null)||e.currentStyle;var a="@media "+t+"{ #matchmediajs-test { width: 1px; } }";return e.styleSheet?e.styleSheet.cssText=a:e.textContent=a,"1px"===i.width}:t.matchesMedia=function(t){if(""==t)return!0;var e,n,i,a,r={"min-width":null,"max-width":null},o=!1;for(i=t.split(/\s+and\s+/),e=0;er["max-width"]||null!==r["min-height"]&&cr["max-height"]?!1:!0},navigator.userAgent.match(/MSIE ([0-9]+)/)&&RegExp.$1<9&&(t.newStyle=function(t){var e=document.createElement("span");return e.innerHTML=' ",e})},initVars:function(){var e,n,i,a=navigator.userAgent;e="other",n=0,i=[["firefox",/Firefox\/([0-9\.]+)/],["bb",/BlackBerry.+Version\/([0-9\.]+)/],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/],["opera",/OPR\/([0-9\.]+)/],["opera",/Opera\/([0-9\.]+)/],["edge",/Edge\/([0-9\.]+)/],["safari",/Version\/([0-9\.]+).+Safari/],["chrome",/Chrome\/([0-9\.]+)/],["ie",/MSIE ([0-9]+)/],["ie",/Trident\/.+rv:([0-9]+)/]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(RegExp.$1),!1):void 0}),t.vars.browser=e,t.vars.browserVersion=n,e="other",n=0,i=[["ios",/([0-9_]+) like Mac OS X/,function(t){return t.replace("_",".").replace("_","")}],["ios",/CPU like Mac OS X/,function(t){return 0}],["wp",/Windows Phone ([0-9\.]+)/,null],["android",/Android ([0-9\.]+)/,null],["mac",/Macintosh.+Mac OS X ([0-9_]+)/,function(t){return t.replace("_",".").replace("_","")}],["windows",/Windows NT ([0-9\.]+)/,null],["bb",/BlackBerry.+Version\/([0-9\.]+)/,null],["bb",/BB[0-9]+.+Version\/([0-9\.]+)/,null]],t.iterate(i,function(t,i){return a.match(i[1])?(e=i[0],n=parseFloat(i[2]?i[2](RegExp.$1):RegExp.$1),!1):void 0}),t.vars.os=e,t.vars.osVersion=n,t.vars.IEVersion="ie"==t.vars.browser?t.vars.browserVersion:99,t.vars.touch="wp"==t.vars.os?navigator.msMaxTouchPoints>0:!!("ontouchstart"in window),t.vars.mobile="wp"==t.vars.os||"android"==t.vars.os||"ios"==t.vars.os||"bb"==t.vars.os}};return t.init(),t}();!function(t,e){"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?module.exports=e():t.skel=e()}(this,function(){return skel}); 3 | -------------------------------------------------------------------------------- /main_application/restcall.js: -------------------------------------------------------------------------------- 1 | // restcall.js - module for handling REST API calls 2 | 3 | // METHOD == GET 4 | exports.get = function(options, secure, callbackFn) { 5 | 6 | var httpcall = require(secure ? 'https' : 'http'); 7 | 8 | var req = httpcall.request(options, function(res) { 9 | res.setEncoding('utf-8'); 10 | var responseString = ''; 11 | 12 | res.on('data', function(data) { 13 | responseString += data; 14 | }); 15 | 16 | res.on('end', function() { 17 | var responseObject = JSON.parse(responseString); 18 | callbackFn(responseObject); 19 | }); 20 | }); 21 | 22 | req.write(""); //method == GET, no data 23 | req.end(); 24 | 25 | }; 26 | 27 | // METHOD == POST 28 | exports.post = function(options, secure, postdata, callbackFn) { 29 | 30 | var httpcall = require(secure ? 'https' : 'http'); 31 | 32 | var req = httpcall.request(options, function(res) { 33 | res.setEncoding('utf-8'); 34 | var responseString = ''; 35 | 36 | res.on('data', function(data) { 37 | responseString += data; 38 | }); 39 | 40 | res.on('end', function() { 41 | var responseObject = JSON.parse(responseString); 42 | callbackFn(responseObject); 43 | }); 44 | }); 45 | 46 | req.setHeader("Content-type", "application/json"); 47 | req.write(JSON.stringify(postdata)); //method == POST, write data 48 | req.end(); 49 | 50 | }; -------------------------------------------------------------------------------- /main_application/routes/index.js: -------------------------------------------------------------------------------- 1 | 2 | // GET initial page 3 | exports.index = function(req, res){ 4 | res.render('index'); 5 | }; 6 | -------------------------------------------------------------------------------- /main_application/routes/tripdata.js: -------------------------------------------------------------------------------- 1 | var tripit = require('../tripit.js'); 2 | 3 | // GET flight data from TripIt (or cached in Cloudant) 4 | exports.getFlights = function(req, res) { 5 | if (req.session.oauth_access_token === "") { 6 | res.redirect("/authorize"); 7 | return; 8 | } 9 | var user = req.session.user; 10 | var authToken = req.session.oauth_access_token; 11 | var authTokenSecret = req.session.oauth_access_token_secret; 12 | tripit.getTrips(user, authToken, authTokenSecret).then(function(data) { 13 | console.log("Returning TripIt trip/flight data for user " + user); 14 | // if we have a development setting to force viewing flights, set 15 | // a variable in the response data stream to mark that 16 | if (process.env.FORCE_FLIGHT_VIEW === "true") { 17 | data.forceFlights = 1; 18 | } 19 | res.send(data); 20 | }).catch(function(err) { 21 | console.log("Error retriving trip data: " + err); 22 | res.send(err); 23 | }); 24 | }; -------------------------------------------------------------------------------- /main_application/tripit.js: -------------------------------------------------------------------------------- 1 | /* Retrieve trip information */ 2 | 3 | var TripItApiClient = require("tripit-node"), 4 | Cloudant = require('cloudant'), 5 | fs = require('fs'); 6 | 7 | var tripItKey = ""; 8 | var tripItSecret = ""; 9 | 10 | if (process.env.DEPLOY === "swarm" || process.env.DEPLOY === "kubernetes") { 11 | tripItKey = global.tripit_api_key; 12 | tripItSecret = global.tripit_api_secret; 13 | } else { 14 | tripItKey = process.env.TRIPIT_API_KEY; 15 | tripItSecret = process.env.TRIPIT_API_SECRET; 16 | } 17 | var client = new TripItApiClient(tripItKey, tripItSecret); 18 | 19 | // cloudant credentials URL 20 | var cURL = ""; 21 | if (process.env.DEVMODE === "true") { 22 | if (process.env.DEPLOY === "swarm") { 23 | cURL = global.cloudant_url; 24 | } else { 25 | cURL = process.env.CLOUDANT_URL; 26 | } 27 | } else if (process.env.DEPLOY === "kubernetes") { 28 | console.log("kubernetes deploy mode is detected") 29 | var binding = JSON.parse(fs.readFileSync('/opt/service-bind/binding', 'utf8')); 30 | cURL = binding.url 31 | } else { 32 | var vcap_services = JSON.parse(process.env.VCAP_SERVICES); 33 | cURL = vcap_services.cloudantNoSQLDB[0].credentials.url; 34 | } 35 | 36 | var cloudant = Cloudant({ url: cURL, plugin: 'promises' }); 37 | 38 | var requestTokenSecrets = {}; 39 | 40 | module.exports = { 41 | authorize: function(callBackURL, isMobile, res) { 42 | var baseURL = "https://www.tripit.com"; 43 | if (isMobile === "true") { 44 | baseURL = "https://m.tripit.com"; 45 | } 46 | client.getRequestToken().then(function(results) { 47 | var token = results[0], 48 | secret = results[1]; 49 | requestTokenSecrets[token] = secret; 50 | res.redirect(baseURL + "/oauth/authorize?oauth_token=" + token + "&oauth_callback=" + callBackURL); 51 | }, function(error) { 52 | res.send(error); 53 | }); 54 | }, 55 | getAccessTokens: function(req) { 56 | var token = req.query.oauth_token, 57 | secret = requestTokenSecrets[token], 58 | verifier = req.query.oauth_verifier; 59 | return client.getAccessToken(token, secret, verifier); 60 | }, 61 | getProfileData: function(accessToken, accessTokenSecret) { 62 | return client.requestResource("/get/profile", "GET", accessToken, accessTokenSecret); 63 | }, 64 | getTrips: function(user, accessToken, accessTokenSecret) { 65 | var tripList = {}; 66 | return getUserTrips(user).then(function(data) { 67 | tripList = data; 68 | }).catch(function(err) { 69 | console.log("[getUserTrips] Cloudant lookup error/empty: " + err); 70 | }).then(function() { 71 | if (isEmpty(tripList)) { 72 | return client.requestResource("/list/trip/traveler/true/exclude_types/weather/include_objects/true", "GET", accessToken, accessTokenSecret); 73 | } else { 74 | // only request and update since the last query of TripIt and then merge 75 | // updates into our cache 76 | var modifiedParam = "modified_since/" + tripList.timestamp; 77 | return client.requestResource("/list/trip/traveler/true/exclude_types/weather/" + modifiedParam + "/include_objects/true", "GET", accessToken, accessTokenSecret); 78 | } 79 | }).then(function(data) { 80 | // process the trip API result data 81 | console.log("Acquired trip data from TripIt for " + user); 82 | return processTripData(user, tripList, JSON.parse(data[0])); 83 | }).catch(function(err) { 84 | console.log("Error retrieving trip data from TripIt: " + err); 85 | }); 86 | } 87 | }; 88 | 89 | function getUserTrips(user) { 90 | // query cloudant to see if we have cached any trips for this username 91 | var tripDB = cloudant.db.use("trips"); 92 | return tripDB.get(user); 93 | } 94 | 95 | function putUserTrips(tripData) { 96 | var tripDB = cloudant.db.use("trips"); 97 | tripDB.insert(tripData, function(err, data) { 98 | if (err) { 99 | console.log("Error on trip DB insert: " + err); 100 | } 101 | }); 102 | } 103 | 104 | // This function is rather ugly. First, one of its jobs is to reduce 105 | // the amount of trip information to just the outer "trip" object metadata 106 | // and flight segments associated with that. Because the TripIt API does 107 | // not join these two objects (Trips and AirObjects), we are doing that 108 | // association/join in our "filtered" JSON representation. Secondly, this function 109 | // handles partial information updates ("modified_since//" in TripIt 110 | // API terms), and then has to re-assemble the filtered/combined JSON representation, 111 | // while ignoring updates if there are no AirObject updates in the partial update. 112 | function processTripData(user, tripJSON, newData) { 113 | if (isEmpty(tripJSON)) { 114 | tripJSON.timestamp = newData.timestamp; 115 | // this is all new data; nothing in the cache, so filter our required information 116 | // and cache it 117 | var trips = []; 118 | var airobjs = []; 119 | // walk the JSON looking for "Trip" and "AirObject" types 120 | for (var key in newData) { 121 | if (key === "Trip") { 122 | var trip = newData.Trip; 123 | if (trip instanceof Array) { 124 | trips = trip; 125 | } else { 126 | trips.push(newData.Trip); 127 | } 128 | } else if (key === "AirObject") { 129 | var airobj = newData.AirObject; 130 | if (airobj instanceof Array) { 131 | airobjs = airobj; 132 | } else { 133 | airobjs.push(newData.AirObject); 134 | } 135 | } 136 | } 137 | // assemble our variant of the JSON for cacheing/use 138 | 139 | var tripMap = new Map(); 140 | for (var i = 0; i < trips.length; i++) { 141 | tripMap.set(trips[i].id, trips[i]); 142 | } 143 | for (var j = 0; j < airobjs.length; j++) { 144 | //find trip ID for this air object/segments and 145 | //add to trip information 146 | var relatedTrip = tripMap.get(airobjs[j].trip_id); 147 | if (relatedTrip === undefined) { 148 | console.log("Can't find trip for air segment!"); 149 | } else { 150 | airArray = relatedTrip.air_segments; 151 | if (airArray === undefined) { 152 | //haven't added any airsegments yet 153 | airArray = [airobjs[j]]; 154 | relatedTrip.air_segments = airArray; 155 | } else { 156 | relatedTrip.air_segments.push(airobjs[j]); 157 | } 158 | } 159 | } 160 | tripJSON.Trips = trips; 161 | tripJSON._id = user; 162 | putUserTrips(tripJSON); 163 | return tripJSON; 164 | } 165 | // first, clear any old trips from the cache (e.g. trips now in the past) 166 | tripJSON = removePastTrips(Date.now(), tripJSON); 167 | // set a new cache timestamp 168 | tripJSON.timestamp = newData.timestamp; 169 | // we have cached data; see if any trips are updated in the newData object from 170 | // the API call and update the cache 171 | var airUpdate = false; 172 | var changedTrips = []; 173 | var changedAirobjs = []; 174 | // walk the JSON looking for "Trip" and "AirObject" types 175 | for (var nkey in newData) { 176 | if (nkey === "Trip") { 177 | var dtrip = newData.Trip; 178 | if (dtrip instanceof Array) { 179 | changedTrips = dtrip; 180 | } else { 181 | changedTrips.push(dtrip); 182 | } 183 | } else if (nkey === "AirObject") { 184 | airUpdate = true; 185 | var dairobj = newData.AirObject; 186 | if (dairobj instanceof Array) { 187 | changedAirobjs = dairobj; 188 | } else { 189 | changedAirobjs.push(dairobj); 190 | } 191 | } 192 | } 193 | if (!airUpdate) { 194 | // no update necessary as no air segment changes happened 195 | // still write the cache as we have an updated "since" 196 | // timestamp that will limit getting back any "ignored" 197 | // updates next time we call the TripIt API. 198 | putUserTrips(tripJSON); 199 | return tripJSON; 200 | } 201 | // only if there are flight changes will we process 202 | // this update as our app only deals with flights 203 | var updateTripMap = new Map(); 204 | for (var i = 0; i < changedTrips.length; i++) { 205 | updateTripMap.set(changedTrips[i].id, changedTrips[i]); 206 | } 207 | var cachedTrips = tripJSON.Trips; 208 | for (var k = 0; k < cachedTrips.length; k++) { 209 | if (updateTripMap.has(cachedTrips[k].id)) { 210 | // if we got an update on this trip, update it 211 | cachedTrips[k] = updateTripMap.get(cachedTrips[k].id); 212 | updateTripMap.delete(cachedTrips[k].id); 213 | } 214 | } 215 | // any elements left in the updated trip map are new 216 | // trips we have never cached: 217 | for (var newTrip of updateTripMap.values()) { 218 | cachedTrips.push(newTrip); 219 | } 220 | // now handle air segment adds/updates 221 | var newTripMap = new Map(); 222 | for (var m = 0; m < cachedTrips.length; m++) { 223 | newTripMap.set(cachedTrips[m].id, cachedTrips[m]); 224 | } 225 | for (var n = 0; n < changedAirobjs.length; n++) { 226 | //find trip ID for this air object/segments and 227 | //add/update trip information 228 | var rTrip = newTripMap.get(changedAirobjs[n].trip_id); 229 | if (rTrip === undefined) { 230 | console.log("Can't find trip for air segment!"); 231 | } else { 232 | airArray = rTrip.air_segments; 233 | if (airArray === undefined) { 234 | //haven't added any airsegments yet 235 | airArray = [changedAirobjs[n]]; 236 | rTrip.air_segments = airArray; 237 | } else { 238 | var found = false; 239 | for (var p = 0; p < rTrip.air_segments.length; p++) { 240 | if (rTrip.air_segments[p].id == changedAirobjs[n].id) { 241 | found = true; 242 | rTrip.air_segments[p] = changedAirobjs[n]; 243 | } 244 | } 245 | if (!found) { 246 | // new segment to add to the trip 247 | rTrip.air_segments.push(changedAirobjs[n]); 248 | } 249 | } 250 | } 251 | } 252 | tripJSON.Trips = cachedTrips; 253 | putUserTrips(tripJSON); 254 | return tripJSON; 255 | } 256 | 257 | function removePastTrips(epochMS, tripJSON) { 258 | var updatedTrips = []; 259 | var cachedTrips = tripJSON.Trips; 260 | for (var k = 0; k < cachedTrips.length; k++) { 261 | var endTrip = Date.parse(cachedTrips[k].end_date); 262 | if (endTrip > epochMS) { 263 | updatedTrips.push(cachedTrips[k]); 264 | } 265 | } 266 | tripJSON.Trips = updatedTrips; 267 | return tripJSON; 268 | } 269 | 270 | function isEmpty(obj) { 271 | if (obj === undefined) { 272 | return true; 273 | } 274 | return Object.keys(obj).length === 0; 275 | } 276 | -------------------------------------------------------------------------------- /main_application/views/conversation.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | FlightAssist | Watson Powered Conversation 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 20 | 21 |
22 |
23 |
24 |
25 |
26 | 31 |
32 |
33 |
34 |
35 |
36 |
37 | 38 |
39 |
40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /main_application/views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | FlightAssist 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 28 | 29 | 30 | 45 | 46 | 47 | 57 | 58 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /main_application/views/trips.ejs: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | FlightAssist | Trip Information 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | <% if (typeof no_data !== "undefined") { %> 21 |
22 | 23 |
24 |
25 |

An error
26 | has occurred!

27 |
28 |

Message: <%= message %>

29 |
30 |
31 | <% } else { %> 32 | 33 | 50 | <% } %> 51 | 52 | 62 | 63 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /main_application/weather.js: -------------------------------------------------------------------------------- 1 | // retrieve and cache weather data 2 | 3 | var Cloudant = require('cloudant'), 4 | fs = require('fs'); 5 | 6 | var restcall = require('./restcall.js'); 7 | var url = require('url'); 8 | 9 | // cloudant & weather co. credentials URL 10 | var cURL = ""; 11 | var weatherURL = ""; 12 | if (process.env.DEVMODE === "true") { 13 | if (process.env.DEPLOY === "swarm") { 14 | cURL = global.cloudant_url; 15 | weatherURL = global.weather_url; 16 | } else { 17 | cURL = process.env.CLOUDANT_URL; 18 | weatherURL = process.env.WEATHER_URL; 19 | } 20 | } else if (process.env.DEPLOY === "kubernetes") { 21 | console.log("kubernetes deploy mode is detected"); 22 | var binding = JSON.parse(fs.readFileSync('/opt/service-bind/binding', 'utf8')); 23 | cURL = binding.url; 24 | if (process.env.USE_WEATHER_SERVERLESS == "true"){ 25 | var weatherbinding = JSON.parse(fs.readFileSync('/opt/service-bind2/binding', 'utf8')); 26 | weatherURL = weatherbinding.url; 27 | } 28 | } else { 29 | var vcap_services = JSON.parse(process.env.VCAP_SERVICES); 30 | cURL = vcap_services.cloudantNoSQLDB[0].credentials.url; 31 | weatherURL = vcap_services.weatherinsights[0].credentials.url; 32 | } 33 | 34 | var cloudant = Cloudant({ url: cURL, plugin: 'promises' }); 35 | 36 | module.exports = { 37 | // requires input in the query string: 38 | // - lat = location latitude 39 | // - lon = location longitude 40 | // - locID = airport code representing this location (for cache key) 41 | getThreeDayForecast: function(req, resp) { 42 | // retrieve forecast from either cache; or 43 | // if cache is "expired", re-query from weather co. API 44 | 45 | // Look up cache.. 46 | getCachedData(req.query.locID).then(function(data) { 47 | var now = Date.now(); 48 | if ((now - data.cachetime) > 10 * 60 * 1000) { 49 | // data older than 10 minutes; don't use cache 50 | console.log("Expiring cached weather data for " + req.query.locID); 51 | data.expired = true; 52 | } 53 | return data; 54 | }).catch(function(err) { 55 | console.log("[getCachedWeatherData] Cloudant lookup error/empty: " + err); 56 | }).then(function(data) { 57 | if (!isEmpty(data) && !data.expired) { 58 | // use cached weather data 59 | console.log("using cached weather data for " + req.query.locID); 60 | resp.send(data); 61 | return; 62 | } 63 | if (process.env.USE_WEATHER_SERVICE !== "true") { 64 | if (process.env.USE_WEATHER_SERVERLESS !== "true") { 65 | // our default mode: as a "monolith" deployment; simply use our external 66 | // API query to retrieve weather company data 67 | return handleViaWeatherAPI(req, resp, data); 68 | } else { 69 | // use an OpenWhisk action to retrieve the weather forecast details 70 | return handleViaWeatherWhiskAction(req, resp, data); 71 | } 72 | } else { 73 | // external weather microservice deployment mode; call 74 | // our microservice using service name ("weather-service") 75 | // or if dev mode, simply look on localhost at the expected port 76 | return handleViaWeatherMicroservice(req, resp, data); 77 | } 78 | }); 79 | } 80 | }; 81 | 82 | // handle a request for weather data via direct call to Weather Co. data API 83 | function handleViaWeatherAPI(req, resp, data) { 84 | var host = ""; 85 | var endpoint = "/api/weather/v1/geocode/" + req.query.lat + "/" + req.query.lon + "/forecast/daily/3day.json"; 86 | var wURLObj = url.parse(weatherURL); 87 | host = wURLObj.host; 88 | var authStr = wURLObj.auth; 89 | 90 | var options = { 91 | host: host, 92 | path: endpoint, 93 | method: "GET", 94 | auth: authStr, 95 | rejectUnauthorized: false 96 | }; 97 | 98 | //send the request to the Weather API 99 | restcall.get(options, true, function(newData) { 100 | // cache this data in cloudant with the current epoch ms 101 | var currentEpochms = Date.now(); 102 | newData.cachetime = currentEpochms; 103 | if (!isEmpty(data)) { 104 | //set the rev ID so cache update works 105 | newData._rev = data._rev; 106 | } 107 | newData._id = req.query.locID; 108 | cacheWeatherData(newData); 109 | // send data as response: 110 | console.log("sending JSON weather response for " + req.query.locID); 111 | resp.send(newData); 112 | }); 113 | } 114 | 115 | function handleViaWeatherMicroservice(req, resp, data) { 116 | var microserviceURL = process.env.MICROSERVICE_URL; 117 | console.log("using external weather microservice: " + process.env.USE_WEATHER_SERVICE); 118 | // overwrite host, endpoint to point to our weather microservice 119 | if (process.env.DEVMODE === "true" && process.env.DEPLOY !== "swarm") { 120 | if(process.env.DEPLOY === "compose"){ 121 | host = "weather-service"; 122 | } else{ 123 | host = "localhost"; 124 | } 125 | } else if(process.env.DEPLOY === "cloudfoundry") { 126 | host = microserviceURL; 127 | } else{ 128 | host = "weather-service"; 129 | } 130 | var endpoint = "/weather/" + req.query.lat + "/" + req.query.lon; 131 | 132 | var options = { 133 | host: host, 134 | port: 5000, 135 | path: endpoint, 136 | method: "GET", 137 | rejectUnauthorized: false 138 | }; 139 | 140 | if (process.env.DEPLOY === "cloudfoundry"){ 141 | options.port = null; 142 | } 143 | //send the request to the Weather API 144 | restcall.get(options, false, function(newData) { 145 | // cache this data in cloudant with the current epoch ms 146 | var currentEpochms = Date.now(); 147 | newData.cachetime = currentEpochms; 148 | if (!isEmpty(data)) { 149 | //set the rev ID so cache update works 150 | newData._rev = data._rev; 151 | } 152 | newData._id = req.query.locID; 153 | cacheWeatherData(newData); 154 | // send data as response: 155 | console.log("sending JSON weather response for " + req.query.locID); 156 | resp.send(newData); 157 | }); 158 | } 159 | 160 | // handle a request for weather data via calling an OpenWhisk action 161 | function handleViaWeatherWhiskAction(req, resp, data) { 162 | console.log("use OpenWhisk action for weather service: " + process.env.USE_WEATHER_SERVERLESS); 163 | 164 | var host = "openwhisk.ng.bluemix.net"; 165 | var endpoint = "/api/v1/namespaces/whisk.system/actions/weather/forecast?blocking=true"; 166 | var options = { 167 | host: host, 168 | path: endpoint, 169 | method: "POST", 170 | auth: process.env.OPENWHISK_AUTH, 171 | rejectUnauthorized: false 172 | }; 173 | 174 | // we need our weather API credentials from the weather URL 175 | var wURLObj = url.parse(weatherURL); 176 | var weatherAuth = wURLObj.auth.split(":"); 177 | var postdata = { 178 | "username": weatherAuth[0], 179 | "password": weatherAuth[1], 180 | "latitude": req.query.lat, 181 | "longitude": req.query.lon, 182 | }; 183 | 184 | //direct call the OpenWhisk action via HTTP 185 | restcall.post(options, true, postdata, function(newData) { 186 | // OpenWhisk HTTP response has the JSON data in "{ response: { result: { ..." 187 | console.log(JSON.stringify(newData)); 188 | var forecastData = newData.response.result; 189 | // cache this data in cloudant with the current epoch ms 190 | var currentEpochms = Date.now(); 191 | forecastData.cachetime = currentEpochms; 192 | if (!isEmpty(data)) { 193 | //set the rev ID so cache update works 194 | forecastData._rev = data._rev; 195 | } 196 | forecastData._id = req.query.locID; 197 | cacheWeatherData(forecastData); 198 | // send data as response: 199 | console.log("sending JSON weather response for " + req.query.locID); 200 | resp.send(forecastData); 201 | }); 202 | } 203 | 204 | function getCachedData(location) { 205 | // query cloudant to see if we have cached any weather for this location 206 | var weatherDB = cloudant.db.use("weather"); 207 | return weatherDB.get(location); 208 | } 209 | 210 | function cacheWeatherData(weatherData) { 211 | var weatherDB = cloudant.db.use("weather"); 212 | weatherDB.insert(weatherData, function(err, data) { 213 | if (err) { 214 | console.log("Error on weather DB insert: " + err); 215 | } 216 | }); 217 | } 218 | 219 | function isEmpty(obj) { 220 | if (obj === undefined) { 221 | return true; 222 | } 223 | return Object.keys(obj).length === 0; 224 | } -------------------------------------------------------------------------------- /ratelimit.yaml: -------------------------------------------------------------------------------- 1 | rules: 2 | - aspects: 3 | - kind: quotas 4 | params: 5 | quotas: 6 | - descriptorName: RequestCount 7 | maxAmount: 50 8 | expiration: 10s 9 | -------------------------------------------------------------------------------- /scripts/bx_login.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ -z $CF_ORG ]; then 4 | CF_ORG="$BLUEMIX_ORG" 5 | fi 6 | if [ -z $CF_SPACE ]; then 7 | CF_SPACE="$BLUEMIX_SPACE" 8 | fi 9 | 10 | 11 | if ([ -z "$BLUEMIX_USER" ] || [ -z "$BLUEMIX_PASSWORD" ] || [ -z "$BLUEMIX_ACCOUNT" ]) && ([ -z "$API_KEY"]); then 12 | echo "Define all required environment variables and re-run the stage." 13 | exit 1 14 | fi 15 | 16 | echo "bx login -a $CF_TARGET_URL" 17 | 18 | if [ -z "$API_KEY"]; then 19 | bx login -a "$CF_TARGET_URL" -u "$BLUEMIX_USER" -p "$BLUEMIX_PASSWORD" -c "$BLUEMIX_ACCOUNT" -o "$CF_ORG" -s "$CF_SPACE" 20 | else 21 | bx login -a "$CF_TARGET_URL" --apikey "$API_KEY" -o "$CF_ORG" -s "$CF_SPACE" 22 | fi 23 | 24 | if [ $? -ne 0 ]; then 25 | echo "Failed to authenticate to Bluemix" 26 | exit 1 27 | fi 28 | 29 | echo "bx cs init" 30 | bx cs init 31 | if [ $? -ne 0 ]; then 32 | echo "Failed to initialize to Bluemix Container Service" 33 | exit 1 34 | fi 35 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Create FlightAssist" 4 | IP_ADDR=$(bx cs workers $CLUSTER_NAME | grep Ready | awk '{ print $2 }') 5 | if [ -z $IP_ADDR ]; then 6 | echo "$CLUSTER_NAME not created or workers not ready" 7 | exit 1 8 | fi 9 | 10 | echo -e "Configuring vars" 11 | exp=$(bx cs cluster-config $CLUSTER_NAME | grep export) 12 | if [ $? -ne 0 ]; then 13 | echo "Cluster $CLUSTER_NAME not created or not ready." 14 | exit 1 15 | fi 16 | eval "$exp" 17 | 18 | echo -e "Deleting previous version of FlightAssist if it exists" 19 | kubectl delete --ignore-not-found=true svc,deployment flightassist-service 20 | kubectl delete --ignore-not-found=true svc,deployment weather-service 21 | kubectl delete --ignore-not-found=true -f secret.yaml 22 | 23 | bx cs cluster-service-bind $CLUSTER_NAME default mycloudant 24 | bx cs cluster-service-bind $CLUSTER_NAME default myweatherinsights 25 | 26 | sed -i s#""#$FLIGHTSTATS_APP_ID# secret.yaml 27 | sed -i s#""#$FLIGHTSTATS_APP_KEY# secret.yaml 28 | sed -i s#""#$TRIPIT_API_KEY# secret.yaml 29 | sed -i s#""#$TRIPIT_API_SECRET# secret.yaml 30 | 31 | kubectl create -f secret.yaml 32 | 33 | if [ -z $OPENWHISK_AUTH ]; then 34 | sed -i s#""#$IP_ADDR:30080# flightassist.yaml 35 | sed -i s#"registry.ng.bluemix.net//flightassist"#docker.io/tomcli/flightassist# flightassist.yaml 36 | sed -i s#"registry.ng.bluemix.net//weather-service"#docker.io/tomcli/weather-service# flightassist.yaml 37 | 38 | kubectl create -f flightassist.yaml 39 | else 40 | sed -i s#""#$IP_ADDR:30080# flightassist_serverless.yaml 41 | sed -i s#"registry.ng.bluemix.net//flightassist"#docker.io/tomcli/flightassist# flightassist_serverless.yaml 42 | sed -i s#""#$OPENWHISK_AUTH# flightassist_serverless.yaml 43 | 44 | kubectl create -f flightassist_serverless.yaml 45 | fi 46 | 47 | echo "" && echo "View your FlightAssist website at http://$IP_ADDR:30080" 48 | 49 | -------------------------------------------------------------------------------- /scripts/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | function install_bluemix_cli() { 4 | #statements 5 | echo "Installing Bluemix cli" 6 | curl -L "https://cli.run.pivotal.io/stable?release=linux64-binary&source=github" | tar -zx 7 | sudo mv cf /usr/local/bin 8 | sudo curl -o /usr/share/bash-completion/completions/cf https://raw.githubusercontent.com/cloudfoundry/cli/master/ci/installers/completion/cf 9 | cf --version 10 | curl -L public.dhe.ibm.com/cloud/bluemix/cli/bluemix-cli/Bluemix_CLI_0.5.1_amd64.tar.gz > Bluemix_CLI.tar.gz 11 | tar -xvf Bluemix_CLI.tar.gz 12 | sudo ./Bluemix_CLI/install_bluemix_cli 13 | } 14 | 15 | function bluemix_auth() { 16 | echo "Authenticating with Bluemix" 17 | echo "1" | bx login -a https://api.ng.bluemix.net -u $BLUEMIX_USER -p $BLUEMIX_PASS 18 | echo "1" | cf login -a https://api.ng.bluemix.net -u $BLUEMIX_USER -p $BLUEMIX_PASS 19 | curl -LO https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl 20 | bx plugin install container-service -r Bluemix 21 | echo "Installing kubectl" 22 | chmod +x ./kubectl 23 | sudo mv ./kubectl /usr/local/bin/kubectl 24 | } 25 | 26 | function cluster_setup() { 27 | #change cluster-travis to cluster name 28 | bx cs workers $CLUSTER 29 | $(bx cs cluster-config $CLUSTER | grep export) 30 | 31 | echo "Deleting old deployment if it exists..." 32 | kubectl delete --ignore-not-found=true svc,deployment flightassist-service 33 | kubectl delete --ignore-not-found=true svc,deployment weather-service 34 | kubectl delete --ignore-not-found=true -f secret.yaml 35 | } 36 | 37 | function application_setup() { 38 | #creating services 39 | 40 | # Trial accounts that are expired cannot use any Bluemix service other than kubernetes cluster. 41 | # bx service create cloudantNoSQLDB Lite mycloudant 42 | # bx service create weatherinsights Free-v2 myweatherinsights 43 | 44 | # bx cs cluster-service-bind $CLUSTER_NAME default mycloudant 45 | # bx cs cluster-service-bind $CLUSTER_NAME default myweatherinsights 46 | 47 | #set dummy cred 48 | sed -i s#""#"1930fe0e"# secret.yaml 49 | sed -i s#""#"2153768d0be39fcb226ee76d28499ded"# secret.yaml 50 | sed -i s#""#"f88710e02094db0b0b2994c0806a53a3f76250d5"# secret.yaml 51 | sed -i s#""#"bca43acbf2d095030f0ea924139acf7e4989ade2"# secret.yaml 52 | kubectl create -f secret.yaml 53 | 54 | echo "Create FlightAssist" 55 | IP_ADDR=$(bx cs workers $CLUSTER | grep Ready | awk '{ print $2 }') 56 | 57 | sed -i s#""#$IP_ADDR:30080# flightassist.yaml 58 | sed -i s#"registry.ng.bluemix.net//flightassist"#docker.io/tomcli/flightassist# flightassist.yaml 59 | sed -i s#"registry.ng.bluemix.net//weather-service"#docker.io/tomcli/weather-service# flightassist.yaml 60 | 61 | kubectl create -f flightassist.yaml 62 | 63 | } 64 | 65 | 66 | 67 | install_bluemix_cli 68 | bluemix_auth 69 | cluster_setup 70 | application_setup 71 | cluster_setup 72 | -------------------------------------------------------------------------------- /scripts/install_bx.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Download Bluemix CLI" 4 | wget --quiet --output-document=/tmp/Bluemix_CLI_amd64.tar.gz http://public.dhe.ibm.com/cloud/bluemix/cli/bluemix-cli/latest/Bluemix_CLI_amd64.tar.gz 5 | tar -xf /tmp/Bluemix_CLI_amd64.tar.gz --directory=/tmp 6 | 7 | # Create bx alias 8 | echo "#!/bin/bash" >/tmp/Bluemix_CLI/bin/bx 9 | echo "/tmp/Bluemix_CLI/bin/bluemix \"\$@\" " >>/tmp/Bluemix_CLI/bin/bx 10 | chmod +x /tmp/Bluemix_CLI/bin/* 11 | 12 | export PATH="/tmp/Bluemix_CLI/bin:$PATH" 13 | 14 | # Install Bluemix CS plugin 15 | echo "Install the Bluemix container-service plugin" 16 | bx plugin install container-service -r Bluemix 17 | 18 | echo "Install kubectl" 19 | wget --quiet --output-document=/tmp/Bluemix_CLI/bin/kubectl https://storage.googleapis.com/kubernetes-release/release/$(curl -s https://storage.googleapis.com/kubernetes-release/release/stable.txt)/bin/linux/amd64/kubectl 20 | chmod +x /tmp/Bluemix_CLI/bin/kubectl 21 | 22 | if [ -n "$DEBUG" ]; then 23 | bx --version 24 | bx plugin list 25 | fi 26 | 27 | -------------------------------------------------------------------------------- /secret.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Secret 3 | metadata: 4 | name: mysecret 5 | type: Opaque 6 | data: 7 | flightstats-app-id: 8 | flightstats-app-key: 9 | tripit-api-key: 10 | tripit-api-secret: 11 | --------------------------------------------------------------------------------