├── .eslintrc.json
├── .gitignore
├── Code_of_Conduct.md
├── Contributions.md
├── LICENSE.md
├── README-DeveloperGuide.md
├── README.md
├── __tests__
├── apiRouter_tests.js
└── db_tests.js
├── index.html
├── jest.config.js
├── package-lock.json
├── package.json
├── postgres_table_create.sql
├── server
├── aws
│ └── cloudFormation.yaml
├── controllers
│ ├── MetricsController.js
│ ├── authControllers.js
│ ├── cookieControllers.js
│ ├── credentialController.js
│ ├── dbControllers.js
│ ├── encryptionController.js
│ ├── fileController.js
│ ├── lambdaLogsController.js
│ ├── listLambdasController.js
│ └── tracesController.js
├── models
│ └── dbPool.js
├── redis.js
├── routes
│ └── api.js
└── server.js
├── src
├── App.jsx
├── Chart
│ ├── BarChart.jsx
│ ├── BubbleChart.jsx
│ ├── Circle.jsx
│ ├── CircleChart.jsx
│ ├── index.html
│ ├── miserables.json
│ └── service.json
├── assets
│ ├── desktop.png
│ ├── docs-gif-01.gif
│ ├── docs-gif-02.gif
│ ├── docs-gif-03.gif
│ ├── enteryourarn.gif
│ ├── getyourstack.gif
│ ├── logo-text.png
│ ├── logo.png
│ ├── logowhite.png
│ ├── mascot.png
│ ├── mascot_head.svg
│ ├── metrics.gif
│ ├── mobile-logs.png
│ ├── mobile-map.png
│ ├── mobile-metrics.png
│ ├── readme-bar.gif
│ ├── readme-bubble.gif
│ ├── readme-node.gif
│ ├── signup.gif
│ └── signyouup.gif
├── components
│ ├── Animation.jsx
│ ├── Auth.jsx
│ ├── ChartDropDown.jsx
│ ├── DataWindow.jsx
│ ├── Docs.jsx
│ ├── LamdaButton.jsx
│ ├── Log.jsx
│ ├── Navbar.jsx
│ ├── Panel.jsx
│ ├── Refresh.jsx
│ ├── SettingsForm.jsx
│ ├── SignInForm.jsx
│ ├── SignUpForm.jsx
│ └── footer.jsx
├── containers
│ ├── DashboardContainer.jsx
│ ├── DiagramContainer.jsx
│ ├── LandingPageContainer.jsx
│ └── SettingsContainer.jsx
├── main.jsx
└── styles
│ ├── _animation.scss
│ ├── _auth.scss
│ ├── _dashboard.scss
│ ├── _docs.scss
│ ├── _dropdown.scss
│ ├── _footer.scss
│ ├── _form.scss
│ ├── _landingpage.scss
│ ├── _metrics.scss
│ ├── _navbar.scss
│ ├── _newnavbar.scss
│ ├── _settings.scss
│ ├── _variables.scss
│ └── application.scss
└── vite.config.js
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "root": true,
3 | "ignorePatterns": ["**/test", "**/__tests__"],
4 | "env": {
5 | "node": true,
6 | "browser": true,
7 | "es2021": true
8 | },
9 | "plugins": ["react"],
10 | "extends": ["eslint:recommended", "plugin:react/recommended"],
11 | "parserOptions": {
12 | "sourceType": "module",
13 | "ecmaFeatures": {
14 | "jsx": true
15 | }
16 | },
17 | "rules": {
18 | "indent": ["warn", 2],
19 | "no-unused-vars": ["off", { "vars": "local" }],
20 | "no-case-declarations": "off",
21 | "prefer-const": "warn",
22 | "quotes": ["warn", "single"],
23 | "react/prop-types": "off",
24 | "semi": ["warn", "always"],
25 | "space-infix-ops": "warn"
26 | },
27 | "settings": {
28 | "react": { "version": "detect" }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # General
3 | .DS_Store
4 |
5 | # dependencies
6 | node_modules/
7 |
8 | # logs
9 | npm-debug.log*
10 |
11 | # SecretPage
12 |
13 | server/aws/
14 |
15 | #.env
16 |
17 | .env
18 |
19 | # postgres table creator
20 |
21 | postgres_table_create.sql
22 |
23 | # redis dumper file
24 |
25 | dump.rdb
26 |
--------------------------------------------------------------------------------
/Code_of_Conduct.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | We as members, contributors, and leaders pledge to make participation in our
6 | community a harassment-free experience for everyone, regardless of age, body
7 | size, visible or invisible disability, ethnicity, sex characteristics, gender
8 | identity and expression, level of experience, education, socio-economic status,
9 | nationality, personal appearance, race, religion, or sexual identity
10 | and orientation.
11 |
12 | We pledge to act and interact in ways that contribute to an open, welcoming,
13 | diverse, inclusive, and healthy community.
14 |
15 | ## Our Standards
16 |
17 | Examples of behavior that contributes to a positive environment for our
18 | community include:
19 |
20 | - Demonstrating empathy and kindness toward other people
21 | - Being respectful of differing opinions, viewpoints, and experiences
22 | - Giving and gracefully accepting constructive feedback
23 | - Accepting responsibility and apologizing to those affected by our mistakes,
24 | and learning from the experience
25 | - Focusing on what is best not just for us as individuals, but for the
26 | overall community
27 |
28 | Examples of unacceptable behavior include:
29 |
30 | - The use of sexualized language or imagery, and sexual attention or
31 | advances of any kind
32 | - Trolling, insulting or derogatory comments, and personal or political attacks
33 | - Public or private harassment
34 | - Publishing others' private information, such as a physical or email
35 | address, without their explicit permission
36 | - Other conduct which could reasonably be considered inappropriate in a
37 | professional setting
38 |
39 | ## Enforcement Responsibilities
40 |
41 | Community leaders are responsible for clarifying and enforcing our standards of
42 | acceptable behavior and will take appropriate and fair corrective action in
43 | response to any behavior that they deem inappropriate, threatening, offensive,
44 | or harmful.
45 |
46 | Community leaders have the right and responsibility to remove, edit, or reject
47 | comments, commits, code, wiki edits, issues, and other contributions that are
48 | not aligned to this Code of Conduct, and will communicate reasons for moderation
49 | decisions when appropriate.
50 |
51 | ## Scope
52 |
53 | This Code of Conduct applies within all community spaces, and also applies when
54 | an individual is officially representing the community in public spaces.
55 | Examples of representing our community include using an official e-mail address,
56 | posting via an official social media account, or acting as an appointed
57 | representative at an online or offline event.
58 |
59 | ## Enforcement
60 |
61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
62 | reported to the community leaders responsible for enforcement at
63 | email.
64 | All complaints will be reviewed and investigated promptly and fairly.
65 |
66 | All community leaders are obligated to respect the privacy and security of the
67 | reporter of any incident.
68 |
69 | ## Enforcement Guidelines
70 |
71 | Community leaders will follow these Community Impact Guidelines in determining
72 | the consequences for any action they deem in violation of this Code of Conduct:
73 |
74 | ### 1. Correction
75 |
76 | **Community Impact**: Use of inappropriate language or other behavior deemed
77 | unprofessional or unwelcome in the community.
78 |
79 | **Consequence**: A private, written warning from community leaders, providing
80 | clarity around the nature of the violation and an explanation of why the
81 | behavior was inappropriate. A public apology may be requested.
82 |
83 | ### 2. Warning
84 |
85 | **Community Impact**: A violation through a single incident or series
86 | of actions.
87 |
88 | **Consequence**: A warning with consequences for continued behavior. No
89 | interaction with the people involved, including unsolicited interaction with
90 | those enforcing the Code of Conduct, for a specified period of time. This
91 | includes avoiding interactions in community spaces as well as external channels
92 | like social media. Violating these terms may lead to a temporary or
93 | permanent ban.
94 |
95 | ### 3. Temporary Ban
96 |
97 | **Community Impact**: A serious violation of community standards, including
98 | sustained inappropriate behavior.
99 |
100 | **Consequence**: A temporary ban from any sort of interaction or public
101 | communication with the community for a specified period of time. No public or
102 | private interaction with the people involved, including unsolicited interaction
103 | with those enforcing the Code of Conduct, is allowed during this period.
104 | Violating these terms may lead to a permanent ban.
105 |
106 | ### 4. Permanent Ban
107 |
108 | **Community Impact**: Demonstrating a pattern of violation of community
109 | standards, including sustained inappropriate behavior, harassment of an
110 | individual, or aggression toward or disparagement of classes of individuals.
111 |
112 | **Consequence**: A permanent ban from any sort of public interaction within
113 | the community.
114 |
115 | ## Attribution
116 |
117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage],
118 | version 2.0, available at
119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
120 |
121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct
122 | enforcement ladder](https://github.com/mozilla/diversity).
123 |
124 | [homepage]: https://www.contributor-covenant.org
125 |
126 | For answers to common questions about this code of conduct, see the FAQ at
127 | https://www.contributor-covenant.org/faq. Translations are available at
128 | https://www.contributor-covenant.org/translations.
129 |
--------------------------------------------------------------------------------
/Contributions.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
Contributions
5 |
6 |
7 |
8 | # Contributing to Lambdawg
9 |
10 | Thank you for your contribution!
11 |
12 | ## Reporting Bugs
13 |
14 | All code changes happen through Github Pull Requests and we actively welcome them. Please submit your pull request to the _dev_ branch, including a description of the work done in your pull request.
15 |
16 | Note: Any contributions you make will be under the MIT Software License and your submissions are understood to be under the same that covers the project. Please reach out to the team if you have any questions.
17 |
18 | ## Issues
19 |
20 | To help us better identify and reproduce the issue, please include the following information:
21 |
22 | **Describe the bug**
23 | A clear and concise description of what the bug is.
24 |
25 | **To Reproduce**
26 | Steps to reproduce the behavior:
27 |
28 | 1. Go to '...'
29 | 2. Click on '....'
30 | 3. Scroll down to '....'
31 | 4. See error
32 |
33 | **Expected behavior**
34 | A clear and concise description of what you expected to happen.
35 |
36 | **Screenshots**
37 | If applicable, add screenshots to help explain your problem.
38 |
39 | **Desktop (please complete the following information):**
40 |
41 | - OS: [e.g. iOS]
42 | - Browser [e.g. chrome, safari]
43 | - Version [e.g. 22]
44 |
45 | **Additional context**
46 | Add any other context about the problem here.
47 |
48 | ## Feature Requests
49 |
50 | We are always looking to enhance our users' experience with the Lambdawg UI. We would love to hear your ideas! To suggest an idea for this project, please be sure to include the following information so we can best address your suggestion.
51 |
52 | **Is your feature request related to a problem? Please describe.**
53 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
54 |
55 | **Describe the solution you'd like**
56 | A clear and concise description of what you want to happen.
57 |
58 | **Describe alternatives you've considered**
59 | A clear and concise description of any alternative solutions or features you've considered.
60 |
61 | **Additional context**
62 | Add any other context or screenshots about the feature request here.
63 |
64 | ## License
65 |
66 | By contributing, you agree that your contributions will be licensed under Cloudband's MIT License.
67 |
68 | ### References
69 |
70 | This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/a9316a723f9e918afde44dea68b5f9f39b7d9b00/CONTRIBUTING.md)
71 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Lambdawg
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-DeveloperGuide.md:
--------------------------------------------------------------------------------
1 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
Lambdawg Developer's Guide
11 |
12 |
13 |
14 |
15 | Table of Contents
16 |
17 | AWS Account Creation
18 | IAM Setup
19 | Streamlining the User Sign-Up Experience
20 | YAML Template
21 | Template Storage in an S3 Bucket
22 | Stack Creation Link
23 | Finish Setup
24 |
25 |
26 |
27 | ## AWS Account Creation
28 |
29 | Lambdawg uses the AWS-SDK to pull data from a user's AWS Lambda functions in order to render trace and metrics data on the Lambdawg UI. It is highly encouraged to create a new AWS account specific to Lambdawg to make full use of the app's features.
30 |
31 | ## IAM Setup
32 |
33 | Once you have created an AWS root account, you will need to set up an IAM role with the required permissions to access users' AWS data.
34 |
35 | On your AWS account, do the following:
36 |
37 | 1. Create an IAM user called lambdawg-user with programmatic access (not console access, as there is no need for users to access the AWS console)
38 |
39 | 2. Attach the following policies directly to lambdawg-user:
40 |
41 |
42 | AdministratorAccess
43 | AmazonEC2FullAccess
44 | AmazonS3FullAccessAWS
45 | AWSLambda_FullAccess
46 | AWSLambdaRole
47 | AWSSecurityHubFullAccess
48 | CloudWatchFullAccess
49 | CloudWatchLogsFullAccess
50 |
51 |
52 | 3. Create an access key secret access key for lambdawg-user. This will be stored in a .env file in the Lambdawg application.
53 |
54 | (back to top )
55 |
56 | ## Streamlining the User Sign-Up Experience
57 |
58 | From the Lambdawg landing page, users can navigate to Settings for a streamlined sign-up experience. The user will first connect to their AWS account by logging into AWS Cloudformation using the link provided. This will initialize a Cloudformation Stack that will create IAM resources for the user to access Lambdawg's services and provide Lambdawg the necessary permissions. Once the stack is created, users can navigate to the "Resources" tab to access their ARN key. Finally, users will simply paste their ARN key and region to access their metrics on the Lambdawg UI.
59 |
60 | ## YAML Template
61 |
62 | As a developer, you will need to create a yaml file to allow users access to Lambdawg's UI. This file will allow CloudFormation to automate the creation of an IAM role upon user sign-up. The yaml file content should look like the example below. (replacing the Principal / AWS ARN with the lambdawg-user’s ARN & replacing the sts:External Id with a secure external ID. You can use a generator such as UUID https://www.uuidgenerator.net/ for this step. :
63 |
64 |
65 |
66 | ```
67 | Description: "CloudFormation stack"
68 |
69 | Resources:
70 | LambdawgDelegationRole:
71 | Type: "AWS::IAM::Role"
72 | Properties:
73 | AssumeRolePolicyDocument:
74 | Version: 2012-10-17
75 | Statement: - Effect: Allow
76 | Principal:
77 | AWS: - arn:aws:iam::403777712406:user/lambdawg-user
78 | Action: - "sts:AssumeRole"
79 | Condition:
80 | StringEquals:
81 | "sts:ExternalId": !Ref ExternalId
82 | Path: /
83 | RoleName: LambdawgDelegationRole
84 | Policies: - PolicyName: Resources
85 | PolicyDocument:
86 | Version: 2012-10-17
87 | Statement: - Effect: Allow
88 | Action: "apigateway:GET"
89 | Resource: "_" - Effect: Allow
90 | Action: "apigateway:HEAD"
91 | Resource: "_" - Effect: Allow
92 | Action: "apigateway:OPTIONS"
93 | Resource: "_" - Effect: Allow
94 | Action: "appsync:get_"
95 | Resource: "_" - Effect: Allow
96 | Action: "appsync:list_"
97 | Resource: "_" - Effect: Allow
98 | Action: "athena:list_"
99 | Resource: "_" - Effect: Allow
100 | Action: "athena:batchGet_"
101 | Resource: "_" - Effect: Allow
102 | Action: "athena:getNamedQuery"
103 | Resource: "_" - Effect: Allow
104 | Action: "athena:getQueryExecution"
105 | Resource: "_" - Effect: Allow
106 | Action: "athena:getQueryExecution"
107 | Resource: "_" - Effect: Allow
108 | Action: "autoscaling:describe*"
109 | Resource: "*" - Effect: Allow
110 | Action: "batch:describe*"
111 | Resource: "*" - Effect: Allow
112 | Action: "cloudformation:describe*"
113 | Resource: "*" - Effect: Allow
114 | Action: "cloudformation:get*"
115 | Resource: "*" - Effect: Allow
116 | Action: "cloudformation:list*"
117 | Resource: "*" - Effect: Allow
118 | Action: "cloudfront:get*"
119 | Resource: "*" - Effect: Allow
120 | Action: "cloudfront:list*"
121 | Resource: "*" - Effect: Allow
122 | Action: "cloudwatch:describe*"
123 | Resource: "*" - Effect: Allow
124 | Action: "cloudwatch:list*"
125 | Resource: "*" - Effect: Allow
126 | Action: "dax:describe*"
127 | Resource: "*" - Effect: Allow
128 | Action: "dax:list*"
129 | Resource: "*" - Effect: Allow
130 | Action: "discovery:describe*"
131 | Resource: "*" - Effect: Allow
132 | Action: "discovery:list*"
133 | Resource: "*" - Effect: Allow
134 | Action: "dynamodb:describe*"
135 | Resource: "*" - Effect: Allow
136 | Action: "dynamodb:list*"
137 | Resource: "*" - Effect: Allow
138 | Action: "ec2:describe*"
139 | Resource: "*" - Effect: Allow
140 | Action: "ecs:describe*"
141 | Resource: "*" - Effect: Allow
142 | Action: "ecs:list*"
143 | Resource: "*" - Effect: Allow
144 | Action: "ecr:describe*"
145 | Resource: "*" - Effect: Allow
146 | Action: "ecr:get*"
147 | Resource: "*" - Effect: Allow
148 | Action: "ecr:list*"
149 | Resource: "*" - Effect: Allow
150 | Action: "eks:describe*"
151 | Resource: "*" - Effect: Allow
152 | Action: "eks:list*"
153 | Resource: "*" - Effect: Allow
154 | Action: "elasticache:describe*"
155 | Resource: "*" - Effect: Allow
156 | Action: "elasticache:list*"
157 | Resource: "*" - Effect: Allow
158 | Action: "elasticloadbalancing:describe*"
159 | Resource: "*" - Effect: Allow
160 | Action: "es:describe*"
161 | Resource: "*" - Effect: Allow
162 | Action: "es:list*"
163 | Resource: "*" - Effect: Allow
164 | Action: "events:describe*"
165 | Resource: "*" - Effect: Allow
166 | Action: "events:list*"
167 | Resource: "*" - Effect: Allow
168 | Action: "firehose:describe*"
169 | Resource: "*" - Effect: Allow
170 | Action: "firehose:list*"
171 | Resource: "*" - Effect: Allow
172 | Action: "glacier:describe*"
173 | Resource: "*" - Effect: Allow
174 | Action: "glacier:getDataRetrievalPolicy"
175 | Resource: "_" - Effect: Allow
176 | Action: "glacier:getVaultAccessPolicy"
177 | Resource: "_" - Effect: Allow
178 | Action: "glacier:getVaultLock"
179 | Resource: "_" - Effect: Allow
180 | Action: "glacier:getVaultNotifications"
181 | Resource: "_" - Effect: Allow
182 | Action: "glacier:listTagsForVault"
183 | Resource: "_" - Effect: Allow
184 | Action: "glacier:listVaults"
185 | Resource: "_" - Effect: Allow
186 | Action: "iot:describe*"
187 | Resource: "*" - Effect: Allow
188 | Action: "iot:get*"
189 | Resource: "*" - Effect: Allow
190 | Action: "iot:list*"
191 | Resource: "*" - Effect: Allow
192 | Action: "kinesis:describe*"
193 | Resource: "*" - Effect: Allow
194 | Action: "kinesis:list*"
195 | Resource: "*" - Effect: Allow
196 | Action: "kinesisanalytics:describe*"
197 | Resource: "*" - Effect: Allow
198 | Action: "kinesisanalytics:list*"
199 | Resource: "*" - Effect: Allow
200 | Action: "lambda:listFunctions"
201 | Resource: "_" - Effect: Allow
202 | Action: "lambda:listTags"
203 | Resource: "_" - Effect: Allow
204 | Action: "rds:describe*"
205 | Resource: "*" - Effect: Allow
206 | Action: "rds:list*"
207 | Resource: "*" - Effect: Allow
208 | Action: "route53:list*"
209 | Resource: "*" - Effect: Allow
210 | Action: "route53:get*"
211 | Resource: "*" - Effect: Allow
212 | Action: "s3:getBucket*"
213 | Resource: "*" - Effect: Allow
214 | Action: "s3:list*"
215 | Resource: "*" - Effect: Allow
216 | Action: "sdb:domainMetadata"
217 | Resource: "_" - Effect: Allow
218 | Action: "sdb:get_"
219 | Resource: "_" - Effect: Allow
220 | Action: "sdb:list_"
221 | Resource: "_" - Effect: Allow
222 | Action: "sns:get_"
223 | Resource: "_" - Effect: Allow
224 | Action: "sns:list_"
225 | Resource: "_" - Effect: Allow
226 | Action: "sqs:get_"
227 | Resource: "_" - Effect: Allow
228 | Action: "sqs:list_"
229 | Resource: "_" - Effect: Allow
230 | Action: "states:describe_"
231 | Resource: "_" - Effect: Allow
232 | Action: "states:get_"
233 | Resource: "_" - Effect: Allow
234 | Action: "states:list_"
235 | Resource: "_" - Effect: Allow
236 | Action: "tag:get_"
237 | Resource: "_" - PolicyName: Logs
238 | PolicyDocument:
239 | Version: 2012-10-17
240 | Statement: - Effect: Allow
241 | Action: "logs:deleteSubscriptionFilter"
242 | Resource: "_" - Effect: Allow
243 | Action: "logs:describeLogStreams"
244 | Resource: "_" - Effect: Allow
245 | Action: "logs:describeSubscriptionFilters"
246 | Resource: "_" - Effect: Allow
247 | Action: "logs:filterLogEvents"
248 | Resource: "_" - Effect: Allow
249 | Action: "logs:putSubscriptionFilter"
250 | Resource: "_" - Effect: Allow
251 | Action: "logs:startQuery"
252 | Resource: "_" - Effect: Allow
253 | Action: "logs:stopQuery"
254 | Resource: "_" - PolicyName: Metrics
255 | PolicyDocument:
256 | Version: 2012-10-17
257 | Statement: - Effect: Allow
258 | Action: "cloudwatch:get*"
259 | Resource: "*" - PolicyName: Traces
260 | PolicyDocument:
261 | Version: 2012-10-17
262 | Statement: - Effect: Allow
263 | Action: "xray:batch*"
264 | Resource: "*" - Effect: Allow
265 | Action: "xray:get*"
266 | Resource: "*"
267 | Parameters:
268 | ExternalId:
269 | Description: "The external ID for the LAMBDAWG delegation role"
270 | Type: String
271 |
272 | Outputs:
273 | Version:
274 | Description: LAMBDAWG CF template version
275 | Value: 2020-02-06
276 | LambdawgDelegationRoleArn:
277 | Description: "The ARN for the LAMBDAWG delegation role"
278 | Value: !GetAtt - LambdawgDelegationRole - Arn
279 |
280 | ```
281 |
282 |
283 |
284 |
285 | ## Template Storage in an S3 Bucket
286 |
287 | The template must be stored on your Lambdawg-specific AWS account. The simplest way to do this is to create an S3 bucket and upload the template yaml file with the following steps:
288 |
289 |
290 | Navigate to the AWS S3.
291 | Select Create Bucket.
292 | Name the bucket "lambdawg".
293 | Unselect "Block all public access".
294 | Create bucket.
295 | Add to bucket policy the text below step 8.
296 | Click upload and upload your created yaml file template.
297 | In the list of objects in your S3 bucket, copy the URL of your lambdawg template.
298 |
299 |
300 | ```
301 |
302 | {
303 | "Version": "2008-10-17",
304 | "Statement": [
305 | {
306 | "Sid": "AllowPublicRead",
307 | "Effect": "Allow",
308 | "Principal": {
309 | "AWS": "*"
310 | },
311 | "Action": "s3:GetObject",
312 | "Resource": "arn:aws:s3:::lambdawg/*"
313 | }
314 | ]
315 | }
316 |
317 | ```
318 |
319 | ## Stack Creation Link:
320 | Use the following link to allow your user to automatically create a stack. This link can be attached to the “Connect your AWS account” link on the Settings Page (in SettingsForm.jsx - line 71). Add in your template URL, region, and external id into the link to ensure the stack is properly configured.
321 |
322 | ```
323 |
324 | https://console.aws.amazon.com/cloudformation/home?region=#/stacks/quickcreate?stackName=cloudband-permission¶m_ExternalId=&templateURL=
325 |
326 | ```
327 |
328 | ## Finish Setup:
329 |
330 | Continue following the main [README](https://github.com/oslabs-beta/lambdawg/blob/dev/README.md).
331 | ```
332 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Lambdawg
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 | Table of Contents
16 |
17 | About LAMBDAWG
18 | Getting Started
19 | Monitoring Features
20 | Contributing
21 | Built With
22 | License
23 | Authors
24 |
25 |
26 |
27 | ## About LAMBDAWG
28 |
29 | LAMBDAWG is a free tool designed to simplify the management and visualization of AWS Lambda functions and metrics. It provides easy access and straightforward insights for AWS developers, eliminating the need to navigate complex documentation. With LAMBDAWG, you can quickly visualize the correlation between Lambda functions within your app and keep an organized log of errors, all in one place. This tool aims to speed up your development process and enhance your overall experience with AWS Lambda.
30 |
31 |
32 | Key Features
33 | Centralized Error Log Management: Keep an organized list of error logs across your AWS Lambda Functions in a single location for easy access and troubleshooting.
34 | Invocation Monitoring: Gain visibility into the number of invocations for your Lambda functions, allowing you to track usage and performance.
35 | Function Execution Duration: Easily track the duration of each Lambda function's execution to identify potential bottlenecks and areas for improvement.
36 | Response Time Analysis: Measure the time taken to generate the response for each function, helping you evaluate efficiency and optimize user experiences.
37 |
38 | (back to top )
39 |
40 | ## Getting Started
41 |
42 | Install LAMBDAWG on your AWS development environment.
43 | Connect your AWS Lambda Functions to LAMBDAWG for streamlined monitoring and management.
44 | Connect to your AWS account and Copy your ARN
45 |
46 |
47 |
48 | Paste your ARN in the form
49 | Select your region
50 | confirm your LAMBDAWG password
51 |
52 |
53 | ## Monitoring Features
54 |
55 |
56 |
57 |
58 |
59 | Access the LAMBDAWG dashboard to view error logs, invocations, execution duration, and response time metrics.
60 | Leverage the provided insights to troubleshoot errors, improve performance, and optimize your application.
61 |
62 | View active service trace data for each Lambda function:
63 |
64 |
65 |
66 | View duration and response times for each active Lambda function:
67 |
68 |
69 |
70 | View invocation data to easily grasp which functions are used most frequently:
71 |
72 |
73 |
74 |
75 | (back to top )
76 |
77 |
78 | ## Contributing
79 |
80 | We welcome contributions to LAMBDAWG from the community. Whether you have a great idea to enhance the tool or you've discovered and fixed a bug, we appreciate your involvement. Follow the steps below to contribute:
81 |
82 |
83 | Fork the LAMBDAWG repository.
84 | Clone the repository to your local machine using the command: `git clone your_repo_url `
85 | Create a new branch for your feature: `git checkout -b your/amazingFeature `
86 | Make the necessary changes and commit them with a brief comment: `git commit -m "Quick comment about your amazing feature" `
87 | Push the changes to your branch: `git push origin your/amazingFeature `
88 | Open a pull request to submit your changes for review and integration
89 |
90 | We value community feedback and participation. Don't forget to give LAMBDAWG a ⭐️ and share it with your friends.
91 | Together, we can make LAMBDAWG even better!
92 | (back to top )
93 |
94 | ## Built With
95 |
96 | [React](https://reactjs.org/)
97 | | [React Router](https://reactrouter.com/en/main)
98 | | [NodeJS](https://nodejs.org/en/)
99 | | [Express](https://expressjs.com/)
100 | | [PostgreSQL](https://www.postgresql.org/)
101 | | [JWT](https://jwt.io/)
102 | [D3](https://d3js.org/)
103 | | [Chart.js](https://www.chartjs.org/)
104 | | [Redis](https://redis.com/)
105 | | [Jest](https://jestjs.io/)
106 | | [Vite](https://vitejs.dev/)
107 | | [AWS SDK](https://aws.amazon.com/sdk-for-javascript/)
108 | | [AWS IAM](https://aws.amazon.com/iam/)
109 | | [AWS Lambda](https://aws.amazon.com/lambda/)
110 | | [AWS Cloudwatch](https://aws.amazon.com/cloudwatch/)
111 | | [AWS STS](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html)
112 | | [AWS Could Formation](https://docs.aws.amazon.com/cloudformation/index.html)
113 | | [BCrypt](https://bcrypt.online/)
114 | | [Validator](https://www.npmjs.com/package/validator)
115 | | [Supertest](https://www.npmjs.com/package/supertest)
116 | | [Client XRay](https://docs.aws.amazon.com/AWSJavaScriptSDK/v3/latest/clients/client-xray/index.html)
117 |
118 | (back to top )
119 |
120 | ## License
121 |
122 | Distributed under the MIT License
123 | (back to top )
124 |
125 | ## Authors
126 |
127 | - Chanda Gonet - [LinkedIn](https://www.linkedin.com/in/chanda-gonet-317b91237/)|[Github](https://github.com/Chanduh)
128 | - Erica Park - [LinkedIn](https://www.linkedin.com/in/erica-s-park/)|[Github](https://github.com/EPsparky)
129 | - Vincent Jacquemin - [LinkedIn](https://www.linkedin.com/in/vincentjacquemin/)|[Github](https://github.com/GIjack2001)
130 | - Ted Gusek - [LinkedIn](https://www.linkedin.com/in/tedgusek/)|[Github](https://github.com/tedgusek)
131 |
132 | (back to top )
133 |
--------------------------------------------------------------------------------
/__tests__/apiRouter_tests.js:
--------------------------------------------------------------------------------
1 | const { beforeEach, afterEach } = require('node:test');
2 | const { afterAll } = require('@jest/globals');
3 | const request = require('supertest');
4 | const server = require('../server/server');
5 | const db = require('../server/models/dbPool');
6 | require('dotenv').config();
7 |
8 | describe('Our apiRoutes Unit Tests', () => {
9 | const testKey = process.env.TEST_TOKEN;
10 | //These are our test users to run through the tests
11 | //Passing User
12 | const newUser = [
13 | {
14 | user_name: 'testUser',
15 | password_: 'password',
16 | full_name: 'Test User',
17 | email: 'testuser@example.com',
18 | arn: null,
19 | region: null,
20 | [testKey]: true,
21 | },
22 | ];
23 | //User Name is too long user- 272 chars
24 | const newUser272CharsUN_1 = [
25 | {
26 | user_name:
27 | 'testUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUser',
28 | password_: 'password',
29 | full_name: 'Test User_1',
30 | email: 'testuser_1@example.com',
31 | arn: null,
32 | region: null,
33 | [testKey]: true,
34 | },
35 | ];
36 | //Password is too short User
37 | const newUserShortPass_2 = [
38 | {
39 | user_name: 'testUser_2',
40 | password_: 'passwor',
41 | full_name: 'Test User_2',
42 | email: 'testuser_2@example.com',
43 | arn: null,
44 | region: null,
45 | [testKey]: true,
46 | },
47 | ];
48 | //Password is too long User
49 | const newUser272CharsPW_3 = [
50 | {
51 | user_name: 'testUser_3',
52 | password_:
53 | 'testUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUser',
54 | full_name: 'Test User_3',
55 | email: 'testuser_3@example.com',
56 | arn: null,
57 | region: null,
58 | [testKey]: true,
59 | },
60 | ];
61 | //Email is wrong format User
62 | const newUserBadEmail_4 = [
63 | {
64 | user_name: 'testUser_4',
65 | password_: 'password',
66 | full_name: 'Test User_4',
67 | email: 'testuserexample.com',
68 | arn: null,
69 | region: null,
70 | [testKey]: true,
71 | },
72 | ];
73 | //Full Name is too long User
74 | const newUser272CharsFN_5 = [
75 | {
76 | user_name: 'testUser_5',
77 | password_: 'password',
78 | full_name:
79 | 'testUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUsertestUser',
80 | email: 'testuser_5@example.com',
81 | arn: null,
82 | region: null,
83 | [testKey]: true,
84 | },
85 | ];
86 |
87 | // want to make sure the user being passed into the test does not
88 | //already exist in our database, so we will invoke this function before and after each test
89 | const deleteUser = async () => {
90 | const text = `DELETE FROM "public"."users" WHERE ${testKey} = true`;
91 | console.log('how many times do you see me');
92 | await db.query(text);
93 | };
94 | // Might need to handle some Open Handle issues, not sure if this is the direction to go
95 | // beforeAll(async () => {
96 | // await db.connect();
97 | // });
98 | // const closeDB = async () => {
99 | // await db.end();
100 | // };
101 |
102 | //Setting up our tests
103 | //The outter most scope is testing our Post request at the Path api/newUser
104 | //The syntax of Jest will explain what each test is doing
105 | describe('POST /newUser', () => {
106 | describe('Testing Validator Middleware', () => {
107 | beforeEach(deleteUser());
108 | afterEach(deleteUser());
109 | test('Should Post newUser successfully', (done) => {
110 | request(server)
111 | .post('/api/newUser')
112 | .send(newUser)
113 | .expect(200)
114 | .end((err, res) => {
115 | if (err) return done(err);
116 | return done();
117 | });
118 | });
119 | test('Should Not Post User because User Name is too Long', (done) => {
120 | request(server)
121 | .post('/api/newUser')
122 | .send(newUser272CharsUN_1)
123 | .expect('User Name must be between 1-255 Characters')
124 | .expect(400)
125 | .end((err, res) => {
126 | if (err) return done(err);
127 | return done();
128 | });
129 | });
130 |
131 | test('Should Not Post User because Password is too Short', (done) => {
132 | request(server)
133 | .post('/api/newUser')
134 | .send(newUserShortPass_2)
135 | .expect('Password must be at least 8 characters long')
136 | .expect(400)
137 | .end((err, res) => {
138 | if (err) return done(err);
139 | return done();
140 | });
141 | });
142 | test('Should Not Post User because Password is too Long', (done) => {
143 | request(server)
144 | .post('/api/newUser')
145 | .send(newUser272CharsPW_3)
146 | .expect('Password must be at least 8 characters long')
147 | .expect(400)
148 | .end((err, res) => {
149 | if (err) return done(err);
150 | return done();
151 | });
152 | });
153 | test('Should Not Post User because Password is too Long', (done) => {
154 | request(server)
155 | .post('/api/newUser')
156 | .send(newUserBadEmail_4)
157 | .expect('Invalid Email Address')
158 | .expect(400)
159 | .end((err, res) => {
160 | if (err) return done(err);
161 | return done();
162 | });
163 | });
164 | test('Should Not Post User because Password is too Long', (done) => {
165 | request(server)
166 | .post('/api/newUser')
167 | .send(newUser272CharsFN_5)
168 | .expect('Full Name must be between 1-255 Characters')
169 | .expect(400)
170 | .end((err, res) => {
171 | if (err) return done(err);
172 | return done();
173 | });
174 | });
175 | });
176 | //This needs to be Changed around, it's just some copy pasta from a previous test
177 | /*
178 | describe('Testing HashPW Middleware', () => {
179 | beforeEach(deleteUser());
180 | afterEach(deleteUser());
181 | test('Should Hash Password successfully', (done) => {
182 | request(server)
183 | .post('/api/newUser')
184 | .send(newUser)
185 | .expect('Content-Type', /json/)
186 | .expect('{}')
187 | .expect(200)
188 | .end((err, res) => {
189 | if (err) return done(err);
190 | // _id = res.body.data[0]._id;
191 | return done();
192 | });
193 | });
194 | });
195 | */
196 | });
197 |
198 | //Here are some more tests that would be good to implement, the below is just a starting point, and need to be refined
199 | //for actual testing of our code
200 | /*
201 | describe('POST /:user_name', () => {
202 | it('should set a session cookie', async () => {
203 | const response = await request(server)
204 | .post('/testuser')
205 | .auth('testuser', 'testpassword'); // set basic authentication
206 |
207 | expect(response.status).toBe(200);
208 | expect(response.headers['set-cookie']).toBeDefined();
209 | });
210 | });
211 |
212 | describe('GET /', () => {
213 | it('should return a user whose cookie matches', async () => {
214 | const response = await request(server)
215 | .get('/')
216 | .set('Cookie', 'sessionid=abc123'); // set a session cookie
217 |
218 | expect(response.status).toBe(200);
219 | expect(response.body).toBeInstanceOf(Array);
220 | });
221 | });
222 |
223 | describe('PATCH /edit', () => {
224 | it('should update a user', async () => {
225 | const response = await request(server)
226 | .patch('/edit')
227 | .auth('testuser', 'testpassword') // set basic authentication
228 | .send({
229 | full_name: 'New Name',
230 | email: 'newemail@example.com',
231 | });
232 |
233 | expect(response.status).toBe(200);
234 | });
235 | });
236 |
237 | describe('DELETE /delete/:user_name', () => {
238 | it('should delete a user', async () => {
239 | const response = await request(server)
240 | .delete('/delete/testuser')
241 | .auth('testuser', 'testpassword'); // set basic authentication
242 |
243 | expect(response.status).toBe(200);
244 | });
245 | });
246 |
247 | describe('GET /logout', () => {
248 | it('should delete the session cookie', async () => {
249 | const response = await request(server)
250 | .get('/logout')
251 | .set('Cookie', 'sessionid=abc123'); // set a session cookie
252 |
253 | expect(response.status).toBe(200);
254 | expect(response.headers['set-cookie']).toBeDefined();
255 | });
256 | });*/
257 | });
258 |
--------------------------------------------------------------------------------
/__tests__/db_tests.js:
--------------------------------------------------------------------------------
1 | const db = require('../server/models/dbPool');
2 | require('dotenv').config();
3 | const { beforeEach, before, afterEach } = require('node:test');
4 |
5 | const testKey = process.env.TEST_TOKEN;
6 | const deleteUser = async function () {
7 | const text = `DELETE FROM "public"."users" WHERE ${testKey} = true`;
8 | console.log('how many times do you see me');
9 | await db.query(text);
10 | };
11 | //cleans the database of any test users before running tests
12 | beforeEach(deleteUser());
13 |
14 | describe('database unit tests', () => {
15 | //assigns a test user that we will use in our testing
16 | //I made a new row to assign to every document that defaults to false, and test users will be true
17 | //I was concerned about there being a row that may end up being able to give access to
18 | //areas users should not be able to access, and started making the column name hidden
19 | //I ended up not needing to do this, so this could be altered at a later time
20 | const newUser = {
21 | full_name: 'Test User',
22 | email: 'testUser@example.com',
23 | user_name: 'testUser',
24 | password_: 'password',
25 | arn: null,
26 | region: null,
27 | [testKey]: true,
28 | };
29 |
30 | describe('#addUser', () => {
31 | it('adds a new user to the database', async () => {
32 | const values = [
33 | newUser.full_name,
34 | newUser.email,
35 | newUser.user_name,
36 | newUser.password_,
37 | newUser.arn,
38 | newUser.region,
39 | newUser[testKey],
40 | ];
41 | const text = `INSERT INTO "public"."users" ("full_name", "email", "user_name", "password_", "arn", "region",${testKey})
42 | VALUES ($1, $2, $3, $4, $5, $6, $7)`;
43 |
44 | await db.query(text, values);
45 |
46 | const queryText = `SELECT * FROM "public"."users" WHERE "${testKey}" = 'true'`;
47 | const queryResult = await db.query(queryText);
48 |
49 | const { _id } = queryResult.rows[0];
50 |
51 | newUser._id = _id;
52 |
53 | expect(queryResult.rows.length).toBe(1);
54 | expect(queryResult.rows[0]).toMatchObject(newUser);
55 | });
56 | });
57 |
58 | describe('#getUser', () => {
59 | it('returns an object of our test users info from the database', async () => {
60 | const text = `SELECT * FROM "public"."users" WHERE "${testKey}" = 'true'`;
61 | const result = await db.query(text);
62 |
63 | expect(result.rows[0]).toMatchObject(newUser);
64 | });
65 | });
66 |
67 | describe('#deleteUser', () => {
68 | it('deletes a user from the database', async () => {
69 | // Delete the test user
70 | const text = `DELETE FROM "public"."users" WHERE "${testKey}" = 'true'`;
71 | const result = await db.query(text);
72 |
73 | expect(result.rows.length).toBe(0);
74 | // Check that the user was deleted
75 | const selectText = `SELECT * FROM "public"."users" WHERE "${testKey}" = 'true'`;
76 | const selectResult = await db.query(selectText);
77 | expect(selectResult.rows.length).toBe(0);
78 | });
79 | });
80 | });
81 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Lambdawg
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/jest.config.js:
--------------------------------------------------------------------------------
1 | /** @returns {Promise} */
2 | /**Allows for detailed description of the tests */
3 | module.exports = async () => {
4 | return {
5 | verbose: true,
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "lambdawg",
3 | "private": true,
4 | "version": "0.0.0",
5 | "description": "Lambdawg is a visualization UI for your AWS Lambda Functions",
6 | "main": "./client/index.js",
7 | "scripts": {
8 | "dev": "vite; export NODE_ENV=development",
9 | "build": "vite build",
10 | "preview": "vite preview",
11 | "fulldev": "concurrently \"nodemon server/server.js\" \"vite\"",
12 | "test": "jest",
13 | "testopen": "jest --detectOpenHandles"
14 | },
15 | "repository": {
16 | "type": "git",
17 | "url": "https://github.com/oslabs-beta/Lambdawg"
18 | },
19 | "keywords": [],
20 | "author": "Vincent Jacquemin, Chanda Gonet, Ted Gusek, Erica Park",
21 | "license": "ISC",
22 | "bugs": {
23 | "url": "https://github.com/oslabs-beta/Lambdawg/blob/main/Contributions.md"
24 | },
25 | "homepage": "https://github.com/oslabs-beta/Lambdawg/blob/main/README.md",
26 | "dependencies": {
27 | "@aws-sdk/client-cloudformation": "^3.272.0",
28 | "@aws-sdk/client-cloudwatch": "^3.272.0",
29 | "@aws-sdk/client-cloudwatch-logs": "^3.272.0",
30 | "@aws-sdk/client-lambda": "^3.272.0",
31 | "@aws-sdk/client-sts": "^3.272.0",
32 | "@aws-sdk/client-xray": "^3.279.0",
33 | "@emotion/react": "^11.10.6",
34 | "@emotion/styled": "^11.10.6",
35 | "@mui/material": "^5.11.12",
36 | "axios": "^1.3.3",
37 | "bcrypt": "^5.1.0",
38 | "chart.js": "^4.2.1",
39 | "concurrently": "^7.6.0",
40 | "cookie-parser": "^1.4.6",
41 | "cors": "^2.8.5",
42 | "d3": "^7.8.2",
43 | "dotenv": "^16.0.3",
44 | "eslint": "^8.36.0",
45 | "express": "^4.18.2",
46 | "fs": "^0.0.1-security",
47 | "html-webpack-plugin": "^5.5.0",
48 | "jest": "^29.4.3",
49 | "jsonwebtoken": "^9.0.0",
50 | "node": "^19.6.0",
51 | "nodemon": "^2.0.20",
52 | "path": "^0.12.7",
53 | "pg": "^8.9.0",
54 | "react": "^18.2.0",
55 | "react-chartjs-2": "^5.2.0",
56 | "react-dom": "^18.2.0",
57 | "react-icons": "^4.8.0",
58 | "react-router-dom": "^6.8.1",
59 | "react-scrollbars-custom": "^4.1.1",
60 | "react-switch": "^7.0.0",
61 | "redis": "^4.6.5",
62 | "redis-server": "^1.2.2",
63 | "sass": "^1.58.3",
64 | "validator": "^13.9.0"
65 | },
66 | "devDependencies": {
67 | "@types/react": "^18.0.27",
68 | "@types/react-dom": "^18.0.10",
69 | "@vitejs/plugin-react": "^3.1.0",
70 | "supertest": "^6.3.3",
71 | "vite": "^4.1.0"
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/postgres_table_create.sql:
--------------------------------------------------------------------------------
1 | --
2 | --PostgreSQL database Creation
3 | --
4 | --Connects to env file
5 | \i .env
6 |
7 | --This file sets up the schema of our database, and will maintain the connection to the database across users
8 | --
9 | --To invoke this file, insert the below code in the terminal
10 | --psql -d ${PG_URI} -f postgres_table_create.sql
11 | --I did not set a port initially, but to do so you would add -p
12 | -- and instead of ${PG_URI} use your own URI
13 | -------------------
14 | SET statement_timeout = 0;
15 | SET lock_timeout = 0;
16 | SET idle_in_transaction_session_timeout = 0;
17 | SET client_encoding = 'UTF8';
18 | SET standard_conforming_strings = on;
19 | SELECT pg_catalog.set_config('search_path','',false);
20 | SET check_function_bodies = false;
21 | SET xmloption = content;
22 | SET client_min_messages = warning;
23 | SET row_security = off;
24 | SET idleTimeoutMillis = 5000;
25 | SET connectionTimeoutMillis = 7500;
26 | --these last 2 items are in place to correct thr open handle issue
27 | -------------------
28 | -- CREATE EXTENSION pgcrypto;
29 | -------------------
30 | CREATE TABLE public.users (
31 | _id serial NOT NULL,
32 | full_name varchar NOT NULL,
33 | user_name varchar NOT NULL UNIQUE,
34 | email varchar NOT NULL UNIQUE,
35 | password_ varchar NOT NULL,
36 | ${TEST_TOKEN} BOOLEAN DEFAULT false,
37 | CONSTRAINT users_pk PRIMARY KEY ("_id")
38 | ) WITH (
39 | OIDS=FALSE
40 | );
41 | -------------------
42 | --
43 | --Test DATA for users
44 | -------------------
45 | -- INSERT INTO public.users (full_name, user_name, email, password_) VALUES ('John Doe', 'JohnnyD', 'johnnyd@johnnyd.com','jdpassword');
46 | -------------------
47 |
48 |
49 | --To clear Table, uncomment the below line, and remove the '/'
50 | -- -- -- D/R/O/P T/A/B/L/E public.users
51 | --
52 | --To retrieve the information
53 | --
54 | -- SELECT _id
55 | -- FROM users
56 | -- WHERE email = 'johnnyd@johnnyd.com'
57 | -- AND password = crypt('jdpassword', password);
58 | --
59 | -- Expected Output
60 | --
61 | -- _id
62 | -- ------
63 | -- 1
64 | -- (1 row)
65 | --
66 | -- Example of Bad Password
67 | --
68 | -- SELECT _id
69 | -- FROM users
70 | -- WHERE email = 'johnnyd@johnnyd.com'
71 | -- AND password = crypt('wrongpassword', password);
72 | --
73 | -- Expected Output
74 | --
75 | -- _id
76 | -- ------
77 | -- (0 rows)
78 |
--------------------------------------------------------------------------------
/server/aws/cloudFormation.yaml:
--------------------------------------------------------------------------------
1 | Description: "CloudFormation stack"
2 |
3 | Resources:
4 | LambdawgDelegationRole:
5 | Type: "AWS::IAM::Role"
6 | Properties:
7 | AssumeRolePolicyDocument:
8 | Version: 2012-10-17
9 | Statement:
10 | - Effect: Allow
11 | Principal:
12 | AWS:
13 | - arn:aws:iam::403777712406:user/New-Lambdawg
14 | Action:
15 | - "sts:AssumeRole"
16 | Condition:
17 | StringEquals:
18 | "sts:ExternalId": !Ref ExternalId
19 | Path: /
20 | RoleName: LambdawgDelegationRole
21 | Policies:
22 | - PolicyName: Resources
23 | PolicyDocument:
24 | Version: 2012-10-17
25 | Statement:
26 | - Effect: Allow
27 | Action: "apigateway:GET"
28 | Resource: "*"
29 | - Effect: Allow
30 | Action: "apigateway:HEAD"
31 | Resource: "*"
32 | - Effect: Allow
33 | Action: "apigateway:OPTIONS"
34 | Resource: "*"
35 | - Effect: Allow
36 | Action: "appsync:get*"
37 | Resource: "*"
38 | - Effect: Allow
39 | Action: "appsync:list*"
40 | Resource: "*"
41 | - Effect: Allow
42 | Action: "athena:list*"
43 | Resource: "*"
44 | - Effect: Allow
45 | Action: "athena:batchGet*"
46 | Resource: "*"
47 | - Effect: Allow
48 | Action: "athena:getNamedQuery"
49 | Resource: "*"
50 | - Effect: Allow
51 | Action: "athena:getQueryExecution"
52 | Resource: "*"
53 | - Effect: Allow
54 | Action: "athena:getQueryExecution"
55 | Resource: "*"
56 | - Effect: Allow
57 | Action: "autoscaling:describe*"
58 | Resource: "*"
59 | - Effect: Allow
60 | Action: "batch:describe*"
61 | Resource: "*"
62 | - Effect: Allow
63 | Action: "cloudformation:describe*"
64 | Resource: "*"
65 | - Effect: Allow
66 | Action: "cloudformation:get*"
67 | Resource: "*"
68 | - Effect: Allow
69 | Action: "cloudformation:list*"
70 | Resource: "*"
71 | - Effect: Allow
72 | Action: "cloudfront:get*"
73 | Resource: "*"
74 | - Effect: Allow
75 | Action: "cloudfront:list*"
76 | Resource: "*"
77 | - Effect: Allow
78 | Action: "cloudwatch:describe*"
79 | Resource: "*"
80 | - Effect: Allow
81 | Action: "cloudwatch:list*"
82 | Resource: "*"
83 | - Effect: Allow
84 | Action: "dax:describe*"
85 | Resource: "*"
86 | - Effect: Allow
87 | Action: "dax:list*"
88 | Resource: "*"
89 | - Effect: Allow
90 | Action: "discovery:describe*"
91 | Resource: "*"
92 | - Effect: Allow
93 | Action: "discovery:list*"
94 | Resource: "*"
95 | - Effect: Allow
96 | Action: "dynamodb:describe*"
97 | Resource: "*"
98 | - Effect: Allow
99 | Action: "dynamodb:list*"
100 | Resource: "*"
101 | - Effect: Allow
102 | Action: "ec2:describe*"
103 | Resource: "*"
104 | - Effect: Allow
105 | Action: "ecs:describe*"
106 | Resource: "*"
107 | - Effect: Allow
108 | Action: "ecs:list*"
109 | Resource: "*"
110 | - Effect: Allow
111 | Action: "ecr:describe*"
112 | Resource: "*"
113 | - Effect: Allow
114 | Action: "ecr:get*"
115 | Resource: "*"
116 | - Effect: Allow
117 | Action: "ecr:list*"
118 | Resource: "*"
119 | - Effect: Allow
120 | Action: "eks:describe*"
121 | Resource: "*"
122 | - Effect: Allow
123 | Action: "eks:list*"
124 | Resource: "*"
125 | - Effect: Allow
126 | Action: "elasticache:describe*"
127 | Resource: "*"
128 | - Effect: Allow
129 | Action: "elasticache:list*"
130 | Resource: "*"
131 | - Effect: Allow
132 | Action: "elasticloadbalancing:describe*"
133 | Resource: "*"
134 | - Effect: Allow
135 | Action: "es:describe*"
136 | Resource: "*"
137 | - Effect: Allow
138 | Action: "es:list*"
139 | Resource: "*"
140 | - Effect: Allow
141 | Action: "events:describe*"
142 | Resource: "*"
143 | - Effect: Allow
144 | Action: "events:list*"
145 | Resource: "*"
146 | - Effect: Allow
147 | Action: "firehose:describe*"
148 | Resource: "*"
149 | - Effect: Allow
150 | Action: "firehose:list*"
151 | Resource: "*"
152 | - Effect: Allow
153 | Action: "glacier:describe*"
154 | Resource: "*"
155 | - Effect: Allow
156 | Action: "glacier:getDataRetrievalPolicy"
157 | Resource: "*"
158 | - Effect: Allow
159 | Action: "glacier:getVaultAccessPolicy"
160 | Resource: "*"
161 | - Effect: Allow
162 | Action: "glacier:getVaultLock"
163 | Resource: "*"
164 | - Effect: Allow
165 | Action: "glacier:getVaultNotifications"
166 | Resource: "*"
167 | - Effect: Allow
168 | Action: "glacier:listTagsForVault"
169 | Resource: "*"
170 | - Effect: Allow
171 | Action: "glacier:listVaults"
172 | Resource: "*"
173 | - Effect: Allow
174 | Action: "iot:describe*"
175 | Resource: "*"
176 | - Effect: Allow
177 | Action: "iot:get*"
178 | Resource: "*"
179 | - Effect: Allow
180 | Action: "iot:list*"
181 | Resource: "*"
182 | - Effect: Allow
183 | Action: "kinesis:describe*"
184 | Resource: "*"
185 | - Effect: Allow
186 | Action: "kinesis:list*"
187 | Resource: "*"
188 | - Effect: Allow
189 | Action: "kinesisanalytics:describe*"
190 | Resource: "*"
191 | - Effect: Allow
192 | Action: "kinesisanalytics:list*"
193 | Resource: "*"
194 | - Effect: Allow
195 | Action: "lambda:listFunctions"
196 | Resource: "*"
197 | - Effect: Allow
198 | Action: "lambda:listTags"
199 | Resource: "*"
200 | - Effect: Allow
201 | Action: "rds:describe*"
202 | Resource: "*"
203 | - Effect: Allow
204 | Action: "rds:list*"
205 | Resource: "*"
206 | - Effect: Allow
207 | Action: "route53:list*"
208 | Resource: "*"
209 | - Effect: Allow
210 | Action: "route53:get*"
211 | Resource: "*"
212 | - Effect: Allow
213 | Action: "s3:getBucket*"
214 | Resource: "*"
215 | - Effect: Allow
216 | Action: "s3:list*"
217 | Resource: "*"
218 | - Effect: Allow
219 | Action: "sdb:domainMetadata"
220 | Resource: "*"
221 | - Effect: Allow
222 | Action: "sdb:get*"
223 | Resource: "*"
224 | - Effect: Allow
225 | Action: "sdb:list*"
226 | Resource: "*"
227 | - Effect: Allow
228 | Action: "sns:get*"
229 | Resource: "*"
230 | - Effect: Allow
231 | Action: "sns:list*"
232 | Resource: "*"
233 | - Effect: Allow
234 | Action: "sqs:get*"
235 | Resource: "*"
236 | - Effect: Allow
237 | Action: "sqs:list*"
238 | Resource: "*"
239 | - Effect: Allow
240 | Action: "states:describe*"
241 | Resource: "*"
242 | - Effect: Allow
243 | Action: "states:get*"
244 | Resource: "*"
245 | - Effect: Allow
246 | Action: "states:list*"
247 | Resource: "*"
248 | - Effect: Allow
249 | Action: "tag:get*"
250 | Resource: "*"
251 | - PolicyName: Logs
252 | PolicyDocument:
253 | Version: 2012-10-17
254 | Statement:
255 | - Effect: Allow
256 | Action: "logs:deleteSubscriptionFilter"
257 | Resource: "*"
258 | - Effect: Allow
259 | Action: "logs:describeLogStreams"
260 | Resource: "*"
261 | - Effect: Allow
262 | Action: "logs:describeSubscriptionFilters"
263 | Resource: "*"
264 | - Effect: Allow
265 | Action: "logs:filterLogEvents"
266 | Resource: "*"
267 | - Effect: Allow
268 | Action: "logs:putSubscriptionFilter"
269 | Resource: "*"
270 | - Effect: Allow
271 | Action: "logs:startQuery"
272 | Resource: "*"
273 | - Effect: Allow
274 | Action: "logs:stopQuery"
275 | Resource: "*"
276 | - PolicyName: Metrics
277 | PolicyDocument:
278 | Version: 2012-10-17
279 | Statement:
280 | - Effect: Allow
281 | Action: "cloudwatch:get*"
282 | Resource: "*"
283 | - PolicyName: Traces
284 | PolicyDocument:
285 | Version: 2012-10-17
286 | Statement:
287 | - Effect: Allow
288 | Action: "xray:batch*"
289 | Resource: "*"
290 | - Effect: Allow
291 | Action: "xray:get*"
292 | Resource: "*"
293 | Parameters:
294 | ExternalId:
295 | Description: "The external ID for the LAMBDAWG delegation role"
296 | Type: String
297 |
298 | Outputs:
299 | Version:
300 | Description: LAMBDAWG CF template version
301 | Value: 2020-02-06
302 | LambdawgDelegationRoleArn:
303 | Description: "The ARN for the LAMBDAWG delegation role"
304 | Value: !GetAtt
305 | - LambdawgDelegationRole
306 | - Arn
307 |
--------------------------------------------------------------------------------
/server/controllers/MetricsController.js:
--------------------------------------------------------------------------------
1 | const Redis = require('redis');
2 | // Open a new redis client to send requests to the redis server.
3 | const redisClient = Redis.createClient();
4 | // Check if the connection is open before proceeding.
5 | (async () => {
6 | await redisClient.connect().catch((err) => {
7 | console.log('Redis Connect Error: ' + err.message);
8 | });
9 | })();
10 | // Use AWS SDK for javascript v3 to import cloudWatch Metrics.
11 | const {
12 | CloudWatch,
13 | GetMetricDataCommand,
14 | } = require('@aws-sdk/client-cloudwatch');
15 |
16 | //declare an rdsMetricsController object
17 | const MetricsController = {};
18 |
19 | MetricsController.getMetrics = async (req, res, next) => {
20 | console.log('in metrics');
21 |
22 | //check if the user's log data is present in redis before making the api calls.
23 | redisClient
24 | .get('LambdaMetrics' + req.body.arn)
25 | .then((data) => JSON.parse(data))
26 | .then((data) => {
27 | console.log(data);
28 | if (data !== null) {
29 | res.locals.getLambdaMetrics = data;
30 | return next();
31 | } else {
32 | console.log('cache miss');
33 | const fullfunc = async () => {
34 | //declare a constant variable called cloudwatch, which will be used to call the AWS CloudWatch API
35 | const cloudwatch = new CloudWatch({
36 | region: 'us-east-1',
37 | credentials: res.locals.credentials,
38 | });
39 |
40 | //declare a constant variable called params, which will be used to pass the parameters of the RDSCpuUtilization metrics to the AWS CloudWatch API
41 | const params = {
42 | MetricDataQueries: [],
43 | StartTime: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000), // one hour ago
44 | EndTime: new Date(), // now
45 | ScanBy: 'TimestampDescending', // the order to return data, newest first
46 | };
47 |
48 | //try to call the AWS CloudWatch API to get the RDSCpuUtilization metrics
49 | for (const Lambda in res.locals.lambdaNames) {
50 | console.log('lambdaName ' + Lambda);
51 | params.MetricDataQueries.push({
52 | Id: `invocations${Lambda}`,
53 | MetricStat: {
54 | Metric: {
55 | Namespace: 'AWS/Lambda',
56 | MetricName: 'Invocations',
57 | Dimensions: [
58 | {
59 | Name: 'FunctionName',
60 | Value: res.locals.lambdaNames[Lambda],
61 | },
62 | ],
63 | },
64 | Period: 300, // the granularity of the data, in seconds
65 | Stat: 'Sum', // the statistic to retrieve for this metric
66 | },
67 | ReturnData: true, // whether to return the data for this query
68 | });
69 | params.MetricDataQueries.push({
70 | Id: `errors${Lambda}`,
71 | MetricStat: {
72 | Metric: {
73 | Namespace: 'AWS/Lambda',
74 | MetricName: 'Errors',
75 | Dimensions: [
76 | {
77 | Name: 'FunctionName',
78 | Value: res.locals.lambdaNames[Lambda],
79 | },
80 | ],
81 | },
82 | Period: 300, // the granularity of the data, in seconds
83 | Stat: 'Sum', // the statistic to retrieve for this metric
84 | },
85 | ReturnData: true, // whether to return the data for this query
86 | });
87 | }
88 | try {
89 | console.log('in try');
90 | // const data = await cloudwatch.getMetricData(params);
91 | const data = await cloudwatch.send(
92 | new GetMetricDataCommand(params)
93 | );
94 | console.log('full montey');
95 | res.locals.getLambdaMetrics = data;
96 | await redisClient.set(
97 | 'LambdaMetrics' + req.body.arn,
98 | JSON.stringify(data),
99 | 'EX',
100 | 60 * 60
101 | );
102 | return next();
103 | //if there is an error, log the error and throw the error
104 | } catch (error) {
105 | console.error(error);
106 | throw error;
107 | }
108 | };
109 | fullfunc();
110 | }
111 | });
112 | };
113 |
114 | module.exports = MetricsController;
115 |
--------------------------------------------------------------------------------
/server/controllers/authControllers.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/dbPool');
2 | const bcrypt = require('bcrypt');
3 | const validator = require('validator');
4 |
5 | const authControllers = {};
6 | //Verifies that the User Name and Password exist and match
7 | authControllers.verifyUN_Pass = (req, res, next) => {
8 | const { user_name } = req.params;
9 | const { password_ } = req.body[0];
10 |
11 | const text = 'SELECT * FROM public.users WHERE user_name = $1';
12 | db.query(text, [user_name], async (err, result) => {
13 | if (err) {
14 | console.log('Error Executing Authorization Query', err);
15 | return res.status(500).send('Error Executing Authorization Query');
16 | }
17 |
18 | if (result.rows.length === 0) {
19 | console.log('Invalid Credentials');
20 | return res.status(401).send('Invalid Credentials');
21 | }
22 |
23 | const password_hash = result.rows[0].password_;
24 |
25 | const match = await bcrypt.compare(password_, password_hash);
26 |
27 | if (!match) {
28 | console.log('username/pw do not match');
29 | return res.status(401).send('Invalid Credentials!');
30 | }
31 |
32 | console.log('User Authentication Successful');
33 | res.locals.user = result.rows[0];
34 | return next();
35 | });
36 | };
37 | //Using Validator to sanitize the incoming data, to help prevent any code injections
38 | authControllers.validator = (req, res, next) => {
39 | const { full_name, user_name, email, password_ } = req.body[0];
40 | if (!validator.isLength(full_name, { min: 1, max: 255 })) {
41 | return res.status(400).send('Full Name must be between 1-255 Characters');
42 | }
43 | if (!validator.isLength(user_name, { min: 1, max: 255 })) {
44 | return res.status(400).send('User Name must be between 1-255 Characters');
45 | }
46 | if (!validator.isEmail(email, { min: 1, max: 255 })) {
47 | return res.status(400).send('Invalid Email Address');
48 | }
49 | if (!validator.isLength(password_, { min: 8, max: 255 })) {
50 | console.log('Password must be at least 8 characters long');
51 | return res.status(400).send('Password must be at least 8 characters long');
52 | }
53 | //the below regex will ensure that the user puts in a more secure password
54 | // else if (
55 | // !validator.matches(
56 | // password_,
57 | // /^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#\$%\^&\*])/
58 | // )
59 | // ) {
60 | // console.log(
61 | // 'Password must contain at least one lowercase letter, one uppercase letter, one digit, and one special character'
62 | // );
63 | // return res
64 | // .status(400)
65 | // .send(
66 | // 'Password must contain at least one lowercase letter, one uppercase letter, one digit, and one special character'
67 | // );
68 | // }
69 | else {
70 | console.log('Password is valid');
71 | return next();
72 | }
73 | };
74 |
75 | module.exports = authControllers;
76 |
--------------------------------------------------------------------------------
/server/controllers/cookieControllers.js:
--------------------------------------------------------------------------------
1 | require('dotenv').config();
2 | const jwt = require('jsonwebtoken');
3 |
4 | const cookieControllers = {};
5 |
6 | cookieControllers.setCookie = (req, res, next) => {
7 | const { user_name } = req.params;
8 |
9 | const accessToken = jwt.sign(user_name, process.env.ACCESS_SECRET_TOKEN);
10 |
11 | //sends the token as a cookie
12 | //Add ' secure: true ' if you want to make it via https only but would need to have certificate first
13 |
14 | res.cookie('jwt', accessToken, {
15 | httpOnly: true,
16 | });
17 | return next();
18 | };
19 |
20 | cookieControllers.authenticateCookie = (req, res, next) => {
21 | console.log('inside cookie controller', JSON.stringify(req.cookie));
22 |
23 | //assigns the jwt string to authHeader
24 | const authHeader = req.cookies.jwt;
25 | //this is checking to see if we have a cookie, and if we do, assign the cookie to token
26 | const token = authHeader && authHeader;
27 |
28 | //checks if token doesn't exist
29 | //if it does, verifies if its legit
30 | //if not legit sends back an error
31 | if (!token) {
32 | return res.status(401).json({ message: 'No Cookie' });
33 | }
34 | try {
35 | const decoded = jwt.verify(token, process.env.ACCESS_SECRET_TOKEN);
36 | res.locals.user_name = decoded;
37 | return next();
38 | } catch (err) {
39 | return res.status(401).json({ message: 'No Cookie' });
40 | }
41 | };
42 |
43 | cookieControllers.deleteCookie = (req, res, next) => {
44 | try {
45 | res.clearCookie('jwt');
46 | return next();
47 | } catch (err) {
48 | return res.status(401).json({ message: 'Issue Deleting Cookie' });
49 | }
50 | };
51 |
52 | module.exports = cookieControllers;
53 |
--------------------------------------------------------------------------------
/server/controllers/credentialController.js:
--------------------------------------------------------------------------------
1 | const Redis = require('redis');
2 | // Open a new redis client to send requests to the redis server.
3 | const redisClient = Redis.createClient();
4 | // Check if the connection is open befors proceeding.
5 | (async () => {
6 | await redisClient.connect().catch((err) => {
7 | console.log('Redis Connect Error: ' + err.message);
8 | });
9 | })();
10 |
11 | // Use AWS SDK for javascript v3 to import session Token.
12 | const { STSClient, AssumeRoleCommand } = require('@aws-sdk/client-sts');
13 | const { triggerAsyncId } = require('async_hooks');
14 | const dotenv = require('dotenv').config();
15 | const { _KEY, _SKEY, USER_ARN } = process.env;
16 |
17 | const credentialController = {};
18 | //declare a constant variable called credentials, which will be used to call the AWS STS API, passing in the access key id and secret access key of the cloudband account
19 | const credentials = {
20 | accessKeyId: _KEY,
21 | secretAccessKey: _SKEY,
22 | };
23 |
24 | //get user's access key id and secret access key from AWS
25 | credentialController.getCredentials = async (req, res, next) => {
26 | // console.log('credential controller arn', req.body.arn);
27 | const { arn } = req.body;
28 | // const arn = USER_ARN;
29 | const region = 'us-east-1';
30 | const info = {
31 | RoleArn: arn,
32 | RoleSessionName: 'LambdawgRoleSession',
33 | DurationSeconds: 900,
34 | ExternalId: 'Lambdawg',
35 | };
36 | //create new STS client instance with cloudband's region and credentials
37 | const stsClient = new STSClient({ region: region, credentials: credentials });
38 |
39 | //send request to AWS to assume role in user's account to retrieve temporary credentials for read-only access
40 | try {
41 | const assumedRole = await stsClient.send(new AssumeRoleCommand(info));
42 | const accessKeyId = assumedRole.Credentials.AccessKeyId;
43 | const secretAccessKey = assumedRole.Credentials.SecretAccessKey;
44 | const sessionToken = assumedRole.Credentials.SessionToken;
45 | res.locals.credentials = { accessKeyId, secretAccessKey, sessionToken };
46 | return next();
47 | } catch (error) {
48 | console.log(error);
49 | next(error);
50 | }
51 | };
52 |
53 | //delete user-specific redis data
54 | credentialController.deleteRedis = async (req, res, next) => {
55 | redisClient.del('LambdaTraces' + req.body.arn, (err, result) => {
56 | if (err) {
57 | console.log(err);
58 | return next(err);
59 | }
60 | });
61 | redisClient.del('LambdaLogs' + req.body.arn, (err, result) => {
62 | if (err) {
63 | console.log(err);
64 | return next(err);
65 | }
66 | });
67 | redisClient.del('LambdaList' + req.body.arn, (err, result) => {
68 | if (err) {
69 | console.log(err);
70 | return next(err);
71 | }
72 | });
73 | redisClient.del('LambdaMetrics' + req.body.arn, (err, result) => {
74 | if (err) {
75 | console.log(err);
76 | return next(err);
77 | }
78 | });
79 | console.log('redis user data deleted !');
80 | return res.status(200).json('Redis user data deleted');
81 | };
82 |
83 | module.exports = credentialController;
--------------------------------------------------------------------------------
/server/controllers/dbControllers.js:
--------------------------------------------------------------------------------
1 | const db = require('../models/dbPool');
2 | const validator = require('validator');
3 | require('dotenv').config();
4 |
5 | const testKey = process.env.TEST_TOKEN;
6 |
7 | const dbControllers = {};
8 |
9 | dbControllers.getUser = (req, res, next) => {
10 | //we will be getting the user name from the authenticate cookie here
11 | const { user_name } = res.locals;
12 | const text = `SELECT * FROM "public"."users" WHERE "user_name" = '${user_name}'`;
13 | db.query(text)
14 | .then((response) => {
15 | res.locals.data = response;
16 | return next();
17 | })
18 | .catch((err) => {
19 | next({
20 | log: `Express Error Handler caught getUsers error: ${err}`,
21 | status: 500,
22 | message: {
23 | err: 'dbControllerTest.getUsers encountered an error',
24 | },
25 | });
26 | });
27 | };
28 |
29 | dbControllers.addUser = (req, res, next) => {
30 | const text = `INSERT INTO "public"."users" (full_name, user_name, email, password_,${testKey}) VALUES ($1, $2, $3, $4, $5)`;
31 | const { full_name, user_name, email } = req.body[0];
32 | const forTesting = req.body[0][testKey];
33 | const { password_ } = res.locals;
34 |
35 | db.query(
36 | text,
37 | [full_name, user_name, email, password_, forTesting],
38 | (err, result) => {
39 | if (err) {
40 | console.log('Error at dbControllers.addUser: ', err);
41 | return res.status(500).send('Error Executing Insert Query ');
42 | }
43 | console.log('Add User Query Executed Successfully');
44 | res.locals.user_name = user_name;
45 | next();
46 | }
47 | );
48 | };
49 |
50 | dbControllers.deleteUser = (req, res, next) => {
51 | const text = 'DELETE FROM "public"."users" WHERE user_name = $1';
52 | const { user_name } = req.params;
53 |
54 | db.query(text, [user_name], (err, result) => {
55 | if (err) {
56 | console.log('Error at dbControllers.deleteUser: ', err);
57 | return res.status(500).send('Error Executing Delete Query');
58 | }
59 | if (result.rowCount === 0) {
60 | console.log('User Not Found');
61 | return res.status(404).send('User Not Found');
62 | }
63 | console.log('Delete User Query Executed Successfully');
64 | next();
65 | });
66 | };
67 |
68 | dbControllers.editUser = (req, res, next) => {
69 | const { arn, region, _id, user_name } = req.body[0];
70 |
71 | const text =
72 | 'UPDATE "public"."users" SET arn = $1, region = $2 WHERE _id = $3';
73 |
74 | db.query(text, [arn, region, _id], (err, result) => {
75 | if (err) {
76 | console.log(`Error Updating User: ${user_name}`, err);
77 | return res.status(500).send(`Error Updating User: ${user_name}`);
78 | }
79 | console.log(`${user_name} updated Successfully`);
80 | next();
81 | });
82 | };
83 |
84 | module.exports = dbControllers;
85 |
--------------------------------------------------------------------------------
/server/controllers/encryptionController.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require('bcrypt');
2 |
3 | //Using Bcrypt to encrypt the users password
4 | const encryptionController = {};
5 |
6 | encryptionController.hashPW = (req, res, next) => {
7 | const { password_ } = req.body[0];
8 | bcrypt.hash(password_, 10, function (err, hash) {
9 | if (err) {
10 | console.log('Error Saving User Credentials');
11 | return res.status(500).send('Error Saving User Credentials');
12 | } else {
13 | console.log('Credentials Saved Successfully');
14 |
15 | res.locals.password_ = hash;
16 | return next();
17 | }
18 | });
19 | };
20 |
21 | module.exports = encryptionController;
22 |
--------------------------------------------------------------------------------
/server/controllers/fileController.js:
--------------------------------------------------------------------------------
1 | const fs = require("fs");
2 | const path = require("path");
3 | const fileController = {};
4 |
5 | //d3 node chart needs to read data from a json file. fs system does not work from the front end
6 | fileController.writeToFile = (req, res, next) => {
7 | const { serviceIds } = req.body;
8 | console.log("in filecontroller", serviceIds);
9 | //writing to service.json file in Charts. D3 node chart is set to read from service.json from the same Charts folder
10 | const filePath = path.resolve(__dirname, "../../src/Chart/service.json");
11 |
12 | //separate service parser function to convert serviceIds array to a format readable by the d3 chart
13 | function ServiceParser(arrayObj) {
14 | const serviceJSON = {
15 | nodes: [],
16 | links: [],
17 | };
18 | arrayObj.forEach((obj) => {
19 | const serviceArray = obj.serviceIds;
20 | //each service id needs a unique key so the links can work from service to service (if related)
21 | serviceArray.forEach((serviceObj, i) => {
22 | serviceJSON.nodes.push({
23 | id: `${serviceObj.Name}${i}`,
24 | name: serviceObj.Type,
25 | });
26 | //source needs a "0" unique id as the first item in each serviceObj is the source for the other 2 services
27 | serviceJSON.links.push({
28 | source: `${serviceObj.Name}0`,
29 | target: `${serviceObj.Name}${i}`,
30 | });
31 | });
32 | });
33 | //convert the dataResults to JSON format before writing to file
34 | const dataResults = JSON.stringify(serviceJSON);
35 |
36 | //the "w" flag will overwrite whatever is already in the JSON file to update it
37 | fs.writeFile(filePath, dataResults, { flag: "w" }, (err) => {
38 | console.log("filePath", filePath);
39 | if (err) {
40 | console.error(err);
41 | return;
42 | }
43 | console.log("Data written to file");
44 | res.locals.writtenServices = dataResults;
45 | return next();
46 | });
47 | }
48 | ServiceParser(serviceIds);
49 | };
50 | module.exports = fileController;
51 |
--------------------------------------------------------------------------------
/server/controllers/lambdaLogsController.js:
--------------------------------------------------------------------------------
1 | const Redis = require('redis');
2 | // Open a new redis client to send requests to the redis server.
3 | const redisClient = Redis.createClient();
4 | // Check if the connection is open before proceeding.
5 | (async () => {
6 | await redisClient.connect().catch((err) => {
7 | console.log('Redis Connect Error: ' + err.message);
8 | });
9 | })();
10 |
11 | // Use AWS SDK for javascript v3 to import cloudWatch logs.
12 | const {
13 | CloudWatchLogsClient,
14 | FilterLogEventsCommand,
15 | } = require('@aws-sdk/client-cloudwatch-logs');
16 |
17 | //retrieve logs of each lambda function
18 | const getLambdaLogs = async (req, res, next) => {
19 | //check if the user's log data is present in redis before making the api calls.
20 | redisClient
21 | .get('LambdaLogs' + req.body.arn)
22 | .then((data) => JSON.parse(data))
23 | .then((data) => {
24 | if (data !== null) {
25 | res.locals.functionLogs = data;
26 | return next();
27 | } else {
28 | console.log('cache miss');
29 | //executes call if no valid redis data is found
30 | const fullfunc = async () => {
31 | res.locals.functionLogs = [];
32 | console.log(res.locals.lambdaNames);
33 | for (let Lambda of res.locals.lambdaNames) {
34 | console.log('in lambda ' + Lambda);
35 | const currFunc = Lambda;
36 | const logGroupName = '/aws/lambda/' + currFunc;
37 |
38 | //create new instance of CloudWatchLogsClient with user's region and credentials
39 | const cloudWatchLogs = new CloudWatchLogsClient({
40 | region: 'us-east-1',
41 | credentials: res.locals.credentials,
42 | });
43 |
44 | //initiate starttime to be 7 days before endtime in milliseconds from epoch time
45 | const now = new Date();
46 | const EndTime = now.valueOf();
47 | const StartTime = new Date(
48 | now.getTime() - 7 * 24 * 60 * 60 * 1000
49 | ).valueOf();
50 |
51 | //helper function to recursively retrieve logs if there's a nextToken
52 | const nextTokenHelper = async (nextToken, data = []) => {
53 | if (!nextToken) {
54 | return data;
55 | }
56 | const nextLogEvents = await cloudWatchLogs.send(
57 | new FilterLogEventsCommand({
58 | logGroupName,
59 | endTime: EndTime.valueOf(),
60 | startTime: StartTime.valueOf(),
61 | nextToken,
62 | filterPattern: '- START - END ',
63 | })
64 | );
65 | data.push(nextLogEvents.events);
66 | return nextTokenHelper(nextLogEvents.nextToken, data);
67 | };
68 |
69 | //AWS query format for each individual lambda function to list logs
70 | try {
71 | const logEvents = await cloudWatchLogs.send(
72 | new FilterLogEventsCommand({
73 | logGroupName,
74 | endTime: EndTime.valueOf(),
75 | startTime: StartTime.valueOf(),
76 | filterPattern: '- START - END ',
77 | })
78 | );
79 |
80 | const Redis = require('redis');
81 | // const getOrSetCache = require('../redis');
82 | const redisClient = Redis.createClient();
83 | (async () => {
84 | await redisClient.connect().catch((err) => {
85 | console.log('Redis Connect Error: ' + err.message);
86 | });
87 | })();
88 | // console.log('LogEvents ' + JSON.stringify(logEvents));
89 | //if there are no logs, return to next middleware
90 | if (!logEvents) {
91 | return next();
92 | }
93 |
94 | //if there is a nextToken, recursively retrieve logs with helper function
95 | if (logEvents.nextToken) {
96 | const nextTokenData = await nextTokenHelper(
97 | logEvents.nextToken
98 | );
99 | logEvents.events = logEvents.events.concat(...nextTokenData);
100 | }
101 |
102 | //only return the first 50 logs
103 | const fiftyLogEvents = logEvents.events.slice(0, 50);
104 |
105 | //format the logs to remove unnecessary information
106 | const logEventsMessages = [];
107 | fiftyLogEvents.forEach((event) => {
108 | if (
109 | event.message.slice(0, 4) !== 'LOGS' &&
110 | event.message.slice(0, 9) !== 'EXTENSION'
111 | ) {
112 | logEventsMessages.push({
113 | message: event.message.slice(67),
114 | timestamp: new Date(event.timestamp),
115 | });
116 | } else {
117 | logEventsMessages.push({
118 | message: event.message,
119 | timeStamp: new Date(event.timestamp),
120 | });
121 | }
122 | });
123 | // console.log('logeventmessages ' + logEventsMessages);
124 | res.locals.functionLogs.push({
125 | FunctionName: Lambda,
126 | logs: logEventsMessages,
127 | });
128 | await redisClient.set(
129 | 'LambdaLogs' + req.body.arn,
130 | JSON.stringify(res.locals.functionLogs),
131 | 'EX',
132 | 60 * 60
133 | );
134 | } catch (err) {
135 | if (err) console.error(err);
136 | console.log('there was an error in the loop : ' + err);
137 | //return next(err);
138 | }
139 | }
140 | return next();
141 | };
142 | fullfunc();
143 | }
144 | });
145 | };
146 |
147 | module.exports = {
148 | getLambdaLogs,
149 | };
150 |
--------------------------------------------------------------------------------
/server/controllers/listLambdasController.js:
--------------------------------------------------------------------------------
1 | const Redis = require('redis');
2 | // Open a new redis client to send requests to the redis server.
3 | const redisClient = Redis.createClient();
4 | // Check if the connection is open before proceeding.
5 | (async () => {
6 | await redisClient.connect().catch((err) => {
7 | console.log('Redis Connect Error: ' + err.message);
8 | });
9 | })();
10 |
11 | // Use AWS SDK for javascript v3 to import cloudWatch logs.
12 | const {
13 | LambdaClient,
14 | ListFunctionsCommand,
15 | } = require('@aws-sdk/client-lambda');
16 | const { get } = require('../routes/api');
17 | const { json } = require('stream/consumers');
18 |
19 | const listLambdasController = {};
20 |
21 | //retrieve name of all user's lambda functions
22 | listLambdasController.getLambdas = (req, res, next) => {
23 | redisClient
24 | //check if the user's function list is present in redis before making the api call
25 | .get('LambdaList' + req.body.arn)
26 | .then((data) => json(data))
27 | .then((data) => (res.locals.lambdaNames = data))
28 | .then((lambdaNames) => {
29 | console.log(res.locals.lambdaNames);
30 | })
31 | .then((data) => next())
32 | .catch((err) => {
33 | console.log('cache miss');
34 | //executes call if no valid redis data is found
35 | const fullfunc = async () => {
36 | //create new instance of LambdaClient with user's region and credentials
37 | const lambdaClient = new LambdaClient({
38 | region: 'us-east-1',
39 | credentials: res.locals.credentials,
40 | });
41 |
42 | //retrieve names of lambda functions (max 10)
43 | try {
44 | const allFuncs = await lambdaClient.send(
45 | new ListFunctionsCommand({
46 | MaxItems: 10,
47 | FunctionVersion: 'ALL',
48 | })
49 | );
50 |
51 | //create a list of all functions and save it to redis and res.locals
52 | const funcList = allFuncs.Functions.map((func) => func.FunctionName);
53 | await redisClient.set(
54 | 'LambdaList' + req.body.arn,
55 | JSON.stringify(funcList),
56 | 'EX',
57 | 60 * 60
58 | );
59 | console.log('full montey');
60 | res.locals.lambdaNames = funcList;
61 |
62 | return next();
63 | } catch (err) {
64 | console.log('Error in listLambdas', err);
65 | return next(err);
66 | }
67 | };
68 | fullfunc();
69 | });
70 | };
71 |
72 | module.exports = listLambdasController;
73 |
--------------------------------------------------------------------------------
/server/controllers/tracesController.js:
--------------------------------------------------------------------------------
1 | const Redis = require('redis');
2 | // const getOrSetCache = require('../redis');
3 | const redisClient = Redis.createClient();
4 | (async () => {
5 | await redisClient.connect().catch((err) => {
6 | console.log('Redis Connect Error: ' + err.message);
7 | });
8 | })();
9 |
10 | const {
11 | XRayClient,
12 | GetTraceSummariesCommand,
13 | } = require('@aws-sdk/client-xray');
14 | const tracesController = {};
15 |
16 | tracesController.getTraces = async (req, res, next) => {
17 | console.log('in tracescontroller getTraces');
18 |
19 | redisClient
20 | .get('LambdaTraces' + req.body.arn)
21 | .then((data) => JSON.parse(data))
22 | .then((data) => {
23 | console.log(data);
24 | if (data !== null) {
25 | res.locals.traces = data;
26 | return next();
27 | } else {
28 | console.log('cache miss');
29 | const fullfunc = async () => {
30 | const { lambdaNames } = res.locals;
31 |
32 | console.log(lambdaNames);
33 | // Set the AWS Region and X-Ray client object
34 | const REGION = 'us-east-1';
35 | const xrayClient = new XRayClient({
36 | region: REGION,
37 | credentials: res.locals.credentials,
38 | });
39 | const dataPromise = [];
40 | const dataArray = [];
41 | lambdaNames.forEach((lambda) => {
42 | console.log(lambda);
43 | const name = `service("${lambda}")`;
44 | const params = {
45 | StartTime: new Date(Date.now() - 1000 * 60 * 60 * 10), //past 10 hours
46 | EndTime: new Date(),
47 | Sampling: false,
48 | FilterExpression: name,
49 | // NextToken: nextToken,
50 | };
51 | try {
52 | dataPromise.push(
53 | xrayClient
54 | .send(new GetTraceSummariesCommand(params))
55 | .then((data) => {
56 | dataArray.push({
57 | name: lambda,
58 | summary: data.TraceSummaries,
59 | });
60 | })
61 | );
62 | } catch (err) {
63 | console.error(err);
64 | }
65 | });
66 | await Promise.all(dataPromise);
67 | res.locals.traces = dataArray;
68 | console.log('full montey');
69 | await redisClient.set(
70 | 'LambdaTraces' + req.body.arn,
71 | JSON.stringify(res.locals.traces),
72 | 'EX',
73 | 60 * 60
74 | );
75 | console.log('dataArray', dataArray);
76 | return next();
77 | };
78 | fullfunc();
79 | }
80 | });
81 | };
82 |
83 | module.exports = tracesController;
84 |
--------------------------------------------------------------------------------
/server/models/dbPool.js:
--------------------------------------------------------------------------------
1 | const { Pool } = require('pg');
2 | const dotenv = require('dotenv').config();
3 |
4 | //our address is stored in an env file
5 | const { PG_URI } = process.env;
6 |
7 | //create a new pool using the connection string above that brings us to our database
8 | //
9 | const pool = new Pool({
10 | connectionString: PG_URI,
11 | idleTimeoutMillis: 5000,
12 | connectionTimeoutMillis: 7500,
13 | end: function () {
14 | return pool.end();
15 | },
16 | connect: function () {
17 | return pool.connect();
18 | },
19 | });
20 |
21 | //Now we need to export an object that has a query method in it, that we'll name query
22 | //this method will return the invocation of the pool.query() after logging the query
23 | //***This will be required in our controllers to gain access to pur database***
24 | module.exports = {
25 | query: (text, params, callback) => {
26 | console.log('Executed Query: ', text);
27 | return pool.query(text, params, callback);
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/server/redis.js:
--------------------------------------------------------------------------------
1 | // const Redis = require('redis'); //for prod: Redis.createClient({url : xxxx})
2 | // const redisClient = Redis.createClient();
3 | // (async () => {
4 | // await redisClient.connect().catch((err) => {
5 | // console.log('Redis Connect Error: ' + err.message);
6 | // });
7 | // })();
8 | // // const DEFAULT_EXPIRATION_REDIS = 3600;
9 | // // incontroller = () => {
10 | // // //check if Redis Data present before making an API call
11 | // // redisClient.get('metrics', (error, metrics) => {
12 | // // if (error) console.error(error);
13 | // // if (metrics) {
14 | // // res.locals.xxxxx = JSON.parse(metrics);
15 | // // } else {
16 | // // //API CALL HERE
17 | // // }
18 | // // });
19 | // // //save data after call
20 | // // redisClient.setex('metrics', DEFAULT_EXPIRATION_REDIS, JSON.stringify(data)); //replace data by the returned value of the API call
21 | // // };
22 |
23 | // async function getOrSetCache(key, expiration, cb) {
24 | // console.log('in redis client');
25 | // return new Promise((resolve, reject) => {
26 | // redisClient.get(key, async (error, data) => {
27 | // if (error) {
28 | // console.log('error');
29 | // return reject(error);
30 | // }
31 | // if (data) {
32 | // console.log('loading2');
33 | // return resolve(JSON.parse(data));
34 | // }
35 | // console.log('loading');
36 | // const freshData = await cb();
37 | // redisClient.set(key, JSON.stringify(freshData), EX, expiration);
38 | // resolve(freshData);
39 | // });
40 | // });
41 | // }
42 |
43 | // // incotroller2 = async () => {
44 | // // const metrics = await getOrSetCache('querystring', async () => {
45 | // // //put API CALL HERE
46 | // // });
47 | // // };
48 |
49 | // module.exports = getOrSetCache;
50 |
--------------------------------------------------------------------------------
/server/routes/api.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const cookieParser = require('cookie-parser');
3 |
4 | const dbController = require('../controllers/dbControllers');
5 | const authController = require('../controllers/authControllers');
6 | const encryptionController = require('../controllers/encryptionController');
7 | const cookieController = require('../controllers/cookieControllers.js');
8 |
9 | const router = express.Router();
10 |
11 | router.use(cookieParser());
12 |
13 | router.get(
14 | '/',
15 | cookieController.authenticateCookie,
16 | dbController.getUser,
17 | (req, res) => {
18 | res.status(200).json(res.locals.data.rows);
19 | }
20 | );
21 |
22 | // Create new user
23 | router.post(
24 | '/newUser',
25 | authController.validator,
26 | encryptionController.hashPW,
27 | dbController.addUser,
28 | dbController.getUser,
29 | (req, res) => {
30 | res.status(200).json(res.locals.data.rows[0]);
31 | }
32 | );
33 |
34 | // Delete user
35 | router.delete(
36 | '/delete/:user_name',
37 | authController.verifyUN_Pass,
38 | dbController.deleteUser,
39 | (req, res) => res.status(200).json({})
40 | );
41 |
42 | // Updates ARN and region
43 | router.patch(
44 | '/edit/:user_name',
45 | authController.verifyUN_Pass,
46 | dbController.editUser,
47 | (req, res) => {
48 | console.log(res.locals.user);
49 | res.status(200).json(res.locals.user);
50 | }
51 | );
52 |
53 | // Sign In
54 | router.post(
55 | '/:user_name',
56 | authController.verifyUN_Pass,
57 | cookieController.setCookie,
58 | dbController.getUser,
59 | (req, res) => {
60 | res.status(200).json(res.locals.user);
61 | }
62 | );
63 |
64 | // Delete JWT
65 | router.get('/logout', cookieController.deleteCookie, (req, res) => {
66 | res.status(200).json();
67 | });
68 |
69 | module.exports = router;
70 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 |
2 | const express = require('express');
3 | const cookieParser = require('cookie-parser');
4 | const path = require('path');
5 | const cors = require('cors');
6 | const credentialController = require('./controllers/credentialController');
7 | const listLambdasController = require('./controllers/listLambdasController');
8 | const MetricsController = require('./controllers/MetricsController.js');
9 | const lambdaLogsController = require('./controllers/lambdaLogsController');
10 | const tracesController = require('./controllers/tracesController.js');
11 | const jwt = require('jsonwebtoken');
12 | const fileController = require("./controllers/fileController.js");
13 |
14 | const app = express();
15 |
16 | const apiRouter = require("./routes/api");
17 |
18 | const PORT = process.env.PORT || 3000;
19 |
20 | /**
21 | * Automatically parse urlencoded body content and form data from incoming requests and place it
22 | * in req.body
23 | */
24 |
25 | app.use(express.json());
26 | app.use(express.urlencoded({ extended: true }));
27 | app.use(
28 | cors({
29 | origin: "http://localhost:5173",
30 | credentials: true,
31 | })
32 | );
33 | app.use(cookieParser());
34 |
35 | app.use(express.static(path.resolve(__dirname, "../src")));
36 |
37 | //Define Route handlers Here
38 | //---------------
39 | app.use("/api", apiRouter);
40 |
41 | app.post(
42 | "/getLambdaNames",
43 | credentialController.getCredentials,
44 | listLambdasController.getLambdas,
45 | // lambdaLogsController.getLambdaLogs,
46 | // rdsMetricsController.getRDSCPUUtilizationMetrics,
47 | (req, res) => {
48 | return res.status(200).json(res.locals.lambdaNames);
49 | }
50 | );
51 |
52 | //Return a JSON object containing all lambda function traces.
53 | //The route first requests fresh credentials from the user's AWS accounts. Then collects lambda function names and uses them to request AWS XRAY trace data for all functions
54 | // Redis implemented
55 | app.post(
56 | "/getTraces",
57 | credentialController.getCredentials,
58 | listLambdasController.getLambdas,
59 | tracesController.getTraces,
60 | (req, res) => {
61 | return res.status(200).json(res.locals.traces);
62 | }
63 | );
64 |
65 | //Return a JSON object containing all lambda function traces.
66 | //The route first requests fresh credentials from the user's AWS accounts. Then collects lambda function names and uses them to request AWS Cloudwatch log data for all functions
67 | // Redis implemented
68 | app.post(
69 | "/getLambdaLogs",
70 | credentialController.getCredentials,
71 | listLambdasController.getLambdas,
72 | lambdaLogsController.getLambdaLogs,
73 | (req, res) => {
74 | return res.status(200).json(res.locals.functionLogs);
75 | }
76 | );
77 |
78 | //Return a JSON object containing all lambda function traces.
79 | //The route first requests fresh credentials from the user's AWS accounts. Then collects lambda function names and uses them to request AWS Cloudwatch Metrics data for all functions
80 | // Redis implemented
81 | app.post(
82 | "/getLambdaMetrics",
83 | credentialController.getCredentials,
84 | listLambdasController.getLambdas,
85 | MetricsController.getMetrics,
86 | (req, res) => {
87 | return res.status(200).json(res.locals.getLambdaMetrics);
88 | }
89 | );
90 |
91 | app.post("/writeToFile", fileController.writeToFile, (req, res) => {
92 | return res.status(200).json(res.locals.writtenServices);
93 | });
94 |
95 | app.delete('/deleteRedis', credentialController.deleteRedis, (req, res) => {
96 | return res.status(200).json('redis cache cleared');
97 | });
98 |
99 |
100 | //Catch All Route Handler for any requests to an unkown route
101 | //----------------
102 | app.use((req, res) => res.status(404).send("This page cannot be found..."));
103 |
104 | //Default Express Error Handler here
105 | //____________
106 | app.use((err, req, res, next) => {
107 | const defaultErr = {
108 | log: `Express error handler caught unknown middleware error: ${err}`,
109 | status: 500,
110 | message: { err: "An error occurred" },
111 | };
112 | const errorObj = Object.assign({}, defaultErr, err);
113 | console.log(errorObj.log);
114 | return res.status(errorObj.status).json(errorObj.message);
115 | });
116 |
117 | //Start Server
118 | //__________
119 |
120 | app.listen(PORT, () => {
121 | console.log(`Listening on port ${PORT}...`);
122 | });
123 |
124 | module.exports = app;
125 |
--------------------------------------------------------------------------------
/src/App.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from "react";
2 | import { Route, Routes } from "react-router-dom";
3 | import DashboardContainer from "./containers/DashboardContainer.jsx";
4 | import LandingPageContainer from "./containers/LandingPageContainer.jsx";
5 | import Auth from "./components/Auth.jsx";
6 | import Docs from "./components/Docs.jsx";
7 | import SettingsContainer from "./containers/SettingsContainer.jsx";
8 | import Navbar from "./components/Navbar.jsx";
9 | import "./styles/application.scss";
10 |
11 | const App = (props) => {
12 | const [loggedIn, setLoggedIn] = useState(false);
13 | const [user, setUser] = useState({});
14 |
15 | // Log user in and fetch data if JWT is present
16 | useEffect(() => {
17 | const checkAuth = async () => {
18 | try {
19 | const response = await fetch("/api/", {
20 | credentials: "include",
21 | });
22 | if (response.ok) {
23 | const data = await response.json();
24 | console.log("jwt fetch ok", data[0]);
25 | const { user_name, full_name, email, _id, arn, region } = data[0];
26 | setUser({
27 | full_name: full_name,
28 | user_name: user_name,
29 | email: email,
30 | _id: _id,
31 | arn: arn,
32 | region: region,
33 | });
34 | setLoggedIn(true);
35 | } else {
36 | console.error("JWT app.jsx Unable to authenticate jwt");
37 | setLoggedIn(false);
38 | }
39 | } catch (error) {
40 | console.error("JWT app.jsx", error);
41 | setLoggedIn(false);
42 | }
43 | };
44 | checkAuth();
45 | }, []);
46 |
47 | return (
48 |
49 |
50 |
51 |
52 |
58 | ) : (
59 |
60 | )
61 | }
62 | />
63 |
69 | ) : (
70 |
71 | )
72 | }
73 | />
74 |
80 | ) : (
81 |
82 | )
83 | }
84 | />
85 | } />
86 | } />
87 |
88 |
89 |
90 | );
91 | };
92 |
93 | export default App;
94 |
--------------------------------------------------------------------------------
/src/Chart/BarChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend } from "chart.js";
3 | import { Bar } from "react-chartjs-2";
4 |
5 | ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend);
6 |
7 | //"Duration": duration of the function execution in seconds.
8 | //"ResponseTime": time taken to generate the response in seconds.
9 |
10 | function HorizontalBarChart(props) {
11 | const { msTraces } = props;
12 | const [lambdaNames, setLambdaNames] = useState([]);
13 | const [duration, setDuration] = useState([]);
14 | const [responseTime, setResponseTime] = useState([]);
15 | const [lambdaServices, setLambdaServices] = useState([]);
16 |
17 | //configurations of the bar graph
18 | const options = {
19 | indexAxis: "y",
20 | elements: {
21 | bar: {
22 | borderWidth: 2,
23 | },
24 | },
25 | responsive: true,
26 | maintainAspectRatio: true,
27 | plugins: {
28 | legend: {
29 | position: "bottom",
30 | },
31 | title: {
32 | display: false,
33 | text: "Lambda Latencies",
34 | },
35 | media: [
36 | {
37 | query: "(max-width: 1000px)",
38 | options: {
39 | plugins: {
40 | legend: {
41 | display: false,
42 | },
43 | },
44 | maintainAspectRatio: false,
45 | },
46 | },
47 | ],
48 | },
49 | };
50 |
51 | const data = {
52 | labels: lambdaNames,
53 | datasets: [
54 | {
55 | label: "Duration",
56 | data: duration,
57 | borderColor: "#f75215",
58 | backgroundColor: "#f75215",
59 | },
60 | {
61 | label: "Response Time",
62 | data: responseTime,
63 | borderColor: "#fad6c9",
64 | backgroundColor: "#fad6c9",
65 | },
66 | ],
67 | };
68 |
69 | useEffect(() => {
70 | if (!msTraces || !Array.isArray(msTraces)) {
71 | return console.log("Data incoming, please wait");
72 | }
73 | const tempDuration = [];
74 | const tempResponseTime = [];
75 | const tempLambdaServices = [];
76 | const tempLambdaNames = [];
77 |
78 | msTraces.forEach((traceObj) => {
79 | tempDuration.push(traceObj.duration);
80 | tempResponseTime.push(traceObj.responseTime);
81 | tempLambdaNames.push(traceObj.name);
82 | tempLambdaServices.push(traceObj.serviceIds);
83 | });
84 | //all parts of msTraces needs to be parsed separately to add to the bar chart options
85 | setDuration(tempDuration);
86 | setResponseTime(tempResponseTime);
87 | setLambdaServices(tempLambdaServices);
88 | setLambdaNames(tempLambdaNames);
89 | }, []);
90 |
91 | const width = window.screen.width / 2;
92 | const height = window.screen.height / 2;
93 |
94 | return (
95 | //manually inputting style to inherit sizing of parent div
96 |
97 | );
98 | }
99 | export default HorizontalBarChart;
100 |
--------------------------------------------------------------------------------
/src/Chart/BubbleChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from "react";
2 | import * as d3 from "d3";
3 | import Circle from "./Circle";
4 |
5 | function BubbleChart({ bubbleChartData, handleTogglePanel }) {
6 | const [state, setState] = useState({ bubbleChartData: [] });
7 | useEffect(() => {
8 | if (!bubbleChartData.length) console.log("No data yet, please wait");
9 | setState({ bubbleChartData });
10 | simulation(bubbleChartData);
11 | }, [bubbleChartData]);
12 |
13 | //sets size and relative radius of bubbles in chart
14 | function simulation(bubbleChartData) {
15 | const maxRadius = d3.max(bubbleChartData, (d) => d.count);
16 | const minRadius = d3.min(bubbleChartData, (d) => d.count);
17 | const radiusScale = d3.scaleSqrt().domain([minRadius, maxRadius]).range([10, 110]);
18 |
19 | const ticked = () => setState({ bubbleChartData });
20 | //this force chart animates bubbles to flow toward the middle
21 | d3.forceSimulation()
22 | .nodes(bubbleChartData)
23 | .force("xTowardsTheCenter", d3.forceX(0).strength(0.01))
24 | .force("yTowardsTheCenter", d3.forceY(100).strength(0.01))
25 | .force(
26 | "collide",
27 | d3
28 | .forceCollide((d) => radiusScale(d.count))
29 | .strength(1.5)
30 | .iterations(1)
31 | )
32 | .on("tick", ticked);
33 | }
34 |
35 | //passes the name (circle id) of the bubble selected back to dashboard to select corresponding panel
36 | const handleCircleClick = (circle) => {
37 | handleTogglePanel(circle.name);
38 | };
39 |
40 | //d3 chart boilerplate sizing
41 | const margins = { top: 20, right: 50, bottom: 20, left: 50 };
42 | const svgDimensions = { width: window.screen.width, height: window.screen.height / 2 };
43 |
44 | return (
45 |
46 |
50 |
51 |
52 |
53 | tooltip
54 |
55 |
56 | );
57 | }
58 |
59 | export default BubbleChart;
60 |
--------------------------------------------------------------------------------
/src/Chart/Circle.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from "react";
2 | import * as d3 from "d3";
3 |
4 | //function to render each bubble in the bubble chart
5 | function Circle({ data, onClick }) {
6 | const maxRadius = d3.max(data, (d) => d.count);
7 | const minRadius = d3.min(data, (d) => d.count);
8 | const radiusScale = d3.scaleSqrt().domain([minRadius, maxRadius]).range([20, 120]);
9 |
10 | //returns each circle with properties to render on bubble chart
11 | return data.map((circle, index) => (
12 |
13 | {
22 | d3.select(".bubbleChartTooltip")
23 | .style("visibility", "visible")
24 | .text(circle.name + " (" + circle.count + ")")
25 | .attr("x", e.nativeEvent.offsetX + 10 + "px")
26 | .attr("y", e.nativeEvent.offsetY - 10 + "px");
27 | }}
28 | onMouseOut={() => {
29 | d3.select(".bubbleChartTooltip").style("visibility", "hidden");
30 | }}
31 | onClick={() => onClick(circle)}
32 | />
33 |
42 | {circle.name + " (" + circle.count + ")"}
43 |
44 |
45 | ));
46 | }
47 |
48 | export default Circle;
49 |
--------------------------------------------------------------------------------
/src/Chart/CircleChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useRef, useEffect, useState } from "react";
2 | import BubbleChart from "./BubbleChart";
3 |
4 | //this is the parent element that renders the bubble chart. Circle -> BubbleChart -> CircleChart
5 | const CircleChart = (props) => {
6 | const { msMetrics, handleTogglePanel } = props;
7 | const [bubbleChartData, setBubbleChartData] = useState([]);
8 | //fetch metrics to render in chart
9 | useEffect(() => {
10 | if (!msMetrics || !Array.isArray(msMetrics)) return console.log("no metrics data");
11 |
12 | function bubbleDataParse(metricsArr) {
13 | const bubbleArray = [];
14 |
15 | //parse metrics string from aws-sdk
16 | metricsArr.forEach((obj, i) => {
17 | const functionName = obj.Label.split(" ")[0];
18 | const type = obj.Label.split(" ")[1];
19 | const invocations = obj.Values.reduce((sum, value) => sum + value, 0);
20 |
21 | const result = {
22 | name: functionName,
23 | type: type,
24 | invocations: invocations,
25 | };
26 | if (type === "Invocations") {
27 | bubbleArray.push(result);
28 | }
29 | });
30 | return bubbleArray;
31 | }
32 |
33 | const bubbleData = bubbleDataParse(msMetrics);
34 | //parsing label and value. Data structure of d3 data: [{name: blah, count: blah}]
35 | const graphData = bubbleData.map((obj) => {
36 | return { name: obj.name, count: obj.invocations };
37 | });
38 | setBubbleChartData(graphData);
39 | }, []);
40 |
41 | const width = window.screen.width / 1;
42 | const height = window.screen.height / 1;
43 | //setting some styles here to overwrite predefined styles from d3 chart
44 | return (
45 |
49 |
50 |
51 | );
52 | };
53 |
54 | export default CircleChart;
55 |
--------------------------------------------------------------------------------
/src/Chart/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
154 |
--------------------------------------------------------------------------------
/src/Chart/miserables.json:
--------------------------------------------------------------------------------
1 | {
2 | "nodes": [
3 | { "id": "4773bf7d1aa9b37b", "name": "GET /date" },
4 | { "id": "db2894c84c643164", "name": "request handler - /date" },
5 | { "id": "9a054dc4257fd82b", "name": "middleware - expressInit" },
6 | { "id": "d297cf02aad4bc9b", "name": "middleware - query" },
7 | { "id": "bc8a725770883100", "name": "HTTP POST" },
8 | { "id": "6955e0a59650ee66", "name": "middleware - expressInit" },
9 | { "id": "f7924394cc345ead", "name": "middleware - query" },
10 | { "id": "f7924394cc345ead1", "name": "fakey" },
11 | { "id": "f7924394cc345ead2", "name": "fakey2" },
12 | { "id": "f7924394cc345ead3", "name": "fakey3" },
13 | { "id": "f7924394cc345ead4", "name": "fakey4" },
14 | { "id": "f7924394cc345ead5", "name": "fakey5" },
15 | { "id": "f7924394cc345ead6", "name": "fakey6" },
16 | { "id": "f7924394cc345ead7", "name": "fakey7" },
17 |
18 | { "id": "f7924394cc345ead8", "name": "fakey8" },
19 | { "id": "f7924394cc345ead9", "name": "fakey9" },
20 | { "id": "f7924394cc345ead10", "name": "fakey10" }
21 | ],
22 | "links": [
23 | { "source": "4773bf7d1aa9b37b", "target": "4773bf7d1aa9b37b" },
24 | { "source": "db2894c84c643164", "target": "4773bf7d1aa9b37b" },
25 | { "source": "9a054dc4257fd82b", "target": "4773bf7d1aa9b37b" },
26 | { "source": "d297cf02aad4bc9b", "target": "4773bf7d1aa9b37b" },
27 | { "source": "bc8a725770883100", "target": "bc8a725770883100" },
28 | { "source": "6955e0a59650ee66", "target": "bc8a725770883100" },
29 | { "source": "f7924394cc345ead", "target": "bc8a725770883100" },
30 |
31 | { "source": "f7924394cc345ead1", "target": "bc8a725770883100" },
32 | { "source": "f7924394cc345ead2", "target": "bc8a725770883100" },
33 | { "source": "f7924394cc345ead3", "target": "bc8a725770883100" },
34 | { "source": "f7924394cc345ead4", "target": "bc8a725770883100" },
35 | { "source": "f7924394cc345ead5", "target": "bc8a725770883100" },
36 | { "source": "f7924394cc345ead6", "target": "f7924394cc345ead5" },
37 | { "source": "f7924394cc345ead7", "target": "f7924394cc345ead5" },
38 |
39 | { "source": "f7924394cc345ead8", "target": "f7924394cc345ead7" },
40 | { "source": "f7924394cc345ead9", "target": "f7924394cc345ead7" },
41 | { "source": "f7924394cc345ead10", "target": "f7924394cc345ead7" }
42 | ]
43 | }
44 |
--------------------------------------------------------------------------------
/src/Chart/service.json:
--------------------------------------------------------------------------------
1 | {"nodes":[{"id":"cakerFourFunction0","name":"client"},{"id":"cakerFourFunction1","name":"AWS::Lambda"},{"id":"cakerFourFunction2","name":"AWS::Lambda::Function"},{"id":"cakerThreeFunction0","name":"client"},{"id":"cakerThreeFunction1","name":"AWS::Lambda::Function"},{"id":"cakerThreeFunction2","name":"AWS::Lambda"},{"id":"cakerFirstFunction0","name":"AWS::Lambda::Function"},{"id":"cakerFirstFunction1","name":"client"},{"id":"cakerFirstFunction2","name":"AWS::Lambda"},{"id":"cakerFiveFunction0","name":"AWS::Lambda"},{"id":"cakerFiveFunction1","name":"client"},{"id":"cakerFiveFunction2","name":"AWS::Lambda::Function"}],"links":[{"source":"cakerFourFunction0","target":"cakerFourFunction0"},{"source":"cakerFourFunction0","target":"cakerFourFunction1"},{"source":"cakerFourFunction0","target":"cakerFourFunction2"},{"source":"cakerThreeFunction0","target":"cakerThreeFunction0"},{"source":"cakerThreeFunction0","target":"cakerThreeFunction1"},{"source":"cakerThreeFunction0","target":"cakerThreeFunction2"},{"source":"cakerFirstFunction0","target":"cakerFirstFunction0"},{"source":"cakerFirstFunction0","target":"cakerFirstFunction1"},{"source":"cakerFirstFunction0","target":"cakerFirstFunction2"},{"source":"cakerFiveFunction0","target":"cakerFiveFunction0"},{"source":"cakerFiveFunction0","target":"cakerFiveFunction1"},{"source":"cakerFiveFunction0","target":"cakerFiveFunction2"}]}
--------------------------------------------------------------------------------
/src/assets/desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/desktop.png
--------------------------------------------------------------------------------
/src/assets/docs-gif-01.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/docs-gif-01.gif
--------------------------------------------------------------------------------
/src/assets/docs-gif-02.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/docs-gif-02.gif
--------------------------------------------------------------------------------
/src/assets/docs-gif-03.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/docs-gif-03.gif
--------------------------------------------------------------------------------
/src/assets/enteryourarn.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/enteryourarn.gif
--------------------------------------------------------------------------------
/src/assets/getyourstack.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/getyourstack.gif
--------------------------------------------------------------------------------
/src/assets/logo-text.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/logo-text.png
--------------------------------------------------------------------------------
/src/assets/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/logo.png
--------------------------------------------------------------------------------
/src/assets/logowhite.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/logowhite.png
--------------------------------------------------------------------------------
/src/assets/mascot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/mascot.png
--------------------------------------------------------------------------------
/src/assets/metrics.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/metrics.gif
--------------------------------------------------------------------------------
/src/assets/mobile-logs.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/mobile-logs.png
--------------------------------------------------------------------------------
/src/assets/mobile-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/mobile-map.png
--------------------------------------------------------------------------------
/src/assets/mobile-metrics.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/mobile-metrics.png
--------------------------------------------------------------------------------
/src/assets/readme-bar.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/readme-bar.gif
--------------------------------------------------------------------------------
/src/assets/readme-bubble.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/readme-bubble.gif
--------------------------------------------------------------------------------
/src/assets/readme-node.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/readme-node.gif
--------------------------------------------------------------------------------
/src/assets/signup.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/signup.gif
--------------------------------------------------------------------------------
/src/assets/signyouup.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lambdawg/50cc8c190f40b4e301944ce723c625ee7a08836b/src/assets/signyouup.gif
--------------------------------------------------------------------------------
/src/components/Animation.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | const mascot = "src/assets/mascot_head.svg";
3 |
4 | const BouncingDotsLoader = (props) => {
5 | return (
6 |
7 |
8 |
{" "}
9 |
10 |
11 |
{" "}
12 |
13 |
14 |
{" "}
15 |
16 |
17 | );
18 | };
19 |
20 | export default BouncingDotsLoader;
21 |
--------------------------------------------------------------------------------
/src/components/Auth.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 | import SignUpForm from './SignUpForm.jsx';
3 | import SignInForm from './SignInForm.jsx';
4 | import { useNavigate } from 'react-router-dom';
5 |
6 | const Auth = (props) => {
7 | const mascot = 'src/assets/logo.png'
8 | const [formType, setFormType] = useState("signIn");
9 | const { loggedIn, setLoggedIn, user, setUser } = props
10 | const navigate = useNavigate();
11 |
12 |
13 | const toggleFormType = () => {
14 | setFormType(prevFormType => prevFormType === 'signIn' ? 'signUp' : 'signIn');
15 | };
16 |
17 | const handleSignUpSuccess = () => {
18 | setLoggedIn(true);
19 | navigate('/settings');
20 | }
21 |
22 | return(
23 |
24 |
25 |
26 | {
27 | formType === 'signIn' ?
28 | :
35 |
43 | }
44 |
45 |
46 | )
47 | }
48 |
49 | export default Auth;
--------------------------------------------------------------------------------
/src/components/ChartDropDown.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { RiBarChartHorizontalFill } from "react-icons/ri";
3 | import { GiBubbles } from "react-icons/gi";
4 | import { GrNodes } from "react-icons/gr";
5 |
6 | //renders each chart icon in the dropdown
7 | function DropdownItem(props) {
8 | const { onClick, chartIcons } = props;
9 | return (
10 |
11 | {chartIcons}
12 |
13 | );
14 | }
15 | //renders the dropdown menu
16 | function Dropdown(props) {
17 | const { setActiveChart } = props;
18 | const [selectedChart, setSelectedChart] = useState(null);
19 | const [dropIsOpen, setDropIsOpen] = useState(false);
20 |
21 | //options and corresponding icons
22 | const options = ["Bar", "Bubble", "Node"];
23 | const chartIcons = [ , , ];
24 |
25 | //icons don't render properly in JSX, so using a func to render icon based on selectedChart
26 | const showIcon = (optionsArr, iconsArr, option) => {
27 | return chartIcons[optionsArr.indexOf(option)];
28 | };
29 |
30 | //sets SelectedChart with the corresponding chart option for the icon clicked. setActiveChart to send it to DiagramContainer for chart render
31 | const handleOptionClick = (chart) => {
32 | console.log("you clicked " + chart);
33 | setSelectedChart(chart);
34 | setActiveChart(chart);
35 | setDropIsOpen(false);
36 | };
37 |
38 | return (
39 |
40 |
setDropIsOpen(!dropIsOpen)}>
41 | {selectedChart ? showIcon(options, chartIcons, selectedChart) : "View"}
42 |
43 |
44 | {options.map((chart, i) => (
45 | handleOptionClick(chart)}
50 | />
51 | ))}
52 |
53 |
54 | );
55 | }
56 | export default Dropdown;
57 |
--------------------------------------------------------------------------------
/src/components/DataWindow.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Log from './Log.jsx';
3 | import { Scrollbar } from 'react-scrollbars-custom';
4 | import BouncingDotsLoader from "../components/Animation";
5 |
6 | const DataWindow = (props) => {
7 | const { dataWindowFullScreen, msLogs } = props;
8 | const [logData, setLogData] = useState({});
9 |
10 | // Parse and sort logs
11 | useEffect(() => {
12 | if (Array.isArray(msLogs)) {
13 | const parsedLogs = {};
14 |
15 | msLogs.forEach((log) => {
16 | if (!parsedLogs[log.FunctionName]) {
17 | parsedLogs[log.FunctionName] = [];
18 | }
19 |
20 | log.logs.forEach((stamp) => {
21 | parsedLogs[log.FunctionName].push({
22 | timestamp: stamp.timestamp,
23 | message: stamp.message,
24 | });
25 | });
26 | });
27 | setLogData(parsedLogs);
28 | }
29 | }, [msLogs]);
30 |
31 | return (
32 |
36 |
37 |
38 | {Object.keys(logData).length > 0 ? (
39 | Object.entries(logData).map(([functionName, logs]) => (
40 |
41 | ))
42 | ) :
43 |
44 | Loading...
45 |
46 |
47 | }
48 |
49 |
50 |
51 | );
52 |
53 | };
54 |
55 | export default DataWindow;
56 |
57 |
--------------------------------------------------------------------------------
/src/components/Docs.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import logo from '../assets/logo.png';
3 | import setup1 from '../assets/docs-gif-01.gif';
4 | import setup2 from '../assets/docs-gif-02.gif';
5 | import setup3 from '../assets/docs-gif-03.gif';
6 | import signup from '../assets/signyouup.gif';
7 | import arn from '../assets/enteryourarn.gif';
8 | import stack from '../assets/getyourstack.gif';
9 |
10 |
11 |
12 | const Docs = () => {
13 | return (
14 |
15 |
16 |
17 |
18 | Lambdawg is your go-to AWS Lambda Function visualization UI.
19 |
20 |
21 | Simply connect your AWS account in the Settings page above, and boom
22 | boom bam - you'll get your trace, metrics, and log data in one simple,
23 | streamlined interface.
24 |
25 |
26 |
Set up & Installation
27 |
28 |
29 |
30 |
Connect to your AWS account
31 |
32 |
33 |
34 |
35 |
Copy and paste your ARN key
36 |
37 |
38 |
39 |
40 |
Select your region
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 | Check us out on{' '}
49 | Github !
50 |
51 |
52 |
53 |
54 | And if you're a developer...{' '}
55 |
56 | click here to
57 | learn more about Lambdawg!
58 |
59 |
60 |
61 | );
62 | };
63 |
64 | export default Docs;
65 |
--------------------------------------------------------------------------------
/src/components/LamdaButton.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { useState, useEffect, useRef } from 'react';
3 |
4 | const LamdaButton = (props) => {
5 | const { name, msMetrics, user, msLogs, setMsLogs } = props;
6 | const [sortedMetrics, setSortedMetrics] = useState({});
7 | const [isPanelOpen, setIsPanelOpen] = useState(false);
8 |
9 | // Parse metrics for specific func name
10 | useEffect(() => {
11 |
12 | if (!msMetrics || !Array.isArray(msMetrics)) return;
13 |
14 | const tempSortedMetrics = {};
15 | let invocationsSum = 0;
16 | let errorsSum = 0;
17 |
18 | msMetrics.forEach((metricsObj) => {
19 | const microName = metricsObj.Label.split(' ')[0];
20 | const label = metricsObj.Label.split(' ')[1];
21 |
22 | const timeStamps = [];
23 |
24 | if (label === 'Invocations') {
25 | invocationsSum = metricsObj.Values.reduce((a, b) => {return a + b} ,0);
26 | } else if (label === 'Errors') {
27 | metricsObj.Values.forEach((num, i) => {
28 | errorsSum += num;
29 | if (num > 0) {
30 | timeStamps.push(
31 | {metricsObj.Timestamps[i]}
32 | );
33 |
34 | } else {
35 | timeStamps.push(
36 | {metricsObj.Timestamps[i]}
37 | );
38 | }
39 | });
40 |
41 | tempSortedMetrics[microName] = {
42 | invocationsSum,
43 | errorsSum,
44 | timeStamps
45 | };
46 | }
47 | });
48 |
49 | setSortedMetrics(tempSortedMetrics);
50 | }, [msMetrics]);
51 |
52 | // Fetch all Logs
53 | useEffect(() => {
54 | if (msMetrics) {
55 |
56 | const fetchLogs = async () => {
57 | try {
58 | const response = await fetch('/aws/getLambdaLogs', {
59 | method: 'POST',
60 | headers: { 'Content-Type': 'application/json' },
61 | body: JSON.stringify({
62 | arn: user.arn,
63 | user_name: user.user_name
64 | }),
65 | muteHttpExceptions: true,
66 | });
67 | const data = await response.json();
68 | setMsLogs(data);
69 | } catch (error) {
70 | console.log('error fetching logs', error);
71 | }
72 | };
73 | fetchLogs();
74 | }
75 | }, [sortedMetrics]);
76 |
77 | // Populate logs in data window
78 | const togglePanel = () => {
79 | setIsPanelOpen(!isPanelOpen);
80 |
81 | const logElement = document.getElementById(`${name}log`);
82 | if (logElement) {
83 | logElement.scrollIntoView();
84 | }
85 | }
86 |
87 | return (
88 |
89 | {sortedMetrics[name] && (
90 |
91 |
96 | {`${name}`}
97 |
98 |
99 |
103 |
104 | Invocations: {sortedMetrics[name].invocationsSum}
105 | Errors: {sortedMetrics[name].errorsSum}
106 | TimeStamps: {sortedMetrics[name].timeStamps}
107 |
108 |
109 |
110 | )}
111 |
112 | );
113 |
114 | }
115 |
116 | export default LamdaButton;
117 |
--------------------------------------------------------------------------------
/src/components/Log.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React from 'react';
3 |
4 | const Log = (props) => {
5 | const { logs, functionName } = props;
6 |
7 | return (
8 |
9 |
{functionName}
10 |
20 |
21 | );
22 | }
23 |
24 | export default Log;
25 |
--------------------------------------------------------------------------------
/src/components/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 | import { Link } from 'react-router-dom';
3 | import { useNavigate } from 'react-router-dom';
4 | import Switch from '@mui/material/Switch';
5 |
6 | const Navbar = (props) => {
7 | const { loggedIn, setLoggedIn, user } = props;
8 | const [navChecked, setNavChecked] = useState(false);
9 |
10 | const docsLink = '/docs';
11 | const dashLink = '/dashboard';
12 | const settingsLink = './settings';
13 | const authLink = './auth';
14 | const homeLink = '/';
15 | const mascot = 'src/assets/logowhite.png';
16 | const logoText = 'src/assets/logo-text.png';
17 |
18 | const navigate = useNavigate();
19 | const handleLinkClick = () => {
20 | setNavChecked(false);
21 | };
22 |
23 | // create a tracker for dark mode in state
24 | const [mode, setMode] = useState('light');
25 | // If state is 'dark' transition to 'light' and vice-versa
26 | //the function creates a progressive transition to dark or light
27 | const darkMode = (mode = 'dark') => {
28 | let transition;
29 | mode === 'dark' ? (transition = 1) : (transition = 0);
30 | const htmlElement = document.querySelector('html');
31 |
32 | if (!htmlElement) {
33 | console.error('Unable to find HTML element.');
34 | return;
35 | }
36 |
37 | const applyFilter = () => {
38 | htmlElement.style.filter = `invert(${transition})`;
39 | mode === 'dark'
40 | ? (() => {
41 | if (transition >= 0) {
42 | transition -= 0.1;
43 | setTimeout(applyFilter, 30);
44 | }
45 | })()
46 | : (() => {
47 | if (transition <= 1) {
48 | transition += 0.1;
49 | setTimeout(applyFilter, 30);
50 | }
51 | })();
52 | };
53 |
54 | setTimeout(applyFilter, 30);
55 | };
56 |
57 | //handle state and triggest transition on click
58 | const darkClick = () => {
59 | setMode(mode === 'dark' ? 'light' : 'dark');
60 | darkMode(mode);
61 | };
62 |
63 | const handleLinkClickAuth = async () => {
64 | if (loggedIn) {
65 | try {
66 | const response = await fetch('/api/logout', {
67 | credentials: 'include',
68 | });
69 | if (response.ok) {
70 | setLoggedIn(false);
71 | navigate('/auth');
72 | }
73 | else console.log('logout failed');
74 | }
75 | catch (error) {
76 | console.log('catch block in logout');
77 | }
78 | }
79 | setNavChecked(false);
80 | };
81 |
82 | const label = { inputProps: { 'aria-label': 'Switch demo' } };
83 |
84 | return (
85 |
86 |
setNavChecked(!navChecked)}
91 | />
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 | {mode === 'light' ? 'Dark Mode' : 'Light Mode'}
115 |
116 |
117 |
118 |
119 |
120 | Documentation
121 |
122 | {user.arn && (
123 |
124 | Dashboard
125 |
126 | )}
127 |
128 | Settings
129 |
130 |
131 | {loggedIn ? 'Log out' : 'Log in'}
132 |
133 |
134 |
135 | );
136 | };
137 |
138 | export default Navbar;
139 |
--------------------------------------------------------------------------------
/src/components/Panel.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState, useMemo } from 'react';
2 | import LamdaButton from '../components/LamdaButton.jsx';
3 | import Refresh from '../components/Refresh.jsx';
4 |
5 | const Panel = (props) => {
6 |
7 | const { panelFullScreen, msNames, setMsNames, msMetrics, user, setUser, msLogs, setMsLogs, refreshRedis, setRefreshRedis } = props;
8 | const [names, setNames] = useState([]);
9 |
10 | // Populate metrics buttons
11 | useEffect(()=>{
12 | if (msNames && msMetrics){
13 | const namesArr = []
14 | let i = 0;
15 |
16 | msNames.forEach((name) => {
17 | i++;
18 | namesArr.push(
19 |
28 | )
29 | })
30 | setNames(namesArr)
31 | }
32 | }, [msMetrics, msNames])
33 |
34 |
35 | return(
36 |
41 |
42 |
50 |
51 |
52 | {(names.length > 0)?
53 | names :
54 | Loading... }
55 |
56 |
57 |
58 | )
59 | }
60 | export default Panel;
61 |
62 |
--------------------------------------------------------------------------------
/src/components/Refresh.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { useNavigate } from 'react-router-dom';
3 |
4 | const Refresh = (props) => {
5 | const { setRefreshRedis, user } = props;
6 | const navigate = useNavigate()
7 |
8 | const refreshMetrics = async () => {
9 | setRefreshRedis(false);
10 | try{
11 | const response = await fetch('/aws/deleteRedis', {
12 | method: 'DELETE',
13 | header: {'content-type': 'application/json'},
14 | body: JSON.stringify({
15 | user_name: user.user_name
16 | }),
17 | muteHttpExceptions: true
18 | })
19 | if (response.ok){
20 | setRefreshRedis(true);
21 | }
22 | else {
23 | console.log('Unable to refresh cached data')
24 | }
25 | }
26 | catch(error){
27 | console.log('error reaching redis for refesh: ', error)
28 | }
29 | }
30 |
31 | return(
32 |
36 | REFRESH METRICS CACHE
37 |
38 | )
39 | }
40 |
41 | export default Refresh;
--------------------------------------------------------------------------------
/src/components/SettingsForm.jsx:
--------------------------------------------------------------------------------
1 |
2 | import React, { useState } from 'react';
3 | import { Link, useNavigate } from "react-router-dom";
4 |
5 | const Settings = (props) => {
6 | const { user, setUser } = props;
7 | const [formData, setFormData] = useState({
8 | password_: '',
9 | arn: '',
10 | region: '',
11 | });
12 |
13 | const navigate = useNavigate();
14 |
15 | const handleInputChange = (event) => {
16 | const { name, value } = event.target;
17 | setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
18 | };
19 |
20 | const handleInvalidArn = () => {
21 | console.log('not a valid arn')
22 | const arnInput = document.getElementById('arnInputField');
23 | arnInput.style.border = '2px solid red';
24 | }
25 |
26 | const handleAuthFail = () => {
27 | const pwInput = document.getElementById('arnPasswordField');
28 | pwInput.style.border = '2px solid red';
29 | setFormData((prevFormData) => ({ ...prevFormData, password_: '' }));
30 | }
31 |
32 | // will update user in db and state
33 | const handleSubmit = async (event) => {
34 | event.preventDefault();
35 | if (formData.arn.substring(0, 12) !== 'arn:aws:iam:') return handleInvalidArn();
36 |
37 | const arnFormData = {
38 | full_name: user.full_name,
39 | user_name: user.user_name,
40 | email: user.email,
41 | password_: formData.password_,
42 | _id: user._id,
43 | arn: formData.arn,
44 | region: formData.aws_region,
45 | };
46 |
47 | try{
48 | const response = await fetch(`/api/edit/${user.user_name}`, {
49 | method: 'PATCH',
50 | credentials: 'include',
51 | headers: {'content-type': 'application/json'},
52 | body: JSON.stringify([arnFormData])
53 | })
54 |
55 | if (response.ok){
56 | const data = await response.json();
57 | navigate('/dashboard');
58 | }
59 | else {
60 | handleAuthFail();
61 | console.log('Unable to patch arn from settings')
62 | }
63 |
64 | }
65 | catch(error){
66 | console.log('Something went wrong in settings patch req')
67 |
68 | }
69 | // setUser({ user_name: user.user_name, arn: formData.arn, region: formData.aws_region })
70 | setUser((prevUser) => ({ ...prevUser, arn: formData.arn, region: formData.aws_region }));
71 | }
72 |
73 | return(
74 |
75 |
76 | Step 1:
77 |
80 |
83 | Connect your AWS account
84 |
85 |
86 | Step 2: Paste your ARN key below
87 |
88 |
89 |
132 |
133 |
136 | Read the Docs
137 |
138 |
139 |
140 |
144 | Get my metrics
145 |
146 |
147 |
148 |
149 |
150 | )
151 |
152 | }
153 | export default Settings;
154 |
155 |
156 |
157 |
158 |
159 |
--------------------------------------------------------------------------------
/src/components/SignInForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect } from 'react';
2 |
3 | const SignInForm = (props) => {
4 | const [formData, setFormData] = useState({
5 | user_name: '',
6 | password_: ''
7 | });
8 | const { setLoggedIn, user, setUser } = props;
9 |
10 | const handleInputChange = (event) => {
11 | const { name, value } = event.target;
12 | setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
13 | };
14 |
15 | // will add user data to state
16 | const handleSubmit = async (event) => {
17 | event.preventDefault();
18 | const signInFormData = {
19 | user_name: formData.user_name,
20 | password_: formData.password_,
21 | };
22 |
23 | try {
24 | const response = await fetch(`/api/${formData.user_name}`, {
25 | method: 'POST',
26 | credentials: 'include',
27 | headers: { 'content-type': 'application/json' },
28 | body: JSON.stringify([signInFormData]),
29 | });
30 |
31 | if (response.ok) {
32 | const data = await response.json();
33 | const { user_name, full_name, email, _id, arn, region } = data
34 | setUser({
35 | full_name: full_name,
36 | user_name: user_name,
37 | email: email,
38 | _id: _id,
39 | arn: arn,
40 | region: region
41 | })
42 | setLoggedIn(true);
43 | }
44 | else {
45 | console.log('Invalid password');
46 | const passwordInput = document.getElementById('password_');
47 | passwordInput.style.border = '2px solid red';
48 | passwordInput.value = '';
49 | }
50 | }
51 | catch (error) {
52 | console.error('error signing in: ', error);
53 | }
54 | };
55 |
56 | useEffect(() => {
57 | console.log('user has been updated in state')
58 | }, [user])
59 |
60 | return (
61 |
62 |
SIGN IN
63 |
64 |
105 |
106 | );
107 | };
108 |
109 | export default SignInForm;
110 |
--------------------------------------------------------------------------------
/src/components/SignUpForm.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from 'react';
2 |
3 | const signUpForm = (props) => {
4 |
5 | const [formData, setFormData] = useState({
6 | full_name: '',
7 | user_name: '',
8 | email: '',
9 | password_: '',
10 | confirmPassword: ''
11 | });
12 | const { toggleFormType, setUser, onSignUpSuccess } = props
13 |
14 | const handleInputChange = (event) => {
15 | const { name, value } = event.target;
16 | setFormData((prevFormData) => ({ ...prevFormData, [name]: value }));
17 | };
18 |
19 | const handleSubmit = async (event) => {
20 | event.preventDefault();
21 | const signUpFormData = {
22 | full_name: formData.full_name,
23 | user_name: formData.user_name,
24 | email: formData.email,
25 | password_: formData.password_
26 | }
27 | if (formData.password_ != formData.confirmPassword) return handleMismatchedPasswords();
28 | if (formData.password_ == formData.confirmPassword) console.log('signing up....')
29 |
30 | try {
31 | const response = await fetch('/api/newUser', {
32 | method: 'POST',
33 | headers: { 'content-type': 'application/json' },
34 | body: JSON.stringify([signUpFormData]),
35 | });
36 |
37 | if (response.ok) {
38 | const data = await response.json();
39 | const { user_name, full_name, email, _id } = data
40 | console.log(data, 'from signup')
41 | setUser({
42 | full_name: full_name,
43 | user_name: user_name,
44 | email: email,
45 | _id: _id
46 | })
47 | onSignUpSuccess();
48 | }
49 | else {
50 | console.log('Sign up failed');
51 | }
52 | }
53 | catch (error) {
54 | console.error(error);
55 | console.log('Unable to sign-up at this time: ');
56 | }
57 | };
58 |
59 | const handleMismatchedPasswords = () => {
60 | console.log('passwords do not match')
61 | const passwordInput = document.getElementById('confirmPassword');
62 | passwordInput.style.border = '2px solid red';
63 | };
64 |
65 |
66 |
67 | return(
68 |
69 |
70 |
SIGN UP
71 |
72 |
99 |
100 |
101 | )
102 | }
103 |
104 |
105 | export default signUpForm;
106 |
107 |
108 |
109 |
110 |
--------------------------------------------------------------------------------
/src/components/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 |
3 | const Footer = () => {
4 | return (
5 |
9 | );
10 | };
11 |
12 | export default Footer;
13 |
--------------------------------------------------------------------------------
/src/containers/DashboardContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from 'react';
2 | import Panel from '../components/Panel.jsx';
3 | import DiagramContainer from '../containers/DiagramContainer.jsx';
4 | import DataWindow from '../components/DataWindow.jsx';
5 |
6 | const DashboardContainer = (props) => {
7 | const { user, setUser } = props;
8 | const [panelFullScreen, setPanelFullScreen] = useState(false);
9 | const [diagramFullScreen, setDiagramFullScreen] = useState(true);
10 | const [dataWindowFullScreen, setDataWindowFullScreen] = useState(false);
11 | const [msNames, setMsNames] = useState([]);
12 | const [msMetrics, setMsMetrics] = useState({});
13 | const [msLogs, setMsLogs] = useState({});
14 | const [msTraces, setMsTraces] = useState([]);
15 | const [msServiceIds, setMsServiceIds] = useState([]);
16 | const [refreshRedis, setRefreshRedis] = useState(false);
17 | const [activePanel, setActivePanel] = useState("");
18 |
19 | const handleTogglePanel = (panelName) => {
20 | //panelName is the name of the bubble that was clicked. Passed back up from bubble chart
21 | setActivePanel(panelName);
22 | //now we can use the panelName id to select the corresponding button in the panel
23 |
24 | if (panelName) {
25 | const button = document.getElementById(panelName);
26 | button.click();
27 | }
28 | };
29 |
30 | useEffect(() => {
31 | console.log('listening in dashboard');
32 | }, [user]);
33 |
34 | /// toggle full screen for mobile Panel
35 | const handlePanelClick = () => {
36 | if (panelFullScreen) {
37 | return;
38 | }
39 | setPanelFullScreen(!panelFullScreen);
40 | setDiagramFullScreen(false);
41 | setDataWindowFullScreen(false);
42 | document
43 | .getElementById('panelButton')
44 | .classList.add('current-window-button');
45 | document
46 | .getElementById('dataButton')
47 | .classList.remove('current-window-button');
48 | };
49 |
50 | // toggle full screen for mobile charts
51 | const handleDiagramClick = () => {
52 | if (diagramFullScreen) {
53 | return;
54 | }
55 | setPanelFullScreen(false);
56 | setDiagramFullScreen(!diagramFullScreen);
57 | setDataWindowFullScreen(false);
58 | document
59 | .getElementById('panelButton')
60 | .classList.remove('current-window-button');
61 | document
62 | .getElementById('dataButton')
63 | .classList.remove('current-window-button');
64 | };
65 |
66 | // toggle full screen for mobile logs
67 | const handleDataClick = () => {
68 | if (dataWindowFullScreen) {
69 | return;
70 | }
71 | setPanelFullScreen(false);
72 | setDiagramFullScreen(false);
73 | setDataWindowFullScreen(!dataWindowFullScreen);
74 | document
75 | .getElementById('dataButton')
76 | .classList.add('current-window-button');
77 | document
78 | .getElementById('panelButton')
79 | .classList.remove('current-window-button');
80 | };
81 |
82 | // fetch names
83 | useEffect(() => {
84 | if(user){
85 | const fetchNames = async() => {
86 | try{
87 | const response = await fetch('/aws/getLambdaNames', {
88 | method: 'POST',
89 | headers: {'Content-Type': 'application/json'},
90 | body: JSON.stringify({
91 | arn: user.arn,
92 | user_name: user.user_name
93 | }),
94 | muteHttpExceptions: true
95 | });
96 | if (response.ok){
97 | const data = await response.json()
98 | setMsNames(data);
99 | }
100 | else {
101 | alert('Please confirm correct ARN and region in settings')
102 | }
103 | }
104 | catch(error){
105 | }
106 | };
107 | fetchNames();
108 | }
109 | }, [user, refreshRedis])
110 |
111 | // fetch metrics
112 | useEffect(() => {
113 | if (msNames) {
114 | const fetchMetrics = async () => {
115 | try {
116 | const response = await fetch('/aws/getLambdaMetrics', {
117 | method: 'POST',
118 | headers: { 'Content-Type': 'application/json' },
119 | body: JSON.stringify({
120 | arn: user.arn,
121 | user_name: user.user_name
122 | }),
123 | muteHttpExceptions: true,
124 | });
125 | const data = await response.json();
126 | setMsMetrics(data.MetricDataResults);
127 | }
128 | catch (error) {
129 | console.log('error fetching metrics', error);
130 | }
131 | };
132 | fetchMetrics();
133 | }
134 | }, [msNames]);
135 |
136 | //fetch trace data
137 | useEffect(() => {
138 | //we need names to fetch traces also
139 | const fetchTraces = async () => {
140 | try {
141 | const response = await fetch('/aws/getTraces', {
142 | method: 'POST',
143 | headers: { 'Content-Type': 'application/json' },
144 | body: JSON.stringify({
145 | arn: user.arn,
146 | }),
147 | muteHttpExceptions: true,
148 | });
149 | const data = await response.json();
150 |
151 | //parsing name, duration, responseTime, service ids
152 | const serviceData = [];
153 | const traceData = await data.map((obj) => {
154 | if (!obj.summary.length) {
155 | return {
156 | name: obj.name,
157 | duration: undefined,
158 | responseTime: undefined,
159 | serviceIds: undefined,
160 | };
161 | } else {
162 | //create a separate serviceData array to pass as msServiceIds
163 | serviceData.push({
164 | name: obj.name,
165 | serviceIds: obj.summary[0].ServiceIds,
166 | });
167 | return {
168 | name: obj.name,
169 | duration: obj.summary[0].Duration,
170 | responseTime: obj.summary[0].ResponseTime,
171 | serviceIds: obj.summary[0].ServiceIds,
172 | };
173 | }
174 | });
175 | //trying to parse serviceIdData all at once
176 | setMsTraces(traceData);
177 | setMsServiceIds(serviceData);
178 |
179 | } catch (error) {
180 | console.log('error fetching traces', error);
181 | }
182 | };
183 | fetchTraces();
184 | }, []);
185 |
186 | return (
187 |
188 |
212 |
213 |
219 |
220 |
221 |
226 | Panel
227 |
228 |
233 | Log
234 |
235 |
236 |
237 |
238 | );
239 | };
240 |
241 | export default DashboardContainer;
242 |
--------------------------------------------------------------------------------
/src/containers/DiagramContainer.jsx:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import HorizontalBarChart from "../Chart/BarChart";
3 | import CircleChart from "../Chart/CircleChart";
4 | import Dropdown from "../components/ChartDropDown";
5 |
6 | const DiagramContainer = (props) => {
7 | const { diagramFullScreen } = props;
8 | const { msNames, msMetrics, msTraces, msServiceIds, handleTogglePanel } = props;
9 | const [activeChart, setActiveChart] = useState("Node");
10 |
11 | //writing serviceIds to the JSON file so the D3 node chart can read it
12 | useEffect(() => {
13 | if (msServiceIds.length) {
14 | const writeToFile = async (services) => {
15 | console.log(msServiceIds, 'msids')
16 | try {
17 | const response = await fetch("/aws/writeToFile", {
18 | method: "POST",
19 | headers: { "Content-Type": "application/json" },
20 | body: JSON.stringify({
21 | serviceIds: msServiceIds,
22 | }),
23 | muteHttpExceptions: true,
24 | });
25 | if (response.ok) {
26 | const data = await response.json();
27 | }
28 | } catch (error) {
29 | console.log(error, "error posting service data");
30 | }
31 | };
32 | writeToFile(msServiceIds);
33 | }
34 | }, []);
35 |
36 | //When a node is clicked in the iframe, the message will be sent back
37 | window.addEventListener("message", async (event) => {
38 | if (event.origin !== "http://localhost:5173") return;
39 |
40 | const serviceNodeId = event.data;
41 | if (!serviceNodeId.id) return;
42 |
43 | let nodeId = serviceNodeId.id;
44 |
45 | nodeId = nodeId.slice(0, nodeId.length - 1);
46 | let button = await document.getElementById(nodeId);
47 | if (button) {
48 | button.click();
49 | } else {
50 | console.log("was the button clicked? noooo");
51 | }
52 | });
53 |
54 | // active chart updates from dropdown, and renders corresponding chart
55 | return (
56 |
57 | {activeChart === "Bar" && }
58 | {activeChart === "Bubble" && }
59 | {activeChart === "Node" && }
60 |
61 |
62 | );
63 | };
64 | export default DiagramContainer;
65 |
--------------------------------------------------------------------------------
/src/containers/LandingPageContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import { Link } from 'react-router-dom';
3 | import Footer from '../components/footer';
4 |
5 | const LandingPageContainer = (props) => {
6 | const mascot = 'src/assets/logo.png';
7 | const video1 = 'src/assets/mock-video1.gif';
8 | const mobilemap = 'src/assets/mobile-map.png';
9 | const mobilelog = 'src/assets/mobile-logs.png';
10 | const mobilemetrics = 'src/assets/mobile-metrics.png';
11 | const screenshot = 'src/assets/desktop.png';
12 |
13 | return (
14 |
15 |
16 |
17 |
18 | An easy visualization tool to manage your AWS Lambda functions more
19 | effectively
20 |
21 |
22 |
23 |
24 | Get Started
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
EASY Connect your AWS account to Lambdawg in just a few
33 | clicks using our custom stack. Follow our detailed instructions
34 | and visualize your data in mere minutes. That's the Lambdawg way!{' '}
35 |
36 |
44 |
45 |
AWESOME Lambdawg was created to save Software Engineers
46 | time when trying to analyse the activity of their Lambda
47 | Functions. It displays data visualization, logs and errors in one
48 | dashboard{' '}
49 |
50 |
51 |
52 |
58 |
59 |
Lambdawg in a few words
60 |
61 | AWS Lambda functions are central in AWS based architectures.
62 | However, as one's Lambda architecture becomes more complex,
63 | monitoring them can become time consuming and outright frustrating.
64 |
65 |
66 | Lambdawg proposes a solution to this problem by offering an
67 | open-source, easy to use Dashboard for monitoring your Lambda
68 | functions.
69 | Lambdawg pulls Metrics and Logs from Cloudwatch as well as traces
70 | from AWS XRAY to provide comprehensive charts for direct comparison
71 | of functions. A much better way to monitor your Lambdas!
72 |
73 | Lambdawg makes herding your 'Lambs' much easy.
74 |
75 |
76 |
77 |
78 |
79 | );
80 | };
81 | export default LandingPageContainer;
82 |
--------------------------------------------------------------------------------
/src/containers/SettingsContainer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import SettingsForm from '../components/SettingsForm.jsx';
3 |
4 |
5 | const SettingsContainer = (props) => {
6 |
7 | const { loggedIn, user, setUser } = props
8 | const mascot = 'src/assets/logo.png'
9 | console.log(user, 'user in settings')
10 |
11 | return(
12 |
13 |
14 |
15 |
16 |
17 |
18 |
Welcome, {`${user.full_name}`}!
19 |
20 |
21 |
22 |
23 |
24 |
25 | Reset my password
26 |
27 |
28 |
29 |
30 |
31 |
32 | )
33 | }
34 |
35 | export default SettingsContainer;
--------------------------------------------------------------------------------
/src/main.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import App from "./App.jsx";
3 | import { BrowserRouter } from "react-router-dom";
4 | import { createRoot } from "react-dom/client";
5 | import "./styles/application.scss";
6 |
7 | const domNode = document.getElementById("root");
8 | const root = createRoot(domNode);
9 | root.render(
10 |
11 |
12 |
13 | );
14 |
--------------------------------------------------------------------------------
/src/styles/_animation.scss:
--------------------------------------------------------------------------------
1 | .bouncing-body {
2 | display: flex;
3 | align-items: center;
4 | justify-content: center;
5 | min-height: 100vh;
6 | z-index: 999;
7 | }
8 |
9 | .bouncing-loader {
10 | display: flex;
11 | justify-content: center;
12 | margin: 40px auto;
13 | }
14 |
15 | .bouncing-loader > div {
16 | width: 2em;
17 | height: 2em;
18 | margin: 3px 6px;
19 | border-radius: 50%;
20 | background-color: transparent;
21 | opacity: 1;
22 | animation: bouncing-loader 0.6s infinite alternate;
23 | }
24 |
25 | @keyframes bouncing-loader {
26 | to {
27 | opacity: 0.1;
28 | transform: translateY(-16px);
29 | }
30 | }
31 |
32 | .bouncing-loader > div:nth-child(2) {
33 | animation-delay: 0.2s;
34 | }
35 |
36 | .bouncing-loader > div:nth-child(3) {
37 | animation-delay: 0.4s;
38 | }
39 |
--------------------------------------------------------------------------------
/src/styles/_auth.scss:
--------------------------------------------------------------------------------
1 | #auth-container {
2 | position: absolute;
3 | width: 100vw;
4 | top: 15vh;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | flex-direction: column;
9 |
10 | }
11 |
12 | .large-mascot {
13 | max-width: 150px;
14 | margin: 1rem;
15 | }
16 |
--------------------------------------------------------------------------------
/src/styles/_dashboard.scss:
--------------------------------------------------------------------------------
1 |
2 | .chart-container {
3 | max-width: 60vw;
4 | padding: 1rem;
5 |
6 | }
7 |
8 | #dashboard-container {
9 | display: flex;
10 | flex-direction: column;
11 | height: calc(100vh - 4rem);
12 | overflow: hidden;
13 | background-color: $dashboard-background-color;
14 | padding: 2rem 1rem 0rem 1rem;
15 | margin-top: 4rem;
16 | }
17 | #dashboard-wrapper { // panel and diagram
18 | display: flex;
19 | flex-direction: row;
20 | // justify-content: flex-start;
21 | max-height: 60vh;
22 | min-height: 60vh;
23 | max-width: 95vw;
24 | gap: 1rem;
25 | margin-bottom: .5rem;
26 | }
27 | #diagram-container-wrapper {
28 | border: 1px solid $dashboard-border-boxes;
29 | border-radius: 1rem;
30 | border-top-left-radius: 0px;
31 | border-bottom-left-radius: 0px;
32 | overflow-y: hidden;
33 | flex: 1;
34 | justify-content: center;
35 | background-color: $diagram-background-color;
36 | // width: calc(100vw - 350px - 5rem);
37 | flex-grow: 2;
38 | height: 60vh;
39 |
40 | }
41 | #data-window-wrapper {
42 | border: 1px solid $dashboard-border-boxes;
43 | overflow: hidden;
44 | // padding: 0rem 10rem 1rem 1rem;
45 | overflow-x: hidden;
46 | overflow-y: hidden !important;
47 |
48 | }
49 | #log-ul {
50 | overflow: hidden;
51 | }
52 |
53 | #panel-wrapper {
54 | border: 1px solid $dashboard-border-boxes;
55 | overflow: auto;
56 | flex-shrink: 0;
57 | resize: horizontal;
58 | padding: 1rem;
59 | background-color: $panel-background-color;
60 | overflow-y: scroll;
61 | overflow-x: hidden;
62 | border-radius: 1rem;
63 | display: flex;
64 | flex-direction: column;
65 | height: 60vh;
66 | }
67 |
68 |
69 | #panelButton, #diagramButton, #dataButton, .block-button-wrapper {
70 | visibility: hidden;
71 | height: 0px;
72 | margin: 0px;
73 |
74 | }
75 | .chart-frame {
76 | height: 100%;
77 | overflow: hidden !important;
78 | border: 2px solid red;
79 | }
80 |
81 | iframe {
82 | padding: 0px !important;
83 | overflow: hidden;
84 | border: none;
85 | }
86 | .logs-loading{
87 | color: $panel-background-color;
88 | }
89 |
90 | #panel-wrapper > button {
91 | white-space: nowrap;
92 | overflow: hidden;
93 | text-overflow: ellipsis;
94 | max-width: 100vw;
95 | text-align: left;
96 | flex-shrink: 0;
97 | }
98 |
99 |
100 | // media queries
101 | @media (max-width: 1000px) {
102 |
103 | #hideDetails {
104 | display: none;
105 | }
106 | #dashboard-container {
107 | box-sizing: border-box;
108 | overflow: hidden;
109 | width: 100vw;
110 | height: 100vh;
111 | // height: calc(100vh - 5rem);
112 | padding: 0rem;
113 | background-color: $dashboard-background-color-mobile;
114 | }
115 |
116 | #data-window-wrapper {
117 | // overflow: visible;
118 | overflow-y: hidden !important;
119 | height: 90vh !important;
120 | position: absolute;
121 | top: 3rem;
122 | width: 100%;
123 | }
124 | #panel-wrapper {
125 | width: 100% !important;
126 | margin: 0px;
127 |
128 | }
129 | .current-window-button{
130 | background-color: $button-current-page-indicator !important;
131 | }
132 |
133 | #dashboard-wrapper {
134 | flex-direction: column;
135 | width: 100vw;
136 | }
137 | #diagram-container-wrapper {
138 | visibility: hidden !important;
139 | }
140 |
141 | .collapse-screen {
142 | visibility: hidden !important;
143 | height: 0px !important;
144 | }
145 | .full-screen {
146 | height: 90vh !important;
147 | visibility: visible !important;
148 | }
149 |
150 |
151 | .block-button-wrapper {
152 | position: fixed;
153 | bottom: 0rem;
154 | display: flex;
155 | justify-content: center;
156 | align-items: center;
157 | width: 100%;
158 | visibility: visible;
159 | height: 3.5rem;
160 | padding: 1.25rem .25rem 1rem .25rem;
161 | background-color: $dashboard-background-color-mobile;
162 | }
163 |
164 | #panelButton, #diagramButton, #dataButton {
165 | width: 48vw;
166 | height: 2.5rem;
167 | margin: 1rem .25rem 1rem .25rem;
168 | visibility: visible;
169 | background-color: $button-color;
170 | color: $button-font-color;
171 | }
172 | #panel-wrapper, #diagram-container-wrapper, #data-window-wrapper, #dashboard-wrapper {
173 | border: none !important;
174 | border-radius: 0px;
175 |
176 | }
177 | #chart-frame {
178 | height: 100vh;
179 | overflow-x: hidden;
180 | overflow-y: hidden;
181 | }
182 |
183 | }
184 |
185 |
186 |
--------------------------------------------------------------------------------
/src/styles/_docs.scss:
--------------------------------------------------------------------------------
1 | .docs-logo {
2 | display: block;
3 | text-align: center;
4 | margin: 0 auto;
5 | padding-top: 3em;
6 | width: 400px;
7 | max-width: 300px;
8 | }
9 |
10 | #docs-page {
11 | display: flex;
12 | flex-direction: column;
13 | align-items: center;
14 | justify-content: center;
15 | max-width: 1000px;
16 | margin: 0 auto;
17 | margin-top: 1rem;
18 | }
19 |
20 | #docs-page p {
21 | text-align: center;
22 | max-width: 800px;
23 | }
24 |
25 | .docs-first-line {
26 | color: $primary-font-color;
27 | font-weight: 700;
28 | font-size: 1.2em;
29 | }
30 |
31 | .docs-setup-section {
32 | display: flex;
33 | // flex-direction: column;
34 | flex-wrap: wrap;
35 | justify-content: center;
36 | max-width: 1000px;
37 | img {
38 | width: 100%;
39 | max-width: 300px;
40 | height: auto;
41 | display: block;
42 | margin: 0 auto;
43 | border: 1px solid $nav-background-color;
44 | background-color: $nav-background-color;
45 | border-radius: 1rem;
46 | padding: .5rem;
47 | margin-top: 1rem;
48 | }
49 |
50 | }
51 |
52 | .docs-setup-section div {
53 | // border-radius: 8px;
54 | // padding: 1em;
55 | margin: 1em;
56 | }
57 |
58 | .docs-setup-section p {
59 | color: $secondary-font-color;
60 | }
61 |
62 | // .docs-dev span {
63 | // font-style: italic;
64 | // a {
65 | // color: rgb(87, 87, 173);
66 | // text-decoration: none;
67 | // font-weight: bold;
68 | // }
69 | // }
70 |
--------------------------------------------------------------------------------
/src/styles/_dropdown.scss:
--------------------------------------------------------------------------------
1 | .chart-dropdown {
2 | position: absolute;
3 | display: block;
4 | top: calc(60vh + 2rem);
5 | border-radius: 10px;
6 | z-index: 999;
7 |
8 | .chart-dropdown-toggle {
9 | background-color: transparent;
10 | padding: 8px 16px;
11 | padding-top: 10px;
12 | font-size: 1.5em;
13 | text-align: center;
14 | font-weight: 500;
15 | border-radius: 7px;
16 | cursor: pointer;
17 | }
18 |
19 | .chart-dropdown-menu {
20 | display: flex;
21 | flex-direction: column;
22 | display: none;
23 | width: 100%;
24 | padding: 5px 10px;
25 | margin: 2px 0 0;
26 | font-size: 1.5em;
27 | text-align: center;
28 | list-style: none;
29 | background-color: white;
30 | border: 1px solid #ccc;
31 | border-radius: 4px;
32 | box-shadow: 0 6px 12px rgba(0, 0, 0, 0.175);
33 |
34 | &.show {
35 | display: block;
36 | }
37 |
38 | .chart-dropdown-item {
39 | color: $primary-button-color;
40 | display: flex;
41 | align-items: center;
42 | justify-content: center;
43 | width: 100%;
44 | height: 100%;
45 | padding: 3px 20px;
46 | clear: both;
47 | font-weight: 400;
48 | color: $button-font-color;
49 | white-space: nowrap;
50 | background-color: $secondary-button-color;
51 | border: none;
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/src/styles/_footer.scss:
--------------------------------------------------------------------------------
1 | #FooterWrapper {
2 | margin-top: 5vh;
3 | display: flex;
4 | align-items: center;
5 | justify-content: center;
6 | min-width: 100vw;
7 | height: 75px;
8 | background-color: lighten(#f75215, 15%);
9 | color: #fff;
10 | }
11 |
--------------------------------------------------------------------------------
/src/styles/_form.scss:
--------------------------------------------------------------------------------
1 |
2 | input{
3 | border: none;
4 | border: 1px solid $input-box-border-color;
5 | border-radius: 3px;
6 | width: 200px;
7 | margin-bottom: 5px;
8 | background-color: $input-box-background-color;
9 | }
10 | .auth-logo {
11 | max-width: 250px;
12 | margin-bottom: 1rem;
13 | }
14 | label{
15 | font-size: .7rem;
16 | }
17 | .form-container > h1 {
18 | margin-top: -2.4rem;
19 | color: $info-card-background-color;
20 | }
21 |
22 | form {
23 | display: flex;
24 | flex-direction: column;
25 | width: 200px;
26 | justify-content: space-between;
27 |
28 | }
29 | .form-container {
30 | display: flex;
31 | justify-content: center;
32 | flex-direction: column;
33 | align-items: center;
34 | margin-top: 0vh;
35 | }
36 |
37 | .button-flex-wrapper {
38 | display: flex;
39 | flex-direction: row;
40 | justify-content: space-evenly;
41 | align-items: center;
42 | }
43 | button {
44 | padding: 5px 10px 5px 10px;
45 | border-radius: 3px;
46 | }
47 | .primary-button {
48 | border: none;
49 | background-color: $primary-button-color;
50 | color: $primary-button-font;
51 | }
52 | .secondary-button {
53 | border: none;
54 | background-color: $secondary-button-color;
55 | color: $secondary-button-font;
56 | }
57 |
58 | #theme-bg-auth {
59 | display: flex;
60 | justify-content: center;
61 | flex-direction: column;
62 | align-items: center;
63 | padding: 1rem 2rem 2rem 2rem;
64 | border-radius: 1.5rem;
65 | background-color: $auth-form-bg-color;
66 | }
--------------------------------------------------------------------------------
/src/styles/_landingpage.scss:
--------------------------------------------------------------------------------
1 | #landing-page-container {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | justify-content: center;
6 | padding: 2rem;
7 | }
8 | #landing-page-flex {
9 | display: flex;
10 | flex-direction: column;
11 | align-items: center;
12 | justify-content: center;
13 | max-width: 800px;
14 | margin-top: 1rem;
15 | }
16 | .landing-logo {
17 | width: 400px;
18 | max-width: 400px;
19 | margin-top: 2rem;
20 | }
21 |
22 | #get-started-button {
23 | padding: 0.5rem 1rem 0.5rem 1rem;
24 | font-size: 1.5rem;
25 | transition: transform 0.2s; /* Animation */
26 | }
27 | #get-started-button:hover {
28 | transform: scale(
29 | 1.1
30 | ); /* (150% zoom - Note: if the zoom is too large, it will go outside of the viewport) */
31 | }
32 |
33 | #landing-page-container > .button-flex-wrapper > button {
34 | margin: 0.5rem;
35 | }
36 | #landing-page-container > .large-mascot {
37 | display: flex;
38 | justify-content: center;
39 | align-items: center;
40 | }
41 |
42 | .screen-shot {
43 | width: 70vw;
44 | max-width: 700px;
45 | border: 1px solid $nav-background-color;
46 | border-radius: 1rem;
47 | margin: 1rem;
48 | transition: transform 0.2s; /* Animation */
49 | }
50 | .screen-shot:hover {
51 | transform: scale(
52 | 1.1
53 | ); /* (150% zoom - Note: if the zoom is too large, it will go outside of the viewport) */
54 | }
55 | .screen-shot-mobile {
56 | max-width: 70vw;
57 | max-width: 200px;
58 | border: 5px solid $nav-background-color;
59 | padding: -0.2rem;
60 | border-radius: 1rem;
61 | margin: 0.5rem;
62 | transition: transform 0.2s; /* Animation */
63 | }
64 | .screen-shot-mobile:hover {
65 | transform: scale(1.1);
66 | }
67 |
68 | .info-card-wrapper {
69 | display: flex;
70 | justify-content: space-evenly;
71 | flex-wrap: wrap;
72 | }
73 | .info-card {
74 | flex: 1;
75 | width: 300px;
76 | display: flex;
77 | flex-direction: column;
78 | justify-content: center;
79 | align-items: center;
80 | border-radius: 1rem;
81 | background-color: $info-card-background-color;
82 | padding: 0rem 1rem 1rem 1rem;
83 | margin: 1rem;
84 | }
85 | .info-card-icon {
86 | font-size: 4rem;
87 | }
88 | .info-card > h1 {
89 | margin-top: -1.4rem;
90 | color: $info-card-background-color;
91 | }
92 | // media queries
93 | @media (max-width: 1000px) {
94 | .video {
95 | width: 90vw;
96 | }
97 | .info-card-wrapper {
98 | flex-direction: column;
99 | }
100 |
101 | .landing-logo {
102 | max-width: 300px;
103 | }
104 | .screen-shot-mobile,
105 | .screen-shot {
106 | max-width: 70vw;
107 | min-width: 70vw;
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/src/styles/_metrics.scss:
--------------------------------------------------------------------------------
1 | .metrics-button {
2 | width: 300px;
3 | background-color: $secondary-button-color;
4 | border: none;
5 | border: 1px solid $error-font-color;
6 | margin-bottom: .2rem;
7 | color: $primary-font-color;
8 | }
9 | #refresh-button {
10 | width: 300px;
11 | text-align: center !important;
12 | border: none;
13 | margin-bottom: .2rem;
14 | background-color: $primary-button-color;
15 | color: $primary-button-font;
16 | }
17 |
18 | #data-window-wrapper {
19 | padding: 0rem 1rem 1rem 1rem;
20 | background-color: $data-window-background-color;
21 | }
22 |
23 | .timestamps-list {
24 | display: flex;
25 | flex-direction: column;
26 | }
27 | .timestamp-error {
28 | color: $error-font-color;
29 | }
30 | .timestamp, .timestamp-error {
31 | font-size: .8rem;
32 | padding: .1rem;
33 | }
34 |
35 | .metrics-toggle-panel > ul {
36 | list-style: none;
37 | border: 1px solid $error-font-color;
38 | border-top: none;
39 | padding-right: 1rem;
40 | max-width: 90vw;
41 | }
42 | .metrics-toggle-panel > ul > li {
43 | color: $primary-font-color;
44 | }
45 | .metrics-toggle-panel {
46 | display: none;
47 | }
48 | .metrics-toggle-panel.open {
49 | display: block;
50 | }
51 |
52 |
53 | #log-ul {
54 | list-style: none;
55 | }
56 | #log-ul > li {
57 | color: $code-font-color;
58 | font-size: .8rem;
59 | }
60 | .log-wrap {
61 | margin-bottom: 1rem;
62 | }
63 | .log-name {
64 | color: $error-font-color;
65 | padding-bottom: .5rem;
66 | border-bottom: 1px solid $code-font-color;
67 |
68 | }
69 | .msg-name {
70 | padding-bottom: .5rem;
71 | }
72 | .time-stamp {
73 | color: $error-font-color;
74 | }
75 |
76 |
77 | @media (max-width: 1000px) {
78 | #log-ul {
79 | padding: 0px;
80 | }
81 | .metrics-button, #refresh-button {
82 | width: 90vw;
83 | padding: .75rem;
84 | margin-bottom: .5rem;
85 | }
86 |
87 | }
--------------------------------------------------------------------------------
/src/styles/_navbar.scss:
--------------------------------------------------------------------------------
1 | .nav {
2 | position: fixed;
3 | top: 0px;
4 | }
5 |
6 | #navbar-container {
7 | width: 100vw;
8 | min-height: 2rem;
9 | display: flex;
10 | justify-content: space-between;
11 | align-items: center;
12 | height: 3rem;
13 | margin: 0px;
14 | border-bottom: 1px solid $nav-border;
15 | background-color: $nav-background-color;
16 | z-index: 999;
17 | }
18 |
19 | .nav-item-group {
20 | display: flex;
21 | align-items: center;
22 | justify-content: flex-end;
23 | }
24 | .nav-item-logo {
25 | margin-left: 1rem;
26 | display: flex;
27 | align-items: center;
28 | }
29 | .nav-item-logo > h1 {
30 | color: $lambdawg-color;
31 | }
32 |
33 | .mascot {
34 | height: 6vh;
35 | transform: scaleX(-1);
36 | margin-right: 10px;
37 | }
38 |
39 | #hamburger-icon {
40 | visibility: hidden;
41 | }
42 |
43 | .collapse-burger-menu {
44 | visibility: hidden;
45 | }
46 |
47 | #desktop-menu {
48 | display: flex;
49 | flex-direction: row;
50 | list-style: none;
51 | justify-content: center;
52 | align-items: baseline;
53 | }
54 | #desktop-menu > li {
55 | padding: 1rem 0px 0.8rem 1rem;
56 | }
57 |
58 | // media queries
59 | @media (max-width: 1000px) {
60 | /////////////
61 |
62 | ////////////
63 |
64 | #desktop-menu {
65 | display: none;
66 | }
67 | .collapse-desktop-menu {
68 | visibility: hidden;
69 | }
70 |
71 | #navbar-container {
72 | position: fixed;
73 | z-index: 999;
74 | }
75 |
76 | #hamburger-icon {
77 | visibility: visible;
78 | }
79 |
80 | body {
81 | overflow-x: hidden;
82 | }
83 |
84 | .nav-icon {
85 | visibility: hidden;
86 | }
87 |
88 | #hamburger-icon {
89 | visibility: visible;
90 | margin: 0px 1rem 0px 0px;
91 | color: $hamburger-icon-color;
92 | font-size: 1.5rem;
93 | }
94 |
95 | #hamburger-menu {
96 | position: fixed;
97 | background-color: $hamburger-menu-bg-color;
98 | margin: 0px;
99 | padding: 1rem 1rem 0px 1rem;
100 | width: 100vw;
101 | max-width: 15rem;
102 | display: flex;
103 | flex-direction: column;
104 | position: absolute;
105 | top: 3rem;
106 | right: 0px;
107 | list-style: none;
108 | z-index: 998;
109 | border-bottom-left-radius: 30px;
110 | }
111 |
112 | #hamburger-menu > li {
113 | background-color: $hamburger-menu-bg-color;
114 | padding: 1rem;
115 | border-top: 1px solid $hamburger-menu-lines;
116 | border-bottom-left-radius: 30px;
117 | color: $hamburger-menu-lines;
118 | z-index: 999;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/styles/_newnavbar.scss:
--------------------------------------------------------------------------------
1 | * {
2 | box-sizing: border-box;
3 | }
4 | .switchLabel {
5 | padding: 10px 13px;
6 | color: #f75215;
7 | padding-right: 0;
8 | }
9 |
10 | .nav-logo-wrapper {
11 | display: flex;
12 | margin-top: 0.4rem;
13 | align-items: center;
14 | }
15 |
16 | .nav-logo {
17 | width: 2rem;
18 | height: 2rem;
19 | margin-right: 0.5rem;
20 | margin-left: 1rem;
21 | // margin-top: -.5rem;
22 | filter: invert(10);
23 | }
24 | .nav-text-logo {
25 | // margin-top: .6rem;
26 | }
27 | .text-logo {
28 | max-height: 1.8rem;
29 | }
30 | .nav {
31 | display: flex;
32 | justify-content: space-between;
33 | align-content: center;
34 | height: fit-content;
35 | width: 100%;
36 | background-color: $nav-background-color;
37 | position: fixed;
38 | top: 0px;
39 | z-index: 999;
40 | }
41 |
42 | .nav > .nav-header {
43 | display: flex;
44 | }
45 |
46 | .nav > .nav-header > .nav-title {
47 | display: inline-block;
48 | font-size: 22px;
49 | padding: 10px 10px 10px 10px;
50 | }
51 |
52 | .nav > .nav-btn {
53 | display: none;
54 | }
55 |
56 | .nav > .nav-links {
57 | display: flex;
58 | flex-direction: row;
59 | align-items: center;
60 | float: right;
61 | font-size: 18px;
62 | margin-right: 3rem;
63 | }
64 |
65 | .nav > .nav-links > a {
66 | display: inline-block;
67 | padding: 13px 10px 13px 10px;
68 | text-decoration: none;
69 | color: $nav-links-color;
70 | }
71 |
72 | // .nav > .nav-links > a:hover {
73 | // background-color: rgba(0, 0, 0, 0.3);
74 | // }
75 |
76 | .nav > #nav-check {
77 | display: none;
78 | }
79 |
80 | @media (max-width: 1000px) {
81 | .menuTop {
82 | margin-top: 10px;
83 | margin-right: 10px;
84 | }
85 | .switchLabel {
86 | padding-right: 13px;
87 | }
88 | .nav > .nav-btn {
89 | display: inline-block;
90 | position: absolute;
91 | right: 15px;
92 | top: 0px;
93 | }
94 | .nav > .nav-btn > label {
95 | display: inline-block;
96 | width: 50px;
97 | height: 50px;
98 | padding: 13px;
99 | }
100 | // .nav > .nav-btn > label:hover,.nav #nav-check:checked ~ .nav-btn > label {
101 | // background-color: pink;
102 | // }
103 | .nav > .nav-btn > label > span {
104 | display: block;
105 | width: 25px;
106 | height: 10px;
107 | border-top: 2px solid $primary-font-color;
108 | }
109 | .nav > .nav-links {
110 | position: absolute;
111 | display: block;
112 | width: 100%;
113 | background-color: $hamburger-menu-bg-color;
114 | height: 0px;
115 | transition: all 0.3s ease-in;
116 | overflow-y: hidden;
117 | top: 60px;
118 | left: 0px;
119 | }
120 | .nav > .nav-links > a {
121 | display: block;
122 | width: 100%;
123 | }
124 | .nav > #nav-check:not(:checked) ~ .nav-links {
125 | height: 0px;
126 | }
127 | .nav > #nav-check:checked ~ .nav-links {
128 | height: calc(100vh - 50px);
129 | overflow-y: auto;
130 | }
131 | .nav {
132 | display: flex;
133 | min-height: fit-content;
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/styles/_settings.scss:
--------------------------------------------------------------------------------
1 | #settings-container {
2 | position: absolute;
3 | width: 100vw;
4 | top: 5rem;
5 | display: flex;
6 | align-items: center;
7 | justify-content: center;
8 | flex-direction: column;
9 |
10 | }
11 |
12 | .large-mascot {
13 | max-width: 150px;
14 |
15 | }
16 | .settings-button-wrap {
17 | justify-content: space-between;
18 | }
19 | .settings-button-wrap > button {
20 | margin: .25rem;
21 |
22 | }
23 | .horizontal-line {
24 | border-top: 1px solid $secondary-font-color;
25 | border-bottom: 1px solid $secondary-font-color;
26 | padding-bottom: .5rem;
27 | padding-top: .5rem;
28 | }
29 | .stack-button {
30 | border-radius: 3px;
31 | margin: 1rem;
32 | height: 2rem;
33 |
34 | }
35 | .settings-form-container {
36 | display: flex;
37 | flex-direction: column;
38 | align-items: baseline;
39 | justify-content: center;
40 |
41 | }
42 | .settings-form-container > form{
43 | width: 100%;
44 | }
45 |
46 | .settings-form-container > form > input{
47 | width: 100%;
48 | height: 2rem;
49 | }
50 | .settings-form-container > form > select{
51 | width: 100%;
52 | height: 2rem;
53 | border-radius: 3px;
54 | }
55 | .settings-primary-button {
56 | border: none;
57 | background-color: $primary-button-color;
58 | color: $primary-button-font;
59 | }
60 | .settings-secondary-button {
61 | border: 1px solid $primary-button-color;
62 | background-color: $body-bg-color;
63 | color: $secondary-button-font;
64 | }
65 | .visible-link {
66 | text-decoration: underline;
67 | }
68 | .settings-button-wrap > button {
69 | border: none;
70 | color: $secondary-font-color;
71 | }
72 | #settings-container > .large-mascot {
73 | max-width: 150px;
74 | margin: 0rem;
75 | }
76 | // * {
77 | // border: 1px solid red;
78 | // }
--------------------------------------------------------------------------------
/src/styles/_variables.scss:
--------------------------------------------------------------------------------
1 | $body-bg-color: white;
2 |
3 | $primary-font-color: #212021;
4 | $secondary-font-color: lighten($primary-font-color, 30%);
5 | $tertiary-font-color: lighten($primary-font-color, 50%);
6 |
7 | $code-font-color: #fad6c9;
8 | $code-background-color: #232324;
9 |
10 | $box-shadow-color: lighten($primary-font-color, 70%);
11 |
12 | /// Navbar
13 | $nav-background-color: #fad6c9;
14 | $nav-border: rgb(199, 192, 192);
15 | $lambdawg-color: #f75215;
16 | $nav-links-color: #f75215;
17 |
18 | $hamburger-icon-color: $primary-font-color;
19 | $hamburger-menu-bg-color: #595959;
20 | $hamburger-menu-lines: #4c4c4c;
21 | $hamburger-menu-font: $nav-background-color;
22 |
23 | /// Dashboard
24 | $dashboard-background-color: #595959;
25 | $dashboard-border-boxes: none;
26 | $dashboard-background-color-mobile: #fff;
27 |
28 | $panel-background-color: #fff;
29 | $error-font-color: #f75215;
30 |
31 | $diagram-background-color: #fff;
32 |
33 | $data-window-background-color: #595959;
34 | $button-background-color: white;
35 |
36 | $button-color: #f75215;
37 | $button-font-color: black;
38 | $button-current-page-indicator: lighten($button-color, 20%);
39 |
40 | /// Forms
41 | $input-box-background-color: white;
42 | $input-box-border-color: black;
43 | $auth-form-bg-color: #fad6c9;
44 |
45 | /// Buttons
46 | $primary-button-color: #f75215;
47 | $secondary-button-color: lighten($primary-button-color, 40%);
48 | $tertiary-button-color: lighten($primary-button-color, 20%);
49 | $primary-button-font: #fff;
50 | $secondary-button-font: #2c2828;
51 |
52 | /// LANDING PAGE
53 | $info-card-background-color: #fad6c9;
54 |
55 | ///CHARTS
56 | $chart-primary-color: #5fba88;
57 | $chart-secondary-color: lighten($chart-primary-color, 20%);
58 | $chart-contrast-color: #297fb8;
59 | $chart-background-color: #ff508480;
60 | $chart-background-contrast-color: #35a2eb80;
61 |
--------------------------------------------------------------------------------
/src/styles/application.scss:
--------------------------------------------------------------------------------
1 | // import fonts
2 | @import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&family=Roboto&display=swap');
3 |
4 | // import style modules
5 | @import 'variables';
6 | @import 'form';
7 | @import 'dashboard';
8 | @import 'navbar';
9 | @import 'auth';
10 | @import 'landingpage';
11 | @import 'settings';
12 | @import 'newnavbar';
13 | @import 'metrics';
14 | @import 'dropdown';
15 |
16 | @import 'docs';
17 |
18 | @import 'animation';
19 | @import 'footer';
20 | // global css stuff here
21 | * {
22 | box-sizing: border-box;
23 | // border: 1px solid red;
24 | }
25 |
26 | .image-effect {
27 | transition: transform 0.2s; /* Animation */
28 | }
29 | .image-effect:hover {
30 | transform: scale(
31 | 1.1
32 | ); /* (150% zoom - Note: if the zoom is too large, it will go outside of the viewport) */
33 | }
34 |
35 | body {
36 | margin: 0px;
37 | padding: 0px;
38 | color: $primary-font-color;
39 | font-family: 'Roboto', sans-serif;
40 | overflow-x: hidden;
41 | background-color: $body-bg-color;
42 | }
43 | #content {
44 | padding-top: 0rem;
45 | }
46 | code {
47 | color: $code-font-color;
48 | font-size: 0.75rem;
49 | }
50 | p {
51 | color: $primary-font-color !important;
52 | }
53 | h1 {
54 | // font-family: 'Press Start 2P', 'Roboto', sans-serif;
55 | text-align: center;
56 | font-size: 1.5rem;
57 | color: $primary-font-color;
58 | }
59 | .centered-text {
60 | text-align: center;
61 | }
62 | a:link {
63 | text-decoration: none;
64 | color: $primary-font-color;
65 | }
66 | a:visited {
67 | text-decoration: none;
68 | color: $primary-font-color;
69 | }
70 | a:active {
71 | color: $tertiary-font-color;
72 | }
73 | button:active {
74 | transform: translateY(2px);
75 | }
76 |
77 | @keyframes pulse {
78 | 0% {
79 | opacity: 0.4;
80 | }
81 | 50% {
82 | opacity: 1;
83 | }
84 | 100% {
85 | opacity: 0.4;
86 | }
87 | }
88 | .loading-text {
89 | text-align: center;
90 | animation: pulse 1s infinite;
91 | }
92 | .scroll-bar {
93 | padding: 5rem;
94 | border: 1 px solid red;
95 | }
96 |
97 | // media queries
98 | @media (max-width: 1000px) {
99 | }
100 |
--------------------------------------------------------------------------------
/vite.config.js:
--------------------------------------------------------------------------------
1 | import { defineConfig } from "vite";
2 | import react from "@vitejs/plugin-react";
3 |
4 | export default defineConfig({
5 | plugins: [react()],
6 | server: {
7 | hmr: {
8 | host: "0.0.0.0",
9 | port: 3000,
10 | },
11 | proxy: {
12 | "/api": {
13 | target: "http://localhost:3000",
14 | changeOrigin: true,
15 | },
16 | "/aws": {
17 | target: "http://localhost:3000",
18 | changeOrigin: true,
19 | rewrite: (path) => path.replace(/^\/aws/, ""),
20 | },
21 | },
22 | },
23 | });
24 |
--------------------------------------------------------------------------------