├── .gitignore ├── Dockerfile ├── Dockerfile-dev ├── Dockerfile-postgres ├── Dockerfile-server ├── LICENSE ├── README.md ├── backend ├── .aws │ └── config ├── assume-role.js ├── cloudwatch.js ├── controllers │ └── authMiddleware.js ├── data.js ├── externalIDGenerator.js ├── fetch.js ├── metrics.js ├── modal.js ├── routes │ └── ApiRoutes.js ├── server.js └── serverDelete.js ├── docker-compose-prod.yaml ├── eslint.config.js ├── index.html ├── nginx └── default.conf ├── package-lock.json ├── package.json ├── src ├── icon.png ├── index.css ├── main.jsx ├── pages │ ├── Dashboard.jsx │ ├── Error.jsx │ ├── Home.jsx │ ├── Layout.jsx │ ├── components │ │ ├── Barchart.jsx │ │ ├── Linechart.jsx │ │ ├── ProtectedRoutes.jsx │ │ └── menu.jsx │ ├── context │ │ └── AuthContext.jsx │ ├── login │ │ ├── Forgot.tsx │ │ ├── Login.tsx │ │ ├── Signup.tsx │ │ └── Verification.tsx │ └── newUserProfile.jsx └── state │ ├── graph-reducer.js │ └── store.js ├── tailwind.config.js ├── tsconfig.json └── vite.config.js /.gitignore: -------------------------------------------------------------------------------- 1 | #userPool 2 | 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | lerna-debug.log* 12 | 13 | node_modules 14 | .env 15 | dist 16 | dist-ssr 17 | *.local 18 | 19 | # Editor directories and fil-es 20 | .vscode/* 21 | !.vscode/extensions.json 22 | .idea 23 | .DS_Store 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | # Aws 31 | backend/.aws/* -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use an official Node.js image. 2 | FROM node:23.6.0 AS builder 3 | 4 | WORKDIR /app 5 | COPY package*.json ./ 6 | RUN npm install 7 | COPY ./ /app 8 | RUN npm run build 9 | 10 | #Running Production 11 | FROM nginx:stable-alpine AS production 12 | COPY --from=builder /app/nginx /etc/nginx/conf.d 13 | COPY --from=builder /app/dist /usr/share/nginx/html 14 | EXPOSE 80 15 | # Run the dev server. 16 | ENTRYPOINT ["nginx", "-g", "daemon off;"] -------------------------------------------------------------------------------- /Dockerfile-dev: -------------------------------------------------------------------------------- 1 | FROM node:16.13 2 | WORKDIR /app 3 | COPY . /app 4 | RUN npm install 5 | EXPOSE 80 6 | ENTRYPOINT ["npm", "run", "dev"] -------------------------------------------------------------------------------- /Dockerfile-postgres: -------------------------------------------------------------------------------- 1 | FROM node:23.6.0 2 | 3 | -------------------------------------------------------------------------------- /Dockerfile-server: -------------------------------------------------------------------------------- 1 | # Use an official Node.js image. 2 | FROM node:23.6.0 3 | 4 | # Set the working directory. 5 | WORKDIR /server 6 | COPY ./package.json /server 7 | RUN npm install 8 | 9 | # Copy package files and install dependencies. 10 | COPY ./backend /server 11 | 12 | # Copy the rest of your application code. 13 | # Expose the port Vite is running on. 14 | EXPOSE 81 15 | 16 | # Run the dev server. 17 | CMD ["npm", "run", "server-docker"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Open Source Labs 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 |
8 |

AWSome

9 |

Real-Time AWS EC2 Instance Monitoring Tool

10 |
11 | 12 | 13 | 14 | ## Table of Contents 15 |
    16 |
  1. About The Project
  2. 17 |
  3. Technologies Used
  4. 18 |
  5. Getting Started
  6. 19 |
  7. Usage
  8. 20 |
  9. Roadmap
  10. 21 |
  11. Contributing
  12. 22 |
  13. License
  14. 23 |
  15. Contact
  16. 24 |
  17. Acknowledgments
  18. 25 |
26 | 27 | 28 | 29 | ## About The Project 30 | 31 | AWSome is a real-time AWS EC2 instance monitoring tool designed to provide comprehensive insights into your EC2 instances using AWS CloudWatch. The project integrates with various AWS services such as Cognito for user authentication, IAM roles for permissions, and EC2 for instance management. The tool offers an interactive user interface built with React, allowing users to view EC2 instance metrics, generate heat maps, and analyze trends. 32 | 33 | ### Key Features: 34 | - Real-time EC2 instance monitoring 35 | - Interactive visualizations using graphs 36 | - Customizable thresholds and alarms for memory usage and CPU load 37 | - Secure user authentication with AWS Cognito 38 | - User-friendly interface built with React and Redux 39 | 40 | ### Built With 41 | This project uses several technologies to provide a full-stack solution for AWS EC2 monitoring: 42 | 43 | - [![React](https://img.shields.io/badge/React-61DAFB?style=flat&logo=react&logoColor=white)](https://reactjs.org/) - Frontend framework for building interactive UIs 44 | - [![AWS Cognito](https://img.shields.io/badge/AWS%20Cognito-FF9900?style=flat&logo=aws&logoColor=white)](https://aws.amazon.com/cognito/) - User authentication and authorization 45 | - [![AWS EC2](https://img.shields.io/badge/AWS%20EC2-FF9900?style=flat&logo=aws&logoColor=white)](https://aws.amazon.com/ec2/) - Cloud computing platform for running instances 46 | - [![AWS CloudWatch](https://img.shields.io/badge/AWS%20CloudWatch-FF9900?style=flat&logo=aws&logoColor=white)](https://aws.amazon.com/cloudwatch/) - Monitoring and logging service 47 | - [![AWS IAM](https://img.shields.io/badge/AWS%20IAM-FF9900?style=flat&logo=aws&logoColor=white)](https://aws.amazon.com/iam/) - Identity and access management 48 | - [![Redux](https://img.shields.io/badge/Redux-764ABC?style=flat&logo=redux&logoColor=white)](https://redux.js.org/) - State management for React 49 | 50 |

(back to top)

51 | 52 | ## Getting Started 53 | If you'd like to learn more visit our website here 54 | Website 55 | 56 | If you'd like to use our app you can visit the following link below. 57 | AWSome Tool 58 | 59 | 60 | Otherwise, if you'd like to contribute to our product you can follow the steps below to get the development environment started! 61 | 62 | ### Installation 63 | //steps on accessing our deployed app, and our landing page 64 | 65 | ### Prerequisites 66 | - Node.js 67 | - AWS access 68 | - NPM 69 | - Docker 70 | 71 |

(back to top)

72 | 73 |

Usage

74 | AWSome provides an intuitive interface for managing and monitoring your EC2 instances. You can: 75 | 80 | 81 | For more examples, please refer to the Documentation 82 | 83 |

(back to top)

84 | 85 | ## Roadmap 86 | - [x] Add support for viewing EC2 instance performance metrics in real-time 87 | - [x] Integrate AWS CloudWatch for detailed metrics visualization 88 | - [x] Add authorization for a user to view their own metrics 89 | - [ ] Add Dark Mode 90 | - [ ] Improve UI responsiveness and accessibility 91 | 92 | See the open issues for a full list of proposed features and known issues. 93 | 94 |

(back to top)

95 | Contributing 96 | ## Contributing 97 | 98 | We welcome contributions from the open-source community! Here's how you can contribute to AWSome: 99 | 100 | ## 🚀 Contributing to Our AWS Monitoring Tool 101 | 102 | We ❤️ contributions from the community! Follow these steps to get involved: 103 | 104 | ### 1️⃣ Fork the Repo 105 | Click the **"Fork"** button at the top-right corner of this page to create your own copy. 106 | 107 | ### 2️⃣ Create a Feature Branch 108 | ```sh 109 | git checkout -b feature/yourNewFeatureName 110 | ``` 111 | 112 | ### 3️⃣ Build Something Awesome 🛠️ 113 | - **Implement Your Feature**: Add your code, tests, or documentation. 114 | - **Ensure Your Changes Align with the Project's Goals**: Make sure your work fits the overall vision. 115 | - **Keep Your Implementation Clean, Modular, and Well-Documented**: Strive for readable, reusable, and well-commented code. 116 | 117 | ### 4️⃣ Commit Your Changes ✅ 118 | ```sh 119 | git commit -m "✨ Added [your-new-feature-description]" 120 | ``` 121 | 122 | ### 5️⃣ Push to Your Branch 🚀 123 | ```sh 124 | git push origin feature/yourNewFeatureName 125 | ``` 126 | 127 | ### 6️⃣ Open a Pull Request (PR) 🔄 128 | - Navigate to your fork on GitHub. 129 | - Click "New Pull Request" and select the dev branch as the base (unless otherwise specified). 130 | - Provide a detailed description of your changes. 131 | - Submit the PR and wait for our team to review it. 132 | 133 | ### 7️⃣ Celebrate! 🎉 134 | Once approved, your contribution will be merged into the project. Thank you for helping us improve this tool for developers! 135 | 136 | 💡 Need help? Feel free to open an issue or reach out to us in discussions! 🚀 137 | 138 |

(back to top)

139 | 140 | ## License 141 | 142 | Distributed under the Unlicense License. See `LICENSE.txt` for more information. 143 | 144 |

(back to top)

145 | 146 | ### Top contributors: 147 | 148 | 149 | 157 | 165 | 173 | 181 | 189 | 190 | 191 |
150 | 151 |
152 | Jayson Sanon 153 |
154 | 💼 155 | 💻 156 |
158 | 159 |
160 | Jose Andrew 161 |
162 | 💼 163 | 💻 164 |
166 | 167 |
168 | Christina Abraham 169 |
170 | 💼 171 | 💻 172 |
174 | 175 |
176 | Elijah Egede 177 |
178 | 💼 179 | 💻 180 |
182 | 183 |
184 | Nathalie Owusu 185 |
186 | 💼 187 | 💻 188 |
192 | 193 | 194 | 195 | 196 | ## Contact 197 | 198 | Feel free to contact us with any questions or suggestions: 199 | 200 | - Jayson: 💼 Jaysonsanon@gmail.com 201 | - Jose: 💼 Joseandrew@live.com 202 | - Christina: 💼 Christinajabraham@yahoo.com 203 | - Elijah: 💼 Eegede22@gmail.com 204 | - Salem: 💼 Nathalieowusudev@gmail.com 205 | 206 |

(back to top)

207 | 208 | ## Acknowledgments 209 | 210 | Special thanks to the following resources: 211 | 212 | - [AWS Documentation](https://aws.amazon.com/documentation/) 213 | - [React Documentation](https://reactjs.org/docs/getting-started.html) 214 | - [Redux Documentation](https://redux.js.org/introduction/getting-started) 215 | - [AWS CloudWatch Documentation](https://docs.aws.amazon.com/cloudwatch/) 216 | - [Choose an Open Source License](https://choosealicense.com) 217 | 218 | 219 | ### Highlights: 220 | - **Technologies**: React, AWS (Cognito, CloudWatch, EC2), Redux, IAM Roles 221 | - **Project Features**: EC2 instance monitoring, CloudWatch visualizations, real-time analytics 222 | - **Usage**: Setting up AWS credentials and visualizing EC2 instance metrics 223 | - **Roadmap**: List of future features like automated scaling recommendations and email/SMS notifications 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | -------------------------------------------------------------------------------- /backend/.aws/config: -------------------------------------------------------------------------------- 1 | [default] 2 | region=us-east-1 3 | output=json -------------------------------------------------------------------------------- /backend/assume-role.js: -------------------------------------------------------------------------------- 1 | import { STSClient, AssumeRoleCommand } from "@aws-sdk/client-sts"; // ES Modules import 2 | 3 | const REGION = "us-east-1"; 4 | 5 | const client = new STSClient({ region: REGION }); 6 | const input = { // AssumeRoleRequest 7 | RoleArn: "arn:aws:iam::536697262297:role/AWSome-help", // required 8 | RoleSessionName: "test-session-1", // required 9 | DurationSeconds: 3600, 10 | ExternalId: "UserJayson", 11 | }; 12 | 13 | const command = new AssumeRoleCommand(input); 14 | 15 | try { 16 | const response = await client.send(command); 17 | console.log(response); 18 | } catch (error) { 19 | console.error(error); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /backend/cloudwatch.js: -------------------------------------------------------------------------------- 1 | import { 2 | CloudWatchClient, // Used to connect to CloudWatch 3 | CloudWatchServiceException, // Handles specific errors from CloudWatch 4 | GetMetricDataCommand, // Sends a request to fetch metric data 5 | } from '@aws-sdk/client-cloudwatch'; 6 | import { fromIni } from '@aws-sdk/credential-providers'; 7 | const region = 'us-east-1'; 8 | 9 | // let log = { 10 | // metric: ['NetworkIn'], 11 | // data: [], 12 | // graph: ['bar'], 13 | // }; 14 | 15 | async function MixedMetrix(metricMap) { 16 | const client = new CloudWatchClient({ 17 | region, 18 | credentials: fromIni({ 19 | profile: 'default', 20 | }), 21 | }); 22 | let response; 23 | const InstanceId = 'i-0610f2356e0d72fcd'; 24 | 25 | const { metric, data, graph } = metricMap; 26 | const queries = []; 27 | 28 | let currentTime = new Date(); 29 | currentTime.setSeconds(0); 30 | while (currentTime.getMinutes() % 5 !== 0) { 31 | currentTime.setMinutes(currentTime.getMinutes() - 1); 32 | } 33 | 34 | let pastHour = new Date(currentTime); 35 | pastHour.setHours(pastHour.getHours() - 1); 36 | 37 | let num = 0; 38 | 39 | for (let i = 0; i < metric.length; i++) { 40 | let id = metric[i].toLowerCase() + graph[i] + num.toString(); 41 | num++; 42 | queries.push( 43 | { 44 | // MetricDataQuery 45 | Id: id, // required 46 | MetricStat: { 47 | // MetricStat 48 | Metric: { 49 | // Metric 50 | Namespace: 'AWS/EC2', 51 | MetricName: metric[i], 52 | Dimensions: [ 53 | // Dimensions 54 | { 55 | // Dimension 56 | Name: 'InstanceId', // required 57 | Value: InstanceId, // required 58 | }, 59 | ], 60 | }, 61 | Period: 300, // required 62 | Stat: 'Average', // required 63 | }, // Lines 17-38 fetches the CPU usage for the EC2 instance 64 | } // Data is averaged over 5 min (period:300 seconds) resulting in a percentage format (Unit:percent) 65 | ); 66 | } 67 | 68 | const input = { 69 | // Input object describes the data we're requesting from CloudWatch 70 | // GetMetricDataInput 71 | MetricDataQueries: queries, 72 | StartTime: pastHour, // required 73 | EndTime: currentTime, // required 74 | ScanBy: 'TimestampAscending', // Gets the newest data first 75 | MaxDatapoints: 10000, // Max Datapoints 100,000 76 | }; 77 | 78 | const command = new GetMetricDataCommand(input); //Creates the request to send to CloudWatch using the input 79 | try { 80 | // Sends the request and waits for the response 81 | response = await client.send(command); 82 | //console.log('response meta: ', response); 83 | 84 | return response.MetricDataResults; // logs the metric data and entire response if successful 85 | } catch (caught) { 86 | if (caught instanceof CloudWatchServiceException) { 87 | // if theres a CloudWatch error, it logs the error name and message 88 | console.error(`Error from CloudWatch. ${caught.name}: ${caught.message}`); 89 | } else { 90 | throw caught; // if its a different error, it throws it so it can be handled elsewhere 91 | } 92 | } 93 | } 94 | 95 | //MixedMetrix(log); 96 | 97 | export default MixedMetrix; 98 | -------------------------------------------------------------------------------- /backend/controllers/authMiddleware.js: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken'; 2 | import jwksClient from 'jwks-rsa'; 3 | import dotenv from 'dotenv'; 4 | dotenv.config(); 5 | 6 | //cognito issuer url 7 | const COGNITO_ISSUER = `https://cognito-idp.us-east-1.amazonaws.com/${process.env.COGNITO_USER_POOL_ID}`; 8 | 9 | //this client is configured to fetch public keys (JWT) 10 | const client = jwksClient({ 11 | jwksUri: `${COGNITO_ISSUER}/.well-known/jwks.json`, 12 | }); 13 | 14 | //function to get signing key 15 | const getKey = async (header) => { 16 | try { 17 | const key = await client.getSigningKey(header.kid); 18 | return key.getPublicKey(); 19 | } catch (err) { 20 | console.error('Error retrieving signing key:', err); 21 | } 22 | }; 23 | 24 | const authenticateToken = async (req, res, next) => { 25 | try { 26 | //grabs the first token (access token) sent in the header otherwise just returns null or undefined 27 | const token = req.headers.authorization?.split(' ')[1]; 28 | 29 | //if token is nonexisten, return error 30 | if (!token) { 31 | return res 32 | .status(401) 33 | .json({ error: 'Access denied. No token provided' }); 34 | } 35 | //if token is not properly fetched and sent (incorrect token) 36 | if (token.split('.').length !== 3) { 37 | return res.status(400).json({ error: 'Malformed JWT token' }); 38 | } 39 | 40 | // Decode the JWT to get the kid (key id) 41 | const decodedHeader = jwt.decode(token, { complete: true })?.header; 42 | //if decodedheader exists, grab the key id 43 | const kid = decodedHeader?.kid; 44 | 45 | //if no key id, return error 46 | if (!kid) { 47 | return res 48 | .status(400) 49 | .json({ error: 'Invalid token: Missing kid in token header' }); 50 | } 51 | // Fetch the public key using the kid 52 | const publicKey = await getKey({ kid }); 53 | 54 | // Verify the JWT using the public key 55 | const decoded = jwt.verify(token, publicKey, { issuer: COGNITO_ISSUER }); 56 | 57 | req.user = decoded; // Attach the decoded user to the request object 58 | next(); 59 | } catch (err) { 60 | console.error('Error authenticating token:', err); 61 | return res.status(403).json({ error: 'Access denied' }); 62 | } 63 | }; 64 | 65 | export default authenticateToken; 66 | -------------------------------------------------------------------------------- /backend/data.js: -------------------------------------------------------------------------------- 1 | import { 2 | CloudWatchClient, // Used to connect to CloudWatch 3 | CloudWatchServiceException, // Handles specific errors from CloudWatch 4 | GetMetricDataCommand, // Sends a request to fetch metric data 5 | } from '@aws-sdk/client-cloudwatch'; 6 | import awsCredentialProviders from '@aws-sdk/credential-providers'; 7 | 8 | const { fromSSO } = awsCredentialProviders; 9 | 10 | const credentials = await fromSSO({ 11 | profile: 'AdministratorAccess-913524940612', 12 | }); 13 | const client = new CloudWatchClient({ 14 | region: 'us-east-1', 15 | credentials, 16 | }); 17 | 18 | 19 | import pkg from 'pg'; 20 | const { Pool } = pkg; 21 | 22 | let response; 23 | 24 | export { client }; 25 | 26 | export const awsData = async () => { 27 | // Starts the awsData function, marked as async because it handles promises (waiting for AWS data) 28 | const input = { 29 | // Input object describes the data we're requesting from CloudWatch 30 | // GetMetricDataInput 31 | MetricDataQueries: [ 32 | // A list of all the metrics we want 33 | // MetricDataQueries // required 34 | { 35 | // MetricDataQuery 36 | Id: 'cpuutilization', // required 37 | MetricStat: { 38 | // MetricStat 39 | Metric: { 40 | // Metric 41 | Namespace: 'AWS/EC2', 42 | MetricName: 'CPUUtilization', 43 | Dimensions: [ 44 | // Dimensions 45 | { 46 | // Dimension 47 | Name: 'InstanceId', // required 48 | Value: 'i-03dc51c409e900f26', // required 49 | }, 50 | ], 51 | }, 52 | Period: 300, // required 53 | Stat: 'Average', // required 54 | Unit: 'Percent', 55 | }, // Lines 17-38 fetches the CPU usage for the EC2 instance 56 | }, // Data is averaged over 5 min (period:300 seconds) resulting in a percentage format (Unit:percent) 57 | { 58 | // MetricDataQuery 59 | Id: 'networkin', // required 60 | MetricStat: { 61 | // MetricStat 62 | Metric: { 63 | // Metric 64 | Namespace: 'AWS/EC2', 65 | MetricName: 'NetworkIn', 66 | Dimensions: [ 67 | // Dimensions 68 | { 69 | // Dimension 70 | Name: 'InstanceId', // required 71 | Value: 'i-03dc51c409e900f26', // required 72 | }, 73 | ], 74 | }, 75 | Period: 300, // required 76 | Stat: 'Average', // required 77 | Unit: 'Bytes', 78 | }, // 41-60 fetching the incoming network traffic (NetworkIn) in Bytes 79 | }, 80 | { 81 | // MetricDataQuery 82 | Id: 'networkout', // required 83 | MetricStat: { 84 | // MetricStat 85 | Metric: { 86 | // Metric 87 | Namespace: 'AWS/EC2', 88 | MetricName: 'NetworkOut', 89 | Dimensions: [ 90 | // Dimensions 91 | { 92 | // Dimension 93 | Name: 'InstanceId', // required 94 | Value: 'i-03dc51c409e900f26', // required 95 | }, 96 | ], 97 | }, 98 | Period: 300, // required 99 | Stat: 'Average', // required 100 | Unit: 'Bytes', 101 | }, // lines 64-83 fetching the outgoing network traffic (NetworkOut) in Bytes 102 | }, 103 | { 104 | // MetricDataQuery 105 | Id: 'write', // required 106 | MetricStat: { 107 | // MetricStat 108 | Metric: { 109 | // Metric 110 | Namespace: 'AWS/EC2', 111 | MetricName: 'EBSWriteOps', 112 | Dimensions: [ 113 | // Dimensions 114 | { 115 | // Dimension 116 | Name: 'InstanceId', // required 117 | Value: 'i-03dc51c409e900f26', // required 118 | }, 119 | ], 120 | }, 121 | Period: 300, // required 122 | Stat: 'Average', // required 123 | Unit: 'Count', 124 | }, //lines 87-106 fetches write operations on the instance's disk (EBSWriteOps) 125 | }, 126 | { 127 | // MetricDataQuery 128 | Id: 'read', // required 129 | MetricStat: { 130 | // MetricStat 131 | Metric: { 132 | // Metric 133 | Namespace: 'AWS/EC2', 134 | MetricName: 'EBSReadOps', 135 | Dimensions: [ 136 | // Dimensions 137 | { 138 | // Dimension 139 | Name: 'InstanceId', // required 140 | Value: 'i-03dc51c409e900f26', // required 141 | }, 142 | ], 143 | }, 144 | Period: 300, // required 145 | Stat: 'Average', // required 146 | Unit: 'Count', 147 | }, //lines 110-129 fetches read operations on the instance's disk (EBSReadOps) 148 | }, 149 | ], 150 | StartTime: new Date('2025-01-18T23:05:00.000Z'), // required 151 | EndTime: new Date('2025-01-18T23:30:00.000Z'), // required 152 | ScanBy: 'TimestampDescending', // Gets the newest data first 153 | MaxDatapoints: 1000, // Max Datapoints 100,000 154 | }; 155 | 156 | const command = new GetMetricDataCommand(input); //Creates the request to send to CloudWatch using the input 157 | try { 158 | // Sends the request and waits for the response 159 | response = await client.send(command); 160 | return response; // logs the metric data and entire response if successful 161 | } catch (caught) { 162 | if (caught instanceof CloudWatchServiceException) { 163 | // if theres a CloudWatch error, it logs the error name and message 164 | console.error(`Error from CloudWatch. ${caught.name}: ${caught.message}`); 165 | } else { 166 | throw caught; // if its a different error, it throws it so it can be handled elsewhere 167 | } 168 | } 169 | 170 | console.log('Inserting metrics into database...'); 171 | for (const metric of response.MetricDataResults) { 172 | for (let i = 0; i < metric.Timestamps.length; i++) { 173 | // console.log('Metric:', metric.Id, 'Value:', metric.Values[i]); // Debugging output 174 | const query = ` 175 | INSERT INTO aws_metrics 176 | (aws_account_id, metric_name, metric_value, timestamp, service_name, region) 177 | VALUES ($1, $2, $3, $4, $5, $6) 178 | `; 179 | const values = [ 180 | 1, // Replace with the actual AWS account ID 181 | metric.Id, 182 | metric.Values[i], 183 | metric.Timestamps[i], 184 | 'EC2', 185 | 'us-east-1', 186 | ]; 187 | 188 | await Pool.query(query, values); 189 | } 190 | } 191 | // console.log('Metrics inserted successfully!'); 192 | }; 193 | 194 | // ✅ Correctly formatted IIFE to execute `awsData()` 195 | // (async () => { 196 | // const data = await awsData(); 197 | // console.log("Final response:", data); 198 | // })(); 199 | 200 | export const awsHourData = async () => { 201 | const input = { 202 | // GetMetricDataInput 203 | MetricDataQueries: [ 204 | // MetricDataQueries // required 205 | { 206 | // MetricDataQuery 207 | Id: 'cpuutilization', // required 208 | MetricStat: { 209 | // MetricStat 210 | Metric: { 211 | // Metric 212 | Namespace: 'AWS/EC2', 213 | MetricName: 'CPUUtilization', 214 | Dimensions: [ 215 | // Dimensions 216 | { 217 | // Dimension 218 | Name: 'InstanceId', // required 219 | Value: 'i-02b1d8cc57898903d', // required 220 | }, 221 | ], 222 | }, 223 | Period: 300, // required 224 | Stat: 'Average', // required 225 | Unit: 'Percent', 226 | }, // Lines 17-38 fetches the CPU usage for the EC2 instance 227 | }, // Data is averaged over 5 min (period:300 seconds) resulting in a percentage format (Unit:percent) 228 | ], 229 | StartTime: new Date('2025-01-27T18:50:00.000Z'), // required 230 | EndTime: new Date('2025-01-27T19:55:00.000Z'), // required 231 | ScanBy: 'TimestampAscending', // Gets the oldest data first 232 | MaxDatapoints: 1000, // Max Datapoints 100,000 233 | }; 234 | const command = new GetMetricDataCommand(input); //Creates the request to send to CloudWatch using the input 235 | try { 236 | // Sends the request and waits for the response 237 | const response = await client.send(command); 238 | //console.log('response results', response.MetricDataResults); 239 | return response.MetricDataResults; // logs the metric data and entire response if successful 240 | } catch (caught) { 241 | if (caught instanceof CloudWatchServiceException) { 242 | // if theres a CloudWatch error, it logs the error name and message 243 | // console.error(`Error from CloudWatch. ${caught.name}: ${caught.message}`); 244 | } else { 245 | console.log('why is it spicy'); 246 | throw caught; // if its a different error, it throws it so it can be handled elsewhere 247 | } 248 | } 249 | }; 250 | -------------------------------------------------------------------------------- /backend/externalIDGenerator.js: -------------------------------------------------------------------------------- 1 | const externalIdGenerator = () => { 2 | const letters = '1234567890qwertyuiopasdfghjklzxcvbnm'; 3 | let result = ''; 4 | for (let i = 0; i < 10; i++) { 5 | result += letters[Math.floor(Math.random() * 35)]; 6 | } 7 | return result; 8 | }; 9 | 10 | export default externalIdGenerator; 11 | -------------------------------------------------------------------------------- /backend/fetch.js: -------------------------------------------------------------------------------- 1 | export async function AWSdata(graphs) { 2 | try { 3 | let res = await fetch('http://localhost:81/data', { 4 | headers: { 5 | 'Content-Type': 'application/json', 6 | }, 7 | method: 'POST', 8 | body: JSON.stringify(graphs), 9 | }); 10 | 11 | if (!res.ok) { 12 | throw new Error('Network response was not ok'); 13 | } 14 | 15 | let data = await res.json(); 16 | 17 | return data; 18 | } catch (error) { 19 | console.error(error); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /backend/metrics.js: -------------------------------------------------------------------------------- 1 | import client from './modal.js'; // Import PostgreSQL client from modal.js 2 | import { awsData } from './data.js'; // Import the awsData function from data.js 3 | 4 | // Function to transform fetched metric data 5 | const transformMetrics = (metricResults) => { 6 | return metricResults.map((result) => ({ 7 | metricName: result.Id, 8 | timestamps: result.Timestamps || [], 9 | values: result.Values || [], 10 | })); 11 | }; 12 | // Function to save the transformed metrics data into PostgreSQL (using UPSERT) 13 | const saveMetricsToDatabase = async (awsAccountId, transformedMetrics) => { 14 | for (const metric of transformedMetrics) { 15 | for (let i = 0; i < metric.timestamps.length; i++) { 16 | // Call updateMetric to handle insert/update logic 17 | await updateMetric( 18 | awsAccountId, 19 | metric.metricName, 20 | null, // instanceId (if available, otherwise null) 21 | metric.values[i], 22 | metric.timestamps[i], 23 | 'Average', // Stat (you can modify based on data) 24 | '%', // Unit (you can modify based on data) 25 | 60 // Period (you can modify based on data) 26 | ); 27 | } 28 | } 29 | }; 30 | // Function to update or insert the metric using UPSERT logic 31 | async function updateMetric( 32 | awsAccountId, 33 | metricName, 34 | instanceId, 35 | metricValue, 36 | timestamp, 37 | stat, 38 | unit, 39 | period 40 | ) { 41 | const query = ` 42 | INSERT INTO aws_metrics (aws_account_id, metric_name, instance_id, metric_value, timestamp, stat, unit, period) 43 | VALUES ($1, $2, $3, $4, $5, $6, $7, $8) 44 | ON CONFLICT (aws_account_id, metric_name, timestamp) 45 | DO UPDATE 46 | SET 47 | metric_value = $4, 48 | stat = $6, 49 | unit = $7, 50 | period = $8; 51 | `; 52 | 53 | try { 54 | // Execute the UPSERT query with the provided parameters 55 | await client.query(query, [ 56 | awsAccountId, 57 | metricName, 58 | instanceId, 59 | metricValue, 60 | timestamp, 61 | stat, 62 | unit, 63 | period, 64 | ]); 65 | console.log('Metric updated successfully'); 66 | } catch (err) { 67 | console.error('Error updating metric:', err); 68 | } 69 | } 70 | 71 | // Function to get AWS account ID by user email 72 | const getAwsAccountIdByEmail = async (email) => { 73 | const query = `SELECT aws_account_id FROM aws_accounts WHERE user_id = (SELECT id FROM users WHERE email = $1)`; 74 | const result = await client.query(query, [email]); 75 | if (result.rows.length === 0) { 76 | throw new Error('AWS account not found for the provided email.'); 77 | } 78 | return result.rows[0].aws_account_id; 79 | }; 80 | 81 | // Orchestrating the full workflow 82 | const Workflow = async () => { 83 | try { 84 | // Get the AWS account ID from the email 85 | const awsAccountId = await getAwsAccountIdByEmail('salem.moon@icloud.com'); 86 | if (!awsAccountId) { 87 | throw new Error('No AWS account found for the provided email.'); 88 | } 89 | //CAN BE DELETED --> testing Account ID 90 | console.log('AWS Account ID:', awsAccountId); 91 | // Fetch the data from AWS CloudWatch 92 | const data = await awsData(); 93 | console.log('Fetched AWS Data:', data); // Log the raw AWS data to inspect it 94 | 95 | // Check if data.MetricDataResults exists 96 | if (!data.MetricDataResults) { 97 | throw new Error('MetricDataResults not found in AWS data.'); 98 | } 99 | 100 | // Transform the data 101 | const transformedMetrics = transformMetrics(data.MetricDataResults); 102 | console.log('Transformed Metrics:', transformedMetrics); // Log the transformed metrics 103 | 104 | // Save the transformed metrics into the database 105 | await saveMetricsToDatabase(awsAccountId, transformedMetrics); 106 | } catch (err) { 107 | console.error('Error in the workflow:', err); 108 | } finally { 109 | await client.end(); // Close connection after all queries 110 | } 111 | }; 112 | 113 | // Call Workflow to initiate the process 114 | Workflow(); 115 | -------------------------------------------------------------------------------- /backend/modal.js: -------------------------------------------------------------------------------- 1 | import pg from 'pg'; 2 | 3 | // Create a PostgreSQL client instance 4 | const client = new pg.Client({ 5 | user: 'awsome_members', // Your PostgreSQL username 6 | host: 'localhost', // If PostgreSQL is running locally 7 | database: 'aws_monitoring', // The database you created 8 | password: 'AWSomepassword', // Your PostgreSQL user's password 9 | port: 5432, // Default PostgreSQL port 10 | }); 11 | 12 | // Connect to the PostgreSQL database 13 | client 14 | .connect() 15 | .then(() => console.log('Connected to PostgreSQL!')) 16 | .catch((err) => console.log('uncomment below in modal:', err)); 17 | //console.error("Error connecting to PostgreSQL:", err) 18 | 19 | export default client; // Export the client instance for use in other modules 20 | -------------------------------------------------------------------------------- /backend/routes/ApiRoutes.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | import client from "../modal.js"; 3 | 4 | const Awsrouter = express.Router(); 5 | 6 | // get all monitored AWS metrics 7 | Awsrouter.get("/", async (req, res) => { 8 | try { 9 | const results = await client.query("SELECT * FROM aws_metrics"); 10 | res.json(results.rows); 11 | } catch (err) { 12 | console.error("Error fetching AWS metrics:", err); 13 | res.status(500).send("Server error"); 14 | } 15 | }); 16 | 17 | //Add a new AWS metrics 18 | Awsrouter.post("/", async (req, res) => { 19 | try { 20 | const { metrics_name, status } = req.body; 21 | const result = await client.query( 22 | "INSERT INTO aws_metrics (metrics_name, status) VALUES ($1, $2) RETURNING *", 23 | [metrics_name, status] 24 | ); 25 | res.json(result.rows[0]); 26 | } catch (err) { 27 | console.error("Error inserting AWS metrics:", err); 28 | res.status(500).send("Server error"); 29 | } 30 | }); 31 | 32 | export default Awsrouter; 33 | -------------------------------------------------------------------------------- /backend/server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import externalIdGenerator from './externalIDGenerator.js'; 4 | import MixedMetrix from './cloudwatch.js'; 5 | import authenticateToken from './controllers/authMiddleware.js'; 6 | 7 | const port = 81; 8 | 9 | const app = express(); 10 | 11 | app.use( 12 | cors({ 13 | origin: true, 14 | methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], 15 | allowedHeaders: ['Content-Type'], 16 | credentials: true, 17 | preflightContinue: false, 18 | }) 19 | ); 20 | app.use(express.json()); 21 | app.use(express.urlencoded({ extended: true })); 22 | 23 | app.get('/random', async (req, res) => { 24 | let id = await externalIdGenerator(); 25 | return res.status(200).json({ id }); 26 | }); 27 | 28 | app.post('/data', async (req, res) => { 29 | const { graph, metric, data } = req.body; 30 | let result = await MixedMetrix({ graph, metric, data }); 31 | return res.status(200).json({ result }); 32 | }); 33 | 34 | app.options('/data', async (req, res) => { 35 | const { graph, metric, data } = req.body; 36 | let result = await MixedMetrix({ graph, metric, data }); 37 | return res.status(200).json({ result }); 38 | }); 39 | 40 | // WILL BE USED TO PROTECT ANY REQUESTS FOR DATA (ENSURES USER IS AUTHENTICATED) 41 | // app.get('/protected', authenticateToken, (req, res) => { 42 | // res.status(200).json('Success, accessed a protected route'); 43 | // }); 44 | 45 | app.use((req, res) => res.status(404).send('No Data')); 46 | 47 | app.use((err, req, res, next) => { 48 | const defaultErr = { 49 | log: 'Something Went Wrong in server 2', 50 | status: 500, 51 | message: { err: 'An error occurred' }, 52 | }; 53 | const errorObj = Object.assign({}, defaultErr, err); 54 | console.log(errorObj.log); 55 | return res.status(errorObj.status).json(errorObj.message); 56 | }); 57 | 58 | app.listen(port, () => { 59 | console.log(`Server started at http://localhost:${port}`); 60 | }); 61 | -------------------------------------------------------------------------------- /backend/serverDelete.js: -------------------------------------------------------------------------------- 1 | //CAN BE DELETED 2 | // import express from 'express'; 3 | // import { fileURLToPath } from 'node:url'; 4 | // import path from 'node:path'; 5 | // import { awsData, awsHourData } from './data.js'; 6 | // import Awsrouter from './routes/ApiRoutes.js'; 7 | // import externalIdGenerator from './externalIDGenerator.js'; 8 | 9 | // const port = 3000; 10 | // const app = express(); 11 | 12 | // const __dirname = 13 | // path.dirname(fileURLToPath(import.meta.url)) || path.resolve(); 14 | 15 | // app.use(express.json()); 16 | // app.use(express.urlencoded({ extended: true })); 17 | // app.use(express.static(path.join(__dirname, 'dist'))); 18 | 19 | // //CAN BE DELETED, WAS ONCE A TEST ROUTER 20 | // app.use('/aws_services', Awsrouter); 21 | 22 | // //TEST ROUTE, can be deleted 23 | // app.get('/protected', authenticateToken, (req, res) => { 24 | // res.json({ message: 'You have accessed a protected route!', user: req.user }); 25 | // }); 26 | 27 | // //VITE CONFIG file allows for this to be just /data instead of /Home/data 28 | // app.get('/data', async (req, res) => { 29 | // let data = await awsHourData(); 30 | // res.status(200).json(data); 31 | // }); 32 | 33 | 34 | 35 | // app.use((req, res) => 36 | // res.status(404).send("This is not the page you're looking for...") 37 | // ); 38 | 39 | // //default global error handler 40 | // app.use((err, req, res, next) => { 41 | // const defaultErr = { 42 | // log: 'Express error handler caught unknown middleware error', 43 | // status: 500, 44 | // message: { err: 'An error occurred' }, 45 | // }; 46 | // const errorObj = Object.assign({}, defaultErr, err); 47 | // console.log(errorObj.log); 48 | // return res.status(errorObj.status).json(errorObj.message); 49 | // }); 50 | 51 | // app.listen(port, () => { 52 | // console.log(`Server started at http://localhost:${port}`); 53 | // }); 54 | -------------------------------------------------------------------------------- /docker-compose-prod.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | dev: 3 | image: prod 4 | container_name: prod-c 5 | ports: 6 | - "80:80" 7 | networks: 8 | - mynetwork 9 | depends_on: 10 | - "server" 11 | server: 12 | image: server 13 | container_name: server-c 14 | networks: 15 | - mynetwork 16 | ports: 17 | - "81:81" 18 | 19 | networks: 20 | mynetwork: -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js' 2 | import globals from 'globals' 3 | import react from 'eslint-plugin-react' 4 | import reactHooks from 'eslint-plugin-react-hooks' 5 | import reactRefresh from 'eslint-plugin-react-refresh' 6 | 7 | export default [ 8 | { ignores: ['dist'] }, 9 | { 10 | files: ['**/*.{js,jsx}'], 11 | languageOptions: { 12 | ecmaVersion: 2020, 13 | globals: globals.browser, 14 | parserOptions: { 15 | ecmaVersion: 'latest', 16 | ecmaFeatures: { jsx: true }, 17 | sourceType: 'module', 18 | }, 19 | }, 20 | settings: { react: { version: '18.3' } }, 21 | plugins: { 22 | react, 23 | 'react-hooks': reactHooks, 24 | 'react-refresh': reactRefresh, 25 | }, 26 | rules: { 27 | ...js.configs.recommended.rules, 28 | ...react.configs.recommended.rules, 29 | ...react.configs['jsx-runtime'].rules, 30 | ...reactHooks.configs.recommended.rules, 31 | 'react/jsx-no-target-blank': 'off', 32 | 'react-refresh/only-export-components': [ 33 | 'warn', 34 | { allowConstantExport: true }, 35 | ], 36 | }, 37 | }, 38 | ] 39 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | AWSome: AWS Monitoring Tool 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /nginx/default.conf: -------------------------------------------------------------------------------- 1 | server { 2 | # Nginx listens on port 80 by default. You can change this if needed. 3 | listen 80; 4 | 5 | # Specifies your domain. Use "localhost" for local development or your domain name for production. 6 | server_name Awsome-prod; 7 | 8 | # The root directory that contains the `dist` folder generated after building your app. 9 | root /usr/share/nginx/html; 10 | index index.html; 11 | 12 | # Serve all routes and pages 13 | # Use the base name to serve all pages. In this case, the base name is "/". 14 | location / { 15 | try_files $uri /index.html =404; 16 | 17 | 18 | 19 | 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AWSome", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite & nodemon ./backend/server.js", 8 | "build": "vite build", 9 | "lint": "eslint .", 10 | "preview": "vite preview & node ./backend/server.js", 11 | "server-docker": "node ./serve.js" 12 | }, 13 | "dependencies": { 14 | "@aws-sdk/client-cloudwatch": "^3.731.1", 15 | "@aws-sdk/client-cognito-identity-provider": "^3.744.0", 16 | "@aws-sdk/client-s3": "^3.730.0", 17 | "@aws-sdk/client-sts": "^3.744.0", 18 | "@aws-sdk/credential-providers": "^3.744.0", 19 | "@headlessui/react": "^2.2.0", 20 | "@material-tailwind/react": "^2.1.10", 21 | "@reduxjs/toolkit": "^2.5.1", 22 | "@tailwindcss/vite": "^4.0.0", 23 | "amazon-cognito-identity-js": "^6.3.12", 24 | "aws-sdk": "^2.1692.0", 25 | "chart.js": "^4.4.7", 26 | "cors": "^2.8.5", 27 | "d3": "^7.9.0", 28 | "express": "^4.21.2", 29 | "jsonwebtoken": "^9.0.2", 30 | "jwks-rsa": "^3.1.0", 31 | "pg": "^8.13.1", 32 | "postgres": "^3.4.5", 33 | "react": "^18.3.1", 34 | "react-chartjs-2": "^5.3.0", 35 | "react-dom": "^18.3.1", 36 | "react-redux": "^9.2.0", 37 | "react-router": "^7.1.2", 38 | "react-router-dom": "^7.2.0", 39 | "uuid": "^11.0.5", 40 | "vite-plugin-node-polyfills": "^0.23.0" 41 | }, 42 | "devDependencies": { 43 | "@eslint/js": "^9.17.0", 44 | "@types/react": "^18.3.18", 45 | "@types/react-dom": "^18.3.5", 46 | "@vitejs/plugin-react": "^4.3.4", 47 | "autoprefixer": "^10.4.20", 48 | "babel-plugin-transform-import-meta": "^2.3.2", 49 | "dotenv": "^16.4.7", 50 | "eslint": "^9.17.0", 51 | "eslint-plugin-react": "^7.37.2", 52 | "eslint-plugin-react-hooks": "^5.0.0", 53 | "eslint-plugin-react-refresh": "^0.4.16", 54 | "globals": "^15.14.0", 55 | "nodemon": "^3.1.9", 56 | "postcss": "^8.5.2", 57 | "tailwindcss": "^3.4.17", 58 | "typescript": "^5.7.3", 59 | "vite": "^6.0.11" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/AWSome/534d7ddad81a1cf7e02f59cd2ab70c698b3de3ed/src/icon.png -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html { 6 | background-color: rgb(255, 255, 255); 7 | } 8 | h1 { 9 | color: rgb(0, 0, 0); 10 | } 11 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import { StrictMode } from 'react'; 2 | import './index.css'; 3 | import { createRoot } from 'react-dom/client'; 4 | import Error from './pages/Error.jsx'; 5 | import Login from './pages/login/Login.tsx'; 6 | import Signup from './pages/login/Signup.tsx'; 7 | import Layout from './pages/Layout.jsx'; 8 | import Forgot from './pages/login/Forgot.tsx'; 9 | import BarChart from './pages/components/Barchart.jsx'; 10 | import { BrowserRouter, Routes, Route } from 'react-router'; 11 | import LineChart from './pages/components/Linechart.jsx'; 12 | import { AuthProvider } from './pages/context/AuthContext.jsx'; 13 | import ProtectedRoute from './pages/components/ProtectedRoutes.jsx'; 14 | import NewUser from './pages/newUserProfile.jsx'; 15 | import Dashboard from './pages/Dashboard.jsx'; 16 | 17 | //polyfill for global 18 | window.global = window; 19 | 20 | createRoot(document.getElementById('root')).render( 21 | 22 | 23 | 24 | {/* }> */} 25 | } /> 26 | } /> 27 | {/* */} 28 | }> 29 | } /> 30 | } /> 31 | 32 | } /> 33 | } /> 34 | 35 | 36 | 37 | ); 38 | // removed strict mode 39 | -------------------------------------------------------------------------------- /src/pages/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import store from '../state/store.js'; 2 | import Home from './Home.jsx'; 3 | import { Provider } from 'react-redux'; 4 | 5 | const Dashboard = () => { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | }; 12 | 13 | export default Dashboard; 14 | -------------------------------------------------------------------------------- /src/pages/Error.jsx: -------------------------------------------------------------------------------- 1 | function Error() { 2 | return ( 3 | <> 4 |

This is not the page you are looking for, sorry about that.

5 | 6 | ); 7 | } 8 | 9 | export default Error; 10 | -------------------------------------------------------------------------------- /src/pages/Home.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router'; 2 | import BarChart from './components/Barchart.jsx'; 3 | import LineChart from './components/Linechart.jsx'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import DropDownMenu from './components/menu.jsx'; 6 | import { useAuth } from './context/AuthContext.jsx'; 7 | import { useState, useRef, useEffect } from 'react'; 8 | import { getData } from '../state/graph-reducer.js'; 9 | import { AWSdata } from '../../backend/fetch.js'; 10 | 11 | const Home = () => { 12 | const { signOut, userSession } = useAuth(); 13 | const navigate = useNavigate(); 14 | 15 | useEffect(() => { 16 | if (!userSession) navigate('/'); 17 | }, [userSession, navigate]); 18 | 19 | const logOut = () => { 20 | signOut(); 21 | }; 22 | 23 | const graphs = useSelector((state) => state.graphs); 24 | const dispatch = useDispatch(); 25 | const newgraph = []; 26 | 27 | for (let i = 0; i < graphs.graph.length; i++) { 28 | if (graphs.graph[i] === 'bar') { 29 | let bar = ( 30 |
34 |

{graphs.metric[i]}

35 | 36 |
37 | ); 38 | newgraph.push(bar); 39 | } else if (graphs.graph[i] === 'areaLine') { 40 | let line = ( 41 |
45 |

{graphs.metric[i]}

46 | 47 |
48 | ); 49 | 50 | newgraph.push(line); 51 | } 52 | } 53 | 54 | const [isOpen, setIsOpen] = useState(false); 55 | const dropdownRef = useRef(null); 56 | 57 | const handleMouseEnter = () => { 58 | setIsOpen(true); 59 | }; 60 | 61 | useEffect(() => { 62 | const handleClickOutside = (event) => { 63 | if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 64 | setIsOpen(false); 65 | } 66 | }; 67 | 68 | document.addEventListener('mousedown', handleClickOutside); 69 | return () => { 70 | document.removeEventListener('mousedown', handleClickOutside); 71 | }; 72 | }, []); 73 | 74 | return ( 75 |
76 | {/* Navbar */} 77 |
78 |

AWSome

79 | 80 |
navigate('/newUserProfile')} 83 | > 84 | Account 85 |
86 | 93 | 94 | 100 | 101 | {/* {isOpen && ( 102 | */} 111 | {/* )} */} 112 |
113 | 114 | {/*

*/} 115 | 116 | {/*

*/} 117 |
118 | {/*Connect to User's AWS Account Button */} 119 |
120 | {/* */} 129 | 130 |
131 | 132 |
136 | {newgraph} 137 |
138 |
139 | 140 | {/* Footer */} 141 | 146 |
147 | ); 148 | }; 149 | 150 | export default Home; 151 | -------------------------------------------------------------------------------- /src/pages/Layout.jsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from 'react-router'; 2 | 3 | //layout for navigation between pages 4 | function Layout() { 5 | //REFACTOR TO ADD NAVBAR COMPONENTS HERE, to reduce repetition of code 6 | return ( 7 | <> 8 |
9 | 10 |
11 | 12 | ); 13 | } 14 | 15 | export default Layout; 16 | -------------------------------------------------------------------------------- /src/pages/components/Barchart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | BarElement, 6 | Title, 7 | Tooltip, 8 | Legend, 9 | } from 'chart.js'; 10 | import { Bar } from 'react-chartjs-2'; 11 | import { useEffect, useState } from 'react'; 12 | 13 | export default function BarChart({ table }) { 14 | let Timestamps = []; 15 | let Values = []; 16 | let type = 'No Data'; 17 | 18 | if (table !== 1) { 19 | const graphed = []; 20 | 21 | Timestamps = table.Timestamps; 22 | Values = table.Values; 23 | let Label = table.Label; 24 | let labels = []; 25 | 26 | if (Label === 'NetworkIn' || Label === 'NetworkOut') { 27 | type = 'Bytes'; 28 | } else if (Label === 'CPUUtilization') { 29 | type = 'Percent'; 30 | } else { 31 | type = 'Count'; 32 | } 33 | 34 | // why does Timestamps.length give error 35 | for (let i = 0; i < 12; i++) { 36 | let string = ''; 37 | const newdate = new Date(Timestamps[i]); 38 | string = `${newdate.getHours()}:${newdate.getMinutes()}`; 39 | 40 | labels.push(); 41 | graphed.push({ x: string, y: Values[i] }); 42 | } 43 | 44 | ChartJS.register( 45 | CategoryScale, 46 | LinearScale, 47 | BarElement, 48 | Title, 49 | Tooltip, 50 | Legend 51 | ); 52 | const options = { 53 | responsive: true, 54 | plugins: { 55 | legend: { 56 | position: 'top', 57 | labels: { 58 | color: 'white', 59 | }, 60 | }, 61 | title: { 62 | display: true, 63 | text: 'Instance: i-0610f2356e0d72fcd', 64 | color: 'white', 65 | }, 66 | }, 67 | scales: { 68 | x: { 69 | ticks: { 70 | color: 'white', 71 | }, 72 | }, 73 | y: { 74 | ticks: { 75 | color: 'white', 76 | }, 77 | }, 78 | }, 79 | }; 80 | 81 | const data = { 82 | labels, 83 | datasets: [ 84 | { 85 | barPercentage: 1, 86 | categoryPercentage: 1, 87 | label: type, 88 | data: graphed, 89 | backgroundColor: [ 90 | '#3e95cd', 91 | '#8e5ea2', 92 | '#3cba9f', 93 | '#e8c3b9', 94 | '#c45850', 95 | ], 96 | }, 97 | ], 98 | }; 99 | 100 | return ( 101 |
102 | 103 |
104 | ); 105 | } else { 106 | 107 | const [barX, setData] = useState([ 108 | { x: 0, y: null }, 109 | { x: 5, y: null }, 110 | { x: 10, y: null }, 111 | { X: 15, y: null }, 112 | { x: 20, y: null }, 113 | { x: 25, y: null }, 114 | { x: 30, y: null }, 115 | { x: 35, y: null }, 116 | { x: 40, y: null }, 117 | { x: 45, y: null }, 118 | { x: 50, y: null }, 119 | { x: 55, y: null }, 120 | { x: 60, y: null }, 121 | { x: null, y: 100 }, 122 | ]); 123 | 124 | const labels = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; 125 | 126 | ChartJS.register( 127 | CategoryScale, 128 | LinearScale, 129 | BarElement, 130 | Title, 131 | Tooltip, 132 | Legend 133 | ); 134 | const options = { 135 | responsive: true, 136 | plugins: { 137 | legend: { 138 | position: 'top', 139 | labels: { 140 | color: 'white', 141 | }, 142 | }, 143 | title: { 144 | display: true, 145 | color: 'white', 146 | }, 147 | }, 148 | scales: { 149 | x: { 150 | ticks: { 151 | color: 'white', 152 | }, 153 | }, 154 | y: { 155 | ticks: { 156 | color: 'white', 157 | }, 158 | }, 159 | }, 160 | }; 161 | 162 | const data = { 163 | labels, 164 | datasets: [ 165 | { 166 | barPercentage: 1, 167 | categoryPercentage: 1, 168 | data: barX, 169 | label: 'No Data', 170 | backgroundColor: [ 171 | '#3e95cd', 172 | '#8e5ea2', 173 | '#3cba9f', 174 | '#e8c3b9', 175 | '#c45850', 176 | ], 177 | }, 178 | ], 179 | }; 180 | 181 | return ( 182 |
183 | 184 |
185 | ); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /src/pages/components/Linechart.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart as ChartJS, 3 | CategoryScale, 4 | LinearScale, 5 | PointElement, 6 | Filler, 7 | LineElement, 8 | Title, 9 | Tooltip, 10 | Legend, 11 | } from 'chart.js'; 12 | import { Line } from 'react-chartjs-2'; 13 | import { useEffect, useState } from 'react'; 14 | 15 | export default function LineChart({ table }) { 16 | let Timestamps = []; 17 | let Values = []; 18 | let type = 'No Data'; 19 | 20 | 21 | if (table !== 1) { 22 | const graphed = []; 23 | 24 | Timestamps = table.Timestamps; 25 | Values = table.Values; 26 | let Label = table.Label; 27 | let labels = []; 28 | 29 | if (Label === 'NetworkIn' || Label === 'NetworkOut') { 30 | type = 'Bytes'; 31 | } else if (Label === 'CPUUtilization') { 32 | type = "Percent"; 33 | } else { 34 | type = 'Count'; 35 | } 36 | 37 | for (let i = 0; i < 12; i++) { 38 | let string = '' 39 | const newdate = new Date(Timestamps[i]); 40 | string = `${newdate.getHours()}:${newdate.getMinutes()}`; 41 | 42 | labels.push() 43 | graphed.push({ x: string, y: Values[i] }); 44 | } 45 | 46 | ChartJS.register( 47 | CategoryScale, 48 | LinearScale, 49 | PointElement, 50 | LineElement, 51 | Title, 52 | Tooltip, 53 | Filler, 54 | Legend 55 | ); 56 | 57 | const options = { 58 | responsive: true, 59 | plugins: { 60 | legend: { 61 | position: 'top', 62 | labels: { 63 | color: 'white', 64 | }, 65 | }, 66 | title: { 67 | display: true, 68 | text: 'Instance: i-0610f2356e0d72fcd', 69 | color: 'white', 70 | }, 71 | }, 72 | scales: { 73 | x: { 74 | ticks: { 75 | color: 'white', 76 | }, 77 | }, 78 | y: { 79 | ticks: { 80 | color: 'white', 81 | }, 82 | }, 83 | }, 84 | }; 85 | 86 | const data = { 87 | labels, 88 | datasets: [ 89 | { 90 | fill: true, 91 | label: type, 92 | data: graphed, 93 | borderColor: 'rgb(53, 162, 235)', 94 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 95 | }, 96 | ], 97 | }; 98 | 99 | return ( 100 |
101 | 102 |
103 | ); 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | } else { 117 | ChartJS.register( 118 | CategoryScale, 119 | LinearScale, 120 | PointElement, 121 | LineElement, 122 | Title, 123 | Tooltip, 124 | Filler, 125 | Legend 126 | ); 127 | 128 | const options = { 129 | responsive: true, 130 | plugins: { 131 | legend: { 132 | position: 'top', 133 | labels: { 134 | color: 'white', 135 | }, 136 | }, 137 | title: { 138 | display: true, 139 | text: 'Instance: i-0610f2356e0d72fcd', 140 | color: 'white', 141 | }, 142 | }, 143 | scales: { 144 | x: { 145 | ticks: { 146 | color: 'white', 147 | }, 148 | }, 149 | y: { 150 | ticks: { 151 | color: 'white', 152 | }, 153 | }, 154 | }, 155 | }; 156 | 157 | const labels = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100]; 158 | 159 | const fakedata = [ 160 | { x: null , y: 0 }, 161 | // { x: 5, y: null }, 162 | // { x: 10, y: null }, 163 | // { X: 15, y: null }, 164 | // { x: 20, y: null }, 165 | // { x: 25, y: null }, 166 | // { x: 30, y: null }, 167 | // { x: 35, y: null }, 168 | // { x: 40, y: null }, 169 | // { x: 45, y: null }, 170 | // { x: 50, y: null }, 171 | // { x: 55, y: null }, 172 | // { x: 60, y: null }, 173 | { x: null, y: 100 }, 174 | ]; 175 | 176 | const data = { 177 | labels, 178 | datasets: [ 179 | { 180 | fill: true, 181 | label: 'No Data', 182 | data: fakedata, 183 | borderColor: 'rgb(53, 162, 235)', 184 | backgroundColor: 'rgba(53, 162, 235, 0.5)', 185 | }, 186 | ], 187 | }; 188 | 189 | return ( 190 |
191 | 192 |
193 | ); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /src/pages/components/ProtectedRoutes.jsx: -------------------------------------------------------------------------------- 1 | import { Navigate, Outlet } from 'react-router'; 2 | import { useAuth } from '../context/AuthContext'; 3 | 4 | const ProtectedRoute = () => { 5 | const { userSession } = useAuth(); 6 | 7 | //checks user session, if not active, redirect to Login; replace here esssentially replaces the current route/page with new route, 8 | // therefore user cannot simply hit back button to go back to this page. 9 | if (!userSession) return ; 10 | 11 | //placeholder for any child routes that will load if user session exists 12 | return ; 13 | }; 14 | 15 | export default ProtectedRoute; 16 | -------------------------------------------------------------------------------- /src/pages/components/menu.jsx: -------------------------------------------------------------------------------- 1 | import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/react'; 2 | import { useDispatch, useSelector } from 'react-redux'; 3 | import { addGraph, getData } from '../../state/graph-reducer.js'; 4 | import { AWSdata } from '../../../backend/fetch.js'; 5 | import { useRef, useEffect } from 'react'; 6 | 7 | export default function DropDownMenu() { 8 | const graphs = useSelector((state) => state.graphs); 9 | const dispatch = useDispatch(); 10 | const first = useRef(true); 11 | 12 | useEffect(() => { 13 | if (first.current === true) { 14 | first.current = false; 15 | return; 16 | } 17 | 18 | async function auto() { 19 | let data = await AWSdata(graphs); 20 | dispatch(getData({ data })); 21 | } 22 | 23 | auto(); 24 | }, [graphs.graph]); 25 | 26 | return ( 27 | 28 |
29 | 30 | Add Metrics 31 | 32 |
33 | 34 | 38 |
39 | 40 | 41 |
42 | 43 |

44 | Bar Graph 45 |

46 |
47 |
48 | 49 | 53 |
54 | 55 | 68 | 69 | 70 | 83 | 84 | 85 | 98 | 99 | 100 | 113 | 114 | 115 | 128 | 129 |
130 |
131 |
132 |
133 | 134 | 135 |
136 | 137 |

138 | Line Graph 139 |

140 |
141 |
142 | 143 | 147 |
148 | 149 | 162 | 163 | 164 | 177 | 178 | 179 | 192 | 193 | 194 | 207 | 208 | 209 | 222 | 223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 | ); 231 | } 232 | -------------------------------------------------------------------------------- /src/pages/context/AuthContext.jsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext, createContext } from 'react'; 2 | // import userPool from '../../pools/userPool'; 3 | import { CognitoUserPool } from 'amazon-cognito-identity-js'; 4 | 5 | //allows for any children component to use this CONTEXT, small scale state management 6 | const AuthContext = createContext(); 7 | 8 | //this gets exported to be used in main 9 | export const AuthProvider = ({ children }) => { 10 | const [userSession, setUserSession] = useState(null); 11 | const [loading, setLoading] = useState(true); 12 | 13 | //grabbing the pool data 14 | const poolData = { 15 | UserPoolId: import.meta.env.VITE_COGNITO_USER_POOL_ID, 16 | ClientId: import.meta.env.VITE_COGNITO_CLIENT_ID, 17 | }; 18 | //makes a userPool instance using pool data 19 | const userPool = new CognitoUserPool(poolData); 20 | 21 | useEffect(() => { 22 | //grabs current logged in user from the browser's local storage 23 | const currentUser = userPool.getCurrentUser(); 24 | 25 | //if current user is active, do the following 26 | if (currentUser) { 27 | //grab userSession info 28 | currentUser.getSession((err, session) => { 29 | if (err || !session.isValid()) { 30 | //if user session is not valid, set session to null 31 | setUserSession(null); 32 | } else { 33 | //save the current user and their session 34 | setUserSession({ user: currentUser, session }); 35 | } 36 | setLoading(false); 37 | }); 38 | } else { 39 | setLoading(false); 40 | } 41 | }, []); 42 | 43 | //to be used to change User's status back to signout, should redirect user 44 | //to Login page 45 | const signOut = () => { 46 | const currentUser = userPool.getCurrentUser(); 47 | if (currentUser) { 48 | currentUser.signOut(); 49 | } 50 | setUserSession(null); 51 | localStorage.clear(); //clear any stored tokens 52 | sessionStorage.clear(); //clear out session data 53 | }; 54 | 55 | //AuthContext.Provider component provided by React's Context API, allows for all child components to receive 56 | //context from parent 57 | return ( 58 | 59 | {loading ?
Loading...
: children} 60 |
61 | ); 62 | }; 63 | 64 | //alows us to use our auth context by creating a custom hook 65 | //reduces need to repetitively write useContext(AuthContext) all the time 66 | export const useAuth = () => useContext(AuthContext); 67 | -------------------------------------------------------------------------------- /src/pages/login/Forgot.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { 3 | CognitoIdentityProviderClient, 4 | ConfirmForgotPasswordCommand, 5 | ForgotPasswordCommand, 6 | } from '@aws-sdk/client-cognito-identity-provider'; 7 | 8 | const client = new CognitoIdentityProviderClient({ 9 | region: 'us-east-1', 10 | }); 11 | 12 | const Forgot: React.FC = (): JSX.Element => { 13 | const [email, setEmail] = useState(''); 14 | const [code, setCode] = useState(''); 15 | const [delivery, setDelivery] = useState(''); 16 | const [passwordOne, setPasswordOne] = useState(''); 17 | const [passwordTwo, setPasswordTwo] = useState(''); 18 | const [verificationComponent, setVerificationComponent] = 19 | useState(false); 20 | const [resetSucess, setResetSuccess] = useState(false); 21 | const [errorMessage, setErrorMessage] = useState(''); 22 | 23 | //form is submitted, code is sent to email 24 | const retrieveCode = async ( 25 | event: React.FormEvent 26 | ): Promise => { 27 | event.preventDefault(); 28 | 29 | //sets email to lowercase first before sending it to Cognito 30 | const lowerCaseEmail = email.toLowerCase(); 31 | 32 | //This is what will be sent to Cognito, so that user can get a verification code 33 | const input = { 34 | ClientId: import.meta.env.VITE_COGNITO_CLIENT_ID, 35 | Username: lowerCaseEmail, 36 | }; 37 | const command = new ForgotPasswordCommand(input); 38 | //sends the email 39 | try { 40 | //sends the client the command in order to get code 41 | const response = await client.send(command); 42 | //the response we get back tells us how we received the code 43 | setDelivery(response?.CodeDeliveryDetails?.DeliveryMedium || 'unknown'); 44 | 45 | console.log(response); 46 | } catch (error) { 47 | console.error('error:', error); 48 | } 49 | //after we receive code, set this to true to conditionally render the next step 50 | setVerificationComponent(true); 51 | }; 52 | 53 | //code is submitted to Cognito and verified for password reset 54 | const codeSubmission = async (event: React.FormEvent) => { 55 | //prevents full page refresh 56 | event.preventDefault(); 57 | 58 | //If user types in two passwords that are not the same, this error message will display 59 | if (passwordOne !== passwordTwo) { 60 | setErrorMessage('Passwords do not match.'); 61 | return; 62 | } 63 | // 64 | const clientId = import.meta.env.VITE_COGNITO_CLIENT_ID; 65 | if (!clientId) { 66 | setErrorMessage('Client Id is missing'); 67 | return; 68 | } 69 | 70 | setErrorMessage(''); 71 | interface Input { 72 | ClientId: string; 73 | Username: string; 74 | ConfirmationCode: string; 75 | Password: string; 76 | } 77 | 78 | const input: Input = { 79 | ClientId: clientId, 80 | Username: email, 81 | ConfirmationCode: code, 82 | Password: passwordOne, 83 | }; 84 | 85 | const command = new ConfirmForgotPasswordCommand(input); 86 | try { 87 | await client.send(command); 88 | setResetSuccess(true); 89 | } catch (error) { 90 | if (error instanceof Error) { 91 | console.error('Error:', error.message); 92 | setErrorMessage(error.message); 93 | } else { 94 | console.error('Error: Unexpected Error'); 95 | setErrorMessage('An unexpected error occurred'); 96 | } 97 | } 98 | }; 99 | 100 | return ( 101 |
102 | {!verificationComponent ? ( 103 |
104 |
105 |
106 |
107 |

108 | {' '} 109 | Forgot password? 110 |

111 |

112 | Enter the email you used to signup below: 113 |

114 | 115 |
116 |
117 | 118 | { 123 | setEmail(e.target.value); 124 | }} 125 | placeholder='Type Email' 126 | required 127 | > 128 |
129 | 135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 | ) : !resetSucess ? ( 143 |
144 |
145 |
146 |
147 |

148 | Your is code sent via {delivery} 149 |

150 |

151 | Enter the verification code you received below 152 |

153 |
154 | 157 | setCode(e.target.value)} 162 | placeholder='1234' 163 | required 164 | > 165 |

166 | 167 | setPasswordOne(e.target.value)} 172 | placeholder=' Password must contain Uppercase, lowercase, number and symbol.' 173 | required 174 | > 175 | 178 | setPasswordTwo(e.target.value)} 183 | placeholder=' Password must contain Uppercase, lowercase, number and symbol.' 184 | required 185 | > 186 | {errorMessage && ( 187 |

188 | {errorMessage} 189 |

190 | )} 191 |
192 | 198 |
199 |
200 |
201 |
202 |
203 |
204 | ) : ( 205 |
206 |

207 | Password Reset Successful 208 |

209 |

210 | Log in with your new password. 211 |

212 |
213 | )} 214 |
215 |
216 |
217 |
218 |
219 | ); 220 | }; 221 | 222 | export default Forgot; 223 | -------------------------------------------------------------------------------- /src/pages/login/Login.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useNavigate, useSearchParams } from 'react-router-dom'; 3 | import { 4 | CognitoUser, 5 | AuthenticationDetails, 6 | CognitoUserPool, 7 | ICognitoUserSessionData, 8 | CognitoUserSession, 9 | } from 'amazon-cognito-identity-js'; 10 | import { useAuth } from '../context/AuthContext.jsx'; 11 | 12 | //types for user below 13 | interface User { 14 | id: string; 15 | email: string; 16 | } 17 | 18 | interface TokenResponse { 19 | access_token: string; 20 | id_token: string; 21 | email: string; 22 | } 23 | 24 | //grabs the pool data from local .env file 25 | //types for the data required for configuring the user pool in AWS Cognito 26 | interface PoolData { 27 | UserPoolId: string; 28 | ClientId: string; 29 | } 30 | 31 | const clientId = import.meta.env.VITE_COGNITO_CLIENT_ID; 32 | const poolID = import.meta.env.VITE_COGNITO_USER_POOL_ID; 33 | const urlPoolID = poolID.toLowerCase().replace('_', ''); //poolid transformed for url 34 | 35 | const authUrl = `https://${urlPoolID}.auth.us-east-1.amazoncognito.com/login?client_id=${clientId}&redirect_uri=http://localhost:80&response_type=code`; 36 | 37 | const Login: React.FC = () => { 38 | const [email, setEmail] = useState(''); 39 | const [password, setPassword] = useState(''); 40 | const [error, setError] = useState(''); 41 | const { setUserSession } = useAuth(); 42 | const navigate = useNavigate(); 43 | 44 | //hook allows you to grab URL query params and manipulate query params as well 45 | const [searchParams] = useSearchParams(); 46 | 47 | //runs everytime there are new parameters in the url after 'sign in with google' 48 | useEffect(() => { 49 | // Get auth code from URL 50 | const code: string | null = searchParams.get('code'); 51 | //if code exists, run the function to exchange code for tokens 52 | if (code) { 53 | exchangeCodeForToken(code); 54 | } 55 | }, [searchParams]); 56 | 57 | //handles the exchange of tokens that are received in the url 58 | const exchangeCodeForToken = async (code: string): Promise => { 59 | try { 60 | //this is an endpoint that is used to fetch access,id, and refresh tokens 61 | const response = await fetch( 62 | `https://${urlPoolID}.auth.us-east-1.amazoncognito.com/oauth2/token`, 63 | { 64 | method: 'POST', 65 | headers: { 66 | 'Content-Type': 'application/x-www-form-urlencoded', 67 | }, 68 | //builds a url to send to this endpoint to retrieve the tokens 69 | body: new URLSearchParams({ 70 | grant_type: 'authorization_code', 71 | client_id: import.meta.env.VITE_COGNITO_CLIENT_ID, 72 | code: code, 73 | redirect_uri: 'http://localhost:80', 74 | }), 75 | } 76 | ); 77 | 78 | //waits to receive a reponse with the proper tokens 79 | const data: TokenResponse = await response.json(); 80 | 81 | if (data.access_token) { 82 | // Store the tokens with Cognito-like format 83 | const userId: string = data.id_token.split('.')[0]; // Using the ID token's first part as a user ID 84 | 85 | localStorage.setItem( 86 | `CognitoIdentityServiceProvider.${clientId}.${userId}.accessToken`, 87 | data.access_token 88 | ); 89 | localStorage.setItem( 90 | `CognitoIdentityServiceProvider.${clientId}.${userId}.idToken`, 91 | data.id_token 92 | ); 93 | 94 | // After tokens are saved, create the session object and call setUserSession 95 | const user: User = { id: userId, email: data.email }; // Customize as per the user data you get 96 | 97 | //creates session from the data collected 98 | const session = { 99 | accessToken: data.access_token, 100 | idToken: data.id_token, 101 | }; 102 | setUserSession({ user, session }); // Set user session after successful login 103 | navigate('/newUserProfile'); 104 | } 105 | } catch (error) { 106 | console.error('Error exchanging auth code for token:', error); 107 | } 108 | }; 109 | 110 | const poolData: PoolData = { 111 | UserPoolId: poolID, 112 | ClientId: clientId, 113 | }; 114 | 115 | //ensures our poolID stays safe, along with ClientId 116 | const userPool = new CognitoUserPool(poolData); 117 | 118 | //handles the login process for users, using AWS Cognito 119 | const handlesLogin = (event: React.FormEvent): void => { 120 | //prevents default action of form from taking place when submitting 121 | event.preventDefault(); 122 | 123 | //sets email to be lowercase (case insensitive) 124 | let lowerCaseEmail: string = email; 125 | lowerCaseEmail = lowerCaseEmail.toLocaleLowerCase(); 126 | 127 | //creates a new CognitoUser object, containing the username and the pool it will access 128 | const user = new CognitoUser({ 129 | Username: lowerCaseEmail, 130 | Pool: userPool, 131 | }); 132 | 133 | //Creates a new AuthenticationDetails object containing the username and password information 134 | const authenticationDetails = new AuthenticationDetails({ 135 | Username: lowerCaseEmail, 136 | Password: password, 137 | }); 138 | 139 | //using the user object, we pass in the authentication to see if this user's password matches 140 | user.authenticateUser(authenticationDetails, { 141 | //on success, we want to print the success and print it to console 142 | onSuccess: (data): void => { 143 | console.log('Login Successful:', data); 144 | setUserSession({ user, session: data }); 145 | navigate('/newUserProfile'); //immediately navigates to Home page, 146 | }, 147 | //upon failure, we instead console the error message, reason why 148 | onFailure: (err): void => { 149 | console.error('Login not successful', err); 150 | setError(err.message || 'Something did not go right'); 151 | }, 152 | }); 153 | }; 154 | 155 | //this function allows user to go to signup page 156 | const signUp = () => { 157 | //this function will call the signup endpoint 158 | navigate('/signup'); 159 | }; 160 | 161 | return ( 162 | <> 163 |
164 |
165 |
166 |
167 |
168 |

169 | Welcome back! 170 |

171 |

172 | Welcome back! Please enter your details. 173 |

174 | 175 |
176 |
177 | 178 | { 183 | setError(''); 184 | setEmail(e.target.value); 185 | }} 186 | required 187 | placeholder='Enter your email' 188 | > 189 | 190 | { 195 | setError(''); 196 | setPassword(e.target.value); 197 | }} 198 | required 199 | placeholder='Enter your password' 200 | > 201 |

{error}

202 |
203 | 209 |
210 |
211 |
212 | 218 |
219 | 220 |
221 |

222 | Do not have an account? 223 |

224 | 232 |

233 | Forgot Password?{' '} 234 | 238 | Click here 239 | 240 |

241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 | 253 | ); 254 | }; 255 | 256 | export default Login; 257 | 258 | /* 259 | WILL EVENTUALLY MAKE A PARENT COMPONENT HOLDING THE STYLING THAT IS COMMON 260 | BETWEEN BOTH LOGIN AND SIGNUP PAGES TO AVOID THE UNNECESARY REPETITION OF STYLES 261 | 262 | 263 | */ 264 | -------------------------------------------------------------------------------- /src/pages/login/Signup.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import Verify from './Verification.tsx'; 3 | import { useNavigate } from 'react-router'; 4 | import { 5 | CognitoUserPool, 6 | CognitoUserAttribute, 7 | } from 'amazon-cognito-identity-js'; 8 | 9 | interface PoolData { 10 | UserPoolId: string; 11 | ClientId: string; 12 | } 13 | 14 | const Signup: React.FC = (): JSX.Element => { 15 | const navigate = useNavigate(); 16 | const [email, setEmail] = useState(''); 17 | const [password, setPassword] = useState(''); 18 | const [success, setSuccess] = useState(false); 19 | const [isVerified, setIsVerified] = useState(false); 20 | const [errorMessage, setErrorMessage] = useState(''); 21 | 22 | //url data to redirect to when user wishes to sign up with Google 23 | const authUrl: string = `https://${import.meta.env.VITE_COGNITO_USER_POOL_ID.toLowerCase().replace( 24 | '_', 25 | '' 26 | )}.auth.us-east-1.amazoncognito.com/login?client_id=${ 27 | import.meta.env.VITE_COGNITO_CLIENT_ID 28 | }&redirect_uri=http://localhost:80&response_type=code`; 29 | 30 | //function to handle the signup process for our users 31 | const handleSignups = (event: React.FormEvent): void => { 32 | //prevents default form loading upon submission 33 | event.preventDefault(); 34 | 35 | //grabs pool data 36 | const poolData: PoolData = { 37 | UserPoolId: import.meta.env.VITE_COGNITO_USER_POOL_ID, 38 | ClientId: import.meta.env.VITE_COGNITO_CLIENT_ID, 39 | }; 40 | //makes a userPool instance using the data from above 41 | const userPool = new CognitoUserPool(poolData); 42 | 43 | //Becomes 'false' to begin 44 | setSuccess(false); 45 | 46 | //ensures email will be saved case insensitive 47 | let lowerCaseEmail: string = email; 48 | lowerCaseEmail = lowerCaseEmail.toLowerCase(); 49 | 50 | //attributes to be included when we send the signup 51 | const attributeList: CognitoUserAttribute[] = [ 52 | new CognitoUserAttribute({ 53 | Name: 'email', 54 | Value: lowerCaseEmail, 55 | }), 56 | ]; 57 | 58 | userPool.signUp( 59 | lowerCaseEmail, 60 | password, 61 | attributeList, 62 | [], 63 | (err, data) => { 64 | if (err) { 65 | //if the email, password or anything is off throw error 66 | console.error('Sign up failed:', err.message); 67 | setErrorMessage(`${err.message}`); 68 | return; 69 | } 70 | //otherwise console log success and show next steps 71 | console.log('Sign up with this was good:', data); 72 | setSuccess(true); 73 | setIsVerified(true); 74 | } 75 | ); 76 | }; 77 | 78 | //This allows user to go to login page 79 | const login = (): void => { 80 | console.log('testing'); 81 | navigate('/'); 82 | }; 83 | 84 | return ( 85 | <> 86 | {!isVerified ? ( 87 |
88 |
89 |
90 |
91 |
92 |

93 | Get Started with AWSome! 94 |

95 |

96 | See all your metrics in one place with an AWSome monitoring 97 | tool for your EC2 instances! 98 |

99 | 100 |
101 |
102 | 103 | { 108 | setEmail(e.target.value); 109 | setErrorMessage(''); 110 | }} 111 | required 112 | placeholder='Enter your email' 113 | > 114 | 115 | 116 | { 121 | setPassword(e.target.value); 122 | setErrorMessage(''); 123 | }} 124 | required 125 | placeholder='Enter your password' 126 | > 127 | {!errorMessage ? ( 128 |

129 | Password must contain Uppercase, lowercase, number and 130 | symbol. 131 |

132 | ) : ( 133 |

{errorMessage}

134 | )} 135 |
136 | 142 |
143 |
144 |
145 | 151 | 152 |

153 | Have an account?{' '} 154 | 162 |

163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 | ) : ( 175 | 176 | )} 177 | 178 | ); 179 | }; 180 | 181 | export default Signup; 182 | -------------------------------------------------------------------------------- /src/pages/login/Verification.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { CognitoUser, CognitoUserPool } from 'amazon-cognito-identity-js'; 3 | import { useNavigate } from 'react-router'; 4 | 5 | //types for the props 6 | interface VerifyProps { 7 | email: string; 8 | } 9 | 10 | interface PoolData { 11 | UserPoolId: string; 12 | ClientId: string; 13 | } 14 | 15 | const Verify: React.FC = ({ email }): JSX.Element => { 16 | const [verificationCode, setVerificationCode] = useState(''); 17 | const [message, setMessage] = useState(''); 18 | const navigate = useNavigate(); 19 | 20 | //handles checking if user enters appropriate code after signup 21 | const handleVerification = (): void => { 22 | //grabs pool data, ensures our poolID stays safe, along with ClientId 23 | const poolData: PoolData = { 24 | UserPoolId: import.meta.env.VITE_COGNITO_USER_POOL_ID, 25 | ClientId: import.meta.env.VITE_COGNITO_CLIENT_ID, 26 | }; 27 | 28 | //creates a userPool out of the data provided above 29 | const userPool = new CognitoUserPool(poolData); 30 | 31 | //creates a CognitoUser instance, which we can run operations on 32 | const cognitoUser = new CognitoUser({ 33 | Username: email, 34 | Pool: userPool, 35 | }); 36 | 37 | //uses confirmRegistration method to ensure the verification code is true 38 | //DEPRECATED CAN BE UPDATED TO new AWS SDK 39 | cognitoUser.confirmRegistration(verificationCode, true, (err, result) => { 40 | if (err) { 41 | setMessage(`Verification failed: ${err.message}`); 42 | } else { 43 | setMessage(`Account verified successfully!`); 44 | navigate('/'); 45 | } 46 | }); 47 | }; 48 | 49 | return ( 50 |
51 |

Verify your account

52 |

Enter the verification code sent to your email

53 | setVerificationCode(e.target.value)} 58 | > 59 | 60 |

{message}

61 |
62 | ); 63 | }; 64 | 65 | export default Verify; 66 | -------------------------------------------------------------------------------- /src/pages/newUserProfile.jsx: -------------------------------------------------------------------------------- 1 | import { useNavigate } from 'react-router-dom'; 2 | import { useAuth } from './context/AuthContext'; 3 | import { useState, useRef, useEffect } from 'react'; 4 | 5 | function NewUser() { 6 | const navigate = useNavigate(); 7 | 8 | const { signOut } = useAuth(); 9 | 10 | const logOut = async () => { 11 | signOut(); 12 | navigate('/'); 13 | }; 14 | 15 | async function RandomID() { 16 | let res = await fetch('http://localhost:81/random'); 17 | let data = await res.json(); 18 | return data; 19 | } 20 | 21 | const [isOpen, setIsOpen] = useState(false); 22 | const dropdownRef = useRef(null); 23 | 24 | // Open dropdown on hover or click 25 | const handleMouseEnter = () => { 26 | setIsOpen(true); 27 | }; 28 | 29 | // Close dropdown when clicking outside 30 | useEffect(() => { 31 | const handleClickOutside = (event) => { 32 | if (dropdownRef.current && !dropdownRef.current.contains(event.target)) { 33 | setIsOpen(false); 34 | } 35 | }; 36 | 37 | document.addEventListener('mousedown', handleClickOutside); 38 | document.addEventListener('mousedown', handleClickOutside); 39 | return () => { 40 | document.removeEventListener('mousedown', handleClickOutside); 41 | document.removeEventListener('mousedown', handleClickOutside); 42 | }; 43 | }, []); 44 | 45 | return ( 46 |
47 | {/* Navbar */} 48 |

AWSome

49 |
50 |

AWSome

51 | 52 | {/* Dropdown Button */} 53 |
57 | Account 58 |
59 | 65 | 66 | 72 | 73 | {/* Dropdown Menu */} 74 | {/* {isOpen && ( */} 75 | {/* 87 | )} */} 88 |
89 | 90 | {/* Page Content */} 91 |
92 |

93 | Welcome, New User! 94 |

95 |

96 | Start setting up your AWS monitoring dashboard. 97 |

98 | 107 |

111 | Unique Id Here! 112 |

113 | 114 | 119 | 125 |
126 | 127 | {/* Footer */} 128 |
129 |

130 | © 2025 AWSome Metrics. All rights reserved. 131 |

132 |
133 |
134 | ); 135 | } 136 | 137 | export default NewUser; 138 | -------------------------------------------------------------------------------- /src/state/graph-reducer.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { data } from 'react-router'; 3 | 4 | export const graphReducer = createSlice({ 5 | name: 'graphReducer', 6 | initialState: { 7 | graph: [], 8 | metric: [], 9 | data: [], 10 | }, 11 | reducers: { 12 | // Redux Toolkit allows us to write "mutating" logic in reducers. It 13 | // doesn't actually mutate the state because it uses the Immer library, 14 | // which detects changes to a "draft state" and produces a brand new 15 | // immutable state based off those changes 16 | addGraph: (state, action) => { 17 | if (action.payload.type === 'bar') { 18 | state.graph.push('bar'); 19 | state.metric.push(action.payload.metric); 20 | } else if (action.payload.type === 'areaLine') { 21 | state.graph.push('areaLine'); 22 | state.metric.push(action.payload.metric); 23 | } else if (action.payload === 'line') { 24 | state.graph.push('line'); 25 | state.metric.push(action.payload.metric); 26 | } 27 | }, 28 | getData: (state, action) => { 29 | if (action.payload.data) { 30 | // console.log('let: ', action.payload.data.result.length); 31 | 32 | state.data = []; 33 | 34 | for (let i = 0; i < action.payload.data.result.length; i++) { 35 | state.data.push(action.payload.data.result[i]); 36 | } 37 | } 38 | }, 39 | }, 40 | }); 41 | 42 | // Action creators are generated for each case reducer function 43 | export const { addGraph, getData } = graphReducer.actions; 44 | 45 | export default graphReducer.reducer; 46 | -------------------------------------------------------------------------------- /src/state/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit' 2 | import graphReducer from './graph-reducer.js' 3 | 4 | export default configureStore({ 5 | reducer: { 6 | graphs: graphReducer 7 | } 8 | }) 9 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | 3 | export default { 4 | content: [ 5 | './index.html', 6 | './src/**/*.{js,ts,jsx,tsx}', // Include all React files 7 | ], 8 | theme: { 9 | extend: {}, 10 | }, 11 | plugins: [], 12 | }; 13 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig to read more about this file */ 4 | 5 | /* Projects */ 6 | // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ 7 | // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ 8 | // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ 9 | // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ 10 | // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ 11 | // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ 12 | 13 | /* Language and Environment */ 14 | "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, 15 | // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ 16 | "jsx": "react-jsx" /* Specify what JSX code is generated. */, 17 | // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ 18 | // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ 19 | // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ 20 | // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ 21 | // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ 22 | // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ 23 | // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ 24 | // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ 25 | // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ 26 | 27 | /* Modules */ 28 | "module": "NodeNext" /* Specify what module code is generated. */, 29 | // "rootDir": "./", /* Specify the root folder within your source files. */ 30 | "moduleResolution": "nodenext" /* Specify how TypeScript looks up a file from a given module specifier. */, 31 | // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ 32 | // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ 33 | // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ 34 | // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ 35 | "types": [ 36 | "vite/client" 37 | ] /* Specify type package names to be included without being referenced in a source file. */, 38 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 39 | // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ 40 | "noEmit": true, 41 | "allowImportingTsExtensions": true /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */, 42 | // "rewriteRelativeImportExtensions": true, /* Rewrite '.ts', '.tsx', '.mts', and '.cts' file extensions in relative import paths to their JavaScript equivalent in output files. */ 43 | // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ 44 | // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ 45 | // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ 46 | // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ 47 | // "resolveJsonModule": true, /* Enable importing .json files. */ 48 | // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ 49 | // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ 50 | 51 | /* JavaScript Support */ 52 | // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ 53 | // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ 54 | // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ 55 | 56 | /* Emit */ 57 | // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ 58 | // "declarationMap": true, /* Create sourcemaps for d.ts files. */ 59 | // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ 60 | // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ 61 | // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ 62 | // "noEmit": true, /* Disable emitting files from a compilation. */ 63 | // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ 64 | // "outDir": "./", /* Specify an output folder for all emitted files. */ 65 | // "removeComments": true, /* Disable emitting comments. */ 66 | // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ 67 | // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ 68 | // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ 69 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 70 | // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ 71 | // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ 72 | // "newLine": "crlf", /* Set the newline character for emitting files. */ 73 | // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ 74 | // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ 75 | // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ 76 | // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ 77 | // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ 78 | 79 | /* Interop Constraints */ 80 | // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ 81 | // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ 82 | // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ 83 | // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ 84 | "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, 85 | // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ 86 | "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, 87 | 88 | /* Type Checking */ 89 | "strict": true /* Enable all strict type-checking options. */, 90 | // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ 91 | // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ 92 | // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ 93 | // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ 94 | // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ 95 | // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ 96 | // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ 97 | // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ 98 | // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ 99 | // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ 100 | // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ 101 | // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ 102 | // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ 103 | // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ 104 | // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ 105 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ 106 | // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ 107 | // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ 108 | // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ 109 | 110 | /* Completeness */ 111 | // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ 112 | "skipLibCheck": true /* Skip type checking all .d.ts files. */ 113 | }, 114 | "include": ["src/**/*"], 115 | "exclude": ["node_modules", "dist"] 116 | } 117 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import tailwindcss from 'tailwindcss'; 3 | import react from '@vitejs/plugin-react'; 4 | import { nodePolyfills } from 'vite-plugin-node-polyfills'; 5 | 6 | export default defineConfig({ 7 | plugins: [ 8 | react(), 9 | nodePolyfills({ 10 | globals: true, // Enable polyfilling for `global` 11 | }), 12 | ], 13 | build: { 14 | outDir: 'dist', 15 | }, 16 | css: { 17 | postcss: { 18 | plugins: [tailwindcss()], 19 | }, 20 | }, 21 | server: { 22 | port: 3000, //runs frontend on 3000 23 | open: true, //will automatically open up the page 24 | proxy: { 25 | //can be deleted 26 | // '/login': 'http://localhost:3000', 27 | // '/signup': 'http://localhost:3000', 28 | // '/protected': 'http://localhost:3000', 29 | '/random': 'http://localhost:81', 30 | '/data': 'http://localhost:81', 31 | }, 32 | historyApiFallback: true, //ensures client-side routing works 33 | }, 34 | 35 | // preview: { 36 | // allowedHosts: true, 37 | // }, 38 | }); 39 | --------------------------------------------------------------------------------