├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE.txt ├── NOTICE.txt ├── README.md ├── deployment ├── build-s3-dist.sh ├── custom-deployment │ ├── bin │ │ └── smart-product-solution.ts │ ├── cdk-manifest.json │ ├── cdk.json │ ├── lib │ │ ├── name-generator.ts │ │ ├── smart-product-api.ts │ │ ├── smart-product-device-defender.ts │ │ ├── smart-product-event.ts │ │ ├── smart-product-jitr.ts │ │ ├── smart-product-owner-web-app.ts │ │ ├── smart-product-solution-stack.ts │ │ └── smart-product-telemetry.ts │ ├── package.json │ └── tsconfig.json ├── manifest-generator │ ├── app.js │ └── package.json ├── run-unit-tests.sh └── smart-product-solution.template └── source ├── console ├── jsconfig.json ├── package.json ├── public │ ├── apple-icon.png │ ├── assets │ │ └── smart_product_config.js │ ├── favicon.ico │ ├── index.html │ └── manifest.json └── src │ ├── assets │ ├── css │ │ ├── animate.min.css │ │ ├── demo.css │ │ ├── light-bootstrap-dashboard-react.css │ │ ├── light-bootstrap-dashboard-react.css.map │ │ ├── light-bootstrap-dashboard-react.min.css │ │ └── pe-icon-7-stroke.css │ ├── fonts │ │ ├── Pe-icon-7-stroke.eot │ │ ├── Pe-icon-7-stroke.svg │ │ ├── Pe-icon-7-stroke.ttf │ │ ├── Pe-icon-7-stroke.woff │ │ ├── glyphicons-halflings-regular.eot │ │ ├── glyphicons-halflings-regular.svg │ │ ├── glyphicons-halflings-regular.ttf │ │ ├── glyphicons-halflings-regular.woff │ │ └── glyphicons-halflings-regular.woff2 │ ├── img │ │ └── logo-aws.png │ └── sass │ │ ├── lbd │ │ ├── _alerts.scss │ │ ├── _buttons.scss │ │ ├── _cards.scss │ │ ├── _chartist.scss │ │ ├── _checkbox-radio-switch.scss │ │ ├── _custom.scss │ │ ├── _dropdown.scss │ │ ├── _footers.scss │ │ ├── _inputs.scss │ │ ├── _misc.scss │ │ ├── _mixins.scss │ │ ├── _navbars.scss │ │ ├── _responsive.scss │ │ ├── _sidebar-and-main-panel.scss │ │ ├── _tables.scss │ │ ├── _typography.scss │ │ ├── _variables.scss │ │ └── mixins │ │ │ ├── _buttons.scss │ │ │ ├── _cards.scss │ │ │ ├── _chartist.scss │ │ │ ├── _icons.scss │ │ │ ├── _inputs.scss │ │ │ ├── _labels.scss │ │ │ ├── _morphing-buttons.scss │ │ │ ├── _navbars.scss │ │ │ ├── _social-buttons.scss │ │ │ ├── _tabs.scss │ │ │ ├── _transparency.scss │ │ │ └── _vendor-prefixes.scss │ │ └── light-bootstrap-dashboard-react.scss │ ├── components │ ├── Card │ │ └── Card.jsx │ ├── CustomCheckbox │ │ └── CustomCheckbox.jsx │ ├── Footer │ │ └── Footer.jsx │ ├── Navbars │ │ ├── AdminNavbar.jsx │ │ └── AdminNavbarLinks.jsx │ └── Sidebar │ │ └── Sidebar.jsx │ ├── index.js │ ├── layouts │ └── Admin.jsx │ ├── routes.js │ ├── variables │ ├── Variables.jsx │ └── configurations.js │ └── views │ ├── Alerts.jsx │ ├── DeviceDetail.jsx │ ├── DeviceRegistration.jsx │ ├── Devices.jsx │ ├── History.jsx │ └── UserSetting.jsx ├── resources ├── authorizer │ ├── auth.common.js │ ├── package.json │ └── test-setup.spec.js ├── cicd │ ├── index.js │ └── package.json ├── cognito │ ├── index.js │ ├── index.spec.js │ └── package.json ├── helper │ ├── index.js │ ├── lib │ │ ├── dynamodb-helper.js │ │ ├── dynamodb-helper.spec.js │ │ ├── iot-helper.js │ │ ├── iot-helper.spec.js │ │ ├── s3-helper.js │ │ └── test-setup.spec.js │ └── package.json ├── logger │ ├── logger.common.js │ ├── package.json │ └── test-setup.spec.js ├── usage-metrics │ ├── metrics.common.js │ ├── package.json │ └── test-setup.spec.js └── utils │ ├── package.json │ ├── test-setup.spec.js │ └── utils.common.js └── services ├── api ├── admin │ ├── index.js │ ├── lib │ │ ├── admin.js │ │ ├── admin.spec.js │ │ ├── app.js │ │ └── test-setup.spec.js │ └── package.json ├── command │ ├── index.js │ ├── lib │ │ ├── app.js │ │ ├── command.js │ │ └── command.spec.js │ └── package.json ├── device │ ├── index.js │ ├── lib │ │ ├── app.js │ │ ├── device.js │ │ ├── device.spec.js │ │ └── test-setup.spec.js │ └── package.json ├── event │ ├── index.js │ ├── lib │ │ ├── app.js │ │ ├── event.js │ │ ├── event.spec.js │ │ └── test-setup.spec.js │ └── package.json ├── registration │ ├── index.js │ ├── lib │ │ ├── app.js │ │ ├── registration.js │ │ └── registration.spec.js │ └── package.json └── status │ ├── index.js │ ├── lib │ ├── app.js │ ├── status.js │ ├── status.spec.js │ └── test-setup.spec.js │ └── package.json ├── command-status ├── index.js ├── lib │ ├── index.js │ ├── index.spec.js │ ├── message.js │ └── message.spec.js └── package.json ├── event-proxy ├── index.js ├── lib │ ├── index.js │ ├── index.spec.js │ ├── message.js │ ├── message.spec.js │ └── test-setup.spec.js └── package.json ├── jitr ├── index.js ├── lib │ ├── index.js │ ├── jitrHelper.js │ └── jitrHelper.spec.js └── package.json ├── notification ├── index.js ├── lib │ ├── alert.js │ ├── alert.spec.js │ └── index.js └── package.json └── telemetry ├── index.js ├── lib ├── index.js └── index.spec.js └── package.json /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | **/dist 3 | **/open-source 4 | **/.zip 5 | **/tmp 6 | **/out-tsc 7 | **/global-s3-assets 8 | **/regional-s3-assets 9 | 10 | # dependencies 11 | **/node_modules 12 | 13 | # e2e 14 | **/e2e/*.js 15 | **/e2e/*.map 16 | 17 | # misc 18 | **/npm-debug.log 19 | **/testem.log 20 | **/package-lock.json 21 | **/.vscode 22 | **.nyc_output 23 | **.pem 24 | 25 | # System Files 26 | **/.DS_Store 27 | 28 | # Amplify Configuration 29 | **/variables/aws_exports.js 30 | **/build 31 | 32 | # CDK Configuration 33 | **/cdk.out 34 | **/cdk.staging -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [1.0.2] - 2020-01-28 8 | ### Updated 9 | - Fix for CDK breaking change *cloudfront: (experimental module) S3OriginConfig.originAccessIdentityId or type string has been removed in favor of S3OriginConfig.originAccessIdentity of type IOriginAccessIdentity* 10 | - Lock CDK version to 1.22.0 11 | - Update JITR microservice to use ```x509``` library 12 | - Update ```react-scripts``` version to fix vulnerability issue: https://npmjs.com/advisories/1426 13 | 14 | ### Removed 15 | - Remove some unnecessary library 16 | - Delete ```template.yml``` files 17 | 18 | ## [1.0.1] - 2019-12-16 19 | ### Updated 20 | - Update Node.JS version from 8 to 12 21 | - Update CDK dependency version to 1.18.0 22 | - Add adm-zip on CI/CD helper Lambda function to work on Node.JS 12.x platform 23 | - Update custom resource helper: ```then().catch()``` to ```async/await```. 24 | - Remove ```\n``` from README 25 | - Update device microservice unit tests 26 | - Remove ```finally``` statement from all unit tests 27 | - Update console package version to fix vulnerability 28 | - Add try/catch for sending anonymous data 29 | - Add API Gateway access logging configuration 30 | 31 | ## [1.0.0] - 2019-09-12 32 | ### Added 33 | - Smart Product Solution release -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/awslabs/aws-smart-product/issues), or [recently closed](https://github.com/awslabs/aws-smart-product/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/awslabs/aws-smart-product/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/awslabs/aws-smart-product/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /NOTICE.txt: -------------------------------------------------------------------------------- 1 | Smart Product Reference Architecture 2 | Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | 16 | ********************** 17 | THIRD PARTY COMPONENTS 18 | ********************** 19 | This software includes third party software subject to the following copyrights: 20 | 21 | AWS SDK under the Apache License Version 2.0 22 | aws-amplify under the Apache License Version 2.0 23 | aws-amplify-react under the Apache License Version 2.0 24 | aws-serverless-express under the Apache License Version 2.0 25 | express under the Massachusetts Institute of Technology (MIT) license 26 | cors under the Massachusetts Institute of Technology (MIT) license 27 | bootstrap under the Massachusetts Institute of Technology (MIT) license 28 | body-parser under the Massachusetts Institute of Technology (MIT) license 29 | node-sass under the Massachusetts Institute of Technology (MIT) license 30 | react under the Massachusetts Institute of Technology (MIT) license 31 | react-bootstrap under the Massachusetts Institute of Technology (MIT) license 32 | react-dom under the Massachusetts Institute of Technology (MIT) license 33 | react-notification-system under the Massachusetts Institute of Technology (MIT) license 34 | react-router under the Massachusetts Institute of Technology (MIT) license 35 | react-router-dom under the Massachusetts Institute of Technology (MIT) license 36 | react-scripts under the Massachusetts Institute of Technology (MIT) license 37 | react-toggle under the Massachusetts Institute of Technology (MIT) license 38 | underscore.js under the Massachusetts Institute of Technology (MIT) license 39 | moment.js under the Massachusetts Institute of Technology (MIT) license 40 | js-base64 under the Massachusetts Institute of Technology (MIT) license 41 | jsonwebtoken under the Massachusetts Institute of Technology (MIT) license 42 | jwk-to-pem under the Apache License Version 2.0 43 | font-awesome under the Massachusetts Institute of Technology (MIT) license 44 | uuid under the Massachusetts Institute of Technology (MIT) license 45 | request-promise under the ISC License 46 | request under the Apache License Version 2.0 47 | @fidm/x509 under the Massachusetts Institute of Technology (MIT) license 48 | 49 | The licenses for these third party components are included in LICENSE.txt 50 | -------------------------------------------------------------------------------- /deployment/custom-deployment/bin/smart-product-solution.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | import cdk = require('@aws-cdk/core'); 15 | import { SmartProductSolutionStack } from '../lib/smart-product-solution-stack'; 16 | 17 | const app = new cdk.App(); 18 | new SmartProductSolutionStack(app, 'SmartProductSolutionStack'); -------------------------------------------------------------------------------- /deployment/custom-deployment/cdk-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "default": { 3 | "version": "v0.0.1", 4 | "sendAnonymousUsage": true, 5 | "api": { 6 | "deploy": true 7 | }, 8 | "defender": { 9 | "deploy": true 10 | }, 11 | "events": { 12 | "deploy": true, 13 | "env": { 14 | "eventTopic": "smartproduct/event" 15 | } 16 | }, 17 | "jitr": { 18 | "deploy": true 19 | }, 20 | "ownerapp": { 21 | "deploy": true 22 | }, 23 | "telemetry": { 24 | "deploy": true, 25 | "env": { 26 | "telemetryTopic": "smartproduct/telemetry" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /deployment/custom-deployment/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node bin/smart-product-solution.ts" 3 | } 4 | -------------------------------------------------------------------------------- /deployment/custom-deployment/lib/name-generator.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | const uuidv4 = require('uuid/v4'); 15 | 16 | export function generateName(prefix: string, maxlength: number): string { 17 | const nameSufix = `-${uuidv4().split("-").pop()}`; 18 | if (maxlength > nameSufix.length) 19 | return `${prefix}`.substring(0, maxlength - nameSufix.length) + nameSufix 20 | else 21 | return `${nameSufix}`.substring(0, maxlength) 22 | } -------------------------------------------------------------------------------- /deployment/custom-deployment/lib/smart-product-device-defender.ts: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | import cdk = require('@aws-cdk/core'); 15 | import sns = require('@aws-cdk/aws-sns'); 16 | import iam = require('@aws-cdk/aws-iam'); 17 | import cfn = require('@aws-cdk/aws-cloudformation'); 18 | 19 | export interface SmartProductDeviceDefenderProps { 20 | helperFunction: cfn.CustomResourceProvider; 21 | helperFunctionPolicy: iam.Policy; 22 | } 23 | 24 | export class SmartProductDeviceDefender extends cdk.Construct { 25 | public readonly response: string; 26 | 27 | constructor(parent: cdk.Construct, name: string, props: SmartProductDeviceDefenderProps) { 28 | super(parent, name); 29 | 30 | //============================================================================================= 31 | // Resources 32 | //============================================================================================= 33 | const deviceDefenderSNS = new sns.Topic(this, 'SNS', { 34 | displayName: "SmartProductDeviceDefenderSNS", 35 | topicName: "SmartProductDeviceDefenderSNS" 36 | }) 37 | 38 | const auditNotifyRole = new iam.Role(this, 'AuditNotifyRole', { 39 | assumedBy: new iam.ServicePrincipal('iot.amazonaws.com') 40 | }) 41 | 42 | const auditNotifyPolicy = new iam.Policy(this, 'AuditNotifyPolicy', { 43 | statements: [new iam.PolicyStatement({ 44 | actions: [ 45 | 'iot:GetLoggingOptions', 46 | 'iot:GetV2LoggingOptions', 47 | 'iot:ListCACertificates', 48 | 'iot:ListCertificates', 49 | 'iot:DescribeCACertificate', 50 | 'iot:DescribeCertificate', 51 | 'iot:ListPolicies', 52 | 'iot:GetPolicy', 53 | 'iot:GetEffectivePolicies', 54 | 'cognito-identity:GetIdentityPoolRoles', 55 | 'iam:ListRolePolicies', 56 | 'iam:ListAttachedRolePolicies', 57 | 'iam:GetPolicy', 58 | 'iam:GetPolicyVersion', 59 | 'iam:GetRolePolicy' 60 | ], 61 | resources: [`*`] 62 | }), 63 | new iam.PolicyStatement({ 64 | actions: ['sns:Publish'], 65 | resources: [deviceDefenderSNS.topicArn] 66 | })] 67 | }) 68 | const auditNotifyPolicyResource = auditNotifyPolicy.node.findChild('Resource') as iam.CfnPolicy; 69 | auditNotifyPolicyResource.cfnOptions.metadata = { 70 | cfn_nag: { 71 | rules_to_suppress: [{ 72 | id: 'W12', 73 | reason: `The * resource allows ${auditNotifyRole.roleName} to audit IoT devices.` 74 | }] 75 | } 76 | } 77 | auditNotifyPolicy.attachToRole(auditNotifyRole); 78 | 79 | const _updateDefender = new cfn.CustomResource(this, 'UpdateIoTDeviceDefender', { 80 | provider: props.helperFunction, 81 | resourceType: 'Custom::UpdateIoTDeviceDefender', 82 | properties: { 83 | Region: `${cdk.Aws.REGION}`, 84 | CustomAction: 'updateIoTDeviceDefender', 85 | SnsRoleArn: auditNotifyRole.roleArn, 86 | SnsTargetArn: deviceDefenderSNS.topicArn, 87 | AuditRoleArn: auditNotifyRole.roleArn 88 | } 89 | }) 90 | _updateDefender.node.addDependency(auditNotifyPolicy.node.findChild('Resource') as cdk.Resource) 91 | _updateDefender.node.addDependency(props.helperFunctionPolicy.node.findChild('Resource') as cdk.Resource) 92 | 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /deployment/custom-deployment/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-solution", 3 | "version": "1.0.0", 4 | "bin": { 5 | "smart-product-solution": "bin/smart-product-solution.ts" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "cdk": "cdk" 11 | }, 12 | "devDependencies": { 13 | "@types/node": "8.10.45", 14 | "typescript": "^3.3.3333", 15 | "ts-node": "^8.1.0" 16 | }, 17 | "dependencies": { 18 | "@aws-cdk/aws-apigateway": "1.22.0", 19 | "@aws-cdk/aws-cloudfront": "1.22.0", 20 | "@aws-cdk/aws-cognito": "1.22.0", 21 | "@aws-cdk/aws-dynamodb": "1.22.0", 22 | "@aws-cdk/aws-iam": "1.22.0", 23 | "@aws-cdk/aws-iot": "1.22.0", 24 | "@aws-cdk/aws-iotanalytics": "1.22.0", 25 | "@aws-cdk/aws-lambda": "1.22.0", 26 | "@aws-cdk/aws-s3": "1.22.0", 27 | "@aws-cdk/aws-sns": "1.22.0", 28 | "aws-cdk": "1.22.0", 29 | "uuid": "^3.3.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /deployment/custom-deployment/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2016", 7 | "es2017.object", 8 | "es2017.string" 9 | ], 10 | "declaration": true, 11 | "strict": true, 12 | "noImplicitAny": true, 13 | "strictNullChecks": true, 14 | "noImplicitThis": true, 15 | "alwaysStrict": true, 16 | "noUnusedLocals": false, 17 | "noUnusedParameters": false, 18 | "noImplicitReturns": true, 19 | "noFallthroughCasesInSwitch": false, 20 | "inlineSourceMap": true, 21 | "inlineSources": true, 22 | "experimentalDecorators": true, 23 | "strictPropertyInitialization": false, 24 | "resolveJsonModule": true, 25 | "esModuleInterop": true, 26 | } 27 | } -------------------------------------------------------------------------------- /deployment/manifest-generator/app.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const fs = require('fs'); 21 | const path = require('path'); 22 | const args = require('minimist')(process.argv.slice(2)); 23 | 24 | let getFileList = function(path) { 25 | let fileInfo; 26 | let filesFound; 27 | let fileList = []; 28 | 29 | filesFound = fs.readdirSync(path); 30 | for (let i = 0; i < filesFound.length; i++) { 31 | fileInfo = fs.lstatSync([path, filesFound[i]].join('/')); 32 | if (fileInfo.isFile()) { 33 | fileList.push(filesFound[i]); 34 | } 35 | 36 | if (fileInfo.isDirectory()) { 37 | console.log([path, filesFound[i]].join('/')); 38 | } 39 | } 40 | 41 | return fileList; 42 | }; 43 | 44 | // List all files in a directory in Node.js recursively in a synchronous fashion 45 | let walkSync = function(dir, filelist) { 46 | // let filelist = []; //getFileList('./temp/site'); 47 | let files = fs.readdirSync(dir); 48 | filelist = filelist || []; 49 | files.forEach(function(file) { 50 | if (fs.statSync(path.join(dir, file)).isDirectory()) { 51 | filelist = walkSync(path.join(dir, file), filelist); 52 | } else { 53 | filelist.push(path.join(dir, file)); 54 | } 55 | }); 56 | 57 | return filelist; 58 | }; 59 | 60 | let _filelist = []; 61 | let _manifest = { 62 | files: [], 63 | }; 64 | 65 | if (!args.hasOwnProperty('target')) { 66 | console.log( 67 | '--target parameter missing. This should be the target directory containing content for the manifest.' 68 | ); 69 | process.exit(1); 70 | } 71 | 72 | if (!args.hasOwnProperty('output')) { 73 | console.log( 74 | '--ouput parameter missing. This should be the out directory where the manifest file will be generated.' 75 | ); 76 | process.exit(1); 77 | } 78 | 79 | console.log( 80 | `Generating a manifest file ${args.output} for directory ${args.target}` 81 | ); 82 | 83 | walkSync(args.target, _filelist); 84 | 85 | for (let i = 0; i < _filelist.length; i++) { 86 | _manifest.files.push(_filelist[i].replace(`${args.target}/`, '')); 87 | } 88 | 89 | fs.writeFileSync(args.output, JSON.stringify(_manifest, null, 4)); 90 | console.log(`Manifest file ${args.output} generated.`); 91 | -------------------------------------------------------------------------------- /deployment/manifest-generator/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "manifest-generator", 3 | "version": "0.0.0", 4 | "private": true, 5 | "description": "Helper utility to create smart product site manifest for deployment", 6 | "main": "app.js", 7 | "author": { 8 | "name": "aws-solutions-builder" 9 | }, 10 | "dependencies": { 11 | "minimist": "*" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /source/console/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src", 4 | "paths": { 5 | "*": ["src/*"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /source/console/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "aws-smart-product", 3 | "version": "1.0.1", 4 | "private": true, 5 | "dependencies": { 6 | "aws-amplify": "^1.2.4", 7 | "aws-amplify-react": "^2.5.4", 8 | "bootstrap": "^3.4.1", 9 | "node-sass": "^4.13.0", 10 | "react": "^16.11.0", 11 | "react-bootstrap": "^0.32.4", 12 | "react-dom": "^16.11.0", 13 | "react-notification-system": "^0.2.17", 14 | "react-router": "^5.1.2", 15 | "react-router-dom": "^5.1.2", 16 | "react-scripts": "^3.3.0", 17 | "react-toggle": "^4.1.1" 18 | }, 19 | "devDependencies": { 20 | "@types/react": "^16.9.11", 21 | "typescript": "3.4.3" 22 | }, 23 | "scripts": { 24 | "start": "react-scripts start", 25 | "build": "react-scripts build", 26 | "test": "react-scripts test --env=jsdom", 27 | "eject": "react-scripts eject", 28 | "install:clean": "rm -rf node_modules/ && rm -rf package-lock.json && npm install && npm start", 29 | "compile-sass": "node-sass src/assets/sass/light-bootstrap-dashboard-react.scss src/assets/css/light-bootstrap-dashboard-react.css", 30 | "minify-sass": "node-sass src/assets/sass/light-bootstrap-dashboard-react.scss src/assets/css/light-bootstrap-dashboard-react.min.css --output-style compressed", 31 | "map-sass": "node-sass src/assets/sass/light-bootstrap-dashboard-react.scss src/assets/css/light-bootstrap-dashboard-react.css --source-map true" 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /source/console/public/apple-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/public/apple-icon.png -------------------------------------------------------------------------------- /source/console/public/assets/smart_product_config.js: -------------------------------------------------------------------------------- 1 | const smart_product_config = { 2 | "aws_project_region": "YOUR_REGION", 3 | "aws_user_pools_id": "YOUR_COGNITO_USER_POOL_ID", 4 | "aws_user_pools_web_client_id": "YOUR_COGNITO_WEB_CLIENT_ID", 5 | "oauth": {}, 6 | "aws_cloud_logic_custom": [ 7 | { 8 | "name": "smart-product-api", 9 | "endpoint": "YOUR_ENDPOINT", 10 | "region": "YOUR_REGION" 11 | } 12 | ], 13 | }; 14 | -------------------------------------------------------------------------------- /source/console/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/public/favicon.ico -------------------------------------------------------------------------------- /source/console/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 25 | AWS Smart Product 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 |
35 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /source/console/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "LBDR", 3 | "name": "AWS Smart Product", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | } 10 | ], 11 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /source/console/src/assets/fonts/Pe-icon-7-stroke.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/Pe-icon-7-stroke.eot -------------------------------------------------------------------------------- /source/console/src/assets/fonts/Pe-icon-7-stroke.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/Pe-icon-7-stroke.ttf -------------------------------------------------------------------------------- /source/console/src/assets/fonts/Pe-icon-7-stroke.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/Pe-icon-7-stroke.woff -------------------------------------------------------------------------------- /source/console/src/assets/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /source/console/src/assets/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /source/console/src/assets/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /source/console/src/assets/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /source/console/src/assets/img/logo-aws.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-solutions/smart-product-solution/827fb190390477037409cb5635d3facc0c4ffe6a/source/console/src/assets/img/logo-aws.png -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_alerts.scss: -------------------------------------------------------------------------------- 1 | .alert{ 2 | border: 0; 3 | border-radius: 0; 4 | color: #FFFFFF; 5 | padding: 10px 15px; 6 | font-size: 14px; 7 | 8 | .container &{ 9 | border-radius: 4px; 10 | 11 | } 12 | .navbar &{ 13 | border-radius: 0; 14 | left: 0; 15 | position: absolute; 16 | right: 0; 17 | top: 85px; 18 | width: 100%; 19 | z-index: 3; 20 | } 21 | .navbar:not(.navbar-transparent) &{ 22 | top: 70px; 23 | } 24 | 25 | span[data-notify="icon"]{ 26 | font-size: 30px; 27 | display: block; 28 | left: 15px; 29 | position: absolute; 30 | top: 50%; 31 | margin-top: -15px; 32 | } 33 | 34 | button.close{ 35 | position: absolute; 36 | right: 10px; 37 | top: 50%; 38 | margin-top: -13px; 39 | z-index: 1033; 40 | background-color: #FFFFFF; 41 | display: block; 42 | border-radius: 50%; 43 | opacity: .4; 44 | line-height: 20px; 45 | font-size: 12px; 46 | width: 25px; 47 | height: 25px; 48 | outline: 0 !important; 49 | text-align: center; 50 | padding: 3px; 51 | font-weight: 300; 52 | 53 | &:hover{ 54 | opacity: .55; 55 | } 56 | } 57 | 58 | .close ~ span{ 59 | display: block; 60 | max-width: 89%; 61 | } 62 | 63 | &[data-notify="container"]{ 64 | padding: 10px 10px 10px 20px; 65 | border-radius: $border-radius-base; 66 | } 67 | 68 | &.alert-with-icon{ 69 | padding-left: 65px; 70 | } 71 | a, a:hover, a:focus{ 72 | text-decoration: underline; 73 | color: white; 74 | } 75 | } 76 | .alert-info{ 77 | background-color: $azure-navbar; 78 | } 79 | .alert-success { 80 | background-color: $green-navbar; 81 | } 82 | .alert-warning { 83 | background-color: $orange-navbar; 84 | } 85 | .alert-danger { 86 | background-color: $red-navbar; 87 | } 88 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_buttons.scss: -------------------------------------------------------------------------------- 1 | .btn{ 2 | border-width: $border-thick; 3 | background-color: $transparent-bg; 4 | font-weight: $font-weight-normal; 5 | border-radius: $border-radius-default; 6 | 7 | @include opacity(.8); 8 | padding: $padding-base-vertical $padding-base-horizontal; 9 | 10 | @include btn-styles($default-color, $default-states-color); 11 | 12 | &:hover, 13 | &:focus{ 14 | @include opacity(1); 15 | outline: 0 !important; 16 | } 17 | &:active, 18 | &.active, 19 | .open > &.dropdown-toggle { 20 | @include box-shadow(none); 21 | outline: 0 !important; 22 | } 23 | 24 | &.btn-icon{ 25 | padding: $padding-base-vertical; 26 | } 27 | 28 | } 29 | 30 | // Apply the mixin to the buttons 31 | .btn-default { @include btn-styles($default-color, $default-states-color); } 32 | .btn-primary { @include btn-styles($primary-color, $primary-states-color); } 33 | .btn-success { @include btn-styles($success-color, $success-states-color); } 34 | .btn-info { @include btn-styles($info-color, $info-states-color); } 35 | .btn-warning { @include btn-styles($warning-color, $warning-states-color); } 36 | .btn-danger { @include btn-styles($danger-color, $danger-states-color); } 37 | .btn-neutral { 38 | @include btn-styles($white-color, $white-color); 39 | 40 | &:active, 41 | &.active, 42 | .open > &.dropdown-toggle{ 43 | background-color: $white-color; 44 | color: $default-color; 45 | } 46 | 47 | &.btn-fill, 48 | &.btn-fill:hover, 49 | &.btn-fill:focus{ 50 | color: $default-color; 51 | } 52 | 53 | &.btn-simple:active, 54 | &.btn-simple.active{ 55 | background-color: transparent; 56 | } 57 | } 58 | 59 | .btn{ 60 | &:disabled, 61 | &[disabled], 62 | &.disabled{ 63 | @include opacity(.5); 64 | } 65 | } 66 | .btn-round{ 67 | border-width: $border-thin; 68 | border-radius: $btn-round-radius !important; 69 | padding: $padding-round-vertical $padding-round-horizontal; 70 | 71 | &.btn-icon{ 72 | padding: $padding-round-vertical; 73 | } 74 | } 75 | .btn-simple{ 76 | border: $none; 77 | font-size: $font-size-medium; 78 | padding: $padding-base-vertical $padding-base-horizontal; 79 | 80 | &.btn-icon{ 81 | padding: $padding-base-vertical; 82 | } 83 | } 84 | .btn-lg{ 85 | @include btn-size($padding-large-vertical, $padding-large-horizontal, $font-size-large, $border-radius-large); 86 | font-weight: $font-weight-normal; 87 | } 88 | .btn-sm{ 89 | @include btn-size($padding-small-vertical, $padding-small-horizontal, $font-size-small, $border-radius-small); 90 | } 91 | .btn-xs { 92 | @include btn-size($padding-xs-vertical, $padding-xs-horizontal, $font-size-small, $border-radius-small); 93 | } 94 | .btn-wd { 95 | min-width: 140px; 96 | } 97 | 98 | .btn-group.select{ 99 | width: 100%; 100 | } 101 | .btn-group.select .btn{ 102 | text-align: left; 103 | } 104 | .btn-group.select .caret{ 105 | position: absolute; 106 | top: 50%; 107 | margin-top: -1px; 108 | right: 8px; 109 | } 110 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_cards.scss: -------------------------------------------------------------------------------- 1 | .card{ 2 | border-radius: $border-radius-base; 3 | box-shadow: 0 1px 2px rgba(0,0,0,.05),0 0 0 1px rgba(63,63,68,.1); 4 | background-color: #FFFFFF; 5 | margin-bottom: 30px; 6 | 7 | .image{ 8 | width: 100%; 9 | overflow: hidden; 10 | height: 260px; 11 | border-radius: $border-radius-base $border-radius-base 0 0; 12 | position: relative; 13 | -webkit-transform-style: preserve-3d; 14 | -moz-transform-style: preserve-3d; 15 | transform-style: preserve-3d; 16 | 17 | img { 18 | width: 100%; 19 | } 20 | } 21 | .filter{ 22 | position: absolute; 23 | z-index: 2; 24 | background-color: rgba(0,0,0,.68); 25 | top: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | text-align: center; 30 | 31 | @include opacity(0); 32 | 33 | .btn{ 34 | @include vertical-align(); 35 | } 36 | } 37 | &:hover .filter{ 38 | @include opacity(1); 39 | } 40 | .btn-hover{ 41 | @include opacity(0); 42 | } 43 | &:hover .btn-hover{ 44 | @include opacity(1); 45 | } 46 | .content{ 47 | padding: 15px 15px 10px 15px; 48 | } 49 | .header{ 50 | padding: 15px 15px 0; 51 | } 52 | .category, 53 | label{ 54 | font-size: $font-size-base; 55 | font-weight: $font-weight-normal; 56 | color: $dark-gray; 57 | margin-bottom: 0px; 58 | 59 | i{ 60 | font-size: $font-paragraph; 61 | } 62 | } 63 | 64 | label{ 65 | font-size: $font-size-small; 66 | margin-bottom: 5px; 67 | text-transform: uppercase; 68 | } 69 | 70 | .title{ 71 | margin: $none; 72 | color: $black-color; 73 | font-weight: $font-weight-light; 74 | } 75 | .avatar{ 76 | width: 30px; 77 | height: 30px; 78 | overflow: hidden; 79 | border-radius: 50%; 80 | margin-right: 5px; 81 | } 82 | .description{ 83 | font-size: $font-size-base; 84 | color: #333; 85 | } 86 | .footer{ 87 | padding: 0; 88 | background-color: $transparent-bg; 89 | line-height: 30px; 90 | 91 | .legend{ 92 | padding: 5px 0; 93 | } 94 | 95 | hr{ 96 | margin-top: 5px; 97 | margin-bottom: 5px; 98 | } 99 | } 100 | .stats{ 101 | color: #a9a9a9; 102 | } 103 | .footer div{ 104 | display: inline-block; 105 | } 106 | 107 | .author{ 108 | font-size: $font-size-small; 109 | font-weight: $font-weight-bold; 110 | text-transform: uppercase; 111 | } 112 | .author i{ 113 | font-size: $font-size-base; 114 | } 115 | h6{ 116 | font-size: $font-size-small; 117 | margin: 0; 118 | } 119 | &.card-separator:after{ 120 | height: 100%; 121 | right: -15px; 122 | top: 0; 123 | width: 1px; 124 | background-color: $medium-gray; 125 | content: ""; 126 | position: absolute; 127 | } 128 | 129 | .ct-chart{ 130 | margin: 30px 0 30px; 131 | height: 245px; 132 | } 133 | 134 | .table{ 135 | tbody td:first-child, 136 | thead th:first-child{ 137 | padding-left: 15px; 138 | } 139 | 140 | tbody td:last-child, 141 | thead th:last-child{ 142 | padding-right: 15px; 143 | } 144 | } 145 | 146 | .alert{ 147 | border-radius: $border-radius-base; 148 | position: relative; 149 | 150 | &.alert-with-icon{ 151 | padding-left: 65px; 152 | } 153 | } 154 | } 155 | .card-user{ 156 | .image{ 157 | height: 110px; 158 | } 159 | .image-plain{ 160 | height: 0; 161 | margin-top: 110px; 162 | } 163 | .author{ 164 | text-align: center; 165 | text-transform: none; 166 | margin-top: -70px; 167 | } 168 | .avatar{ 169 | width: 124px; 170 | height: 124px; 171 | border: 5px solid #FFFFFF; 172 | position: relative; 173 | margin-bottom: 15px; 174 | 175 | &.border-gray{ 176 | border-color: #EEEEEE; 177 | } 178 | } 179 | .title{ 180 | line-height: 24px; 181 | } 182 | .content{ 183 | min-height: 240px; 184 | } 185 | } 186 | 187 | .card-user, 188 | .card-price{ 189 | .footer{ 190 | padding: 5px 15px 10px; 191 | } 192 | hr{ 193 | margin: 5px 15px; 194 | } 195 | } 196 | .card-plain{ 197 | background-color: transparent; 198 | box-shadow: none; 199 | border-radius: 0; 200 | 201 | .image{ 202 | border-radius: 4px; 203 | } 204 | } 205 | 206 | .card-stats{ 207 | .icon-big{ 208 | font-size: 3em; 209 | min-height: 64px; 210 | i{ 211 | font-weight: bold; 212 | line-height: 59px; 213 | } 214 | } 215 | .numbers{ 216 | font-size: 2em; 217 | text-align: right; 218 | p { 219 | margin: 0; 220 | } 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_custom.scss: -------------------------------------------------------------------------------- 1 | .custom_div { 2 | position: relative; 3 | display: block; 4 | width: 100%; 5 | } 6 | 7 | .custom_list_item { 8 | position: relative; 9 | padding: 10px 10px 10px 10px; 10 | border-bottom: 1px solid #e1e4ea; 11 | } 12 | 13 | .custom_mobile_title { 14 | display: block; 15 | overflow: hidden; 16 | } 17 | 18 | .mobile_title { 19 | font-weight: bold; 20 | text-overflow: ellipsis; 21 | white-space: nowrap; 22 | max-width: 100%; 23 | display: block; 24 | overflow: hidden; 25 | } 26 | 27 | .custom_mobile_date { 28 | float: right; 29 | vertical-align: top; 30 | font-size: 10px; 31 | } 32 | 33 | .mobile_date { 34 | display: inline-block; 35 | vertical-align: top; 36 | } 37 | 38 | .mobile_date_bt { 39 | display: inline-block; 40 | vertical-align: bottom; 41 | } 42 | 43 | .custom_date_td { 44 | width: 200px; 45 | } 46 | 47 | .custom_event_type_td { 48 | width: 50px; 49 | } 50 | 51 | .custom_mobile_message { 52 | display: block; 53 | overflow: hidden; 54 | } 55 | 56 | .mobile_message { 57 | text-overflow: ellipsis; 58 | white-space: nowrap; 59 | max-width: 100%; 60 | display: block; 61 | overflow: hidden; 62 | } 63 | 64 | .no_result { 65 | text-align: center; 66 | font-weight: bold; 67 | } 68 | 69 | .custom_devicename_td { 70 | display: block; 71 | width: calc(100% - 130px); 72 | overflow: hidden; 73 | text-overflow: ellipsis; 74 | white-space: nowrap; 75 | } 76 | 77 | .custom_btn { 78 | display: block; 79 | width: calc(100% - 100px); 80 | overflow: hidden; 81 | text-overflow: ellipsis; 82 | white-space: nowrap; 83 | } 84 | 85 | .mobile_tabs { 86 | font-size: 12px; 87 | } 88 | 89 | .custom_registration { 90 | font-size: 12px; 91 | } 92 | 93 | .custom_well { 94 | word-break: break-all; 95 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_dropdown.scss: -------------------------------------------------------------------------------- 1 | .dropdown-menu{ 2 | visibility: hidden; 3 | margin: 0; 4 | padding: 0; 5 | border-radius: $border-radius-extreme; 6 | display: block; 7 | z-index: 9000; 8 | position: absolute; 9 | 10 | @include opacity(0); 11 | @include box-shadow($dropdown-shadow); 12 | 13 | .open &{ 14 | @include opacity(1); 15 | visibility: visible; 16 | } 17 | .select &{ 18 | border-radius: $border-radius-bottom; 19 | @include box-shadow(none); 20 | @include transform-origin($select-coordinates); 21 | @include transform-scale(1); 22 | @include transition($fast-transition-time, $transition-linear); 23 | margin-top: -20px; 24 | } 25 | .select.open &{ 26 | margin-top: -1px; 27 | } 28 | 29 | > li > a { 30 | padding: $padding-base-vertical $padding-base-horizontal; 31 | color: #333333; 32 | 33 | img{ 34 | margin-top: -3px; 35 | } 36 | } 37 | > li > a:focus{ 38 | outline: 0 !important; 39 | } 40 | 41 | .btn-group.select &{ 42 | min-width: 100%; 43 | } 44 | 45 | > li:first-child > a{ 46 | border-top-left-radius: $border-radius-extreme; 47 | border-top-right-radius: $border-radius-extreme; 48 | } 49 | 50 | > li:last-child > a{ 51 | border-bottom-left-radius: $border-radius-extreme; 52 | border-bottom-right-radius: $border-radius-extreme; 53 | } 54 | 55 | .select & > li:first-child > a{ 56 | border-radius: 0; 57 | border-bottom: 0 none; 58 | } 59 | 60 | > li > a:hover, 61 | > li > a:focus { 62 | background-color: $smoke-bg; 63 | color: #333333; 64 | opacity: 1; 65 | text-decoration: none; 66 | } 67 | 68 | &.dropdown-blue > li > a:hover, 69 | &.dropdown-blue > li > a:focus{ 70 | background-color: $light-blue; 71 | } 72 | &.dropdown-azure > li > a:hover, 73 | &.dropdown-azure > li > a:focus{ 74 | background-color: $light-azure; 75 | } 76 | &.ct-green > li > a:hover, 77 | &.ct-green > li > a:focus{ 78 | background-color: $light-green; 79 | } 80 | &.dropdown-orange > li > a:hover, 81 | &.dropdown-orange > li > a:focus{ 82 | background-color: $light-orange; 83 | } 84 | &.dropdown-red > li > a:hover, 85 | &.dropdown-red > li > a:focus{ 86 | background-color: $light-red; 87 | } 88 | 89 | } 90 | 91 | .dropdown-with-icons{ 92 | > li > a{ 93 | padding-left: 0px; 94 | line-height: 28px; 95 | } 96 | i{ 97 | text-align: center; 98 | line-height: 28px; 99 | float: left; 100 | 101 | &[class^="pe-"]{ 102 | font-size: 24px; 103 | width: 46px; 104 | } 105 | &[class^="fa"]{ 106 | font-size: 14px; 107 | width: 38px; 108 | } 109 | } 110 | } 111 | 112 | //fix bug for the select items in btn-group 113 | .btn-group.select{ 114 | overflow: hidden; 115 | } 116 | .btn-group.select.open{ 117 | overflow: visible; 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_footers.scss: -------------------------------------------------------------------------------- 1 | .footer{ 2 | background-color: $white-color; 3 | line-height: $line-height; 4 | 5 | nav > ul{ 6 | list-style: none; 7 | margin: 0; 8 | padding: 0; 9 | font-weight: normal; 10 | 11 | a:not(.btn){ 12 | color: $dark-gray; 13 | display: block; 14 | margin-bottom: 3px; 15 | &:hover, 16 | &:focus{ 17 | color: $default-states-color; 18 | } 19 | } 20 | } 21 | .social-area{ 22 | padding: 15px 0; 23 | h5{ 24 | padding-bottom: 15px; 25 | } 26 | } 27 | .social-area > a:not(.btn){ 28 | color: $dark-gray; 29 | display: inline-block; 30 | vertical-align: top; 31 | padding: $padding-social-a; 32 | font-size: $font-size-large-navbar; 33 | font-weight: normal; 34 | line-height: $line-height; 35 | text-align: center; 36 | &:hover, 37 | &:focus{ 38 | color: $default-states-color; 39 | } 40 | } 41 | .copyright{ 42 | color: $default-states-color; 43 | padding: 10px 15px; 44 | margin: 10px 3px; 45 | line-height: 20px; 46 | font-size: $font-size-base; 47 | } 48 | hr{ 49 | border-color: $medium-gray; 50 | } 51 | .title{ 52 | color: $default-states-color; 53 | } 54 | } 55 | 56 | .footer-default{ 57 | background-color: $smoke-bg; 58 | } 59 | 60 | .footer:not(.footer-big){ 61 | nav > ul{ 62 | font-size: $font-size-base; 63 | li{ 64 | margin-left: 20px; 65 | float: left; 66 | } 67 | a{ 68 | padding: 10px 0px; 69 | margin: 10px 10px 10px 0px; 70 | } 71 | } 72 | } 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_inputs.scss: -------------------------------------------------------------------------------- 1 | .form-control::-moz-placeholder{ 2 | @include placeholder($medium-gray,1); 3 | } 4 | .form-control:-moz-placeholder{ 5 | @include placeholder($medium-gray,1); 6 | } 7 | .form-control::-webkit-input-placeholder{ 8 | @include placeholder($medium-gray,1); 9 | } 10 | .form-control:-ms-input-placeholder{ 11 | @include placeholder($medium-gray,1); 12 | } 13 | 14 | .form-control { 15 | background-color: $white-bg; 16 | border: 1px solid $light-gray; 17 | border-radius: $border-radius-base; 18 | color: #565656; 19 | @include input-size($padding-base-vertical, $padding-base-horizontal - 4, $height-base); 20 | @include box-shadow(none); 21 | 22 | &:focus{ 23 | background-color: $white-bg; 24 | border: 1px solid $medium-dark-gray; 25 | @include box-shadow(none); 26 | outline: 0 !important; 27 | color: #333333; 28 | } 29 | 30 | .has-success &, 31 | .has-error &, 32 | .has-success &:focus, 33 | .has-error &:focus{ 34 | border-color: $light-gray; 35 | @include box-shadow(none); 36 | } 37 | 38 | .has-success &{ 39 | color: $success-color; 40 | } 41 | .has-success &:focus{ 42 | border-color: $success-color; 43 | } 44 | .has-error &{ 45 | color: $danger-color; 46 | } 47 | .has-error &:focus{ 48 | border-color: $danger-color; 49 | } 50 | 51 | & + .form-control-feedback{ 52 | border-radius: $border-radius-large; 53 | font-size: $font-size-base; 54 | margin-top: -7px; 55 | position: absolute; 56 | right: 10px; 57 | top: 50%; 58 | vertical-align: middle; 59 | } 60 | 61 | .open &{ 62 | border-radius: $border-radius-base $border-radius-base 0 0; 63 | border-bottom-color: transparent; 64 | } 65 | } 66 | 67 | .input-lg{ 68 | height: 55px; 69 | padding: $padding-large-vertical $padding-large-horizontal; 70 | } 71 | 72 | .has-error{ 73 | .form-control-feedback{ 74 | color: $danger-color; 75 | } 76 | } 77 | .has-success{ 78 | .form-control-feedback{ 79 | color: $success-color 80 | } 81 | } 82 | 83 | 84 | .input-group-addon { 85 | background-color: $white-color; 86 | border: 1px solid $light-gray; 87 | border-radius: $border-radius-base; 88 | 89 | .has-success &, 90 | .has-error &{ 91 | background-color: $white-color; 92 | border: 1px solid $light-gray; 93 | } 94 | .has-error .form-control:focus + &{ 95 | border-color: $danger-color; 96 | color: $danger-color; 97 | } 98 | .has-success .form-control:focus + &{ 99 | border-color: $success-color; 100 | color: $success-color; 101 | } 102 | .form-control:focus + &, 103 | .form-control:focus ~ &{ 104 | background-color: $white-color; 105 | border-color: $dark-gray; 106 | } 107 | } 108 | 109 | .input-group .form-control:first-child, 110 | .input-group-addon:first-child, 111 | .input-group-btn:first-child > .dropdown-toggle, 112 | .input-group-btn:last-child > .btn:not(:last-child):not(.dropdown-toggle) { 113 | border-right: 0 none; 114 | } 115 | .input-group .form-control:last-child, 116 | .input-group-addon:last-child, 117 | .input-group-btn:last-child > .dropdown-toggle, 118 | .input-group-btn:first-child > .btn:not(:first-child) { 119 | border-left: 0 none; 120 | } 121 | .form-control[disabled], .form-control[readonly], fieldset[disabled] .form-control { 122 | background-color: $smoke-bg; 123 | color: $default-color; 124 | cursor: not-allowed; 125 | } 126 | 127 | .input-group-btn .btn{ 128 | border-width: $border-thin; 129 | padding: $padding-round-vertical $padding-base-horizontal; 130 | } 131 | .input-group-btn .btn-default:not(.btn-fill){ 132 | border-color: $medium-gray; 133 | } 134 | 135 | .input-group-btn:last-child > .btn{ 136 | margin-left: 0; 137 | } 138 | 139 | .input-group-focus .input-group-addon{ 140 | border-color: $dark-gray; 141 | } 142 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_misc.scss: -------------------------------------------------------------------------------- 1 | /* General overwrite */ 2 | body, 3 | .wrapper{ 4 | min-height: 100vh; 5 | position: relative; 6 | background-color: white; 7 | } 8 | a{ 9 | color: $info-color; 10 | 11 | &:hover, &:focus{ 12 | color: $info-states-color; 13 | text-decoration: none; 14 | } 15 | } 16 | 17 | a:focus, a:active, 18 | button::-moz-focus-inner, 19 | input::-moz-focus-inner, 20 | input[type="reset"]::-moz-focus-inner, 21 | input[type="button"]::-moz-focus-inner, 22 | input[type="submit"]::-moz-focus-inner, 23 | select::-moz-focus-inner, 24 | input[type="file"] > input[type="button"]::-moz-focus-inner{ 25 | outline:0; 26 | } 27 | .ui-slider-handle:focus, 28 | .navbar-toggle, 29 | input:focus { 30 | outline : 0 !important; 31 | } 32 | 33 | /* Animations */ 34 | .form-control, 35 | .input-group-addon, 36 | .tagsinput, 37 | .navbar, 38 | .navbar .alert{ 39 | @include transition($general-transition-time, $transition-linear); 40 | } 41 | 42 | .sidebar .nav a, 43 | .table > tbody > tr .td-actions .btn{ 44 | @include transition($fast-transition-time, $transition-ease-in); 45 | } 46 | 47 | .btn{ 48 | @include transition($ultra-fast-transition-time, $transition-ease-in); 49 | } 50 | .fa{ 51 | width: 18px; 52 | text-align: center; 53 | } 54 | .margin-top{ 55 | margin-top: 50px; 56 | } 57 | 58 | .wrapper{ 59 | position: relative; 60 | top: 0; 61 | height: 100vh; 62 | } 63 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_mixins.scss: -------------------------------------------------------------------------------- 1 | //Utilities 2 | 3 | @import "mixins/transparency"; 4 | @import "mixins/vendor-prefixes"; 5 | 6 | 7 | //Components 8 | 9 | @import "mixins/buttons"; 10 | @import "mixins/inputs"; 11 | @import "mixins/labels"; 12 | @import "mixins/tabs"; 13 | 14 | @import "mixins/navbars"; 15 | @import "mixins/icons"; 16 | @import "mixins/social-buttons"; 17 | 18 | @import "mixins/morphing-buttons"; 19 | 20 | @import "mixins/cards"; 21 | 22 | @import "mixins/chartist"; -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_sidebar-and-main-panel.scss: -------------------------------------------------------------------------------- 1 | .sidebar{ 2 | position: fixed; 3 | top: 0; 4 | bottom: 0; 5 | left: 0; 6 | width: 260px; 7 | display: block; 8 | z-index: 1; 9 | color: #fff; 10 | font-weight: 200; 11 | background-size: cover; 12 | background-position: center center; 13 | 14 | .sidebar-wrapper{ 15 | position: relative; 16 | height: calc(100vh - 75px); 17 | overflow: auto; 18 | width: 260px; 19 | z-index: 4; 20 | padding-bottom: 30px; 21 | } 22 | 23 | .logo{ 24 | position: relative; 25 | z-index: 4; 26 | 27 | p{ 28 | float: left; 29 | font-size: 20px; 30 | margin: 10px 10px; 31 | color: $white-color; 32 | line-height: 20px; 33 | font-family: "Amazon Ember", "Helvetica Neue", Helvetica, Arial, sans-serif; 34 | } 35 | 36 | div.logo-mini{ 37 | float: left; 38 | text-align: center; 39 | width: 30px; 40 | margin-right: 15px; 41 | 42 | img{ 43 | width: 40px; 44 | margin-left: 15px; 45 | display: block; 46 | margin-top: 10px; 47 | } 48 | } 49 | 50 | .logo-img{ 51 | width: 34px; 52 | display: inline-block; 53 | height: 34px; 54 | margin-left: -2px; 55 | margin-top: -2px; 56 | margin-right: 10px; 57 | border-radius: 30px; 58 | text-align: center; 59 | } 60 | } 61 | 62 | .nav{ 63 | margin-top: 20px; 64 | 65 | li{ 66 | > a{ 67 | color: #FFFFFF; 68 | margin: 5px 15px; 69 | opacity: .86; 70 | border-radius: 4px; 71 | } 72 | 73 | &:hover > a, 74 | &.open > a, 75 | &.open > a:focus, 76 | &.open > a:hover{ 77 | background: rgba(255,255,255,0.13); 78 | opacity: 1; 79 | } 80 | 81 | &.active > a{ 82 | color: #FFFFFF; 83 | opacity: 1; 84 | background: rgba(255,255,255,0.23); 85 | 86 | } 87 | } 88 | 89 | p{ 90 | margin: 0; 91 | line-height: 30px; 92 | font-size: 12px; 93 | font-weight: 600; 94 | text-transform: uppercase; 95 | } 96 | 97 | .caret{ 98 | top: 24px; 99 | position: absolute; 100 | right: 15px; 101 | } 102 | 103 | i{ 104 | font-size: 28px; 105 | float: left; 106 | margin-right: 15px; 107 | line-height: 30px; 108 | width: 30px; 109 | text-align: center; 110 | } 111 | } 112 | } 113 | 114 | 115 | .sidebar, 116 | body > .navbar-collapse{ 117 | .logo{ 118 | padding: 10px 30px; 119 | border-bottom: 1px solid rgba(255, 255, 255, 0.2); 120 | 121 | p{ 122 | float: left; 123 | font-size: 20px; 124 | margin: 10px 10px; 125 | color: $white-color; 126 | line-height: 20px; 127 | font-family: "Amazon Ember", "Helvetica Neue", Helvetica, Arial, sans-serif; 128 | } 129 | 130 | .simple-text{ 131 | padding: $padding-small-vertical 15px; 132 | text-align: right; 133 | display: block; 134 | font-size: $font-size-large; 135 | color: $white-color; 136 | font-weight: $font-weight-normal; 137 | line-height: 30px; 138 | } 139 | } 140 | 141 | &:after, 142 | &:before{ 143 | display: block; 144 | content: ""; 145 | position: absolute; 146 | width: 100%; 147 | height: 100%; 148 | top: 0; 149 | left: 0; 150 | z-index: 2; 151 | } 152 | 153 | &:before{ 154 | opacity: .33; 155 | background: #000000; 156 | } 157 | 158 | &:after{ 159 | @include icon-gradient($black-color-top, $black-color-bottom); 160 | z-index: 3; 161 | opacity: 1; 162 | } 163 | } 164 | 165 | 166 | .main-panel{ 167 | background: rgba(203,203,210,.15); 168 | position: relative; 169 | float: right; 170 | width: $sidebar-width; 171 | 172 | > .content{ 173 | padding: 30px 15px; 174 | min-height: calc(100% - 123px); 175 | } 176 | 177 | > .footer{ 178 | border-top: 1px solid #e7e7e7; 179 | } 180 | 181 | .navbar{ 182 | margin-bottom: 0; 183 | } 184 | } 185 | 186 | .sidebar, 187 | .main-panel{ 188 | -webkit-transition-property: top,bottom; 189 | transition-property: top,bottom; 190 | -webkit-transition-duration: .2s,.2s; 191 | transition-duration: .2s,.2s; 192 | -webkit-transition-timing-function: linear,linear; 193 | transition-timing-function: linear,linear; 194 | -webkit-overflow-scrolling: touch; 195 | } 196 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_tables.scss: -------------------------------------------------------------------------------- 1 | .table{ 2 | 3 | .radio, 4 | .checkbox{ 5 | position: relative; 6 | height: 20px; 7 | display: block; 8 | width: 20px; 9 | padding: 0px 0px; 10 | margin: 0px 5px; 11 | text-align: center; 12 | 13 | .icons{ 14 | left: 5px; 15 | } 16 | } 17 | > thead > tr > th, 18 | > tbody > tr > th, 19 | > tfoot > tr > th, 20 | > thead > tr > td, 21 | > tbody > tr > td, 22 | > tfoot > tr > td{ 23 | padding: 12px 8px; 24 | vertical-align: middle; 25 | } 26 | // MANU : aici este ceva schimbat!!! 27 | > tbody > tr > td{ 28 | font-size: 14px; 29 | } 30 | > thead > tr > th{ 31 | border-bottom-width: 1px; 32 | font-size: $font-size-small; 33 | text-transform: uppercase; 34 | color: $dark-gray; 35 | font-weight: $font-weight-normal; 36 | padding-bottom: 5px; 37 | } 38 | 39 | .td-actions .btn{ 40 | @include opacity(0.36); 41 | 42 | &.btn-xs{ 43 | padding-left: 3px; 44 | padding-right: 3px; 45 | } 46 | } 47 | .td-actions{ 48 | min-width: 90px; 49 | } 50 | 51 | > tbody > tr{ 52 | position: relative; 53 | 54 | &:hover{ 55 | .td-actions .btn{ 56 | @include opacity(1); 57 | } 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/_typography.scss: -------------------------------------------------------------------------------- 1 | /* Font Smoothing */ 2 | body, 3 | h1, .h1, 4 | h2, .h2, 5 | h3, .h3, 6 | h4, .h4, 7 | h5, .h5, 8 | h6, .h6, 9 | p, 10 | .navbar, 11 | .brand, 12 | .btn-simple, 13 | .alert, 14 | a, 15 | .td-name, 16 | td, 17 | button.close{ 18 | -moz-osx-font-smoothing: grayscale; 19 | -webkit-font-smoothing: antialiased; 20 | font-family: "Amazon Ember", "Roboto", "Helvetica Neue", Arial, sans-serif; 21 | font-weight: $font-weight-normal; 22 | } 23 | 24 | h1, .h1, h2, .h2, h3, .h3, h4, .h4{ 25 | font-weight: $font-weight-light; 26 | margin: $margin-large-vertical 0 $margin-base-vertical; 27 | } 28 | 29 | h1, .h1 { 30 | font-size: $font-size-h1; 31 | } 32 | h2, .h2{ 33 | font-size: $font-size-h2; 34 | } 35 | h3, .h3{ 36 | font-size: $font-size-h3; 37 | margin: 20px 0 10px; 38 | } 39 | h4, .h4{ 40 | font-size: $font-size-h4; 41 | line-height: 30px; 42 | } 43 | h5, .h5 { 44 | font-size: $font-size-h5; 45 | margin-bottom: 15px; 46 | } 47 | h6, .h6{ 48 | font-size: $font-size-h6; 49 | font-weight: $font-weight-bold; 50 | text-transform: uppercase; 51 | } 52 | p{ 53 | font-size: $font-paragraph; 54 | line-height: $line-height-general; 55 | } 56 | 57 | h1 small, h2 small, h3 small, h4 small, h5 small, h6 small, .h1 small, .h2 small, .h3 small, .h4 small, .h5 small, .h6 small, h1 .small, h2 .small, h3 .small, h4 .small, h5 .small, h6 .small, .h1 .small, .h2 .small, .h3 .small, .h4 .small, .h5 .small, .h6 .small { 58 | color: $dark-gray; 59 | font-weight: $font-weight-light; 60 | line-height: $line-height-general; 61 | } 62 | 63 | h1 small, h2 small, h3 small, h1 .small, h2 .small, h3 .small { 64 | font-size: 60%; 65 | } 66 | 67 | h1 .subtitle{ 68 | display: block; 69 | margin: 0 0 $margin-large-vertical; 70 | } 71 | 72 | .text-muted{ 73 | color: #9A9A9A; 74 | } 75 | .text-primary, .text-primary:hover{ 76 | color: #1D62F0 !important; 77 | } 78 | .text-info, .text-info:hover{ 79 | color: $info-color !important; 80 | } 81 | .text-success, .text-success:hover{ 82 | color: $success-color !important; 83 | } 84 | .text-warning, .text-warning:hover{ 85 | color: $warning-color !important; 86 | } 87 | .text-danger, .text-danger:hover{ 88 | color: $danger-color !important; 89 | } 90 | 91 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_buttons.scss: -------------------------------------------------------------------------------- 1 | // Mixin for generating new styles 2 | @mixin btn-styles($btn-color, $btn-states-color) { 3 | border-color: $btn-color; 4 | color: $btn-color; 5 | 6 | &:hover, 7 | &:focus, 8 | &:active, 9 | &.active, 10 | .open > &.dropdown-toggle { 11 | background-color: $transparent-bg; 12 | color: $btn-states-color; 13 | border-color: $btn-states-color; 14 | } 15 | 16 | &.disabled, 17 | &:disabled, 18 | &[disabled], 19 | fieldset[disabled] & { 20 | &, 21 | &:hover, 22 | &:focus, 23 | &.focus, 24 | &:active, 25 | &.active { 26 | background-color: $transparent-bg; 27 | border-color: $btn-color; 28 | } 29 | } 30 | 31 | 32 | &.btn-fill { 33 | color: $white-color; 34 | background-color: $btn-color; 35 | @include opacity(1); 36 | 37 | // &:hover, 38 | &:focus, 39 | &:active, 40 | &.active, 41 | .open > &.dropdown-toggle{ 42 | background-color: $btn-states-color; 43 | color: $white-color; 44 | } 45 | 46 | .caret{ 47 | border-top-color: $white-color; 48 | } 49 | } 50 | 51 | .caret{ 52 | border-top-color: $btn-color; 53 | } 54 | } 55 | 56 | 57 | @mixin btn-size($padding-vertical, $padding-horizontal, $font-size, $border){ 58 | font-size: $font-size; 59 | border-radius: $border; 60 | padding: $padding-vertical $padding-horizontal; 61 | 62 | &.btn-round{ 63 | padding: $padding-vertical + 1 $padding-horizontal; 64 | } 65 | 66 | &.btn-simple{ 67 | padding: $padding-vertical + 2 $padding-horizontal; 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_cards.scss: -------------------------------------------------------------------------------- 1 | @mixin filter($color){ 2 | @if $color == #FFFFFF{ 3 | background-color: rgba($color,.91); 4 | } @else { 5 | background-color: rgba($color,.69); 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_chartist.scss: -------------------------------------------------------------------------------- 1 | // Scales for responsive SVG containers 2 | $ct-scales: ((1), (15/16), (8/9), (5/6), (4/5), (3/4), (2/3), (5/8), (1/1.618), (3/5), (9/16), (8/15), (1/2), (2/5), (3/8), (1/3), (1/4)) !default; 3 | $ct-scales-names: (ct-square, ct-minor-second, ct-major-second, ct-minor-third, ct-major-third, ct-perfect-fourth, ct-perfect-fifth, ct-minor-sixth, ct-golden-section, ct-major-sixth, ct-minor-seventh, ct-major-seventh, ct-octave, ct-major-tenth, ct-major-eleventh, ct-major-twelfth, ct-double-octave) !default; 4 | 5 | // Class names to be used when generating CSS 6 | $ct-class-chart: ct-chart !default; 7 | $ct-class-chart-line: ct-chart-line !default; 8 | $ct-class-chart-bar: ct-chart-bar !default; 9 | $ct-class-horizontal-bars: ct-horizontal-bars !default; 10 | $ct-class-chart-pie: ct-chart-pie !default; 11 | $ct-class-chart-donut: ct-chart-donut !default; 12 | $ct-class-label: ct-label !default; 13 | $ct-class-series: ct-series !default; 14 | $ct-class-line: ct-line !default; 15 | $ct-class-point: ct-point !default; 16 | $ct-class-area: ct-area !default; 17 | $ct-class-bar: ct-bar !default; 18 | $ct-class-slice-pie: ct-slice-pie !default; 19 | $ct-class-slice-donut: ct-slice-donut !default; 20 | $ct-class-grid: ct-grid !default; 21 | $ct-class-vertical: ct-vertical !default; 22 | $ct-class-horizontal: ct-horizontal !default; 23 | $ct-class-start: ct-start !default; 24 | $ct-class-end: ct-end !default; 25 | 26 | // Container ratio 27 | $ct-container-ratio: (1/1.618) !default; 28 | 29 | // Text styles for labels 30 | $ct-text-color: rgba(0, 0, 0, 0.4) !default; 31 | $ct-text-size: 1.3rem !default; 32 | $ct-text-align: flex-start !default; 33 | $ct-text-justify: flex-start !default; 34 | $ct-text-line-height: 1; 35 | 36 | // Grid styles 37 | $ct-grid-color: rgba(0, 0, 0, 0.2) !default; 38 | $ct-grid-dasharray: 2px !default; 39 | $ct-grid-width: 1px !default; 40 | 41 | // Line chart properties 42 | $ct-line-width: 3px !default; 43 | $ct-line-dasharray: false !default; 44 | $ct-point-size: 8px !default; 45 | // Line chart point, can be either round or square 46 | $ct-point-shape: round !default; 47 | // Area fill transparency between 0 and 1 48 | $ct-area-opacity: 0.8 !default; 49 | 50 | // Bar chart bar width 51 | $ct-bar-width: 10px !default; 52 | 53 | // Donut width (If donut width is to big it can cause issues where the shape gets distorted) 54 | $ct-donut-width: 60px !default; 55 | 56 | // If set to true it will include the default classes and generate CSS output. If you're planning to use the mixins you 57 | // should set this property to false 58 | $ct-include-classes: true !default; 59 | 60 | // If this is set to true the CSS will contain colored series. You can extend or change the color with the 61 | // properties below 62 | $ct-include-colored-series: $ct-include-classes !default; 63 | 64 | // If set to true this will include all responsive container variations using the scales defined at the top of the script 65 | $ct-include-alternative-responsive-containers: $ct-include-classes !default; 66 | 67 | // Series names and colors. This can be extended or customized as desired. Just add more series and colors. 68 | $ct-series-names: (a, b, c, d, e, f, g, h, i, j, k, l, m, n, o) !default; 69 | $ct-series-colors: ( 70 | $new-blue, 71 | $new-red, 72 | $new-orange, 73 | $new-purple, 74 | $new-green, 75 | $new-dark-blue, 76 | $new-black, 77 | $social-google, 78 | $social-tumblr, 79 | $social-youtube, 80 | $social-twitter, 81 | $social-pinterest, 82 | $social-behance, 83 | #6188e2, 84 | #a748ca 85 | ) !default; -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_icons.scss: -------------------------------------------------------------------------------- 1 | @mixin icon-background ($icon-url){ 2 | background-image : url($icon-url); 3 | 4 | } 5 | 6 | @mixin icon-shape ($size, $padding, $border-radius) { 7 | height: $size; 8 | width: $size; 9 | padding: $padding; 10 | border-radius: $border-radius; 11 | display: inline-table; 12 | 13 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_inputs.scss: -------------------------------------------------------------------------------- 1 | @mixin input-size($padding-vertical, $padding-horizontal, $height){ 2 | padding: $padding-vertical $padding-horizontal; 3 | height: $height; 4 | } 5 | 6 | @mixin placeholder($color, $opacity){ 7 | color: $color; 8 | @include opacity(1); 9 | } 10 | 11 | @mixin light-form(){ 12 | border-radius: 0; 13 | border:0; 14 | padding: 0; 15 | background-color: transparent; 16 | 17 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_labels.scss: -------------------------------------------------------------------------------- 1 | @mixin label-style(){ 2 | padding: $padding-label-vertical $padding-label-horizontal; 3 | border: 1px solid $default-color; 4 | border-radius: $border-radius-small; 5 | color: $default-color; 6 | font-weight: $font-weight-semi; 7 | font-size: $font-size-small; 8 | text-transform: uppercase; 9 | display: inline-block; 10 | vertical-align: middle; 11 | } 12 | 13 | @mixin label-color($color){ 14 | border-color: $color; 15 | color: $color; 16 | } 17 | @mixin label-color-fill($color){ 18 | border-color: $color; 19 | color: $white-color; 20 | background-color: $color; 21 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_morphing-buttons.scss: -------------------------------------------------------------------------------- 1 | $prefixes: ('', '-moz-', '-webkit-', '-ms-') !default; 2 | 3 | @mixin circle-animation(){ 4 | @for $i from 0 to length($prefixes) { 5 | @include circle-animation-details(nth($prefixes, $i + 1)); 6 | } 7 | } 8 | 9 | @mixin circle-animation-details($name){ 10 | #{$name}animation-name: spin; 11 | #{$name}animation-duration: 1250ms; 12 | #{$name}animation-iteration-count: infinite; 13 | #{$name}animation-timing-function: linear; 14 | 15 | } 16 | @keyframes spin { 17 | from { transform:rotate(0deg); } 18 | to { transform:rotate(360deg); } 19 | } 20 | 21 | @-webkit-keyframes spin { 22 | from { -webkit-transform: rotate(0deg); } 23 | to { -webkit-transform: rotate(360deg); } 24 | } 25 | 26 | @-moz-keyframes spin { 27 | from { -moz-transform: rotate(0deg); } 28 | to { -moz-transform: rotate(360deg); } 29 | } 30 | 31 | @-ms-keyframes spin { 32 | from { -ms-transform: rotate(0deg); } 33 | to { -ms-transform: rotate(360deg); } 34 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_navbars.scss: -------------------------------------------------------------------------------- 1 | @mixin navbar-color($color){ 2 | background-color: $color; 3 | } 4 | 5 | @mixin center-item(){ 6 | left: 0; 7 | right: 0; 8 | margin-right: auto; 9 | margin-left: auto; 10 | position: absolute; 11 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_social-buttons.scss: -------------------------------------------------------------------------------- 1 | @mixin social-buttons-color ($color){ 2 | 3 | border-color: $color; 4 | color: $color; 5 | 6 | &:hover, 7 | &:focus, 8 | &:active, 9 | &.active, 10 | .open > &.dropdown-toggle { 11 | background-color: $transparent-bg; 12 | color: $color; 13 | border-color: $color; 14 | opacity: 1; 15 | } 16 | 17 | &:disabled, 18 | &[disabled], 19 | &.disabled { 20 | background-color: $transparent-bg; 21 | border-color: $color; 22 | } 23 | 24 | &.btn-fill { 25 | color: $white-color; 26 | background-color: $color; 27 | opacity: 0.9; 28 | 29 | &:hover, 30 | &:focus, 31 | &:active, 32 | &.active, 33 | .open > &.dropdown-toggle{ 34 | background-color: $color; 35 | color: $white-color; 36 | opacity: 1; 37 | } 38 | 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_tabs.scss: -------------------------------------------------------------------------------- 1 | @mixin pill-style($color){ 2 | border: 1px solid $color; 3 | color: $color; 4 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/lbd/mixins/_transparency.scss: -------------------------------------------------------------------------------- 1 | // Opacity 2 | 3 | @mixin opacity($opacity) { 4 | opacity: $opacity; 5 | // IE8 filter 6 | $opacity-ie: ($opacity * 100); 7 | filter: #{alpha(opacity=$opacity-ie)}; 8 | } 9 | 10 | @mixin black-filter($opacity){ 11 | top: 0; 12 | left: 0; 13 | height: 100%; 14 | width: 100%; 15 | position: absolute; 16 | background-color: rgba(17,17,17,$opacity); 17 | display: block; 18 | content: ""; 19 | z-index: 1; 20 | } -------------------------------------------------------------------------------- /source/console/src/assets/sass/light-bootstrap-dashboard-react.scss: -------------------------------------------------------------------------------- 1 | /*! 2 | 3 | ========================================================= 4 | * Light Bootstrap Dashboard React - v1.3.0 5 | * Based on Light Bootstrap Dashboard - v1.3.0 6 | ========================================================= 7 | 8 | * Product Page: http://www.creative-tim.com/product/light-bootstrap-dashboard-react 9 | * Copyright 2017 Creative Tim (http://www.creative-tim.com) 10 | * Licensed under MIT (https://github.com/creativetimofficial/light-bootstrap-dashboard-react/blob/master/LICENSE.md) 11 | 12 | ========================================================= 13 | 14 | * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 15 | 16 | */ 17 | 18 | @import "lbd/variables"; 19 | @import "lbd/mixins"; 20 | 21 | // Core CSS 22 | @import "lbd/typography"; 23 | @import "lbd/misc"; 24 | @import "lbd/sidebar-and-main-panel"; 25 | @import "lbd/buttons"; 26 | @import "lbd/inputs"; 27 | 28 | @import "lbd/alerts"; 29 | @import "lbd/tables"; 30 | 31 | @import "lbd/checkbox-radio-switch"; 32 | @import "lbd/navbars"; 33 | @import "lbd/footers"; 34 | 35 | // Fancy Stuff 36 | @import "lbd/dropdown"; 37 | @import "lbd/cards"; 38 | @import "lbd/chartist"; 39 | @import "lbd/responsive"; 40 | 41 | @import "lbd/custom"; -------------------------------------------------------------------------------- /source/console/src/components/Card/Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | export class Card extends Component { 4 | render() { 5 | return ( 6 |
7 |
8 |

{this.props.title}

9 |

{this.props.category}

10 |
11 |
20 | {this.props.content} 21 | 22 |
23 | {this.props.legend} 24 | {this.props.stats != null ?
: ""} 25 |
26 | {this.props.stats} 27 |
28 |
29 |
30 |
31 | ); 32 | } 33 | } 34 | 35 | export default Card; 36 | -------------------------------------------------------------------------------- /source/console/src/components/CustomCheckbox/CustomCheckbox.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | 3 | class CustomCheckbox extends Component { 4 | constructor(props) { 5 | super(props); 6 | this.state = { 7 | is_checked: props.isChecked ? true : false 8 | }; 9 | this.handleClick = this.handleClick.bind(this); 10 | } 11 | handleClick() { 12 | this.setState({ is_checked: !this.state.is_checked }); 13 | } 14 | render() { 15 | const { isChecked, number, label, inline, ...rest } = this.props; 16 | const classes = 17 | inline !== undefined ? "checkbox checkbox-inline" : "checkbox"; 18 | return ( 19 |
20 | 27 | 28 |
29 | ); 30 | } 31 | } 32 | 33 | export default CustomCheckbox; 34 | -------------------------------------------------------------------------------- /source/console/src/components/Footer/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Grid } from "react-bootstrap"; 3 | 4 | class Footer extends Component { 5 | render() { 6 | return ( 7 | 19 | ); 20 | } 21 | } 22 | 23 | export default Footer; 24 | -------------------------------------------------------------------------------- /source/console/src/components/Navbars/AdminNavbar.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Navbar } from "react-bootstrap"; 3 | 4 | import AdminNavbarLinks from "./AdminNavbarLinks.jsx"; 5 | 6 | class Header extends Component { 7 | constructor(props) { 8 | super(props); 9 | this.mobileSidebarToggle = this.mobileSidebarToggle.bind(this); 10 | this.state = { 11 | sidebarExists: false 12 | }; 13 | } 14 | mobileSidebarToggle(e) { 15 | if (this.state.sidebarExists === false) { 16 | this.setState({ 17 | sidebarExists: true 18 | }); 19 | } 20 | e.preventDefault(); 21 | document.documentElement.classList.toggle("nav-open"); 22 | var node = document.createElement("div"); 23 | node.id = "bodyClick"; 24 | node.onclick = function () { 25 | this.parentElement.removeChild(this); 26 | document.documentElement.classList.toggle("nav-open"); 27 | }; 28 | document.body.appendChild(node); 29 | } 30 | render() { 31 | return ( 32 | 33 | 34 | 35 | 36 | 37 | 42 | 43 | 44 | ); 45 | } 46 | } 47 | 48 | export default Header; 49 | -------------------------------------------------------------------------------- /source/console/src/components/Navbars/AdminNavbarLinks.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { 3 | NavItem, 4 | Nav 5 | } from "react-bootstrap"; 6 | import { Auth, Logger, API } from 'aws-amplify'; 7 | 8 | import configurations from "variables/configurations"; 9 | 10 | class AdminNavbarLinks extends Component { 11 | constructor(props) { 12 | super(props); 13 | 14 | this.handleSelect = this.handleSelect.bind(this); 15 | 16 | this.logger = new Logger(configurations.logger.name, configurations.logger.level); 17 | 18 | // Sets up initial state 19 | this.state = { 20 | alertsCount: '-', 21 | side: this.props.side 22 | } 23 | } 24 | 25 | componentDidMount() { 26 | // In case the class is called from the side bar, it will get the value from the main container. 27 | // The main container gets the value through the API. 28 | if (this.state.side) { 29 | this.setState({ 30 | alertsCount: document.getElementById('notification-count').innerText 31 | }); 32 | this.timer = setInterval(() => { 33 | this.setState({ 34 | alertsCount: document.getElementById('notification-count').innerText 35 | }); 36 | }, 10000); // Gets alerts every 10 seconds from the main container 37 | } else { 38 | this.getAlertsCount(); 39 | this.timer = setInterval(async() => { 40 | await this.getAlertsCount(); 41 | }, 300000); // Gets alerts every 5 minute 42 | } 43 | } 44 | 45 | componentWillUnmount() { 46 | clearInterval(this.timer); 47 | } 48 | 49 | // Handles menu select 50 | handleSelect(eventKey) { 51 | if (eventKey === 'logout') { 52 | Auth.signOut() 53 | .then(data => this.logger.debug("Logged out")) 54 | .catch(err => this.logger.error(err)); 55 | } else if (eventKey === 'alerts') { 56 | this.props.history.push('/alerts'); 57 | } 58 | } 59 | 60 | // Gets alerts count 61 | getAlertsCount = async () => { 62 | let token = await this.props.getToken(); 63 | let apiName = 'smart-product-api'; 64 | let path = 'devices/alerts/count'; 65 | let params = { 66 | headers: { 67 | 'Authorization': token, 68 | }, 69 | response: false, 70 | }; 71 | 72 | API.get(apiName, path, params) 73 | .then(response => { 74 | this.setState({ alertsCount: response.alertsCount }); 75 | if (response.alertsCount > 0) { 76 | let message = `You have ${this.state.alertsCount} new alerts.`; 77 | this.props.handleNotification(message, 'warning', 'pe-7s-bell', 10); 78 | } 79 | }) 80 | .catch(error => { 81 | this.props.handleNotification('An error occurred while getting the count of alerts', 'error', 'pe-7s-close-circle', 10); 82 | }); 83 | } 84 | 85 | render() { 86 | return ( 87 |
88 | 100 |
101 | ); 102 | } 103 | } 104 | 105 | export default AdminNavbarLinks; 106 | -------------------------------------------------------------------------------- /source/console/src/components/Sidebar/Sidebar.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { NavLink } from "react-router-dom"; 3 | 4 | import AdminNavbarLinks from "../Navbars/AdminNavbarLinks.jsx"; 5 | import logo from "assets/img/logo-aws.png"; 6 | 7 | class Sidebar extends Component { 8 | constructor(props) { 9 | super(props); 10 | this.state = { 11 | width: window.innerWidth 12 | }; 13 | } 14 | activeRoute(routeName) { 15 | return this.props.location.pathname.indexOf(routeName) > -1 ? "active" : ""; 16 | } 17 | updateDimensions() { 18 | this.setState({ width: window.innerWidth }); 19 | } 20 | componentDidMount() { 21 | this.updateDimensions(); 22 | window.addEventListener("resize", this.updateDimensions.bind(this)); 23 | } 24 | render() { 25 | return ( 26 | 57 | ); 58 | } 59 | } 60 | 61 | export default Sidebar; 62 | -------------------------------------------------------------------------------- /source/console/src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | 4 | import { BrowserRouter, Route, Switch } from "react-router-dom"; 5 | 6 | import "bootstrap/dist/css/bootstrap.min.css"; 7 | import "./assets/css/animate.min.css"; 8 | import "./assets/sass/light-bootstrap-dashboard-react.scss?v=1.3.0"; 9 | import "./assets/css/demo.css"; 10 | import "./assets/css/pe-icon-7-stroke.css"; 11 | 12 | import AdminLayout from "layouts/Admin.jsx"; 13 | 14 | ReactDOM.render( 15 | 16 | 17 | } /> 18 | 19 | , 20 | document.getElementById("root") 21 | ); 22 | -------------------------------------------------------------------------------- /source/console/src/layouts/Admin.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import { Route, Switch, Redirect } from "react-router-dom"; 3 | import NotificationSystem from "react-notification-system"; 4 | import { withAuthenticator } from "aws-amplify-react"; 5 | import Amplify from '@aws-amplify/core'; 6 | import Auth from "@aws-amplify/auth"; 7 | 8 | import routes from "routes.js"; 9 | 10 | import AdminNavbar from "components/Navbars/AdminNavbar"; 11 | import Footer from "components/Footer/Footer"; 12 | import Sidebar from "components/Sidebar/Sidebar"; 13 | import { style } from "variables/Variables.jsx"; 14 | 15 | declare var smart_product_config; 16 | Amplify.configure(smart_product_config); 17 | 18 | class Admin extends Component { 19 | constructor(props) { 20 | super(props); 21 | this.state = { 22 | notificationSystem: null, 23 | color: "black", 24 | fixedClasses: "dropdown show-dropdown open" 25 | }; 26 | } 27 | 28 | // Handles Notification 29 | handleNotification = (message, level, iconClassName, autoDismissSecond) => { 30 | this.state.notificationSystem.addNotification({ 31 | title: (), 32 | message: ( 33 |
{message}
34 | ), 35 | level: level, 36 | position: 'tr', 37 | autoDismiss: autoDismissSecond, 38 | }); 39 | }; 40 | 41 | // Gets API token 42 | getToken = async () => { 43 | let user = await Auth.currentAuthenticatedUser(); 44 | let token = user.signInUserSession.idToken.jwtToken; 45 | 46 | return token; 47 | }; 48 | 49 | // Checks scroll 50 | isScrollBottom = () => { 51 | let scrollTop = document.scrollingElement.scrollTop; 52 | let offsetHeight = document.documentElement.offsetHeight; 53 | let innerHeight = window.innerHeight; 54 | 55 | return innerHeight + scrollTop === offsetHeight; 56 | }; 57 | 58 | // Goes to the top of the page 59 | goTop = () => { 60 | document.scrollingElement.scrollTop = 0; 61 | }; 62 | 63 | handleDateSize = (date) => { 64 | return date.substring(0, 10); 65 | }; 66 | 67 | getRoutes = routes => { 68 | return routes.map((prop, key) => { 69 | if (prop.layout === "/admin") { 70 | return ( 71 | ( 74 | 82 | )} 83 | exact 84 | key={key} 85 | /> 86 | ); 87 | } else { 88 | return null; 89 | } 90 | }); 91 | }; 92 | componentDidMount() { 93 | this.setState({ notificationSystem: this.refs.notificationSystem }); 94 | } 95 | componentDidUpdate(e) { 96 | if ( 97 | window.innerWidth < 993 && 98 | e.history.location.pathname !== e.location.pathname && 99 | document.documentElement.className.indexOf("nav-open") !== -1 100 | ) { 101 | document.documentElement.classList.toggle("nav-open"); 102 | } 103 | if (e.history.action === "PUSH") { 104 | document.documentElement.scrollTop = 0; 105 | document.scrollingElement.scrollTop = 0; 106 | this.refs.mainPanel.scrollTop = 0; 107 | } 108 | } 109 | render() { 110 | return ( 111 |
112 | 113 | 114 |
115 | 120 | 121 | {this.getRoutes(routes)} 122 | 123 | 124 |
126 |
127 | ); 128 | } 129 | } 130 | 131 | export default withAuthenticator(Admin); 132 | -------------------------------------------------------------------------------- /source/console/src/routes.js: -------------------------------------------------------------------------------- 1 | import Devices from "views/Devices.jsx"; 2 | import DeviceRegistration from "views/DeviceRegistration.jsx"; 3 | import DeviceDetail from "views/DeviceDetail.jsx"; 4 | import Alerts from "views/Alerts.jsx"; 5 | import History from "views/History.jsx"; 6 | import UserSetting from "views/UserSetting.jsx"; 7 | 8 | const dashboardRoutes = [ 9 | { 10 | path: "/devices", 11 | name: "My Devices", 12 | icon: "pe-7s-home", 13 | component: Devices, 14 | layout: "/admin", 15 | visible: true 16 | }, 17 | { 18 | path: "/devices/registration", 19 | name: "New Device Registration", 20 | icon: "pe-7s-home", 21 | component: DeviceRegistration, 22 | layout: "/admin", 23 | visible: false 24 | }, 25 | { 26 | path: "/devices/:deviceId", 27 | name: "Device Detail", 28 | icon: "pe-7s-home", 29 | component: DeviceDetail, 30 | layout: "/admin", 31 | visible: false 32 | }, 33 | { 34 | path: "/alerts", 35 | name: "Event Alerts", 36 | icon: "pe-7s-attention", 37 | component: Alerts, 38 | layout: "/admin", 39 | visible: true 40 | }, 41 | { 42 | path: "/history", 43 | name: "Event History", 44 | icon: "pe-7s-note2", 45 | component: History, 46 | layout: "/admin", 47 | visible: true 48 | }, 49 | { 50 | path: "/user", 51 | name: "User Setting", 52 | icon: "pe-7s-user", 53 | component: UserSetting, 54 | layout: "/admin", 55 | visible: true 56 | } 57 | ]; 58 | 59 | export default dashboardRoutes; 60 | -------------------------------------------------------------------------------- /source/console/src/variables/configurations.js: -------------------------------------------------------------------------------- 1 | const configurations = { 2 | "logger": { 3 | "name": "awsSmartProduct", 4 | "level": "INFO" 5 | } 6 | } 7 | export default configurations; -------------------------------------------------------------------------------- /source/resources/authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorizer", 3 | "version": "0.0.1", 4 | "description": "Authorizer is an auxiliary class to create authentication and authorization claim ticket for solution services", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "private": true, 9 | "main": "./auth.common.js", 10 | "dependencies": { 11 | "aws-sdk": "*", 12 | "moment": "*", 13 | "underscore": "*", 14 | "jsonwebtoken": "*", 15 | "jwk-to-pem": "*", 16 | "request": "*", 17 | "js-base64": "*", 18 | "logger": "file:../logger" 19 | }, 20 | "devDependencies": { 21 | "chai": "*", 22 | "sinon": "*", 23 | "sinon-chai": "*", 24 | "mocha": "*", 25 | "aws-sdk-mock": "*", 26 | "npm-run-all": "*", 27 | "proxyquire": "*" 28 | }, 29 | "scripts": { 30 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 31 | "test": "mocha lib/*.spec.js", 32 | "prestart": "rm -rf package-lock.json && npm install", 33 | "start": "node index.js", 34 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 35 | "build": "npm run build:init && npm install --production" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /source/resources/authorizer/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.sandbox.create(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/resources/cicd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cicd-helper", 3 | "version": "0.0.1", 4 | "description": "creates commit in users account with smart product code artifact", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "dependencies": { 9 | "aws-sdk": "*", 10 | "adm-zip": "^0.4.13", 11 | "logger": "file:../logger" 12 | }, 13 | "main": "index.js", 14 | "scripts": { 15 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 16 | "build:zip": "zip -rq smart-product-cicd.zip . -x template.yml", 17 | "build:dist": "mkdir dist && mv smart-product-cicd.zip dist/", 18 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /source/resources/cognito/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const AWS = require('aws-sdk'); 21 | const moment = require('moment'); 22 | const Logger = require('logger'); 23 | 24 | const creds = new AWS.EnvironmentCredentials('AWS'); 25 | const dynamoConfig = { 26 | credentials: creds, 27 | region: process.env.AWS_REGION, 28 | }; 29 | const docClient = new AWS.DynamoDB.DocumentClient(dynamoConfig); 30 | 31 | /** 32 | * Request handler. 33 | */ 34 | const handler = async (event) => { 35 | try { 36 | let triggerSource = event.triggerSource; 37 | if (triggerSource === 'PostConfirmation_ConfirmSignUp') { 38 | let settingId = event.request.userAttributes.sub; 39 | let _setting = { 40 | settingId: settingId, 41 | setting: { 42 | alertLevel: [ 43 | 'error', 44 | 'warning', 45 | ], 46 | sendNotification: false 47 | }, 48 | createdAt: moment().utc().format(), 49 | updatedAt: moment().utc().format(), 50 | }; 51 | 52 | let params = { 53 | TableName: process.env.SETTINGS_TBL, 54 | Item: _setting, 55 | }; 56 | await docClient.put(params).promise(); 57 | 58 | Logger.log( 59 | Logger.levels.INFO, 60 | `Success to process the setting: ${settingId}.` 61 | ); 62 | } 63 | return Promise.resolve(event); 64 | } catch (err) { 65 | Logger.error( 66 | Logger.levels.INFO, 67 | `Error occurred while processing the Cognito trigger: ${err}` 68 | ); 69 | return Promise.reject(err); 70 | } 71 | }; 72 | 73 | module.exports = { 74 | handler, 75 | }; -------------------------------------------------------------------------------- /source/resources/cognito/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | const expect = require('chai').expect; 4 | const AWS = require('aws-sdk-mock'); 5 | 6 | let event = { 7 | version: '1', 8 | region: 'REGION', 9 | userPoolId: 'USER_POOL_ID', 10 | userName: 'USER_NAME', 11 | callerContext: { 12 | awsSdkVersion: 'aws-sdk-unknown-unknown', 13 | clientId: 'CLIENT_ID' 14 | }, 15 | triggerSource: 'PostConfirmation_ConfirmSignUp', 16 | request: { 17 | userAttributes: { 18 | sub: 'SAMPLE_COGNITO_USER_ID', 19 | 'cognito:user_status': 'CONFIRMED', 20 | email_verified: 'true', 21 | 'cognito:email_alias': 'E_MAIL_ADDRESS', 22 | phone_number_verified: 'false', 23 | phone_number: 'PHONE_NUMBER', 24 | email: 'E_MAIL_ADDRESS' 25 | } 26 | }, 27 | response: {} 28 | }; 29 | 30 | let _module = require('./index.js'); 31 | 32 | describe('Index', () => { 33 | /** 34 | * The test throws error due to the restriction of aws-sdk-mock. 35 | * Please refer to https://www.npmjs.com/package/aws-sdk-mock. 36 | * To test below, you need to put resource declarations in the handler function. 37 | */ 38 | // afterEach(() => { 39 | // AWS.restore('DynamoDB.DocumentClient'); 40 | // }); 41 | 42 | // it('should return success when ddb put successful', function(done) { 43 | // AWS.mock('DynamoDB.DocumentClient', 'put', Promise.resolve('success')); 44 | 45 | // _module.handler(event) 46 | // .then(data => { 47 | // expect(data).to.deep.equal(event); 48 | // done(); 49 | // }) 50 | // .catch(err => { 51 | // done(err); 52 | // }); 53 | // }); 54 | 55 | // it('should return error information when ddb put fails', function(done) { 56 | // AWS.mock('DynamoDB.DocumentClient', 'put', Promise.reject('error')); 57 | 58 | // _module.handler(event) 59 | // .then(_data => { 60 | // done('invalid failure for negative test'); 61 | // }) 62 | // .catch(err => { 63 | // expect(err).to.equal('error'); 64 | // done(); 65 | // }); 66 | // }); 67 | }); 68 | -------------------------------------------------------------------------------- /source/resources/cognito/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cognito-trigger", 3 | "description": "Smart Product solution Cognito trigger Lambda function", 4 | "main": "index.js", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "version": "0.0.1", 9 | "private": true, 10 | "dependencies": { 11 | "moment": "*", 12 | "logger": "file:../logger" 13 | }, 14 | "devDependencies": { 15 | "chai": "*", 16 | "mocha": "*", 17 | "aws-sdk-mock": "*" 18 | }, 19 | "scripts": { 20 | "pretest": "rm -rf package-lock.json && rm -rf dist && npm install", 21 | "test": "mocha *.spec.js", 22 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 23 | "build:zip": "zip -rq smart-product-cognito.zip . -x template.yml", 24 | "build:dist": "mkdir dist && mv smart-product-cognito.zip dist/", 25 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist" 26 | }, 27 | "bundledDependencies": [ 28 | "moment" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /source/resources/helper/lib/dynamodb-helper.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const moment = require('moment'); 21 | let AWS = require('aws-sdk'); 22 | const _ = require('underscore'); 23 | const Logger = require('logger'); 24 | 25 | /** 26 | * Helper function to interact with dynamodb for cfn custom resource. 27 | * 28 | * @class dynamoDBHelper 29 | */ 30 | class dynamoDBHelper { 31 | /** 32 | * @class dynamoDBHelper 33 | * @constructor 34 | */ 35 | constructor() { 36 | this.creds = new AWS.EnvironmentCredentials('AWS'); // Lambda provided credentials 37 | this.dynamoConfig = { 38 | credentials: this.creds, 39 | region: process.env.AWS_REGION, 40 | }; 41 | } 42 | 43 | saveItem(item, ddbTable) { 44 | // Handling Promise Rejection 45 | process.on('unhandledRejection', error => { 46 | throw error; 47 | }); 48 | 49 | return new Promise((resolve, reject) => { 50 | for (var i = 0; i < _.keys(item).length; i++) { 51 | item[_.keys(item)[i]] = this._checkAssignedDataType( 52 | item[_.keys(item)[i]] 53 | ); 54 | } 55 | 56 | item.createdAt = moment.utc().format(); 57 | item.updatedAt = moment.utc().format(); 58 | 59 | let params = { 60 | TableName: ddbTable, 61 | Item: item, 62 | }; 63 | 64 | const docClient = new AWS.DynamoDB.DocumentClient(this.dynamoConfig); 65 | docClient.put(params, function (err, resp) { 66 | if (err) { 67 | Logger.error( 68 | Logger.levels.INFO, 69 | `Error occurred while attempting to save item ${JSON.stringify(params)}.` 70 | ); 71 | reject(err); 72 | } else { 73 | Logger.log( 74 | Logger.levels.INFO, 75 | `${JSON.stringify(item)} saved.` 76 | ); 77 | resolve(item); 78 | } 79 | }); 80 | }); 81 | } 82 | 83 | _checkAssignedDataType(attr) { 84 | if (_.isObject(attr)) { 85 | if (_.has(attr, 'N')) { 86 | return parseInt(attr['N']); 87 | } else if (_.has(attr, 'B')) { 88 | return attr['B'] === 'true'; 89 | } else { 90 | for (var i = 0; i < _.keys(attr).length; i++) { 91 | attr[_.keys(attr)[i]] = this._checkAssignedDataType( 92 | attr[_.keys(attr)[i]] 93 | ); 94 | } 95 | return attr; 96 | } 97 | } else { 98 | return attr; 99 | } 100 | } 101 | } 102 | 103 | module.exports = dynamoDBHelper; 104 | -------------------------------------------------------------------------------- /source/resources/helper/lib/dynamodb-helper.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let assert = require('chai').assert; 4 | let expect = require('chai').expect; 5 | var path = require('path'); 6 | let AWS = require('aws-sdk-mock'); 7 | AWS.setSDK(path.resolve('./node_modules/aws-sdk')); 8 | 9 | let DynamoDBHelper = require('./dynamodb-helper.js'); 10 | 11 | describe('dynamoDBHelper', function() { 12 | const _item = { 13 | id: 'abcxyz', 14 | val: { 15 | N: '12', 16 | }, 17 | setting: { 18 | limit: { 19 | N: '45', 20 | }, 21 | test: 'good', 22 | isABool: { 23 | B: 'true', 24 | }, 25 | }, 26 | }; 27 | 28 | describe('#saveItem', function() { 29 | beforeEach(function() {}); 30 | 31 | afterEach(function() { 32 | AWS.restore('DynamoDB.DocumentClient'); 33 | }); 34 | 35 | it('should return item when ddb put successful', function(done) { 36 | AWS.mock('DynamoDB.DocumentClient', 'put', function(params, callback) { 37 | callback(null, {}); 38 | }); 39 | 40 | let _helper = new DynamoDBHelper(); 41 | _helper 42 | .saveItem(_item, 'testtbl') 43 | .then(data => { 44 | expect(data).to.have.property('id'); 45 | expect(data).to.have.property('createdAt'); 46 | expect(data).to.have.property('updatedAt'); 47 | done(); 48 | }) 49 | .catch(err => { 50 | done(err); 51 | }); 52 | }); 53 | 54 | it('should return error information when ddb put fails', function(done) { 55 | AWS.mock('DynamoDB.DocumentClient', 'put', function(params, callback) { 56 | callback( 57 | { 58 | error: 'failed', 59 | }, 60 | {} 61 | ); 62 | }); 63 | 64 | let _helper = new DynamoDBHelper(); 65 | _helper 66 | .saveItem(_item, 'testtbl') 67 | .then(data => { 68 | done('invalid failure for negative test'); 69 | }) 70 | .catch(err => { 71 | expect(err).to.deep.equal({ 72 | error: 'failed', 73 | }); 74 | done(); 75 | }); 76 | }); 77 | }); 78 | 79 | describe('#_checkAssignedDataType', function() { 80 | beforeEach(function() {}); 81 | 82 | afterEach(function() {}); 83 | 84 | it('should return number when numeric value specifically assigned', function(done) { 85 | let _attr = { 86 | N: '12', 87 | }; 88 | 89 | let _helper = new DynamoDBHelper(); 90 | let val = _helper._checkAssignedDataType(_attr); 91 | expect(val).to.equal(12); 92 | done(); 93 | }); 94 | 95 | it('should return true/false when bool value specifically assigned', function(done) { 96 | let _attr = { 97 | B: 'false', 98 | }; 99 | 100 | let _helper = new DynamoDBHelper(); 101 | let val = _helper._checkAssignedDataType(_attr); 102 | expect(val).to.equal(false); 103 | done(); 104 | }); 105 | 106 | it('should return NaN when numeric value specifically assigned but not a number', function(done) { 107 | let _attr = { 108 | N: 'test', 109 | }; 110 | 111 | let _helper = new DynamoDBHelper(); 112 | let val = _helper._checkAssignedDataType(_attr); 113 | expect(val).to.be.NaN; 114 | done(); 115 | }); 116 | 117 | it('should return valid number when numeric value specifically assigned in nested object', function(done) { 118 | let _attr = { 119 | test: 'tesval', 120 | isNumber: { 121 | N: '88', 122 | }, 123 | }; 124 | 125 | let _helper = new DynamoDBHelper(); 126 | let val = _helper._checkAssignedDataType(_attr); 127 | expect(val).to.deep.equal({ 128 | test: 'tesval', 129 | isNumber: 88, 130 | }); 131 | done(); 132 | }); 133 | }); 134 | }); 135 | -------------------------------------------------------------------------------- /source/resources/helper/lib/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.createSandbox(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/resources/helper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ui-framework-helper", 3 | "description": "Smart Product solution custom resource helper Lambda function", 4 | "main": "index.js", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "version": "0.0.1", 9 | "private": true, 10 | "dependencies": { 11 | "aws-sdk": "*", 12 | "moment": "*", 13 | "underscore": "*", 14 | "password-generator": "*", 15 | "uuid": "^3.3.2", 16 | "usage-metrics": "file:../usage-metrics/", 17 | "logger": "file:../logger", 18 | "request-promise": "*", 19 | "request": "*" 20 | }, 21 | "devDependencies": { 22 | "aws-sdk": "*", 23 | "chai": "*", 24 | "sinon": "*", 25 | "sinon-chai": "*", 26 | "mocha": "*", 27 | "aws-sdk-mock": "*", 28 | "npm-run-all": "*", 29 | "proxyquire": "*" 30 | }, 31 | "scripts": { 32 | "pretest": "rm -rf package-lock.json && rm -rf dist && npm install", 33 | "test": "mocha lib/*.spec.js", 34 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 35 | "build:zip": "zip -rq smart-product-helper.zip . -x template.yml", 36 | "build:dist": "mkdir dist && mv smart-product-helper.zip dist/", 37 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist" 38 | }, 39 | "bundledDependencies": [ 40 | "aws-sdk", 41 | "moment", 42 | "underscore", 43 | "password-generator", 44 | "uuid", 45 | "request-promise", 46 | "request" 47 | ] 48 | } 49 | -------------------------------------------------------------------------------- /source/resources/logger/logger.common.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | // Logging class for sending messages to console log 21 | class Logger { 22 | constructor() {} 23 | 24 | static log(level, message) { 25 | if (level <= process.env.LOGGING_LEVEL) { 26 | console.log('[info] ', message); 27 | } 28 | } 29 | 30 | static warn(level, message) { 31 | if (level <= process.env.LOGGING_LEVEL) { 32 | console.log('[warn] ', message); 33 | } 34 | } 35 | 36 | static error(level, message) { 37 | if (level <= process.env.LOGGING_LEVEL) { 38 | console.log('[error] ', message); 39 | } 40 | } 41 | 42 | static get levels() { 43 | return { 44 | INFO: 1, 45 | ROBUST: 2, 46 | }; 47 | } 48 | } 49 | 50 | module.exports = Object.freeze(Logger); 51 | -------------------------------------------------------------------------------- /source/resources/logger/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "logger", 3 | "version": "0.0.1", 4 | "description": "Logger is an auxiliary logging class for solution services", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "private": true, 9 | "main": "./logger.common.js", 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "chai": "*", 13 | "sinon": "*", 14 | "sinon-chai": "*", 15 | "mocha": "*", 16 | "aws-sdk-mock": "*", 17 | "npm-run-all": "*", 18 | "proxyquire": "*" 19 | }, 20 | "scripts": { 21 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 22 | "test": "mocha lib/*.spec.js", 23 | "prestart": "rm -rf package-lock.json && npm install", 24 | "start": "node index.js", 25 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 26 | "build": "npm run build:init && npm install --production" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/resources/logger/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.sandbox.create(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/resources/usage-metrics/metrics.common.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | let https = require('https'); 20 | 21 | // Metrics class for sending usage metrics to sb endpoints 22 | class Metrics { 23 | constructor() { 24 | this.endpoint = 'metrics.awssolutionsbuilder.com'; 25 | } 26 | 27 | sendAnonymousMetric(metric) { 28 | return new Promise((resolve, reject) => { 29 | let _options = { 30 | hostname: this.endpoint, 31 | port: 443, 32 | path: '/generic', 33 | method: 'POST', 34 | headers: { 35 | 'Content-Type': 'application/json', 36 | }, 37 | }; 38 | 39 | let request = https.request(_options, function(response) { 40 | // data is streamed in chunks from the server 41 | // so we have to handle the "data" event 42 | let buffer; 43 | let data; 44 | let route; 45 | 46 | response.on('data', function(chunk) { 47 | buffer += chunk; 48 | }); 49 | 50 | response.on('end', function(err) { 51 | resolve('metric sent'); 52 | }); 53 | }); 54 | 55 | if (metric) { 56 | request.write(JSON.stringify(metric)); 57 | } 58 | 59 | request.end(); 60 | 61 | request.on('error', e => { 62 | console.error(e); 63 | reject(`Error occurred when sending metric request. ${JSON.stringify(_payload)}`); 64 | }); 65 | }); 66 | } 67 | } 68 | 69 | module.exports = Metrics; 70 | -------------------------------------------------------------------------------- /source/resources/usage-metrics/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "usage-metrics", 3 | "version": "0.0.1", 4 | "description": "Usage metrics is an auxiliary class to capture metrics pertinent for feedback on the solution", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "private": true, 9 | "main": "metrics.common.js", 10 | "dependencies": { 11 | "moment": "*" 12 | }, 13 | "devDependencies": { 14 | "aws-sdk": "*", 15 | "chai": "*", 16 | "sinon": "*", 17 | "sinon-chai": "*", 18 | "mocha": "*", 19 | "aws-sdk-mock": "*", 20 | "npm-run-all": "*", 21 | "proxyquire": "*" 22 | }, 23 | "scripts": { 24 | "test": "mocha *.spec.js" 25 | } 26 | } -------------------------------------------------------------------------------- /source/resources/usage-metrics/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.sandbox.create(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/resources/utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "utils", 3 | "version": "0.0.1", 4 | "description": "Utils is a common util class for solution services", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "private": true, 9 | "main": "./utils.common.js", 10 | "dependencies": {}, 11 | "devDependencies": { 12 | "chai": "*", 13 | "sinon": "*", 14 | "sinon-chai": "*", 15 | "mocha": "*", 16 | "aws-sdk-mock": "*", 17 | "npm-run-all": "*", 18 | "proxyquire": "*" 19 | }, 20 | "scripts": { 21 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 22 | "test": "mocha lib/*.spec.js", 23 | "prestart": "rm -rf package-lock.json && npm install", 24 | "start": "node index.js", 25 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 26 | "build": "npm run build:init && npm install --production" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /source/resources/utils/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.sandbox.create(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/resources/utils/utils.common.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | // Utils class for reducing redundant code 21 | class CommonUtils { 22 | constructor() { 23 | 24 | } 25 | 26 | /** 27 | * Generates DynamoDB query parameter. 28 | * @param {string} tableName - table name 29 | * @param {string} keyConditionExpression - key condition expression 30 | * @param {JSON} expressionAttributeValues - expression attribute values 31 | * @param {JSON} expressionAttributeNames - expression attribute names 32 | * @param {string} filterExpression - filter expression 33 | * @param {string} indexName - index name 34 | * @param {boolean} scanIndexForward - scan index forward 35 | * @param {JSON|string} lastevalkey - last evaluated key 36 | * @param {string} projectionExpression - projection expression 37 | * @param {Number} limit - limit 38 | */ 39 | generateDynamoDBQueryParams(tableName, keyConditionExpression, expressionAttributeValues, 40 | expressionAttributeNames, filterExpression, indexName, scanIndexForward, lastevalkey, 41 | projectionExpression, limit) { 42 | let params = { 43 | TableName: tableName, 44 | KeyConditionExpression: keyConditionExpression, 45 | ExpressionAttributeValues: expressionAttributeValues, 46 | Limit: 50, 47 | }; 48 | 49 | if (indexName) { 50 | params.IndexName = indexName; 51 | } 52 | 53 | if (scanIndexForward !== undefined) { 54 | params.ScanIndexForward = scanIndexForward 55 | } 56 | 57 | if (expressionAttributeNames) { 58 | params.ExpressionAttributeNames = expressionAttributeNames; 59 | } 60 | 61 | if (filterExpression) { 62 | params.FilterExpression = filterExpression; 63 | } 64 | 65 | if (projectionExpression) { 66 | params.ProjectionExpression = projectionExpression; 67 | } 68 | 69 | if (limit) { 70 | params.Limit = limit; 71 | } 72 | 73 | if (lastevalkey) { 74 | // If the LastEvaluatedKey is from the UI, parse the string to JSON object. 75 | if (typeof lastevalkey === 'string') { 76 | if (lastevalkey !== 'null') { 77 | params.ExclusiveStartKey = JSON.parse(decodeURIComponent(lastevalkey)); 78 | } 79 | } else { 80 | if (lastevalkey !== null) { 81 | params.ExclusiveStartKey = lastevalkey; 82 | } 83 | } 84 | } 85 | 86 | return params; 87 | } 88 | } 89 | 90 | module.exports = CommonUtils; 91 | -------------------------------------------------------------------------------- /source/services/api/admin/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const awsServerlessExpress = require('aws-serverless-express'); 21 | const Logger = require('logger'); 22 | let app = require('./lib/app.js'); 23 | 24 | const server = awsServerlessExpress.createServer(app); 25 | 26 | exports.handler = function(event, context) { 27 | Logger.log(Logger.levels.INFO, 'Admin service recieved event:'); 28 | Logger.log(Logger.levels.INFO, event); 29 | 30 | awsServerlessExpress.proxy(server, event, context); 31 | }; 32 | -------------------------------------------------------------------------------- /source/services/api/admin/lib/app.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | const Auth = require('authorizer'); 25 | const Admin = require('./admin.js'); 26 | const express = require('express'); 27 | const bodyParser = require('body-parser'); 28 | const cors = require('cors'); 29 | const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); 30 | const app = express(); 31 | const router = express.Router(); 32 | 33 | // declare a new express app 34 | router.use(cors()); 35 | router.use((req, res, next) => { 36 | bodyParser.json()(req, res, err => { 37 | if (err) { 38 | return res.status(400).json({ 39 | code: 400, 40 | error: 'BadRequest', 41 | message: err.message 42 | }); 43 | } 44 | next(); 45 | }); 46 | }); 47 | router.use(bodyParser.urlencoded({extended: true})); 48 | router.use(awsServerlessExpressMiddleware.eventContext()); 49 | 50 | const claimTicketHandler = async (req, res, next) => { 51 | try { 52 | const ticket = await Auth.getUserClaimTicket(req.header('Authorization')); 53 | req.ticket = ticket; 54 | next(); 55 | } catch (err) { 56 | return res 57 | .status(401) 58 | .json({error: 'AccessDeniedException', message: err.message}); 59 | } 60 | }; 61 | 62 | // Gets a setting 63 | const getSettings = async (req, res) => { 64 | const {settingId} = req.params; 65 | let _admin = new Admin(); 66 | Logger.log( 67 | Logger.levels.INFO, 68 | `Attempting to get setting for a setting ${settingId}` 69 | ); 70 | 71 | try { 72 | const result = await _admin.getSettings(settingId); 73 | res.json(result); 74 | } catch (err) { 75 | Logger.log(Logger.levels.INFO, err); 76 | 77 | let status = err.code; 78 | return res.status(status).json(err); 79 | } 80 | }; 81 | 82 | // Updates a setting 83 | const updateSettings = async (req, res) => { 84 | const {settingId} = req.params; 85 | const {body} = req; 86 | let _admin = new Admin(); 87 | Logger.log( 88 | Logger.levels.INFO, 89 | `Attempting to update a setting ${settingId}` 90 | ); 91 | 92 | try { 93 | const result = await _admin.updateSetting(settingId, body); 94 | res.json(result); 95 | } catch (err) { 96 | Logger.log(Logger.levels.INFO, err); 97 | 98 | let status = err.code; 99 | return res.status(status).json(err); 100 | } 101 | }; 102 | 103 | /**************************** 104 | * Event methods * 105 | ****************************/ 106 | 107 | router.get('/admin/settings/config/:settingId', claimTicketHandler, getSettings); 108 | 109 | router.put('/admin/settings/config/:settingId', claimTicketHandler, updateSettings); 110 | 111 | app.use('/', router); 112 | 113 | // Export the app object. When executing the application local this does nothing. However, 114 | // to port it to AWS Lambda we will create a wrapper around that will load the app from 115 | // this file 116 | module.exports = app; -------------------------------------------------------------------------------- /source/services/api/admin/lib/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.createSandbox(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/services/api/admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-admin-service", 3 | "description": "The administration microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "aws-serverless-express": "^3.3.6", 9 | "body-parser": "^1.17.1", 10 | "express": "^4.15.2", 11 | "cors": "^2.8.3", 12 | "authorizer": "file:../../../resources/authorizer", 13 | "logger": "file:../../../resources/logger", 14 | "moment": "*", 15 | "underscore": "*" 16 | }, 17 | "devDependencies": { 18 | "aws-sdk": "*", 19 | "chai": "*", 20 | "sinon": "*", 21 | "sinon-chai": "*", 22 | "mocha": "*", 23 | "aws-sdk-mock": "*", 24 | "proxyquire": "*" 25 | }, 26 | "scripts": { 27 | "pretest": "rm -rf package-lock.json && npm install", 28 | "test": "mocha lib/*.spec.js", 29 | "prestart": "rm -rf package-lock.json && npm install", 30 | "start": "node index.js", 31 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 32 | "build:zip": "zip -rq smart-product-admin-service.zip . -x template.yml", 33 | "build:dist": "mkdir dist && mv smart-product-admin-service.zip dist/", 34 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 35 | "local:depfix": "rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && cp -R ../../resources/authorizer ./node_modules/authorizer && cp -R ../../resources/logger ./node_modules/logger", 36 | "local:test": "sam local invoke 'AdminService' -e ../tests/admin/get-user-event.json --env-vars ../tests/env-vars.json", 37 | "local:api": "sam local start-api --env-vars ../tests/env-vars.json" 38 | }, 39 | "bundledDependencies": [ 40 | "aws-serverless-express", 41 | "body-parser", 42 | "express", 43 | "cors", 44 | "moment", 45 | "underscore" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /source/services/api/command/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const awsServerlessExpress = require('aws-serverless-express'); 21 | const Logger = require('logger'); 22 | let app = require('./lib/app.js'); 23 | 24 | const server = awsServerlessExpress.createServer(app); 25 | 26 | exports.handler = (event, context) => { 27 | Logger.log(Logger.levels.INFO, 'Command service recieved event:'); 28 | Logger.log(Logger.levels.INFO, event); 29 | 30 | awsServerlessExpress.proxy(server, event, context); 31 | }; 32 | -------------------------------------------------------------------------------- /source/services/api/command/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-command-service", 3 | "description": "The command microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "aws-serverless-express": "^3.3.6", 9 | "body-parser": "^1.17.1", 10 | "express": "^4.15.2", 11 | "cors": "^2.8.3", 12 | "moment": "*", 13 | "underscore": "*", 14 | "authorizer": "file:../../../resources/authorizer/", 15 | "logger": "file:../../../resources/logger/", 16 | "uuid": "^3.3.2", 17 | "usage-metrics": "file:../../../resources/usage-metrics", 18 | "utils": "file:../../../resources/utils/" 19 | }, 20 | "devDependencies": { 21 | "aws-sdk": "*", 22 | "aws-sdk-mock": "*", 23 | "chai": "*", 24 | "mocha": "*", 25 | "npm-run-all": "*", 26 | "proxyquire": "*", 27 | "sinon": "*", 28 | "sinon-chai": "*", 29 | "sinon-test": "^2.4.0" 30 | }, 31 | "scripts": { 32 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 33 | "test": "env AWS_REGION='test' mocha lib/*.spec.js", 34 | "prestart": "rm -rf package-lock.json && npm install", 35 | "start": "node index.js", 36 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 37 | "build:zip": "zip -rq smart-product-command-service.zip . -x template.yml", 38 | "build:dist": "mkdir dist && mv smart-product-command-service.zip dist/", 39 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 40 | "local:depfix": "rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && rm -rf ./node_modules/utils && rm -rf ./node_modules/usage-metrics && cp -R ../../../resources/authorizer ./node_modules/authorizer && cp -R ../../../resources/logger ./node_modules/logger && cp -R ../../../resources/utils ./node_modules/utils && cp -R ../../../resources/usage-metrics ./node_modules/usage-metrics", 41 | "local:api": "npm run test && npm run local:depfix && sam local start-api --env-vars ../../tests/env-vars.json" 42 | }, 43 | "bundledDependencies": [ 44 | "aws-serverless-express", 45 | "body-parser", 46 | "express", 47 | "cors", 48 | "moment", 49 | "underscore", 50 | "uuid", 51 | "usage-metrics", 52 | "utils" 53 | ] 54 | } 55 | -------------------------------------------------------------------------------- /source/services/api/device/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const awsServerlessExpress = require('aws-serverless-express'); 21 | const Logger = require('logger'); 22 | let app = require('./lib/app.js'); 23 | 24 | const server = awsServerlessExpress.createServer(app); 25 | 26 | exports.handler = (event, context) => { 27 | Logger.log(Logger.levels.INFO, 'Device service recieved event:'); 28 | Logger.log(Logger.levels.INFO, event); 29 | 30 | awsServerlessExpress.proxy(server, event, context); 31 | }; 32 | -------------------------------------------------------------------------------- /source/services/api/device/lib/app.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | const Auth = require('authorizer'); 25 | const Device = require('./device.js'); 26 | const express = require('express'); 27 | const bodyParser = require('body-parser'); 28 | const cors = require('cors'); 29 | const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); 30 | const app = express(); 31 | const router = express.Router(); 32 | 33 | // declare a new express app 34 | router.use(cors()); 35 | router.use((req, res, next) => { 36 | bodyParser.json()(req, res, err => { 37 | if (err) { 38 | return res.status(400).json({ 39 | code: 400, 40 | error: 'BadRequest', 41 | message: err.message 42 | }); 43 | } 44 | next(); 45 | }); 46 | }); 47 | router.use(bodyParser.urlencoded({extended: true})); 48 | router.use(awsServerlessExpressMiddleware.eventContext()); 49 | 50 | const claimTicketHandler = async (req, res, next) => { 51 | try { 52 | const ticket = await Auth.getUserClaimTicket(req.header('Authorization')); 53 | req.ticket = ticket; 54 | next(); 55 | } catch (err) { 56 | return res 57 | .status(401) 58 | .json({error: 'AccessDeniedException', message: err.message}); 59 | } 60 | }; 61 | 62 | // Gets the list of devices 63 | const getDevices = async (req, res) => { 64 | const {ticket} = req; 65 | let _device = new Device(); 66 | Logger.log( 67 | Logger.levels.INFO, 68 | `Attempting to retrieve the list of devices` 69 | ); 70 | 71 | try { 72 | const result = await _device.getDevices(ticket); 73 | res.json(result); 74 | } catch (err) { 75 | Logger.log(Logger.levels.INFO, err); 76 | 77 | let status = err.code; 78 | return res.status(status).json(err); 79 | } 80 | }; 81 | 82 | // Gets device information 83 | const getDevice = async (req, res) => { 84 | const {ticket} = req; 85 | const {deviceId} = req.params; 86 | let _device = new Device(); 87 | Logger.log( 88 | Logger.levels.INFO, 89 | `Attempting to retrieve device information for a device ${deviceId}` 90 | ); 91 | 92 | try { 93 | const result = await _device.getDevice(ticket, deviceId); 94 | res.json(result); 95 | } catch (err) { 96 | Logger.log(Logger.levels.INFO, err); 97 | 98 | let status = err.code; 99 | return res.status(status).json(err); 100 | } 101 | }; 102 | 103 | // Deletes a device 104 | const deleteDevice = async (req, res) => { 105 | const {ticket} = req; 106 | const {deviceId} = req.params; 107 | let _device = new Device(); 108 | Logger.log( 109 | Logger.levels.INFO, 110 | `Attempting to delete a device ${deviceId}` 111 | ); 112 | 113 | try { 114 | const result = await _device.deleteDevice(ticket, deviceId); 115 | res.json(result); 116 | } catch (err) { 117 | Logger.log(Logger.levels.INFO, err); 118 | 119 | let status = err.code; 120 | return res.status(status).json(err); 121 | } 122 | } 123 | 124 | /**************************** 125 | * Event methods * 126 | ****************************/ 127 | 128 | router.get('/devices', claimTicketHandler, getDevices); 129 | 130 | router.get('/devices/:deviceId', claimTicketHandler, getDevice); 131 | 132 | router.delete('/devices/:deviceId', claimTicketHandler, deleteDevice); 133 | 134 | app.use('/', router); 135 | 136 | // Export the app object. When executing the application local this does nothing. However, 137 | // to port it to AWS Lambda we will create a wrapper around that will load the app from 138 | // this file 139 | module.exports = app; 140 | -------------------------------------------------------------------------------- /source/services/api/device/lib/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.createSandbox(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/services/api/device/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-device-service", 3 | "description": "The device microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "aws-sdk": "*", 9 | "aws-serverless-express": "^3.3.6", 10 | "body-parser": "^1.17.1", 11 | "express": "^4.15.2", 12 | "cors": "^2.8.3", 13 | "underscore": "*", 14 | "authorizer": "file:../../../resources/authorizer/", 15 | "logger": "file:../../../resources/logger/", 16 | "moment": "^2.24.0" 17 | }, 18 | "devDependencies": { 19 | "aws-sdk": "*", 20 | "aws-sdk-mock": "*", 21 | "chai": "*", 22 | "mocha": "*", 23 | "npm-run-all": "*", 24 | "proxyquire": "*", 25 | "sinon": "*", 26 | "sinon-chai": "*" 27 | }, 28 | "scripts": { 29 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 30 | "test": "mocha lib/*.spec.js", 31 | "prestart": "rm -rf package-lock.json && npm install", 32 | "start": "node index.js", 33 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 34 | "build:zip": "zip -rq smart-product-device-service.zip . -x template.yml", 35 | "build:dist": "mkdir dist && mv smart-product-device-service.zip dist/", 36 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 37 | "local:depfix": "npm run build:init && npm install && rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && cp -R ../../../resources/authorizer ./node_modules/authorizer && cp -R ../../../resources/logger ./node_modules/logger", 38 | "local:api": "npm run test && npm run local:depfix && sam local start-api --env-vars ../../tests/env-vars.json" 39 | }, 40 | "bundledDependencies": [ 41 | "aws-sdk", 42 | "aws-serverless-express", 43 | "body-parser", 44 | "express", 45 | "cors", 46 | "underscore", 47 | "moment" 48 | ] 49 | } 50 | -------------------------------------------------------------------------------- /source/services/api/event/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const awsServerlessExpress = require('aws-serverless-express'); 21 | const Logger = require('logger'); 22 | let app = require('./lib/app.js'); 23 | 24 | const server = awsServerlessExpress.createServer(app); 25 | 26 | exports.handler = (event, context) => { 27 | Logger.log(Logger.levels.INFO, 'Event service recieved event:'); 28 | Logger.log(Logger.levels.INFO, event); 29 | 30 | awsServerlessExpress.proxy(server, event, context); 31 | }; 32 | -------------------------------------------------------------------------------- /source/services/api/event/lib/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.createSandbox(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/services/api/event/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-event-service", 3 | "description": "The event microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "aws-serverless-express": "^3.3.6", 9 | "body-parser": "^1.17.1", 10 | "express": "^4.15.2", 11 | "cors": "^2.8.3", 12 | "moment": "*", 13 | "underscore": "*", 14 | "authorizer": "file:../../../resources/authorizer/", 15 | "logger": "file:../../../resources/logger/", 16 | "utils": "file:../../../resources/utils/" 17 | }, 18 | "devDependencies": { 19 | "aws-sdk": "*", 20 | "aws-sdk-mock": "*", 21 | "chai": "*", 22 | "mocha": "*", 23 | "npm-run-all": "*", 24 | "proxyquire": "*", 25 | "sinon": "*", 26 | "sinon-chai": "*" 27 | }, 28 | "scripts": { 29 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 30 | "test": "mocha lib/*.spec.js", 31 | "prestart": "rm -rf package-lock.json && npm install", 32 | "start": "node index.js", 33 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 34 | "build:zip": "zip -rq smart-product-event-service.zip . -x template.yml", 35 | "build:dist": "mkdir dist && mv smart-product-event-service.zip dist/", 36 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 37 | "local:depfix": "npm run build:init && npm install && rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && rm -rf ./node_modules/utils && cp -R ../../../resources/authorizer ./node_modules/authorizer && cp -R ../../../resources/logger ./node_modules/logger && cp -R ../../../resources/utils ./node_modules/utils", 38 | "local:api": "npm run test && npm run local:depfix && sam local start-api --env-vars ../../tests/env-vars.json" 39 | }, 40 | "bundledDependencies": [ 41 | "aws-serverless-express", 42 | "body-parser", 43 | "express", 44 | "cors", 45 | "moment", 46 | "underscore", 47 | "colors", 48 | "utils" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /source/services/api/registration/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const awsServerlessExpress = require('aws-serverless-express'); 21 | const Logger = require('logger'); 22 | let app = require('./lib/app.js'); 23 | 24 | const server = awsServerlessExpress.createServer(app); 25 | 26 | exports.handler = (event, context) => { 27 | Logger.log(Logger.levels.INFO, 'Registration service recieved event:'); 28 | Logger.log(Logger.levels.INFO, event); 29 | 30 | awsServerlessExpress.proxy(server, event, context); 31 | }; 32 | -------------------------------------------------------------------------------- /source/services/api/registration/lib/app.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | const Auth = require('authorizer'); 25 | const Registration = require('./registration.js'); 26 | const express = require('express'); 27 | const bodyParser = require('body-parser'); 28 | const cors = require('cors'); 29 | const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); 30 | const app = express(); 31 | const router = express.Router(); 32 | 33 | // declare a new express app 34 | router.use(cors()); 35 | router.use((req, res, next) => { 36 | bodyParser.json()(req, res, err => { 37 | if (err) { 38 | return res.status(400).json({ 39 | code: 400, 40 | error: 'BadRequest', 41 | message: err.message 42 | }); 43 | } 44 | next(); 45 | }); 46 | }); 47 | router.use(bodyParser.urlencoded({extended: true})); 48 | router.use(awsServerlessExpressMiddleware.eventContext()); 49 | 50 | const claimTicketHandler = async (req, res, next) => { 51 | try { 52 | const ticket = await Auth.getUserClaimTicket(req.header('Authorization')); 53 | req.ticket = ticket; 54 | next(); 55 | } catch (err) { 56 | return res 57 | .status(401) 58 | .json({error: 'AccessDeniedException', message: err.message}); 59 | } 60 | }; 61 | 62 | const createRegistration = async (req, res) => { 63 | const {body, ticket} = req; 64 | let _registration = new Registration(); 65 | Logger.log( 66 | Logger.levels.INFO, 67 | `Attempting create device registration for device` 68 | ); 69 | 70 | try { 71 | const result = await _registration.createRegistration(ticket, body); 72 | res.json(result); 73 | } catch (err) { 74 | Logger.log(Logger.levels.INFO, err); 75 | 76 | let status = err.code; 77 | return res.status(status).json(err); 78 | } 79 | }; 80 | 81 | const getRegistrations = async (req, res) => { 82 | const {ticket} = req; 83 | let _registration = new Registration(); 84 | Logger.log( 85 | Logger.levels.INFO, 86 | `Attempting retrieve device registrations` 87 | ); 88 | 89 | try { 90 | const result = await _registration.listRegistrations(ticket); 91 | res.json(result); 92 | } catch (err) { 93 | Logger.log(Logger.levels.INFO, err); 94 | 95 | let status = err.code; 96 | return res.status(status).json(err); 97 | } 98 | }; 99 | 100 | /**************************** 101 | * Event methods * 102 | ****************************/ 103 | 104 | router.post('/registration', claimTicketHandler, createRegistration); 105 | 106 | router.get('/registration', claimTicketHandler, getRegistrations); 107 | 108 | app.use('/', router); 109 | 110 | // Export the app object. When executing the application local this does nothing. However, 111 | // to port it to AWS Lambda we will create a wrapper around that will load the app from 112 | // this file 113 | module.exports = app; 114 | -------------------------------------------------------------------------------- /source/services/api/registration/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-registration-service", 3 | "description": "The registration microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "aws-serverless-express": "^3.3.6", 9 | "body-parser": "^1.17.1", 10 | "express": "^4.15.2", 11 | "cors": "^2.8.3", 12 | "moment": "*", 13 | "underscore": "*", 14 | "authorizer": "file:../../../resources/authorizer/", 15 | "logger": "file:../../../resources/logger/", 16 | "usage-metrics": "file:../../../resources/usage-metrics" 17 | }, 18 | "devDependencies": { 19 | "aws-sdk": "*", 20 | "aws-sdk-mock": "*", 21 | "chai": "*", 22 | "mocha": "*", 23 | "npm-run-all": "*", 24 | "proxyquire": "*", 25 | "sinon": "*", 26 | "sinon-chai": "*", 27 | "sinon-test": "^2.4.0" 28 | }, 29 | "scripts": { 30 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 31 | "test": "env AWS_REGION='testRegion', THING_TYPE='testType' mocha lib/*.spec.js", 32 | "prestart": "rm -rf package-lock.json && npm install", 33 | "start": "node index.js", 34 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 35 | "build:zip": "zip -rq smart-product-registration-service.zip . -x template.yml", 36 | "build:dist": "mkdir dist && mv smart-product-registration-service.zip dist/", 37 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 38 | "local:depfix": "npm run build:init && npm install && rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && cp -R ../../../resources/authorizer ./node_modules/authorizer && cp -R ../../../resources/logger ./node_modules/logger", 39 | "local:api": "npm run local:depfix && sam local start-api --env-vars ../../tests/env-vars.json" 40 | }, 41 | "bundledDependencies": [ 42 | "aws-serverless-express", 43 | "body-parser", 44 | "express", 45 | "cors", 46 | "moment", 47 | "underscore", 48 | "usage-metrics" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /source/services/api/status/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const awsServerlessExpress = require('aws-serverless-express'); 21 | const Logger = require('logger'); 22 | let app = require('./lib/app.js'); 23 | 24 | const server = awsServerlessExpress.createServer(app); 25 | 26 | exports.handler = (event, context) => { 27 | Logger.log(Logger.levels.INFO, 'Registration service recieved event:'); 28 | Logger.log(Logger.levels.INFO, event); 29 | 30 | awsServerlessExpress.proxy(server, event, context); 31 | }; 32 | -------------------------------------------------------------------------------- /source/services/api/status/lib/app.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | const Auth = require('authorizer'); 25 | const Status = require('./status.js'); 26 | const express = require('express'); 27 | const bodyParser = require('body-parser'); 28 | const cors = require('cors'); 29 | const awsServerlessExpressMiddleware = require('aws-serverless-express/middleware'); 30 | const app = express(); 31 | const router = express.Router(); 32 | 33 | // declare a new express app 34 | router.use(cors()); 35 | router.use((req, res, next) => { 36 | bodyParser.json()(req, res, err => { 37 | if (err) { 38 | return res.status(400).json({ 39 | code: 400, 40 | error: 'BadRequest', 41 | message: err.message 42 | }); 43 | } 44 | next(); 45 | }); 46 | }); 47 | router.use(bodyParser.urlencoded({extended: true})); 48 | router.use(awsServerlessExpressMiddleware.eventContext()); 49 | 50 | const claimTicketHandler = async (req, res, next) => { 51 | try { 52 | const ticket = await Auth.getUserClaimTicket(req.header('Authorization')); 53 | req.ticket = ticket; 54 | next(); 55 | } catch (err) { 56 | return res 57 | .status(401) 58 | .json({error: 'AccessDeniedException', message: err.message}); 59 | } 60 | }; 61 | 62 | const getStatus = async (req, res) => { 63 | const {ticket} = req; 64 | const {deviceId} = req.params; 65 | let _status = new Status(); 66 | Logger.log( 67 | Logger.levels.INFO, 68 | `Attempting retrieve device status for device ${deviceId}` 69 | ); 70 | 71 | try { 72 | const result = await _status.getDeviceStatus(ticket, deviceId); 73 | res.json(result); 74 | } catch (err) { 75 | Logger.log(Logger.levels.INFO, err); 76 | 77 | let status = err.code; 78 | return res.status(status).json(err); 79 | } 80 | }; 81 | 82 | /**************************** 83 | * Status methods * 84 | ****************************/ 85 | 86 | router.get('/devices/:deviceId/status', claimTicketHandler, getStatus); 87 | 88 | app.use('/', router); 89 | 90 | // Export the app object. When executing the application local this does nothing. However, 91 | // to port it to AWS Lambda we will create a wrapper around that will load the app from 92 | // this file 93 | module.exports = app; 94 | -------------------------------------------------------------------------------- /source/services/api/status/lib/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.createSandbox(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/services/api/status/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-status-service", 3 | "description": "The status microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "aws-sdk": "*", 9 | "aws-serverless-express": "^3.3.6", 10 | "body-parser": "^1.17.1", 11 | "express": "^4.15.2", 12 | "cors": "^2.8.3", 13 | "underscore": "*", 14 | "authorizer": "file:../../../resources/authorizer/", 15 | "logger": "file:../../../resources/logger/" 16 | }, 17 | "devDependencies": { 18 | "aws-sdk": "*", 19 | "aws-sdk-mock": "*", 20 | "chai": "*", 21 | "mocha": "*", 22 | "npm-run-all": "*", 23 | "proxyquire": "*", 24 | "sinon": "*", 25 | "sinon-chai": "*" 26 | }, 27 | "scripts": { 28 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 29 | "test": "mocha lib/*.spec.js", 30 | "prestart": "rm -rf package-lock.json && npm install", 31 | "start": "node index.js", 32 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 33 | "build:zip": "zip -rq smart-product-status-service.zip . -x template.yml", 34 | "build:dist": "mkdir dist && mv smart-product-status-service.zip dist/", 35 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 36 | "local:depfix": "npm run build:init && npm install && rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && cp -R ../../../resources/authorizer ./node_modules/authorizer && cp -R ../../../resources/logger ./node_modules/logger", 37 | "local:api": "npm run local:depfix && sam local start-api --env-vars ../../tests/env-vars.json" 38 | }, 39 | "bundledDependencies": [ 40 | "aws-sdk", 41 | "aws-serverless-express", 42 | "body-parser", 43 | "express", 44 | "cors", 45 | "underscore" 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /source/services/command-status/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | console.log('Loading function'); 21 | const Logger = require('logger'); 22 | let lib = require('./lib'); 23 | 24 | exports.handler = function(event, context, callback) { 25 | Logger.log(Logger.levels.INFO, 'recieved event:'); 26 | Logger.log(Logger.levels.INFO, event); 27 | 28 | lib 29 | .response(event) 30 | .then(data => { 31 | return callback(null, data); 32 | }) 33 | .catch(err => { 34 | return callback(err, null); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /source/services/command-status/lib/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | const Message = require('./message.js'); 25 | 26 | const response = async event => { 27 | let _m = new Message(); 28 | let _message = {}; 29 | 30 | if (typeof event === 'object') { 31 | _message = event; 32 | } else { 33 | _message = JSON.parse(event); 34 | } 35 | 36 | try { 37 | const result = await _m.statusUpdate(_message); 38 | return Promise.resolve(result); 39 | } catch (err) { 40 | Logger.error(Logger.levels.INFO, err); 41 | Logger.error( 42 | Logger.levels.INFO, 43 | `Error occurred while updating status for device ${ 44 | _message.deviceId 45 | } command ${_message.commandId}.` 46 | ); 47 | return Promise.reject(err); 48 | } 49 | }; 50 | 51 | module.exports = { 52 | response, 53 | }; 54 | -------------------------------------------------------------------------------- /source/services/command-status/lib/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AWS = require('aws-sdk-mock'); 4 | const moment = require('moment'); 5 | const sinon = require('sinon'); 6 | const sinonTest = require('sinon-test'); 7 | const test = sinonTest(sinon); 8 | const expect = require('chai').expect; 9 | 10 | const lib = require('./index.js'); 11 | const Message = require('./message.js'); 12 | 13 | describe('Index', function() { 14 | let _event = { 15 | deviceId: '42adad4d-fdd1-4db0-a501-61cffd0fa3e4', 16 | status: 'ACK', 17 | commandId: '11advd4d-ccd1-4zw0-a459-6trfpq0fa454', 18 | }; 19 | 20 | it( 21 | 'should return event message with successful library Message updateStatus response', 22 | test(async function() { 23 | let _resp = { 24 | ..._event, 25 | }; 26 | _resp.updatedAt = moment() 27 | .utc() 28 | .format(); 29 | 30 | const stub = this.stub(Message.prototype, 'statusUpdate'); 31 | stub.resolves(_resp); 32 | try { 33 | const data = await lib.response(_event); 34 | expect(data.deviceId).to.equal(_resp.deviceId); 35 | expect(data.commandId).to.equal(_resp.commandId); 36 | expect(data.status).to.equal(_resp.status); 37 | expect(data).to.have.property('updatedAt'); 38 | } catch (err) { 39 | console.log('negative test: ', err); 40 | } 41 | }) 42 | ); 43 | 44 | it( 45 | 'should return error with failed library Message updateStatus response', 46 | test(async function() { 47 | const _resp = { 48 | code: 500, 49 | error: 'StatusUpdateFailure', 50 | message: `Error occurred while updating command status for device ${ 51 | _event.deviceId 52 | } command ${_event.commandId}.`, 53 | }; 54 | const stub = this.stub(Message.prototype, 'statusUpdate'); 55 | stub.rejects(_resp); 56 | try { 57 | await lib.response(_event); 58 | } catch (err) { 59 | expect(err).to.be.deep.equal(_resp); 60 | } 61 | }) 62 | ); 63 | }); 64 | -------------------------------------------------------------------------------- /source/services/command-status/lib/message.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const Logger = require('logger'); 21 | const moment = require('moment'); 22 | const AWS = require('aws-sdk'); 23 | 24 | /** 25 | * Performs proxy actions for messages from smart products to IoT. 26 | * 27 | * @class Message 28 | */ 29 | class Message { 30 | /** 31 | * @class Message 32 | * @constructor 33 | */ 34 | constructor() { 35 | this.creds = new AWS.EnvironmentCredentials('AWS'); // Lambda provided credentials 36 | this.dynamoConfig = { 37 | credentials: this.creds, 38 | region: process.env.AWS_REGION, 39 | }; 40 | } 41 | 42 | /** 43 | * Updates command status for device 44 | * @param {JSON} message - message object 45 | */ 46 | async statusUpdate(message) { 47 | // const _self = this; 48 | let _event = { 49 | ...message, 50 | }; 51 | _event.updatedAt = moment() 52 | .utc() 53 | .format(); 54 | 55 | const params = { 56 | TableName: process.env.COMMANDS_TBL, 57 | Key: { 58 | deviceId: message.deviceId, 59 | commandId: message.commandId, 60 | }, 61 | ExpressionAttributeNames: { 62 | '#S': 'status', 63 | '#U': 'updatedAt', 64 | '#R': 'reason', 65 | }, 66 | ExpressionAttributeValues: { 67 | ':s': message.status, 68 | ':u': moment() 69 | .utc() 70 | .format(), 71 | ':r': message.reason, 72 | }, 73 | UpdateExpression: 'SET #S = :s, #U = :u, #R = :r', 74 | }; 75 | 76 | let docClient = new AWS.DynamoDB.DocumentClient(this.dynamoConfig); 77 | let ddbPromise = docClient.update(params).promise(); 78 | 79 | try { 80 | await ddbPromise; 81 | return Promise.resolve(_event); 82 | } catch (err) { 83 | Logger.error(Logger.levels.INFO, err); 84 | return Promise.reject({ 85 | code: 500, 86 | error: 'StatusUpdateFailure', 87 | message: `Error occurred while updating command status for device ${ 88 | message.deviceId 89 | } command ${message.commandId}.`, 90 | }); 91 | } 92 | } 93 | } 94 | 95 | module.exports = Message; 96 | -------------------------------------------------------------------------------- /source/services/command-status/lib/message.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let expect = require('chai').expect; 4 | let AWS = require('aws-sdk-mock'); 5 | const sinon = require('sinon'); 6 | const sinonTest = require('sinon-test'); 7 | const test = sinonTest(sinon); 8 | 9 | let Message = require('./message.js'); 10 | 11 | let message = { 12 | deviceId: '42adad4d-fdd1-4db0-a501-61cffd0fa3e4', 13 | status: 'ACK', 14 | commandId: '11advd4d-ccd1-4zw0-a459-6trfpq0fa454', 15 | }; 16 | 17 | describe('Message', function() { 18 | beforeEach(function() {}); 19 | 20 | describe('#statusUpdate', function() { 21 | afterEach(function() { 22 | AWS.restore('DynamoDB.DocumentClient'); 23 | }); 24 | 25 | it( 26 | 'should return event with successful update', 27 | test(async function() { 28 | AWS.mock('DynamoDB.DocumentClient', 'update', Promise.resolve()); 29 | 30 | let _m = new Message(); 31 | try { 32 | const data = await _m.statusUpdate(message); 33 | expect(data.deviceId).to.equal(message.deviceId); 34 | expect(data.commandId).to.equal(message.commandId); 35 | expect(data.status).to.equal(message.status); 36 | expect(data).to.have.property('updatedAt'); 37 | } catch (e) { 38 | console.log(e); 39 | } 40 | }) 41 | ); 42 | 43 | it( 44 | 'should return error information when ddb update fails', 45 | test(async function() { 46 | AWS.mock('DynamoDB.DocumentClient', 'update', Promise.reject()); 47 | 48 | let _m = new Message(); 49 | try { 50 | await _m.statusUpdate(message); 51 | } catch (e) { 52 | expect(e).to.be.deep.equal({ 53 | code: 500, 54 | error: 'StatusUpdateFailure', 55 | message: `Error occurred while updating command status for device ${ 56 | message.deviceId 57 | } command ${message.commandId}.`, 58 | }); 59 | } 60 | }) 61 | ); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /source/services/command-status/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-command-status", 3 | "description": "The command status microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "logger": "file:../../resources/logger/" 9 | }, 10 | "devDependencies": { 11 | "aws-sdk-mock": "*", 12 | "chai": "*", 13 | "mocha": "*", 14 | "moment": "^2.24.0", 15 | "npm-run-all": "*", 16 | "sinon": "*", 17 | "sinon-test": "^2.4.0" 18 | }, 19 | "scripts": { 20 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 21 | "test": "env COMMANDS_TBL='cmdTable', AWS_REGION='test' mocha lib/*.spec.js", 22 | "prestart": "rm -rf package-lock.json && npm install", 23 | "start": "node index.js", 24 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 25 | "build:zip": "zip -rq smart-product-command-status.zip . -x template.yml", 26 | "build:dist": "mkdir dist && mv smart-product-command-status.zip dist/", 27 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 28 | "local:depfix": "rm -rf ./node_modules/logger && cp -R ../../resources/logger ./node_modules/logger", 29 | "local:test": "npm run local:depfix && sam local invoke 'CommandStatus' -e ../tests/event-message.json --env-vars ../tests/env-vars.json" 30 | }, 31 | "bundledDependencies": [ 32 | "moment" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /source/services/event-proxy/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | console.log('Loading function'); 21 | const Logger = require('logger'); 22 | let lib = require('./lib'); 23 | 24 | exports.handler = function(event, context, callback) { 25 | Logger.log(Logger.levels.INFO, 'Message proxy recieved event:'); 26 | Logger.log(Logger.levels.INFO, event); 27 | 28 | lib 29 | .response(event) 30 | .then(data => { 31 | return callback(null, data); 32 | }) 33 | .catch(err => { 34 | return callback(err, null); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /source/services/event-proxy/lib/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const _ = require('underscore'); 24 | const Logger = require('logger'); 25 | const Message = require('./message.js'); 26 | const AWS = require('aws-sdk'); 27 | 28 | const response = async event => { 29 | let _m = new Message(); 30 | let _message = {}; 31 | 32 | if (typeof event === 'object') { 33 | _message = event; 34 | } else { 35 | _message = JSON.parse(event); 36 | } 37 | 38 | try { 39 | const result = await _m.createEvent(_message); 40 | const lambda = new AWS.Lambda({region: process.env.AWS_REGION}); 41 | await lambda 42 | .invoke({ 43 | FunctionName: process.env.NOTIFICATION_LAMBDA, 44 | InvocationType: 'Event', 45 | Payload: JSON.stringify(result), 46 | }) 47 | .promise(); 48 | return Promise.resolve(result); 49 | } catch (err) { 50 | Logger.error(Logger.levels.INFO, err); 51 | Logger.error( 52 | Logger.levels.INFO, 53 | `Error occurred while attempting to create event and invoke notification for device.` 54 | ); 55 | return Promise.reject(err); 56 | } 57 | }; 58 | 59 | module.exports = { 60 | response, 61 | }; 62 | -------------------------------------------------------------------------------- /source/services/event-proxy/lib/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const sinon = require('sinon'); 4 | const assert = require('chai').assert; 5 | const expect = require('chai').expect; 6 | const path = require('path'); 7 | const AWS = require('aws-sdk-mock'); 8 | //AWS.setSDK(path.resolve('./node_modules/aws-sdk')); 9 | 10 | const lib = require('./index.js'); 11 | const Message = require('./message.js'); 12 | 13 | let sandbox; 14 | 15 | describe('Index', function() { 16 | beforeEach(function() { 17 | sandbox = sinon.createSandbox(); 18 | }); 19 | 20 | afterEach(function() { 21 | sandbox.restore(); 22 | }); 23 | 24 | it('should return event message with successful library Message createEvent response', function(done) { 25 | const _event = { 26 | createdAt: '2018-02-06T20:57:48Z', 27 | deviceId: '42adad4d-fdd1-4db0-a501-61cffd0fa3e4', 28 | messageId: '085e4e22-bd06-4ca6-b913-8b0b6bf154c1', 29 | message: 'R-410A refrigerant pressure exceeding upper threshold', 30 | details: { 31 | type: 'warning', 32 | eventId: 'R-410A-UT', 33 | sensorId: 'cps-1234', 34 | sensor: 'coolant pressure switch', 35 | value: 612, 36 | }, 37 | type: 'warning', 38 | sentAt: '2018-02-06T20:57:48Z', 39 | }; 40 | 41 | let _resp = { 42 | ..._event, 43 | }; 44 | _resp.id = 123; 45 | 46 | sandbox.stub(Message.prototype, 'createEvent').resolves(_resp); 47 | AWS.mock('Lambda', 'invoke', Promise.resolve()); 48 | 49 | lib 50 | .response(_event) 51 | .then(data => { 52 | expect(data.deviceId).to.equal(_event.deviceId); 53 | expect(data.messageId).to.equal(_event.messageId); 54 | expect(data).to.have.property('id'); 55 | done(); 56 | AWS.restore('Lambda'); 57 | }) 58 | .catch(err => { 59 | done(err); 60 | }); 61 | }); 62 | 63 | it('should return error with failed library Message createEvent response', function(done) { 64 | const _event = { 65 | createdAt: '2018-02-06T20:57:48Z', 66 | deviceId: '42adad4d-fdd1-4db0-a501-61cffd0fa3e4', 67 | messageId: '085e4e22-bd06-4ca6-b913-8b0b6bf154c1', 68 | message: 'R-410A refrigerant pressure exceeding upper threshold', 69 | details: { 70 | type: 'warning', 71 | eventId: 'R-410A-UT', 72 | sensorId: 'cps-1234', 73 | sensor: 'coolant pressure switch', 74 | value: 612, 75 | }, 76 | type: 'warning', 77 | sentAt: '2018-02-06T20:57:48Z', 78 | }; 79 | 80 | let _resp = { 81 | code: 500, 82 | error: 'EventCreateFailure', 83 | message: `Error occurred while attempting to create event message for device ${ 84 | _event.deviceId 85 | }.`, 86 | }; 87 | 88 | sandbox.stub(Message.prototype, 'createEvent').rejects(_resp); 89 | AWS.mock('Lambda', 'invoke', Promise.resolve()); 90 | 91 | lib 92 | .response(_event) 93 | .then(data => { 94 | done('invalid failure for negative test'); 95 | }) 96 | .catch(err => { 97 | expect(err).to.deep.equal(_resp); 98 | AWS.restore('Lambda'); 99 | done(); 100 | }); 101 | }); 102 | 103 | it('should return error for failed lambda invocation', function(done) { 104 | const _event = { 105 | createdAt: '2018-02-06T20:57:48Z', 106 | deviceId: '42adad4d-fdd1-4db0-a501-61cffd0fa3e4', 107 | messageId: '085e4e22-bd06-4ca6-b913-8b0b6bf154c1', 108 | message: 'R-410A refrigerant pressure exceeding upper threshold', 109 | details: { 110 | type: 'warning', 111 | eventId: 'R-410A-UT', 112 | sensorId: 'cps-1234', 113 | sensor: 'coolant pressure switch', 114 | value: 612, 115 | }, 116 | type: 'warning', 117 | sentAt: '2018-02-06T20:57:48Z', 118 | }; 119 | 120 | let _resp = { 121 | code: 500, 122 | error: 'LambdaInvocationFailed', 123 | message: `Error occurred while invoking lambda function.`, 124 | }; 125 | 126 | sandbox.stub(Message.prototype, 'createEvent').resolves(); 127 | AWS.mock('Lambda', 'invoke', Promise.reject(_resp)); 128 | 129 | lib 130 | .response(_event) 131 | .then(data => { 132 | done('invalid failure for negative test'); 133 | }) 134 | .catch(err => { 135 | expect(err).to.deep.equal(_resp); 136 | AWS.restore('Lambda'); 137 | done(); 138 | }); 139 | }); 140 | }); 141 | -------------------------------------------------------------------------------- /source/services/event-proxy/lib/message.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | let assert = require('chai').assert; 4 | let expect = require('chai').expect; 5 | var path = require('path'); 6 | let AWS = require('aws-sdk-mock'); 7 | // AWS.setSDK(path.resolve('./node_modules/aws-sdk')); 8 | 9 | let Message = require('./message.js'); 10 | 11 | let message = { 12 | createdAt: '2018-02-06T20:57:48Z', 13 | deviceId: '42adad4d-fdd1-4db0-a501-61cffd0fa3e4', 14 | messageId: '085e4e22-bd06-4ca6-b913-8b0b6bf154c1', 15 | message: 'R-410A refrigerant pressure exceeding upper threshold', 16 | details: { 17 | type: 'warning', 18 | eventId: 'R-410A-UT', 19 | sensorId: 'cps-1234', 20 | sensor: 'coolant pressure switch', 21 | value: 612, 22 | }, 23 | type: 'warning', 24 | sentAt: '2018-02-06T20:57:48Z', 25 | }; 26 | 27 | let successUserId = { 28 | Items: [ 29 | { 30 | deviceId: '21c131f9-81a1-4c4f-8d3d-919803ca3234', 31 | userId: '6aeec314-405d-4371-b2f9-42bab52c2ea1', 32 | updatedAt: '2019-05-17T15:45:55.687Z', 33 | status: 'complete', 34 | activedAt: '2019-05-17T15:45:55.687Z', 35 | createdAt: '2019-05-17T15:45:55.687Z', 36 | } 37 | ], 38 | Count: 1, 39 | ScannedCount: 1 40 | }; 41 | 42 | describe('Message', function() { 43 | beforeEach(function() {}); 44 | 45 | describe('#createEvent', function() { 46 | afterEach(function() { 47 | AWS.restore('DynamoDB.DocumentClient'); 48 | }); 49 | 50 | it('should return event with successful create', function(done) { 51 | AWS.mock('DynamoDB.DocumentClient', 'query', function(params, callback) { 52 | callback(null, successUserId); 53 | }); 54 | AWS.mock('DynamoDB.DocumentClient', 'put', function(params, callback) { 55 | callback(null, message); 56 | }); 57 | 58 | let _mm = new Message(); 59 | _mm 60 | .createEvent(message) 61 | .then(data => { 62 | expect(data.deviceId).to.equal(message.deviceId); 63 | expect(data.messageId).to.equal(message.messageId); 64 | expect(data).to.have.property('id'); 65 | done(); 66 | }) 67 | .catch(err => { 68 | done(err); 69 | }); 70 | }); 71 | 72 | it('should return error information when ddb put fails', function(done) { 73 | AWS.mock('DynamoDB.DocumentClient', 'query', function(params, callback) { 74 | callback(null, successUserId); 75 | }); 76 | AWS.mock('DynamoDB.DocumentClient', 'put', function(params, callback) { 77 | callback('ddb error', null); 78 | }); 79 | 80 | let _mm = new Message(); 81 | _mm 82 | .createEvent(message) 83 | .then(data => { 84 | done('invalid failure for negative test'); 85 | }) 86 | .catch(err => { 87 | expect(err).to.deep.equal({ 88 | code: 500, 89 | error: 'EventCreateFailure', 90 | message: `Error occurred while attempting to create event message for device ${ 91 | message.deviceId 92 | }.`, 93 | }); 94 | done(); 95 | }); 96 | }); 97 | }); 98 | }); 99 | -------------------------------------------------------------------------------- /source/services/event-proxy/lib/test-setup.spec.js: -------------------------------------------------------------------------------- 1 | const sinon = require('sinon'); 2 | const chai = require('chai'); 3 | const sinonChai = require('sinon-chai'); 4 | 5 | before(function() { 6 | chai.use(sinonChai); 7 | }); 8 | 9 | beforeEach(function() { 10 | this.sandbox = sinon.createSandbox(); 11 | }); 12 | 13 | afterEach(function() { 14 | this.sandbox.restore(); 15 | }); 16 | -------------------------------------------------------------------------------- /source/services/event-proxy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-event-proxy", 3 | "description": "The event proxy microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "moment": "*", 9 | "underscore": "*", 10 | "logger": "file:../../resources/logger/", 11 | "uuid": "*" 12 | }, 13 | "devDependencies": { 14 | "aws-sdk": "*", 15 | "aws-sdk-mock": "*", 16 | "chai": "*", 17 | "mocha": "*", 18 | "npm-run-all": "*", 19 | "proxyquire": "*", 20 | "sinon": "*", 21 | "sinon-chai": "*" 22 | }, 23 | "scripts": { 24 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 25 | "test": "env NOTIFICATION_LAMBDA='lambda_arn', AWS_REGION='test' mocha lib/*.spec.js", 26 | "prestart": "rm -rf package-lock.json && npm install", 27 | "start": "node index.js", 28 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 29 | "build:zip": "zip -rq smart-product-event-proxy.zip . -x template.yml", 30 | "build:dist": "mkdir dist && mv smart-product-event-proxy.zip dist/", 31 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 32 | "local:depfix": "rm -rf ./node_modules/logger && cp -R ../../resources/logger ./node_modules/logger", 33 | "local:test": "npm run local:depfix && sam local invoke 'EventProxy' -e ../tests/event-message.json --env-vars ../tests/env-vars.json" 34 | }, 35 | "bundledDependencies": [ 36 | "moment", 37 | "underscore", 38 | "colors", 39 | "uuid", 40 | "logger" 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /source/services/jitr/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | console.log('Loading function'); 21 | const Logger = require('logger'); 22 | let lib = require('./lib'); 23 | 24 | exports.handler = async (event, context) => { 25 | Logger.log(Logger.levels.INFO, 'JITR proxy recieved event:'); 26 | Logger.log(Logger.levels.INFO, event); 27 | 28 | try { 29 | await lib.respond(event); 30 | } catch (err) { 31 | Logger.error(Logger.levels.INFO, err); 32 | throw new Error(err); 33 | } 34 | }; 35 | -------------------------------------------------------------------------------- /source/services/jitr/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-jitr", 3 | "description": "A Lambda function for the smart product device just-in-time-registration microservice", 4 | "main": "index.js", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "version": "0.0.1", 9 | "private": true, 10 | "dependencies": { 11 | "authorizer": "file:../../resources/authorizer/", 12 | "logger": "file:../../resources/logger/", 13 | "moment": "*", 14 | "usage-metrics": "file:../../resources/usage-metrics", 15 | "@fidm/x509": "^1.2.1" 16 | }, 17 | "devDependencies": { 18 | "aws-sdk-mock": "*", 19 | "chai": "^4.2.0", 20 | "eslint": "^5.16.0", 21 | "eslint-config-strongloop": "^2.1.0", 22 | "mocha": "^7.0.0", 23 | "sinon": "^8.0.4", 24 | "sinon-chai": "^3.4.0", 25 | "sinon-test": "^2.4.1" 26 | }, 27 | "scripts": { 28 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 29 | "test": "env LOGGING_LEVEL='2' AWS_REGION='us-east-1' mocha lib/*.spec.js", 30 | "prestart": "rm -rf package-lock.json && npm install", 31 | "start": "node index.js", 32 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 33 | "build:zip": "zip -rq smart-product-jitr-service.zip . -x template.yml", 34 | "build:dist": "mkdir dist && mv smart-product-jitr-service.zip dist/", 35 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist" 36 | }, 37 | "bundledDependencies": [], 38 | "eslintConfig": { 39 | "extends": "strongloop", 40 | "env": { 41 | "node": true, 42 | "mocha": true 43 | }, 44 | "parserOptions": { 45 | "ecmaVersion": 8 46 | }, 47 | "rules": { 48 | "max-len": [ 49 | 2, 50 | 120, 51 | 8 52 | ] 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /source/services/notification/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | console.log('Loading function'); 21 | const Logger = require('logger'); 22 | let lib = require('./lib/index.js'); 23 | 24 | exports.handler = function(event, context, callback) { 25 | Logger.log(Logger.levels.INFO, 'Notification service recieved event:'); 26 | Logger.log(Logger.levels.INFO, event); 27 | 28 | lib 29 | .respond(event) 30 | .then(data => { 31 | return callback(null, data); 32 | }) 33 | .catch(err => { 34 | return callback(err, null); 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /source/services/notification/lib/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | const Alert = require('./alert.js'); 25 | 26 | const respond = async event => { 27 | const _a = new Alert(); 28 | try { 29 | const result = await _a.sendAlert(event); 30 | return Promise.resolve(result); 31 | } catch (err) { 32 | Logger.error(Logger.levels.INFO, err); 33 | Logger.error( 34 | Logger.levels.INFO, 35 | `Error occurred while attempting to send alert notification` 36 | ); 37 | return Promise.reject(err); 38 | } 39 | }; 40 | 41 | module.exports = { 42 | respond, 43 | }; 44 | -------------------------------------------------------------------------------- /source/services/notification/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-notification-service", 3 | "description": "The notification microservice for the smart product solution", 4 | "main": "index.js", 5 | "version": "0.0.1", 6 | "private": true, 7 | "dependencies": { 8 | "authorizer": "file:../../resources/authorizer/", 9 | "logger": "file:../../resources/logger/", 10 | "moment": "*", 11 | "underscore": "^1.9.1" 12 | }, 13 | "devDependencies": { 14 | "aws-sdk": "*", 15 | "aws-sdk-mock": "*", 16 | "chai": "*", 17 | "mocha": "*", 18 | "npm-run-all": "*", 19 | "proxyquire": "*", 20 | "sinon": "*", 21 | "sinon-chai": "*", 22 | "sinon-test": "^2.4.0" 23 | }, 24 | "scripts": { 25 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 26 | "test": "env AWS_REGION='test', REGISTRATION_TBL='table1', SETTINGS_TBL='table2', IDP='userPoolID' mocha lib/alert.spec.js", 27 | "prestart": "rm -rf package-lock.json && npm install", 28 | "start": "node index.js", 29 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 30 | "build:zip": "zip -rq smart-product-notification-service.zip . -x template.yml", 31 | "build:dist": "mkdir dist && mv smart-product-notification-service.zip dist/", 32 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist", 33 | "local:depfix": "rm -rf ./node_modules/authorizer && rm -rf ./node_modules/logger && cp -R ../../resources/authorizer ./node_modules/authorizer && cp -R ../../resources/logger ./node_modules/logger", 34 | "local:api": "sam local start-api --env-vars ../tests/env-vars.json" 35 | }, 36 | "bundledDependencies": [ 37 | "moment", 38 | "underscore", 39 | "colors" 40 | ], 41 | "eslintConfig": { 42 | "extends": "strongloop", 43 | "env": { 44 | "node": true, 45 | "mocha": true 46 | }, 47 | "parserOptions": { 48 | "ecmaVersion": 8 49 | }, 50 | "rules": { 51 | "max-len": [ 52 | 2, 53 | 120, 54 | 8 55 | ] 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /source/services/telemetry/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | const Logger = require('logger'); 21 | let lib = require('./lib'); 22 | 23 | exports.handler = function(event, context, callback) { 24 | Logger.log(Logger.levels.INFO, 'Telemetry recieved event:'); 25 | Logger.log(Logger.levels.INFO, event); 26 | 27 | lib 28 | .process(event) 29 | .then(data => { 30 | return callback(null, data); 31 | }) 32 | .catch(err => { 33 | return callback(err, null); 34 | }); 35 | }; 36 | -------------------------------------------------------------------------------- /source/services/telemetry/lib/index.js: -------------------------------------------------------------------------------- 1 | /********************************************************************************************************************* 2 | * Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved. * 3 | * * 4 | * Licensed under the Apache License, Version 2.0 (the "License"). You may not use this file except in compliance * 5 | * with the License. A copy of the License is located at * 6 | * * 7 | * http://www.apache.org/licenses/LICENSE-2.0 * 8 | * * 9 | * or in the 'license' file accompanying this file. This file is distributed on an 'AS IS' BASIS, WITHOUT WARRANTIES * 10 | * OR CONDITIONS OF ANY KIND, express or implied. See the License for the specific language governing permissions * 11 | * and limitations under the License. * 12 | *********************************************************************************************************************/ 13 | 14 | /** 15 | * @author Solution Builders 16 | */ 17 | 18 | 'use strict'; 19 | 20 | /** 21 | * Lib 22 | */ 23 | const Logger = require('logger'); 24 | 25 | const has = Object.prototype.hasOwnProperty; 26 | 27 | /** 28 | * Convert Fahrenheit of degree to Celsius of degree 29 | * @param {number} temperature 30 | * @returns {number} 31 | */ 32 | const fahrenheitToCelsius = temperature => { 33 | return parseFloat((((temperature - 32) * 5) / 9).toFixed(2)); 34 | }; 35 | 36 | const process = async function(event) { 37 | try { 38 | event.forEach(e => { 39 | // Converting temperature from Fahrenheit to Celsius 40 | if (has.call(e, 'actualTemperature')) { 41 | e.actualTemperatureC = fahrenheitToCelsius(e.actualTemperature); 42 | } 43 | if (has.call(e, 'targetTemperature')) { 44 | e.targetTemperatureC = fahrenheitToCelsius(e.targetTemperature); 45 | } 46 | 47 | // Adding UTC time 48 | if (has.call(e, 'timestamp')) { 49 | let utcTime = new Date(e.timestamp).toISOString(); 50 | e.sentAtUtc = utcTime; 51 | e.createdAtUtc = utcTime; 52 | } 53 | }); 54 | 55 | return Promise.resolve(event); 56 | } catch (err) { 57 | Logger.error(Logger.levels.INFO, err); 58 | Logger.error( 59 | Logger.levels.INFO, 60 | `Error occurred while transforming the telemetry event.` 61 | ); 62 | return Promise.reject(err); 63 | } 64 | }; 65 | 66 | module.exports = { 67 | process, 68 | }; 69 | -------------------------------------------------------------------------------- /source/services/telemetry/lib/index.spec.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const expect = require('chai').expect; 4 | 5 | const lib = require('./index.js'); 6 | let event = {}; 7 | 8 | const successData = [ 9 | { 10 | createdAt: '2019-05-01T12:43:07-07:00', 11 | deviceId: '9fd6fd62-0b10-4b5c-a7a1-5995c81db07e', 12 | actualTemperature: 45.75, 13 | targetTemperature: 70, 14 | sentAt: '2019-05-01T12:43:07-07:00', 15 | timestamp: 1556739787113, 16 | actualTemperatureC: 7.64, 17 | targetTemperatureC: 21.11, 18 | sentAtUtc: '2019-05-01T19:43:07.113Z', 19 | createdAtUtc: '2019-05-01T19:43:07.113Z', 20 | }, 21 | { 22 | createdAt: '2019-05-01T12:43:17-07:00', 23 | deviceId: '9fd6fd62-0b10-4b5c-a7a1-5995c81db07e', 24 | actualTemperature: 46.5, 25 | targetTemperature: 70, 26 | sentAt: '2019-05-01T12:43:17-07:00', 27 | timestamp: 1556739797117, 28 | actualTemperatureC: 8.06, 29 | targetTemperatureC: 21.11, 30 | sentAtUtc: '2019-05-01T19:43:17.117Z', 31 | createdAtUtc: '2019-05-01T19:43:17.117Z', 32 | }, 33 | { 34 | createdAt: '2019-05-01T12:43:27-07:00', 35 | deviceId: '9fd6fd62-0b10-4b5c-a7a1-5995c81db07e', 36 | actualTemperature: 47.25, 37 | targetTemperature: 70, 38 | sentAt: '2019-05-01T12:43:27-07:00', 39 | timestamp: 1556739807117, 40 | actualTemperatureC: 8.47, 41 | targetTemperatureC: 21.11, 42 | sentAtUtc: '2019-05-01T19:43:27.117Z', 43 | createdAtUtc: '2019-05-01T19:43:27.117Z', 44 | }, 45 | ]; 46 | 47 | describe('Index', () => { 48 | beforeEach(() => {}); 49 | 50 | it('should return event messages with Celsius temperature and UTC time if keys exist', (done) => { 51 | event = [ 52 | { 53 | createdAt: '2019-05-01T12:43:07-07:00', 54 | deviceId: '9fd6fd62-0b10-4b5c-a7a1-5995c81db07e', 55 | actualTemperature: 45.75, 56 | targetTemperature: 70, 57 | sentAt: '2019-05-01T12:43:07-07:00', 58 | timestamp: 1556739787113, 59 | }, 60 | { 61 | createdAt: '2019-05-01T12:43:17-07:00', 62 | deviceId: '9fd6fd62-0b10-4b5c-a7a1-5995c81db07e', 63 | actualTemperature: 46.5, 64 | targetTemperature: 70, 65 | sentAt: '2019-05-01T12:43:17-07:00', 66 | timestamp: 1556739797117, 67 | }, 68 | { 69 | createdAt: '2019-05-01T12:43:27-07:00', 70 | deviceId: '9fd6fd62-0b10-4b5c-a7a1-5995c81db07e', 71 | actualTemperature: 47.25, 72 | targetTemperature: 70, 73 | sentAt: '2019-05-01T12:43:27-07:00', 74 | timestamp: 1556739807117, 75 | }, 76 | ]; 77 | 78 | lib 79 | .process(event) 80 | .then(data => { 81 | expect(data).to.deep.equal(successData); 82 | done(); 83 | }) 84 | .catch(err => { 85 | done(err); 86 | }); 87 | }); 88 | }); 89 | -------------------------------------------------------------------------------- /source/services/telemetry/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "smart-product-telemetry", 3 | "description": "A Lambda function for the smart product telemetry transformation", 4 | "main": "index.js", 5 | "author": { 6 | "name": "aws-solutions-builder" 7 | }, 8 | "version": "0.0.1", 9 | "private": true, 10 | "dependencies": { 11 | "moment": "*", 12 | "logger": "file:../../resources/logger/" 13 | }, 14 | "devDependencies": { 15 | "chai": "*", 16 | "sinon": "*", 17 | "sinon-chai": "*", 18 | "mocha": "*", 19 | "npm-run-all": "*", 20 | "proxyquire": "*" 21 | }, 22 | "scripts": { 23 | "pretest": "rm -rf package-lock.json && rm -rf node_modules && npm install", 24 | "test": "mocha lib/*.spec.js", 25 | "prestart": "rm -rf package-lock.json && npm install", 26 | "start": "node index.js", 27 | "build:init": "rm -rf package-lock.json && rm -rf dist && rm -rf node_modules", 28 | "build:zip": "zip -rq smart-product-telemetry-service.zip . -x template.yml", 29 | "build:dist": "mkdir dist && mv smart-product-telemetry-service.zip dist/", 30 | "build": "npm run build:init && npm install --production && npm run build:zip && npm run build:dist" 31 | }, 32 | "bundledDependencies": [ 33 | "moment" 34 | ] 35 | } 36 | --------------------------------------------------------------------------------