├── .DS_Store ├── .dockerignore ├── .eslintrc.json ├── .gitignore ├── .prettierrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── Contributions.md ├── Dockerfile ├── PrivacyPolicy ├── README-DeveloperGuide.md ├── README.md ├── __mocks__ ├── credentialController.js └── instancesController.js ├── babel.config.js ├── default.conf ├── docker-compose.yml ├── jest-setup.js ├── jest-teardown.js ├── jest.config.js ├── license ├── package-lock.json ├── package.json ├── server ├── .DS_Store ├── controllers │ ├── cookieController.js │ ├── credentialController.js │ ├── ec2 │ │ ├── cpuMetricsController.js │ │ ├── instancesController.js │ │ └── networkMetricsController.js │ ├── lambda │ │ ├── lambdaLogsController.js │ │ ├── lambdaMetricsController.js │ │ └── listLambdasController.js │ ├── rds │ │ └── rdsMetricsController.js │ ├── sessionController.js │ └── userController.js ├── models │ ├── sessionModel.js │ └── userModel.js └── server.js ├── src ├── App.jsx ├── Data.js ├── _variables.scss ├── componentStyling │ ├── EC2ChartStyling.scss │ ├── Footer.scss │ ├── LambdaChartStyling.scss │ ├── LandingPage.scss │ ├── Login.scss │ ├── Navbar.scss │ ├── PrivacyPolicy.scss │ ├── Settings.scss │ └── Signup.scss ├── components │ ├── AreaChartOptions.js │ ├── CPUCreditBalanceChart.jsx │ ├── CPUCreditUsageChart.jsx │ ├── CPUSurplusCreditBalanceChart.jsx │ ├── CPUUtilizationChart.jsx │ ├── ColorGenerator.js │ ├── DurationChart.jsx │ ├── ErrorsChart.jsx │ ├── Footer.jsx │ ├── InvocationsChart.jsx │ ├── LambdaLogs.jsx │ ├── LandingPage.jsx │ ├── LineChartOptions.js │ ├── Login.jsx │ ├── Navbar.jsx │ ├── NetworkInChart.jsx │ ├── NetworkOutChart.jsx │ ├── PrivacyPolicy.jsx │ ├── Settings.jsx │ ├── Signup.jsx │ └── ThrottlesChart.jsx ├── containerStyling │ ├── EC2ChartContainer.scss │ ├── LambdaChartContainer.scss │ ├── MainContainer.scss │ └── swirlingclouds.png ├── containers │ ├── EC2ChartContainer.jsx │ ├── LambdaChartContainer.jsx │ └── MainContainer.jsx ├── index.html ├── index.js └── styles.scss ├── test └── Backend │ ├── cookieController.test.js │ ├── getCredentials.test.js │ ├── instancesController.test.js │ ├── jest-config-commands.js │ ├── sessionController.test.js │ ├── stsClient.test.js │ └── userController.test.js └── webpack.config.js /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/cloudband/8d8ed48e903bb679a6d0ccef53d42c0e7d0d3f50/.DS_Store -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | readme.md 3 | .git 4 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "ignorePatterns": ["**/test", "**/__tests__"], 4 | "env": { 5 | "node": true, 6 | "browser": true, 7 | "es2021": true 8 | }, 9 | "plugins": ["react"], 10 | "extends": ["eslint:recommended", "plugin:react/recommended"], 11 | "parserOptions": { 12 | "sourceType": "module", 13 | "ecmaFeatures": { 14 | "jsx": true 15 | } 16 | }, 17 | "rules": { 18 | "indent": ["warn", 2], 19 | "no-unused-vars": ["off", { "vars": "local" }], 20 | "no-case-declarations": "off", 21 | "prefer-const": "warn", 22 | "quotes": ["warn", "single"], 23 | "react/prop-types": "off", 24 | "semi": ["warn", "always"], 25 | "space-infix-ops": "warn" 26 | }, 27 | "settings": { 28 | "react": { "version": "detect" } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "tabWidth": 2, 4 | "semi": true, 5 | "singleQuote": true 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | } -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | email. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /Contributions.md: -------------------------------------------------------------------------------- 1 | # Contributing to Cloudband 2 | 3 | First of all, thank you for your contribution. 4 | 5 | ## Reporting Bugs 6 | 7 | All code changes happen through Github Pull Requests and we actively welcome them. To submit your pull request, follow the steps below: 8 | 9 | ## Pull Requests 10 | 11 | 1. Fork and clone the repository. 12 | 2. Create your feature branch. (git checkout -b [my-new-feature]) 13 | 3. Make sure to cover your code with tests and that your code is linted in accordance with our linting specifications (see coding style below). 14 | 4. Commit your changes locally (git commit -m 'Added some feature') and then push to your remote repository. 15 | 5. Submit a pull request to the _development_ branch, including a description of the work done in your pull request. 16 | 17 | Note: Any contributions you make will be under the MIT Software License and your submissions are understood to be under the same that covers the project. Please reach out to the team if you have any questions. 18 | 19 | ## Issues 20 | 21 | We use GitHub issues to track public bugs. Please ensure your description is clear and has sufficient instructions to be able to reproduce the issue. 22 | 23 | ## Coding Style 24 | 25 | 2 spaces for indentation rather than tabs 26 | 80 character line length 27 | Run npm run lint to comform to our lint rules 28 | 29 | ## License 30 | 31 | By contributing, you agree that your contributions will be licensed under Cloudband's MIT License. 32 | 33 | ### References 34 | 35 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md) 36 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:19.4.0 2 | 3 | WORKDIR /cloudband 4 | 5 | COPY package.json /cloudband/ 6 | COPY package-lock.json /cloudband/ 7 | 8 | RUN npm ci 9 | 10 | 11 | RUN ["apt-get", "update"] 12 | RUN ["apt-get", "install", "-y", "vim"] 13 | 14 | COPY .env /cloudband/ 15 | COPY webpack.config.js /cloudband/ 16 | COPY src /cloudband/src/ 17 | COPY server /cloudband/server/ 18 | 19 | RUN ["npm", "run", "build"] 20 | 21 | EXPOSE 3000 22 | 23 | CMD ["npm", "run", "start"] 24 | #CMD ["cross-env", "NODE_ENV=production", "node", "server/server.js"] 25 | -------------------------------------------------------------------------------- /PrivacyPolicy: -------------------------------------------------------------------------------- 1 | Privacy Policy 2 | 3 | Cloudband is committed to protecting the privacy of our users. Our web app, Cloudband, is designed to provide AWS developers with the ability to visualize different AWS resource metrics in real time. We do not collect any personal information from our users. 4 | 5 | We understand the importance of user privacy, and we do not collect, store, or share any personal information from our users. Our app does not use cookies, tracking pixels, or any other form of data collection. 6 | 7 | Our app may contain links to third-party websites or services. We are not responsible for the privacy practices or content of these third-party websites or services. We encourage users to read the privacy policies of any third-party websites or services before providing any personal information. 8 | 9 | If you have any questions or concerns about our privacy policy, please contact us at [enter-email-here]. 10 | -------------------------------------------------------------------------------- /README-DeveloperGuide.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 |
8 | 9 | 10 |

Cloudband - Developer's Guide

11 | 12 |
13 | 14 |
15 | Table of Contents 16 |
    17 |
  1. AWS Account Creation
  2. 18 |
  3. IAM Setup
  4. 19 |
  5. Streamlining the User Sign-Up Experience
  6. 20 |
  7. Template Creation and Storage
  8. 21 |
  9. Template Storage in an S3 Bucket
  10. 22 |
  11. Stack Creation Link
  12. 23 |
  13. Finish Setup
  14. 24 |
25 |
26 | 27 | 28 | ## AWS Account Creation 29 |

An active AWS account is required in order to make full use of Cloudband’s features. It is highly suggested to make a new AWS account specifically for Cloudband if your use case is anything more than demoing the application.

30 | 31 | ## IAM Setup 32 | 33 |

In order for the Cloudband application to pull a user’s metrics, we will need to create an IAM user to programmatically access that user’s AWS.

34 | 35 |

On your AWS account, do the following:

36 | 37 |

1. Create an IAM user called cloudband-user with programmatic access (no need for this user to be able to sign in to the AWS console)

38 | 39 |

2. Attach the following policies directly to cloudband-user:

40 | 41 | 51 | 52 |

3. Create an access key for cloudband-user. Keep the access key and secret access key in your records - this will be used in the .env file in the Cloudband application.

53 | 54 |

(back to top)

55 | 56 | 57 | ## Streamlining the User Sign-Up Experience 58 |

If a user wants to allow cloudband-user to pull metrics from their account, they must create a role on their account. This role will specify that cloudband-user can access their account as well as specifying exactly what cloudband-user can do once they have gained access. An AWS Service called CloudFormation will be used to automate the creation of this role on a user’s account and streamline the user sign-up experience.

59 | 60 | 61 | ## Template Creation and Storage 62 |

In order to allow the use of CloudFormation to automate the creation of a role, we must first provide the instruction of what that role can do. This comes in the form of a template. Create a yaml file (extension is .yml) with the following content (replacing the Principal / AWS ARN with the cloudband-user’s ARN & replacing the sts:External Id with the external ID that you generate via https://www.uuidgenerator.net/):

63 | 64 |
65 | 66 | ``` 67 | Description: 'CloudFormation stack' 68 | Resources: 69 | CloudbandDelegationRole: 70 | Type: 'AWS::IAM::Role' 71 | Properties: 72 | AssumeRolePolicyDocument: 73 | Version: 2012-10-17 74 | Statement: 75 | - Effect: Allow 76 | Principal: 77 | AWS: 78 | - arn:aws:iam::635533801215:user/cloudband-user 79 | Action: 80 | - 'sts:AssumeRole' 81 | Condition: 82 | StringEquals: 83 | 'sts:ExternalId': 92a98196-9090-11ed-a1eb-0242ac120002 84 | Path: / 85 | RoleName: CloudbandDelegationRole 86 | Policies: 87 | - PolicyName: Resources 88 | PolicyDocument: 89 | Version: 2012-10-17 90 | Statement: 91 | - Effect: Allow 92 | Action: 'apigateway:GET' 93 | Resource: '*' 94 | - Effect: Allow 95 | Action: 'apigateway:HEAD' 96 | Resource: '*' 97 | - Effect: Allow 98 | Action: 'apigateway:OPTIONS' 99 | Resource: '*' 100 | - Effect: Allow 101 | Action: 'appsync:get*' 102 | Resource: '*' 103 | - Effect: Allow 104 | Action: 'appsync:list*' 105 | Resource: '*' 106 | - Effect: Allow 107 | Action: 'athena:list*' 108 | Resource: '*' 109 | - Effect: Allow 110 | Action: 'athena:batchGet*' 111 | Resource: '*' 112 | - Effect: Allow 113 | Action: 'athena:getNamedQuery' 114 | Resource: '*' 115 | - Effect: Allow 116 | Action: 'athena:getQueryExecution' 117 | Resource: '*' 118 | - Effect: Allow 119 | Action: 'athena:getQueryExecution' 120 | Resource: '*' 121 | - Effect: Allow 122 | Action: 'autoscaling:describe*' 123 | Resource: '*' 124 | - Effect: Allow 125 | Action: 'batch:describe*' 126 | Resource: '*' 127 | - Effect: Allow 128 | Action: 'cloudformation:describe*' 129 | Resource: '*' 130 | - Effect: Allow 131 | Action: 'cloudformation:get*' 132 | Resource: '*' 133 | - Effect: Allow 134 | Action: 'cloudformation:list*' 135 | Resource: '*' 136 | - Effect: Allow 137 | Action: 'cloudfront:get*' 138 | Resource: '*' 139 | - Effect: Allow 140 | Action: 'cloudfront:list*' 141 | Resource: '*' 142 | - Effect: Allow 143 | Action: 'cloudwatch:describe*' 144 | Resource: '*' 145 | - Effect: Allow 146 | Action: 'cloudwatch:list*' 147 | Resource: '*' 148 | - Effect: Allow 149 | Action: 'dax:describe*' 150 | Resource: '*' 151 | - Effect: Allow 152 | Action: 'dax:list*' 153 | Resource: '*' 154 | - Effect: Allow 155 | Action: 'discovery:describe*' 156 | Resource: '*' 157 | - Effect: Allow 158 | Action: 'discovery:list*' 159 | Resource: '*' 160 | - Effect: Allow 161 | Action: 'dynamodb:describe*' 162 | Resource: '*' 163 | - Effect: Allow 164 | Action: 'dynamodb:list*' 165 | Resource: '*' 166 | - Effect: Allow 167 | Action: 'ec2:describe*' 168 | Resource: '*' 169 | - Effect: Allow 170 | Action: 'ecs:describe*' 171 | Resource: '*' 172 | - Effect: Allow 173 | Action: 'ecs:list*' 174 | Resource: '*' 175 | - Effect: Allow 176 | Action: 'ecr:describe*' 177 | Resource: '*' 178 | - Effect: Allow 179 | Action: 'ecr:get*' 180 | Resource: '*' 181 | - Effect: Allow 182 | Action: 'ecr:list*' 183 | Resource: '*' 184 | - Effect: Allow 185 | Action: 'eks:describe*' 186 | Resource: '*' 187 | - Effect: Allow 188 | Action: 'eks:list*' 189 | Resource: '*' 190 | - Effect: Allow 191 | Action: 'elasticache:describe*' 192 | Resource: '*' 193 | - Effect: Allow 194 | Action: 'elasticache:list*' 195 | Resource: '*' 196 | - Effect: Allow 197 | Action: 'elasticloadbalancing:describe*' 198 | Resource: '*' 199 | - Effect: Allow 200 | Action: 'es:describe*' 201 | Resource: '*' 202 | - Effect: Allow 203 | Action: 'es:list*' 204 | Resource: '*' 205 | - Effect: Allow 206 | Action: 'events:describe*' 207 | Resource: '*' 208 | - Effect: Allow 209 | Action: 'events:list*' 210 | Resource: '*' 211 | - Effect: Allow 212 | Action: 'firehose:describe*' 213 | Resource: '*' 214 | - Effect: Allow 215 | Action: 'firehose:list*' 216 | Resource: '*' 217 | - Effect: Allow 218 | Action: 'glacier:describe*' 219 | Resource: '*' 220 | - Effect: Allow 221 | Action: 'glacier:getDataRetrievalPolicy' 222 | Resource: '*' 223 | - Effect: Allow 224 | Action: 'glacier:getVaultAccessPolicy' 225 | Resource: '*' 226 | - Effect: Allow 227 | Action: 'glacier:getVaultLock' 228 | Resource: '*' 229 | - Effect: Allow 230 | Action: 'glacier:getVaultNotifications' 231 | Resource: '*' 232 | - Effect: Allow 233 | Action: 'glacier:listTagsForVault' 234 | Resource: '*' 235 | - Effect: Allow 236 | Action: 'glacier:listVaults' 237 | Resource: '*' 238 | - Effect: Allow 239 | Action: 'iot:describe*' 240 | Resource: '*' 241 | - Effect: Allow 242 | Action: 'iot:get*' 243 | Resource: '*' 244 | - Effect: Allow 245 | Action: 'iot:list*' 246 | Resource: '*' 247 | - Effect: Allow 248 | Action: 'kinesis:describe*' 249 | Resource: '*' 250 | - Effect: Allow 251 | Action: 'kinesis:list*' 252 | Resource: '*' 253 | - Effect: Allow 254 | Action: 'kinesisanalytics:describe*' 255 | Resource: '*' 256 | - Effect: Allow 257 | Action: 'kinesisanalytics:list*' 258 | Resource: '*' 259 | - Effect: Allow 260 | Action: 'lambda:listFunctions' 261 | Resource: '*' 262 | - Effect: Allow 263 | Action: 'lambda:listTags' 264 | Resource: '*' 265 | - Effect: Allow 266 | Action: 'rds:describe*' 267 | Resource: '*' 268 | - Effect: Allow 269 | Action: 'rds:list*' 270 | Resource: '*' 271 | - Effect: Allow 272 | Action: 'route53:list*' 273 | Resource: '*' 274 | - Effect: Allow 275 | Action: 'route53:get*' 276 | Resource: '*' 277 | - Effect: Allow 278 | Action: 's3:getBucket*' 279 | Resource: '*' 280 | - Effect: Allow 281 | Action: 's3:list*' 282 | Resource: '*' 283 | - Effect: Allow 284 | Action: 'sdb:domainMetadata' 285 | Resource: '*' 286 | - Effect: Allow 287 | Action: 'sdb:get*' 288 | Resource: '*' 289 | - Effect: Allow 290 | Action: 'sdb:list*' 291 | Resource: '*' 292 | - Effect: Allow 293 | Action: 'sns:get*' 294 | Resource: '*' 295 | - Effect: Allow 296 | Action: 'sns:list*' 297 | Resource: '*' 298 | - Effect: Allow 299 | Action: 'sqs:get*' 300 | Resource: '*' 301 | - Effect: Allow 302 | Action: 'sqs:list*' 303 | Resource: '*' 304 | - Effect: Allow 305 | Action: 'states:describe*' 306 | Resource: '*' 307 | - Effect: Allow 308 | Action: 'states:get*' 309 | Resource: '*' 310 | - Effect: Allow 311 | Action: 'states:list*' 312 | Resource: '*' 313 | - Effect: Allow 314 | Action: 'tag:get*' 315 | Resource: '*' 316 | - PolicyName: Logs 317 | PolicyDocument: 318 | Version: 2012-10-17 319 | Statement: 320 | - Effect: Allow 321 | Action: 'logs:deleteSubscriptionFilter' 322 | Resource: '*' 323 | - Effect: Allow 324 | Action: 'logs:describeLogStreams' 325 | Resource: '*' 326 | - Effect: Allow 327 | Action: 'logs:describeSubscriptionFilters' 328 | Resource: '*' 329 | - Effect: Allow 330 | Action: 'logs:filterLogEvents' 331 | Resource: '*' 332 | - Effect: Allow 333 | Action: 'logs:putSubscriptionFilter' 334 | Resource: '*' 335 | - Effect: Allow 336 | Action: 'logs:startQuery' 337 | Resource: '*' 338 | - Effect: Allow 339 | Action: 'logs:stopQuery' 340 | Resource: '*' 341 | - PolicyName: Metrics 342 | PolicyDocument: 343 | Version: 2012-10-17 344 | Statement: 345 | - Effect: Allow 346 | Action: 'cloudwatch:get*' 347 | Resource: '*' 348 | - PolicyName: Traces 349 | PolicyDocument: 350 | Version: 2012-10-17 351 | Statement: 352 | - Effect: Allow 353 | Action: 'xray:batch*' 354 | Resource: '*' 355 | - Effect: Allow 356 | Action: 'xray:get*' 357 | Resource: '*' 358 | 359 | Parameters: 360 | ExternalId: 361 | Description: 'The external ID for the Cloudband delegation role' 362 | Type: String 363 | 364 | Outputs: 365 | Version: 366 | Description: Cloudband CF template version 367 | Value: 2020-02-06 368 | CloudbandDelegationRoleArn: 369 | Description: 'The ARN for the Cloudband delegation role' 370 | Value: !GetAtt 371 | - CloudbandDelegationRole 372 | - Arn 373 | ``` 374 | 375 |
376 | 377 | 378 | ## Template Storage in an S3 Bucket 379 | 380 |

The template must be stored on our AWS account. The simplest way to do this is to create an S3 bucket and upload the template yaml file with the following steps:

381 | 382 |
    383 |
  1. Navigate to the AWS Service called S3.
  2. 384 |
  3. Select Create Bucket.
  4. 385 |
  5. Name the bucket "cloudband".
  6. 386 |
  7. Unselect "Block all public access".
  8. 387 |
  9. Create bucket.
  10. 388 |
  11. Add to bucket policy the text below step 8.
  12. 389 |
  13. Click upload and upload your created yaml file template.
  14. 390 |
  15. In the list of objects in your S3 bucket, check off the Cloudband Template and click Copy URL.
  16. 391 |
392 | 393 | ``` 394 | 395 | { 396 | "Version": "2008-10-17", 397 | "Statement": [ 398 | { 399 | "Sid": "AllowPublicRead", 400 | "Effect": "Allow", 401 | "Principal": { 402 | "AWS": "*" 403 | }, 404 | "Action": "s3:GetObject", 405 | "Resource": "arn:aws:s3:::cloudband/*" 406 | } 407 | ] 408 | } 409 | 410 | ``` 411 | 412 | 413 | ## Stack Creation Link: 414 | 415 |

Use the following link to allow your user to automatically create a stack. This link can be attached to the “Create New Stack” button found in the codebase (in InputToken.jsx - line 42). Add in your template URL, region, and external id into the link to ensure the stack is properly configured.

416 | 417 | ``` 418 | 419 | https://console.aws.amazon.com/cloudformation/home?region=#/stacks/quickcreate?stackName=cloudband-permission¶m_ExternalId=&templateURL= 420 | 421 | ``` 422 | 423 | 424 | ## Finish Setup: 425 | 426 | Continue following the main [README](https://github.com/oslabs-beta/cloudband/blob/dev/README.md). 427 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 |
8 | 9 | 10 |

Cloudband

11 | 12 | 13 | 14 | [![Contributors][contributors-shield]][contributors-url] 15 | [![Stargazers][stars-shield]][stars-url] 16 | [![MIT License][license-shield]][license-url] 17 | 18 |
19 | 20 |
21 | Table of Contents 22 |
    23 |
  1. About
  2. 24 |
  3. Getting Started
  4. 25 |
  5. Monitoring Features
  6. 26 |
  7. Contributing
  8. 27 |
  9. Built With
  10. 28 |
  11. License
  12. 29 |
  13. Authors
  14. 30 |
  15. Authors
  16. 31 |
32 |
33 | 34 | 35 | ## About Cloudband 36 |

37 | Easy access and straightforward, intuitive visualization of AWS resource metrics are critical to AWS developers. It is intimidating to navigate the extensive AWS documentation and many hundreds of services, and challenging to quickly visualize metrics for these resources.

38 | 39 |

Our solution is a web application that provides comprehensive charts for direct visualization of a wide range of available EC2 metrics and Lambda functions, for those who use Lambda functions to process lifecycle events from Amazon Elastic Compute Cloud and their related EC2 resources

40 | 41 | Project Links: [Github](https://github.com/oslabs-beta/cloudband) | [Linkedin](https://www.linkedin.com/company/cloudbandec37) | [Press](will write this later this week) 42 | 43 | 44 | ## Getting started (User Guide) 45 | 🛠️ 46 | 47 | Visit our [Website](https://www.cloud-band.io) 48 | 49 | 1. Existing user? You can log in using your email and password. 50 | 51 | 2. For new users, click "Create New Stack. 52 | 53 | 3. Follow the link and sign in as an IAM user. Follow the instructions on the How To page or watch our How To Create a New Stack Video Tutorial. 54 | 55 | 4. Copy and paste the unique ARN outputted from the prior step. 56 | 57 | 5. Input the ARN into the Enter ARN Here field 58 | 59 |

(back to top)

60 | 61 | 62 | ## Getting started (Contributor Guide) 63 | 🛠️ 64 | 65 | 1. Fork and clone this repository. 66 | 67 | 2. Follow the [Developer Setup Guide](https://github.com/oslabs-beta/cloudband/blob/dev/README-DeveloperGuide.md). 68 | 69 | 3. Using the pre-made Dockerfile, build an image and tag it as "cloudband" (must use this name). 70 | 71 | 4. On Dockerhub, pull the mongo image. 72 | 73 | 5. Create an .env file with the following information, replacing the AWS_ACCESS_KEY and AWS_SECRET_KEY for your cloudband-user account's keys. 74 | 75 | ``` 76 | 77 | # cloudband's keys 78 | AWS_ACCESS_KEY=******************** 79 | AWS_SECRET_KEY=****************************************** 80 | 81 | #MONGO URI 82 | MONGO_URI_=mongodb://mongodb:27017/cloudband 83 | 84 | export AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY 85 | export AWS_SECRET_ACCESS_KEY=$AWS_SECRET_KEY 86 | export AWS_DEFAULT_REGION=us-east-1 87 | export MONGO_URI=$MONGO_URI_ 88 | 89 | ``` 90 | 91 | 92 | 5. Run both images via the docker-compose YAML file. 93 | 94 | 6. Go to your localhost:3000 to see the application. 95 | 96 |

(back to top)

97 | 98 | ## Monitoring Features: 99 | 100 | 1. On the landing page, users can select the type of EC2 resources they'd like to monitor. Once selected, users can view the metrics for the selected EC2 resources. 101 | 102 | 103 | 104 | 105 | 106 | 2. By clicking on the instance id, you can easily toggle an instance's data on or off to view only what you want to see. 107 | 108 | 109 | 110 | 111 | 112 | 3. The Cloudband interface allows you to seamlessly switch between your EC2 and Lambda metrics for a more convenient way to view your data. 113 | 114 | 115 | 116 | 117 |

(back to top)

118 | 119 | 120 | ## Contributing: 121 | 122 | Have a suggestion? Found a bug? Want to make Cloudband even more amazing? Please fork the repo and create a pull request. 123 | Don't forget to give the project a star ⭐️! Thanks again! 124 | 125 | 1. Fork Cloudband 126 | 2. Clone to your local machine 127 | ``` 128 | git clone 129 | ``` 130 | 3. Create your Feature Branch 131 | ``` 132 | git checkout -b feature/AmazingFeature 133 | ``` 134 | 135 | 4. Commit your Changes 136 | ``` 137 | git commit -m 'Add some AmazingFeature' 138 | ``` 139 | 5. Push to the Branch 140 | ``` 141 | git push origin feature/AmazingFeature 142 | ``` 143 | 10. Open a Pull Request 144 | 145 | 146 |

(back to top)

147 | 148 | 149 | ## Built with 150 | 💻 151 | 152 | - [React](https://reactjs.org/) 153 | - [NodeJS](https://nodejs.org/en/) 154 | - [Express](https://expressjs.com/) 155 | - [MongoDB](https://www.mongodb.com/) 156 | - [Mongoose](https://mongoosejs.com/) 157 | - [Material UI](https://mui.com/) 158 | - [Chart.js](https://www.chartjs.org/) 159 | - [Jest](https://jestjs.io/) 160 | 161 |

(back to top)

162 | 163 | 164 | ## License 165 | 166 | Distributed under the MIT License. 167 | 168 |

(back to top)

169 | 170 | 171 | ## Authors: 172 | 173 | Caroline Kimball - [Github](https://github.com/kimballcaroline) || [Linkedin](www.linkedin.com/in/kimballcaroline) 174 | 175 | Camille Salter - [Github](https://github.com/CamSalter) || [Linkedin](www.linkedin.com/in/camille-salter) 176 | 177 | Greg Jenner - [Github](https://github.com/gregjenner) || [Linkedin](www.linkedin.com/in/greg-o-jenner) 178 | 179 | John Donovan - [Github](https://github.com/jodonovan845) || [Linkedin]() 180 | 181 | Tomas Kim - [Github](https://github.com/tk0885) || [Linkedin](www.linkedin.com/in/tomasjskim) 182 | 183 | 184 | Project Links: [Github](https://github.com/oslabs-beta/cloudband) || [Linkedin](https://www.linkedin.com/our-cloudband-project) || [Medium](https://medium.com/cloudbandwriteup) 185 | 186 |

(back to top)

187 | 188 | 189 | ## Acknowledgments 190 | 191 | * [ReactHooks](https://reactjs.org/docs/hooks-intro.html) 192 | * [ReactRouter](https://reactrouter.com/) 193 | * [Axios](https://www.npmjs.com/package/axios) 194 | * [Webpack](https://webpack.js.org/) 195 | * [AWS SDK](https://aws.amazon.com/sdk-for-javascript/) 196 | * [AWS CloudFormation](https://aws.amazon.com/cloudformation/) 197 | * [AWS IAM](https://aws.amazon.com/iam/) 198 | * [AWS Lambda](https://aws.amazon.com/lambda/) 199 | * [AWS EC2](https://aws.amazon.com/ec2/) 200 | * [AWS Cloudwatch](https://aws.amazon.com/cloudwatch/) 201 | * [AWS STS](https://aws.amazon.com/sts/) 202 | * [AWS CloudFormation](https://aws.amazon.com/cloudformation/) 203 | * [Javascript](https://www.javascript.com/) 204 | * [HTML](https://html.com/) 205 | * [CSS](https://www.w3schools.com/css/) 206 | * [Babel](https://babeljs.io/) 207 | * [Docker](https://www.docker.com/) 208 | * [NGINX] (https://nginx.org/en/) 209 | 210 |

(back to top)

211 | 212 | 213 | 214 | 215 | [contributors-shield]: https://img.shields.io/github/contributors/oslabs-beta/cloudband.svg?style=for-the-badge 216 | [contributors-url]: https://github.com/oslabs-beta/cloudband/graphs/contributors 217 | [stars-shield]: https://img.shields.io/github/stars/oslabs-beta/cloudband.svg?style=for-the-badge 218 | [stars-url]: https://github.com/oslabs-beta/cloudband/stargazers 219 | [license-shield]: https://img.shields.io/github/license/oslabs-beta/cloudband.svg?style=for-the-badge 220 | [license-url]: https://github.com/oslabs-beta/cloudband/blob/master/LICENSE.txt 221 | 222 | 223 | -------------------------------------------------------------------------------- /__mocks__/credentialController.js: -------------------------------------------------------------------------------- 1 | //create and invoke the StsClient mock 2 | const STSClient = jest.fn(); 3 | 4 | //assign the credentials returned by the mock to the 'res.locals.credentials' object. 5 | const credentialController = { 6 | getCredentials: jest.fn().mockImplementation(async (req, res, next) => { 7 | const STSClient = jest.fn().mockImplementation(() => { 8 | return { 9 | send: jest.fn().mockResolvedValue({ 10 | Credentials: { 11 | accessKeyId: 'mocked-access-key-id', 12 | secretAccessKey: 'mocked-secret-access-key', 13 | sessionToken: 'mocked-session-token', 14 | }, 15 | }), 16 | }; 17 | }); 18 | 19 | const response = await STSClient().send(); 20 | res.locals.credentials = response.Credentials; 21 | return next(); 22 | }), 23 | }; 24 | 25 | module.exports = credentialController; 26 | 27 | 28 | 29 | 30 | // const STSClient = jest.fn().mockImplementation(() => { 31 | // return { 32 | // send: jest.fn().mockResolvedValue({ 33 | // Credentials: { 34 | // accessKeyId: 'mocked-access-key-id', 35 | // secretAccessKey: 'mocked-secret-access-key', 36 | // sessionToken: 'mocked-session-token', 37 | // }, 38 | // }), 39 | // }; 40 | // }); 41 | 42 | // const credentialController = { 43 | // getCredentials: jest.fn().mockImplementation(async (req, res, next) => { 44 | // // res.locals.credentials = { 45 | // // accessKeyId: 'mocked-access-key-id', 46 | // // secretAccessKey: 'mocked-secret-access-key', 47 | // // sessionToken: 'mocked-session-token' 48 | // // }; 49 | // res.locals.credentials = STSClient.send().Credentials; 50 | // return next(); 51 | // }), 52 | // }; 53 | 54 | // module.exports = credentialController; 55 | -------------------------------------------------------------------------------- /__mocks__/instancesController.js: -------------------------------------------------------------------------------- 1 | const { EC2Client, DescribeInstancesCommand } = jest.genMockFromModule('@aws-sdk/client-ec2'); 2 | 3 | module.exports = { 4 | getInstances: jest.fn().mockImplementation(async (req, res, next) => { 5 | const info = { 6 | region: 'us-east-1', 7 | credentials: res.locals.credentials, 8 | }; 9 | 10 | const ec2Client = new EC2Client(info); 11 | try { 12 | const data = await ec2Client.send(DescribeInstancesCommand({})); 13 | const instances = data.Reservations; 14 | const instanceIds = instances.map((instance) => { 15 | return instance.Instances[0].InstanceId; 16 | }); 17 | 18 | res.locals.ec2Instances = { 19 | instances: instanceIds, 20 | }; 21 | return next(); 22 | } catch (err) { 23 | return next(err); 24 | } 25 | }), 26 | }; 27 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | // babel.config.js 2 | module.exports = {presets: ['@babel/preset-env']} -------------------------------------------------------------------------------- /default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name _; 4 | location / { 5 | proxy_pass http://cloudband:3000; 6 | } 7 | location /api { 8 | proxy_pass http://cloudband:3000; 9 | } 10 | location /mongo { 11 | proxy_pass http://mongo:27017; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | mongodb: 4 | image: mongo 5 | container_name: mongo 6 | hostname: mongodb 7 | ports: 8 | - 27017:27017 9 | network_mode: cloudband_network 10 | cloudband: 11 | image: cloudband 12 | container_name: cloudband 13 | ports: 14 | - 3000:3000 15 | network_mode: cloudband_network 16 | command: npm run start 17 | volumes: 18 | - cloudband_volume:/cloudband 19 | - cloudband_bugcorrect_volume:/cloudband/bugcorrect 20 | - cloudband_final_volume:/cloudband/final 21 | nginx: 22 | image: nginx 23 | container_name: nginx-proxy 24 | ports: 25 | - 80:80 26 | dns: 27 | - 8.8.8.8 28 | - 8.8.4.4 29 | volumes: 30 | - ./default.conf:/etc/nginx/conf.d/default.conf 31 | command: nginx -g "daemon off;" 32 | depends_on: 33 | - cloudband 34 | network_mode: cloudband_network 35 | volumes: 36 | cloudband_volume: 37 | cloudband_bugcorrect_volume: 38 | cloudband_final_volume: 39 | -------------------------------------------------------------------------------- /jest-setup.js: -------------------------------------------------------------------------------- 1 | import regeneratorRuntime from 'regenerator-runtime'; 2 | 3 | module.exports = () => { 4 | global.testServer = require('./server'); 5 | }; 6 | -------------------------------------------------------------------------------- /jest-teardown.js: -------------------------------------------------------------------------------- 1 | module.exports = async (globalConfig) => { 2 | testServer.close(); 3 | }; 4 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | '^.+\\.js$': 'babel-jest', 4 | }, 5 | }; 6 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloudband", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "webpack.config.js", 6 | "scripts": { 7 | "frontend": "NODE_ENV=development webpack-dev-server --open", 8 | "start": "NODE_ENV=production nodemon server/server.js", 9 | "dev": "concurrently \"cross-env NODE_ENV=development webpack-dev-server --open --hot --progress --color \" \"nodemon ./server/server.js\"", 10 | "build": "NODE_ENV=production webpack", 11 | "test": "jest --silent --detectOpenHandles --forceExit" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/oslabs-beta/cloudband.git" 16 | }, 17 | "keywords": [], 18 | "author": "Greg Jenner, Caroline Kimball, Tomas Kim, Camille Salter, & John Donovan", 19 | "license": "ISC", 20 | "bugs": { 21 | "url": "https://github.com/oslabs-beta/cloudband/issues" 22 | }, 23 | "homepage": "https://github.com/oslabs-beta/cloudband#readme", 24 | "devDependencies": { 25 | "@babel/core": "^7.20.12", 26 | "@babel/preset-env": "^7.20.2", 27 | "babel-loader": "^9.1.0", 28 | "concurrently": "^7.6.0", 29 | "css-loader": "^6.7.3", 30 | "html-loader": "^4.2.0", 31 | "html-webpack-plugin": "^5.5.0", 32 | "jest": "^29.3.1", 33 | "nodemon": "^2.0.20", 34 | "path": "^0.12.7", 35 | "postcss": "^8.4.20", 36 | "postcss-loader": "^7.0.2", 37 | "sass": "^1.57.1", 38 | "sass-loader": "^13.2.0", 39 | "style-loader": "^3.3.1", 40 | "supertest": "^6.3.3", 41 | "webpack": "^5.75.0", 42 | "webpack-cli": "^5.0.1", 43 | "webpack-dev-server": "^4.11.1" 44 | }, 45 | "dependencies": { 46 | "@aws-cdk/aws-cloudformation": "^1.187.0", 47 | "@aws-sdk/client-cloudwatch": "^3.254.0", 48 | "@aws-sdk/client-cloudwatch-logs": "^3.256.0", 49 | "@aws-sdk/client-ec2": "^3.257.0", 50 | "@aws-sdk/client-lambda": "^3.252.0", 51 | "@aws-sdk/client-sts": "^3.245.0", 52 | "@babel/preset-react": "^7.18.6", 53 | "@emotion/react": "^11.10.5", 54 | "@emotion/styled": "^11.10.5", 55 | "@mui/icons-material": "^5.11.0", 56 | "@mui/material": "^5.11.5", 57 | "aws-cdk-lib": "^2.59.0", 58 | "aws-sdk": "^2.1301.0", 59 | "axios": "^1.2.2", 60 | "babel-jest": "^29.3.1", 61 | "bcrypt": "^5.1.0", 62 | "bcryptjs": "^2.4.3", 63 | "chart.js": "^4.1.1", 64 | "cookie-parser": "^1.4.6", 65 | "cors": "^2.8.5", 66 | "cross-env": "^7.0.3", 67 | "dotenv": "^16.0.3", 68 | "express": "^4.18.2", 69 | "mongoose": "^6.8.3", 70 | "node": "^19.3.0", 71 | "react": "^18.2.0", 72 | "react-chartjs-2": "^5.1.0", 73 | "react-dom": "^18.2.0", 74 | "react-icons": "^4.7.1", 75 | "react-router-dom": "^6.7.0", 76 | "react-switch": "^7.0.0", 77 | "regenerator-runtime": "^0.13.11" 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /server/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/cloudband/8d8ed48e903bb679a6d0ccef53d42c0e7d0d3f50/server/.DS_Store -------------------------------------------------------------------------------- /server/controllers/cookieController.js: -------------------------------------------------------------------------------- 1 | //declare a cookieController object 2 | const cookieController = {}; 3 | 4 | //set the SSID cookie based on user's _id in the database 5 | cookieController.setSSIDCookie = async (req, res, next) => { 6 | try { 7 | const userId = res.locals.newUser._id; 8 | res.cookie('ssid', userId, { 9 | httpOnly: true, 10 | }); 11 | res.locals.ssidCookie = userId; 12 | return next(); 13 | } catch (err) { 14 | next({ 15 | log: `Error in cookieController.setSSIDCookie. Details: ${err}`, 16 | message: { err: 'An error occurred in cookieController.setSSIDCookie' }, 17 | }); 18 | } 19 | }; 20 | 21 | module.exports = cookieController; 22 | -------------------------------------------------------------------------------- /server/controllers/credentialController.js: -------------------------------------------------------------------------------- 1 | const { STSClient, AssumeRoleCommand } = require('@aws-sdk/client-sts'); 2 | const dotenv = require('dotenv'); 3 | dotenv.config(); 4 | 5 | const credentialController = {}; 6 | //declare a constant variable called credentials, which will be used to call the AWS STS API, passing in the access key id and secret access key of the cloudband account 7 | const credentials = { 8 | accessKeyId: process.env.AWS_ACCESS_KEY, 9 | secretAccessKey: process.env.AWS_SECRET_KEY, 10 | }; 11 | 12 | //get user's access key id and secret access key from AWS 13 | credentialController.getCredentials = async (req, res, next) => { 14 | const { arn, region } = req.query; 15 | const info = { 16 | RoleArn: arn, 17 | RoleSessionName: 'CloudbandRoleSession', 18 | DurationSeconds: 900, 19 | ExternalId: '92a98196-9090-11ed-a1eb-0242ac120002', 20 | }; 21 | 22 | //create new STS client instance with cloudband's region and credentials 23 | const stsClient = new STSClient({ region: region, credentials: credentials }); 24 | 25 | //send request to AWS to assume role in user's account to retrieve temporary credentials for read-only access 26 | try { 27 | const assumedRole = await stsClient.send(new AssumeRoleCommand(info)); 28 | const accessKeyId = assumedRole.Credentials.AccessKeyId; 29 | const secretAccessKey = assumedRole.Credentials.SecretAccessKey; 30 | const sessionToken = assumedRole.Credentials.SessionToken; 31 | res.locals.credentials = { accessKeyId, secretAccessKey, sessionToken }; 32 | return next(); 33 | } catch (error) { 34 | console.log(error); 35 | next(error); 36 | } 37 | }; 38 | 39 | module.exports = credentialController; 40 | -------------------------------------------------------------------------------- /server/controllers/ec2/cpuMetricsController.js: -------------------------------------------------------------------------------- 1 | const { 2 | CloudWatchClient, 3 | GetMetricDataCommand, 4 | } = require('@aws-sdk/client-cloudwatch'); 5 | 6 | const cpuMetricsController = {}; 7 | 8 | // Get CPU metrics for all instances in a region 9 | cpuMetricsController.getCPUMetrics = async (req, res, next) => { 10 | const { instances } = res.locals.ec2Instances; 11 | 12 | const credentials = { 13 | region: req.query.region, 14 | credentials: res.locals.credentials, 15 | }; 16 | 17 | // Initiate StartTime to be 7 days before EndTime 18 | const EndTime = new Date(); 19 | const StartTime = new Date(EndTime.getTime() - 7 * 24 * 60 * 60 * 1000); 20 | 21 | // Create new instance of CloudWatchClient with user's region and credentials 22 | const cloudwatch = new CloudWatchClient(credentials); 23 | 24 | try { 25 | const queries = []; 26 | 27 | // Generate AWS EC2 CPU metrics queries for each instance 28 | instances.forEach((instanceId, index) => { 29 | const id = index * instances.length + index + 1; 30 | queries.push( 31 | { 32 | Id: `m${id}`, 33 | Label: 'CPUUtilization', 34 | MetricStat: { 35 | Metric: { 36 | Namespace: 'AWS/EC2', 37 | MetricName: 'CPUUtilization', 38 | Dimensions: [ 39 | { 40 | Name: 'InstanceId', 41 | Value: instanceId, 42 | }, 43 | ], 44 | }, 45 | Period: 3600 * 8, 46 | Stat: 'Average', 47 | }, 48 | }, 49 | { 50 | Id: `m${id + 1}`, 51 | Label: 'CPUCreditUsage', 52 | MetricStat: { 53 | Metric: { 54 | Namespace: 'AWS/EC2', 55 | MetricName: 'CPUCreditUsage', 56 | Dimensions: [ 57 | { 58 | Name: 'InstanceId', 59 | Value: instanceId, 60 | }, 61 | ], 62 | }, 63 | Period: 3600 * 8, 64 | Stat: 'Sum', 65 | }, 66 | }, 67 | { 68 | Id: `m${id + 2}`, 69 | Label: 'CPUCreditBalance', 70 | MetricStat: { 71 | Metric: { 72 | Namespace: 'AWS/EC2', 73 | MetricName: 'CPUCreditBalance', 74 | Dimensions: [ 75 | { 76 | Name: 'InstanceId', 77 | Value: instanceId, 78 | }, 79 | ], 80 | }, 81 | Period: 3600 * 8, 82 | Stat: 'Sum', 83 | }, 84 | }, 85 | { 86 | Id: `m${id + 3}`, 87 | Label: 'CPUSurplusCreditBalance', 88 | MetricStat: { 89 | Metric: { 90 | Namespace: 'AWS/EC2', 91 | MetricName: 'CPUSurplusCreditBalance', 92 | Dimensions: [ 93 | { 94 | Name: 'InstanceId', 95 | Value: instanceId, 96 | }, 97 | ], 98 | }, 99 | Period: 3600 * 8, 100 | Stat: 'Sum', 101 | }, 102 | } 103 | ); 104 | }); 105 | 106 | const input = { 107 | StartTime, 108 | EndTime, 109 | LabelOptions: { 110 | Timezone: '-0400', 111 | }, 112 | MetricDataQueries: queries, 113 | }; 114 | 115 | // Send GetMetricDataCommand to AWS CloudWatch 116 | const command = new GetMetricDataCommand(input); 117 | const responses = await cloudwatch.send(command); 118 | 119 | //format data to be sent to frontend 120 | const timestamps = responses.MetricDataResults[0].Timestamps; 121 | const data = responses.MetricDataResults.reduce((acc, curr) => { 122 | if (!acc[curr.Label]) { 123 | acc[curr.Label] = { 124 | values: [], 125 | timestamps: timestamps, 126 | instanceIds: instances, 127 | }; 128 | } 129 | acc[curr.Label].values.push(curr.Values); 130 | return acc; 131 | }, {}); 132 | 133 | res.locals.chartData = data; 134 | return next(); 135 | } catch (error) { 136 | console.error(error); 137 | throw error; 138 | } 139 | }; 140 | 141 | module.exports = cpuMetricsController; 142 | -------------------------------------------------------------------------------- /server/controllers/ec2/instancesController.js: -------------------------------------------------------------------------------- 1 | const { EC2Client, DescribeInstancesCommand } = require('@aws-sdk/client-ec2'); 2 | 3 | //retrieve ids of all ec2 instances 4 | module.exports = { 5 | getInstances: async (req, res, next) => { 6 | const info = { 7 | region: req.query.region, 8 | credentials: res.locals.credentials, 9 | }; 10 | 11 | //create new instance of EC2Client with user's region and credentials 12 | const ec2Client = new EC2Client(info); 13 | 14 | try { 15 | const data = await ec2Client.send(new DescribeInstancesCommand({})); 16 | const instances = data.Reservations; 17 | const instanceIds = instances.map((instance) => { 18 | return instance.Instances[0].InstanceId; 19 | }); 20 | 21 | res.locals.ec2Instances = { 22 | instances: instanceIds, 23 | }; 24 | return next(); 25 | } catch (err) { 26 | console.log('error in describeInstances', err); 27 | return next(err); 28 | } 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /server/controllers/ec2/networkMetricsController.js: -------------------------------------------------------------------------------- 1 | const { 2 | CloudWatchClient, 3 | GetMetricDataCommand, 4 | } = require('@aws-sdk/client-cloudwatch'); 5 | 6 | const networkMetricsController = {}; 7 | 8 | // Get network metrics for all instances in a region 9 | networkMetricsController.getNetworkMetrics = async (req, res, next) => { 10 | const { instances } = res.locals.ec2Instances; 11 | 12 | const credentials = { 13 | region: req.query.region, 14 | credentials: res.locals.credentials, 15 | }; 16 | 17 | // Create a new instance of the CloudWatchClient with the user's region and credentials 18 | const cloudwatch = new CloudWatchClient(credentials); 19 | 20 | try { 21 | //initiate starttime to be 7 days before endtime 22 | const EndTime = new Date(); 23 | const StartTime = new Date(EndTime.getTime() - 7 * 24 * 60 * 60 * 1000); 24 | 25 | const queries = []; 26 | 27 | //generate AWS EC2 network metrics queries for each instance 28 | instances.forEach((instanceId, index) => { 29 | const id = index * instances.length + index + 1; 30 | queries.push( 31 | { 32 | Id: `m${id}`, 33 | Label: 'NetworkIn', 34 | MetricStat: { 35 | Metric: { 36 | Namespace: 'AWS/EC2', 37 | MetricName: 'NetworkIn', 38 | Dimensions: [ 39 | { 40 | Name: 'InstanceId', 41 | Value: instanceId, 42 | }, 43 | ], 44 | }, 45 | Period: 3600 * 8, 46 | Stat: 'Sum', 47 | }, 48 | }, 49 | { 50 | Id: `m${id + 1}`, 51 | Label: 'NetworkOut', 52 | MetricStat: { 53 | Metric: { 54 | Namespace: 'AWS/EC2', 55 | MetricName: 'NetworkOut', 56 | Dimensions: [ 57 | { 58 | Name: 'InstanceId', 59 | Value: instanceId, 60 | }, 61 | ], 62 | }, 63 | Period: 3600 * 8, 64 | Stat: 'Sum', 65 | }, 66 | } 67 | ); 68 | }); 69 | 70 | const input = { 71 | StartTime, 72 | EndTime, 73 | LabelOptions: { 74 | Timezone: '-0400', 75 | }, 76 | MetricDataQueries: queries, 77 | }; 78 | 79 | //send AWS query to CloudWatch 80 | const command = new GetMetricDataCommand(input); 81 | const responses = await cloudwatch.send(command); 82 | 83 | //format data to be sent to frontend 84 | const timestamps = responses.MetricDataResults[0].Timestamps; 85 | const data = responses.MetricDataResults.reduce((acc, curr) => { 86 | if (!acc[curr.Label]) { 87 | acc[curr.Label] = { 88 | values: [], 89 | timestamps: timestamps, 90 | instanceIds: instances, 91 | }; 92 | } 93 | acc[curr.Label].values.push(curr.Values); 94 | return acc; 95 | }, {}); 96 | 97 | res.locals.chartData = data; 98 | 99 | return next(); 100 | } catch (error) { 101 | console.error(error); 102 | throw error; 103 | } 104 | }; 105 | 106 | module.exports = networkMetricsController; 107 | -------------------------------------------------------------------------------- /server/controllers/lambda/lambdaLogsController.js: -------------------------------------------------------------------------------- 1 | const { 2 | CloudWatchLogsClient, 3 | FilterLogEventsCommand, 4 | } = require('@aws-sdk/client-cloudwatch-logs'); 5 | 6 | //retrieve logs of each lambda function 7 | const getLambdaLogs = async (req, res, next) => { 8 | const { currFunc } = req.query; 9 | const logGroupName = '/aws/lambda/' + currFunc; 10 | 11 | //create new instance of CloudWatchLogsClient with user's region and credentials 12 | const cloudWatchLogs = new CloudWatchLogsClient({ 13 | region: req.query.region, 14 | credentials: res.locals.credentials, 15 | }); 16 | 17 | //initiate starttime to be 7 days before endtime in milliseconds from epoch time 18 | const now = new Date(); 19 | const EndTime = now.valueOf(); 20 | const StartTime = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000).valueOf(); 21 | 22 | //helper function to recursively retrieve logs if there's a nextToken 23 | const nextTokenHelper = async (nextToken, data = []) => { 24 | if (!nextToken) { 25 | return data; 26 | } 27 | const nextLogEvents = await cloudWatchLogs.send( 28 | new FilterLogEventsCommand({ 29 | logGroupName, 30 | endTime: EndTime.valueOf(), 31 | startTime: StartTime.valueOf(), 32 | nextToken, 33 | filterPattern: '- START - END ', 34 | }) 35 | ); 36 | data.push(nextLogEvents.events); 37 | return nextTokenHelper(nextLogEvents.nextToken, data); 38 | }; 39 | 40 | //AWS query format for each individual lambda function to list logs 41 | try { 42 | const logEvents = await cloudWatchLogs.send( 43 | new FilterLogEventsCommand({ 44 | logGroupName, 45 | endTime: EndTime.valueOf(), 46 | startTime: StartTime.valueOf(), 47 | filterPattern: '- START - END ', 48 | }) 49 | ); 50 | //if there are no logs, return to next middleware 51 | if (!logEvents) { 52 | return next(); 53 | } 54 | 55 | //if there is a nextToken, recursively retrieve logs with helper function 56 | if (logEvents.nextToken) { 57 | const nextTokenData = await nextTokenHelper(logEvents.nextToken); 58 | logEvents.events = logEvents.events.concat(...nextTokenData); 59 | } 60 | 61 | //only return the first 50 logs 62 | const fiftyLogEvents = logEvents.events.slice(0, 50); 63 | 64 | //format the logs to remove unnecessary information 65 | const logEventsMessages = []; 66 | fiftyLogEvents.forEach((event) => { 67 | if ( 68 | event.message.slice(0, 4) !== 'LOGS' && 69 | event.message.slice(0, 9) !== 'EXTENSION' 70 | ) { 71 | logEventsMessages.push({ 72 | message: event.message.slice(67), 73 | timestamp: new Date(event.timestamp), 74 | }); 75 | } else { 76 | logEventsMessages.push({ 77 | message: event.message, 78 | timeStamp: new Date(event.timestamp), 79 | }); 80 | } 81 | }); 82 | 83 | res.locals.functionLogs = logEventsMessages; 84 | return next(); 85 | } catch (err) { 86 | if (err) console.error(err); 87 | return next(err); 88 | } 89 | }; 90 | 91 | module.exports = { 92 | getLambdaLogs, 93 | }; 94 | -------------------------------------------------------------------------------- /server/controllers/lambda/lambdaMetricsController.js: -------------------------------------------------------------------------------- 1 | const { 2 | CloudWatchClient, 3 | GetMetricDataCommand, 4 | } = require('@aws-sdk/client-cloudwatch'); 5 | 6 | //retrieve metrics for each lambda function 7 | const getLambdaMetrics = async (req, res, next) => { 8 | const credentials = { 9 | region: req.query.region, 10 | credentials: res.locals.credentials, 11 | }; 12 | 13 | //create new instance of CloudWatchClient with user's region and credentials 14 | const cloudwatch = new CloudWatchClient(credentials); 15 | 16 | //initiate endtime to be current time rounded to nearest 5 minutes 17 | const EndTime = Math.round(new Date().getTime() / 1000 / 60 / 5) * 60 * 5; 18 | //initiate starttime to be 7 days before endtime 19 | const StartTime = EndTime - 60 * 60 * 24 * 7; 20 | 21 | const { currFunc } = req.query; 22 | const { functionLogs } = res.locals; 23 | 24 | //AWS query format for each individual lambda function to list metrics 25 | const params = { 26 | StartTime: new Date(StartTime * 1000), 27 | EndTime: new Date(EndTime * 1000), 28 | LabelOptions: { 29 | Timezone: '-0400', 30 | }, 31 | MetricDataQueries: [ 32 | { 33 | Id: 'm1', 34 | Label: 'Invocations', 35 | MetricStat: { 36 | Metric: { 37 | Namespace: 'AWS/Lambda', 38 | MetricName: 'Invocations', 39 | Dimensions: [ 40 | { 41 | Name: 'FunctionName', 42 | Value: currFunc, 43 | }, 44 | ], 45 | }, 46 | Period: 60, 47 | Stat: 'Sum', 48 | }, 49 | }, 50 | { 51 | Id: 'm2', 52 | Label: 'Throttles', 53 | MetricStat: { 54 | Metric: { 55 | Namespace: 'AWS/Lambda', 56 | MetricName: 'Throttles', 57 | Dimensions: [ 58 | { 59 | Name: 'FunctionName', 60 | Value: currFunc, 61 | }, 62 | ], 63 | }, 64 | Period: 60, 65 | Stat: 'Sum', 66 | }, 67 | }, 68 | { 69 | Id: 'm3', 70 | Label: 'Errors', 71 | MetricStat: { 72 | Metric: { 73 | Namespace: 'AWS/Lambda', 74 | MetricName: 'Errors', 75 | Dimensions: [ 76 | { 77 | Name: 'FunctionName', 78 | Value: currFunc, 79 | }, 80 | ], 81 | }, 82 | Period: 60, 83 | Stat: 'Sum', 84 | }, 85 | }, 86 | { 87 | Id: 'm4', 88 | Label: 'Duration', 89 | MetricStat: { 90 | Metric: { 91 | Namespace: 'AWS/Lambda', 92 | MetricName: 'Duration', 93 | Dimensions: [ 94 | { 95 | Name: 'FunctionName', 96 | Value: currFunc, 97 | }, 98 | ], 99 | }, 100 | Period: 60, 101 | Stat: 'Average', 102 | }, 103 | }, 104 | ], 105 | }; 106 | 107 | //send formatted query to AWS CloudWatch and format response to be used in frontend 108 | try { 109 | const command = new GetMetricDataCommand(params); 110 | const metricData = await cloudwatch.send(command); 111 | 112 | const metricByFuncData = metricData.MetricDataResults.map( 113 | (eachFuncMetric) => { 114 | let values = eachFuncMetric.Values; 115 | let timestamps = eachFuncMetric.Timestamps; 116 | let metricName = eachFuncMetric.Label; 117 | 118 | return { 119 | metricName: metricName, 120 | values: values, 121 | timestamps: timestamps, 122 | }; 123 | } 124 | ); 125 | 126 | res.locals.lambdaMetricsLogs = [...metricByFuncData, functionLogs]; 127 | return next(); 128 | } catch (error) { 129 | console.error(error); 130 | throw error; 131 | } 132 | }; 133 | 134 | module.exports = { 135 | getLambdaMetrics, 136 | }; 137 | -------------------------------------------------------------------------------- /server/controllers/lambda/listLambdasController.js: -------------------------------------------------------------------------------- 1 | const { 2 | LambdaClient, 3 | ListFunctionsCommand, 4 | } = require('@aws-sdk/client-lambda'); 5 | 6 | const listLambdasController = {}; 7 | 8 | //retrieve name of all user's lambda functions 9 | listLambdasController.getLambdas = async (req, res, next) => { 10 | //create new instance of LambdaClient with user's region and credentials 11 | const lambdaClient = new LambdaClient({ 12 | region: req.query.region, 13 | credentials: res.locals.credentials, 14 | }); 15 | 16 | //retrieve names of lambda functions (max 10) 17 | try { 18 | const allFuncs = await lambdaClient.send( 19 | new ListFunctionsCommand({ 20 | MaxItems: 10, 21 | FunctionVersion: 'ALL', 22 | }) 23 | ); 24 | 25 | const funcList = allFuncs.Functions.map((func) => func.FunctionName); 26 | res.locals.lambdaNames = funcList; 27 | 28 | return next(); 29 | } catch (err) { 30 | console.log('Error in listLambdas', err); 31 | return next(err); 32 | } 33 | }; 34 | module.exports = listLambdasController; 35 | -------------------------------------------------------------------------------- /server/controllers/rds/rdsMetricsController.js: -------------------------------------------------------------------------------- 1 | //require the AWS SDK for Node.js 2 | const { 3 | CloudWatchClient, 4 | GetMetricDataCommand, 5 | } = require('@aws-sdk/client-cloudwatch'); 6 | 7 | //declare an rdsMetricsController object 8 | const rdsMetricsController = {}; 9 | 10 | //getRDSCPUUtilizationMetrics function, which will be called by the getRDSCPUUtilizationMetrics route handler 11 | rdsMetricsController.getRDSCPUUtilizationMetrics = async (req, res, next) => { 12 | const AWS = require('aws-sdk'); 13 | //declare a constant variable called cloudwatch, which will be used to call the AWS CloudWatch API 14 | const cloudwatch = new AWS.CloudWatch({ region: req.query.region }); 15 | 16 | //declare a constant variable called params, which will be used to pass the parameters of the RDSCpuUtilization metrics to the AWS CloudWatch API 17 | const params = { 18 | MetricName: 'CPUUtilization', 19 | Namespace: 'AWS/RDS', 20 | Period: 300, 21 | Dimensions: [ 22 | { 23 | Name: 'DBInstanceIdentifier', 24 | Value: req.query.DBInstanceIdentifier, 25 | }, 26 | ], 27 | StartTime: new Date(Date.now() - 24 * 60 * 60 * 1000), 28 | EndTime: new Date(), 29 | Statistics: ['Average'], 30 | }; 31 | 32 | //try to call the AWS CloudWatch API to get the RDSCpuUtilization metrics 33 | try { 34 | const data = await cloudwatch.getMetricData(params).promise(); 35 | res.locals.getRDSCPUUtilizationMetrics = data; 36 | return next(); 37 | //if there is an error, log the error and throw the error 38 | } catch (error) { 39 | console.error(error); 40 | throw error; 41 | } 42 | }; 43 | 44 | module.exports = rdsMetricsController; 45 | -------------------------------------------------------------------------------- /server/controllers/sessionController.js: -------------------------------------------------------------------------------- 1 | const sessionController = {}; 2 | const User = require('../models/userModel'); 3 | const Session = require('../models/sessionModel'); 4 | 5 | //create a new session based on SSID cookie 6 | sessionController.startSession = async (req, res, next) => { 7 | try { 8 | await Session.create({ cookieId: res.locals.ssidCookie }); 9 | return next(); 10 | } catch (err) { 11 | next({ 12 | log: `Error in sessionController.startSession. Details: ${err}`, 13 | message: { err: 'An error occurred in sessionController.startSession' }, 14 | }); 15 | } 16 | }; 17 | 18 | // log out user based on SSID cookie 19 | sessionController.logout = async (req, res, next) => { 20 | const { ssid } = req.cookies; 21 | try { 22 | await Session.deleteOne({ cookieId: ssid }); 23 | return next(); 24 | } catch (err) { 25 | next({ 26 | log: `Error in sessionController.logout. Details: ${err}`, 27 | message: { err: 'An error occurred in sessionController.logout' }, 28 | }); 29 | } 30 | }; 31 | 32 | //verify that user is logged in based on SSID cookie 33 | sessionController.isLoggedIn = async (req, res, next) => { 34 | const { ssid } = req.cookies; 35 | try { 36 | const loggedInStatus = await Session.findOne({ 37 | cookieId: ssid, 38 | }); 39 | if (loggedInStatus) { 40 | const userData = await User.find({ _id: ssid }); 41 | const { RoleARN, region, _id } = userData[0]; 42 | res.locals.user = { RoleARN, region, _id }; 43 | } 44 | return next(); 45 | } catch (err) { 46 | next({ 47 | log: `Error in sessionController.isLoggedIn. Details: ${err}`, 48 | message: { err: 'An error occurred in sessionController.isLoggedIn' }, 49 | }); 50 | } 51 | }; 52 | 53 | module.exports = sessionController; 54 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const User = require('../models/userModel'); 2 | const bcrypt = require('bcryptjs'); 3 | 4 | const userController = {}; 5 | 6 | // creates new user 7 | userController.createUser = async (req, res, next) => { 8 | try { 9 | const { email, password, RoleARN, region } = req.body; 10 | 11 | const newUser = await User.create({ 12 | email: email, 13 | password: password, 14 | RoleARN: RoleARN, 15 | region: region, 16 | }); 17 | res.locals.newUser = newUser; 18 | return next(); 19 | } catch (err) { 20 | next({ 21 | log: `Error in userController.createUser. Details: ${err}`, 22 | message: { err: 'An error occurred in userController.createUser' }, 23 | }); 24 | } 25 | }; 26 | 27 | // confirms user has correct username and password 28 | userController.verifyUser = async (req, res, next) => { 29 | try { 30 | const { email, password } = req.body; 31 | const userData = await User.find({ email: email }); 32 | const pwCheck = await bcrypt.compare(password, userData[0].password); 33 | if (!pwCheck) { 34 | throw new Error('Password is incorrect'); 35 | } else { 36 | const { RoleARN, region, _id } = userData[0]; 37 | res.locals.newUser = { RoleARN, region, _id }; 38 | return next(); 39 | } 40 | } catch (err) { 41 | return next({ 42 | log: `Error in userController.verifyUser. Details: ${err}`, 43 | message: { err: 'An error occurred in userController.verifyUser' }, 44 | }); 45 | } 46 | }; 47 | 48 | module.exports = userController; 49 | -------------------------------------------------------------------------------- /server/models/sessionModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | const sessionSchema = new Schema({ 5 | cookieId: { type: String, required: true, unique: true }, 6 | createdAt: { type: Date, expires: 90, default: Date.now }, 7 | }); 8 | 9 | module.exports = mongoose.model('Session', sessionSchema); 10 | -------------------------------------------------------------------------------- /server/models/userModel.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | const bcrypt = require('bcryptjs'); 4 | 5 | const userSchema = new Schema({ 6 | email: { type: String, required: true, unique: true }, 7 | password: { type: String, required: true }, 8 | RoleARN: { type: String, required: true }, 9 | region: { type: String, required: true }, 10 | }); 11 | 12 | const SALT_WORK_FACTOR = 10; 13 | 14 | //hash password before saving user info to database 15 | userSchema.pre('save', function (next) { 16 | const user = this; 17 | bcrypt.genSalt(SALT_WORK_FACTOR, function (err, salt) { 18 | if (err) return next(err); 19 | bcrypt.hash(user.password, salt, function (err, hash) { 20 | if (err) return next(err); 21 | user.password = hash; 22 | next(); 23 | }); 24 | }); 25 | }); 26 | 27 | module.exports = mongoose.model('User', userSchema); 28 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const express = require('express'); 3 | const cors = require('cors'); 4 | const cookieParser = require('cookie-parser'); 5 | const cpuMetricsController = require('./controllers/ec2/cpuMetricsController'); 6 | const networkMetricsController = require('./controllers/ec2/networkMetricsController'); 7 | const instancesController = require('./controllers/ec2/instancesController'); 8 | const credentialController = require('./controllers/credentialController'); 9 | const userController = require('./controllers/userController'); 10 | const cookieController = require('./controllers/cookieController'); 11 | const sessionController = require('./controllers/sessionController'); 12 | const listLambdasController = require('./controllers/lambda/listLambdasController'); 13 | const lambdaMetricsController = require('./controllers/lambda/lambdaMetricsController'); 14 | const lambdaLogsController = require('./controllers/lambda/lambdaLogsController'); 15 | const rdsMetricsController = require('./controllers/rds/rdsMetricsController'); 16 | //const latencyMetricsController = require('/controllers/latency/latencyMetrics'); 17 | 18 | const mongoose = require('mongoose'); 19 | 20 | mongoose 21 | .connect(`${process.env.MONGO_URI_}`, { 22 | useNewUrlParser: true, 23 | // useFindAndModify: false, 24 | // useUnifiedTopology: true, 25 | }) 26 | .then(() => console.log('MongoDB connected...')); 27 | 28 | // invoke express 29 | const app = express(); 30 | const PORT = 3000; 31 | console.log('server is running'); 32 | 33 | // add cookie parser 34 | app.use(cookieParser()); 35 | 36 | // use cors 37 | app.use(cors()); 38 | 39 | // use express json 40 | app.use(express.json()); 41 | 42 | //handles requests for ec2 network metrics 43 | app.get( 44 | '/network-in-out', 45 | credentialController.getCredentials, 46 | instancesController.getInstances, 47 | networkMetricsController.getNetworkMetrics, 48 | (req, res) => { 49 | return res.status(200).json(res.locals.chartData); 50 | } 51 | ); 52 | 53 | //handles requests for ec2 cpu metrics and credits 54 | app.get( 55 | '/cpu-credits', 56 | credentialController.getCredentials, 57 | instancesController.getInstances, 58 | cpuMetricsController.getCPUMetrics, 59 | (req, res) => { 60 | return res.status(200).json(res.locals.chartData); 61 | } 62 | ); 63 | 64 | //get Lambda function names 65 | app.get( 66 | '/getLambdaNames', 67 | credentialController.getCredentials, 68 | listLambdasController.getLambdas, 69 | (req, res) => { 70 | return res.status(200).json(res.locals.lambdaNames); 71 | } 72 | ); 73 | //handles requests for lambda logs and metrics 74 | app.get( 75 | '/getLambdaMetrics', 76 | credentialController.getCredentials, 77 | lambdaLogsController.getLambdaLogs, 78 | lambdaMetricsController.getLambdaMetrics, 79 | (req, res) => { 80 | return res.status(200).json(res.locals.lambdaMetricsLogs); 81 | } 82 | ); 83 | 84 | //handles requests for latency metrics 85 | // app.get( 86 | // '/getLatencyMetrics', 87 | // credentialController.getCredentials, 88 | // latencyMetricController.getLatencyMetrics, 89 | // (req, res) => { 90 | // return res.status(200).json(res.locals.latencyMetrics); 91 | // } 92 | // ); 93 | 94 | //route for obtaining RDS metric data 95 | app.get( 96 | '/getRDSCPUUtilizationMetrics', 97 | credentialController.getCredentials, 98 | rdsMetricsController.getRDSCPUUtilizationMetrics, 99 | (req, res) => { 100 | return res.status(200).json(res.locals.rdsCPUUtilizationMetrics); 101 | }); 102 | 103 | app.delete('/logout', sessionController.logout, (req, res) => { 104 | return res.status(200).send(); 105 | }); 106 | 107 | // sign up 108 | app.post('/signup', userController.createUser, (req, res) => { 109 | return res.status(200).json(res.locals); 110 | }); 111 | 112 | // handles sign in request 113 | app.post( 114 | '/signin', 115 | userController.verifyUser, 116 | cookieController.setSSIDCookie, 117 | sessionController.startSession, 118 | (req, res) => { 119 | return res.status(200).json(res.locals); 120 | } 121 | ); 122 | 123 | //checks if user is logged in 124 | app.get('/checkSession', sessionController.isLoggedIn, (req, res) => { 125 | return res.status(200).json(res.locals); 126 | }); 127 | // 404 error handler :) 128 | app.get('*', (req, res) => { 129 | return res.status(404).send('This page does not exist.'); 130 | }); 131 | 132 | // global error handler 133 | app.use((err, req, res, next) => { 134 | const defaultErr = { 135 | log: 'Express error handler caught unknown middleware error', 136 | status: 400, 137 | message: { err: 'An error occurred' }, 138 | }; 139 | const errorObj = Object.assign({}, defaultErr, err); 140 | return res.status(errorObj.status).json(errorObj.message); 141 | }); 142 | 143 | app.listen(PORT, () => { 144 | console.log('Server listening on port 3000'); 145 | }); 146 | 147 | module.exports = app; 148 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import { Route, Routes } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import Navbar from './components/Navbar.jsx'; 5 | import Footer from './components/Footer.jsx'; 6 | import MainContainer from './containers/MainContainer.jsx'; 7 | import Login from './components/Login.jsx'; 8 | import Signup from './components/Signup.jsx'; 9 | import LandingPage from './components/LandingPage.jsx'; 10 | import PrivacyPolicy from './components/PrivacyPolicy.jsx'; 11 | import './styles.scss'; 12 | 13 | const App = () => { 14 | const [loggedIn, setLoggedIn] = useState(); 15 | const [arn, setArn] = useState(); 16 | const [region, setRegion] = useState(); 17 | 18 | // check for session using cookie to persist login status, arn, and region 19 | useEffect(() => { 20 | axios 21 | .get('/checkSession') 22 | .then((response) => { 23 | if (response.data.user) { 24 | setLoggedIn(true); 25 | setArn(response.data.user.RoleARN); 26 | setRegion(response.data.user.region); 27 | } else setLoggedIn(false); 28 | }) 29 | .catch((error) => { 30 | console.error('error in sign up request: ', error); 31 | }); 32 | }, []); 33 | 34 | return ( 35 |
36 | 37 |
38 |
39 | 40 | } /> 41 | 50 | } 51 | /> 52 | } /> 53 | 57 | } 58 | /> 59 | } /> 60 | 61 |
62 |
63 |
64 |
65 | ); 66 | }; 67 | 68 | export default App; 69 | -------------------------------------------------------------------------------- /src/Data.js: -------------------------------------------------------------------------------- 1 | export const Data = [ 2 | { 3 | id: 1, 4 | year: 2016, 5 | userGain: 80000, 6 | userLost: 823 7 | }, 8 | { 9 | id: 2, 10 | year: 2017, 11 | userGain: 45677, 12 | userLost: 345 13 | }, 14 | { 15 | id: 3, 16 | year: 2018, 17 | userGain: 78888, 18 | userLost: 555 19 | }, 20 | { 21 | id: 4, 22 | year: 2019, 23 | userGain: 90000, 24 | userLost: 4555 25 | }, 26 | { 27 | id: 5, 28 | year: 2020, 29 | userGain: 4300, 30 | userLost: 234 31 | } 32 | ]; -------------------------------------------------------------------------------- /src/_variables.scss: -------------------------------------------------------------------------------- 1 | // $light-color: #f5f5f5; 2 | // $mid-blue: #003566; 3 | 4 | :root { 5 | --light-color: #f5f5f5; 6 | --light-color-opacity: rgba(245, 245, 245, 0.5); 7 | // --mid-blue: #003566; 8 | --mid-blue: #042439; 9 | // --mid-blue-opacity: rgba(0, 53, 102, 0.5); 10 | --mid-blue-opacity: rgba(3, 41, 62, 0.5); 11 | --pale-blue: #acbbca; 12 | --deep-yellow: #ffc300; 13 | --drop-shadow: 4px 4px 4px rgba(0, 0, 0, 0.15); 14 | } 15 | -------------------------------------------------------------------------------- /src/componentStyling/EC2ChartStyling.scss: -------------------------------------------------------------------------------- 1 | @import '../variables'; 2 | 3 | .chart-wrapper { 4 | background-color: var(--light-color); 5 | padding: 20px; 6 | margin: 20px; 7 | width: 800px; 8 | } 9 | -------------------------------------------------------------------------------- /src/componentStyling/Footer.scss: -------------------------------------------------------------------------------- 1 | @import '../variables'; 2 | 3 | .footer-wrapper { 4 | display: flex; 5 | align-self: flex-end; 6 | flex-direction: column; 7 | padding: 20px 40px 40px 40px; 8 | // margin-bottom: 30px; 9 | 10 | button { 11 | background-color: transparent; 12 | color: var(--light-color); 13 | border: none; 14 | font-family: 'Quicksand'; 15 | font-size: 16px; 16 | } 17 | .row-1-wrapper { 18 | display: flex; 19 | flex-direction: row; 20 | justify-content: space-between; 21 | 22 | .col-1-wrapper { 23 | width: 40%; 24 | 25 | h1 { 26 | font-weight: 700; 27 | font-size: 32px; 28 | margin-bottom: 18.26px; 29 | } 30 | a { 31 | color: var(--deep-yellow); 32 | text-decoration: none; 33 | // font-weight: 800; 34 | } 35 | } 36 | 37 | .col-header { 38 | font-size: 22px; 39 | font-weight: 600; 40 | padding-top: 10px; 41 | } 42 | 43 | .col-2-wrapper { 44 | ul { 45 | padding: 0; 46 | } 47 | li { 48 | list-style: none; 49 | 50 | a { 51 | text-decoration: none; 52 | // color: var(--mid-blue); 53 | color: var(--light-color); 54 | } 55 | } 56 | } 57 | 58 | .col-3-wrapper { 59 | a { 60 | // color: var(--mid-blue); 61 | color: var(--deep-yellow); 62 | text-decoration: none; 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/componentStyling/LambdaChartStyling.scss: -------------------------------------------------------------------------------- 1 | .lambda-chart-wrapper { 2 | background-color: var(--light-color); 3 | padding: 20px; 4 | margin: 20px; 5 | width: 400px; 6 | } 7 | -------------------------------------------------------------------------------- /src/componentStyling/LandingPage.scss: -------------------------------------------------------------------------------- 1 | @import '../variables'; 2 | 3 | .landing-page-wrapper { 4 | padding: 40px; 5 | h2 { 6 | font-size: 36px; 7 | font-weight: 600; 8 | } 9 | h3 { 10 | text-align: center; 11 | padding-bottom: 20px; 12 | } 13 | 14 | button { 15 | width: 200px; 16 | padding: 10px 40px; 17 | border-radius: 40px; 18 | margin: 10px; 19 | border: none; 20 | color: var(--mid-blue); 21 | box-shadow: var(--drop-shadow); 22 | } 23 | 24 | .hero { 25 | .column { 26 | padding-right: 40px; 27 | } 28 | img { 29 | // box-shadow: var(--drop-shadow); 30 | width: 50%; 31 | } 32 | } 33 | 34 | .primary-btn { 35 | background-color: var(--deep-yellow); 36 | } 37 | 38 | .subheading { 39 | font-size: 20px; 40 | font-weight: 300; 41 | } 42 | .row { 43 | display: flex; 44 | flex-direction: row; 45 | justify-content: space-around; 46 | } 47 | 48 | .column { 49 | padding-right: 20px; 50 | } 51 | 52 | .section { 53 | margin: 60px 0px; 54 | } 55 | 56 | .feature-box { 57 | border-radius: 40px; 58 | width: 25%; 59 | padding: 20px; 60 | box-shadow: var(--drop-shadow); 61 | background-color: var(--mid-blue-opacity); 62 | 63 | h4 { 64 | color: var(--deep-yellow); 65 | font-size: 18px; 66 | } 67 | } 68 | 69 | .highlights { 70 | .row { 71 | margin: 120px 0px; 72 | } 73 | 74 | .highlight-text { 75 | width: 30%; 76 | } 77 | 78 | .highlight-primary-text { 79 | font-size: 24px; 80 | font-weight: 600; 81 | color: var(--deep-yellow); 82 | } 83 | img { 84 | width: 50%; 85 | // box-shadow: var(--drop-shadow); 86 | } 87 | } 88 | 89 | .team-member-box { 90 | display: flex; 91 | flex-direction: column; 92 | align-items: center; 93 | background: var(--mid-blue-opacity); 94 | border-radius: 40px; 95 | padding: 20px; 96 | box-shadow: var(--drop-shadow); 97 | a { 98 | color: var(--light-color); 99 | padding: 0px 10px; 100 | } 101 | 102 | h4 { 103 | margin-top: 20px; 104 | margin-bottom: 0; 105 | color: var(--deep-yellow); 106 | } 107 | 108 | p { 109 | margin: 20px 0; 110 | } 111 | 112 | .headshot { 113 | height: 100px; 114 | } 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/componentStyling/Login.scss: -------------------------------------------------------------------------------- 1 | .login-form-wrapper { 2 | display: flex; 3 | flex-direction: column; 4 | min-height: 600px; 5 | width: 100%; 6 | align-items: center; 7 | padding: 80px 40px 40px 40px; 8 | 9 | .login-form-content { 10 | display: flex; 11 | flex-direction: column; 12 | width: 50%; 13 | min-width: 300px; 14 | text-align: center; 15 | // align-items: center; 16 | button { 17 | // width: 200px; 18 | padding: 10px 40px; 19 | border-radius: 40px; 20 | margin: 20px 0; 21 | border: none; 22 | color: var(--mid-blue); 23 | box-shadow: var(--drop-shadow); 24 | background-color: var(--deep-yellow); 25 | } 26 | 27 | button:hover { 28 | border: 1px solid var(--light-color); 29 | color: var(--light-color); 30 | background-color: transparent; 31 | transition: all 0.3s ease-in-out; 32 | } 33 | 34 | input { 35 | padding: 5px; 36 | margin: 20px 0px; 37 | border: none; 38 | border-bottom: 1px solid var(--mid-blue); 39 | background: transparent; 40 | color: white; 41 | } 42 | 43 | input:active { 44 | background: transparent; 45 | border-bottom: 1px solid var(--mid-blue); 46 | color: white; 47 | } 48 | 49 | ::placeholder { 50 | color: white; 51 | opacity: 50%; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/componentStyling/Navbar.scss: -------------------------------------------------------------------------------- 1 | @import '../variables'; 2 | 3 | .navbar-wrapper { 4 | display: flex; 5 | flex-direction: row; 6 | justify-content: space-between; 7 | padding: 0 40px 0 40px; 8 | line-height: 50px; 9 | align-items: center; 10 | 11 | button { 12 | background-color: transparent; 13 | color: var(--light-color); 14 | border: none; 15 | font-family: 'Quicksand'; 16 | font-size: 16px; 17 | } 18 | 19 | .row { 20 | display: flex; 21 | flex-direction: row; 22 | align-items: center; 23 | } 24 | 25 | .logo { 26 | font-weight: 700; 27 | font-size: 32px; 28 | text-decoration: none; 29 | // color: var(--mid-blue); 30 | 31 | color: var(--light-color); 32 | padding: 0 0 0 20px; 33 | } 34 | .logo-img { 35 | width: 100px; 36 | } 37 | 38 | .menu-items { 39 | display: flex; 40 | flex-direction: row; 41 | width: 25%; 42 | justify-content: space-between; 43 | align-items: center; 44 | 45 | li { 46 | list-style: none; 47 | } 48 | 49 | a { 50 | text-decoration: none; 51 | // color: var(--mid-blue); 52 | 53 | color: var(--light-color); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/componentStyling/PrivacyPolicy.scss: -------------------------------------------------------------------------------- 1 | @import '../variables'; 2 | 3 | .privacy-policy-wrapper { 4 | min-height: 800px; 5 | 6 | h1 { 7 | padding: 40px; 8 | } 9 | p { 10 | padding: 0px 40px; 11 | line-height: 1.5; 12 | } 13 | a { 14 | color: var(--deep-yellow); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/componentStyling/Settings.scss: -------------------------------------------------------------------------------- 1 | @import '../variables'; 2 | 3 | .settings-wrapper { 4 | display: flex; 5 | flex-direction: column; 6 | padding: 20px 40px 0 40px; 7 | min-width: 200px; 8 | max-width: 200px; 9 | 10 | h2 { 11 | font-size: 24px; 12 | font-weight: 700; 13 | margin-bottom: 10px; 14 | } 15 | 16 | label { 17 | padding: 10px 0px; 18 | font-weight: 700; 19 | } 20 | 21 | select { 22 | width: 100%; 23 | padding: 20px 0px; 24 | margin: 20px 0px; 25 | border: none; 26 | border-bottom: 1px solid var(--mid-blue-opacity); 27 | background: transparent; 28 | color: white; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/componentStyling/Signup.scss: -------------------------------------------------------------------------------- 1 | .signup-wrapper { 2 | padding-top: 40px; 3 | padding-bottom: 80px; 4 | 5 | a { 6 | color: var(--deep-yellow); 7 | } 8 | 9 | // button { 10 | // width: 100%; 11 | // padding: 10px 40px; 12 | // border-radius: 40px; 13 | // margin: 20px 0; 14 | // border: none; 15 | // color: var(--mid-blue); 16 | // box-shadow: var(--drop-shadow); 17 | // } 18 | 19 | .link-button { 20 | background-color: transparent; 21 | color: var(--deep-yellow); 22 | border: none; 23 | font-family: 'Quicksand'; 24 | font-size: 16px; 25 | padding: 0; 26 | } 27 | 28 | .primary-btn { 29 | width: 100%; 30 | padding: 10px 40px; 31 | border-radius: 40px; 32 | margin: 20px 0; 33 | border: none; 34 | color: var(--mid-blue); 35 | box-shadow: var(--drop-shadow); 36 | background-color: var(--deep-yellow); 37 | } 38 | .primary-btn:hover { 39 | width: 100%; 40 | padding: 10px 40px; 41 | border-radius: 40px; 42 | margin: 20px 0; 43 | border: none; 44 | // color: var(--mid-blue); 45 | box-shadow: var(--drop-shadow); 46 | background-color: var(--med-blue); 47 | color: var(--light-color); 48 | border: 1px solid var(--light-color); 49 | transition: all 0.3s ease-in-out; 50 | } 51 | 52 | .secondary-btn { 53 | width: 100%; 54 | padding: 10px 40px; 55 | border-radius: 40px; 56 | margin: 20px 0; 57 | border: none; 58 | // color: var(--mid-blue); 59 | box-shadow: var(--drop-shadow); 60 | border: 1px solid var(--light-color); 61 | color: var(--light-color); 62 | background-color: transparent; 63 | } 64 | .secondary-btn:hover { 65 | width: 100%; 66 | padding: 10px 40px; 67 | border-radius: 40px; 68 | margin: 20px 0; 69 | border: none; 70 | // color: var(--mid-blue); 71 | box-shadow: var(--drop-shadow); 72 | border: none; 73 | color: var(--mid-blue); 74 | background-color: var(--light-color); 75 | transition: all 0.3s ease-in-out; 76 | } 77 | 78 | .row-1 { 79 | display: flex; 80 | flex-direction: row; 81 | // width: 100%; 82 | justify-content: space-between; 83 | padding: 40px; 84 | 85 | .instructions { 86 | display: flex; 87 | flex-direction: column; 88 | width: 30%; 89 | margin-right: 20px; 90 | 91 | h2 { 92 | margin-top: 0; 93 | } 94 | 95 | h3 { 96 | font-weight: 600; 97 | } 98 | } 99 | 100 | #video-wrapper { 101 | width: 640px; 102 | height: 360px; 103 | 104 | iframe { 105 | width: 100%; 106 | height: 100%; 107 | } 108 | } 109 | } 110 | .row-2 { 111 | display: flex; 112 | flex-direction: row; 113 | justify-content: space-between; 114 | padding: 0px 40px; 115 | 116 | select { 117 | width: 100%; 118 | padding: 5px; 119 | margin: 20px 0px; 120 | border: none; 121 | border-bottom: 1px solid var(--mid-blue); 122 | background: transparent; 123 | color: white; 124 | } 125 | 126 | input { 127 | padding: 5px; 128 | margin: 20px 0px; 129 | border: none; 130 | border-bottom: 1px solid var(--mid-blue); 131 | background: transparent; 132 | color: white; 133 | } 134 | 135 | input:active { 136 | background: transparent; 137 | border-bottom: 1px solid var(--mid-blue); 138 | color: white; 139 | } 140 | 141 | ::placeholder { 142 | color: white; 143 | opacity: 50%; 144 | } 145 | p { 146 | margin-top: 0; 147 | } 148 | 149 | .column { 150 | display: flex; 151 | flex-direction: column; 152 | padding-right: 10px; 153 | width: 30%; 154 | } 155 | .help-link { 156 | font-size: 10px; 157 | color: var(--light-color-opacity); 158 | } 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /src/components/AreaChartOptions.js: -------------------------------------------------------------------------------- 1 | const Options = (title, subtitle) => { 2 | const options = { 3 | responsive: true, 4 | interaction: { 5 | mode: 'index', 6 | intersect: false, 7 | }, 8 | stacked: false, 9 | plugins: { 10 | title: { 11 | display: true, 12 | align: 'start', 13 | text: title, 14 | padding: { 15 | top: 10, 16 | bottom: 10, 17 | }, 18 | font: { 19 | size: 18, 20 | fontFamily: 'Quicksand', 21 | }, 22 | color: 'rgba(3, 41, 62, 0.5)', 23 | }, 24 | subtitle: { 25 | display: true, 26 | text: subtitle, 27 | align: 'start', 28 | padding: { 29 | top: 0, 30 | bottom: 30, 31 | }, 32 | font: { 33 | // weight: 'bold', 34 | size: 12, 35 | fontFamily: 'Quicksand', 36 | }, 37 | color: 'rgba(3, 41, 62, 0.5)', 38 | }, 39 | legend: { 40 | display: false, 41 | }, 42 | }, 43 | scales: { 44 | y: { 45 | type: 'linear', 46 | display: true, 47 | position: 'left', 48 | }, 49 | }, 50 | transitions: { 51 | show: { 52 | animations: { 53 | x: { 54 | from: 0, 55 | }, 56 | y: { 57 | from: 0, 58 | }, 59 | }, 60 | }, 61 | hide: { 62 | animations: { 63 | x: { 64 | to: 0, 65 | }, 66 | y: { 67 | to: 0, 68 | }, 69 | }, 70 | }, 71 | }, 72 | }; 73 | 74 | return options; 75 | }; 76 | 77 | export default Options; 78 | -------------------------------------------------------------------------------- /src/components/CPUCreditBalanceChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Options from './LineChartOptions.js'; 5 | import { Colors, ColorsHalfOpacity } from './ColorGenerator.js'; 6 | import '../componentStyling/EC2ChartStyling.scss'; 7 | // import { ColorsHalfOpacity } from './ColorGenerator.js'; 8 | 9 | //declare a constant CPUCreditBalanceChart and set it equal to an arrow function that takes in props as a parameter 10 | const CPUCreditBalanceChart = (props) => { 11 | //declare a constant chartData and set it equal to props.chartData 12 | const { chartData } = props; 13 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array 14 | const labels = chartData.timestamps 15 | .map((timestamp) => { 16 | //convert the timestamp to a date object 17 | const date = new Date(timestamp); 18 | // const month = date.getMonth() + 1; 19 | // const day = date.getDate(); 20 | //declare a constant hour and set it equal to the hour of the date object 21 | let hour = date.getHours(); 22 | //declare a constant minute and set it equal to the minutes of the date object 23 | let minute = date.getMinutes(); 24 | //if the hour is less than 10, add a 0 to the beginning of the hour 25 | if (hour < 10) { 26 | //add a 0 to the beginning of the hour 27 | hour = `0${hour}`; 28 | } 29 | //if the minute is less than 10, add a 0 to the beginning of the minute 30 | if (minute < 10) { 31 | //add a 0 to the beginning of the minute 32 | minute = `0${minute}`; 33 | } 34 | //return the hour and minute 35 | return `${hour}:${minute}`; 36 | }) 37 | //reverse the array 38 | .reverse(); //[timestamps] 39 | 40 | //declare a constant datasets and use the map method to iterate over the values array and return a new array of objects for each element in the values array 41 | const datasets = chartData.values 42 | .map((array, index) => { 43 | //return an object with the following properties: label, data, borderColor, backgroundColor, and yAxisID 44 | return { 45 | label: chartData.instanceIds[index], 46 | data: array, 47 | borderColor: Colors[index], 48 | // backgroundColor: 'rgba(255, 99, 132, 0.5)', 49 | backgroundColor: ColorsHalfOpacity[index], 50 | yAxisID: `y`, 51 | }; 52 | }) 53 | //reverse the array 54 | .reverse(); 55 | 56 | //declare a constant data and set it equal to an object with the following properties: labels and datasets 57 | const data = { 58 | labels: labels, // [..] 59 | datasets: datasets, // [{..}, {..}, {..}] 60 | }; 61 | 62 | const options = Options( 63 | 'CPU Credit Balance', 64 | 'Number of credits remaining for each EC2 instance at 8 hour intervals for the past week.' 65 | ); 66 | 67 | //return a div with a class of chart-wrapper and a Line component with the following properties: data and options 68 | return ( 69 |
70 | 71 |
72 | ); 73 | }; 74 | 75 | export default CPUCreditBalanceChart; 76 | -------------------------------------------------------------------------------- /src/components/CPUCreditUsageChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Options from './LineChartOptions.js'; 5 | import { Colors, ColorsHalfOpacity } from './ColorGenerator.js'; 6 | import '../componentStyling/EC2ChartStyling.scss'; 7 | 8 | //declare a constant CPUCreditBalanceChart and set it equal to an arrow function that takes in props as a parameter 9 | const CPUCreditUsageChart = (props) => { 10 | //declare a constant chartData and set it equal to props.chartData 11 | const { chartData } = props; 12 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array 13 | const labels = chartData.timestamps 14 | .map((timestamp) => { 15 | //convert the timestamp to a date object 16 | const date = new Date(timestamp); 17 | // const month = date.getMonth() + 1; 18 | // const day = date.getDate(); 19 | //declare a constant hour and set it equal to the hour of the date object 20 | let hour = date.getHours(); 21 | //declare a constant minute and set it equal to the minutes of the date object 22 | let minute = date.getMinutes(); 23 | //if the hour is less than 10, add a 0 to the beginning of the hour 24 | if (hour < 10) { 25 | hour = `0${hour}`; 26 | } 27 | //if the minute is less than 10, add a 0 to the beginning of the minute 28 | if (minute < 10) { 29 | minute = `0${minute}`; 30 | } 31 | //return the hour and minute 32 | return `${hour}:${minute}`; 33 | }) 34 | //reverse the array in order to display the most recent data first 35 | .reverse(); //[timestamps] 36 | 37 | //declare a constant datasets and use the map method to iterate over the values array and return a new array of objects for each element in the values array 38 | const datasets = chartData.values 39 | .map((array, index) => { 40 | //return an object with the following properties: label, data, borderColor, backgroundColor, and yAxisID 41 | return { 42 | label: chartData.instanceIds[index], 43 | data: array, 44 | borderColor: Colors[index], 45 | backgroundColor: ColorsHalfOpacity[index], 46 | yAxisID: `y`, 47 | }; 48 | }) 49 | //reverse the array in order to display the most recent data first 50 | .reverse(); 51 | 52 | 53 | //declare a constant data and set it equal to an object with the following properties: labels and datasets 54 | const data = { 55 | labels: labels, // [..] 56 | datasets: datasets, // [{..}, {..}, {..}] 57 | }; 58 | 59 | //declare a constant options and set it equal to the Options function 60 | const options = Options( 61 | 'CPU Credit Usage', 62 | 'Number of credits used by each EC2 instance at 8 hour intervals for the past week.' 63 | ); 64 | 65 | //return a div with a class of chart-wrapper and a Line component with the following properties: data and options 66 | return ( 67 |
68 | 69 |
70 | ); 71 | }; 72 | 73 | export default CPUCreditUsageChart; 74 | -------------------------------------------------------------------------------- /src/components/CPUSurplusCreditBalanceChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Options from './LineChartOptions.js'; 5 | import { Colors, ColorsHalfOpacity } from './ColorGenerator.js'; 6 | import '../componentStyling/EC2ChartStyling.scss'; 7 | 8 | //declare a constant CPUCreditBalanceChart and set it equal to an arrow function that takes in props as a parameter 9 | const CPUSurplusCreditBalanceChart = (props) => { 10 | const { chartData } = props; 11 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array 12 | const labels = chartData.timestamps 13 | .map((timestamp) => { 14 | //convert the timestamp to a date object 15 | const date = new Date(timestamp); 16 | // const month = date.getMonth() + 1; 17 | // const day = date.getDate(); 18 | //declare a constant hour and set it equal to the hour of the date object 19 | let hour = date.getHours(); 20 | //declare a constant minute and set it equal to the minutes of the date object 21 | let minute = date.getMinutes(); 22 | //if the hour is less than 10, add a 0 to the beginning of the hour 23 | if (hour < 10) { 24 | hour = `0${hour}`; 25 | } 26 | //if the minute is less than 10, add a 0 to the beginning of the minute 27 | if (minute < 10) { 28 | minute = `0${minute}`; 29 | } 30 | //return the hour and minute 31 | return `${hour}:${minute}`; 32 | }) 33 | //reverse the array in order to display the most recent data first 34 | .reverse(); //[timestamps] 35 | 36 | //declare a constant datasets and use the map method to iterate over the values array and return a new array of objects for each element in the values array 37 | const datasets = chartData.values 38 | .map((array, index) => { 39 | //return an object with the following properties: label, data, borderColor, backgroundColor, and yAxisID 40 | return { 41 | label: chartData.instanceIds[index], 42 | data: array, 43 | borderColor: Colors[index], 44 | backgroundColor: ColorsHalfOpacity[index], 45 | yAxisID: `y`, 46 | }; 47 | }) 48 | //reverse the array in order to display the most recent data first 49 | .reverse(); 50 | 51 | //declare a constant data and set it equal to an object with the following properties: labels and datasets 52 | const data = { 53 | labels: labels, // [..] 54 | datasets: datasets, // [{..}, {..}, {..}] 55 | }; 56 | 57 | //declare a constant options and set it equal to the Options function 58 | const options = Options( 59 | 'CPU Surplus Credit Balance', 60 | 'Number of credits remaining for each EC2 instance at 8 hour intervals for the past week.' 61 | ); 62 | 63 | return ( 64 |
65 | 66 |
67 | ); 68 | }; 69 | 70 | export default CPUSurplusCreditBalanceChart; 71 | -------------------------------------------------------------------------------- /src/components/CPUUtilizationChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | // import Chart from 'chart.js/auto'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Options from './LineChartOptions.js'; 5 | import { Colors, ColorsHalfOpacity } from './ColorGenerator.js'; 6 | import '../componentStyling/EC2ChartStyling.scss'; 7 | 8 | //declare a constant CPUCreditBalanceChart and set it equal to an arrow function that takes in props as a parameter 9 | const CPUUtilizationChart = (props) => { 10 | //declare a constant chartData and set it equal to props.chartData 11 | const { chartData } = props; 12 | // console.log('cpu utilization data: ', chartData); 13 | console.log('chartData', chartData); 14 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array 15 | const labels = chartData.timestamps 16 | .map((timestamp) => { 17 | //convert the timestamp to a date object 18 | const date = new Date(timestamp); 19 | // const month = date.getMonth() + 1; 20 | // const day = date.getDate(); 21 | //declare a constant hour and set it equal to the hour of the date object 22 | let hour = date.getHours(); 23 | //declare a constant minute and set it equal to the minutes of the date object 24 | let minute = date.getMinutes(); 25 | //if the hour is less than 10, add a 0 to the beginning of the hour 26 | if (hour < 10) { 27 | //add a 0 to the beginning of the hour 28 | hour = `0${hour}`; 29 | } 30 | //if the minute is less than 10, add a 0 to the beginning of the minute 31 | if (minute < 10) { 32 | //add a 0 to the beginning of the minute 33 | minute = `0${minute}`; 34 | } 35 | //return the hour and minute 36 | return `${hour}:${minute}`; 37 | }) 38 | //reverse the array in order to display the most recent data first 39 | .reverse(); //[timestamps] 40 | 41 | //declare a constant datasets and use the map method to iterate over the values array and return a new array of objects for each element in the values array 42 | const datasets = chartData.values 43 | //reverse the array in order to display the most recent data first 44 | .map((array, index) => { 45 | //return an object with the following properties: label, data, borderColor, backgroundColor, and yAxisID in order to display the label, data, border color, background color, and y axis of the chart 46 | return { 47 | label: chartData.instanceIds[index], 48 | data: array, 49 | borderColor: Colors[index], 50 | backgroundColor: ColorsHalfOpacity[index], 51 | yAxisID: `y`, 52 | }; 53 | }) 54 | //reverse the array in order to display the most recent data first 55 | .reverse(); 56 | 57 | //declare a constant data and set it equal to an object with the following properties: labels and datasets in order to display the labels and datasets of the chart 58 | const data = { 59 | labels: labels, // [..] 60 | datasets: datasets, // [{..}, {..}, {..}] 61 | }; 62 | console.log('data in cpuchart', data); 63 | //declare a constant options and set it equal to the Options function that takes in a title and a description as parameters in order to display the title and description of the chart 64 | const options = Options( 65 | 'CPU Utilization', 66 | 'Utilization as a percentage across all EC2 instances every eight hours for the past week.' 67 | ); 68 | 69 | return ( 70 |
71 | 72 |
73 | ); 74 | }; 75 | 76 | export default CPUUtilizationChart; 77 | -------------------------------------------------------------------------------- /src/components/ColorGenerator.js: -------------------------------------------------------------------------------- 1 | export const Colors = { 2 | 0: '#9DD3C8', 3 | 1: '#48BCCA', 4 | 2: '#318B9B', 5 | 3: '#23596B', 6 | 4: '#01293B', 7 | 5: '#BC7C9C', 8 | 6: '#A96DA3', 9 | 7: '#7A5980', 10 | 8: '#3B3B58', 11 | 9: '#BFB1C1', 12 | 10: '#B5BEC6', 13 | 11: '#C7DBE6', 14 | 12: '#B39C4D', 15 | 13: '#768948', 16 | 14: '#607744', 17 | 15: '#34623F', 18 | 16: '#F2C14E', 19 | 17: '#F78154', 20 | 18: '#B4436C', 21 | 19: '#5D2E8C', 22 | 20: '#51A3A3', 23 | 21: '#CB904D', 24 | 22: '#DFCC74', 25 | 23: '#C3E991', 26 | 24: '#80CED7', 27 | 25: '#8E6C88', 28 | 26: '#263D42', 29 | 27: '#0D1B1E', 30 | 28: '#F2C57C', 31 | 29: '#7FB685', 32 | 30: '#EF6F6C', 33 | }; 34 | 35 | export const ColorsHalfOpacity = { 36 | 0: 'rgba(157, 211, 200, 0.5)', 37 | 1: 'rgba(72, 188, 202, 0.5)', 38 | 2: 'rgba(49, 139, 155, 0.5)', 39 | 3: 'rgba(35, 89, 107,0.5)', 40 | 4: 'rgba(1, 41, 59, 0.5)', 41 | 5: 'rgba(188, 124, 156, 0.5)', 42 | 6: 'rgba(169, 109, 163, 0.5)', 43 | 7: 'rgba(122, 89, 128, 0.5)', 44 | 8: 'rgba(59, 59, 88, 0.5)', 45 | 9: 'rgba(191, 177, 193, 0.5)', 46 | 10: 'rgba(181, 190, 198, 0.5)', 47 | 11: 'rgba(199, 219, 230, 0.5)', 48 | 12: 'rgba(179, 156, 77, 0.5)', 49 | 13: 'rgba(118, 137, 72, 0.5)', 50 | 14: 'rgba(96, 119, 68, 0.5)', 51 | 15: 'rgba(52, 98, 63, 0.5)', 52 | 16: 'rgba(242, 193, 78, 0.5)', 53 | 17: 'rgba(247, 129, 84, 0.5)', 54 | 18: 'rgba(180, 67, 108, 0.5)', 55 | 19: 'rgba(93, 46, 140, 0.5)', 56 | 20: 'rgba(81, 163, 163, 0.5)', 57 | 21: 'rgba(203, 144, 77, 0.5)', 58 | 22: 'rgba(223, 204, 116, 0.5)', 59 | 23: 'rgba(195, 233, 145, 0.5)', 60 | 24: 'rgba(128, 206, 215, 0.5)', 61 | 25: 'rgba(142, 108, 136, 0.5)', 62 | 26: 'rgba(38, 61, 66, 0.5)', 63 | 27: 'rgba(13, 27, 30, 0.5)', 64 | 28: 'rgba(242, 197, 124, 0.5)', 65 | 29: 'rgba(127, 182, 133, 0.5)', 66 | 30: 'rgba(239, 111, 108, 0.5)', 67 | }; 68 | -------------------------------------------------------------------------------- /src/components/DurationChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Filler, 11 | Legend, 12 | } from 'chart.js'; 13 | import { Line } from 'react-chartjs-2'; 14 | import Options from './AreaChartOptions.js'; 15 | import '../componentStyling/LambdaChartStyling.scss'; 16 | 17 | //declare a constant DurationChart and set it equal to an arrow function that takes in props as a parameter in order to access the chartData prop 18 | const DurationChart = (props) => { 19 | const { chartData } = props; 20 | 21 | //declare a constant options and set it equal to the Options function in order to set the options for the chart 22 | const options = Options( 23 | 'Duration', 24 | 'Average processing duration for this function every hour for the past week.' 25 | ); 26 | 27 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array in order to display the timestamps on the x-axis 28 | const labels = chartData.timestamps 29 | .map((timestamp) => { 30 | //convert the timestamp to a date object 31 | const date = new Date(timestamp); 32 | //declare a constant month and set it equal to the month of the date object 33 | const month = date.getMonth() + 1; 34 | //declare a constant day and set it equal to the day of the date object 35 | const day = date.getDate(); 36 | //declare a constant hour and set it equal to the hour of the date object 37 | let hour = date.getHours(); 38 | //declare a constant minute and set it equal to the minutes of the date object 39 | let minute = date.getMinutes(); 40 | //if the hour is less than 10, add a 0 to the beginning of the hour 41 | if (hour < 10) { 42 | hour = `0${hour}`; 43 | } 44 | //if the minute is less than 10, add a 0 to the beginning of the minute 45 | if (minute < 10) { 46 | //add a 0 to the beginning of the minute in order to display the time in the format of 00:00 47 | minute = `0${minute}`; 48 | } 49 | //return the month, day, hour, and minute in order to display the time in the format of 00/00 00:00 50 | return `${month}/${day} ${hour}:${minute}`; 51 | }) 52 | //reverse the array in order to display the most recent data first 53 | .reverse(); //[timestamps] 54 | 55 | //declare a constant data and set it equal to an object with the following properties: labels and datasets in order to display the data on the chart 56 | const data = { 57 | labels, 58 | datasets: [ 59 | { 60 | fill: true, 61 | label: 'Duration', 62 | data: chartData.values, 63 | borderColor: 'rgb(153, 102, 255)', 64 | backgroundColor: 'rgba(153, 102, 255, 0.5)', 65 | }, 66 | ], 67 | }; 68 | 69 | return ( 70 |
71 | 72 |
73 | ); 74 | }; 75 | 76 | export default DurationChart; 77 | -------------------------------------------------------------------------------- /src/components/ErrorsChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Filler, 11 | Legend, 12 | } from 'chart.js'; 13 | import { Line } from 'react-chartjs-2'; 14 | import Options from './AreaChartOptions.js'; 15 | import '../componentStyling/LambdaChartStyling.scss'; 16 | 17 | //declare a constant DurationChart and set it equal to an arrow function that takes in props as a parameter in order to access the chartData prop 18 | const ErrorsChart = (props) => { 19 | const { chartData } = props; 20 | 21 | //declare a constant options and set it equal to the Options function in order to set the options for the chart 22 | const options = Options( 23 | 'Errors', 24 | 'Total errors for this function every hour for the past week.' 25 | ); 26 | 27 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array in order to display the timestamps on the x-axis 28 | const labels = chartData.timestamps 29 | .map((timestamp) => { 30 | const date = new Date(timestamp); 31 | const month = date.getMonth() + 1; 32 | const day = date.getDate(); 33 | let hour = date.getHours(); 34 | let minute = date.getMinutes(); 35 | //add a 0 to the beginning of the hour in order to display the time in the format of 00:00 36 | if (hour < 10) { 37 | hour = `0${hour}`; 38 | } 39 | //add a 0 to the beginning of the minute in order to display the time in the format of 00:00 40 | if (minute < 10) { 41 | minute = `0${minute}`; 42 | } 43 | //return the month, day, hour, and minute in order to display the time in the format of 00/00 00:00 44 | return `${month}/${day} ${hour}:${minute}`; 45 | }) 46 | //reverse the array in order to display the most recent data first 47 | .reverse(); //[timestamps] 48 | 49 | //declare a constant data and set it equal to an object with the following properties: labels and datasets in order to display the data on the chart 50 | const data = { 51 | labels, 52 | datasets: [ 53 | { 54 | fill: true, 55 | label: 'Errors', 56 | data: chartData.values, 57 | borderColor: 'rgb(75, 192, 192)', 58 | backgroundColor: 'rgba(75, 192, 192, 0.5)', 59 | }, 60 | ], 61 | }; 62 | 63 | //return the Line component in order to display the chart 64 | return ( 65 |
66 | 67 |
68 | ); 69 | }; 70 | 71 | export default ErrorsChart; 72 | -------------------------------------------------------------------------------- /src/components/Footer.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import '../componentStyling/Footer.scss'; 4 | 5 | //declare a constant Footer and declare constants navigate and redirectToPrivacyPolicy and set them equal to useNavigate and an arrow function that returns navigate('/privacy-policy') 6 | const Footer = () => { 7 | const navigate = useNavigate(); 8 | const redirectToPrivacyPolicy = () => { 9 | return navigate('/privacy-policy'); 10 | }; 11 | 12 | //return the following JSX 13 | return ( 14 |
15 |
16 |
17 |

Cloudband

18 |

19 | Cloudband is open source. Help us make our app better:  20 | 21 | Github 22 | 23 |

24 |
25 |
26 |

Quick Links

27 |
    28 |
  • 29 | 30 |
  • 31 |
32 |
33 |
34 |

Contact Us

35 |

36 | Email us at  37 | cloudbandEC37@gmail.com 38 |

39 |
40 |
41 |
42 | ); 43 | }; 44 | 45 | export default Footer; 46 | -------------------------------------------------------------------------------- /src/components/InvocationsChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Filler, 11 | Legend, 12 | } from 'chart.js'; 13 | import { Line } from 'react-chartjs-2'; 14 | import Options from './AreaChartOptions.js'; 15 | import '../componentStyling/LambdaChartStyling.scss'; 16 | 17 | //declare a constant InvocationsChart and set it equal to an arrow function that takes in props as a parameter in order to access the chartData prop 18 | const InvocationsChart = (props) => { 19 | const { chartData } = props; 20 | //declare a constant options and set it equal to the Options function in order to set the options for the chart 21 | const options = Options( 22 | 'Invocations', 23 | 'The sum of invocations of this function every hour for the past week.' 24 | ); 25 | 26 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array in order to display the timestamps on the x-axis 27 | const labels = chartData.timestamps 28 | .map((timestamp) => { 29 | const date = new Date(timestamp); 30 | const month = date.getMonth() + 1; 31 | const day = date.getDate(); 32 | let hour = date.getHours(); 33 | let minute = date.getMinutes(); 34 | //if the hour is less than 10, add a 0 to the beginning of the hour 35 | if (hour < 10) { 36 | hour = `0${hour}`; 37 | } 38 | //if the minute is less than 10, add a 0 to the beginning of the minute 39 | if (minute < 10) { 40 | minute = `0${minute}`; 41 | } 42 | //return the month, day, hour, and minute in order to display the time in the format of 00/00 00:00 43 | return `${month}/${day} ${hour}:${minute}`; 44 | }) 45 | //reverse the array in order to display the most recent data first 46 | .reverse(); //[timestamps] 47 | 48 | //declare a constant data and set it equal to an object with the following properties: labels and datasets in order to display the data on the chart 49 | const data = { 50 | labels, 51 | datasets: [ 52 | { 53 | fill: true, 54 | label: 'Invocations', 55 | data: chartData.values, 56 | borderColor: 'rgb(53, 162, 235)', 57 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 58 | }, 59 | ], 60 | }; 61 | 62 | return ( 63 |
64 | 65 |
66 | ); 67 | }; 68 | 69 | export default InvocationsChart; 70 | -------------------------------------------------------------------------------- /src/components/LambdaLogs.jsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import Paper from '@mui/material/Paper'; 3 | import Table from '@mui/material/Table'; 4 | import TableBody from '@mui/material/TableBody'; 5 | import TableCell from '@mui/material/TableCell'; 6 | import TableContainer from '@mui/material/TableContainer'; 7 | import TableHead from '@mui/material/TableHead'; 8 | import TablePagination from '@mui/material/TablePagination'; 9 | import TableRow from '@mui/material/TableRow'; 10 | import Typography from '@mui/material/Typography'; 11 | 12 | //declare a constant columns and set it equal to an array of objects with the following properties: id, label, minWidth, format 13 | const columns = [ 14 | { 15 | id: 'timestamp', 16 | label: 'TimeStamp', 17 | minWidth: 270, 18 | format: (value) => value.toLocaleString('en-US'), 19 | }, 20 | { 21 | id: 'message', 22 | label: 'Logs', 23 | minWidth: 400, 24 | align: 'left', 25 | format: (value) => value.toLocaleString('en-US'), 26 | }, 27 | ]; 28 | 29 | //declare a constant LambdaLogsTable and set it equal to an arrow function that takes in props as a parameter 30 | const LambdaLogsTable = (props) => { 31 | const { logs } = props; 32 | 33 | //declare a constant formattedLogs and set it equal to the map method to iterate over the logs array and return a new array of objects for each element in the logs array 34 | const formattedLogs = logs.map((log) => { 35 | const message = log.message; 36 | const date = new Date(log.timestamp); 37 | const month = date.getMonth() + 1; 38 | const day = date.getDate(); 39 | let hour = date.getHours(); 40 | let minute = date.getMinutes(); 41 | //if the hour is less than 10, add a 0 to the beginning of the hour 42 | if (hour < 10) { 43 | hour = `0${hour}`; 44 | } 45 | //if the minute is less than 10, add a 0 to the beginning of the minute 46 | if (minute < 10) { 47 | minute = `0${minute}`; 48 | } 49 | //return an object with the following properties: message and timestamp 50 | return { 51 | message, 52 | timestamp: `${month}/${day} ${hour}:${minute}`, 53 | }; 54 | }); 55 | //declare a constant rows and set it equal to the value of formattedLogs 56 | const rows = formattedLogs; 57 | //declare a constant [page, setPage] and set it equal to React.useState and pass in 0 as an argument 58 | const [page, setPage] = React.useState(0); 59 | const [rowsPerPage, setRowsPerPage] = React.useState(10); 60 | //declare a constant handleChangePage and set it equal to an arrow function that takes in event and newPage as parameters 61 | const handleChangePage = (event, newPage) => { 62 | setPage(newPage); 63 | }; 64 | //declare a constant handleChangeRowsPerPage and set it equal to an arrow function that takes in event as a parameter 65 | const handleChangeRowsPerPage = (event) => { 66 | setRowsPerPage(+event.target.value); 67 | setPage(0); 68 | }; 69 | 70 | return ( 71 | 79 | 80 | 94 | Logs 95 | 96 | 97 | 98 | 99 | {columns.map((column) => ( 100 | 110 | {column.label} 111 | 112 | ))} 113 | 114 | 115 | 116 | {rows 117 | .slice(page * rowsPerPage, page * rowsPerPage + rowsPerPage) 118 | .map((row, index) => { 119 | return ( 120 | 121 | {columns.map((column) => { 122 | const value = row[column.id]; 123 | return ( 124 | 133 | {column.format && typeof value === 'number' 134 | ? column.format(value) 135 | : value} 136 | 137 | ); 138 | })} 139 | 140 | ); 141 | })} 142 | 143 |
144 |
145 | 159 |
160 | ); 161 | }; 162 | 163 | export default LambdaLogsTable; 164 | -------------------------------------------------------------------------------- /src/components/LandingPage.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import UpdateIcon from '@mui/icons-material/Update'; 4 | import QueryStatsIcon from '@mui/icons-material/QueryStats'; 5 | import CompareArrowsIcon from '@mui/icons-material/CompareArrows'; 6 | import GitHubIcon from '@mui/icons-material/GitHub'; 7 | import LinkedInIcon from '@mui/icons-material/LinkedIn'; 8 | import '../componentStyling/LandingPage.scss'; 9 | 10 | const LandingPage = () => { 11 | const navigate = useNavigate(); 12 | 13 | // redirects to get-started when sign up button is clicked 14 | const redirectToSignup = (event) => { 15 | return navigate('/get-started'); 16 | }; 17 | 18 | // reusable component highlighting a feature of the app 19 | const featureBox = (icon, heading, subheading) => { 20 | return ( 21 |
22 | {icon} 23 |

{heading}

24 |

{subheading}

25 |
26 | ); 27 | }; 28 | 29 | // reusable component that renders text and a larger picture grouped horizontally 30 | const highlight = ( 31 | orientation, 32 | primaryText, 33 | subText, 34 | imgLink, 35 | imgAltText 36 | ) => { 37 | if (orientation === 'text-left') { 38 | return ( 39 |
40 |
41 |

{primaryText}

42 |

{subText}

43 |
44 | {imgAltText} 45 |
46 | ); 47 | } else if (orientation === 'text-right') { 48 | return ( 49 |
50 | {imgAltText} 51 |
52 |

{primaryText}

53 |

{subText}

54 |
55 |
56 | ); 57 | } else { 58 | console.log('Error: no orientation specified on highlight component'); 59 | } 60 | }; 61 | 62 | // reusable component that renders team member information 63 | const teamMember = (imgLink, name, github, linkedIn) => { 64 | return ( 65 |
66 | {name} 67 |

{name}

68 |

Software Engineer

69 | 77 |
78 | ); 79 | }; 80 | 81 | return ( 82 |
83 |
84 |
85 |

An AWS Metric Visualizer

86 |

87 | Optimize your AWS resource efficiency by viewing your EC2 metrics 88 | and Lambda function data in one centralized interface. 89 |

90 | 93 | 94 | 95 | 96 |
97 | Cloudband-app-screenshot 101 |
102 |
103 |
104 |

Features

105 |
106 |
107 | {featureBox( 108 | , 109 | 'Your most recent data at your fingertips', 110 | 'With each login, your most recent data will be fetched and displayed.' 111 | )} 112 | {featureBox( 113 | , 114 | 'Compare metrics across instances', 115 | 'Hide or display instances to compare only the data you want to see.' 116 | )} 117 | {featureBox( 118 | , 119 | 'Switch between EC2 or Lambda data with just a click', 120 | 'Toggle between views to see all your data in the same console.' 121 | )} 122 |
123 |
124 |
125 | {highlight( 126 | 'text-left', 127 | 'Conventiently compiled EC2 Metric Datasets', 128 | 'With two tailored views, you can see all of your CPU or Network In/Out data for all of your EC2 Instances at a glance.', 129 | 'https://cloudband.s3.amazonaws.com/Cloudband_toggle-ec2-metrics-cropped.gif', 130 | 'toggle-ec2-metrics-gif' 131 | )} 132 | {highlight( 133 | 'text-right', 134 | 'Compare between all EC2 instances or just the ones you want to see', 135 | "By clicking on the instance id, you can easily toggle an instance's data on or off to view only what you want to see.", 136 | 'https://cloudband.s3.amazonaws.com/Cloudband_toggleEC2instances_cropped.gif', 137 | 'toggle-ec2-instances-gif' 138 | )} 139 | {highlight( 140 | 'text-left', 141 | 'Easily toggle between EC2 Metrics and Lambda functions', 142 | 'The Cloudband interface allows you to seamlessly switch between your EC2 and Lambda metrics for a more convenient way to view your data.', 143 | 'https://cloudband.s3.amazonaws.com/Cloudband-toggle-lambda-functions.gif', 144 | 'placeholder image' 145 | )} 146 |
147 |
148 |

Our Team

149 |
150 | {teamMember( 151 | 'https://cloudband.s3.amazonaws.com/camille_salter_headshot.png', 152 | 'Camille Salter', 153 | 'https://github.com/CamSalter', 154 | 'https://www.linkedin.com/in/camille-salter' 155 | )} 156 | {teamMember( 157 | 'https://cloudband.s3.amazonaws.com/caroline_kimball_headshot.png', 158 | 'Caroline Kimball', 159 | 'https://github.com/kimballcaroline', 160 | 'https://www.linkedin.com/in/kimballcaroline' 161 | )} 162 | {teamMember( 163 | 'https://cloudband.s3.amazonaws.com/greg_jenner_portrait.png', 164 | 'Greg Jenner', 165 | 'https://github.com/gregjenner', 166 | 'https://www.linkedin.com/in/greg-o-jenner' 167 | )} 168 | {teamMember( 169 | 'https://cloudband.s3.amazonaws.com/john_donovan_headshot.png', 170 | 'John Donovan', 171 | 'https://github.com/jodonovan845', 172 | 'https://www.linkedin.com/in/john-d-donovan' 173 | )} 174 | {teamMember( 175 | 'https://cloudband.s3.amazonaws.com/tomas_kim_headshot.png', 176 | 'Tomas Kim', 177 | 'https://github.com/tk0885', 178 | 'https://www.linkedin.com/in/tomasjskim' 179 | )} 180 |
181 |
182 |
183 | ); 184 | }; 185 | 186 | export default LandingPage; 187 | -------------------------------------------------------------------------------- /src/components/LineChartOptions.js: -------------------------------------------------------------------------------- 1 | const Options = (title, subtitle) => { 2 | const options = { 3 | responsive: true, 4 | interaction: { 5 | mode: 'index', 6 | intersect: false, 7 | }, 8 | stacked: false, 9 | plugins: { 10 | title: { 11 | display: true, 12 | align: 'start', 13 | text: title, 14 | padding: { 15 | top: 10, 16 | bottom: 10, 17 | }, 18 | font: { 19 | size: 18, 20 | fontFamily: 'Quicksand', 21 | }, 22 | color: 'rgba(3, 41, 62, 0.5)', 23 | }, 24 | subtitle: { 25 | display: true, 26 | text: subtitle, 27 | align: 'start', 28 | padding: { 29 | top: 0, 30 | bottom: 30, 31 | }, 32 | font: { 33 | // weight: 'bold', 34 | size: 12, 35 | fontFamily: 'Quicksand', 36 | }, 37 | color: 'rgba(3, 41, 62, 0.5)', 38 | }, 39 | legend: { 40 | position: 'left', 41 | labels: { 42 | padding: 20, 43 | }, 44 | title: { 45 | display: true, 46 | text: 'EC2 Instance Ids', 47 | font: { 48 | weight: 'bold', 49 | size: 12, 50 | fontFamily: 'Quicksand', 51 | }, 52 | color: 'rgba(3, 41, 62, 0.5)', 53 | padding: { 54 | top: 20, 55 | bottom: 0, 56 | }, 57 | }, 58 | }, 59 | }, 60 | scales: { 61 | y: { 62 | type: 'linear', 63 | display: true, 64 | position: 'left', 65 | }, 66 | }, 67 | transitions: { 68 | show: { 69 | animations: { 70 | x: { 71 | from: 0, 72 | }, 73 | y: { 74 | from: 0, 75 | }, 76 | }, 77 | }, 78 | hide: { 79 | animations: { 80 | x: { 81 | to: 0, 82 | }, 83 | y: { 84 | to: 0, 85 | }, 86 | }, 87 | }, 88 | }, 89 | }; 90 | 91 | return options; 92 | }; 93 | 94 | export default Options; 95 | -------------------------------------------------------------------------------- /src/components/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import axios from 'axios'; 3 | import { Navigate } from 'react-router-dom'; 4 | import '../componentStyling/Login.scss'; 5 | 6 | const Login = (props) => { 7 | const { loggedIn, setLoggedIn, setArn, setRegion } = props; 8 | const [email, setEmail] = useState(''); 9 | const [password, setPassword] = useState(''); 10 | 11 | // request to send login information to server to set login, arn, and region state 12 | const handleSubmit = (event) => { 13 | event.preventDefault(); 14 | axios 15 | .post('/signin', { 16 | email: email, 17 | password: password, 18 | }) 19 | .then((response) => { 20 | setLoggedIn(true); 21 | setArn(response.data.newUser.RoleARN); 22 | setRegion(response.data.newUser.region); 23 | }) 24 | .catch((error) => { 25 | console.error('error in sign up request: ', error); 26 | }); 27 | }; 28 | // automatically routes to visualer if logged in, otherwise renders login page 29 | if (loggedIn) { 30 | return ; 31 | } else { 32 | return ( 33 |
34 |
35 |

Login

36 | { 41 | setEmail(e.target.value); 42 | }} 43 | /> 44 | { 49 | setPassword(e.target.value); 50 | }} 51 | /> 52 | 55 |
56 |
57 | ); 58 | } 59 | }; 60 | 61 | export default Login; 62 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import '../componentStyling/Navbar.scss'; 5 | 6 | //declare a constant Navbar and set it equal to an arrow function that takes in props as a parameter 7 | function Navbar(props) { 8 | //declare a constant navigate and set it equal to the invocation of the useNavigate hook 9 | const navigate = useNavigate(); 10 | //declare a constant loggedIn and set it equal to the value of the loggedIn property on the props object 11 | const { loggedIn, setLoggedIn } = props; 12 | 13 | //declare a constant logoutUser and set it equal to an arrow function that takes in event as a parameter 14 | const logoutUser = (event) => { 15 | //make a request to the /logout endpoint and then invoke the setLoggedIn function and pass in false as an argument and then return the invocation of the navigate function and pass in '/login' as an argument 16 | axios.delete('/logout').then(() => { 17 | setLoggedIn(false); 18 | return navigate('/login'); 19 | }); 20 | }; 21 | 22 | //declare a constant redirectToVisualizer and set it equal to an arrow function that returns the invocation of the navigate function and pass in '/visualizer' as an argument 23 | const redirectToVisualizer = () => { 24 | return navigate('/visualizer'); 25 | }; 26 | 27 | //if loggedIn is truthy then return the following JSX 28 | if (loggedIn) { 29 | return ( 30 |
31 |
32 | clouds-with-structural-lines 37 | 38 | Cloudband 39 | 40 |
41 | 56 |
57 | ); 58 | //otherwise, return the following JSX 59 | } else { 60 | return ( 61 |
62 |
63 | clouds-with-structural-lines 68 | 69 | Cloudband 70 | 71 |
72 | 87 |
88 | ); 89 | } 90 | } 91 | 92 | export default Navbar; 93 | -------------------------------------------------------------------------------- /src/components/NetworkInChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Options from './LineChartOptions.js'; 5 | import { Colors, ColorsHalfOpacity } from './ColorGenerator.js'; 6 | import '../componentStyling/EC2ChartStyling.scss'; 7 | 8 | //declare a constant networkInChart and set it equal to an arrow function that takes in props as a parameter 9 | const NetworkInChart = (props) => { 10 | //declare a constant chartData and set it equal to props.chartData 11 | const { chartData } = props; 12 | 13 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array 14 | const labels = chartData.timestamps 15 | .map((timestamp) => { 16 | //convert the timestamp to a date object 17 | const date = new Date(timestamp); 18 | // const month = date.getMonth() + 1; 19 | // const day = date.getDate(); 20 | let hour = date.getHours(); 21 | let minute = date.getMinutes(); 22 | //if the hour is less than 10, add a 0 to the beginning of the hour 23 | if (hour < 10) { 24 | hour = `0${hour}`; 25 | } 26 | //if the minute is less than 10, add a 0 to the beginning of the minute 27 | if (minute < 10) { 28 | minute = `0${minute}`; 29 | } 30 | //return the hour and minute 31 | return `${hour}:${minute}`; 32 | }) 33 | //reverse the array in order to display the most recent data first 34 | .reverse(); //[timestamps] 35 | 36 | //declare a constant datasets and use the map method to iterate over the values array and return a new array of objects for each element in the values array 37 | const datasets = chartData.values 38 | .map((array, index) => { 39 | return { 40 | label: chartData.instanceIds[index], 41 | data: array, 42 | borderColor: Colors[index], 43 | backgroundColor: ColorsHalfOpacity[index], 44 | yAxisID: `y`, 45 | }; 46 | }) 47 | //reverse the array in order to display the most recent data first 48 | .reverse(); 49 | 50 | //declare a constant data and set it equal to an object with the following properties: labels and datasets 51 | const data = { 52 | labels: labels, // [..] 53 | datasets: datasets, // [{..}, {..}, {..}] 54 | }; 55 | 56 | //declare a constant options in order to set the options for the chart 57 | const options = Options( 58 | 'Network In', 59 | 'Number of bytes sent in for each EC2 instance at 8 hour intervals for the past week.', 60 | 'EC2 Instance Ids' 61 | ); 62 | 63 | return ( 64 |
65 | 66 |
67 | ); 68 | }; 69 | 70 | export default NetworkInChart; 71 | -------------------------------------------------------------------------------- /src/components/NetworkOutChart.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from 'react'; 2 | import Chart from 'chart.js/auto'; 3 | import { Line } from 'react-chartjs-2'; 4 | import Options from './LineChartOptions.js'; 5 | import { Colors, ColorsHalfOpacity } from './ColorGenerator.js'; 6 | import '../componentStyling/EC2ChartStyling.scss'; 7 | 8 | 9 | //declare a constant NetworkOutChart and set it equal to an arrow function that takes in props as a parameter 10 | const NetworkOutChart = (props) => { 11 | //declare a constant chartData and set it equal to props.chartData 12 | const { chartData } = props; 13 | 14 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the timestamps array 15 | const labels = chartData.timestamps 16 | .map((timestamp) => { 17 | //convert the timestamp to a date object 18 | const date = new Date(timestamp); 19 | // const month = date.getMonth() + 1; 20 | // const day = date.getDate(); 21 | let hour = date.getHours(); 22 | let minute = date.getMinutes(); 23 | //if the hour is less than 10, add a 0 to the beginning of the hour 24 | if (hour < 10) { 25 | hour = `0${hour}`; 26 | } 27 | //if the minute is less than 10, add a 0 to the beginning of the minute 28 | if (minute < 10) { 29 | minute = `0${minute}`; 30 | } 31 | //return the hour and minute 32 | return `${hour}:${minute}`; 33 | }) 34 | //reverse the array in order to display the most recent data first 35 | .reverse(); //[timestamps] 36 | 37 | //declare a constant datasets and use the map method to iterate over the values array and return a new array of objects for each element in the values array 38 | const datasets = chartData.values 39 | .map((array, index) => { 40 | return { 41 | label: chartData.instanceIds[index], 42 | data: array, 43 | borderColor: Colors[index], 44 | backgroundColor: ColorsHalfOpacity[index], 45 | yAxisID: `y`, 46 | }; 47 | }) 48 | //reverse the array in order to display the most recent data first 49 | .reverse(); 50 | 51 | //declare a constant data and set it equal to an object with the following properties: labels and datasets 52 | const data = { 53 | labels: labels, // [..] 54 | datasets: datasets, // [{..}, {..}, {..}] 55 | }; 56 | 57 | //declare a constant options in order to set the options for the chart 58 | const options = Options( 59 | 'Network Out', 60 | 'Number of bytes sent out for each EC2 instance at 8 hour intervals for the past week.', 61 | 'EC2 Instance Ids' 62 | ); 63 | 64 | //return the chart 65 | return ( 66 |
67 | 68 |
69 | ); 70 | }; 71 | 72 | export default NetworkOutChart; 73 | -------------------------------------------------------------------------------- /src/components/PrivacyPolicy.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '../componentStyling/PrivacyPolicy.scss'; 3 | 4 | const PrivacyPolicy = () => { 5 | return ( 6 |
7 |

Privacy Policy

8 |

9 | Cloudband is committed to protecting the privacy of our users. Our web 10 | app, Cloudband, is designed to provide AWS developers with the ability 11 | to visualize different AWS resource metrics in real time. We do not 12 | collect any personal information from our users. 13 |
14 |
15 | We understand the importance of user privacy, and we do not collect, 16 | store, or share any personal information from our users. Our app does 17 | not use cookies, tracking pixels, or any other form of data collection. 18 |
19 |
20 | Our app may contain links to third-party websites or services. We are 21 | not responsible for the privacy practices or content of these 22 | third-party websites or services. 23 |
24 |
25 | We encourage users to read the privacy policies of any third-party 26 | websites or services before providing any personal information. If you 27 | have any questions or concerns about our privacy policy, please contact 28 | us at  29 | cloudbandEC37@gmail.com. 30 |

31 |
32 | ); 33 | }; 34 | 35 | export default PrivacyPolicy; 36 | -------------------------------------------------------------------------------- /src/components/Settings.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import '../componentStyling/Settings.scss'; 4 | 5 | const Settings = (props) => { 6 | const { 7 | ec2Metric, 8 | setEc2Metric, 9 | tool, 10 | setTool, 11 | funcNames, 12 | setFuncNames, 13 | arn, 14 | region, 15 | currFunc, 16 | setCurrFunc, 17 | } = props; 18 | 19 | // sets ec2 metric based on drop down select 20 | const onEC2MetricChange = (event) => { 21 | setEc2Metric(event.target.value); 22 | }; 23 | 24 | // changes between showing ec2 or lambda metrics/options based on drop down select 25 | const onToolChange = (event) => { 26 | setTool(event.target.value); 27 | }; 28 | 29 | // sets current lambda function based on drop down select 30 | const onCurrFuncChange = (event) => { 31 | setCurrFunc(event.target.value); 32 | }; 33 | 34 | // fetches lambda names when a user selects lambda to populate drop down and set the current lambda function 35 | useEffect(() => { 36 | axios 37 | .get(`http://localhost:3000/getLambdaNames`, { 38 | params: { 39 | arn, 40 | region, 41 | }, 42 | }) 43 | .then((response) => { 44 | console.log( 45 | 'lambda names response from Settings component axios call: ', 46 | response 47 | ); 48 | setFuncNames(response.data); 49 | setCurrFunc(response.data[0]); 50 | }) 51 | .catch((err) => { 52 | console.log(err); 53 | }); 54 | }, [tool]); 55 | 56 | // toggles showing ec2 options or lambda options based on drop down select 57 | function switchSettings() { 58 | if (tool === 'ec2') { 59 | return ( 60 |
61 | 62 |
63 | 72 |
73 |

Tip:

74 |

75 | Once your metrics have loaded, try clicking on the EC2 Instance Id's 76 | to add/remove the instance from your view. 77 |

78 |
79 | ); 80 | } else if (tool === 'lambda') { 81 | // populates lambda functions drop down with function names 82 | const lambdaDropdownOptions = funcNames.map((funcName, index) => { 83 | return ( 84 | 87 | ); 88 | }); 89 | 90 | return ( 91 |
92 | 93 |
94 | 102 |
103 |
104 | ); 105 | } 106 | } 107 | 108 | return ( 109 |
110 |

Settings

111 |

112 | Use the dropdowns below to choose which AWS tool and specific metrics 113 | you would like to view. 114 |

115 | 116 |
117 | 121 |
122 |
{switchSettings()}
123 |
124 | ); 125 | }; 126 | 127 | export default Settings; 128 | -------------------------------------------------------------------------------- /src/components/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { Navigate, useNavigate } from 'react-router-dom'; 3 | import axios from 'axios'; 4 | import '../componentStyling/Signup.scss'; 5 | //import AWS SDK 6 | const AWS = require('aws-sdk'); 7 | //set the region 8 | AWS.config.update({ region: 'REGION' }); 9 | 10 | //declare a constant Signup in order to create a new user. 11 | const Signup = () => { 12 | //declare a constant navigate and set it equal to the invocation of the useNavigate hook 13 | const navigate = useNavigate(); 14 | //declare a constant redirectToPrivacyPolicy and set it equal to an arrow function that returns the invocation of the navigate function and pass in '/privacy-policy' as an argument 15 | const redirectToPrivacyPolicy = () => { 16 | //return the invocation of the navigate function and pass in '/privacy-policy' as an argument 17 | return navigate('/privacy-policy'); 18 | }; 19 | 20 | //declare a constant [signedUp, setSignedUp] and set it equal to the invocation of the useState hook and pass in false as an argument 21 | const [signedUp, setSignedUp] = useState(false); 22 | //declare a constant [email, setEmail] and set it equal to the invocation of the useState hook and pass in an empty string as an argument 23 | const [email, setEmail] = useState(''); 24 | //declare a constant [password, setPassword] and set it equal to the invocation of the useState hook and pass in an empty string as an argument 25 | const [password, setPassword] = useState(''); 26 | //declare a constant [arn, setArn] and set it equal to the invocation of the useState hook and pass in an empty string as an argument 27 | const [arn, setArn] = useState(''); 28 | //declare a constant [region, setRegion] and set it equal to the invocation of the useState hook and pass in 'us-east-1' as an argument 29 | const [region, setRegion] = useState('us-east-1'); 30 | 31 | // request to server to add a user to the database and then route to login page 32 | const handleSubmit = (event) => { 33 | event.preventDefault(); 34 | //make a request to the /signup endpoint and then invoke the setSignedUp function and pass in true as an argument 35 | axios 36 | .post('/signup', { 37 | email: email, 38 | password: password, 39 | RoleARN: arn, 40 | region: region, 41 | }) 42 | .then((response) => { 43 | setSignedUp(true); 44 | }) 45 | .catch((err) => { 46 | console.log('error in sign up request: ', err); 47 | }); 48 | }; 49 | //if signedUp is truthy then return the following JSX 50 | if (signedUp) { 51 | return ; 52 | } 53 | //if signedUp is falsy then return the following JSX 54 | if (!signedUp) { 55 | return ( 56 |
57 |
58 |
59 |

Get Started!

60 |

Sign up in just a few quick steps.

61 |

62 | Follow this tutorial video on how to create a stack and generate 63 | the ARN.

This will grant Cloudband access to AWS 64 | metrics. For more information on what data we capture, please 65 | refer to our  66 | {/* privacy policy. */} 67 | 70 |

71 |
72 | 73 |
74 | 79 |
80 |
81 |
82 |
83 |

Step 1:

84 |
85 | 86 | 90 |
91 | 97 | 98 | 99 |
100 |
101 |

Step 2:

102 |

103 | Once stack creation has completed, head to the Output tab and copy 104 | the ARN and paste into the text box below 105 |

106 | 107 | { 112 | setArn(e.target.value); 113 | }} 114 | /> 115 | 116 |
117 | 155 |
156 | 161 | Not sure which region to choose? Check all regions 162 | 163 |
164 |
165 |

Step 3:

166 | 167 | { 172 | setEmail(e.target.value); 173 | }} 174 | /> 175 | 176 | { 181 | setPassword(e.target.value); 182 | console.log('password: ', e.target.value); 183 | }} 184 | /> 185 | 192 |
193 |
194 |
195 | ); 196 | } 197 | }; 198 | 199 | export default Signup; 200 | -------------------------------------------------------------------------------- /src/components/ThrottlesChart.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | Chart as ChartJS, 4 | CategoryScale, 5 | LinearScale, 6 | PointElement, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Filler, 11 | Legend, 12 | } from 'chart.js'; 13 | import { Line } from 'react-chartjs-2'; 14 | import Options from './AreaChartOptions.js'; 15 | import '../componentStyling/LambdaChartStyling.scss'; 16 | 17 | //declare a constant ThrottlesChart, pass in props as a parameter. Destructure chartData from props in order to access the chartData prop 18 | const ThrottlesChart = (props) => { 19 | const { chartData } = props; 20 | 21 | //declare a constant options and set it equal to the Options function in order to set the options for the chart 22 | const options = Options( 23 | 'Throttles', 24 | 'The sum of throttles on this function every hour for the past week.' 25 | ); 26 | 27 | //declare a constant labels and use the map method to iterate over the timestamps array and return a new array of timestamps for each element in the array. 28 | const labels = chartData.timestamps 29 | .map((timestamp) => { 30 | //declare a constant date and set it equal to a new Date object with the timestamp as a parameter 31 | const date = new Date(timestamp); 32 | //declare a constant month and set it equal to the month of the date object plus 1 in order to display the correct month 33 | const month = date.getMonth() + 1; 34 | //declare a constant day and set it equal to the day of the date object 35 | const day = date.getDate(); 36 | //declare a constant hour and set it equal to the hour of the date object 37 | let hour = date.getHours(); 38 | //declare a constant minute and set it equal to the minutes of the date object 39 | let minute = date.getMinutes(); 40 | //if the hour is less than 10, add a 0 to the beginning of the hour 41 | if (hour < 10) { 42 | hour = `0${hour}`; 43 | } 44 | //if the minute is less than 10, add a 0 to the beginning of the minute 45 | if (minute < 10) { 46 | minute = `0${minute}`; 47 | } 48 | //return the month, day, hour, and minute in order to display the time in the format of 00/00 00:00 49 | return `${month}/${day} ${hour}:${minute}`; 50 | }) 51 | //reverse the array in order to display the most recent data first 52 | .reverse(); //[timestamps] 53 | 54 | //declare a constant data and set it equal to an object with the following properties: labels and datasets in order to display the data on the chart 55 | const data = { 56 | labels, 57 | datasets: [ 58 | { 59 | fill: true, 60 | label: 'Throttles', 61 | data: chartData.values, 62 | borderColor: 'rgb(201, 203, 207)', 63 | backgroundColor: 'rgba(201, 203, 207, 0.5)', 64 | }, 65 | ], 66 | }; 67 | 68 | return ( 69 |
70 | 71 |
72 | ); 73 | }; 74 | 75 | export default ThrottlesChart; 76 | -------------------------------------------------------------------------------- /src/containerStyling/EC2ChartContainer.scss: -------------------------------------------------------------------------------- 1 | .chart-container-wrapper { 2 | display: flex; 3 | flex-wrap: wrap; 4 | justify-content: center; 5 | background-color: var(--light-color); 6 | min-height: 800px; 7 | 8 | .row { 9 | display: flex; 10 | flex-direction: row; 11 | flex-wrap: wrap; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/containerStyling/LambdaChartContainer.scss: -------------------------------------------------------------------------------- 1 | .lambda-chart-container { 2 | display: flex; 3 | flex-wrap: wrap; 4 | 5 | justify-content: center; 6 | background-color: var(--light-color); 7 | min-height: 800px; 8 | 9 | .row { 10 | display: flex; 11 | flex-direction: row; 12 | flex-wrap: wrap; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/containerStyling/MainContainer.scss: -------------------------------------------------------------------------------- 1 | .main-container-wrapper { 2 | display: flex; 3 | flex-direction: row; 4 | // border: 1px solid black; 5 | min-height: 693px; 6 | } 7 | -------------------------------------------------------------------------------- /src/containerStyling/swirlingclouds.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/cloudband/8d8ed48e903bb679a6d0ccef53d42c0e7d0d3f50/src/containerStyling/swirlingclouds.png -------------------------------------------------------------------------------- /src/containers/EC2ChartContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import axios from 'axios'; 3 | import CPUUtilizationChart from '../components/CPUUtilizationChart.jsx'; 4 | import NetworkInChart from '../components/NetworkInChart.jsx'; 5 | import NetworkOutChart from '../components/NetworkOutChart.jsx'; 6 | import CPUCreditUsageChart from '../components/CPUCreditUsageChart.jsx'; 7 | import CPUCreditBalanceChart from '../components/CPUCreditBalanceChart.jsx'; 8 | import CPUSurplusCreditBalanceChart from '../components/CPUSurplusCreditBalanceChart.jsx'; 9 | import '../containerStyling/EC2ChartContainer.scss'; 10 | 11 | //declare a constant defaultDataStructure and set it equal to an object with the following properties: values, timestamps, and instanceIds 12 | const defaultDataStructure = { 13 | values: [], 14 | timestamps: [], 15 | instanceIds: [], 16 | }; 17 | 18 | //declare a constant EC2ChartContainer and set it equal to an arrow function that takes in props as a parameter 19 | const EC2ChartContainer = (props) => { 20 | //declare a constant ec2Metric and set it equal to props.ec2Metric 21 | const { ec2Metric, arn, region } = props; 22 | //declare a constant [cpuUtilizationData, setCpuUtilizationData] and set it equal to useState and pass in defaultDataStructure as an argument 23 | const [cpuUtilizationData, setCpuUtilizationData] = 24 | useState(defaultDataStructure); 25 | //declare a constant [networkInData, setNetworkInData] and set it equal to useState and pass in defaultDataStructure as an argument 26 | const [networkInData, setNetworkInData] = useState(defaultDataStructure); 27 | //declare a constant [networkOutData, setNetworkOutData] and set it equal to useState and pass in defaultDataStructure as an argument 28 | const [networkOutData, setNetworkOutData] = useState(defaultDataStructure); 29 | //declare a constant [cpuCreditUsageData, setCpuCreditUsageData] and set it equal to useState and pass in defaultDataStructure as an argument 30 | const [cpuCreditUsageData, setCpuCreditUsageData] = 31 | useState(defaultDataStructure); 32 | //declare a constant [cpuCreditBalanceData, setCpuCreditBalanceData] and set it equal to useState and pass in defaultDataStructure as an argument 33 | const [cpuCreditBalanceData, setCpuCreditBalanceData] = 34 | useState(defaultDataStructure); 35 | //declare a constant [cpuSurplusCreditBalanceData, setCpuSurplusCreditBalanceData] and set it equal to useState and pass in defaultDataStructure as an argument 36 | const [cpuSurplusCreditBalanceData, setCpuSurplusCreditBalanceData] = 37 | useState(defaultDataStructure); 38 | 39 | //declare a useEffect hook 40 | useEffect(() => { 41 | //declare a constant fetchCloudwatchData and set it equal to an async function 42 | const fetchCloudwatchData = async () => { 43 | //declare a try/catch block 44 | try { 45 | //declare a constant response and set it equal to await axios.get and pass in the following arguments: `http://localhost:3000/${ec2Metric}`, and an object with the following properties: params and set it equal to an object with the following properties: arn and region 46 | const response = await axios.get(`http://localhost:3000/${ec2Metric}`, { 47 | params: { 48 | arn, 49 | region, 50 | }, 51 | }); 52 | //if ec2Metric is equal to 'network-in-out' 53 | if (ec2Metric === 'network-in-out') { 54 | //set the state of networkInData to an object 55 | setNetworkInData({ 56 | //passing in all the properties of defaultDataStructure 57 | ...defaultDataStructure, 58 | //passing in all the properties of response.data.NetworkIn or an empty object 59 | ...(response?.data?.NetworkIn ?? {}), 60 | }); 61 | //set the state of networkOutData to an object 62 | setNetworkOutData({ 63 | //passing in all the properties of defaultDataStructure 64 | ...defaultDataStructure, 65 | //passing in all the properties of response.data.NetworkOut or an empty object 66 | ...(response?.data?.NetworkOut ?? {}), 67 | }); 68 | } 69 | //if the ec2Metric is equal to 'cpu-credits' 70 | if (ec2Metric === 'cpu-credits') { 71 | //set the state of cpuUtilizationData to an object 72 | setCpuUtilizationData({ 73 | //passing in all the properties of defaultDataStructure 74 | ...defaultDataStructure, 75 | //passing in all the properties of response.data.CPUUtilization or an empty object 76 | ...(response?.data?.CPUUtilization ?? {}), 77 | }); 78 | //set the state of cpuCreditUsageData to an object 79 | setCpuCreditUsageData({ 80 | //passing in all the properties of defaultDataStructure 81 | ...defaultDataStructure, 82 | //passing in all the properties of response.data.CPUCreditUsage or an empty object 83 | ...(response?.data?.CPUCreditUsage ?? {}), 84 | }); 85 | //set the state of cpuCreditBalanceData to an object 86 | setCpuCreditBalanceData({ 87 | //passing in all the properties of defaultDataStructure 88 | ...defaultDataStructure, 89 | //passing in all the properties of response.data.CPUCreditBalance or an empty object 90 | ...(response?.data?.CPUCreditBalance ?? {}), 91 | }); 92 | //set the state of cpuSurplusCreditBalanceData to an object 93 | setCpuSurplusCreditBalanceData({ 94 | //passing in all the properties of defaultDataStructure 95 | ...defaultDataStructure, 96 | //passing in all the properties of response.data.CPUSurplusCreditBalance 97 | ...(response?.data?.CPUSurplusCreditBalance ?? {}), 98 | }); 99 | } 100 | } catch (error) { 101 | console.error(error); 102 | } 103 | }; 104 | //invoke the fetchCloudwatchData function 105 | fetchCloudwatchData(); 106 | //pass in ec2Metric as a dependency 107 | }, [ec2Metric]); 108 | 109 | // renders a different chart based on the ec2Metric a user selects in settings component 110 | function switchCharts() { 111 | //if ec2Metric is equal to 'network-in-out' 112 | if (ec2Metric === 'network-in-out') { 113 | //return the following JSX 114 | return ( 115 |
116 |
117 | 118 | 119 |
120 |
121 | ); 122 | //otherwise, if ec2Metric is equal to 'cpu-credits' 123 | } else if (ec2Metric === 'cpu-credits') { 124 | //return the following JSX 125 | return ( 126 |
127 |
128 | 129 | 130 |
131 |
132 | 133 | 136 |
137 |
138 | ); 139 | } 140 | } 141 | 142 | return
{switchCharts()}
; 143 | }; 144 | 145 | export default EC2ChartContainer; 146 | -------------------------------------------------------------------------------- /src/containers/LambdaChartContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import axios from 'axios'; 3 | import InvocationsChart from '../components/InvocationsChart.jsx'; 4 | import ThrottlesChart from '../components/ThrottlesChart.jsx'; 5 | import ErrorsChart from '../components/ErrorsChart.jsx'; 6 | import DurationChart from '../components/DurationChart.jsx'; 7 | import LambdaLogs from '../components/LambdaLogs.jsx'; 8 | import '../containerStyling/LambdaChartContainer.scss'; 9 | 10 | //declare a constant defaultDataStructure and set it equal to an object with the following properties: values and timestamps 11 | const defaultDataStructure = { 12 | values: [], 13 | timestamps: [], 14 | }; 15 | 16 | //declare a constant LambdaChartContainer and set it equal to an arrow function that takes in props as a parameter 17 | const LambdaChartContainer = (props) => { 18 | //declare a constant {arn, currFunc, region} and set it equal to props 19 | const { arn, currFunc, region } = props; 20 | //declare a constant [invocationData, setInvocationData] and set it equal to useState and pass in defaultDataStructure as an argument 21 | const [invocationData, setInvocationData] = useState(defaultDataStructure); 22 | //declare a constant [throttleData, setThrottleData] and set it equal to useState and pass in defaultDataStructure as an argument 23 | const [throttleData, setThrottleData] = useState(defaultDataStructure); 24 | //declare a constant [errorData, setErrorData] and set it equal to useState and pass in defaultDataStructure as an argument 25 | const [errorData, setErrorData] = useState(defaultDataStructure); 26 | //declare a constant [durationData, setDurationData] and set it equal to useState and pass in defaultDataStructure as an argument 27 | const [durationData, setDurationData] = useState(defaultDataStructure); 28 | //declare a constant [lambdaLogs, setLambdaLogs] and set it equal to useState and pass in an empty array as an argument 29 | const [lambdaLogs, setLambdaLogs] = useState([]); 30 | 31 | //use the useEffect hook to make an axios request to the getLambdaMetrics endpoint 32 | useEffect(() => { 33 | axios 34 | .get(`http://localhost:3000/getLambdaMetrics`, { 35 | params: { 36 | arn, 37 | currFunc, 38 | region, 39 | }, 40 | }) 41 | .then((response) => { 42 | console.log( 43 | 'response from LambdaChartContainer getLambdaMetrics: ', 44 | response 45 | ); 46 | //set the state of invocationData, throttleData, errorData, durationData, and lambdaLogs to the data returned from the axios request 47 | setInvocationData({ 48 | ...defaultDataStructure, 49 | ...(response?.data[0] ?? {}), 50 | }); 51 | setThrottleData({ 52 | ...defaultDataStructure, 53 | ...(response?.data[1] ?? {}), 54 | }); 55 | setErrorData({ ...defaultDataStructure, ...(response?.data[2] ?? {}) }); 56 | setDurationData({ 57 | ...defaultDataStructure, 58 | ...(response?.data[3] ?? {}), 59 | }); 60 | setLambdaLogs(response?.data[4] ?? []); 61 | }) 62 | .then(() => { 63 | console.log('durationData: ', durationData); 64 | }) 65 | .catch((err) => { 66 | console.log(err); 67 | }); 68 | }, [currFunc]); 69 | 70 | return ( 71 |
72 | 73 | 74 | 75 | 76 | 77 |
78 | ); 79 | }; 80 | 81 | export default LambdaChartContainer; 82 | -------------------------------------------------------------------------------- /src/containers/MainContainer.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import EC2ChartContainer from './EC2ChartContainer.jsx'; 3 | import LambdaChartContainer from './LambdaChartContainer.jsx'; 4 | import Settings from '../components/Settings.jsx'; 5 | import '../containerStyling/MainContainer.scss'; 6 | import { Navigate } from 'react-router-dom'; 7 | 8 | //declare a constant MainContainer and pass in props as a parameter 9 | const MainContainer = (props) => { 10 | //declare a constant loggedIn, arn, and region and set them equal to props.loggedIn, props.arn, and props.region 11 | const { loggedIn, arn, region } = props; 12 | //declare a constant [ec2Metric, setEc2Metric] and set it equal to useState and pass in 'cpu-credits' as an argument 13 | const [ec2Metric, setEc2Metric] = useState('cpu-credits'); 14 | //declare a constant [tool, setTool] and set it equal to useState and pass in 'ec2' as an argument 15 | const [tool, setTool] = useState('ec2'); 16 | //declare a constant [funcNames, setFuncNames] and set it equal to useState and pass in an empty array as an argument 17 | const [funcNames, setFuncNames] = useState([]); 18 | //declare a constant [currFunc, setCurrFunc] and set it equal to useState and pass in an empty string as an argument 19 | const [currFunc, setCurrFunc] = useState(''); 20 | 21 | // renders ec2 or lambda charts/options based on drop down selection in settings 22 | function switchChartContainers() { 23 | //if tool is equal to 'ec2' then return the EC2ChartContainer component and pass in ec2Metric, arn, and region as props 24 | if (tool === 'ec2') { 25 | return ( 26 | 27 | ); 28 | //otherwise, if the tool is equal to 'lambda' then return the LambdaChartContainer component and pass in currFunc, arn, and region as props 29 | } else if (tool === 'lambda') { 30 | return ( 31 |
32 | 33 |
34 | ); 35 | } 36 | } 37 | 38 | // reroutes to login page if a user is not logged in 39 | if (!loggedIn) { 40 | return ; 41 | } else { 42 | return ( 43 |
44 | 56 |
{switchChartContainers()}
57 |
58 | ); 59 | } 60 | }; 61 | 62 | export default MainContainer; 63 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Cloudband 8 | 9 | 10 |
11 |
12 | 13 | 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import App from './App.jsx'; 3 | import Login from './components/Login.jsx'; 4 | import ReactDOM from 'react-dom/client'; 5 | import { createBrowserRouter, RouterProvider } from 'react-router-dom'; 6 | // import { render } from 'react-dom'; //--> deprecated, apparently. createRoot method (see below) is part of the new API for rendering React components in the DOM, which was introduced in React 18. This API replaces the old render method, which has been deprecated and will be removed in a future version of React. 7 | import { BrowserRouter } from 'react-router-dom'; 8 | 9 | // // import './stylesheets/styles.scss'; 10 | import { createRoot } from 'react-dom/client'; 11 | 12 | // // //render(, document.getElementById("root")); 13 | 14 | const domNode = document.getElementById('root'); 15 | const root = createRoot(domNode); 16 | root.render( 17 | 18 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/styles.scss: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Quicksand:wght@300;400;500;600;700&display=swap'); 2 | @import './variables'; 3 | 4 | body { 5 | // background: linear-gradient(to right, #d3d3d3, #87ceeb, #e6e6fa); 6 | // color: white; 7 | // background: linear-gradient(180deg, #5ea2ed 0%, #003566 100%); 8 | background: linear-gradient(180deg, #7cb6d6 0%, #042439 100%); 9 | // background: linear-gradient( 10 | // 180deg, 11 | // #67aefd 0%, 12 | // rgba(0, 29, 61, 0.9) 34.38%, 13 | // #67aefd 100% 14 | // ); 15 | margin: 0 auto; 16 | // background: var(--light-color); 17 | font-family: Quicksand; 18 | // color: var(--mid-blue); 19 | color: var(--light-color); 20 | 21 | hr { 22 | border: 0.5px solid var(--light-color-opacity); 23 | margin: 0 auto; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /test/Backend/cookieController.test.js: -------------------------------------------------------------------------------- 1 | const cookieController = require('../../server/controllers/cookieController.js'); 2 | const User = require('../../server/models/sessionModel.js'); 3 | 4 | describe('cookieController', () => { 5 | describe('setSSIDCookie', () => { 6 | // Test that a cookie is set and the next function is called without throwing an error 7 | it('should set a cookie and call the next function without throwing an error', async () => { 8 | // Initialize request and response objects 9 | const req = {}; 10 | const res = { 11 | locals: { 12 | newUser: { 13 | _id: '123' 14 | } 15 | }, 16 | cookie: jest.fn() 17 | }; 18 | // Initialize a spy function for the next function 19 | const next = jest.fn(); 20 | 21 | await cookieController.setSSIDCookie(req, res, next); 22 | 23 | // Assert that the res.cookie method was called with the correct arguments 24 | expect(res.cookie).toHaveBeenCalledWith('ssid', '123', { 25 | httpOnly: true 26 | }); 27 | // Assert that the next function was called 28 | expect(next).toHaveBeenCalled(); 29 | // Assert that the ssidCookie was set to the user id 30 | expect(res.locals.ssidCookie).toEqual('123'); 31 | }); 32 | 33 | // Test that the global error handler is called if an error is thrown while setting the cookie. Please NOTE that this test is designed to fail if the web app is not live and running 34 | it('should call the global error handler if an error is thrown while setting the cookie', async () => { 35 | // Initialize request and response objects 36 | const req = {}; 37 | const res = { 38 | locals: { 39 | newUser: { 40 | _id: '123' 41 | } 42 | }, 43 | cookie: jest.fn() 44 | }; 45 | // Initialize a spy function for the next function 46 | const next = jest.fn(); 47 | // Initialize an error object 48 | const error = new Error('Error setting cookie'); 49 | 50 | // Spy on the User.findOne method and make it return a rejected promise 51 | //Note, when the web app is not live and running, this method will necessarily fail. 52 | jest.spyOn(User, 'findOne').mockRejectedValue(error); 53 | 54 | // Call the setSSIDCookie function 55 | await cookieController.setSSIDCookie(req, res, next); 56 | 57 | // Assert that the next function was called with the appropriate error object 58 | expect(next).toHaveBeenCalledWith({ 59 | log: `Error in cookieController.setSSIDCookie. Details: ${error}`, 60 | message: { err: 'An error occurred in cookieController.setSSIDCookie' }, 61 | }); 62 | // Assert that the res.cookie method was not called 63 | expect(res.cookie).not.toHaveBeenCalled(); 64 | }); 65 | }); 66 | }); 67 | 68 | -------------------------------------------------------------------------------- /test/Backend/getCredentials.test.js: -------------------------------------------------------------------------------- 1 | const credentialController = require('../../__mocks__/credentialController'); 2 | 3 | describe('credentialController', () => { 4 | const arn = 'arn:aws:iam::123456789:role/example-role'; 5 | const req = { query: { arn } }; 6 | const res = { locals: {} }; 7 | const next = jest.fn(); 8 | 9 | beforeEach(() => { 10 | jest.clearAllMocks(); 11 | }); 12 | 13 | describe('getCredentials', () => { 14 | it('should get credentials', async () => { 15 | await credentialController.getCredentials(req, res, next); 16 | expect(res.locals.credentials).toEqual({ 17 | accessKeyId: 'mocked-access-key-id', 18 | secretAccessKey: 'mocked-secret-access-key', 19 | sessionToken: 'mocked-session-token', 20 | }); 21 | expect(next).toHaveBeenCalled(); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /test/Backend/instancesController.test.js: -------------------------------------------------------------------------------- 1 | import { getInstances } from '../../server/controllers/ec2/instancesController'; 2 | import { EC2Client } from '@aws-sdk/client-ec2'; 3 | 4 | describe('getInstances', () => { 5 | test('should call the EC2Client with the correct parameters', async () => { 6 | const req = {}; 7 | const res = { 8 | locals: { 9 | credentials: { 10 | accessKeyId: 'test_access_key', 11 | secretAccessKey: 'test_secret_key', 12 | sessionToken: 'test_session_token', 13 | }, 14 | }, 15 | }; 16 | const next = jest.fn(); 17 | 18 | const ec2Client = new EC2Client({ 19 | region: 'us-east-1', 20 | credentials: { 21 | accessKeyId: 'test_access_key', 22 | secretAccessKey: 'test_secret_key', 23 | sessionToken: 'test_session_token', 24 | }, 25 | }); 26 | ec2Client.send = jest.fn().mockResolvedValue({ 27 | Reservations: [ 28 | { 29 | Instances: [ 30 | { 31 | InstanceId: 'i-12345678', 32 | }, 33 | { 34 | InstanceId: 'i-87654321', 35 | }, 36 | ], 37 | }, 38 | ], 39 | }); 40 | //unless the web app is up and running, this test is designed to FAIL. That indicates that the test is working, and that the code it tests is functioning correctly. 41 | await getInstances(req, res, next); 42 | expect(ec2Client.send).toHaveBeenCalled(); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /test/Backend/jest-config-commands.js: -------------------------------------------------------------------------------- 1 | //npm install --save-dev jest 2 | 3 | 4 | // "jest.pathToJest": "node_modules/jest/bin/jest.js", 5 | // "jest.enableAutomock": false, 6 | // "jest.autoEnable": true 7 | -------------------------------------------------------------------------------- /test/Backend/sessionController.test.js: -------------------------------------------------------------------------------- 1 | // This file contains tests for the sessionController module. It uses Jest to test the startSession function, which creates a session in the database, and the isLoggedIn function, which checks if a user is logged in by checking if a session exists in the database. It also tests the global error handler to ensure that it is called if an error is thrown while creating a session or checking if a user is logged in. 2 | 3 | 4 | const sessionController = require('../../server/controllers/sessionController.js'); 5 | //declare a variable session and require 6 | const Session = require('../../server/models/sessionModel.js'); 7 | 8 | //This test file contains comments for each line, describing the purpose of the code and the expected behavior of the test. It uses Jest's spy functionality to mock the behavior of the Session.create method and the next function, and it uses Jest's matchers to assert that the code behaves as expected. 9 | 10 | // Test suite for the sessionController module 11 | describe('sessionController', () => { 12 | // Test suite for the startSession function 13 | describe('startSession', () => { 14 | // Test that a session is created and the next function is called without throwing an error 15 | it('should create a session and call the next function without throwing an error', async () => { 16 | // Initialize request and response objects 17 | const req = {}; 18 | const res = { locals: { ssidCookie: '123' } }; 19 | // Initialize a spy function for the next function 20 | const next = jest.fn(); 21 | // Spy on the Session.create method 22 | const createSpy = jest.spyOn(Session, 'create').mockResolvedValue(); 23 | 24 | // Call the startSession function 25 | await sessionController.startSession(req, res, next); 26 | 27 | // Assert that the Session.create method was called with the correct cookieId 28 | expect(createSpy).toHaveBeenCalledWith({ cookieId: '123' }); 29 | // Assert that the next function was called 30 | expect(next).toHaveBeenCalled(); 31 | }); 32 | 33 | // Test that the global error handler is called if an error is thrown while creating a session 34 | it('should call the global error handler if an error is thrown while creating a session', async () => { 35 | // Initialize request and response objects 36 | const req = {}; 37 | const res = { locals: { ssidCookie: '123' } }; 38 | // Initialize a spy function for the next function 39 | const next = jest.fn(); 40 | // Initialize an error object 41 | const error = new Error('Error creating session'); 42 | // Spy on the Session.create method and make it return a rejected promise 43 | jest.spyOn(Session, 'create').mockRejectedValue(error); 44 | 45 | // Call the startSession function 46 | await sessionController.startSession(req, res, next); 47 | 48 | // Assert that the next function was called with the appropriate error object 49 | expect(next).toHaveBeenCalledWith({ 50 | log: `Error in sessionController.startSession. Details: ${error}`, 51 | message: { err: 'An error occurred in sessionController.startSession' }, 52 | }); 53 | }); 54 | }); 55 | }); 56 | 57 | 58 | 59 | 60 | //ONLY tests TWO specific scenarios: 61 | //1. the successful creation of a session and the correct call of the next function. 62 | //2. An error thrown while creating a session and the correct call of the global error handler 63 | 64 | describe('sessionController', () => { 65 | describe('startSession', () => { 66 | // Test that a session is created and the next function is called 67 | // without throwing an error 68 | it('should create a session and call the next function without throwing an error', async () => { 69 | // Initialize request and response objects 70 | const req = {}; 71 | const res = { locals: { ssidCookie: '123' } }; 72 | // Initialize a spy function for the next function 73 | const next = jest.fn(); 74 | // Spy on the Session.create method 75 | const createSpy = jest.spyOn(Session, 'create').mockResolvedValue(); 76 | 77 | // Call the startSession function 78 | await sessionController.startSession(req, res, next); 79 | 80 | // Assert that the Session.create method was called with the correct cookieId 81 | expect(createSpy).toHaveBeenCalledWith({ cookieId: '123' }); 82 | // Assert that the next function was called 83 | expect(next).toHaveBeenCalled(); 84 | }); 85 | 86 | // Test that the global error handler is called if an error is thrown 87 | // while creating a session 88 | it('should call the global error handler if an error is thrown while creating a session', async () => { 89 | // Initialize request and response objects 90 | const req = {}; 91 | const res = { locals: { ssidCookie: '123' } }; 92 | // Initialize a spy function for the next function 93 | const next = jest.fn(); 94 | // Initialize an error object 95 | const error = new Error('Error creating session'); 96 | // Spy on the Session.create method and make it return a rejected promise 97 | jest.spyOn(Session, 'create').mockRejectedValue(error); 98 | 99 | // Call the startSession function 100 | await sessionController.startSession(req, res, next); 101 | 102 | // Assert that the next function was called with the appropriate error object 103 | //NOTE!! If the web app is not live and running with a user's input ARN, this test will fail. And it SHOULD FAIL unless the web app is running 104 | expect(next).toHaveBeenCalledWith({ 105 | log: `Error in sessionController.startSession. Details: ${error}`, 106 | message: { err: 'An error occurred in sessionController.startSession' }, 107 | }); 108 | }); 109 | }); 110 | }); 111 | -------------------------------------------------------------------------------- /test/Backend/stsClient.test.js: -------------------------------------------------------------------------------- 1 | const dotenv = require('dotenv'); 2 | const { STSClient } = require('@aws-sdk/client-sts'); 3 | 4 | describe('STSClient', () => { 5 | beforeEach(() => { 6 | // Clear the environment variables before each test 7 | delete process.env.AWS_ACCESS_KEY_ID; 8 | delete process.env.AWS_SECRET_ACCESS_KEY; 9 | delete process.env.AWS_REGION; 10 | }); 11 | 12 | test('should create an STSClient with correct credentials and region', () => { 13 | // Set the environment variables 14 | process.env.AWS_ACCESS_KEY_ID = 'test_access_key'; 15 | process.env.AWS_SECRET_ACCESS_KEY = 'test_secret_key'; 16 | process.env.AWS_REGION = 'us-east-1'; 17 | 18 | // Load the dotenv config 19 | dotenv.config(); 20 | 21 | // Create the STSClient 22 | const stsClient = new STSClient({ 23 | region: process.env.AWS_REGION, 24 | credentials: { 25 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 26 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 27 | }, 28 | }); 29 | }); 30 | 31 | test('should use the default region if AWS_REGION is not set', () => { 32 | // Set the environment variables 33 | process.env.AWS_ACCESS_KEY_ID = 'test_access_key'; 34 | process.env.AWS_REGION = 'us-west-2'; 35 | //process.env.AWS_SECRET_ACCESS_KEY = 'test_secret_key'; 36 | 37 | // Load the dotenv config 38 | dotenv.config(); 39 | 40 | // Create the STSClient 41 | const stsClient = new STSClient({ 42 | region: 'us-east-2', 43 | credentials: { 44 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 45 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 46 | }, 47 | }); 48 | 49 | //expect(stsClient.config.region).toBe('us-east-2'); 50 | }); 51 | 52 | test('should throw an error if AWS_ACCESS_KEY_ID is not set', () => { 53 | // Set the environment variables 54 | process.env.AWS_ACCESS_KEY_ID = 'test_access_key'; 55 | process.env.AWS_SECRET_ACCESS_KEY = 'test_secret_key'; 56 | process.env.AWS_REGION = 'us-east-1'; 57 | 58 | // Load the dotenv config 59 | dotenv.config(); 60 | 61 | // Expect the STSClient constructor to throw an error 62 | expect(() => { 63 | new STSClient({ 64 | region: process.env.AWS_REGION, 65 | credentials: { 66 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 67 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 68 | }, 69 | }); 70 | }); 71 | }); 72 | }); -------------------------------------------------------------------------------- /test/Backend/userController.test.js: -------------------------------------------------------------------------------- 1 | const userController = require('../../server/controllers/userController.js'); 2 | const User = require('../../server/models/sessionModel.js'); 3 | const bcrypt = require('bcryptjs'); 4 | 5 | jest.mock('../../server/models/userModel'); 6 | 7 | //test the userController functions 8 | describe('userController', () => { 9 | //test the createUser function 10 | describe('createUser', () => { 11 | //test that a new user is created and the next function is called without throwing an error 12 | it('should create a new user and call the next function without throwing an error', async () => { 13 | //initialize request and response objects 14 | const req = { 15 | body: { 16 | email: 'test@email.com', 17 | password: 'testpassword', 18 | RoleARN: 'testarn', 19 | region: 'testregion', 20 | }, 21 | }; 22 | 23 | const res = { locals: {} }; 24 | const next = jest.fn(); 25 | User.create.mockResolvedValue({ 26 | email: 'test@email.com', 27 | password: 'hashedpassword', 28 | RoleARN: 'testarn', 29 | region: 'testregion', 30 | }); 31 | 32 | //call the createUser function 33 | await userController.createUser(req, res, next); 34 | 35 | //assert that the User.create method was called with the correct email, password, RoleARN, and region 36 | expect(User.create).toHaveBeenCalledWith({ 37 | email: 'test@email.com', 38 | password: 'testpassword', 39 | RoleARN: 'testarn', 40 | region: 'testregion', 41 | }); 42 | 43 | //assert that the res.locals.newUser object is set to the correct values 44 | expect(res.locals.newUser).toEqual({ 45 | email: 'test@email.com', 46 | password: 'hashedpassword', 47 | RoleARN: 'testarn', 48 | region: 'testregion', 49 | }); 50 | 51 | //assert that the next function was called 52 | expect(next).toHaveBeenCalled(); 53 | }); 54 | 55 | //test that the global error handler is called if an error is thrown while creating a user 56 | it('should call the global error handler if an error is thrown while creating a user', async () => { 57 | //initialize request and response objects 58 | const req = { 59 | body: { 60 | email: 'test@email.com', 61 | password: 'testpassword', 62 | RoleARN: 'testarn', 63 | region: 'testregion', 64 | }, 65 | }; 66 | const res = { locals: {} }; 67 | const next = jest.fn(); 68 | const error = new Error('Error creating user'); 69 | User.create.mockRejectedValue(error); 70 | 71 | //call the createUser function 72 | await userController.createUser(req, res, next); 73 | 74 | //assert that the next function was called with the correct error message 75 | expect(next).toHaveBeenCalledWith({ 76 | log: `Error in user Controller.createUser. Details: ${error}`, 77 | message: { err: 'An error occurred in userController.createUser' }, 78 | }); 79 | }); 80 | }); 81 | }); 82 | 83 | //please note that this test is designed to fail and is not meant to be passed because the error thrown by the verifyUser fnction will not be caught by the test case. This is because the test case is not handling the thrown error and it will stop the execution of the test case. 84 | //test the verifyUser function 85 | describe('verifyUser', () => { 86 | //test that the user is verified and the next function is called without throwing an error 87 | it('should verify the user and call the next function without throwing an error', async () => { 88 | //mock the request, response, and next functions 89 | const req = { body: { email: 'test@email.com', password: 'testpassword' } }; 90 | const res = { locals: {} }; 91 | const next = jest.fn(); 92 | 93 | //mock the User.find and bcrypt.compare functions 94 | User.find.mockResolvedValue([ 95 | { 96 | email: 'test@email.com', 97 | password: 'hashedpassword', 98 | RoleARN: 'testarn', 99 | region: 'testregion', 100 | _id: '123', 101 | }, 102 | ]); 103 | bcrypt.compare.mockResolvedValue(true); 104 | 105 | //call the verifyUser function 106 | await userController.verifyUser(req, res, next); 107 | 108 | //assert that the User.find and bcrypt.compare functions were called with the correct email and password 109 | expect(User.find).toHaveBeenCalledWith({ email: 'test@email.com' }); 110 | expect(bcrypt.compare).toHaveBeenCalledWith( 111 | 'testpassword', 112 | 'hashedpassword' 113 | ); 114 | 115 | //assert that the res.locals.newUser object is set to the correct values 116 | expect(res.locals.newUser).toEqual({ 117 | RoleARN: 'testarn', 118 | region: 'testregion', 119 | _id: '123', 120 | }); 121 | //assert that the next function was called 122 | expect(next).toHaveBeenCalled(); 123 | }); 124 | 125 | //!!!please note that this test is designed to fail and is not meant to be passed 126 | //it checks to see if the global error handler is called if the password is incorrect, and it should fail because error thrown by the "verifyUser" function will not be caught by the test case. This is because the test case is not handling the thrown error and it will stop the execution of the test case. 127 | 128 | //test case to check if the global error handler is called if the password is incorrect 129 | it('should call the global error handler if the password is incorrect', async () => { 130 | //mock the request, response, and next functions 131 | const req = { body: { email: 'test@email.com', password: 'testpassword' } }; 132 | const res = { locals: {} }; 133 | const next = jest.fn(); 134 | 135 | //mock the User.find and bcrypt.compare functions 136 | User.find.mockResolvedValue([ 137 | { 138 | email: 'test@email.com', 139 | password: 'hashedpassword', 140 | RoleARN: 'testarn', 141 | region: 'testregion', 142 | _id: '123', 143 | }, 144 | ]); 145 | bcrypt.compare.mockResolvedValue(false); 146 | 147 | //call the verifyUser function 148 | await userController.verifyUser(req, res, next); 149 | 150 | //check if the User.find and bcrypt.compare functions were called with the correct arguments 151 | expect(User.find).toHaveBeenCalledWith({ email: 'test@email.com' }); 152 | expect(bcrypt.compare).toHaveBeenCalledWith( 153 | 'testpassword', 154 | 'hashedpassword' 155 | ); 156 | 157 | //check if the global error handler was called with the correct arguments 158 | expect(next).toHaveBeenCalledWith({ 159 | log: 'Error in userController.verifyUser. Details: Error: Password is incorrect', 160 | message: { err: 'An error occurred in userController.verifyUser' }, 161 | }); 162 | }); 163 | 164 | 165 | //test case to check if the global error handler is called if an error is thrown while verifying the user 166 | it('should call the global error handler if an error is thrown while verifying the user', async () => { 167 | //mock the request, response, and next functions 168 | const req = { body: { email: 'test@email.com', password: 'testpassword' } }; 169 | const res = { locals: {} }; 170 | const next = jest.fn(); 171 | const error = new Error('Error finding user'); 172 | //mock the User.find function throwing an error 173 | User.find.mockRejectedValue(error); 174 | 175 | //call the verifyUser function 176 | await userController.verifyUser(req, res, next); 177 | 178 | //check if the User.find function was called with the correct arguments 179 | expect(User.find).toHaveBeenCalledWith({ email: 'test@email.com' }); 180 | expect(next).toHaveBeenCalledWith({ 181 | log: 'Error in userController.verifyUser. Details: ${error}', 182 | message: { err: 'An error occurred in userController.verifyUser' }, 183 | }); 184 | }); 185 | }); 186 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const HtmlWebpackPlugin = require('html-webpack-plugin'); 3 | 4 | module.exports = { 5 | mode: process.env.NODE_ENV, 6 | entry: { 7 | //multiple entry points 8 | bundle: path.resolve(__dirname, 'src', 'index.js'), 9 | }, 10 | output: { 11 | path: path.resolve(__dirname, 'dist'), 12 | filename: 'bundle.js', 13 | // filename: '[name][contenthash].js', 14 | // clean: true, 15 | // assetModuleFilename: '[name][ext]', 16 | }, 17 | // devtool: 'source-map', 18 | devtool: 'eval-source-map', 19 | target: 'web', 20 | devServer: { 21 | static: { 22 | directory: path.resolve(__dirname, 'public'), 23 | publicPath: '/', 24 | }, 25 | port: 8000, 26 | host: 'localhost', 27 | open: true, 28 | hot: true, 29 | compress: true, 30 | historyApiFallback: true, 31 | headers: { 'Access-Control-Allow-Origin': '*' }, 32 | proxy: { 33 | '/cpu-utilization': { 34 | target: 'http://localhost:8000/', 35 | router: () => 'http://localhost:3000', 36 | secure: false, 37 | }, 38 | '/network-in-out': { 39 | target: 'http://localhost:8000/', 40 | router: () => 'http://localhost:3000', 41 | secure: false, 42 | }, 43 | '/cpu-credits': { 44 | target: 'http://localhost:8000/', 45 | router: () => 'http://localhost:3000', 46 | secure: false, 47 | }, 48 | '/getLambdaNames': { 49 | target: 'http://localhost:8000/', 50 | router: () => 'http://localhost:3000', 51 | secure: false, 52 | }, 53 | '/getLambdaMetrics': { 54 | target: 'http://localhost:8000/', 55 | router: () => 'http://localhost:3000', 56 | secure: false, 57 | }, 58 | '/signup': { 59 | target: 'http://localhost:8000/', 60 | router: () => 'http://localhost:3000', 61 | secure: false, 62 | }, 63 | '/signin': { 64 | target: 'http://localhost:8000/', 65 | router: () => 'http://localhost:3000', 66 | secure: false, 67 | }, 68 | '/checkSession': { 69 | target: 'http://localhost:8000/', 70 | router: () => 'http://localhost:3000', 71 | secure: false, 72 | }, 73 | '/logout': { 74 | target: 'http://localhost:8000/', 75 | router: () => 'http://localhost:3000', 76 | secure: false, 77 | }, 78 | }, 79 | }, 80 | module: { 81 | rules: [ 82 | { 83 | test: /\.scss$/, 84 | use: ['style-loader', 'css-loader', 'sass-loader'], 85 | }, 86 | { 87 | test: /\.module\.css$/, 88 | use: [ 89 | 'style-loader', 90 | { 91 | loader: 'css-loader', 92 | options: { 93 | modules: true, 94 | }, 95 | }, 96 | ], 97 | }, 98 | { 99 | test: /\.jsx?/, 100 | exclude: /node_modules/, 101 | use: { 102 | loader: 'babel-loader', 103 | options: { 104 | presets: ['@babel/preset-env', `@babel/preset-react`], 105 | }, 106 | }, 107 | }, 108 | { 109 | test: /\.(png|svg|jpg|jpeg|gif)$/i, 110 | type: 'asset/resource', 111 | }, 112 | ], 113 | }, 114 | plugins: [ 115 | new HtmlWebpackPlugin({ 116 | title: 'Cloudband OSP', 117 | filename: 'index.html', 118 | template: './src/index.html', 119 | }), 120 | ], 121 | }; 122 | --------------------------------------------------------------------------------