├── .dockerignore ├── .env.production ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── __tests__ ├── playwright.test.js ├── react.test.jsx ├── server-mock.js └── tests.test.js ├── client ├── public │ ├── pulse.svg │ └── vite.svg └── src │ ├── App.css │ ├── App.jsx │ ├── apiBaseUrl.js │ ├── assets │ ├── apigatewayendpoint.svg │ ├── chart-bar-svgrepo-com.svg │ ├── client-svgrepo-com.svg │ ├── dynamoDB.svg │ ├── emptyIcon.svg │ ├── error-905.svg │ ├── error-svgrepo-com.svg │ ├── home-1391-svgrepo-com.svg │ ├── lambdaFunc.svg │ ├── list-svgrepo-com.svg │ ├── logout-svgrepo-com.svg │ ├── map-svgrepo-com.svg │ ├── network-2-1118-svgrepo-com.svg │ ├── pulse-1.1s-200px.svg │ ├── pulse.svg │ ├── react.svg │ ├── refresh-svgrepo-com.svg │ ├── sampleData.json │ ├── sampleLog.js │ ├── sampleTraces.json │ ├── sesIcon.svg │ ├── settings-svgrepo-com.svg │ ├── simpleNotification.svg │ └── team-svgrepo-com.svg │ ├── components │ ├── AppTree.jsx │ ├── DebugTraceDisplay.jsx │ ├── EventGraph.jsx │ ├── HomeDisplay.css │ ├── HomeDisplay.jsx │ ├── LogPanel.jsx │ ├── Metrics.jsx │ ├── NavBar.jsx │ ├── NodeDetail.jsx │ ├── NodeTree.jsx │ ├── Settings.jsx │ ├── SimpleDataDisplay.jsx │ ├── TraceFilters.jsx │ ├── TraceList.jsx │ ├── TraceSelector.jsx │ ├── custom-tree.css │ ├── event-graph.css │ └── nav-bar.css │ ├── index.css │ ├── main.jsx │ └── pages │ ├── About │ ├── About.css │ ├── About.jsx │ └── graph.png │ ├── ComingSoon.jsx │ ├── Dashboard │ ├── Dashboard.css │ └── Dashboard.jsx │ ├── Login.css │ ├── Login.jsx │ └── Signup.jsx ├── docker-compose.yml ├── index.html ├── package-lock.json ├── package.json ├── server ├── aws_sdk │ ├── sortingSegments.js │ └── traceDetails.js ├── controllers │ ├── awsCredentialsController.js │ ├── jwtController.js │ ├── redisController.js │ └── userController.js ├── db.config.js └── server.js └── vite.config.js /.dockerignore: -------------------------------------------------------------------------------- 1 | .env 2 | node_modules 3 | __tests__ 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /.env.production: -------------------------------------------------------------------------------- 1 | VITE_CAPTCHA_KEY="6LcXA7QlAAAAAN-uuwIe_z5KZAutU8ReiZwxh3Mp" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | .env.production 75 | 76 | # parcel-bundler cache (https://parceljs.org/) 77 | .cache 78 | 79 | # Next.js build output 80 | .next 81 | 82 | # Nuxt.js build / generate output 83 | .nuxt 84 | dist 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | venv -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:18.12 2 | WORKDIR /user/src/app 3 | COPY . /user/src/app 4 | RUN npm install 5 | RUN npm run build 6 | EXPOSE 3000 7 | CMD ["npm", "run", "serve"] -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 OSLabs Beta 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LambdaPulse README 2 | 3 | 4 | ## Product Description 5 | 6 | LambdaPulse is a simplified, streamlined tool to view the health of your AWS Lambda Functions. 7 | 8 | 9 | ## Instructions 10 | 11 | LambdaPulse functions best as a (currently free) SaaS tool, accessible via the URL in our About section. 12 | However, if you'd like to fork & host your own local copy of LambdaPulse, instructions are provided below. 13 | 14 | ### Option A: Sign up as a user! 15 | 1) Go to http://lambdapulse.com 16 | 2) Sign Up 17 | 3) Go into Settings and enter your ARN 18 | 4) Go into Traces and click the Refresh icon 19 | 20 | ### Option B: Dev Installation Instructions 21 | 1) Git clone 22 | 2) install redis-server via apt/brew/etc 23 | 3) npm install in / 24 | 4) In .env, set up the following: 25 | AWS_ACCESS_KEY_ID 26 | AWS_SECRET_ACCESS_KEY 27 | USER_ROLE_ARN 28 | JWT_KEY 29 | -also a separate .env in client folder, setup VITE_CAPTCHA_KEY = "google captchav2 key" 30 | 5) install redis-server via apt/brew/etc 31 | #) Update vite.config.js to change port to something other than 3000 32 | #) Update package.json fulldev to add NODE_ENV = dev 33 | 6) npm run fulldev 34 | 7) create user and enter ARN in settings 35 | 8) refresh data 36 | 37 | 38 | ## How to Contribute 39 | 40 | | Feature | Status | 41 | |---------------------------------------------------------------------------------------|-----------| 42 | | Ingest traces from AWS | ✅ | 43 | | Display tree graph of traces and application | ✅ | 44 | | Display logs and trace details | ✅ | 45 | | Hyperlink into logs | 🙏🏻 | 46 | | Filter logs on click in node graph | 🙏🏻 | 47 | | Change tree graph to a network to better show crossover between traces | 🙏🏻 | 48 | | Refactor to use Typescript | 🙏🏻 | 49 | | Add more metrics to dashboard | 🙏🏻 | 50 | 51 | - ✅ = Ready to use 52 | - ⏳ = In progress 53 | - 🙏🏻 = Looking for contributors 54 | 55 | 56 | ## Contributor Information 57 | 58 | 59 | 60 | 68 | 76 | 84 | 92 |
61 | 62 |
63 | Andrii Karpenko 64 |
65 | 🖇️ 66 | 🐙 67 |
69 | 70 |
71 | Jacob Alarcon 72 |
73 | 🖇️ 74 | 🐙 75 |
77 | 78 |
79 | Bryent Sariwati 80 |
81 | 🖇️ 82 | 🐙 83 |
85 | 86 |
87 | Matt Lusby 88 |
89 | 🖇️ 90 | 🐙 91 |
93 | 94 | - 💻 = Website 95 | - 🖇️ = LinkedIn 96 | - 🐙 = Github 97 | 98 | 99 | ## Press & Publications 100 | -------------------------------------------------------------------------------- /__tests__/playwright.test.js: -------------------------------------------------------------------------------- 1 | import { test } from 'vitest'; 2 | import { chromium } from 'playwright'; 3 | import server from './server-mock'; 4 | 5 | // setting up mock server for tests 6 | beforeAll(() => server.listen()); 7 | afterEach(() => server.resetHandlers()); 8 | afterAll(() => server.close()); 9 | 10 | 11 | test('Home page loads', async ({ expect }) => { 12 | const browser = await chromium.launch(); 13 | const page = await browser.newPage(); 14 | await page.goto('http://localhost:5173'); // Replace with your app's URL 15 | 16 | const pageTitle = await page.title(); 17 | expect(pageTitle).toBe('LambdaPulse'); // Replace with your app's title 18 | 19 | await browser.close(); 20 | }); -------------------------------------------------------------------------------- /__tests__/react.test.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from '@testing-library/react'; 2 | import userEvent from '@testing-library/user-event'; 3 | import React from 'react'; 4 | import '@testing-library/jest-dom'; 5 | import App from '../client/src/App'; 6 | import Login from '../client/src/pages/Login'; 7 | import Signup from '../client/src/pages/Signup'; 8 | import Settings from '../client/src/components/Settings'; 9 | import { BrowserRouter, MemoryRouter } from 'react-router-dom'; 10 | import server from './server-mock'; 11 | 12 | // setting up mock server for tests 13 | beforeAll(() => server.listen()); 14 | afterEach(() => server.resetHandlers()); 15 | afterAll(() => server.close()); 16 | 17 | // testing App.jsx 18 | test('full app rendering/navigating', async () => { 19 | render(, { wrapper: BrowserRouter }); 20 | const user = userEvent.setup(); 21 | 22 | // verify page content for default route (Login page) 23 | expect(screen.getByText(/Login/i)).toBeInTheDocument(); 24 | screen.debug(); 25 | 26 | // verify page content for expected route after navigating (Signup page) 27 | userEvent.click(screen.getByRole('link', { name: /Sign Up/i })); 28 | expect(screen.getByText(/Sign Up/i)).toBeInTheDocument(); 29 | }); 30 | 31 | 32 | 33 | describe('correctly render Login', () => { 34 | beforeEach(() => render(, { wrapper: BrowserRouter })); 35 | 36 | test('correctly renders input fields in Login', async () => { 37 | // check email input exists 38 | expect(screen.getByLabelText(/email/i)).toHaveAttribute('type', 'email'); 39 | // check password input exists 40 | expect(screen.getByLabelText(/password/i)).toHaveAttribute( 41 | 'type', 42 | 'password' 43 | ); 44 | // password field not accessible over selector, input with type password has no role 45 | const inputs = await screen.getAllByRole('textbox'); 46 | expect(inputs.length).toEqual(1); 47 | }); 48 | 49 | test('correctly renders 2 buttons in Login', async () => { 50 | const button = await screen.getAllByRole('button'); 51 | expect(button.length).toEqual(2); 52 | }); 53 | 54 | test('correctly renders link to Signup in Login', async () => { 55 | const link = await screen.getAllByRole('link'); 56 | expect(link.length).toEqual(1); 57 | }); 58 | }); 59 | 60 | // testing Signup.jsx 61 | 62 | describe('correctly render Signup', () => { 63 | beforeEach(() => render(, { wrapper: BrowserRouter })); 64 | 65 | test('correctly renders input fields in Signup', async () => { 66 | // check username input exists 67 | expect(screen.getByLabelText(/First and Last Name/i)).toHaveAttribute( 68 | 'type', 69 | 'text' 70 | ); 71 | // check email input exists 72 | expect(screen.getByLabelText(/Email/i)).toHaveAttribute('type', 'email'); 73 | // check password input exists 74 | const passwordInputs = screen.getAllByLabelText(/Password/i); 75 | const passwordInput = passwordInputs.find( 76 | (input) => input.getAttribute('id') === 'password' 77 | ); 78 | expect(passwordInput).toHaveAttribute('type', 'password'); 79 | // password confirmation input exists 80 | const confirmPasswordInputs = screen.getAllByLabelText(/Confirm Password/i); 81 | const confirmPasswordInput = confirmPasswordInputs.find( 82 | (input) => input.getAttribute('id') === 'confirm-password' 83 | ); 84 | expect(confirmPasswordInput).toHaveAttribute('type', 'password'); 85 | const inputs = await screen.getAllByRole('textbox'); 86 | expect(inputs.length).toEqual(2); 87 | }); 88 | 89 | test('correctly renders 2 button in Signup', async () => { 90 | const button = await screen.getAllByRole('button'); 91 | expect(button.length).toEqual(2); 92 | }); 93 | 94 | test('correctly renders link to Login in Signup', async () => { 95 | const link = await screen.getAllByRole('button', { 96 | name: /LOGIN/i, 97 | }); 98 | expect(link.length).toEqual(1); 99 | }); 100 | }); 101 | 102 | describe('correctly render Settings', () => { 103 | beforeEach(() => render(, { wrapper: BrowserRouter })); 104 | 105 | test('correctly renders header in Settings', () => { 106 | expect( 107 | screen.getByRole('heading', { name: /settings/i }) 108 | ).toBeInTheDocument(); 109 | }); 110 | 111 | test('correctly renders current ARN in Settings', () => { 112 | const currentArnText = screen.getByText(/current arn/i); 113 | expect(currentArnText).toBeInTheDocument(); 114 | }); 115 | 116 | test('correctly renders button and input field in Settings', async () => { 117 | const button = await screen.getAllByRole('button'); 118 | expect(button.length).toEqual(1); 119 | 120 | const input = screen.getByPlaceholderText('ARN Key'); 121 | expect(input).toBeInTheDocument(); 122 | expect(input).toHaveAttribute('type', 'text'); 123 | }); 124 | 125 | test('correctly renders submit button in Settings', () => { 126 | const submitButton = screen.getByRole('button', { name: /set arn/i }); 127 | expect(submitButton).toBeInTheDocument(); 128 | }); 129 | }); -------------------------------------------------------------------------------- /__tests__/server-mock.js: -------------------------------------------------------------------------------- 1 | import { rest } from 'msw'; 2 | import { setupServer } from 'msw/node'; 3 | 4 | const server = setupServer( 5 | rest.get('/getCurrentArn', (req, res, ctx) => { 6 | return res( 7 | ctx.json({ rows: [{ role_arn: 'arn:aws:mock:arn:123456789012' }] }) 8 | ); 9 | }), 10 | ); 11 | 12 | export default server; -------------------------------------------------------------------------------- /__tests__/tests.test.js: -------------------------------------------------------------------------------- 1 | const sleep = time => new Promise(resolve => setTimeout(resolve, time)) 2 | 3 | describe("Filter function", () => { 4 | test("it should be true", () => { 5 | const variable = true; 6 | expect(variable).toEqual(true) 7 | }) 8 | }); 9 | 10 | 11 | 12 | describe("Test APIs", () => { 13 | const request = require('supertest'); 14 | let rand = Math.random(); 15 | let token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpZCI6MjcsImlhdCI6MTY4Mjc5MzI1NiwiZXhwIjoxNjgyNzk2ODU2fQ.SDQcMyhi-4Bw4hC9kMrlaOvfMU65tLgKUMqYprS1X7A' 16 | 17 | test('Basic API call returns hello', () => { 18 | request('http://localhost:3000') 19 | .get('/api/') 20 | .expect(200) 21 | .expect(function(res) { 22 | if (!res.body == 'hello') throw new Error("Expected 'hello' body!"); 23 | }) 24 | .end(function(err, res) { 25 | if (err) throw err; 26 | })}) 27 | test('Creates a user', async () => { 28 | request('http://localhost:3000') 29 | .post('/createUser') 30 | .send({fullName:'abc' + rand, email:'abc' + rand + '@abc.abc', password:'abc'}) 31 | .expect(201) 32 | .end(function(err, res) { 33 | if (err) throw err; 34 | }) 35 | }) 36 | 37 | test('Waits 1 second for user to be created', async () => { 38 | await sleep(1000); 39 | }) 40 | 41 | test('Verifies a user', async () => { 42 | request('http://localhost:3000') 43 | .post('/verifyUser') 44 | .send({email:'abc' + rand + '@abc.abc', password:'abc'}) 45 | .expect(200) 46 | .end(function(err, res) { 47 | if (err) throw err; 48 | }) 49 | }) 50 | 51 | test('New user should not have an ARN yet', async () => { 52 | request('http://localhost:3000') 53 | .get('/getCurrentArn') 54 | .set('Cookie', ['token', token]) 55 | .expect(200) 56 | .expect((res) => { 57 | (res.body.rows).toBeDefined(); 58 | res.body.should.have.property("rows", []); 59 | }) 60 | .end(function(err, res) { 61 | 62 | if (err) throw err; 63 | }) 64 | }) 65 | }) -------------------------------------------------------------------------------- /client/public/pulse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/App.css: -------------------------------------------------------------------------------- 1 | #root { 2 | 3 | margin: 0; 4 | text-align: center; 5 | } 6 | 7 | .logo { 8 | height: 6em; 9 | padding: 1.5em; 10 | will-change: filter; 11 | transition: filter 300ms; 12 | } 13 | .logo:hover { 14 | filter: drop-shadow(0 0 2em #ff3300aa); 15 | } 16 | .logo.react:hover { 17 | filter: drop-shadow(0 0 2em #ff3300aa); 18 | } 19 | 20 | @keyframes logo-spin { 21 | from { 22 | transform: rotate(0deg); 23 | } 24 | to { 25 | transform: rotate(360deg); 26 | } 27 | } 28 | 29 | @media (prefers-reduced-motion: no-preference) { 30 | a:nth-of-type(2) .logo { 31 | animation: logo-spin infinite 20s linear; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /client/src/App.jsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState } from 'react'; 3 | import './App.css'; 4 | import { Routes, Route } from 'react-router-dom'; 5 | import Login from './pages/Login'; 6 | import Signup from './pages/Signup'; 7 | import About from './pages/About/About'; 8 | 9 | import Dashboard from './pages/Dashboard/Dashboard'; 10 | 11 | function App() { 12 | return ( 13 | 14 | } /> 15 | } /> 16 | } /> 17 | } /> 18 | {/* }/> */} 19 | 20 | ); 21 | } 22 | 23 | export default App; 24 | -------------------------------------------------------------------------------- /client/src/apiBaseUrl.js: -------------------------------------------------------------------------------- 1 | export function getApiBaseUrl() { 2 | if (import.meta.env.MODE === 'test') { 3 | return 'http://localhost:3000'; 4 | } 5 | return ''; 6 | } -------------------------------------------------------------------------------- /client/src/assets/apigatewayendpoint.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/chart-bar-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /client/src/assets/client-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /client/src/assets/dynamoDB.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/assets/emptyIcon.svg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LambdaPulse/f6ef43b6567ed2d02946abd5edc3c129628930b2/client/src/assets/emptyIcon.svg -------------------------------------------------------------------------------- /client/src/assets/error-905.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/assets/error-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/src/assets/home-1391-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | home [#1391] 6 | Created with Sketch. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/src/assets/lambdaFunc.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/assets/list-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/logout-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/map-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /client/src/assets/network-2-1118-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | network_2 [#1118] 6 | Created with Sketch. 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /client/src/assets/pulse-1.1s-200px.svg: -------------------------------------------------------------------------------- 1 | 2 | pulsepulse,wave,heartbeat,signal,voltage,currentbypfxhat -------------------------------------------------------------------------------- /client/src/assets/pulse.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /client/src/assets/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/refresh-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /client/src/assets/sampleData.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "66083ccfc311f302", 3 | "name": "dev-todo-list-app/dev", 4 | "subsegments": [ 5 | { 6 | "id": "4c17ecc1b6cd296a", 7 | "name": "Lambda", 8 | "start_time": 1681574429.581, 9 | "end_time": 1681574431.023, 10 | "http": { 11 | "request": { 12 | "url": "https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask/invocations", 13 | "method": "POST" 14 | }, 15 | "response": { 16 | "status": 201, 17 | "content_length": 2 18 | } 19 | }, 20 | "aws": { 21 | "function_name": "todo-list-app-dev-postTask", 22 | "region": "us-east-1", 23 | "operation": "Invoke", 24 | "resource_names": [ 25 | "todo-list-app-dev-postTask" 26 | ] 27 | }, 28 | "namespace": "aws" 29 | } 30 | ], 31 | "children": [ 32 | { 33 | "id": "5f8d1a23aa89bd66", 34 | "name": "todo-list-app-dev-postTask", 35 | "parent_id": "4c17ecc1b6cd296a", 36 | "children": [ 37 | { 38 | "id": "03e476fc59b9eecd", 39 | "name": "todo-list-app-dev-postTask", 40 | "parent_id": "5f8d1a23aa89bd66", 41 | "subsegments": [ 42 | { 43 | "id": "8a2b10d894511fc9", 44 | "name": "Initialization", 45 | "start_time": 1681574429.970087, 46 | "end_time": 1681574430.8215926, 47 | "aws": { 48 | "function_arn": "arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask" 49 | } 50 | }, 51 | { 52 | "id": "9a00f74249b3df61", 53 | "name": "Invocation", 54 | "start_time": 1681574430.8230484, 55 | "end_time": 1681574431.0199275, 56 | "aws": { 57 | "function_arn": "arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask" 58 | }, 59 | "subsegments": [ 60 | { 61 | "id": "72196dbd1971fbad", 62 | "name": "Lambda", 63 | "start_time": 1681574430.93, 64 | "end_time": 1681574431.015, 65 | "http": { 66 | "response": { 67 | "status": 200 68 | } 69 | }, 70 | "aws": { 71 | "retries": 0, 72 | "status_code": 200, 73 | "function_name": "todo-list-app-dev-sendNotification", 74 | "region": "us-east-1", 75 | "operation": "Invoke", 76 | "request_id": "1a61d1e3-4df6-4c2e-9beb-e150bc55ff34", 77 | "resource_names": [ 78 | "todo-list-app-dev-sendNotification" 79 | ] 80 | }, 81 | "namespace": "aws" 82 | }, 83 | { 84 | "id": "b65ecbebf0c80be7", 85 | "name": "DynamoDB", 86 | "start_time": 1681574430.842, 87 | "end_time": 1681574430.924, 88 | "http": { 89 | "response": { 90 | "status": 200 91 | } 92 | }, 93 | "aws": { 94 | "retries": 0, 95 | "region": "us-east-1", 96 | "operation": "PutItem", 97 | "request_id": "ONUHTPGP3PBVIN2UJI284UBUA7VV4KQNSO5AEMVJF66Q9ASUAAJG", 98 | "table_name": "todo-list-tasks", 99 | "resource_names": [ 100 | "todo-list-tasks" 101 | ] 102 | }, 103 | "namespace": "aws" 104 | } 105 | ] 106 | }, 107 | { 108 | "id": "03cea9c3be41a0ba", 109 | "name": "Overhead", 110 | "start_time": 1681574431.0199893, 111 | "end_time": 1681574431.0205321, 112 | "aws": { 113 | "function_arn": "arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask" 114 | } 115 | } 116 | ], 117 | "children": [ 118 | { 119 | "id": "3a049070bf15849c", 120 | "name": "todo-list-app-dev-sendNotification", 121 | "parent_id": "72196dbd1971fbad", 122 | "children": [ 123 | { 124 | "id": "4dd5002f3760b1c7", 125 | "name": "todo-list-app-dev-sendNotification", 126 | "parent_id": "3a049070bf15849c", 127 | "subsegments": [ 128 | { 129 | "id": "a53cba167510c8be", 130 | "name": "Invocation", 131 | "start_time": 1681574430.9920678, 132 | "end_time": 1681574430.9951615, 133 | "aws": { 134 | "function_arn": "arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification" 135 | } 136 | }, 137 | { 138 | "id": "6318618ac9af4267", 139 | "name": "Overhead", 140 | "start_time": 1681574430.9951978, 141 | "end_time": 1681574431.0279648, 142 | "aws": { 143 | "function_arn": "arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification" 144 | } 145 | } 146 | ], 147 | "children": [], 148 | "origin": "AWS::Lambda::Function" 149 | } 150 | ], 151 | "origin": "AWS::Lambda", 152 | "http": { 153 | "response": { 154 | "status": 200 155 | } 156 | } 157 | }, 158 | { 159 | "id": "3dd87a2724860b51", 160 | "name": "DynamoDB", 161 | "parent_id": "b65ecbebf0c80be7", 162 | "children": [], 163 | "origin": "AWS::DynamoDB::Table", 164 | "http": { 165 | "response": { 166 | "status": 200 167 | } 168 | } 169 | } 170 | ], 171 | "origin": "AWS::Lambda::Function" 172 | } 173 | ], 174 | "origin": "AWS::Lambda", 175 | "http": { 176 | "response": { 177 | "status": 200 178 | } 179 | } 180 | } 181 | ], 182 | "origin": "AWS::ApiGateway::Stage", 183 | "http": { 184 | "request": { 185 | "url": "https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/add-task", 186 | "method": "POST", 187 | "user_agent": "PostmanRuntime/7.31.3", 188 | "client_ip": "172.58.229.173", 189 | "x_forwarded_for": true 190 | }, 191 | "response": { 192 | "status": 201, 193 | "content_length": 0 194 | } 195 | } 196 | } -------------------------------------------------------------------------------- /client/src/assets/sampleLog.js: -------------------------------------------------------------------------------- 1 | const sampleLog = [ 2 | { 3 | "@timestamp": "2023-04-18 15:32:46.721", 4 | "@message": "END RequestId: ea643eb8-1547-41f5-8c51-4ff6abe3e0d1\n", 5 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 6 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 7 | }, 8 | { 9 | "@timestamp": "2023-04-18 15:32:46.721", 10 | "@message": "REPORT RequestId: ea643eb8-1547-41f5-8c51-4ff6abe3e0d1\tDuration: 20.39 ms\tBilled Duration: 21 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\tInit Duration: 178.50 ms\t\nXRAY TraceId: 1-643eb81e-7b15c13c3bff33247638ad8d\tSegmentId: 4c72ac137fd02fb0\tSampled: true\t\n", 11 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 12 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 13 | }, 14 | { 15 | "@timestamp": "2023-04-18 15:32:46.703", 16 | "@message": "2023-04-18T15:32:46.703Z\tea643eb8-1547-41f5-8c51-4ff6abe3e0d1\tINFO\tRunning again at 2023-04-18T15:32:46.702Z\n", 17 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 18 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 19 | }, 20 | { 21 | "@timestamp": "2023-04-18 15:32:46.702", 22 | "@message": "2023-04-18T15:32:46.702Z\tea643eb8-1547-41f5-8c51-4ff6abe3e0d1\tINFO\tReceived event: {\n \"key1\": \"hello world\"\n}\n", 23 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 24 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 25 | }, 26 | { 27 | "@timestamp": "2023-04-18 15:32:46.701", 28 | "@message": "START RequestId: ea643eb8-1547-41f5-8c51-4ff6abe3e0d1 Version: $LATEST\n", 29 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 30 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 31 | }, 32 | { 33 | "@timestamp": "2023-04-18 15:32:46.698", 34 | "@message": "2023-04-18T15:32:46.697Z\tundefined\tINFO\tLoading function\n", 35 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 36 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 37 | }, 38 | { 39 | "@timestamp": "2023-04-18 15:32:46.522", 40 | "@message": "INIT_START Runtime Version: nodejs:14.v29\tRuntime Version ARN: arn:aws:lambda:us-east-2::runtime:be6b7a67cb4533b2e602f284c4e41058155b081b5879c71929b33e71c124b81d\n", 41 | "@logStream": "2023/04/18/[$LATEST]2a9d1984cf9d476ca0657779c35a92e0", 42 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 43 | }, 44 | { 45 | "@timestamp": "2023-04-18 15:22:46.759", 46 | "@message": "REPORT RequestId: ea643eb5-bd47-41f5-8c51-4ff6abe3e0d1\tDuration: 30.31 ms\tBilled Duration: 31 ms\tMemory Size: 128 MB\tMax Memory Used: 58 MB\tInit Duration: 201.04 ms\t\nXRAY TraceId: 1-643eb5c6-519368cf33b3601f33ca1925\tSegmentId: 304bc3827bc8267a\tSampled: true\t\n", 47 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 48 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 49 | }, 50 | { 51 | "@timestamp": "2023-04-18 15:22:46.759", 52 | "@message": "END RequestId: ea643eb5-bd47-41f5-8c51-4ff6abe3e0d1\n", 53 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 54 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 55 | }, 56 | { 57 | "@timestamp": "2023-04-18 15:22:46.732", 58 | "@message": "2023-04-18T15:22:46.732Z\tea643eb5-bd47-41f5-8c51-4ff6abe3e0d1\tINFO\tRunning again at 2023-04-18T15:22:46.731Z\n", 59 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 60 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 61 | }, 62 | { 63 | "@timestamp": "2023-04-18 15:22:46.731", 64 | "@message": "2023-04-18T15:22:46.730Z\tea643eb5-bd47-41f5-8c51-4ff6abe3e0d1\tINFO\tReceived event: {\n \"key1\": \"hello world\"\n}\n", 65 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 66 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 67 | }, 68 | { 69 | "@timestamp": "2023-04-18 15:22:46.729", 70 | "@message": "START RequestId: ea643eb5-bd47-41f5-8c51-4ff6abe3e0d1 Version: $LATEST\n", 71 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 72 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 73 | }, 74 | { 75 | "@timestamp": "2023-04-18 15:22:46.726", 76 | "@message": "2023-04-18T15:22:46.726Z\tundefined\tINFO\tLoading function\n", 77 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 78 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 79 | }, 80 | { 81 | "@timestamp": "2023-04-18 15:22:46.527", 82 | "@message": "INIT_START Runtime Version: nodejs:14.v29\tRuntime Version ARN: arn:aws:lambda:us-east-2::runtime:be6b7a67cb4533b2e602f284c4e41058155b081b5879c71929b33e71c124b81d\n", 83 | "@logStream": "2023/04/18/[$LATEST]22bcb3249a944a3c885c613981af10cc", 84 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 85 | }, 86 | { 87 | "@timestamp": "2023-04-18 15:12:46.722", 88 | "@message": "END RequestId: ea643eb3-6547-41f5-8c51-4ff6abe3e0d1\n", 89 | "@logStream": "2023/04/18/[$LATEST]7e72c81cd4204d338d2fc53aa1a674a0", 90 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 91 | }, 92 | { 93 | "@timestamp": "2023-04-18 15:12:46.722", 94 | "@message": "REPORT RequestId: ea643eb3-6547-41f5-8c51-4ff6abe3e0d1\tDuration: 26.52 ms\tBilled Duration: 27 ms\tMemory Size: 128 MB\tMax Memory Used: 57 MB\tInit Duration: 162.53 ms\t\nXRAY TraceId: 1-643eb36e-5e97e0837055cb18657c248f\tSegmentId: 0e4b3eb03e3ab649\tSampled: true\t\n", 95 | "@logStream": "2023/04/18/[$LATEST]7e72c81cd4204d338d2fc53aa1a674a0", 96 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 97 | }, 98 | { 99 | "@timestamp": "2023-04-18 15:12:46.700", 100 | "@message": "2023-04-18T15:12:46.700Z\tea643eb3-6547-41f5-8c51-4ff6abe3e0d1\tINFO\tRunning again at 2023-04-18T15:12:46.697Z\n", 101 | "@logStream": "2023/04/18/[$LATEST]7e72c81cd4204d338d2fc53aa1a674a0", 102 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 103 | }, 104 | { 105 | "@timestamp": "2023-04-18 15:12:46.697", 106 | "@message": "2023-04-18T15:12:46.697Z\tea643eb3-6547-41f5-8c51-4ff6abe3e0d1\tINFO\tReceived event: {\n \"key1\": \"hello world\"\n}\n", 107 | "@logStream": "2023/04/18/[$LATEST]7e72c81cd4204d338d2fc53aa1a674a0", 108 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 109 | }, 110 | { 111 | "@timestamp": "2023-04-18 15:12:46.696", 112 | "@message": "START RequestId: ea643eb3-6547-41f5-8c51-4ff6abe3e0d1 Version: $LATEST\n", 113 | "@logStream": "2023/04/18/[$LATEST]7e72c81cd4204d338d2fc53aa1a674a0", 114 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 115 | }, 116 | { 117 | "@timestamp": "2023-04-18 15:12:46.692", 118 | "@message": "2023-04-18T15:12:46.692Z\tundefined\tINFO\tLoading function\n", 119 | "@logStream": "2023/04/18/[$LATEST]7e72c81cd4204d338d2fc53aa1a674a0", 120 | "@log": "637410497107:/aws/lambda/run-every-10-minutes" 121 | } 122 | ] 123 | 124 | export default sampleLog; -------------------------------------------------------------------------------- /client/src/assets/sampleTraces.json: -------------------------------------------------------------------------------- 1 | [{"id":"52c5a1407164e610","name":"dev-todo-list-app/dev","time_taken":1555.999994277954,"subsegments":[{"id":"3d165f951d5e5777","name":"Lambda","start_time":1681935065.396,"end_time":1681935066.948,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask/invocations","method":"DELETE"}},"aws":{"function_name":"todo-list-app-dev-deleteTask","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-deleteTask"]},"namespace":"aws"}],"children":[{"id":"52a73c3be901f5e1","name":"todo-list-app-dev-deleteTask","parent_id":"3d165f951d5e5777","time_taken":1523.9999294281006,"children":[{"id":"42c55a110fc51738","name":"todo-list-app-dev-deleteTask","parent_id":"52a73c3be901f5e1","time_taken":108.98804664611816,"subsegments":[{"id":"4595896ffbd056c6","name":"Overhead","start_time":1681935066.9468117,"end_time":1681935066.9471943,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"}},{"id":"41bcbcb0e8098d58","name":"Initialization","start_time":1681935065.8709512,"end_time":1681935066.836363,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"}},{"id":"730443d6986eac39","name":"Invocation","start_time":1681935066.8388786,"end_time":1681935066.9467273,"error":true,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"},"subsegments":[{"id":"e80daf342f9d312e","name":"DynamoDB","start_time":1681935066.856,"end_time":1681935066.939,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"GetItem","request_id":"5N0SO925L0MQL3TLDL5PFA8S1FVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]}],"children":[{"id":"06667def0045bc80","name":"DynamoDB","parent_id":"e80daf342f9d312e","time_taken":82.99994468688965,"children":[],"origin":"AWS::DynamoDB::Table","http":{"response":{"status":200}},"fullData":{"Document":{"id":"06667def0045bc80","name":"DynamoDB","start_time":1681935066.856,"trace_id":"1-64404ad9-6d57ad881ff49788478d7743","end_time":1681935066.939,"parent_id":"e80daf342f9d312e","inferred":true,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"GetItem","request_id":"5N0SO925L0MQL3TLDL5PFA8S1FVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"origin":"AWS::DynamoDB::Table"},"Id":"06667def0045bc80"}}],"origin":"AWS::Lambda::Function","fullData":{"Document":{"id":"42c55a110fc51738","name":"todo-list-app-dev-deleteTask","start_time":1681935066.8385012,"trace_id":"1-64404ad9-6d57ad881ff49788478d7743","end_time":1681935066.9474893,"parent_id":"52a73c3be901f5e1","error":true,"aws":{"account_id":"263792328682","function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask","resource_names":["todo-list-app-dev-deleteTask"]},"origin":"AWS::Lambda::Function","cause":{"working_directory":"/var/task","paths":["/var/task/src/taskController.js","internal/process/task_queues.js","/var/task/src/handler.js"],"exceptions":[{"message":"Error while checking task existence","type":"Error","stack":[{"path":"/var/task/src/taskController.js","line":63,"label":"Object.taskController.deleteTask"},{"path":"internal/process/task_queues.js","line":95,"label":"processTicksAndRejections"},{"path":"/var/task/src/handler.js","line":22,"label":"async Runtime.module.exports.deleteTask [as handler]"}]}]},"subsegments":[{"id":"4595896ffbd056c6","name":"Overhead","start_time":1681935066.9468117,"end_time":1681935066.9471943,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"}},{"id":"41bcbcb0e8098d58","name":"Initialization","start_time":1681935065.8709512,"end_time":1681935066.836363,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"}},{"id":"730443d6986eac39","name":"Invocation","start_time":1681935066.8388786,"end_time":1681935066.9467273,"error":true,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"},"subsegments":[{"id":"e80daf342f9d312e","name":"DynamoDB","start_time":1681935066.856,"end_time":1681935066.939,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"GetItem","request_id":"5N0SO925L0MQL3TLDL5PFA8S1FVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]}]},"Id":"42c55a110fc51738"}}],"origin":"AWS::Lambda","http":{"response":{"status":200}},"fullData":{"Document":{"id":"52a73c3be901f5e1","name":"todo-list-app-dev-deleteTask","start_time":1681935065.423,"trace_id":"1-64404ad9-6d57ad881ff49788478d7743","end_time":1681935066.947,"parent_id":"3d165f951d5e5777","error":true,"http":{"response":{"status":200}},"aws":{"request_id":"ef5ecf8b-9299-4534-b523-b1f63125fd6b"},"origin":"AWS::Lambda","resource_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask"},"Id":"52a73c3be901f5e1"}}],"origin":"AWS::ApiGateway::Stage","http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/delete-task","method":"DELETE","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":502,"content_length":0}},"fullData":{"Document":{"id":"52c5a1407164e610","name":"dev-todo-list-app/dev","start_time":1681935065.393,"trace_id":"1-64404ad9-6d57ad881ff49788478d7743","end_time":1681935066.949,"fault":true,"error":false,"throttle":false,"http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/delete-task","method":"DELETE","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":502,"content_length":0}},"aws":{"api_gateway":{"account_id":"263792328682","rest_api_id":"cm8rpw34l8","stage":"dev","request_id":"1add81cf-251a-4ca3-b7e2-07ec2baa41ea"}},"annotations":{"aws:api_id":"cm8rpw34l8","aws:api_stage":"dev"},"metadata":{"default":{"extended_request_id":"DpCiAEbaoAMFmKw=","request_id":"1add81cf-251a-4ca3-b7e2-07ec2baa41ea"}},"origin":"AWS::ApiGateway::Stage","resource_arn":"arn:aws:apigateway:us-east-1::/restapis/cm8rpw34l8/stages/dev","subsegments":[{"id":"3d165f951d5e5777","name":"Lambda","start_time":1681935065.396,"end_time":1681935066.948,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-deleteTask/invocations","method":"DELETE"}},"aws":{"function_name":"todo-list-app-dev-deleteTask","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-deleteTask"]},"namespace":"aws"}]},"Id":"52c5a1407164e610"},"cold_start":true,"logs":[{"eventId":"37508405366027811214982002581019491958743208845931511811","ingestionTime":1681935070918,"logStreamName":"2023/04/19/[$LATEST]d46eb8a5d88c4b229ab5c3c8326666fd","message":"START RequestId: ef5ecf8b-9299-4534-b523-b1f63125fd6b Version: $LATEST\n","timestamp":1681935066838},{"eventId":"37508405368413990951224779257163813813916583527071416324","ingestionTime":1681935070918,"logStreamName":"2023/04/19/[$LATEST]d46eb8a5d88c4b229ab5c3c8326666fd","message":"2023-04-19T20:11:06.945Z\tef5ecf8b-9299-4534-b523-b1f63125fd6b\tERROR\tInvoke Error \t{\"errorType\":\"Error\",\"errorMessage\":\"Error while checking task existence\",\"stack\":[\"Error: Error while checking task existence\",\" at Object.taskController.deleteTask (/var/task/src/taskController.js:63:11)\",\" at processTicksAndRejections (internal/process/task_queues.js:95:5)\",\" at async Runtime.module.exports.deleteTask [as handler] (/var/task/src/handler.js:22:3)\"]}\n","timestamp":1681935066945},{"eventId":"37508405368458592441621840503446885250461880250083377157","ingestionTime":1681935070918,"logStreamName":"2023/04/19/[$LATEST]d46eb8a5d88c4b229ab5c3c8326666fd","message":"END RequestId: ef5ecf8b-9299-4534-b523-b1f63125fd6b\n","timestamp":1681935066947},{"eventId":"37508405368458592441621840503446885250461880250083377158","ingestionTime":1681935070918,"logStreamName":"2023/04/19/[$LATEST]d46eb8a5d88c4b229ab5c3c8326666fd","message":"REPORT RequestId: ef5ecf8b-9299-4534-b523-b1f63125fd6b\tDuration: 109.20 ms\tBilled Duration: 110 ms\tMemory Size: 1024 MB\tMax Memory Used: 102 MB\tInit Duration: 965.84 ms\t\nXRAY TraceId: 1-64404ad9-6d57ad881ff49788478d7743\tSegmentId: 42c55a110fc51738\tSampled: true\t\n","timestamp":1681935066947}]},{"id":"1f0bec4b7fb90cc2","name":"dev-todo-list-app/dev","time_taken":3289.9999618530273,"subsegments":[{"id":"44c5823f71d5e7d5","name":"Lambda","start_time":1681935060.603,"end_time":1681935063.891,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask/invocations","method":"POST"},"response":{"status":201,"content_length":2}},"aws":{"function_name":"todo-list-app-dev-postTask","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-postTask"]},"namespace":"aws"}],"children":[{"id":"27f8a658e75effa8","name":"todo-list-app-dev-postTask","parent_id":"44c5823f71d5e7d5","time_taken":3253.999948501587,"children":[{"id":"31cd7b89660873fd","name":"todo-list-app-dev-postTask","parent_id":"27f8a658e75effa8","time_taken":1920.4912185668945,"subsegments":[{"id":"c8894d95314d853e","name":"Initialization","start_time":1681935061.1361778,"end_time":1681935061.966683,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"}},{"id":"fc719b862864178e","name":"Overhead","start_time":1681935063.889133,"end_time":1681935063.8895288,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"}},{"id":"a73ac0d19515499c","name":"Invocation","start_time":1681935061.9694362,"end_time":1681935063.8890877,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"},"subsegments":[{"id":"261ebf4a7724eccf","name":"Lambda","start_time":1681935062.151,"end_time":1681935063.887,"http":{"response":{"status":200}},"aws":{"retries":0,"status_code":200,"function_name":"todo-list-app-dev-sendNotification","region":"us-east-1","operation":"Invoke","request_id":"103426eb-7689-467f-a30b-84f305422aab","resource_names":["todo-list-app-dev-sendNotification"]},"namespace":"aws"},{"id":"31064f9d6fa52bfd","name":"DynamoDB","start_time":1681935061.973,"end_time":1681935062.111,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"PutItem","request_id":"FLBDI2VSLF1PDJ6OSSIV8PURH7VV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]}],"children":[{"id":"435f5ac626c10655","name":"todo-list-app-dev-sendNotification","parent_id":"261ebf4a7724eccf","time_taken":1622.999906539917,"children":[{"id":"5b9975645bcaddd2","name":"todo-list-app-dev-sendNotification","parent_id":"435f5ac626c10655","time_taken":214.15376663208008,"subsegments":[{"id":"8dc76a9b28711ebd","name":"Overhead","start_time":1681935063.8816533,"end_time":1681935063.8820937,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"}},{"id":"40c0e2b41e886ec5","name":"Initialization","start_time":1681935062.819072,"end_time":1681935063.6663818,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"}},{"id":"70c696ec3768b80a","name":"Invocation","start_time":1681935063.6685655,"end_time":1681935063.8816164,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"},"subsegments":[{"id":"58293a6b4d3372bb","name":"SES","start_time":1681935063.676,"end_time":1681935063.865,"fault":true,"error":true,"http":{"response":{"status":400}},"aws":{"retries":0,"region":"us-east-1","operation":"SendEmail","request_id":"f55e224d-f809-42c1-aa2c-1a410abdf185"},"namespace":"aws","cause":{"working_directory":"/var/task","exceptions":[{"id":"e938fefc91105dd3","message":"Email address is not verified. The following identities failed the check in region US-EAST-1: karpuxa@gmail.com","type":"MessageRejected","remote":true,"stack":[{"path":"/var/task/node_modules/aws-xray-sdk-core/dist/lib/patchers/aws_p.js","line":64,"label":"features.constructor.captureAWSRequest [as customRequestHandler]"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":298,"label":"features.constructor.addAllRequestListeners"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":222,"label":"features.constructor.makeRequest"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":706,"label":"features.constructor.svc. [as sendEmail]"},{"path":"/var/task/src/handler.js","line":63,"label":"Runtime.module.exports.sendNotification [as handler]"},{"path":"/var/runtime/Runtime.js","line":74,"label":"Runtime.handleOnceNonStreaming"}]}]}}]}],"children":[{"id":"1ee96ebb22b331db","name":"SES","parent_id":"58293a6b4d3372bb","time_taken":188.99989128112793,"children":[],"origin":"AWS::SES","http":{"response":{"status":400}},"fullData":{"Document":{"id":"1ee96ebb22b331db","name":"SES","start_time":1681935063.676,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935063.865,"parent_id":"58293a6b4d3372bb","inferred":true,"error":true,"http":{"response":{"status":400}},"aws":{"retries":0,"region":"us-east-1","operation":"SendEmail","request_id":"f55e224d-f809-42c1-aa2c-1a410abdf185"},"origin":"AWS::SES","cause":{"message":"Email address is not verified. The following identities failed the check in region US-EAST-1: karpuxa@gmail.com"}},"Id":"1ee96ebb22b331db"}}],"origin":"AWS::Lambda::Function","fullData":{"Document":{"id":"5b9975645bcaddd2","name":"todo-list-app-dev-sendNotification","start_time":1681935063.668181,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935063.8823347,"parent_id":"435f5ac626c10655","aws":{"account_id":"263792328682","function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification","resource_names":["todo-list-app-dev-sendNotification"]},"origin":"AWS::Lambda::Function","subsegments":[{"id":"8dc76a9b28711ebd","name":"Overhead","start_time":1681935063.8816533,"end_time":1681935063.8820937,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"}},{"id":"40c0e2b41e886ec5","name":"Initialization","start_time":1681935062.819072,"end_time":1681935063.6663818,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"}},{"id":"70c696ec3768b80a","name":"Invocation","start_time":1681935063.6685655,"end_time":1681935063.8816164,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"},"subsegments":[{"id":"58293a6b4d3372bb","name":"SES","start_time":1681935063.676,"end_time":1681935063.865,"fault":true,"error":true,"http":{"response":{"status":400}},"aws":{"retries":0,"region":"us-east-1","operation":"SendEmail","request_id":"f55e224d-f809-42c1-aa2c-1a410abdf185"},"namespace":"aws","cause":{"working_directory":"/var/task","exceptions":[{"id":"e938fefc91105dd3","message":"Email address is not verified. The following identities failed the check in region US-EAST-1: karpuxa@gmail.com","type":"MessageRejected","remote":true,"stack":[{"path":"/var/task/node_modules/aws-xray-sdk-core/dist/lib/patchers/aws_p.js","line":64,"label":"features.constructor.captureAWSRequest [as customRequestHandler]"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":298,"label":"features.constructor.addAllRequestListeners"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":222,"label":"features.constructor.makeRequest"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":706,"label":"features.constructor.svc. [as sendEmail]"},{"path":"/var/task/src/handler.js","line":63,"label":"Runtime.module.exports.sendNotification [as handler]"},{"path":"/var/runtime/Runtime.js","line":74,"label":"Runtime.handleOnceNonStreaming"}]}]}}]}]},"Id":"5b9975645bcaddd2"}}],"origin":"AWS::Lambda","http":{"response":{"status":200}},"fullData":{"Document":{"id":"435f5ac626c10655","name":"todo-list-app-dev-sendNotification","start_time":1681935062.26,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935063.883,"parent_id":"261ebf4a7724eccf","http":{"response":{"status":200}},"aws":{"request_id":"103426eb-7689-467f-a30b-84f305422aab"},"origin":"AWS::Lambda","resource_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"},"Id":"435f5ac626c10655"}},{"id":"33e2656b12736599","name":"DynamoDB","parent_id":"31064f9d6fa52bfd","time_taken":138.0000114440918,"children":[],"origin":"AWS::DynamoDB::Table","http":{"response":{"status":200}},"fullData":{"Document":{"id":"33e2656b12736599","name":"DynamoDB","start_time":1681935061.973,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935062.111,"parent_id":"31064f9d6fa52bfd","inferred":true,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"PutItem","request_id":"FLBDI2VSLF1PDJ6OSSIV8PURH7VV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"origin":"AWS::DynamoDB::Table"},"Id":"33e2656b12736599"}}],"origin":"AWS::Lambda::Function","fullData":{"Document":{"id":"31cd7b89660873fd","name":"todo-list-app-dev-postTask","start_time":1681935061.969155,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935063.8896463,"parent_id":"27f8a658e75effa8","aws":{"account_id":"263792328682","function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask","resource_names":["todo-list-app-dev-postTask"]},"origin":"AWS::Lambda::Function","subsegments":[{"id":"c8894d95314d853e","name":"Initialization","start_time":1681935061.1361778,"end_time":1681935061.966683,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"}},{"id":"fc719b862864178e","name":"Overhead","start_time":1681935063.889133,"end_time":1681935063.8895288,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"}},{"id":"a73ac0d19515499c","name":"Invocation","start_time":1681935061.9694362,"end_time":1681935063.8890877,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"},"subsegments":[{"id":"261ebf4a7724eccf","name":"Lambda","start_time":1681935062.151,"end_time":1681935063.887,"http":{"response":{"status":200}},"aws":{"retries":0,"status_code":200,"function_name":"todo-list-app-dev-sendNotification","region":"us-east-1","operation":"Invoke","request_id":"103426eb-7689-467f-a30b-84f305422aab","resource_names":["todo-list-app-dev-sendNotification"]},"namespace":"aws"},{"id":"31064f9d6fa52bfd","name":"DynamoDB","start_time":1681935061.973,"end_time":1681935062.111,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"PutItem","request_id":"FLBDI2VSLF1PDJ6OSSIV8PURH7VV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]}]},"Id":"31cd7b89660873fd"}}],"origin":"AWS::Lambda","http":{"response":{"status":200}},"fullData":{"Document":{"id":"27f8a658e75effa8","name":"todo-list-app-dev-postTask","start_time":1681935060.635,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935063.889,"parent_id":"44c5823f71d5e7d5","http":{"response":{"status":200}},"aws":{"request_id":"33ad85c7-d0c6-4902-90b1-effbf9d9e03a"},"origin":"AWS::Lambda","resource_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"},"Id":"27f8a658e75effa8"}}],"origin":"AWS::ApiGateway::Stage","http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/add-task","method":"POST","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":201,"content_length":0}},"fullData":{"Document":{"id":"1f0bec4b7fb90cc2","name":"dev-todo-list-app/dev","start_time":1681935060.601,"trace_id":"1-64404ad4-0372b96637c899165e908034","end_time":1681935063.891,"http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/add-task","method":"POST","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":201,"content_length":0}},"aws":{"api_gateway":{"account_id":"263792328682","rest_api_id":"cm8rpw34l8","stage":"dev","request_id":"f61033f0-1537-4a20-afb6-18f63762ff15"}},"annotations":{"aws:api_id":"cm8rpw34l8","aws:api_stage":"dev"},"metadata":{"default":{"extended_request_id":"DpChRHuDoAMF-Ug=","request_id":"f61033f0-1537-4a20-afb6-18f63762ff15"}},"origin":"AWS::ApiGateway::Stage","resource_arn":"arn:aws:apigateway:us-east-1::/restapis/cm8rpw34l8/stages/dev","subsegments":[{"id":"44c5823f71d5e7d5","name":"Lambda","start_time":1681935060.603,"end_time":1681935063.891,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask/invocations","method":"POST"},"response":{"status":201,"content_length":2}},"aws":{"function_name":"todo-list-app-dev-postTask","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-postTask"]},"namespace":"aws"}]},"Id":"1f0bec4b7fb90cc2"},"cold_start":true,"logs":[{"eventId":"37508405257445482843336398496716858515752166827427692547","ingestionTime":1681935064164,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"START RequestId: 33ad85c7-d0c6-4902-90b1-effbf9d9e03a Version: $LATEST\n","timestamp":1681935061969},{"eventId":"37508405257490084333733459742999929952297463550439653380","ingestionTime":1681935064164,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"2023-04-19T20:11:01.971Z\t33ad85c7-d0c6-4902-90b1-effbf9d9e03a\tINFO\tpost task controller\n","timestamp":1681935061971},{"eventId":"37508405261481917724270441285334823523101520260010147845","ingestionTime":1681935064164,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"2023-04-19T20:11:02.150Z\t33ad85c7-d0c6-4902-90b1-effbf9d9e03a\tINFO\tpost task controller\n","timestamp":1681935062150},{"eventId":"37508405300262913624515194928465437599237020918910091270","ingestionTime":1681935064164,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"END RequestId: 33ad85c7-d0c6-4902-90b1-effbf9d9e03a\n","timestamp":1681935063889},{"eventId":"37508405300262913624515194928465437599237020918910091271","ingestionTime":1681935064164,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"REPORT RequestId: 33ad85c7-d0c6-4902-90b1-effbf9d9e03a\tDuration: 1920.62 ms\tBilled Duration: 1921 ms\tMemory Size: 1024 MB\tMax Memory Used: 102 MB\tInit Duration: 831.24 ms\t\nXRAY TraceId: 1-64404ad4-0372b96637c899165e908034\tSegmentId: 31cd7b89660873fd\tSampled: true\t\n","timestamp":1681935063889}]},{"id":"6ff95cfd74bc7d47","name":"dev-todo-list-app/dev","time_taken":1625,"subsegments":[{"id":"02e2b5d67a625fcd","name":"Lambda","start_time":1681935063.046,"end_time":1681935064.669,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks/invocations","method":"GET"},"response":{"status":200,"content_length":3552}},"aws":{"function_name":"todo-list-app-dev-getTasks","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-getTasks"]},"namespace":"aws"}],"children":[{"id":"4d363a828629f23a","name":"todo-list-app-dev-getTasks","parent_id":"02e2b5d67a625fcd","time_taken":1605.9999465942383,"children":[{"id":"7873cd980a4bc265","name":"todo-list-app-dev-getTasks","parent_id":"4d363a828629f23a","time_taken":154.61111068725586,"subsegments":[{"id":"1fac1d7b64215f1f","name":"Invocation","start_time":1681935064.5145133,"end_time":1681935064.6668608,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"},"subsegments":[{"id":"f20145ce349d0c58","name":"DynamoDB","start_time":1681935064.534,"end_time":1681935064.661,"http":{"response":{"status":200}},"aws":{"retries":0,"scanned_count":53,"count":53,"region":"us-east-1","operation":"Scan","request_id":"L8RHB48ME253MVVS8A7DGDI9MJVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]},{"id":"61c76d46ab264e74","name":"Initialization","start_time":1681935063.7610717,"end_time":1681935064.5116537,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"}},{"id":"640240ba8f50951b","name":"Overhead","start_time":1681935064.6668901,"end_time":1681935064.6678033,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"}}],"children":[{"id":"21cfc53c060ebdae","name":"DynamoDB","parent_id":"f20145ce349d0c58","time_taken":127.00009346008301,"children":[],"origin":"AWS::DynamoDB::Table","http":{"response":{"status":200}},"fullData":{"Document":{"id":"21cfc53c060ebdae","name":"DynamoDB","start_time":1681935064.534,"trace_id":"1-64404ad7-7d1028ba3d311c2045beab1e","end_time":1681935064.661,"parent_id":"f20145ce349d0c58","inferred":true,"http":{"response":{"status":200}},"aws":{"retries":0,"scanned_count":53,"count":53,"region":"us-east-1","operation":"Scan","request_id":"L8RHB48ME253MVVS8A7DGDI9MJVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"origin":"AWS::DynamoDB::Table"},"Id":"21cfc53c060ebdae"}}],"origin":"AWS::Lambda::Function","fullData":{"Document":{"id":"7873cd980a4bc265","name":"todo-list-app-dev-getTasks","start_time":1681935064.5134594,"trace_id":"1-64404ad7-7d1028ba3d311c2045beab1e","end_time":1681935064.6680706,"parent_id":"4d363a828629f23a","aws":{"account_id":"263792328682","function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks","resource_names":["todo-list-app-dev-getTasks"]},"origin":"AWS::Lambda::Function","subsegments":[{"id":"1fac1d7b64215f1f","name":"Invocation","start_time":1681935064.5145133,"end_time":1681935064.6668608,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"},"subsegments":[{"id":"f20145ce349d0c58","name":"DynamoDB","start_time":1681935064.534,"end_time":1681935064.661,"http":{"response":{"status":200}},"aws":{"retries":0,"scanned_count":53,"count":53,"region":"us-east-1","operation":"Scan","request_id":"L8RHB48ME253MVVS8A7DGDI9MJVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]},{"id":"61c76d46ab264e74","name":"Initialization","start_time":1681935063.7610717,"end_time":1681935064.5116537,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"}},{"id":"640240ba8f50951b","name":"Overhead","start_time":1681935064.6668901,"end_time":1681935064.6678033,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"}}]},"Id":"7873cd980a4bc265"}}],"origin":"AWS::Lambda","http":{"response":{"status":200}},"fullData":{"Document":{"id":"4d363a828629f23a","name":"todo-list-app-dev-getTasks","start_time":1681935063.061,"trace_id":"1-64404ad7-7d1028ba3d311c2045beab1e","end_time":1681935064.667,"parent_id":"02e2b5d67a625fcd","http":{"response":{"status":200}},"aws":{"request_id":"953fe93a-e2ad-4fb7-aeda-50f464d1e132"},"origin":"AWS::Lambda","resource_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks"},"Id":"4d363a828629f23a"}}],"origin":"AWS::ApiGateway::Stage","http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/get-tasks","method":"GET","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":200,"content_length":0}},"fullData":{"Document":{"id":"6ff95cfd74bc7d47","name":"dev-todo-list-app/dev","start_time":1681935063.044,"trace_id":"1-64404ad7-7d1028ba3d311c2045beab1e","end_time":1681935064.669,"http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/get-tasks","method":"GET","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":200,"content_length":0}},"aws":{"api_gateway":{"account_id":"263792328682","rest_api_id":"cm8rpw34l8","stage":"dev","request_id":"d44ee5f4-06af-4890-942d-ea971466f73f"}},"annotations":{"aws:api_id":"cm8rpw34l8","aws:api_stage":"dev"},"metadata":{"default":{"extended_request_id":"DpChpFSfoAMFf4A=","request_id":"d44ee5f4-06af-4890-942d-ea971466f73f"}},"origin":"AWS::ApiGateway::Stage","resource_arn":"arn:aws:apigateway:us-east-1::/restapis/cm8rpw34l8/stages/dev","subsegments":[{"id":"02e2b5d67a625fcd","name":"Lambda","start_time":1681935063.046,"end_time":1681935064.669,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-getTasks/invocations","method":"GET"},"response":{"status":200,"content_length":3552}},"aws":{"function_name":"todo-list-app-dev-getTasks","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-getTasks"]},"namespace":"aws"}]},"Id":"6ff95cfd74bc7d47"},"cold_start":true,"logs":[{"eventId":"37508405314200879373596834396762522068225136883996819459","ingestionTime":1681935068165,"logStreamName":"2023/04/19/[$LATEST]061b25a4906c4b998f153fcb6a4d8de8","message":"START RequestId: 953fe93a-e2ad-4fb7-aeda-50f464d1e132 Version: $LATEST\n","timestamp":1681935064514},{"eventId":"37508405317635194134170550360559022682212984555917803524","ingestionTime":1681935068165,"logStreamName":"2023/04/19/[$LATEST]061b25a4906c4b998f153fcb6a4d8de8","message":"END RequestId: 953fe93a-e2ad-4fb7-aeda-50f464d1e132\n","timestamp":1681935064668},{"eventId":"37508405317635194134170550360559022682212984555917803525","ingestionTime":1681935068165,"logStreamName":"2023/04/19/[$LATEST]061b25a4906c4b998f153fcb6a4d8de8","message":"REPORT RequestId: 953fe93a-e2ad-4fb7-aeda-50f464d1e132\tDuration: 155.22 ms\tBilled Duration: 156 ms\tMemory Size: 1024 MB\tMax Memory Used: 102 MB\tInit Duration: 750.97 ms\t\nXRAY TraceId: 1-64404ad7-7d1028ba3d311c2045beab1e\tSegmentId: 7873cd980a4bc265\tSampled: true\t\n","timestamp":1681935064668}]},{"id":"5f1c861d6f80b651","name":"dev-todo-list-app/dev","time_taken":1172.0001697540283,"subsegments":[{"id":"42979cb403e5bc2b","name":"Lambda","start_time":1681935068.954,"end_time":1681935070.123,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask/invocations","method":"POST"},"response":{"status":201,"content_length":2}},"aws":{"function_name":"todo-list-app-dev-postTask","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-postTask"]},"namespace":"aws"}],"children":[{"id":"33e141e0065cb41b","name":"todo-list-app-dev-postTask","parent_id":"42979cb403e5bc2b","time_taken":1157.9999923706055,"children":[{"id":"7dae52540909690d","name":"todo-list-app-dev-postTask","parent_id":"33e141e0065cb41b","time_taken":1151.1225700378418,"subsegments":[{"id":"c43d5f8cd2ad1351","name":"Overhead","start_time":1681935070.1212032,"end_time":1681935070.121594,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"}},{"id":"3d95ce606b1fe75b","name":"Invocation","start_time":1681935068.970756,"end_time":1681935070.1211765,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"},"subsegments":[{"id":"6087903191f4a08e","name":"Lambda","start_time":1681935069.005,"end_time":1681935070.12,"http":{"response":{"status":200}},"aws":{"retries":0,"status_code":200,"function_name":"todo-list-app-dev-sendNotification","region":"us-east-1","operation":"Invoke","request_id":"f3f453ae-9e9d-4f26-afe3-e66050dbebd9","resource_names":["todo-list-app-dev-sendNotification"]},"namespace":"aws"},{"id":"5031472a748a4a21","name":"DynamoDB","start_time":1681935068.974,"end_time":1681935069.003,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"PutItem","request_id":"FIMJQ5R22UTD215OR2HTMHOROVVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]}],"children":[{"id":"3825ef9b9ee73c89","name":"todo-list-app-dev-sendNotification","parent_id":"6087903191f4a08e","time_taken":1094.0001010894775,"children":[{"id":"6d63ead43f720bac","name":"todo-list-app-dev-sendNotification","parent_id":"3825ef9b9ee73c89","time_taken":1087.0997905731201,"subsegments":[{"id":"952475b5a194d185","name":"Overhead","start_time":1681935070.1161554,"end_time":1681935070.116562,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"}},{"id":"baf6469afb99b436","name":"Invocation","start_time":1681935069.0296588,"end_time":1681935070.1161044,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"},"subsegments":[{"id":"7e9616b9c99800b2","name":"SES","start_time":1681935069.03,"end_time":1681935070.114,"fault":true,"error":true,"http":{"response":{"status":400}},"aws":{"retries":0,"region":"us-east-1","operation":"SendEmail","request_id":"2549fc89-2e2a-4b49-a67b-b17cc5065794"},"namespace":"aws","cause":{"working_directory":"/var/task","exceptions":[{"id":"4c910e93c3bbce9f","message":"Email address is not verified. The following identities failed the check in region US-EAST-1: karpuxa@gmail.com","type":"MessageRejected","remote":true,"stack":[{"path":"/var/task/node_modules/aws-xray-sdk-core/dist/lib/patchers/aws_p.js","line":64,"label":"features.constructor.captureAWSRequest [as customRequestHandler]"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":298,"label":"features.constructor.addAllRequestListeners"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":222,"label":"features.constructor.makeRequest"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":706,"label":"features.constructor.svc. [as sendEmail]"},{"path":"/var/task/src/handler.js","line":63,"label":"Runtime.module.exports.sendNotification [as handler]"},{"path":"/var/runtime/Runtime.js","line":74,"label":"Runtime.handleOnceNonStreaming"}]}]}}]}],"children":[{"id":"17e149dc38b10d14","name":"SES","parent_id":"7e9616b9c99800b2","time_taken":1084.0001106262207,"children":[],"origin":"AWS::SES","http":{"response":{"status":400}},"fullData":{"Document":{"id":"17e149dc38b10d14","name":"SES","start_time":1681935069.03,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935070.114,"parent_id":"7e9616b9c99800b2","inferred":true,"error":true,"http":{"response":{"status":400}},"aws":{"retries":0,"region":"us-east-1","operation":"SendEmail","request_id":"2549fc89-2e2a-4b49-a67b-b17cc5065794"},"origin":"AWS::SES","cause":{"message":"Email address is not verified. The following identities failed the check in region US-EAST-1: karpuxa@gmail.com"}},"Id":"17e149dc38b10d14"}}],"origin":"AWS::Lambda::Function","fullData":{"Document":{"id":"6d63ead43f720bac","name":"todo-list-app-dev-sendNotification","start_time":1681935069.0295627,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935070.1166625,"parent_id":"3825ef9b9ee73c89","aws":{"account_id":"263792328682","function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification","resource_names":["todo-list-app-dev-sendNotification"]},"origin":"AWS::Lambda::Function","subsegments":[{"id":"952475b5a194d185","name":"Overhead","start_time":1681935070.1161554,"end_time":1681935070.116562,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"}},{"id":"baf6469afb99b436","name":"Invocation","start_time":1681935069.0296588,"end_time":1681935070.1161044,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"},"subsegments":[{"id":"7e9616b9c99800b2","name":"SES","start_time":1681935069.03,"end_time":1681935070.114,"fault":true,"error":true,"http":{"response":{"status":400}},"aws":{"retries":0,"region":"us-east-1","operation":"SendEmail","request_id":"2549fc89-2e2a-4b49-a67b-b17cc5065794"},"namespace":"aws","cause":{"working_directory":"/var/task","exceptions":[{"id":"4c910e93c3bbce9f","message":"Email address is not verified. The following identities failed the check in region US-EAST-1: karpuxa@gmail.com","type":"MessageRejected","remote":true,"stack":[{"path":"/var/task/node_modules/aws-xray-sdk-core/dist/lib/patchers/aws_p.js","line":64,"label":"features.constructor.captureAWSRequest [as customRequestHandler]"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":298,"label":"features.constructor.addAllRequestListeners"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":222,"label":"features.constructor.makeRequest"},{"path":"/var/task/node_modules/aws-sdk/lib/service.js","line":706,"label":"features.constructor.svc. [as sendEmail]"},{"path":"/var/task/src/handler.js","line":63,"label":"Runtime.module.exports.sendNotification [as handler]"},{"path":"/var/runtime/Runtime.js","line":74,"label":"Runtime.handleOnceNonStreaming"}]}]}}]}]},"Id":"6d63ead43f720bac"}}],"origin":"AWS::Lambda","http":{"response":{"status":200}},"fullData":{"Document":{"id":"3825ef9b9ee73c89","name":"todo-list-app-dev-sendNotification","start_time":1681935069.023,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935070.117,"parent_id":"6087903191f4a08e","http":{"response":{"status":200}},"aws":{"request_id":"f3f453ae-9e9d-4f26-afe3-e66050dbebd9"},"origin":"AWS::Lambda","resource_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-sendNotification"},"Id":"3825ef9b9ee73c89"}},{"id":"053972ae25848977","name":"DynamoDB","parent_id":"5031472a748a4a21","time_taken":29.000043869018555,"children":[],"origin":"AWS::DynamoDB::Table","http":{"response":{"status":200}},"fullData":{"Document":{"id":"053972ae25848977","name":"DynamoDB","start_time":1681935068.974,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935069.003,"parent_id":"5031472a748a4a21","inferred":true,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"PutItem","request_id":"FIMJQ5R22UTD215OR2HTMHOROVVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"origin":"AWS::DynamoDB::Table"},"Id":"053972ae25848977"}}],"origin":"AWS::Lambda::Function","fullData":{"Document":{"id":"7dae52540909690d","name":"todo-list-app-dev-postTask","start_time":1681935068.9706764,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935070.121799,"parent_id":"33e141e0065cb41b","aws":{"account_id":"263792328682","function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask","resource_names":["todo-list-app-dev-postTask"]},"origin":"AWS::Lambda::Function","subsegments":[{"id":"c43d5f8cd2ad1351","name":"Overhead","start_time":1681935070.1212032,"end_time":1681935070.121594,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"}},{"id":"3d95ce606b1fe75b","name":"Invocation","start_time":1681935068.970756,"end_time":1681935070.1211765,"aws":{"function_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"},"subsegments":[{"id":"6087903191f4a08e","name":"Lambda","start_time":1681935069.005,"end_time":1681935070.12,"http":{"response":{"status":200}},"aws":{"retries":0,"status_code":200,"function_name":"todo-list-app-dev-sendNotification","region":"us-east-1","operation":"Invoke","request_id":"f3f453ae-9e9d-4f26-afe3-e66050dbebd9","resource_names":["todo-list-app-dev-sendNotification"]},"namespace":"aws"},{"id":"5031472a748a4a21","name":"DynamoDB","start_time":1681935068.974,"end_time":1681935069.003,"http":{"response":{"status":200}},"aws":{"retries":0,"region":"us-east-1","operation":"PutItem","request_id":"FIMJQ5R22UTD215OR2HTMHOROVVV4KQNSO5AEMVJF66Q9ASUAAJG","table_name":"todo-list-tasks","resource_names":["todo-list-tasks"]},"namespace":"aws"}]}]},"Id":"7dae52540909690d"}}],"origin":"AWS::Lambda","http":{"response":{"status":200}},"fullData":{"Document":{"id":"33e141e0065cb41b","name":"todo-list-app-dev-postTask","start_time":1681935068.964,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935070.122,"parent_id":"42979cb403e5bc2b","http":{"response":{"status":200}},"aws":{"request_id":"b3b0d9e3-8c4c-436a-aaaa-1a45823094eb"},"origin":"AWS::Lambda","resource_arn":"arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask"},"Id":"33e141e0065cb41b"}}],"origin":"AWS::ApiGateway::Stage","http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/add-task","method":"POST","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":201,"content_length":0}},"fullData":{"Document":{"id":"5f1c861d6f80b651","name":"dev-todo-list-app/dev","start_time":1681935068.952,"trace_id":"1-64404adc-70a67b4f04679f1750f09a91","end_time":1681935070.124,"http":{"request":{"url":"https://cm8rpw34l8.execute-api.us-east-1.amazonaws.com/dev/add-task","method":"POST","user_agent":"PostmanRuntime/7.31.3","client_ip":"172.56.161.54","x_forwarded_for":true},"response":{"status":201,"content_length":0}},"aws":{"api_gateway":{"account_id":"263792328682","rest_api_id":"cm8rpw34l8","stage":"dev","request_id":"beb4cd2d-9a3a-41f7-b70c-a230e95e7bf3"}},"annotations":{"aws:api_id":"cm8rpw34l8","aws:api_stage":"dev"},"metadata":{"default":{"extended_request_id":"DpCikGdJIAMF9SQ=","request_id":"beb4cd2d-9a3a-41f7-b70c-a230e95e7bf3"}},"origin":"AWS::ApiGateway::Stage","resource_arn":"arn:aws:apigateway:us-east-1::/restapis/cm8rpw34l8/stages/dev","subsegments":[{"id":"42979cb403e5bc2b","name":"Lambda","start_time":1681935068.954,"end_time":1681935070.123,"http":{"request":{"url":"https://lambda.us-east-1.amazonaws.com/2015-03-31/functions/arn:aws:lambda:us-east-1:263792328682:function:todo-list-app-dev-postTask/invocations","method":"POST"},"response":{"status":201,"content_length":2}},"aws":{"function_name":"todo-list-app-dev-postTask","region":"us-east-1","operation":"Invoke","resource_names":["todo-list-app-dev-postTask"]},"namespace":"aws"}]},"Id":"5f1c861d6f80b651"},"cold_start":true,"logs":[{"eventId":"37508405413572999978249291127328083047567788380815228928","ingestionTime":1681935077994,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"START RequestId: b3b0d9e3-8c4c-436a-aaaa-1a45823094eb Version: $LATEST\n","timestamp":1681935068970},{"eventId":"37508405413595300723447821750469618765840436742321209345","ingestionTime":1681935077994,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"2023-04-19T20:11:08.971Z\tb3b0d9e3-8c4c-436a-aaaa-1a45823094eb\tINFO\tpost task controller\n","timestamp":1681935068971},{"eventId":"37508405414331225314999332314140297468837832672018563074","ingestionTime":1681935077994,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"2023-04-19T20:11:09.004Z\tb3b0d9e3-8c4c-436a-aaaa-1a45823094eb\tINFO\tpost task controller\n","timestamp":1681935069004},{"eventId":"37508405439241157701758038363235694779386052474198687747","ingestionTime":1681935077994,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"END RequestId: b3b0d9e3-8c4c-436a-aaaa-1a45823094eb\n","timestamp":1681935070121},{"eventId":"37508405439241157701758038363235694779386052474198687748","ingestionTime":1681935077994,"logStreamName":"2023/04/19/[$LATEST]3adf2feaed664eccac9b695a47de0c9d","message":"REPORT RequestId: b3b0d9e3-8c4c-436a-aaaa-1a45823094eb\tDuration: 1151.25 ms\tBilled Duration: 1152 ms\tMemory Size: 1024 MB\tMax Memory Used: 102 MB\t\nXRAY TraceId: 1-64404adc-70a67b4f04679f1750f09a91\tSegmentId: 7dae52540909690d\tSampled: true\t\n","timestamp":1681935070121}]}] -------------------------------------------------------------------------------- /client/src/assets/sesIcon.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /client/src/assets/settings-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /client/src/assets/simpleNotification.svg: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /client/src/assets/team-svgrepo-com.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /client/src/components/AppTree.jsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LambdaPulse/f6ef43b6567ed2d02946abd5edc3c129628930b2/client/src/components/AppTree.jsx -------------------------------------------------------------------------------- /client/src/components/DebugTraceDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import DataTable, { createTheme } from 'react-data-table-component' 3 | import TraceFilters from './TraceFilters' 4 | 5 | const columns = [ 6 | { 7 | name: 'name', 8 | selector: row => row["name"], 9 | width:"250px", 10 | sortable: true, 11 | }, 12 | { 13 | name: 'start time', 14 | selector: row => row["start_time"], 15 | width:"200px", 16 | sortable: true, 17 | }, 18 | { 19 | name: 'duration', 20 | selector: row => row["duration"], 21 | width:"100px", 22 | sortable: true, 23 | }, 24 | { 25 | name: 'error', 26 | selector: row => row["error"], 27 | width:"100px", 28 | sortable: true, 29 | }, 30 | { 31 | name: 'cause', 32 | selector: row => row["cause"], 33 | width:"400px", 34 | wrap:true 35 | }, 36 | { 37 | name: 'status', 38 | selector: row => row["status"], 39 | width:"100px", 40 | sortable: true, 41 | }, 42 | { 43 | name: 'origin', 44 | selector: row => row["origin"], 45 | width:"200px", 46 | sortable: true, 47 | }, 48 | { 49 | name: 'subsegments', 50 | selector: row => row["subsegments"], 51 | width:"200px", 52 | sortable: true, 53 | }, 54 | { 55 | name: 'method', 56 | selector: row => row["method"], 57 | width:"200px", 58 | sortable: true, 59 | }, 60 | { 61 | name: 'fulldata', 62 | selector: row => row["fullData"], 63 | width:"200px", 64 | sortable: true, 65 | }, 66 | 67 | //{ 68 | // name: 'subsegments', 69 | // selector: row => row["subsegments"], 70 | // width:"200px", 71 | //}, 72 | ] 73 | 74 | createTheme('dark', { 75 | text: { 76 | primary: '#dddddd', 77 | secondary: '#2aa198', 78 | }, 79 | background: { 80 | default: '#222222', 81 | }, 82 | context: { 83 | background: '#cb4b16', 84 | text: '#FFFFFF', 85 | }, 86 | divider: { 87 | default: '#073642', 88 | }, 89 | action: { 90 | button: 'rgba(0,0,0,.54)', 91 | hover: 'rgba(0,0,0,.08)', 92 | disabled: 'rgba(0,0,0,.12)', 93 | }, 94 | }, 'dark'); 95 | 96 | const getStartLocale = (startSec) => { 97 | let start = new Date(0); 98 | start.setUTCSeconds(startSec); 99 | return start.toLocaleString() 100 | } 101 | 102 | 103 | const flattenTrace = (trace) => { 104 | const result = []; 105 | if (!trace) { 106 | console.log('Can\'t flatten trace; it doesn\'t exist!'); 107 | } 108 | 109 | const process = (node) => { 110 | if (!node) { 111 | console.log('Error! No node...') 112 | return; 113 | } 114 | if (!node.fullData) { 115 | console.log('Error! Not getting full data from node...'); 116 | return; 117 | } 118 | 119 | const nr = {}; 120 | const n = node.fullData.Document; 121 | 122 | if (n['start_time']) nr.start_time = getStartLocale(n['start_time']); 123 | if (n['start_time'] && n['end_time']) nr.duration = Math.floor( (n['end_time'] - n['start_time']) * 1000)/1000; 124 | nr.name = n['name']; 125 | if (n.http && n.http.response) nr.status = n.http.response.status; 126 | if (n.cause) { 127 | nr.cause = n.cause.message; 128 | for (const e in n.cause.exceptions) { 129 | nr.cause += n.cause.exceptions[e].message; 130 | } 131 | } 132 | if (n.error) nr.error = 'yes'; 133 | if (n.origin) nr.origin = n.origin; 134 | if (n.subsegments) { 135 | let c = 0; 136 | for (let i = 0; i < n.subsegments.length; i++) { 137 | c++; 138 | } 139 | nr.subsegments = c; 140 | } 141 | 142 | for (const c in node.children) { 143 | process(node.children[c]); 144 | } 145 | 146 | result.push(nr); 147 | } 148 | 149 | process(trace); 150 | 151 | return result; 152 | } 153 | 154 | const DebugTraceDisplay = (props) => { 155 | 156 | return ( 157 |
158 |

Segment Detail:

159 | 165 |
166 | ) 167 | }; 168 | 169 | export default DebugTraceDisplay; 170 | -------------------------------------------------------------------------------- /client/src/components/EventGraph.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import NodeTree from './NodeTree'; 3 | import NodeDetail from './NodeDetail'; 4 | import LogPanel from './LogPanel'; 5 | import NavBar from './NavBar'; 6 | import TraceList from './TraceList'; 7 | import './event-graph.css'; 8 | 9 | const EventGraph = (props) => { 10 | const [nodeDetailState,setNodeDetailState] = useState({left: 150, top:150, display: 'none', curNode: null}); 11 | 12 | return ( 13 |
14 |
15 |
16 | 17 | 18 |
19 | 20 |
21 |
22 | ) 23 | }; 24 | 25 | export default EventGraph; 26 | -------------------------------------------------------------------------------- /client/src/components/HomeDisplay.css: -------------------------------------------------------------------------------- 1 | .metrics__home__container { 2 | position: relative; 3 | display: grid; 4 | } 5 | .trace-list-container { 6 | position: relative; 7 | display: flex; 8 | } 9 | 10 | .trace-list-traces { 11 | display: flex; 12 | flex-direction: column; 13 | width: 15% 14 | } 15 | 16 | .debug-trace-display { 17 | position: relative; 18 | overflow-inline: scroll; 19 | max-width: 85%; 20 | } 21 | 22 | .loading-spinner { 23 | position: absolute; 24 | top: 50%; 25 | left: 50%; 26 | transform: translate(-50%, -50%); 27 | z-index: 100; 28 | } 29 | 30 | .datepicker__override { 31 | color: #ffffff; 32 | } 33 | 34 | .datepicker__popper__override div div div { 35 | background-color: #232323; 36 | color: #ffffff 37 | } 38 | 39 | .filter__label { 40 | font-size: x-small; 41 | float: left; 42 | } 43 | 44 | .filter__container { 45 | display: inline-block; 46 | margin-left: 10px; 47 | margin-right: 10px; 48 | } -------------------------------------------------------------------------------- /client/src/components/HomeDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import SimpleDataDisplay from './SimpleDataDisplay'; 4 | import { PieChart } from 'react-minimal-pie-chart'; 5 | import spinner from '../assets/pulse-1.1s-200px.svg'; 6 | import './HomeDisplay.css'; 7 | 8 | const getStartLocale = (startSec) => { 9 | let start = new Date(0); 10 | start.setUTCSeconds(startSec); 11 | return start.toLocaleString(); 12 | }; 13 | 14 | const flattenTrace = (trace) => { 15 | const result = []; 16 | if (!trace) return; 17 | 18 | const process = (node) => { 19 | const nr = {}; 20 | const n = node.fullData.Document; 21 | 22 | if (n['start_time']) nr.start_time = getStartLocale(n['start_time']); 23 | if (n['start_time'] && n['end_time']) 24 | nr.duration = Math.floor((n['end_time'] - n['start_time']) * 1000) / 1000; 25 | nr.name = n['name']; 26 | if (n.http && n.http.response) nr.status = n.http.response.status; 27 | if (n.cause) { 28 | nr.cause = n.cause.message; 29 | for (const e in n.cause.exceptions) { 30 | nr.cause += n.cause.exceptions[e].message; 31 | } 32 | } 33 | if (n.error) nr.error = 'yes'; 34 | if (n.origin) nr.origin = n.origin; 35 | if (n.subsegments) { 36 | let c = 0; 37 | for (let i = 0; i < n.subsegments.length; i++) { 38 | c++; 39 | } 40 | nr.subsegments = c; 41 | } 42 | 43 | for (const c in node.children) { 44 | process(node.children[c]); 45 | } 46 | 47 | result.push(nr); 48 | }; 49 | 50 | process(trace); 51 | 52 | return result; 53 | }; 54 | 55 | const traceHasErrors = (trace) => { 56 | let segmentQueue = []; 57 | segmentQueue.push(trace); 58 | while (segmentQueue.length) { 59 | let s = segmentQueue.pop(); 60 | s.children.forEach((segment) => segmentQueue.push(segment)); 61 | if (s.fullData && s.fullData.Document.error) return true; 62 | } 63 | return false; 64 | } 65 | 66 | const getFromRight = (s) => { 67 | let result = ''; 68 | for (let i = s.length-1; i >= 0; i--) { 69 | if (s[i] == '/') return result; 70 | result = s[i] + result; 71 | } 72 | return result; 73 | } 74 | 75 | const uniqueFunctions = (traces) => { 76 | const uniques = {}; 77 | for (const t in traces) { 78 | if (traces[t].fullData && traces[t].fullData.Document.http.request) { 79 | const u = getFromRight(traces[t].fullData.Document.http.request.url); 80 | if (!uniques[u]) uniques[u] = 0; 81 | uniques[u]++; 82 | } 83 | } 84 | return uniques; 85 | } 86 | 87 | const tallyOrigins = (flattenedTrace) => { 88 | const result = {}; 89 | 90 | for (const f in flattenedTrace) { 91 | let origin = flattenedTrace[f].origin; 92 | if (result[origin]) result[origin]++; 93 | else result[origin] = 1; 94 | } 95 | 96 | return result; 97 | }; 98 | 99 | const tallyToCircle = (tally) => { 100 | const result = []; 101 | const colors = ['#E38627', '#C13C37', '#6A2135']; 102 | let curColor = 0; 103 | for (const k in tally) { 104 | const r = {}; 105 | r.title = k; 106 | r.value = tally[k]; 107 | r.color = colors[curColor]; 108 | curColor++; 109 | if (curColor >= colors.length) curColor = 0; 110 | result.push(r); 111 | } 112 | return result; 113 | }; 114 | 115 | const uniqueFunctionsToCircle = (uniqueFunctions) => { 116 | const result = []; 117 | const colors = ['#E38627', '#C13C37', '#6A2135']; 118 | let curColor = 0; 119 | for (const k in uniqueFunctions) { 120 | const r = {}; 121 | r.title = k; 122 | r.value = uniqueFunctions[k]; 123 | r.color = colors[curColor]; 124 | curColor++; 125 | if (curColor >= colors.length) curColor = 0; 126 | result.push(r); 127 | } 128 | 129 | return result; 130 | } 131 | 132 | const HomeDisplay = (props) => { 133 | 134 | const invocationCount = props.traces ? props.traces.length : 0; 135 | let successCount = 0; 136 | let errorCount = 0; 137 | for (const t in props.traces) { 138 | const n = props.traces[t]; 139 | if (traceHasErrors(n)) errorCount++; 140 | else successCount++; 141 | } 142 | 143 | let sumDuration = 0; 144 | let countDuration = 0; 145 | for (const t in props.traces) { 146 | const n = props.traces[t]; 147 | if (n.averageTime) { 148 | sumDuration += n.averageTime; 149 | countDuration++; 150 | } 151 | } 152 | const avgDuration = (sumDuration > 0 ? Math.floor(sumDuration / countDuration * 1)/1 : 0) + ' ms'; 153 | 154 | const origins = tallyToCircle(tallyOrigins(flattenTrace(props.traces[props.currentTrace]))); 155 | 156 | const uniqueFuncs = uniqueFunctionsToCircle(uniqueFunctions(props.traces)); 157 | 158 | 159 | return ( 160 |
161 | {props.loading && (Loading)} 162 | 163 | 164 | 165 | 166 |
167 | {return props.dataEntry.title} } 178 | labelStyle={{ 179 | fill: 'white', 180 | fontSize: '4px' 181 | }} 182 | /> 183 |
184 |
185 | {return props.dataEntry.title} } 196 | labelStyle={{ 197 | fill: 'white', 198 | fontSize: '4px' 199 | }} 200 | /> 201 |
202 | 203 |
204 | ) 205 | } 206 | 207 | export default HomeDisplay; 208 | -------------------------------------------------------------------------------- /client/src/components/LogPanel.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import DataTable, { createTheme } from 'react-data-table-component'; 3 | import sampleLog from '../assets/sampleLog'; 4 | 5 | const columns = [ 6 | { 7 | name: 'timestamp', 8 | selector: (row) => row['timestamp'], 9 | width: '180px', 10 | format: d => { 11 | return (new Date(d.timestamp).toLocaleString() ) 12 | } 13 | }, 14 | { 15 | name: 'message', 16 | selector: (row) => row['message'], 17 | width: '800px', 18 | wrap: true 19 | }, 20 | { 21 | name: 'logStreamName', 22 | selector: (row) => row['logStreamName'], 23 | width: '350px', 24 | }, 25 | { 26 | name: 'eventId', 27 | selector: row => row["eventId"], 28 | width:"350px" 29 | }, 30 | ]; 31 | 32 | createTheme('dark', { 33 | text: { 34 | primary: '#dddddd', 35 | secondary: '#2aa198', 36 | }, 37 | background: { 38 | default: '#222222', 39 | }, 40 | context: { 41 | background: '#cb4b16', 42 | text: '#FFFFFF', 43 | }, 44 | divider: { 45 | default: '#073642', 46 | }, 47 | action: { 48 | button: 'rgba(0,0,0,.54)', 49 | hover: 'rgba(0,0,0,.08)', 50 | disabled: 'rgba(0,0,0,.12)', 51 | }, 52 | }, 'dark'); 53 | 54 | 55 | const customStyles = { 56 | rows: { 57 | style: { 58 | minHeight: '72px', // override the row height 59 | }, 60 | }, 61 | headCells: { 62 | style: { 63 | paddingLeft: '8px', // override the cell padding for head cells 64 | paddingRight: '8px', 65 | }, 66 | }, 67 | cells: { 68 | style: { 69 | paddingLeft: '8px', // override the cell padding for data cells 70 | paddingRight: '8px', 71 | }, 72 | }, 73 | }; 74 | 75 | 76 | 77 | const LogPanel = (props) => { 78 | 79 | return ( 80 |
81 |

Log Detail:

82 | 88 |
89 | ); 90 | }; 91 | 92 | export default LogPanel; 93 | -------------------------------------------------------------------------------- /client/src/components/Metrics.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | import { PieChart } from 'react-minimal-pie-chart'; 3 | 4 | const Metrics = (props) => { 5 | return ( 6 |
7 |
8 | 9 | 10 | ; 26 |
27 |
28 | ) 29 | }; 30 | 31 | export default Metrics; 32 | -------------------------------------------------------------------------------- /client/src/components/NavBar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './nav-bar.css'; 3 | import pulse from '../assets/pulse.svg'; 4 | import { Link } from 'react-router-dom'; 5 | 6 | const NavBar = (props) => { 7 | return ( 8 |
9 | 15 | 27 |
28 | ); 29 | }; 30 | 31 | export default NavBar; 32 | -------------------------------------------------------------------------------- /client/src/components/NodeDetail.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import './custom-tree.css'; 3 | 4 | 5 | 6 | const NodeDetail = (props) => { 7 | 8 | const attrs = []; 9 | 10 | if (props.nds.curNode) { 11 | 12 | const nodeDetails = []; 13 | const cn = props.nds.curNode; 14 | if (cn.id) nodeDetails.push(
id: {cn.id}
); 15 | if (cn.origin) nodeDetails.push(
origin: {cn.origin}
); 16 | if (cn.http && cn.http.request) nodeDetails.push(
req url: {cn.http.request.url}
); 17 | if (cn.http && cn.http.request) nodeDetails.push(
req method: {cn.http.request.method}
); 18 | if (cn.http && cn.http.response) nodeDetails.push(
res status: {cn.http.response.status}
); 19 | if (cn.time_taken) nodeDetails.push(
time taken ms: {cn.time_taken}
); 20 | if (cn.averageTime) nodeDetails.push(
average time ms: {cn.averageTime}
); 21 | if (cn.cold_start) nodeDetails.push(
cold start: {cn.cold_start}
); 22 | if (cn.fullData && cn.fullData.Document.error) nodeDetails.push(
error: {cn.fullData.Document.error? 'yes' : 'no'}
); 23 | if (cn.fullData && cn.fullData.Document.cause) { 24 | nodeDetails.push(
cause: {cn.fullData.Document.cause.message}
); 25 | for (const e in cn.fullData.Document.cause.exceptions) { 26 | nodeDetails.push(
exception: {cn.fullData.Document.cause.exceptions[e].message}
); 27 | } 28 | } 29 | 30 | 31 | const subSegments = []; 32 | for (const s in props.nds.curNode.subsegments) { 33 | const curSubSeg = props.nds.curNode.subsegments[s]; 34 | let start = new Date(0); 35 | start.setUTCSeconds(curSubSeg.start_time); 36 | start = start.toLocaleString() 37 | let duration = curSubSeg.end_time - curSubSeg.start_time; 38 | subSegments.push(
39 |
subsegment name: {curSubSeg.name}
40 |
start time: {start}
41 |
duration: {duration}
42 |
) 43 | } 44 | 45 | const subsegmentBox =
{subSegments}
46 | nodeDetails.push(subsegmentBox); 47 | 48 | const nodeDetailBox =
{nodeDetails}
49 | attrs.push(nodeDetailBox); 50 | } 51 | 52 | return ( 53 |
54 |

{props.nds.curNode ? props.nds.curNode.name : 'n/a'}

55 |
56 | {attrs} 57 |
58 |
59 | ) 60 | }; 61 | 62 | export default NodeDetail; 63 | -------------------------------------------------------------------------------- /client/src/components/NodeTree.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import Tree from 'react-d3-tree'; 3 | import dynamoDBIcon from '../assets/dynamoDB.svg' 4 | import lambdaFuncIcon from '../assets/lambdaFunc.svg' 5 | import simpleNotificationIcon from '../assets/simpleNotification.svg' 6 | import apiGatewayEndpointIcon from '../assets/apigatewayendpoint.svg' 7 | import sesIcon from '../assets/sesIcon.svg' 8 | import errorIcon from '../assets/error-905.svg' 9 | import errorIcon2 from '../assets/error-svgrepo-com.svg' 10 | import rs from '../assets/react.svg' 11 | import clientIcon from '../assets/client-svgrepo-com.svg' 12 | import './custom-tree.css'; 13 | 14 | /* 15 | name = "" 16 | id = "" 17 | origin = "" 18 | fullData -> Document = {} 19 | children = [] 20 | 21 | */ 22 | 23 | const NodeTree = (props) => { 24 | const getDynamicPathClass = ({ source, target }, orientation) => { 25 | if (!target.children) { 26 | // Target node has no children -> this link leads to a leaf node. 27 | return 'link__to-leaf'; 28 | } 29 | 30 | // Style it as a link connecting two branch nodes by default. 31 | return 'link__to-branch'; 32 | }; 33 | 34 | const getIcon = (nType) => { 35 | if (nType == 'AWS::Lambda') return lambdaFuncIcon; 36 | else if (nType == 'AWS::Lambda::Function') return lambdaFuncIcon; 37 | else if (nType == 'AWS::DynamoDB::Table') return dynamoDBIcon; 38 | else if (nType == 'AWS::ApiGateway::Stage') return apiGatewayEndpointIcon; 39 | else if (nType == 'AWS::SES') return sesIcon; 40 | else if (nType == 'simpleNotification') return simpleNotificationIcon; 41 | else if (nType == 'thrownError') return errorIcon; 42 | else if (nType == 'client') return clientIcon; 43 | else { 44 | console.log('Got unknown origin: ' + nType) 45 | return rs; 46 | } 47 | } 48 | 49 | const getColor = (nType) => { 50 | if (nType == 'AWS::Lambda') return 'orange'; 51 | else if (nType == 'AWS::Lambda::Function') return 'red'; 52 | else if (nType == 'AWS::DynamoDB::Table') return 'blue'; 53 | else if (nType == 'AWS::ApiGateway::Stage') return '#4D27AA'; 54 | else if (nType == 'AWS::SES') return 'blue'; 55 | else if (nType == 'simpleNotification') return 'pink'; 56 | else if (nType == 'thrownError') return 'gray'; 57 | else return 'gray'; 58 | } 59 | 60 | const handleClick = (e,logs) => { 61 | props.setLData({logs:logs}); 62 | } 63 | 64 | const handleMouseEnter = (e,curNode) => { 65 | props.setNds({top: e.clientY+10, left: e.clientX+10, display: 'inline', curNode: curNode}); 66 | } 67 | 68 | const handleMouseLeave = (e) => { 69 | props.setNds({top: e.clientY+10, left: e.clientX+10, display: 'none', curNode: null}); 70 | } 71 | 72 | const getErrorDim = (e) => { 73 | if (e) return 24; 74 | else return 0; 75 | } 76 | 77 | 78 | const renderRectSvgNode = ({ nodeDatum, toggleNode }) => ( 79 | 80 | handleClick(e,nodeDatum.logs)} 83 | /> 84 | handleClick(e,nodeDatum.logs)} 87 | /> 88 | handleClick(e,nodeDatum.logs)}/> 91 | handleClick(e,nodeDatum.logs)} 93 | onMouseEnter={(e) => handleMouseEnter(e, nodeDatum)} 94 | onMouseLeave={(e) => handleMouseLeave(e)} 95 | /> 96 | 97 | 99 | 100 | {nodeDatum.name} 101 | 102 | 103 | ); 104 | 105 | const initialTranslate = {x:150, y:100}; 106 | const textLayout = {textAnchor: "start", x: 10, y: 50, transform: undefined } 107 | const nodeSize = {x:160+40, y:80}; 108 | 109 | let nodeData; 110 | if (!props.nData) { 111 | nodeData = { 112 | children: [], 113 | http: {request: {}, response: {}}, 114 | id: "abc123", 115 | name: "no-traces-found", 116 | origin: "thrownError", 117 | parent_id: undefined, 118 | subsegments: [{}]} 119 | } 120 | else nodeData = props.nData; 121 | 122 | 123 | return ( 124 | // `` will fill width/height of its container; in this case `#treeWrapper`. 125 |
126 | 145 | 146 |
147 | ); 148 | } 149 | 150 | export default NodeTree; -------------------------------------------------------------------------------- /client/src/components/Settings.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { getApiBaseUrl } from '../apiBaseUrl.js'; 3 | 4 | const Settings = () => { 5 | const [arn, setArn] = useState(''); 6 | const [currentArn, setCurrentArn] = useState(''); 7 | const [success, setSuccess] = useState(''); 8 | 9 | useEffect(() => { 10 | console.log('in useffect'); 11 | fetch(`${getApiBaseUrl()}/getCurrentArn`, { 12 | method: 'GET', 13 | headers: { 14 | 'Content-type': 'application/json', 15 | }, 16 | }) 17 | .then((result) => result.json()) 18 | .then((data) => { 19 | console.log(data.rows[0].role_arn); 20 | if (data.rows[0].role_arn !== null) { 21 | setCurrentArn(data.rows[0].role_arn); 22 | } 23 | }); 24 | // console.log(result); 25 | // setCurrentArn(result); 26 | }, []); 27 | 28 | const addArn = (e) => { 29 | console.log('adding arn'); 30 | let userData = { userARN: arn }; 31 | fetch(`${getApiBaseUrl()}/setUserARN`, { 32 | method: 'POST', 33 | headers: { 34 | 'Content-type': 'application/json', 35 | }, 36 | body: JSON.stringify(userData), 37 | }) 38 | .then((result) => { 39 | console.log('this is result', result); 40 | setSuccess('User ARN successfully updated!'); 41 | }) 42 | .catch((err) => console.log(err)); 43 | console.log('finished adding arn'); 44 | }; 45 | return ( 46 |
47 |

Settings

48 |

Current Arn: {currentArn}

49 |

50 | Click{' '} 51 | 52 | here 53 | 54 | , create your stack, navigate to the outputs tab, and paste your ARN key 55 | into the field below! 56 |

57 |

{success}

58 |
{ 61 | e.preventDefault(); 62 | addArn(e); 63 | }} 64 | > 65 | setArn(e.target.value)} 68 | placeholder='ARN Key' 69 | /> 70 | 73 |
74 |
75 | ); 76 | }; 77 | 78 | export default Settings; 79 | -------------------------------------------------------------------------------- /client/src/components/SimpleDataDisplay.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | const SimpleDataDisplay = (props) => { 4 | return ( 5 |
6 |

{props.label}:

7 |

{props.metric}

8 |
9 | ) 10 | }; 11 | 12 | export default SimpleDataDisplay; 13 | -------------------------------------------------------------------------------- /client/src/components/TraceFilters.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react" 2 | import DatePicker from 'react-datepicker'; 3 | //import 'react-calendar/dist/Calendar.css'; 4 | //import 'react-clock/dist/Clock.css'; 5 | import "react-datepicker/dist/react-datepicker.css" 6 | import refreshIcon from '../assets/refresh-svgrepo-com.svg' 7 | 8 | const TraceFilters = (props) => { 9 | 10 | 11 | return ( 12 |
13 |

Filters

14 | 15 | 16 | Start Time: 17 | 28 | 29 | 30 | End Time: 31 | 42 | 43 | 44 | Refresh: 45 | 48 | 49 |
50 | ) 51 | }; 52 | 53 | export default TraceFilters; 54 | -------------------------------------------------------------------------------- /client/src/components/TraceList.jsx: -------------------------------------------------------------------------------- 1 | import {useEffect,useState} from 'react'; 2 | import DebugTraceDisplay from './DebugTraceDisplay' 3 | import spinner from '../assets/pulse-1.1s-200px.svg'; 4 | import './HomeDisplay.css'; 5 | import TraceSelector from './TraceSelector' 6 | import TraceFilters from './TraceFilters'; 7 | 8 | function TraceList (props) { 9 | 10 | 11 | function refreshData() { 12 | //clears Traces table from redis 13 | fetch('/clearTraces', { 14 | method: 'GET', 15 | }).then((result) => { 16 | console.log(result); 17 | }); 18 | //changes refresh which fires off useEffect(in dashboard.jsx) to fetch new data 19 | props.setRefresh(!props.refresh); 20 | } 21 | return ( 22 |
23 |
24 | {props.loading && ( 25 | Loading 26 | )} 27 |
28 | 32 |
33 | 36 | 37 |
38 |
39 | ); 40 | } 41 | export default TraceList; 42 | -------------------------------------------------------------------------------- /client/src/components/TraceSelector.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react" 2 | import errorImage from '../assets/error-svgrepo-com.svg'; 3 | 4 | 5 | const getFromRight = (s) => { 6 | let result = ''; 7 | for (let i = s.length-1; i >= 0; i--) { 8 | if (s[i] == '/') return result; 9 | result = s[i] + result; 10 | } 11 | return result; 12 | } 13 | 14 | const findErrorsInTrace = (trace) => { 15 | let errors = 0; 16 | 17 | const process = (node) => { 18 | if (node.fullData && node.fullData.Document.error) errors++; 19 | if (node.children) node.children.forEach((n) => {process(n)}) 20 | } 21 | 22 | process(trace); 23 | return errors; 24 | } 25 | 26 | const TraceSelector = (props) => { 27 | const traces = []; 28 | for (let n = 0; n < props.traces.length; n++) { 29 | const url = props.traces[n].fullData.Document.http.request.url; 30 | const endpt = getFromRight(url) 31 | const startTime = props.traces[n].fullData.Document.start_time; 32 | const endTime = props.traces[n].fullData.Document.end_time; 33 | let startFilter; 34 | try {startFilter = props.start_value.getTime()} 35 | catch (e) {startFilter = props.start_value} 36 | startFilter /= 1000; 37 | let endFilter; 38 | try {endFilter = props.end_value.getTime()} 39 | catch (e) {endFilter = props.end_value} 40 | endFilter /= 1000; 41 | 42 | 43 | if (startTime >= startFilter && endTime <= endFilter) { 44 | let errors = findErrorsInTrace(props.traces[n]); 45 | traces.push() 65 | } 66 | } 67 | 68 | 69 | 70 | return ( 71 |
72 |

TraceList

73 | {traces} 74 |
75 | 76 | ) 77 | }; 78 | 79 | export default TraceSelector; 80 | -------------------------------------------------------------------------------- /client/src/components/custom-tree.css: -------------------------------------------------------------------------------- 1 | /* custom-tree.css */ 2 | 3 | svg g .rd3t-link { 4 | stroke: #999; 5 | } 6 | 7 | .link__to-branch { 8 | stroke-width: 1; 9 | stroke: #f0f; 10 | border-color: #dddddd; 11 | color: #dddddd; 12 | } 13 | 14 | .link__to-leaf { 15 | stroke-width: 1; 16 | stroke: #f0f; 17 | border-color: #dddddd; 18 | color: #dddddd; 19 | } 20 | 21 | .node__detail { 22 | min-width: 200px; 23 | max-width: 300px; 24 | min-height: 200px; 25 | max-height: 400px; 26 | color: #dddddd; 27 | background-color: #191919; 28 | border-radius: 10px; 29 | border-width: 1px; 30 | border-style: dotted; 31 | border-color: #777777; 32 | top: 40px; 33 | left: 40px; 34 | position: absolute; 35 | display: inline; 36 | font-size: small; 37 | } 38 | 39 | .log__list { 40 | background-color: #333333; 41 | margin: 20px; 42 | } 43 | 44 | .nd__attribute { 45 | 46 | } 47 | 48 | .nd__subsegment__container { 49 | font-size: x-small; 50 | background-color: #242424; 51 | padding: 2px; 52 | margin: 2px; 53 | } 54 | 55 | .nd__subsegment { 56 | 57 | } -------------------------------------------------------------------------------- /client/src/components/event-graph.css: -------------------------------------------------------------------------------- 1 | .EventGraph { 2 | width: 100%; 3 | display:flex; 4 | justify-content: space-between; 5 | } 6 | 7 | .EventPanelContainer { 8 | width:50%; 9 | display: flex; 10 | background-color: #252525; 11 | } 12 | 13 | .LogPanel { 14 | background-color: #191919; 15 | color: #dddddd; 16 | width: 50%; 17 | position:relative; 18 | right:0px; 19 | scroll-behavior:auto; 20 | } 21 | 22 | .metrics__info__display { 23 | background-color: #191919; 24 | height: 160px; 25 | width: 300px; 26 | padding: 40px; 27 | margin: 10px; 28 | } 29 | 30 | .metrics__info__display h2 { 31 | color: #ff3300; 32 | } 33 | 34 | .metrics__visual__display { 35 | background-color: #191919; 36 | height: 340px; 37 | width: 300px; 38 | padding: 4px; 39 | margin: 10px; 40 | } 41 | 42 | .metrics__home__container { 43 | display: grid; 44 | grid-template-columns: 1fr 1fr 1fr 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /client/src/components/nav-bar.css: -------------------------------------------------------------------------------- 1 | .NavBar{ 2 | display:flex; 3 | flex-direction: row; 4 | justify-content: space-between; 5 | border-style: inset; 6 | border-color:rgb(93, 93, 93); 7 | border-width: 3px; 8 | } 9 | #logo{ 10 | display:flex; 11 | padding-left:10px; 12 | } 13 | #links{ 14 | position:relative; 15 | top: 35px; 16 | display:flex; 17 | justify-content: space-around; 18 | width:200px; 19 | color:rgb(255, 51, 0); 20 | } -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; 3 | line-height: 1.5; 4 | font-weight: 400; 5 | 6 | color-scheme: light dark; 7 | color: rgba(255, 255, 255, 0.87); 8 | background-color: #242424; 9 | 10 | font-synthesis: none; 11 | text-rendering: optimizeLegibility; 12 | -webkit-font-smoothing: antialiased; 13 | -moz-osx-font-smoothing: grayscale; 14 | -webkit-text-size-adjust: 100%; 15 | } 16 | 17 | a { 18 | font-weight: 500; 19 | color: #ff3300; 20 | text-decoration: inherit; 21 | } 22 | a:hover { 23 | color: #ff3300; 24 | } 25 | 26 | body { 27 | margin: 0; 28 | place-items: center; 29 | min-width: 320px; 30 | min-height: 100vh; 31 | background-color: #111111; 32 | } 33 | 34 | h1 { 35 | font-size: 3.2em; 36 | line-height: 1.1; 37 | } 38 | 39 | button { 40 | border-radius: 8px; 41 | border: 1px solid transparent; 42 | padding: 0.6em 1.2em; 43 | font-size: 1em; 44 | font-weight: 500; 45 | font-family: inherit; 46 | background-color: #1a1a1a; 47 | cursor: pointer; 48 | transition: border-color 0.25s; 49 | } 50 | button:hover { 51 | border-color: #ff3300; 52 | } 53 | button:focus, 54 | button:focus-visible { 55 | } 56 | 57 | @media (prefers-color-scheme: light) { 58 | :root { 59 | color: #dddddd; 60 | background-color: #ffffff; 61 | } 62 | a:hover { 63 | color: #ff3300; 64 | } 65 | button { 66 | background-color: #232323; 67 | color: #ff3300; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /client/src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import { BrowserRouter } from 'react-router-dom'; 4 | import App from './App' 5 | import './index.css' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')).render( 8 | 9 | 10 | 11 | 12 | , 13 | ) 14 | -------------------------------------------------------------------------------- /client/src/pages/About/About.css: -------------------------------------------------------------------------------- 1 | main { 2 | height: 100vh; 3 | width: 100%; 4 | } 5 | 6 | section { 7 | font-family: sans-serif; 8 | font-size: 12ch; 9 | height: 100%; 10 | width: 100%; 11 | display: flex; 12 | justify-content: center; 13 | align-items: center; 14 | } 15 | 16 | section:nth-child(1) { 17 | background: #252525; 18 | color: white; 19 | } 20 | 21 | section:nth-child(2) { 22 | background: #191919; 23 | color: white; 24 | } 25 | 26 | section:nth-child(3) { 27 | background: #252525; 28 | color: white; 29 | } 30 | 31 | .about-signup-btn { 32 | color: #080710; 33 | margin-top: 30px; 34 | background-color: #ffffff; 35 | background: linear-gradient(to bottom right, #fd3302, #ff9a5a); 36 | transition: box-shadow 0.2s ease-in-out; 37 | height: 2em; 38 | } 39 | 40 | .about-signup-btn:not([disabled]):hover { 41 | box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.5), 42 | -0.125rem -0.125rem 1rem rgba(239, 71, 101, 0.5), 43 | 0.125rem 0.125rem 1rem rgba(255, 154, 90, 0.5); 44 | } 45 | 46 | .about-hero { 47 | font-size: large; 48 | color: white; 49 | display: flex; 50 | flex-direction: row; 51 | align-items: center; 52 | } 53 | 54 | .about-hero-left { 55 | flex: 1; 56 | float: left; 57 | font-size: larger; 58 | align-items: center; 59 | justify-content: center; 60 | } 61 | 62 | .about-hero-right { 63 | flex: 1; 64 | float: right; 65 | font-size: larger; 66 | align-items: center; 67 | justify-content: center; 68 | } 69 | 70 | .about-features { 71 | font-size: large; 72 | color: white; 73 | display: flex; 74 | flex-direction: row; 75 | align-items: center; 76 | } 77 | 78 | .about-features-left { 79 | float: left; 80 | font-size: larger; 81 | align-items: center; 82 | justify-content: center; 83 | } 84 | 85 | .about-features-right { 86 | float: right; 87 | font-size: larger; 88 | align-items: center; 89 | justify-content: left; 90 | text-align: left; 91 | margin-left: 5em; 92 | } 93 | 94 | .about-cta { 95 | justify-content: left; 96 | align-items: center; 97 | text-align: left; 98 | display: flex; 99 | font-size: x-large; 100 | color: white; 101 | } -------------------------------------------------------------------------------- /client/src/pages/About/About.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import NavBar from '../../components/NavBar'; 4 | import { Link } from 'react-router-dom'; 5 | import './About.css' 6 | import graphPng from './graph.png' 7 | import spinner from '../../assets/pulse-1.1s-200px.svg'; 8 | 9 | 10 | const About = () => { 11 | 12 | const navigate = useNavigate(); 13 | 14 | 15 | return ( 16 |
17 | 18 |
19 |
20 |
21 |
22 |
LambdaPulse is your solution to monitoring AWS Lambda X-Ray Traces.
23 | 24 | 25 | 26 |
27 |
28 | 29 |
30 |
31 | 32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |

Features

40 |
    41 |
  • Persist traces beyond X-ray's built in time horizon
  • 42 |
  • View and filter traces by date & time range
  • 43 |
  • Visualize traces
  • 44 |
  • Find and view errors
  • 45 |
  • View detailed logs per trace
  • 46 |
  • Visualize system graph
  • 47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 | 55 |
56 |
57 | 58 |
59 | 60 |
61 |
62 | 63 | 64 | 65 |
66 |
67 |
68 |
69 | ); 70 | }; 71 | 72 | export default About; 73 | -------------------------------------------------------------------------------- /client/src/pages/About/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/LambdaPulse/f6ef43b6567ed2d02946abd5edc3c129628930b2/client/src/pages/About/graph.png -------------------------------------------------------------------------------- /client/src/pages/ComingSoon.jsx: -------------------------------------------------------------------------------- 1 | import React from "react" 2 | 3 | const name = (props) => { 4 | return ( 5 |
6 |

Coming Soon!

7 |
8 | ) 9 | }; 10 | 11 | export default name; 12 | -------------------------------------------------------------------------------- /client/src/pages/Dashboard/Dashboard.css: -------------------------------------------------------------------------------- 1 | #dashboardBody { 2 | display:flex; 3 | } 4 | #sideBar { 5 | width:6vw; 6 | display:flex; 7 | flex-direction: column; 8 | border-style: inset; 9 | border-color:rgb(93, 93, 93); 10 | border-width: 3px; 11 | text-align: center; 12 | } 13 | button{ 14 | color:rgb(255, 51, 0); 15 | background-color: #252525 16 | } 17 | #bodyContent{ 18 | width:94vw; 19 | background-color: #252525; 20 | border-style: inset; 21 | border-color:rgb(93, 93, 93); 22 | border-width: 3px; 23 | } -------------------------------------------------------------------------------- /client/src/pages/Dashboard/Dashboard.jsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import NavBar from '../../components/NavBar'; 3 | import EventGraph from '../../components/EventGraph'; 4 | import HomeDisplay from '../../components/HomeDisplay'; 5 | import TraceList from '../../components/TraceList'; 6 | import Metrics from '../../components/Metrics'; 7 | import Settings from '../../components/Settings'; 8 | import { Route, Routes } from 'react-router-dom'; 9 | import { useNavigate } from 'react-router-dom'; 10 | import './Dashboard.css'; 11 | import sampleTraces from '../../assets/sampleTraces.json'; 12 | import ComingSoon from '../ComingSoon' 13 | 14 | import homeIcon from '../../assets/home-1391-svgrepo-com.svg'; 15 | import eventGraphIcon from '../../assets/network-2-1118-svgrepo-com.svg'; 16 | import traceListIcon from '../../assets/list-svgrepo-com.svg'; 17 | import metricsIcon from '../../assets/chart-bar-svgrepo-com.svg'; 18 | import teamIcon from '../../assets/team-svgrepo-com.svg'; 19 | import settingsIcon from '../../assets/settings-svgrepo-com.svg'; 20 | import mapIcon from '../../assets/map-svgrepo-com.svg' 21 | import logoutIcon from '../../assets/logout-svgrepo-com.svg'; 22 | 23 | const Dashboard = () => { 24 | const [currentPage, setCurrentPage] = useState('Home'); 25 | const [currentTrace, setCurrentTrace] = useState(0); 26 | const [traceList, setTraceList] = useState({}); 27 | const [nodeData, setNodeData] = useState(null); 28 | const [refresh, setRefresh] = useState(false); 29 | const [loading, setLoading] = useState(false); 30 | const [appTreeNode, setAppTreeNode] = useState({}); 31 | const [appLogs, setAppLogs] = useState([]); 32 | const [start_value, onChangeStart] = useState(new Date()-1000*60*60*24*7); 33 | const [end_value, onChangeEnd] = useState(new Date()); 34 | 35 | const navigate = useNavigate(); 36 | 37 | 38 | useEffect(() => { 39 | console.log('FIRED OFF USEFFECT'); 40 | setLoading(true); 41 | fetch('/getTraces', { 42 | method: 'GET', 43 | headers: { 44 | 'Content-type': 'application/json', 45 | }, 46 | }) 47 | .then((result) => { 48 | if (result.status === 419) { 49 | navigate('/'); 50 | } 51 | return result.json() 52 | }) 53 | .then((data) => { 54 | setLoading(false); 55 | if (data.length) { 56 | setNodeData(data[currentTrace]); 57 | setTraceList(data); 58 | } else { 59 | console.log('Fetched nothing, defaulting to placeholder data'); 60 | setNodeData(sampleTraces[currentTrace]); 61 | setTraceList(sampleTraces); 62 | } 63 | }) 64 | .catch((err) => console.log(err)); 65 | }, [refresh]); 66 | 67 | useEffect(() => { 68 | setNodeData(traceList[currentTrace]); 69 | }, [currentTrace]); 70 | 71 | useEffect(() => { 72 | //get from right 73 | const getFromRight = (s) => { 74 | let result = ''; 75 | for (let i = s.length-1; i >= 0; i--) { 76 | if (s[i] == '/') return result; 77 | result = s[i] + result; 78 | } 79 | return result; 80 | } 81 | let newAppChildren = []; 82 | //loop thru tracelist get endpt 83 | for (let n = 0; n < traceList.length; n++) { 84 | const url = traceList[n].fullData.Document.http.request.url; 85 | const endpt = getFromRight(url) 86 | // let errors = findErrorsInTrace(traceList[n]); 87 | 88 | //ADDING TO APP TREE CHILDREN 89 | 90 | let found = false; 91 | // console.log(`this is trace${n} endpt`, endpt) 92 | //loop thru children get endpt 93 | for (let j = 0; j < newAppChildren.length; j++) { 94 | let appChildrenUrl = newAppChildren[j].fullData.Document.http.request.url; 95 | let appChildrenEndpt = getFromRight(appChildrenUrl) 96 | // console.log(`this is appchild${j} endpt`, appChildrenEndpt) 97 | if (appChildrenEndpt == endpt) { 98 | found = true; 99 | } 100 | } 101 | if (!found) { 102 | // console.log('shud b adding') 103 | newAppChildren.push(traceList[n]); 104 | } 105 | } 106 | console.log('this is apptreechildren',newAppChildren); 107 | let logs = []; 108 | for (let i = 0; i < newAppChildren.length; i++){ 109 | logs=[...logs,...traceList[i].logs] 110 | } 111 | let client = { 112 | id :'client', 113 | name:'client', 114 | logs: logs, 115 | children: newAppChildren, 116 | origin: 'client', 117 | //fulldata, has .Document 118 | } 119 | setAppTreeNode(client); 120 | //ADDING TO APP TREE CHILDREN 121 | setAppLogs(logs); 122 | console.log('this is logs: ', logs); 123 | if (newAppChildren.length!= 0) { 124 | console.log(newAppChildren[0].fullData.Document) 125 | 126 | } 127 | 128 | }, [traceList]) 129 | 130 | function logout() { 131 | fetch('/logout', { 132 | method: 'GET', 133 | headers: { 134 | 'Content-type': 'application/json', 135 | }, 136 | }) 137 | .then((response) => { 138 | if(response.status === 200) { 139 | navigate('/'); 140 | } 141 | }) 142 | } 143 | 144 | function Body() { 145 | return ( 146 |
147 | {currentPage === 'Home' && ( 148 | 149 | )} 150 | {currentPage === 'EventGraph' && ( 151 | 155 | )} 156 | {currentPage === 'AppTree' && ( 157 | 161 | )} 162 | {currentPage === 'TraceList' && ( 163 | 175 | )} 176 | {currentPage === 'Metrics' && ( 177 | 178 | )} 179 | {currentPage === 'Team' && ( 180 | 181 | )} 182 | {currentPage === 'Settings' && ( 183 | 184 | )} 185 |
186 | ); 187 | } 188 | return ( 189 |
190 | 191 |
192 | 218 |
219 | 220 |
221 |
222 |
223 | ); 224 | }; 225 | 226 | export default Dashboard; 227 | -------------------------------------------------------------------------------- /client/src/pages/Login.css: -------------------------------------------------------------------------------- 1 | *, 2 | *:before, 3 | *:after { 4 | padding: 0; 5 | margin: 0; 6 | box-sizing: border-box; 7 | } 8 | body { 9 | background-color: #080710; 10 | } 11 | .background { 12 | width: 430px; 13 | height: 520px; 14 | position: absolute; 15 | transform: translate(-50%, -50%); 16 | left: 50%; 17 | top: 50%; 18 | } 19 | .background .shape { 20 | height: 200px; 21 | width: 200px; 22 | position: absolute; 23 | border-radius: 50%; 24 | } 25 | .shape:last-child { 26 | background: linear-gradient(#1845ad, #23a2f6); 27 | left: -80px; 28 | top: -80px; 29 | } 30 | .shape:first-child { 31 | background: linear-gradient(to right, #fd3302, #f09819); 32 | right: -30px; 33 | bottom: -80px; 34 | } 35 | .auth-form { 36 | width: 400px; 37 | background-color: rgba(255, 255, 255, 0.13); 38 | position: absolute; 39 | transform: translate(-50%, -50%); 40 | top: 50%; 41 | left: 50%; 42 | border-radius: 10px; 43 | backdrop-filter: blur(10px); 44 | border: 2px solid rgba(255, 255, 255, 0.1); 45 | box-shadow: 0 0 40px rgba(8, 7, 16, 0.6); 46 | padding: 50px 35px; 47 | } 48 | 49 | .auth-form * { 50 | color: #ffffff; 51 | letter-spacing: 0.5px; 52 | outline: none; 53 | border: none; 54 | } 55 | .auth-form h3 { 56 | font-size: 32px; 57 | font-weight: 500; 58 | line-height: 42px; 59 | text-align: center; 60 | margin-top: 0px; 61 | } 62 | .login-form { 63 | height: 450px; 64 | } 65 | 66 | .signup-form { 67 | height: 690px; 68 | margin-top: 20px; 69 | } 70 | label { 71 | text-align: left; 72 | display: block; 73 | margin-top: 15px; 74 | font-size: 16px; 75 | font-weight: 500; 76 | } 77 | input { 78 | display: block; 79 | height: 40px; 80 | width: 100%; 81 | background-color: rgba(255, 255, 255, 0.07); 82 | border-radius: 3px; 83 | padding: 0 10px; 84 | margin-top: 3px; 85 | font-size: 14px; 86 | font-weight: 300; 87 | } 88 | ::placeholder { 89 | color: #e5e5e5; 90 | } 91 | .login-btn { 92 | color: #080710; 93 | margin-top: 30px; 94 | background-color: #ffffff; 95 | background: linear-gradient(to bottom right, #fd3302, #ff9a5a); 96 | transition: box-shadow 0.2s ease-in-out; 97 | } 98 | 99 | .login-btn:not([disabled]):hover { 100 | box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.5), 101 | -0.125rem -0.125rem 1rem rgba(239, 71, 101, 0.5), 102 | 0.125rem 0.125rem 1rem rgba(255, 154, 90, 0.5); 103 | } 104 | 105 | .btn-login { 106 | color: #080710; 107 | margin-top: 30px; 108 | background-color: #f7f7f7; 109 | transition: box-shadow 0.2s ease-in-out; 110 | } 111 | .btn-login:not([disabled]):hover { 112 | box-shadow: 0 0 0.25rem rgba(0, 0, 0, 0.5), 113 | -0.125rem -0.125rem 1rem rgba(255, 180, 120, 0.5), 114 | 0.125rem 0.125rem 1rem rgba(255, 180, 120, 0.5); 115 | } 116 | .already-have-account { 117 | margin-top: 10px; 118 | margin-bottom: 5px; 119 | } 120 | button { 121 | width: 100%; 122 | background-color: #ffffff; 123 | color: #080710; 124 | padding: 15px 0; 125 | font-size: 18px; 126 | font-weight: 600; 127 | border-radius: 5px; 128 | cursor: pointer; 129 | } 130 | 131 | .error-message { 132 | color: #f09819; 133 | } 134 | 135 | .captcha { 136 | margin-top: 20px; 137 | } 138 | -------------------------------------------------------------------------------- /client/src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import NavBar from '../components/NavBar'; 4 | import { Link } from 'react-router-dom'; 5 | import { getApiBaseUrl } from '../apiBaseUrl.js'; 6 | 7 | import './Login.css'; 8 | 9 | const Login = () => { 10 | const [email, setEmail] = useState(''); 11 | const [password, setPassword] = useState(''); 12 | const [errorMessage, setErrorMessage] = useState(''); 13 | 14 | const navigate = useNavigate(); 15 | 16 | const verifyLogin = (e) => { 17 | const userData = { 18 | email, 19 | password, 20 | }; 21 | fetch(`${getApiBaseUrl()}/verifyUser`, { 22 | method: 'POST', 23 | headers: { 'Content-type': 'application/json' }, 24 | body: JSON.stringify(userData), 25 | }) 26 | .then((response) => { 27 | if (response.status === 200) { 28 | navigate('/dashboard'); 29 | } else if (response.status === 401) { 30 | setErrorMessage('Invalid password or email'); 31 | } 32 | }) 33 | .catch((err) => { 34 | console.log('Error:', err); 35 | }); 36 | }; 37 | return ( 38 |
39 | 40 |
41 |
42 |
43 |
44 |
{ 47 | e.preventDefault(); 48 | verifyLogin(e); 49 | }} 50 | > 51 | {errorMessage &&

{errorMessage}

} 52 | 53 |

Login

54 | 55 | 56 | setEmail(e.target.value)} 61 | autoComplete='off' 62 | /> 63 | 64 | 65 | setPassword(e.target.value)} 70 | autoComplete='off' 71 | /> 72 | 73 | 76 | 77 | 78 | 79 | 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default Login; 86 | -------------------------------------------------------------------------------- /client/src/pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import NavBar from '../components/NavBar'; 4 | import Reaptcha from 'reaptcha'; 5 | import './Login.css'; 6 | 7 | const Signup = () => { 8 | const [email, setEmail] = useState(''); 9 | const [password, setPassword] = useState(''); 10 | const [fullName, setFulltName] = useState(''); 11 | const [confirmPassword, setConfirmPassword] = useState(''); 12 | const [errorMessage, setErrorMessage] = useState(''); 13 | const [captcha, setCaptcha] = useState(''); 14 | 15 | const navigate = useNavigate(); 16 | 17 | const handleSignup = (e) => { 18 | if (password !== confirmPassword) { 19 | setErrorMessage('Passwords do not match'); 20 | return; 21 | } 22 | if (captcha !== 'passed') { 23 | setErrorMessage('Captcha required'); 24 | return; 25 | } 26 | 27 | const userData = { 28 | email, 29 | password, 30 | fullName, 31 | }; 32 | 33 | fetch('/createUser', { 34 | method: 'POST', 35 | headers: { 'Content-type': 'application/json' }, 36 | body: JSON.stringify(userData), 37 | }) 38 | .then((response) => { 39 | console.log(response); 40 | if (response.status === 201) { 41 | navigate('/dashboard'); 42 | } else if (response.status === 409) { 43 | setErrorMessage('Email already exists'); 44 | } 45 | }) 46 | .catch((error) => { 47 | console.log('Error:', error); 48 | }); 49 | }; 50 | 51 | return ( 52 |
53 | 54 |
55 |
56 |
57 |
58 |
{ 61 | e.preventDefault(); 62 | handleSignup(e); 63 | }} 64 | > 65 | {errorMessage &&

{errorMessage}

} 66 | 67 |

Sign Up

68 | 69 | setFulltName(e.target.value)} 74 | /> 75 | 76 | 77 | setEmail(e.target.value)} 82 | /> 83 | 84 | 85 | setPassword(e.target.value)} 90 | /> 91 | 92 | setConfirmPassword(e.target.value)} 97 | /> 98 |
99 | setCaptcha('passed')} 102 | required 103 | /> 104 |
105 | 112 | 113 |

Already have an account?

114 | 121 |
122 |
123 | ); 124 | }; 125 | export default Signup; 126 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.9' 2 | services: 3 | prod: 4 | build: . 5 | container_name: lambdapulse-prod 6 | ports: 7 | - '80:3000' 8 | networks: 9 | - lambdapulse-network 10 | volumes: 11 | - ./:/usr/src/app 12 | env_file: 13 | - .env 14 | depends_on: 15 | - redis 16 | command: npm run serve 17 | 18 | redis: 19 | container_name: redis 20 | image: redis:latest 21 | command: ['redis-server', '--bind', 'redis', '--port', '6379'] 22 | restart: always 23 | environment: 24 | - ALLOW_EMPTY_PASSWORD=yes 25 | ports: 26 | - '6379:6379' 27 | networks: 28 | - lambdapulse-network 29 | 30 | networks: 31 | lambdapulse-network: 32 | driver: bridge 33 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | LambdaPulse 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "osp7project", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "start": "concurrently \"cd ./server && npm run server\" \"cd && npm run dev\"", 8 | "server": "node server/server.js", 9 | "fulldev": " NODE_ENV=DEV concurrently \"redis-server\" \"npx nodemon server/server.js\" \" npx vite \"", 10 | "dev": "vite", 11 | "build": "vite build", 12 | "preview": "vite preview", 13 | "test": "vite build --mode test && vitest", 14 | "test:coverage": "vitest --coverage", 15 | "serve": "concurrently \"npm run server\"", 16 | "fullprod": "concurrently \"redis-server\" \"npx nodemon server/server.js\" \"cd ./client/dist && http-server\"", 17 | "redis-test": "node redis_test.js" 18 | }, 19 | "keywords": [], 20 | "author": "", 21 | "license": "ISC", 22 | "dependencies": { 23 | "@aws-sdk/client-cloudwatch-logs": "^3.316.0", 24 | "@aws-sdk/client-dynamodb": "^3.315.0", 25 | "@aws-sdk/client-xray": "^3.312.0", 26 | "@aws-sdk/lib-dynamodb": "^3.315.0", 27 | "@aws-sdk/util-dynamodb": "^3.315.0", 28 | "aws-sdk": "^2.1354.0", 29 | "axios": "^1.3.5", 30 | "bcrypt": "^5.1.0", 31 | "concurrently": "^8.0.1", 32 | "cookie-parser": "^1.4.6", 33 | "cors": "^2.8.5", 34 | "d3": "^7.8.4", 35 | "dotenv": "^16.0.3", 36 | "express": "^4.18.2", 37 | "install": "^0.13.0", 38 | "ioredis": "^5.3.2", 39 | "jest": "^29.5.0", 40 | "jsonwebtoken": "^9.0.0", 41 | "nodemon": "^2.0.22", 42 | "npm": "^9.6.4", 43 | "pg": "^8.10.0", 44 | "react": "^18.2.0", 45 | "react-d3-tree": "^3.5.1", 46 | "react-data-table-component": "^7.5.3", 47 | "react-datepicker": "^4.11.0", 48 | "react-dom": "^18.2.0", 49 | "react-minimal-pie-chart": "^8.4.0", 50 | "react-router-dom": "^6.10.0", 51 | "reaptcha": "^1.12.1", 52 | "redis": "^4.6.6", 53 | "supertest": "^6.3.3", 54 | "uuid": "^9.0.0" 55 | }, 56 | "devDependencies": { 57 | "@testing-library/jest-dom": "^5.16.5", 58 | "@testing-library/react": "^14.0.0", 59 | "@testing-library/user-event": "^14.4.3", 60 | "@types/react": "^18.0.28", 61 | "@types/react-dom": "^18.0.11", 62 | "@vitejs/plugin-react": "^3.1.0", 63 | "@vitest/coverage-c8": "^0.30.1", 64 | "msw": "^1.2.1", 65 | "playwright": "^1.33.0", 66 | "vite": "^4.2.0", 67 | "vitest": "^0.30.1" 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /server/aws_sdk/sortingSegments.js: -------------------------------------------------------------------------------- 1 | //parse JSON data within a given array of segments 2 | const parseData = (segmentArray) => { 3 | const parsedDocumentData = segmentArray.map((segments) => { 4 | const doc = JSON.parse(segments['Document']); 5 | segments['Document'] = doc; 6 | return segments; 7 | }); 8 | return parsedDocumentData; 9 | }; 10 | 11 | //create node. adjust data as needed 12 | function Node(segment) { 13 | this.id = segment.Document.id; 14 | this.name = segment.Document.name; 15 | this.parent_id = segment.Document.parent_id; 16 | this.time_taken = Math.round( 17 | (segment.Document.end_time - segment.Document.start_time) * 1000 18 | ); 19 | this.subsegments = segment.Document.subsegments; 20 | this.children = []; 21 | this.origin = segment.Document.origin; 22 | this.http = segment.Document.http; 23 | this.fullData = segment; 24 | } 25 | 26 | // create individual node for an array of related segments 27 | const createAllNodes = (segmentarray) => { 28 | return segmentarray.map((segment) => { 29 | return new Node(segment); 30 | }); 31 | }; 32 | 33 | // create root node 34 | const createRoot = (arr) => { 35 | let rootNode; 36 | for (let i = 0; i < arr.length; i++) { 37 | if (!arr[i].parent_id) { 38 | rootNode = arr[i]; 39 | } 40 | } 41 | return rootNode; 42 | }; 43 | 44 | // create parent/child relationship from an array of nodes starting from root 45 | const addChildChildren = (tree, nodeArray) => { 46 | let currNode = tree; 47 | if (!currNode) return; 48 | nodeArray.forEach((node) => { 49 | if (node.parent_id === currNode.id) { 50 | currNode.children.push(node); 51 | } else if (currNode.subsegments) { 52 | currNode.subsegments.forEach((subSegment) => { 53 | if (node.parent_id === subSegment.id) { 54 | currNode.children.push(node); 55 | } 56 | if (subSegment.subsegments) { 57 | if (checkForSubsegments(node, subSegment.subsegments) === true) { 58 | currNode.children.push(node); 59 | } 60 | } 61 | }); 62 | } 63 | }); 64 | for (let i = 0; i < currNode.children.length; i++) { 65 | addChildChildren(currNode.children[i], nodeArray); 66 | } 67 | }; 68 | 69 | // checks nodes subsegments for relationship between nodes if subsegment exists 70 | const checkForSubsegments = (node, subsegment) => { 71 | for (let i = 0; i < subsegment.length; i++) { 72 | if (node.parent_id === subsegment[i].id) return true; 73 | if (subsegment[i].subsegments) { 74 | const result = checkForSubsegments(node, subsegment[i].subsegments); 75 | if (result) return true; 76 | } 77 | } 78 | }; 79 | 80 | const getAverageOfTrace = (nodeArray) => { 81 | let total = 0; 82 | nodeArray.forEach((node) => { 83 | total += node.time_taken; 84 | }); 85 | return total / nodeArray.length; 86 | }; 87 | 88 | const main = (segment) => { 89 | if (!segment) return; 90 | const segmentData = parseData(segment); 91 | const arrayOfSegmentNodes = createAllNodes(segmentData); 92 | const averageTimeTaken = getAverageOfTrace(arrayOfSegmentNodes); 93 | const root = createRoot(arrayOfSegmentNodes); 94 | root.averageTime = averageTimeTaken; 95 | addChildChildren(root, arrayOfSegmentNodes); 96 | return [root, segmentData]; 97 | }; 98 | 99 | module.exports = main; 100 | -------------------------------------------------------------------------------- /server/aws_sdk/traceDetails.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | const { query } = require('../db.config.js'); 3 | const Redis = require('ioredis'); 4 | 5 | const { 6 | XRayClient, 7 | GetTraceSummariesCommand, 8 | BatchGetTracesCommand, 9 | } = require('@aws-sdk/client-xray'); 10 | const { 11 | CloudWatchLogsClient, 12 | FilterLogEventsCommand, 13 | } = require('@aws-sdk/client-cloudwatch-logs'); 14 | 15 | let redisClient; 16 | 17 | if (process.env.NODE_ENV === 'DEV' || process.env.NODE_ENV === 'TEST') { 18 | console.log('devmode in rediscontroller'); 19 | redisClient = new Redis(); 20 | } else { 21 | console.log('not devmode in rediscontroller'); 22 | redisClient = new Redis({ 23 | host: 'redis', 24 | port: 6379, 25 | }); 26 | } 27 | redisClient.on('connect', () => { 28 | console.log('Connected to Redis.'); 29 | }); 30 | 31 | redisClient.on('error', (err) => { 32 | console.error(err); 33 | }); 34 | 35 | const main = require('./sortingSegments'); 36 | 37 | const getTraceMiddleware = { 38 | getSummary: async (req, res, next) => { 39 | if (res.locals.redisTraces != undefined) return next(); 40 | console.log('in getTraceMiddleware'); 41 | 42 | const xClient = new XRayClient({ 43 | credentials: res.locals.awsCredentials, 44 | region: 'us-east-1', 45 | }); 46 | const getTraceSummary = async () => { 47 | console.log('in getTracesummary'); 48 | const endTime = new Date(); 49 | const startTime = new Date(endTime.getTime() - 24 * 60 * 60 * 1000); 50 | 51 | const params = { 52 | StartTime: startTime, 53 | EndTime: endTime, 54 | TimeRangeType: 'TraceId', 55 | }; 56 | 57 | const response = await xClient.send(new GetTraceSummariesCommand(params)); 58 | 59 | return response; 60 | }; 61 | // get the data in xray. will return an array on res 62 | try { 63 | const result = await getTraceSummary(); 64 | const traceArray = result.TraceSummaries; 65 | const traceIds = traceArray.map((node) => { 66 | // console.log(node); 67 | // get arn for each node 68 | // console.log(node.ResourceARNs[1]); 69 | // console.log(node.Id); 70 | return node.Id; 71 | }); 72 | // console.log(traceIds); 73 | res.locals.traceArray = traceIds; 74 | next(); 75 | } catch (err) { 76 | next(err); 77 | } 78 | }, 79 | 80 | // get segment data 81 | getSegmentArray: async (req, res, next) => { 82 | if (res.locals.redisTraces != undefined) return next(); 83 | console.log('in getSegmentArray'); 84 | const xClient = new XRayClient({ 85 | credentials: res.locals.awsCredentials, 86 | region: 'us-east-1', 87 | }); 88 | const getTraceDetails = async (traceIds) => { 89 | const params = { 90 | TraceIds: traceIds, 91 | }; 92 | 93 | const response = await xClient.send(new BatchGetTracesCommand(params)); 94 | return response; 95 | }; 96 | try { 97 | let fullTraceArray = []; 98 | 99 | let currTraceIds = []; 100 | while (res.locals.traceArray.length) { 101 | if (currTraceIds.length < 5) 102 | currTraceIds.push(res.locals.traceArray.shift()); 103 | else { 104 | const result = await getTraceDetails(currTraceIds); 105 | fullTraceArray = fullTraceArray.concat(result.Traces); 106 | currTraceIds = []; 107 | } 108 | } 109 | if (currTraceIds.length > 0) { 110 | const result = await getTraceDetails(currTraceIds); 111 | fullTraceArray = fullTraceArray.concat(result.Traces); 112 | } 113 | console.log(fullTraceArray, 'this is full trace array'); 114 | res.locals.traceSegmentData = fullTraceArray; 115 | next(); 116 | } catch (err) { 117 | next({ log: err }); 118 | } 119 | }, 120 | 121 | sortSegments: async (req, res, next) => { 122 | if (res.locals.redisTraces != undefined) { 123 | res.locals.userTraces = res.locals.redisTraces; 124 | return next(); 125 | } 126 | console.log('in sortedSegments'); 127 | try { 128 | const allNodes = []; 129 | // traceIds can be found at the element 130 | 131 | for (let i = 0; i < res.locals.traceSegmentData.length; i++) { 132 | const currSegment = res.locals.traceSegmentData[i].Segments; 133 | // console.log(currSegment); 134 | const currRoot = main(currSegment); 135 | 136 | // below is the process to get the logs for the lambda functions 137 | // currRoot[0] is the node 138 | let currentAllSegments = currRoot[1]; 139 | 140 | if (currentAllSegments.length) { 141 | for (let i = 0; i < currentAllSegments.length; i++) { 142 | if ( 143 | currentAllSegments[i].Document.origin === 'AWS::Lambda' && 144 | currentAllSegments[i].Document.aws.request_id 145 | ) { 146 | let requestId = currentAllSegments[i].Document.aws.request_id; 147 | let segmentName = `/aws/lambda/${currentAllSegments[i].Document.name}`; 148 | // await getLogs(); 149 | console.log(requestId, ' ', segmentName); 150 | 151 | // call the functino for get logs in here and add the to the node 152 | // add the logs onto currRoot[0].logs = logs or something 153 | 154 | const logs = await getLogs(requestId, segmentName); 155 | 156 | logs.forEach((log) => { 157 | if (log.message.includes('START')) { 158 | currRoot[0].cold_start = true; 159 | } 160 | }); 161 | currRoot[0].logs = logs; 162 | } 163 | } 164 | } 165 | allNodes.push(currRoot[0]); 166 | } 167 | 168 | async function getLogs(requestId, logGroupName) { 169 | const endTime = new Date(); 170 | const startTime = new Date(endTime.getTime() - 60 * 60 * 1000); 171 | 172 | const cloudwatchlogs = new CloudWatchLogsClient({ 173 | credentials: res.locals.awsCredentials, 174 | region: 'us-east-1', 175 | }); 176 | 177 | const params = { 178 | logGroupName, 179 | 180 | startTime: startTime.getTime(), 181 | endTime: endTime.getTime(), 182 | 183 | // filter: 184 | }; 185 | 186 | const command = new FilterLogEventsCommand(params); 187 | 188 | try { 189 | const data = await cloudwatchlogs.send(command); 190 | console.log(data, 'this is the data'); 191 | const node_logs = data.events.filter((segEvent) => { 192 | return segEvent.message.includes(requestId); 193 | }); 194 | return node_logs; 195 | } catch (error) { 196 | console.error('Error fetching logs:', error); 197 | } 198 | } 199 | 200 | // save all nodes to res.locals 201 | res.locals.nodes = allNodes; 202 | 203 | // grab current userId to send a query to the DB 204 | const userId = res.locals.userId; 205 | 206 | // inserting new traces into traces table. On conflict (when traces already in DB) does nothing 207 | try { 208 | const insertTraceQuery = ` 209 | INSERT INTO traces (_id, root_node, role_arn) 210 | VALUES ($1, $2, (SELECT role_arn FROM users WHERE _id = $3)) 211 | ON CONFLICT (_id) DO NOTHING RETURNING *; 212 | `; 213 | for (let i = 0; i < allNodes.length; i++) { 214 | const rootNode = allNodes[i]; 215 | const traceId = rootNode.id; 216 | console.log(rootNode.fullData.Document.start_time); 217 | const resultTraces = await query(insertTraceQuery, [ 218 | traceId, 219 | JSON.stringify(rootNode), 220 | userId, 221 | ]); 222 | } 223 | 224 | // deletes traces older than 7 days 225 | const deleteOldTracesQuery = ` 226 | DELETE FROM traces 227 | WHERE role_arn = (SELECT role_arn FROM users WHERE _id = $1) 228 | AND ((root_node -> 'fullData' -> 'Document' ->> 'start_time'):: 229 | double precision < EXTRACT(EPOCH FROM (NOW() - INTERVAL '7 days')));`; 230 | await query(deleteOldTracesQuery, [userId]); 231 | } catch (err) { 232 | console.log('error', err); 233 | next(err); 234 | } 235 | 236 | // Selecting traces, sorting them in descending order and passing on to res.locals 237 | try { 238 | const selectTracesQuery = ` 239 | SELECT t.root_node 240 | FROM traces t 241 | JOIN users u ON t.role_arn = u.role_arn 242 | WHERE u._id = $1 243 | ORDER BY (t.root_node -> 'fullData' -> 'Document' ->> 'start_time')::double precision DESC; 244 | `; 245 | const tracesResult = await query(selectTracesQuery, [userId]); 246 | 247 | const userTraces = tracesResult.rows.map((row) => row.root_node); 248 | console.log('this is userTraces', userTraces); 249 | res.locals.userTraces = userTraces; 250 | } catch (err) { 251 | console.log('error', err); 252 | next(err); 253 | } 254 | 255 | try { 256 | redisClient.set('Traces', JSON.stringify(res.locals.userTraces)); 257 | } catch (err) { 258 | next(err); 259 | } 260 | next(); 261 | } catch (err) { 262 | next(err); 263 | } 264 | }, 265 | }; 266 | 267 | module.exports = getTraceMiddleware; 268 | -------------------------------------------------------------------------------- /server/controllers/awsCredentialsController.js: -------------------------------------------------------------------------------- 1 | const { AssumeRoleCommand } = require('@aws-sdk/client-sts'); 2 | const { stsClient } = require('../db.config.js'); 3 | const { query } = require('../db.config.js'); 4 | 5 | const awsCredentialsController = {}; 6 | 7 | // retrieve user's AWS Role ARN from the database. 8 | awsCredentialsController.getCredentials = async (req, res, next) => { 9 | console.log('in getCredentials'); 10 | if (res.locals.redisTraces != undefined) { 11 | return next(); 12 | } 13 | console.log('Received creds request at ' + Date.now()); 14 | 15 | const roleResult = await query('SELECT role_arn FROM users WHERE _id = $1 ;', [res.locals.userId]) 16 | const getRole = roleResult.rows[0].role_arn 17 | 18 | const userRoleArn = getRole; 19 | console.log('req body: ', req.body); 20 | 21 | if (!userRoleArn) { 22 | return res.status(400).json({ message: 'User Role ARN is required.' }); 23 | } 24 | 25 | const assumeRoleParams = { 26 | RoleArn: userRoleArn, 27 | RoleSessionName: 'lambdaPulseSession', 28 | }; 29 | // assume the role specified by the Role ARN, create temp credentials 30 | try { 31 | const data = await stsClient.send(new AssumeRoleCommand(assumeRoleParams)); 32 | const temporaryCredentials = { 33 | accessKeyId: data.Credentials.AccessKeyId, 34 | secretAccessKey: data.Credentials.SecretAccessKey, 35 | sessionToken: data.Credentials.SessionToken, 36 | }; 37 | console.log('in awsCredentialsController'); 38 | res.locals.awsCredentials = temporaryCredentials; 39 | return next(); 40 | } catch (err) { 41 | console.error('Error assuming role:', err); 42 | let error = { 43 | log: 'Express error handler caught awsCredentialsController.getCredentials', 44 | message: { err: err }, 45 | }; 46 | return next(error); 47 | } 48 | }; 49 | 50 | module.exports = awsCredentialsController; 51 | -------------------------------------------------------------------------------- /server/controllers/jwtController.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | 3 | 4 | const jwtController = { 5 | createJwt: (req,res,next) => { 6 | console.log("increate jwt") 7 | try { 8 | const token = jwt.sign({id:res.locals.userId}, process.env.JWT_KEY, {expiresIn:"1h"}); 9 | res.cookie("token", token, { 10 | httpOnly:true, 11 | // secure: true, 12 | // maxAge: 100000, 13 | // signed: true, 14 | }) 15 | return next(); 16 | } catch(err) { 17 | console.log('Error', err); 18 | let error = { 19 | log: 'Express error handler caught jwtController.createJwt', 20 | message: { err: err }, 21 | }; 22 | return next(error); 23 | } 24 | }, 25 | verifyJwt: (req,res,next) => { 26 | console.log('in verifyjwt') 27 | console.log('this is req.cookies',req.cookies); 28 | const token = req.cookies.token; 29 | console.log(token); 30 | // console.log("this is req", req); 31 | try { 32 | const user = jwt.verify(token,process.env.JWT_KEY); 33 | console.log(user + ' connected') 34 | const userId = user.id; 35 | res.locals.userId = userId; 36 | return next(); 37 | } catch(err) { 38 | res.clearCookie("token"); 39 | res.sendStatus(419); 40 | } 41 | }, 42 | } 43 | module.exports = jwtController; -------------------------------------------------------------------------------- /server/controllers/redisController.js: -------------------------------------------------------------------------------- 1 | const Redis = require('ioredis'); 2 | 3 | let redisClient; 4 | 5 | if (process.env.NODE_ENV === 'DEV' || process.env.NODE_ENV === 'TEST') { 6 | console.log('devmode in rediscontroller'); 7 | redisClient = new Redis(); 8 | } else { 9 | console.log('not devmode in rediscontroller'); 10 | redisClient = new Redis({ 11 | host: 'redis', 12 | port: 6379, 13 | }); 14 | } 15 | redisClient.on('connect', () => { 16 | console.log('Connected to Redis.'); 17 | }); 18 | 19 | redisClient.on('error', (err) => { 20 | console.error(err); 21 | }); 22 | 23 | const getRedisTraces = async (req, res, next) => { 24 | //Set TableName 25 | const TableName = 'Traces'; 26 | //Pull in logs from redis 27 | try { 28 | let data = await redisClient.get(TableName); 29 | if (data == null) { 30 | console.log('REDIS CACHE MISS'); 31 | return next(); 32 | } else { 33 | console.log('REDIS CACHE HIT'); 34 | res.locals.redisTraces = JSON.parse(data); 35 | return next(); 36 | } 37 | } catch (err) { 38 | console.log('Error', err); 39 | let error = { 40 | log: 'Express error handler caught redisController.getErrLogs', 41 | message: { err: err }, 42 | }; 43 | return next(error); 44 | } 45 | }; 46 | 47 | const clearTraces = async (req, res, next) => { 48 | //Set TableName 49 | console.log('in clearTraces'); 50 | const TableName = 'Traces'; 51 | //Pull in logs from redis 52 | try { 53 | let data = await redisClient.del(TableName); 54 | return next(); 55 | } catch (err) { 56 | console.log('Error', err); 57 | let error = { 58 | log: 'Express error handler caught redisController.clearTraces', 59 | message: { err: err }, 60 | }; 61 | return next(error); 62 | } 63 | }; 64 | 65 | module.exports = { getRedisTraces, clearTraces }; 66 | -------------------------------------------------------------------------------- /server/controllers/userController.js: -------------------------------------------------------------------------------- 1 | const bcrypt = require('bcrypt'); 2 | const { query } = require('../db.config.js'); 3 | const jwt = require('jsonwebtoken'); 4 | 5 | // create user using PostgresSQL 6 | 7 | const createUser = async (req, res, next) => { 8 | const { fullName, email, password } = req.body; 9 | console.log('in create user'); 10 | try { 11 | const getResult = await query('SELECT * FROM users WHERE email = $1', [ 12 | email, 13 | ]); 14 | if (getResult.rows.length > 0) { 15 | return res.sendStatus(409); 16 | } 17 | const salt = await bcrypt.genSalt(10); 18 | const hashedPassword = await bcrypt.hash(password, salt); 19 | 20 | const result = await query( 21 | 'INSERT INTO users (full_name, email, password) VALUES ($1, $2, $3) RETURNING _id', 22 | [fullName, email, hashedPassword] 23 | ); 24 | console.log('result.rows[0].id', result.rows[0]._id); 25 | const userId = result.rows[0]._id; 26 | 27 | console.log('user created successfully'); 28 | res.locals.userId = userId; 29 | 30 | return next(); 31 | } catch (err) { 32 | console.log('Error', err); 33 | let error = { 34 | log: 'Express error handler caught userController.createUser', 35 | message: { err: err }, 36 | }; 37 | return next(error); 38 | } 39 | }; 40 | 41 | // verify user using PostgresSQL 42 | 43 | const verifyUser = async (req, res, next) => { 44 | const { email, password } = req.body; 45 | 46 | try { 47 | const { rows } = await query('SELECT * FROM users WHERE email = $1', [ 48 | email, 49 | ]); 50 | 51 | if (rows.length === 0) { 52 | return res.sendStatus(401); 53 | } 54 | const userData = rows[0]; 55 | console.log('user id is :', userData._id); 56 | console.log('userdata', userData); 57 | const isMatch = await bcrypt.compare(password, userData.password); 58 | if (!isMatch) { 59 | return res.sendStatus(401); 60 | } 61 | res.locals.userId = userData._id; 62 | return next(); 63 | } catch (err) { 64 | console.log('Error', err); 65 | let error = { 66 | log: 'Express error handler caught userController.verifyUser', 67 | message: { err: err }, 68 | }; 69 | return next(error); 70 | } 71 | }; 72 | 73 | const logout = (req, res, next) => { 74 | try { 75 | res.clearCookie('token'); 76 | res.sendStatus(200); 77 | } catch (err) { 78 | console.log('Error', err); 79 | let error = { 80 | log: 'Express error handler caught userController.verifyUser', 81 | message: { err: err }, 82 | }; 83 | return next(error); 84 | } 85 | }; 86 | 87 | module.exports = { createUser, verifyUser, logout }; 88 | -------------------------------------------------------------------------------- /server/db.config.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config(); 2 | 3 | const { Pool } = require('pg'); 4 | 5 | const { STSClient } = require('@aws-sdk/client-sts'); 6 | 7 | const PG_URI = process.env.PG_URI; 8 | 9 | // create a new pool here using the connection string above 10 | const pool = new Pool({ 11 | connectionString: PG_URI, 12 | }); 13 | 14 | // create credentials variable from .env 15 | const region = 'us-east-1'; 16 | const credentials = { 17 | accessKeyId: process.env.AWS_ACCESS_KEY_ID, 18 | secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, 19 | }; 20 | 21 | // STSClient to be used later on to get assess role over user's account and get temp creds 22 | const stsClient = new STSClient({ region, credentials }); 23 | 24 | module.exports = { 25 | stsClient, 26 | query: (text, params, callback) => { 27 | console.log('executed query', text); 28 | return pool.query(text, params, callback); 29 | }, 30 | }; 31 | -------------------------------------------------------------------------------- /server/server.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const PORT = process.env.PORT || '3000'; 3 | app = express(); 4 | const cors = require('cors'); 5 | const userController = require('./controllers/userController'); 6 | const redisController = require('./controllers/redisController'); 7 | const awsCredentialsController = require('./controllers/awsCredentialsController'); 8 | const getTraceMiddleware = require('./aws_sdk/traceDetails'); 9 | const jwtController = require('./controllers/jwtController'); 10 | const cookieParser = require('cookie-parser'); 11 | const { query } = require('./db.config.js'); 12 | const path = require('path'); 13 | 14 | app.use(express.static(path.join(__dirname, '../dist'))); 15 | 16 | app.use(cors()); 17 | app.use(express.json()); 18 | app.use(express.urlencoded({ extended: true })); 19 | app.use(cookieParser()); 20 | 21 | // handle CORS 22 | app.use(function (req, res, next) { 23 | res.header('Access-Control-Allow-Origin', '*'); 24 | res.header( 25 | 'Access-Control-Allow-Headers', 26 | 'Origin, X-Requested-With, Content-Type, Accept' 27 | ); 28 | res.header('Access-Control-Allow-Methods', 'GET, POST, OPTIONS, PUT, DELETE'); 29 | next(); 30 | }); 31 | 32 | // route to create user in DB 33 | app.post( 34 | '/createUser', 35 | userController.createUser, 36 | jwtController.createJwt, 37 | (req, res) => { 38 | res.sendStatus(201); 39 | } 40 | ); 41 | 42 | // route to verify user in DB and create JWT 43 | app.post( 44 | '/verifyUser', 45 | userController.verifyUser, 46 | jwtController.createJwt, 47 | (req, res) => { 48 | res.sendStatus(200); 49 | } 50 | ); 51 | 52 | // route to logout 53 | app.get('/logout', redisController.clearTraces, userController.logout); 54 | 55 | // route to retrieve user's ARN from DB 56 | app.get('/getCurrentArn', jwtController.verifyJwt, async (req, res) => { 57 | const currentArn = await query( 58 | 'SELECT role_arn FROM users WHERE _id = $1 ; ', 59 | [res.locals.userId] 60 | ); 61 | res.status(200).send(currentArn); 62 | }); 63 | 64 | // route to set user ARN in DB 65 | app.post('/setUserARN', jwtController.verifyJwt, async (req, res) => { 66 | console.log('in Set User ARN'); 67 | const { userARN } = req.body; 68 | const userId = res.locals.userId; 69 | console.log(userId); 70 | console.log(userId); 71 | try { 72 | await query('UPDATE users SET role_arn = $1 WHERE _id = $2 ;', [ 73 | userARN, 74 | userId, 75 | ]); 76 | } catch (err) { 77 | console.log('Error setting roleARN', err); 78 | } 79 | res.status(200).send({ success: 'User ARN successfully added!' }); 80 | }); 81 | 82 | 83 | // route to get the temp credentials, grab traces from SDK and pass to frontend 84 | app.get( 85 | '/getTraces', 86 | jwtController.verifyJwt, 87 | redisController.getRedisTraces, 88 | awsCredentialsController.getCredentials, 89 | getTraceMiddleware.getSummary, 90 | getTraceMiddleware.getSegmentArray, 91 | getTraceMiddleware.sortSegments, 92 | (req, res) => { 93 | console.log('Sending this back to the frontend:', res.locals.userTraces); 94 | res.status(200).json(res.locals.userTraces); 95 | } 96 | ); 97 | 98 | // routes to access pages on refresh or through link 99 | app.get('/signup', (req, res) => { 100 | res.sendFile(path.join(__dirname, '../dist', 'index.html')); 101 | }); 102 | app.get('/dashboard', (req, res) => { 103 | res.sendFile(path.join(__dirname, '../dist', 'index.html')); 104 | }); 105 | app.get('/about', (req, res) => { 106 | res.sendFile(path.join(__dirname, '../dist', 'index.html')); 107 | }); 108 | 109 | //Route not found 110 | app.use((req, res, err) => { 111 | console.log(err); 112 | res.sendStatus(404); 113 | res.sendStatus(404); 114 | }); 115 | 116 | //Global error handler 117 | app.use((err, req, res, next) => { 118 | const defaultErr = { 119 | log: 'Express error handler caught unknown middleware error', 120 | status: 400, 121 | message: { err: 'An error occurred' }, 122 | }; 123 | const errorObj = Object.assign({}, defaultErr, err); 124 | console.log('Global error: ', errorObj.log); 125 | return res.status(errorObj.status).json(errorObj.message); 126 | }); 127 | 128 | app.listen(PORT, () => console.log(`Listening on port ${PORT}`)); 129 | -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite'; 2 | import react from '@vitejs/plugin-react'; 3 | import dotenv from 'dotenv'; 4 | import path from 'path'; 5 | 6 | dotenv.config({ path: path.resolve(__dirname, './.env') }); 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig(({ mode }) => { 10 | 11 | const config = { 12 | plugins: [react()], 13 | define: { 14 | captchaKey: `"${process.env.VITE_CAPTCHA_KEY}"`, 15 | }, 16 | test: { 17 | globals: true, 18 | environment: 'jsdom', 19 | }, 20 | }; 21 | 22 | config.server = { 23 | host: true, 24 | // port: 3000, 25 | proxy: { 26 | '/getTraces': 'http://localhost:3000/', 27 | '/clearTraces': 'http://localhost:3000/', 28 | '/api': 'http://localhost:3000/', 29 | '/createUser': 'http://localhost:3000/', 30 | '/verifyUser': { 31 | target: 'http://localhost:3000', 32 | changeOrigin: true, 33 | }, 34 | '/logout': 'http://localhost:3000/', 35 | '/setUserARN': 'http://localhost:3000/', 36 | '/getCurrentArn': 'http://localhost:3000/', 37 | }, 38 | }; 39 | return config; 40 | }); 41 | --------------------------------------------------------------------------------