├── .dockerignore ├── .gitignore ├── Dockerfile ├── README.md ├── READMEDEV.md ├── SereneTemplate.json ├── client ├── .eslintrc.cjs ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.cjs ├── src │ ├── App.tsx │ ├── assets │ │ ├── FUNCS.jpeg │ │ ├── ari.jpeg │ │ ├── aws.jpeg │ │ ├── demo1.gif │ │ ├── demo2.gif │ │ ├── demo3.gif │ │ ├── githubcat.png │ │ ├── kenny.jpeg │ │ ├── kyle.jpeg │ │ ├── lambdaFuncs.jpeg │ │ ├── linkedinlogo.png │ │ ├── login.jpeg │ │ ├── logsdemo.gif │ │ ├── metricsdemo.gif │ │ ├── serene.png │ │ ├── signup.jpeg │ │ ├── signupdemo.gif │ │ ├── versionsdemo.gif │ │ ├── wade.jpeg │ │ ├── warmingdemo.gif │ │ ├── waves.png │ │ ├── waves2.png │ │ ├── waves3.png │ │ ├── waves4.png │ │ ├── waves5.png │ │ ├── waves6.png │ │ ├── waves7.png │ │ ├── waves9.png │ │ └── whiteserene.png │ ├── components │ │ ├── FunctionDetails.tsx │ │ ├── Functions.tsx │ │ ├── Home.tsx │ │ ├── LandingPage.tsx │ │ ├── LeftSideBar.tsx │ │ ├── LineGraph.tsx │ │ ├── LogStream.tsx │ │ ├── Login.tsx │ │ ├── Logs.tsx │ │ ├── Metric.tsx │ │ ├── NotFound.tsx │ │ ├── RightSidebar.tsx │ │ ├── Signup.tsx │ │ ├── VersionHistory.tsx │ │ └── Warming.tsx │ ├── index.css │ ├── main.tsx │ ├── shared.ts │ └── vite-env.d.ts ├── tailwind.config.cjs ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts ├── package-lock.json ├── package.json ├── serene.zip └── server ├── .gitignore ├── __test__ ├── cloudWatchRouter.test.js ├── lambdaRouter.test.js ├── userRouter.test.js ├── versionHistoryRouter.test.js └── warmingRouter.test.js ├── controllers ├── authentication │ └── cookieController.ts ├── cloudwatch │ ├── cloudWatchLogController.ts │ └── cloudWatchMetricsController.ts ├── lambda │ └── lambdaController.ts ├── stsController.ts ├── userController.ts ├── versions │ └── versionHistoryController.ts └── warming │ └── warmingController.ts ├── coverage ├── clover.xml ├── coverage-final.json ├── coverage-summary.json ├── lcov-report │ ├── base.css │ ├── block-navigation.js │ ├── favicon.png │ ├── index.html │ ├── prettify.css │ ├── prettify.js │ ├── server │ │ ├── controllers │ │ │ ├── authentication │ │ │ │ ├── cookieController.js.html │ │ │ │ └── index.html │ │ │ ├── cloudwatch │ │ │ │ ├── cloudWatchLogController.js.html │ │ │ │ ├── cloudWatchMetricsController.js.html │ │ │ │ └── index.html │ │ │ ├── index.html │ │ │ ├── lambda │ │ │ │ ├── index.html │ │ │ │ └── lambdaController.js.html │ │ │ ├── stsController.js.html │ │ │ ├── userController.js.html │ │ │ ├── versions │ │ │ │ ├── index.html │ │ │ │ └── versionHistoryController.js.html │ │ │ └── warming │ │ │ │ ├── index.html │ │ │ │ └── warmingController.js.html │ │ ├── index.html │ │ ├── models │ │ │ ├── index.html │ │ │ ├── sessionModel.js.html │ │ │ └── userModel.js.html │ │ ├── routes │ │ │ ├── cloudWatchRouter.js.html │ │ │ ├── index.html │ │ │ ├── lambdaRouter.js.html │ │ │ ├── userRouter.js.html │ │ │ ├── versionRouter.js.html │ │ │ └── warmingRouter.js.html │ │ └── server.js.html │ ├── sort-arrow-sprite.png │ └── sorter.js └── lcov.info ├── models ├── sessionModel.ts └── userModel.ts ├── package-lock.json ├── package.json ├── routes ├── cloudWatchRouter.ts ├── lambdaRouter.ts ├── userRouter.ts ├── versionRouter.ts └── warmingRouter.ts ├── server.ts ├── tsconfig.json ├── tslint.json └── types.ts /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | .env 11 | node_modules 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | 27 | komodoTestTemplate.json -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Production Build 2 | # Build stage for the client 3 | FROM node:18.0-alpine AS builder 4 | 5 | WORKDIR /app 6 | 7 | COPY ./client/package*.json ./ 8 | 9 | RUN npm install 10 | 11 | COPY ./client ./ 12 | 13 | RUN npm install 14 | 15 | RUN npm run build 16 | 17 | 18 | # Stage 2: Production Build 19 | FROM node:18.0-alpine 20 | 21 | COPY ./server/package*.json ./ 22 | 23 | RUN npm install 24 | 25 | COPY ./server ./ 26 | 27 | RUN npm install --only=production 28 | 29 | COPY --from=builder /app/dist ./dist 30 | 31 | RUN npm install 32 | 33 | EXPOSE 3000 34 | 35 | CMD ["npm", "start"] -------------------------------------------------------------------------------- /READMEDEV.md: -------------------------------------------------------------------------------- 1 | INSTRUCTIONS FOR FUTURE SERENE DEVS/ITERATORS 2 | 3 | This Readme provides insutrctions on how to set up your AWS environment so that you can allow users to utilize Serene. 4 | 5 | 1. AWS Account Setup: 6 | a. Create an AWS account if you don't have one already. 7 | 8 | 2. IAM (Identity and Access Management) Setup: 9 | a. Create a New Policy: 10 | - Navigate to the IAM service in AWS and click on "Policies" in the sidebar. 11 | - Create a custom policy allowing certain actions: 12 | - Service: Search for 'STS' -- AWS Security Token Service 13 | - Actions allowed: Write/AssumeRole 14 | - Resources: All resources 15 | - Click next 16 | - Under Policy name, type 'SerenePolicy' 17 | - Click 'Create policy' 18 | 19 | b. Create a New User: 20 | - Navigate back to the IAM service in AWS and click on "Users" in the sidebar. 21 | - Create a new IAM user 22 | - Name your user 'SereneUserParent' 23 | - Check the 'Provide user access to the AWS Management Console' box 24 | - Select 'I want to create an IAM user' 25 | - Select 'custom password' and create a password for your IAM user that you will remember 26 | - For added security, select 'Users must create a new password at next sign-in' 27 | - Hit 'Next' to go to the next page 28 | - Under 'Permission options', select 'Attach policies directly' 29 | - Search for the policy you made in the previous step, select it, and go to the next page 30 | - At this point, you should be on the 'Review and Create' page 31 | - Your 'User details' should look familiar 32 | - Your 'Permissions Summary' should consist of the custom policy you attached, and 'IAMUserChangePassword' 33 | - Create your user and download the .csv in case you forget your credentials 34 | 35 | c. Generate Access Key: 36 | - Navigate back to the IAM service in AWS and click on "Users" in the sidebar. 37 | - Click into SereneUserParent 38 | - Generate an access key for the user by clicking 'Create access key' on the top right hand side of Summary section. Select 'Application running outside AWS'.Provide a tag value of SereneAccessKey. click on 'Create access key' to finalize. This key will be used for programmatic access. 39 | - **WARNING: Save the secret access key provided upon creation, as you will not be able to access this again** 40 | - **You can download the .csv for these access keys as well** 41 | 42 | d. Storing Keys and User Information: 43 | - Store the access key and secret access key in a .env file for application use. 44 | - The secret key and secret access key are being directly accessed by only the STSController 45 | 46 | e. Update SereneTemplate with User ARN: 47 | - Add the ARN of the SereneUserParent to the CloudFormation template named "SereneTemplate." 48 | - This template can be found under SereneTemplate in the root directory of Serene 49 | - Change the value of AWS to newly created SereneUserParent ARN 50 | - We won't be using this yet. We will need to upload this json onto our S3 bucket in the following steps. 51 | 52 | 3. S3 Bucket Setup: 53 | a. Navigate to S3 in your AWS console 54 | - Select 'Create bucket' 55 | b. Create an S3 bucket to store files. 56 | - Name bucket 'serenetemplate' 57 | - turn OFF block all public access 58 | - Enable 'Bucket Versioning' 59 | c. Upload Template and Set Permissions: 60 | - Select newly created 'serenetemplate' under Amazon s3 buckets 61 | - Upload the "SereneTemplate.json" to the S3 bucket. 62 | - Navigate back to "Permissions" for the serenetemplate bucket 63 | - Under Bucket Policy, select 'Edit' 64 | - Copy and paste the below object. Input the serenepolicy Bucket ARN as the value for Resource 65 | { 66 | "Version": "2012-10-17", 67 | "Statement": [ 68 | { 69 | "Effect": "Allow", 70 | "Principal": "*", 71 | "Action": "s3:GetObject", 72 | "Resource": "arn:aws:s3:::serenetemplate/*" 73 | } 74 | ] 75 | } 76 | **Make sure to keep the "/*" at the end of the ARN for the Resource** 77 | 4. URL Generation for AWS CloudFormation: 78 | a. Generate a URL with parameters for quick stack creation using AWS CloudFormation. 79 | b. Nagivate to AWS CloudFormation 80 | - Select 'Create stack' 81 | - Copy and paste s3 URL 82 | - This can be found in the serenetemplate bucket 83 | - Click into the SereneTemplate.json 84 | - Copy the Object URL 85 | - Name the stack 'SereneStack' 86 | - Click 'Next' and then 'Submit' 87 | - While your stack is being created, fill our the below url to test out your stack once it is completed 88 | b. https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/quickcreate?templateURL=${templateURL}&stackName=${stackName} 89 | - eg: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https://serenetemplate.s3.amazonaws.com/SereneTemplate.json&stackName=SereneStack 90 | - eg: https://us-east-1.console.aws.amazon.com/cloudformation/home?region=us-east-1#/stacks/quickcreate?templateURL=https://serene-admin-bucket.s3.amazonaws.com/SereneTemplate.json&stackName=SereneStack 91 | c. The above URL is in the sign up component and is prompted to the user during sign up. This will allow them to create a quick stack based on the SereneTemplate. 92 | -------------------------------------------------------------------------------- /SereneTemplate.json: -------------------------------------------------------------------------------- 1 | { 2 | "AWSTemplateFormatVersion": "2010-09-09", 3 | "Description": "Creation of Role for Serene", 4 | "Resources": { 5 | "SereneRole": { 6 | "Type": "AWS::IAM::Role", 7 | "Properties": { 8 | "AssumeRolePolicyDocument": { 9 | "Version": "2012-10-17", 10 | "Statement": [ 11 | { 12 | "Sid": "Statement1", 13 | "Effect": "Allow", 14 | "Principal": { 15 | "AWS": "arn:aws:iam::097265058099:user/SereneAdmin" 16 | }, 17 | "Action": "sts:AssumeRole" 18 | } 19 | ] 20 | }, 21 | "Policies": [ 22 | { 23 | "PolicyName": "SerenePolicy", 24 | "PolicyDocument": { 25 | "Version": "2012-10-17", 26 | "Statement": [ 27 | { 28 | "Sid": "VisualEditor0", 29 | "Effect": "Allow", 30 | "Action": [ 31 | "logs:DescribeLogGroups", 32 | "logs:DescribeLogStreams", 33 | "logs:GetLogEvents", 34 | "logs:FilterLogEvents", 35 | "cloudwatch:GetMetricData", 36 | "lambda:listFunctions", 37 | "lambda:ListVersionsByFunction", 38 | "lambda:InvokeFunction", 39 | "lambda:ListAliases", 40 | "lambda:GetAlias", 41 | "lambda:*" 42 | ], 43 | "Resource": "*" 44 | } 45 | ] 46 | } 47 | } 48 | ] 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /client/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | env: { browser: true, es2020: true }, 4 | extends: [ 5 | 'eslint:recommended', 6 | 'plugin:@typescript-eslint/recommended', 7 | 'plugin:react-hooks/recommended', 8 | ], 9 | ignorePatterns: ['dist', '.eslintrc.cjs'], 10 | parser: '@typescript-eslint/parser', 11 | plugins: ['react-refresh'], 12 | rules: { 13 | 'react-refresh/only-export-components': [ 14 | 'warn', 15 | { allowConstantExport: true }, 16 | ], 17 | }, 18 | }; 19 | -------------------------------------------------------------------------------- /client/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | parserOptions: { 18 | ecmaVersion: 'latest', 19 | sourceType: 'module', 20 | project: ['./tsconfig.json', './tsconfig.node.json'], 21 | tsconfigRootDir: __dirname, 22 | }, 23 | ``` 24 | 25 | - Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` 26 | - Optionally add `plugin:@typescript-eslint/stylistic-type-checked` 27 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list 28 | -------------------------------------------------------------------------------- /client/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | SERENE 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /client/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serene", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "tsc && vite build", 9 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 10 | "preview": "vite preview" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.11.1", 14 | "@emotion/styled": "^11.11.0", 15 | "@heroicons/react": "^2.0.18", 16 | "@material-tailwind/react": "^2.1.0", 17 | "@mui/material": "^5.14.5", 18 | "autoprefixer": "^10.4.14", 19 | "chart.js": "^4.3.3", 20 | "chartjs-adapter-date-fns": "^3.0.0", 21 | "d3": "^7.8.5", 22 | "date-fns": "^2.30.0", 23 | "install": "^0.13.0", 24 | "module": "^1.2.5", 25 | "npm": "^9.8.1", 26 | "path": "^0.12.7", 27 | "react": "^18.2.0", 28 | "react-chartjs-2": "^5.2.0", 29 | "react-dom": "^18.2.0", 30 | "react-icons": "^4.10.1", 31 | "react-router-dom": "^6.14.2", 32 | "react-spinners-kit": "^1.9.1", 33 | "react-tsparticles": "^2.12.2", 34 | "react-xarrows": "^2.0.2", 35 | "reactjs-popup": "^2.0.5", 36 | "scss": "^0.2.4", 37 | "tsparticles": "^2.12.0", 38 | "tw-elements": "^1.0.0-beta3" 39 | }, 40 | "devDependencies": { 41 | "@types/node": "^20.5.4", 42 | "@types/react": "^18.2.15", 43 | "@types/react-dom": "^18.2.7", 44 | "@typescript-eslint/eslint-plugin": "^6.0.0", 45 | "@typescript-eslint/parser": "^6.0.0", 46 | "@vitejs/plugin-react": "^4.0.3", 47 | "eslint": "^8.45.0", 48 | "eslint-plugin-react-hooks": "^4.6.0", 49 | "eslint-plugin-react-refresh": "^0.4.3", 50 | "postcss": "^8.4.27", 51 | "sass": "^1.64.2", 52 | "tailwindcss": "^3.3.3", 53 | "typescript": "^5.0.2", 54 | "vite": "^4.4.8" 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /client/postcss.config.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /client/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, createContext } from 'react'; // div element does not exist on jsx.intrinsic element or something 2 | import Login from './components/Login'; 3 | import Signup from './components/Signup'; 4 | import Home from './components/Home'; 5 | import VersionHistory from './components/VersionHistory'; 6 | import Metric from './components/Metric'; 7 | import Warming from './components/Warming'; 8 | import Logs from './components/Logs'; 9 | import LandingPage from './components/LandingPage'; 10 | import NotFound from './components/NotFound'; 11 | import { BrowserRouter as Router, Route, Routes } from 'react-router-dom'; 12 | 13 | export const FunctionContext = createContext< 14 | | { 15 | funcName: string; 16 | setFuncName: React.Dispatch>; 17 | } 18 | | undefined 19 | >(undefined); 20 | export const FunctionArnContext = createContext< 21 | | { 22 | funcArn: string; 23 | setFuncArn: React.Dispatch>; 24 | } 25 | | undefined 26 | >(undefined); 27 | export const FunctionDataContext = createContext< 28 | | { 29 | funcData: any[]; 30 | setFuncData: React.Dispatch>; 31 | } 32 | | undefined 33 | >(undefined); 34 | export const WarmingContext = createContext< 35 | | { 36 | warmArray: any[]; 37 | setWarmArray: React.Dispatch>; 38 | } 39 | | undefined 40 | >(undefined); 41 | export const RegionContext = createContext< 42 | | { region: string; setRegion: React.Dispatch> } 43 | | undefined 44 | >(undefined); 45 | export const UserContext = createContext< 46 | | { 47 | currentUser: string; 48 | setCurrentUser: React.Dispatch>; 49 | } 50 | | undefined 51 | >(undefined); 52 | 53 | function App(): JSX.Element { 54 | const [funcName, setFuncName] = useState('SELECT A FUNCTION'); 55 | const [currentUser, setCurrentUser] = useState(''); 56 | const [funcArn, setFuncArn] = useState('SELECT FUNC ARN'); 57 | const [region, setRegion] = useState('us-east-1'); 58 | const [funcData, setFuncData] = useState([]); 59 | const [warmArray, setWarmArray] = useState([]); 60 | 61 | return ( 62 |
63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | } /> 71 | } /> 72 | } /> 73 | } /> 74 | } /> 75 | } /> 76 | } /> 77 | } /> 78 | } /> 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
87 | ); 88 | } 89 | 90 | export default App; 91 | -------------------------------------------------------------------------------- /client/src/assets/FUNCS.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/FUNCS.jpeg -------------------------------------------------------------------------------- /client/src/assets/ari.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/ari.jpeg -------------------------------------------------------------------------------- /client/src/assets/aws.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/aws.jpeg -------------------------------------------------------------------------------- /client/src/assets/demo1.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/demo1.gif -------------------------------------------------------------------------------- /client/src/assets/demo2.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/demo2.gif -------------------------------------------------------------------------------- /client/src/assets/demo3.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/demo3.gif -------------------------------------------------------------------------------- /client/src/assets/githubcat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/githubcat.png -------------------------------------------------------------------------------- /client/src/assets/kenny.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/kenny.jpeg -------------------------------------------------------------------------------- /client/src/assets/kyle.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/kyle.jpeg -------------------------------------------------------------------------------- /client/src/assets/lambdaFuncs.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/lambdaFuncs.jpeg -------------------------------------------------------------------------------- /client/src/assets/linkedinlogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/linkedinlogo.png -------------------------------------------------------------------------------- /client/src/assets/login.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/login.jpeg -------------------------------------------------------------------------------- /client/src/assets/logsdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/logsdemo.gif -------------------------------------------------------------------------------- /client/src/assets/metricsdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/metricsdemo.gif -------------------------------------------------------------------------------- /client/src/assets/serene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/serene.png -------------------------------------------------------------------------------- /client/src/assets/signup.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/signup.jpeg -------------------------------------------------------------------------------- /client/src/assets/signupdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/signupdemo.gif -------------------------------------------------------------------------------- /client/src/assets/versionsdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/versionsdemo.gif -------------------------------------------------------------------------------- /client/src/assets/wade.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/wade.jpeg -------------------------------------------------------------------------------- /client/src/assets/warmingdemo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/warmingdemo.gif -------------------------------------------------------------------------------- /client/src/assets/waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves.png -------------------------------------------------------------------------------- /client/src/assets/waves2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves2.png -------------------------------------------------------------------------------- /client/src/assets/waves3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves3.png -------------------------------------------------------------------------------- /client/src/assets/waves4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves4.png -------------------------------------------------------------------------------- /client/src/assets/waves5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves5.png -------------------------------------------------------------------------------- /client/src/assets/waves6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves6.png -------------------------------------------------------------------------------- /client/src/assets/waves7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves7.png -------------------------------------------------------------------------------- /client/src/assets/waves9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/waves9.png -------------------------------------------------------------------------------- /client/src/assets/whiteserene.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/client/src/assets/whiteserene.png -------------------------------------------------------------------------------- /client/src/components/FunctionDetails.tsx: -------------------------------------------------------------------------------- 1 | import { useContext } from 'react'; 2 | import { useNavigate } from 'react-router-dom'; 3 | import { FunctionContext } from '../App'; 4 | 5 | const FunctionDetails = () => { 6 | const { funcName, setFuncName }: any = useContext(FunctionContext); 7 | const navigate = useNavigate(); 8 | 9 | return ( 10 |
11 | {/* FUNCTION PAGE WINDOW */} 12 |
13 |

14 | {' '} 15 | Function Name: {funcName.toUpperCase()} 16 |

17 |
18 |
19 | { 21 | navigate('/versions'); 22 | }} 23 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 24 | > 25 | 26 | 27 | {' '} 28 | Version History 29 |
30 | for {funcName.toUpperCase()} 31 |
32 |
33 | { 35 | navigate('/metrics'); 36 | }} 37 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 38 | > 39 | 40 | 41 | {' '} 42 | Metrics 43 |
44 | for {funcName.toUpperCase()} 45 |
46 |
47 | { 49 | navigate('/warming'); 50 | }} 51 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 52 | > 53 | 54 | 55 | {' '} 56 | Warm Functions 57 |
58 | for {funcName.toUpperCase()} 59 |
60 |
61 | { 63 | navigate('/logs'); 64 | }} 65 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 66 | > 67 | 68 | 69 | {' '} 70 | View Logs 71 |
72 | for {funcName.toUpperCase()} 73 |
74 |
75 |
76 |
77 |
78 | ); 79 | }; 80 | 81 | export default FunctionDetails; 82 | -------------------------------------------------------------------------------- /client/src/components/Functions.tsx: -------------------------------------------------------------------------------- 1 | // import { FunctionContext } from '@/App'; 2 | import { FunctionContext } from '@/App'; 3 | import { useContext } from 'react'; 4 | 5 | type Props = { 6 | name: string; 7 | }; 8 | 9 | const Functions = ({ name }: Props) => { 10 | const { funcName, setFuncName }: any = useContext(FunctionContext); 11 | 12 | const handleNameButtonClick = (e: React.MouseEvent) => { 13 | 14 | setFuncName((e.target as HTMLInputElement).value); 15 | }; 16 | 17 | return ( 18 | 29 | ); 30 | }; 31 | 32 | export default Functions; 33 | -------------------------------------------------------------------------------- /client/src/components/Home.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useContext } from 'react'; 2 | import LeftSideBar from './LeftSideBar'; 3 | import RightSideBar from './RightSidebar'; 4 | import FunctionDetails from './FunctionDetails'; 5 | import serene from '../assets/serene.png'; 6 | 7 | import { FunctionContext } from '../App'; 8 | 9 | type Props = {}; 10 | 11 | const Home = ({}: Props) => { 12 | const [isRightMenuToggled, setIsRightMenuToggled] = useState(false); 13 | const [data, setData] = useState([]); 14 | const { funcName, setFuncName }: any = useContext(FunctionContext); 15 | 16 | return ( 17 |
18 |
19 | 20 | 21 | 26 | Serene image 27 | 28 | 29 | 30 |
31 | 32 |
33 | {' '} 34 |
35 | 36 |
37 |
© SERENE 2023
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default Home; 44 | -------------------------------------------------------------------------------- /client/src/components/LeftSideBar.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect, useContext } from 'react'; 2 | import Functions from './Functions'; 3 | import { FetchFunctions } from '../shared'; 4 | import waves3 from '../assets/waves3.png'; 5 | import { 6 | FunctionContext, 7 | FunctionDataContext, 8 | FunctionArnContext, 9 | } from '@/App'; 10 | 11 | interface Props {} 12 | 13 | const LeftSideBar = (props: Props) => { 14 | const [showSidebar, setShowSidebar] = useState(false); 15 | const { funcName, setFuncName }: any = useContext(FunctionContext); 16 | const { funcData, setFuncData }: any = useContext(FunctionDataContext); 17 | const { funcArn, setFuncArn }: any = useContext(FunctionArnContext); 18 | 19 | useEffect(() => { 20 | FetchFunctions().then((returnedFuncData) => { 21 | setFuncData(returnedFuncData); 22 | returnedFuncData.forEach((item, index) => { 23 | if (item.name === funcName) { 24 | setFuncArn(item.arn); 25 | } 26 | }); 27 | }); 28 | }, [funcName]); 29 | 30 | return ( 31 |
32 | {showSidebar ? ( 33 | 39 | ) : ( 40 | setShowSidebar(!showSidebar)} 46 | className="fixed z-40 flex items-center cursor-pointer left-4 top-6 " 47 | viewBox="10 0 5 25" 48 | width="55" 49 | height="55" 50 | > 51 | 56 | 57 | )} 58 | 59 |
64 | 68 |

69 | F U N C T I O N S 70 |

71 |
74 |
75 | {funcData.map((item) => ( 76 | 77 | ))} 78 |
79 |
80 |
81 |
82 | ); 83 | }; 84 | 85 | export default LeftSideBar; 86 | -------------------------------------------------------------------------------- /client/src/components/LineGraph.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | Chart, 3 | LineElement, 4 | TimeScale, 5 | LinearScale, 6 | PointElement, 7 | Tooltip, 8 | Legend, 9 | } from 'chart.js'; 10 | import { Line } from 'react-chartjs-2'; 11 | import 'chartjs-adapter-date-fns'; 12 | 13 | type Props = { 14 | TimeStamps: []; 15 | Values: []; 16 | metric: string; 17 | }; 18 | 19 | Chart.register( 20 | LineElement, 21 | TimeScale, 22 | LinearScale, 23 | PointElement, 24 | Tooltip, 25 | Legend 26 | ); 27 | 28 | const LineGraph = ({ TimeStamps, Values, metric }: Props) => { 29 | const newTimeArr = TimeStamps.map((ts, index) => ({ 30 | x: new Date(ts), // Parse date strings into Date objects 31 | y: Values[index], 32 | })); 33 | 34 | return ( 35 |
36 | 72 |
73 | ); 74 | }; 75 | 76 | export default LineGraph; 77 | -------------------------------------------------------------------------------- /client/src/components/LogStream.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { JellyfishSpinner, GridSpinner } from 'react-spinners-kit'; 3 | type Props = { 4 | logStreamArr: []; 5 | isLoading?: boolean; 6 | }; 7 | 8 | type Log = { 9 | message: string, 10 | timestamp: string 11 | } 12 | 13 | const LogStream = ({ logStreamArr, isLoading }: Props) => { 14 | return ( 15 | 16 |
17 | {logStreamArr.length ? ( 18 | isLoading ? ( 19 |
20 | 21 |
22 | ) : ( 23 | logStreamArr.map((log: Log) => ( 24 |
25 |

Message: {log.message}

26 |

Time Stamp: {log.timestamp}

27 |
28 | )) 29 | ) 30 | ) : ( 31 |
32 | 33 |

Please choose a logstream

34 | 35 |
36 | )} 37 |
38 | ); 39 | }; 40 | 41 | export default LogStream; 42 | -------------------------------------------------------------------------------- /client/src/components/Login.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import { Link, useNavigate } from 'react-router-dom'; 3 | import waves from '../assets/waves.png'; 4 | import { FunctionContext } from '../App'; 5 | import { UserContext } from '../App'; 6 | 7 | const Login = () => { 8 | const [username, setUsername] = useState(''); 9 | const [password, setPassword] = useState(''); 10 | const { currentUser, setCurrentUser }: any = useContext(UserContext); 11 | 12 | const navigate = useNavigate(); 13 | 14 | const handleUsernameChange = (e: React.ChangeEvent) => { 15 | setUsername(e.target.value); 16 | }; 17 | const handlePasswordChange = (e: React.ChangeEvent) => { 18 | setPassword(e.target.value); 19 | }; 20 | 21 | const handleSubmit = async (e: React.FormEvent) => { 22 | e.preventDefault(); 23 | const body = { 24 | username, 25 | password, 26 | }; 27 | try { 28 | const response = await fetch('/api/user/login', { 29 | method: 'POST', 30 | headers: { 31 | 'Content-Type': 'application/json', 32 | }, 33 | body: JSON.stringify(body), 34 | }); 35 | if (response.status === 200) { 36 | setCurrentUser(username); 37 | navigate('/home'); 38 | } else { 39 | alert('Invalid username or password'); 40 | } 41 | } catch (error) { 42 | console.log('NOW Error: ', error); 43 | } 44 | }; 45 | 46 | return ( 47 |
48 | 49 |
50 |
54 |
55 | 64 | 65 | 74 |
75 | 81 |
82 |

83 | Don't have an account?{' '} 84 | 85 | 89 | Sign up 90 | {' '} 91 | here 92 | 93 |

94 |
95 |
96 | ); 97 | }; 98 | 99 | export default Login; 100 | -------------------------------------------------------------------------------- /client/src/components/Logs.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect, useContext } from 'react'; 2 | import LeftSideBar from './LeftSideBar'; 3 | import RightSideBar from './RightSidebar'; 4 | import LogStream from './LogStream'; 5 | import { JellyfishSpinner, GridSpinner } from 'react-spinners-kit'; 6 | import { FunctionContext, RegionContext } from '@/App'; 7 | import { useNavigate } from 'react-router-dom'; 8 | import serene from '../assets/serene.png'; 9 | 10 | const Logs = () => { 11 | const [allLogs, setAllLogs] = useState([]); 12 | const [logStream, setLogStream] = useState(''); 13 | const [logArray, setLogArray] = useState(['']); 14 | 15 | const { funcName, setFuncName }: any = useContext(FunctionContext); 16 | // const { region, setRegion} = useContext(RegionContext); 17 | 18 | const navigate = useNavigate(); 19 | 20 | const FetchLogs = async () => { 21 | const body = { 22 | funcName, 23 | }; 24 | try { 25 | const response = await fetch('/api/cloudwatch/getLogs', { 26 | method: 'POST', 27 | headers: { 28 | 'Content-Type': 'application/json', 29 | }, 30 | body: JSON.stringify(body), 31 | }); 32 | const data = response.json(); 33 | return data; 34 | } catch (error) { 35 | console.log('Error is: ', error); 36 | } 37 | }; 38 | 39 | useEffect(() => { 40 | if (funcName !== 'SELECT A FUNCTION') { 41 | FetchLogs().then((funcLogs) => { 42 | setAllLogs(funcLogs); 43 | }); 44 | } 45 | }, [funcName]); 46 | 47 | const handleLogClick = (e) => { 48 | setLogStream(e.target.value); 49 | }; 50 | 51 | useEffect(() => { 52 | console.log('this is logstream', logStream); 53 | }, [logStream]); 54 | 55 | const FetchLogStreams = async () => { 56 | //need logName, streamName, region 57 | const body = { 58 | logName: funcName, 59 | streamName: logStream, 60 | }; 61 | try { 62 | const response = await fetch('/api/cloudwatch/getStreamDetails', { 63 | method: 'POST', 64 | headers: { 65 | 'Content-Type': 'application/json', 66 | }, 67 | body: JSON.stringify(body), 68 | }); 69 | 70 | const data = await response.json(); 71 | setLogArray(data); 72 | console.log('return data from fetchlogstreams', data); 73 | return data; 74 | } catch (error) { 75 | console.log('fetch log stream Error: ', error); 76 | } 77 | }; 78 | 79 | useEffect(() => { 80 | const fetchData = async () => { 81 | await FetchLogStreams(); 82 | }; 83 | fetchData(); 84 | }, [logStream]); 85 | 86 | console.log('LOG ARRAY IS HERE', logArray); 87 | 88 | return ( 89 |
90 | {/* TOP SECTION OF EVERY PAGE */} 91 |
92 | 93 | {' '} 101 | 102 |
103 | 104 |
105 | { 107 | navigate('/home'); 108 | }} 109 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 110 | > 111 | 112 | 113 | Home 114 | 115 | 116 | { 118 | navigate('/versions'); 119 | }} 120 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 121 | > 122 | 123 | 124 | Version History 125 | 126 | 127 | 128 | { 130 | navigate('/metrics'); 131 | }} 132 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 133 | > 134 | 135 | 136 | Metrics 137 | 138 | 139 | 140 | { 142 | navigate('/warming'); 143 | }} 144 | className="w-64 rounded-md px-3.5 py-2 m-1 overflow-hidden relative group cursor-pointer border-2 font-medium border-black text-black text-white text-center" 145 | > 146 | 147 | 148 | Warm Functions 149 | 150 | 151 |
152 | 153 |
154 | {/* this is where all the streamlogs are */} 155 |
156 | {allLogs.map((log) => ( 157 | 167 | ))} 168 |
169 | 170 |
171 | 172 |
173 |
174 |
175 |
© SERENE 2023
176 |
177 |
178 | ); 179 | }; 180 | 181 | export default Logs; 182 | -------------------------------------------------------------------------------- /client/src/components/NotFound.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Link } from 'react-router-dom'; 3 | import { CubeSpinner } from 'react-spinners-kit'; 4 | 5 | const NotFound = () => { 6 | return ( 7 | // MAIN DIV 8 |
9 |
10 |
11 | 12 |

404

13 | 14 |
15 |
16 |

17 | Lost? Let's take you{' '} 18 |

19 | 20 | 21 | home 22 | {' '} 23 | 24 |
25 |
26 |
27 | ); 28 | }; 29 | 30 | export default NotFound; 31 | -------------------------------------------------------------------------------- /client/src/components/Signup.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useContext } from 'react'; 2 | import { Link, useNavigate } from 'react-router-dom'; 3 | import waves2 from '../assets/waves2.png'; 4 | import { RegionContext } from '@/App'; 5 | 6 | type Props = {}; 7 | 8 | const Signup = (props: Props) => { 9 | const [username, setUsername] = useState(''); 10 | const [password, setPassword] = useState(''); 11 | const [arnInput, setArnInput] = useState(''); 12 | const [response, setResponse] = useState(''); 13 | const { region, setRegion }: any = useContext(RegionContext); 14 | 15 | const navigate = useNavigate(); 16 | 17 | const handleUsernameChange = (e: React.ChangeEvent) => { 18 | setUsername(e.target.value); 19 | }; 20 | const handlePasswordChange = (e: React.ChangeEvent) => { 21 | setPassword(e.target.value); 22 | }; 23 | const handleArnChange = (e: React.ChangeEvent) => { 24 | setArnInput(e.target.value); 25 | }; 26 | const handleRegionChange = (e: React.ChangeEvent) => { 27 | setRegion(e.target.value); 28 | }; 29 | 30 | const awsConsoleURL = `https://${region}.console.aws.amazon.com/cloudformation/home?region=${region}#/stacks/quickcreate?templateURL=https://serene-admin-bucket.s3.amazonaws.com/SereneTemplate.json&stackName=SereneStack`; 31 | 32 | const handleSubmit = async (e: React.FormEvent) => { 33 | e.preventDefault(); 34 | // if (!region){} 35 | const body = { 36 | username, 37 | password, 38 | region, 39 | ARN: arnInput, 40 | }; 41 | console.log(body); 42 | try { 43 | await fetch('/api/user/signup', { 44 | method: 'POST', 45 | headers: { 46 | 'Content-Type': 'application/json', 47 | }, 48 | body: JSON.stringify(body), 49 | }); 50 | console.log('fetch successful'); 51 | 52 | navigate('/home'); 53 | } catch (error) { 54 | console.log('NOW Error: ', error); 55 | } 56 | }; 57 | 58 | return ( 59 |
60 | 61 |
62 |
66 |
67 | 76 | 85 | 94 | {/* */} 95 |
96 | 123 | 128 | 129 | 135 |
136 |

137 | Already have an account?{' '} 138 | 139 | 140 | Login 141 | {' '} 142 | here 143 | 144 |

145 | {(() => { 146 | switch (response) { 147 | case 'username taken': 148 | return 'username is already taken'; 149 | default: 150 | return ; 151 | } 152 | })()} 153 |
154 |
155 | ); 156 | }; 157 | 158 | export default Signup; 159 | -------------------------------------------------------------------------------- /client/src/index.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Cabin:wght@400;700&family=Inconsolata:wght@200;300;400;500;600;800;900&family=Oswald&family=Roboto:wght@400;500;700&display=swap'); 2 | 3 | @tailwind base; 4 | @tailwind components; 5 | @tailwind utilities; 6 | 7 | html, 8 | body, 9 | #root, 10 | .app { 11 | height: 100%; 12 | width: 100%; 13 | font-family: "Cabin"; 14 | } 15 | 16 | -------------------------------------------------------------------------------- /client/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom/client' 3 | import App from './App.tsx' 4 | import './index.css' 5 | import {BrowserRouter as Router} from 'react-router-dom' 6 | 7 | ReactDOM.createRoot(document.getElementById('root')!).render( 8 | 9 | 10 | 11 | 12 | , 13 | ) 14 | -------------------------------------------------------------------------------- /client/src/shared.ts: -------------------------------------------------------------------------------- 1 | import {useState, useEffect} from 'react' 2 | 3 | export const profile = "P R O F I L E" 4 | 5 | 6 | export const FetchFunctions = async () => { 7 | try{ 8 | const response = await fetch('/api/lambda/functions') 9 | const data = await response.json(); 10 | return data; 11 | } catch (error) { 12 | console.log('Error is: ', error) 13 | } 14 | 15 | } 16 | 17 | 18 | export const FetchUser = async () => { 19 | try{ 20 | const response = await fetch('/api/user/edit') 21 | const data = response.json(); 22 | return data; 23 | } catch (error) { 24 | console.log('Error is: ', error) 25 | } 26 | } 27 | 28 | 29 | const funcName = 'testingfunc' 30 | const sortBy = 'TimestampDescending' 31 | const period = '5 minutes' 32 | const startDate = '1w' 33 | const region = 'us-east-1' 34 | 35 | export const FetchMetrics = async () => { 36 | const body = { 37 | funcName, 38 | sortBy, 39 | period, 40 | startDate, 41 | region 42 | }; 43 | try { 44 | const response = await fetch('/api/cloudwatch/getMetrics', { 45 | method: 'POST', 46 | headers: { 47 | 'Content-Type': 'application/json', 48 | }, 49 | body: JSON.stringify(body), 50 | }); 51 | 52 | const data = await response.json(); 53 | console.log('fetched Logs: ', data); 54 | console.log('fetch Logs successful'); 55 | return data; 56 | } catch (error) { 57 | console.log('NOW Error: ', error); 58 | } 59 | } 60 | 61 | -------------------------------------------------------------------------------- /client/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /client/tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./index.html", './src/**/*.{js,ts,jsx,tsx}'], // no spaces in file path 4 | theme: { 5 | extend: { 6 | colors: { 7 | 8 | }, 9 | // width: { 10 | // '1096': '1096px', 11 | // }, 12 | // height: { 13 | // '1096': '1096px', 14 | // }, 15 | }, 16 | }, 17 | 18 | plugins: [], 19 | }; 20 | -------------------------------------------------------------------------------- /client/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "paths": { 4 | "@/*": ["./src/*"] 5 | }, 6 | "target": "ES2020", 7 | "useDefineForClassFields": true, 8 | "lib": ["ES2020", "DOM", "DOM.Iterable"], 9 | "module": "CommonJS", 10 | "skipLibCheck": true, 11 | // "types": [ 12 | // "node" 13 | // ], 14 | "noImplicitAny": false, 15 | 16 | /* Bundler mode */ 17 | "moduleResolution": "Node", 18 | "allowSyntheticDefaultImports": true, 19 | "resolveJsonModule": true, 20 | "isolatedModules": true, 21 | "noEmit": true, 22 | "jsx": "react-jsx", 23 | "allowImportingTsExtensions": true, 24 | 25 | /* Linting */ 26 | "strict": true, 27 | "noUnusedLocals": false, 28 | "noUnusedParameters": false, 29 | "noFallthroughCasesInSwitch": true 30 | }, 31 | "include": ["src"], 32 | "references": [{ "path": "./tsconfig.node.json" }] 33 | } 34 | -------------------------------------------------------------------------------- /client/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "composite": true, 4 | "skipLibCheck": true, 5 | "module": "ESNext", 6 | "moduleResolution": "Node", 7 | "allowSyntheticDefaultImports": true 8 | }, 9 | "include": ["vite.config.ts"] 10 | } 11 | -------------------------------------------------------------------------------- /client/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | import path from 'path' 4 | 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | base: '/', 9 | plugins: [ 10 | react(), 11 | ], 12 | resolve: { 13 | alias: [{ find: "@", replacement: path.resolve(__dirname, "src")}] //@ will actually be current directory folder with SRC 14 | }, 15 | build: { 16 | manifest: true, 17 | rollupOptions: { 18 | input: './index.html', 19 | } 20 | }, 21 | server: { 22 | host: true, 23 | 24 | port: 5173, 25 | proxy: { 26 | "/api": { 27 | // target: "http://127.0.0.1:3000", 28 | target: "http://localhost:3000", 29 | // changeOrigin: true, 30 | // rewrite: (path) => path.replace(/^\/api/, ''), 31 | // secure: false, 32 | }, 33 | }, 34 | watch: { 35 | usePolling: true 36 | } 37 | }, 38 | }) 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "@jest/globals": "^29.6.2", 4 | "body-parser": "^1.20.2", 5 | "jest": "^29.6.2", 6 | "supertest": "^6.3.3" 7 | }, 8 | "devDependencies": { 9 | "@types/node": "^20.5.4" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /serene.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/serene.zip -------------------------------------------------------------------------------- /server/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | .env 11 | node_modules 12 | dist 13 | dist-ssr 14 | *.local 15 | 16 | # Editor directories and files 17 | .vscode/* 18 | !.vscode/extensions.json 19 | .idea 20 | .DS_Store 21 | *.suo 22 | *.ntvs* 23 | *.njsproj 24 | *.sln 25 | *.sw? 26 | coverage -------------------------------------------------------------------------------- /server/__test__/cloudWatchRouter.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const express = require('express'); 3 | const mongoose = require("mongoose"); 4 | const { describe, beforeEach, expect, test, jest: requiredJest } = require('@jest/globals'); 5 | const dotenv = require('dotenv').config(); 6 | 7 | const app = require('../server.ts'); 8 | 9 | afterAll(() => { 10 | mongoose.disconnect(); 11 | app.close(); 12 | }) 13 | 14 | /* 15 | cloudWatchRouter (cloudWatchLogController) Testing Suite: 16 | -Test stream names array with valid input 17 | -Test stream names array with wrong funcName 18 | -Test stream names array with wrong credentials 19 | 20 | cloudWatchRouter (cloudWatchMetricsController) Testing Suite: 21 | - 22 | */ 23 | 24 | 25 | // cloudWatchLogsController Testing 26 | 27 | describe('testing view function streams post', () => { 28 | it('should return an array of strings (stream names) and status of 200', async () => { 29 | const reqBody = { 30 | funcName: 'secondFunction' 31 | } 32 | const res = await request(app) 33 | .post('/api/cloudwatch/getLogs') 34 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 35 | .send(reqBody) 36 | console.log('res.body', res.body); 37 | expect(res.status).toEqual(200); 38 | expect(res.body.length).toBeGreaterThan(1); 39 | expect(typeof res.body[0]).toBe('string') 40 | }) 41 | }) 42 | 43 | describe('testing view function streams post', () => { 44 | it('should try to return the stream array but return status of 400 (proper creds - wrong func name)', async () => { 45 | const reqBody = { 46 | funcName: 'thisFuncDoesNotExist' 47 | } 48 | const res = await request(app) 49 | .post('/api/cloudwatch/getLogs') 50 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 51 | .send(reqBody) 52 | expect(res.status).toEqual(400); 53 | }) 54 | }) 55 | 56 | 57 | describe('testing view function streams post', () => { 58 | it('should return status 400 with improper credentials', async () => { 59 | const reqBody = { 60 | funcName: 'secondFunction' 61 | } 62 | const res = await request(app) 63 | .post('/api/cloudwatch/getLogs') 64 | .set('Cookie', `SSID=64dac7ac144ec92c900cf30f`) 65 | .send(reqBody) 66 | expect(res.status).toEqual(400); 67 | }) 68 | }) 69 | 70 | 71 | describe('testing view stream info post', () => { 72 | it('should return status 200 and an array of stream logs', async () => { 73 | const reqBody = { 74 | streamName: '2023/08/09/[$LATEST]1dfeaa37a6f64189ac6bd8fceedfad52', 75 | logName: 'secondFunction' 76 | } 77 | const res = await request(app) 78 | .post('/api/cloudwatch/getStreamDetails') 79 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 80 | .send(reqBody) 81 | expect(res.status).toEqual(200); 82 | expect(res.body.length).toBeGreaterThanOrEqual(3); 83 | }) 84 | }) 85 | 86 | describe('testing view stream info post', () => { 87 | it('should return status 400 (logName is invalid)', async () => { 88 | const reqBody = { 89 | streamName: '2023/08/09/[$LATEST]1dfeaa37a6f64189ac6bd8fceedfad52', 90 | logName: 'thisFuncDoesNotExist' 91 | } 92 | const res = await request(app) 93 | .post('/api/cloudwatch/getStreamDetails') 94 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 95 | .send(reqBody) 96 | expect(res.status).toEqual(400); 97 | }) 98 | }) 99 | 100 | describe('testing view stream info post', () => { 101 | it('should return status 400 (streamName invalid)', async () => { 102 | const reqBody = { 103 | streamName: 'thisStreamNameDoesNotExist', 104 | logName: 'secondFunction' 105 | } 106 | const res = await request(app) 107 | .post('/api/cloudwatch/getStreamDetails') 108 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 109 | .send(reqBody) 110 | expect(res.status).toEqual(400); 111 | }) 112 | }) 113 | 114 | describe('testing view stream info post', () => { 115 | it('should return status 400 (SSID invalid)', async () => { 116 | const reqBody = { 117 | streamName: '2023/08/09/[$LATEST]1dfeaa37a6f64189ac6bd8fceedfad52', 118 | logName: 'secondFunction' 119 | } 120 | const res = await request(app) 121 | .post('/api/cloudwatch/getStreamDetails') 122 | .set('Cookie', `SSID=64dac7ac144ec92c900cf30f`) 123 | .send(reqBody) 124 | expect(res.status).toEqual(400); 125 | }) 126 | }) 127 | 128 | 129 | 130 | // cloudWatchMetricsController Testing 131 | 132 | 133 | describe('testing view metrics post', () => { 134 | it('should return status 200 and an object', async () => { 135 | const reqBody = { 136 | funcName: 'secondFunction', 137 | sortBy: 'TimestampAscending', 138 | period: '15 minutes', 139 | startDate: '3d' 140 | } 141 | const res = await request(app) 142 | .post('/api/cloudwatch/getMetrics') 143 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 144 | .send(reqBody) 145 | expect(res.status).toEqual(200) 146 | expect(typeof res.body).toBe('object') 147 | }) 148 | }) 149 | 150 | 151 | describe('testing view metrics post', () => { 152 | it('should return status 400 (invalid funcName)', async () => { 153 | const reqBody = { 154 | sortBy: 'TimestampAscending', 155 | period: '15 minutes', 156 | startDate: '3d' 157 | } 158 | const res = await request(app) 159 | .post('/api/cloudwatch/getMetrics') 160 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 161 | .send(reqBody) 162 | expect(res.status).toEqual(400) 163 | }) 164 | }) 165 | 166 | describe('testing view metrics post', () => { 167 | it('should return status 400 (invalid sortBy)', async () => { 168 | const reqBody = { 169 | funcName: 'secondFunction', 170 | sortBy: 'notSorting', 171 | period: '15 minutes', 172 | startDate: '3d' 173 | } 174 | const res = await request(app) 175 | .post('/api/cloudwatch/getMetrics') 176 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 177 | .send(reqBody) 178 | expect(res.status).toEqual(400) 179 | }) 180 | }) 181 | 182 | describe('testing view metrics post', () => { 183 | it('should return status 400 (invalid period)', async () => { 184 | const reqBody = { 185 | funcName: 'secondFunction', 186 | sortBy: 'TimestampAscending', 187 | period: 'notARealPeriod', 188 | startDate: '3d' 189 | } 190 | const res = await request(app) 191 | .post('/api/cloudwatch/getMetrics') 192 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 193 | .send(reqBody) 194 | expect(res.status).toEqual(400) 195 | }) 196 | }) 197 | 198 | describe('testing view metrics post', () => { 199 | it('should return status 400 (invalid startDate)', async () => { 200 | const reqBody = { 201 | funcName: 'secondFunction', 202 | sortBy: 'TimestampAscending', 203 | period: '15 minutes', 204 | startDate: 'notARealDate' 205 | } 206 | const res = await request(app) 207 | .post('/api/cloudwatch/getMetrics') 208 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 209 | .send(reqBody) 210 | expect(res.status).toEqual(400) 211 | }) 212 | }) 213 | 214 | 215 | describe('testing view metrics post', () => { 216 | it('should return status 400 (invalid credentials)', async () => { 217 | const reqBody = { 218 | funcName: 'secondFunction', 219 | sortBy: 'TimestampAscending', 220 | period: '15 minutes', 221 | startDate: '3d' 222 | } 223 | const res = await request(app) 224 | .post('/api/cloudwatch/getMetrics') 225 | .set('Cookie', 'SSID=64dac7ac144ec92c900cf30f') 226 | .send(reqBody) 227 | expect(res.status).toEqual(400) 228 | }) 229 | }) -------------------------------------------------------------------------------- /server/__test__/lambdaRouter.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const express = require('express'); 3 | const mongoose = require("mongoose"); 4 | const { describe, beforeEach, expect, test, jest: requiredJest } = require('@jest/globals'); 5 | const dotenv = require('dotenv').config(); 6 | 7 | const app = require('../server.ts'); 8 | 9 | afterAll(() => { 10 | mongoose.disconnect(); 11 | app.close(); 12 | }) 13 | 14 | /* 15 | lambdaRouter Testing Suite: 16 | -Test lambdaRouter get with valid creds 17 | -Test lambdaRouter get with invalid creds 18 | */ 19 | 20 | 21 | describe('test GET request for lambda function list', () => { 22 | it('should return an array of objects (functions)', async () => { 23 | const res = await request(app) 24 | .get('/api/lambda/functions') 25 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 26 | console.log('res.body', res.body) 27 | expect(res.status).toEqual(200); 28 | expect(res.body.length).toBe(2); 29 | }) 30 | }) 31 | 32 | describe('test GET for lambda function list (invalid creds)', () => { 33 | it('should try to return an array but return a status of 400 instead', async () => { 34 | const res = await request(app) 35 | .get('/api/lambda/functions') 36 | .set('Cookie', 'SSID=64dac7ac144ec92c900cf30f') 37 | expect(res.status).toEqual(400); 38 | }) 39 | }) 40 | 41 | 42 | -------------------------------------------------------------------------------- /server/__test__/userRouter.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const express = require('express'); 3 | const mongoose = require('mongoose'); 4 | const { 5 | describe, 6 | beforeEach, 7 | expect, 8 | test, 9 | jest: requiredJest, 10 | } = require('@jest/globals'); 11 | 12 | const app = require('../server.ts'); 13 | 14 | /* 15 | userRouter Testing Suite: 16 | -Test user signup with valid inputs (should pass - does pass) 17 | -Test user signup with invalid inputs (should fail - does fail) 18 | -Test user login with valid inputs (should pass - does pass) 19 | -Test user login with invalid inputs (should fail - does fail) 20 | -Test updating user region and ARN (should pass - does pass) 21 | -Not sure if passing properly -- requires more testing 22 | */ 23 | 24 | afterAll(() => { 25 | // Closing the DB connection allows Jest to exit successfully. 26 | // mongoose.connection.close() 27 | // await app.close(); 28 | mongoose.disconnect(); 29 | app.close(); 30 | }); 31 | 32 | // user signup test 33 | describe('test user signup', () => { 34 | it('should create a new user', async () => { 35 | const requestBody = { 36 | username: 'newTestUsername', 37 | password: 'testPassword', 38 | ARN: 'testARN', 39 | region: 'testRegion', 40 | }; 41 | console.log('in test block'); 42 | const res = await request(app).post('/api/user/signup').send(requestBody); 43 | console.log(res.body); 44 | expect(res.status).toEqual(200); 45 | }); 46 | }); 47 | 48 | describe('test user signup', () => { 49 | it('should reject the user signup', async () => { 50 | const requestBody = { 51 | password: 'testPassword', 52 | ARN: 'testARN', 53 | region: 'testRegion', 54 | }; 55 | const res = await request(app).post('/api/user/signup').send(requestBody); 56 | expect(res.status).toEqual(400); 57 | }); 58 | }); 59 | 60 | describe('test user login', () => { 61 | it('should verify the user', async () => { 62 | const requestBody = { 63 | username: 'testUsername', 64 | password: 'testPassword', 65 | }; 66 | const res = await request(app).post('/api/user/login').send(requestBody); 67 | expect(res.status).toEqual(200); 68 | }); 69 | }); 70 | 71 | describe('test user login', () => { 72 | it('should reject the user login', async () => { 73 | const requestBody = { 74 | username: 'thisUsernameDoesNotExist', 75 | password: 'neitherDoesThisPassword', 76 | }; 77 | const res = await request(app).post('/api/user/login').send(requestBody); 78 | expect(res.status).toEqual(400); 79 | }); 80 | }); 81 | 82 | describe('test user update', () => { 83 | it('should successfully update a user', async () => { 84 | const newStuff = { 85 | newRegion: 'newRegion', 86 | newARN: 'newARN', 87 | }; 88 | 89 | // Make the PATCH request to update the user 90 | const res = await request(app) 91 | .patch('/api/user/edit') 92 | .set('Cookie', `SSID=64dac7ac144ec92c900cf30f`) // Set the cookie with the user ID 93 | .send(newStuff); 94 | console.log('res.body: ', res.body); 95 | // Check the response and user properties 96 | expect(res.status).toEqual(200); 97 | expect(res.body.ARN).toBe('newARN'); // Assuming ARN is a property of the user 98 | expect(res.body.region).toBe('newRegion'); 99 | }); 100 | }); 101 | 102 | 103 | describe('test user deletion', () => { 104 | it('should delete the user and return status 200', async () => { 105 | const res = await request(app) 106 | .delete('/api/user/delete') 107 | .set('Cooke', 'SSID=64d4d95291905a7b6650a125') 108 | }) 109 | expect(res.status).toEqual(200); 110 | }) -------------------------------------------------------------------------------- /server/__test__/versionHistoryRouter.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const express = require('express'); 3 | const mongoose = require("mongoose"); 4 | const { describe, beforeEach, expect, test, jest: requiredJest } = require('@jest/globals'); 5 | const dotenv = require('dotenv').config(); 6 | 7 | const app = require('../server.ts'); 8 | 9 | afterAll(() => { 10 | mongoose.disconnect(); 11 | app.close(); 12 | }) 13 | 14 | describe(' GET testing version history', () => { 15 | it('should return an object', async () => { 16 | const reqBody = { 17 | funcName: "secondFunction" 18 | } 19 | const res = await request(app) 20 | .get('/api/versions/versionList') 21 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 22 | .send(reqBody) 23 | expect(res.status).toEqual(200) 24 | expect(typeof res.body).toBe('object') 25 | expect(Object.keys(res.body).length).toEqual(4) 26 | }) 27 | }) 28 | 29 | 30 | describe(' GET testing version history', () => { 31 | it('should return status of 400 (invalid funcName)', async () => { 32 | const reqBody = { 33 | funcName: "thisNameDoesNotExist" 34 | } 35 | const res = await request(app) 36 | .get('/api/versions/versionList') 37 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 38 | .send(reqBody) 39 | expect(res.status).toEqual(400) 40 | }) 41 | }) 42 | 43 | 44 | 45 | 46 | 47 | describe('GET testing version details', () => { 48 | it('it should return an object', async () => { 49 | const reqBody = { 50 | functionArn: "arn:aws:lambda:us-east-1:097265058099:function:secondFunction:1" 51 | } 52 | const res = await request(app) 53 | .get('/api/versions/functionVersion') 54 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 55 | .send(reqBody) 56 | expect(res.status).toEqual(200) 57 | expect(typeof res.body).toBe('object') 58 | expect(Object.keys(res.body).length).toEqual(5) 59 | }) 60 | }) 61 | 62 | 63 | describe('GET testing version details', () => { 64 | it('it should return status 400 (invalid ARN)', async () => { 65 | const reqBody = { 66 | functionArn: "absolutelyFalseArnThisDoesNotWork" 67 | } 68 | const res = await request(app) 69 | .get('/api/versions/functionVersion') 70 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 71 | .send(reqBody) 72 | expect(res.status).toEqual(400) 73 | }) 74 | }) 75 | 76 | 77 | describe('GET testing aliases', () => { 78 | it('should return status of 200 and an array of objects', async () => { 79 | const reqBody = { 80 | funcName: 'secondFunction' 81 | } 82 | const res = await request(app) 83 | .get('/api/versions/getAlias') 84 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 85 | .send(reqBody) 86 | expect(res.status).toEqual(200) 87 | expect(typeof res.body).toBe('object') 88 | expect(Object.keys(res.body).length).toEqual(3) 89 | }) 90 | }) 91 | 92 | 93 | describe('GET testing aliases', () => { 94 | it('should return status 400 (invalid funcName)', async () => { 95 | const reqBody = { 96 | funcName: 'thisFunctionDoesNotExist' 97 | } 98 | const res = await request(app) 99 | .get('/api/versions/getAlias') 100 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 101 | .send(reqBody) 102 | expect(res.status).toEqual(400) 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /server/__test__/warmingRouter.test.js: -------------------------------------------------------------------------------- 1 | const request = require('supertest'); 2 | const express = require('express'); 3 | const mongoose = require("mongoose"); 4 | const { describe, beforeEach, expect, test, jest: requiredJest } = require('@jest/globals'); 5 | const dotenv = require('dotenv').config(); 6 | 7 | const app = require('../server.ts'); 8 | 9 | afterAll(() => { 10 | mongoose.disconnect(); 11 | app.close(); 12 | }) 13 | 14 | describe('testing warming router', () => { 15 | it('should return status of 200', async () => { 16 | const reqBody = { 17 | functionArn: 'arn:aws:lambda:us-east-1:097265058099:function:secondFunction' 18 | } 19 | const res = await request(app) 20 | .get('/api/warming/functions') 21 | .set('Cookie', `SSID=64d51ed8a4b2f36d496865a0`) 22 | .send(reqBody) 23 | expect(res.status).toEqual(200) 24 | expect(res.body).toEqual(200) 25 | }) 26 | }) 27 | 28 | describe('testing warming router', () => { 29 | it('should return status of 400', async () => { 30 | const reqBody = { 31 | functionArn: 'fakearn' 32 | } 33 | const res = await request(app) 34 | .get('/api/warming/functions') 35 | .set('Cookie', 'SSID=64d51ed8a4b2f36d496865a0') 36 | .send(reqBody) 37 | expect(res.status).toEqual(400); 38 | }) 39 | }) -------------------------------------------------------------------------------- /server/controllers/authentication/cookieController.ts: -------------------------------------------------------------------------------- 1 | // import models 2 | import User from '../../models/userModel'; 3 | import Session from '../../models/sessionModel'; 4 | 5 | // import types 6 | import { CookieController, UserInfo } from '../../types' 7 | 8 | const cookieController = {} as CookieController; 9 | 10 | // if user exists, finds user by username and grabs ID 11 | // sends the ID back to be set as the cookie value 12 | cookieController.setSSIDCookie = async(req, res, next) => { 13 | try { 14 | if(res.locals.loginUsername) { 15 | const foundUser: UserInfo = await User.findOne({ username: res.locals.loginUsername }); 16 | res.locals.ssid = foundUser._id; 17 | return next(); 18 | } else { 19 | const foundUser: UserInfo = await User.findOne({ username: res.locals.signUpUsername }); 20 | res.locals.ssid = foundUser._id; 21 | return next(); 22 | }; 23 | } catch (err) { 24 | return next({ 25 | log: `The following error occured: ${err} in setSSIDCookie`, 26 | status: 400, 27 | message: { err: 'An error occured while trying to set a cookie' } 28 | }); 29 | }; 30 | }; 31 | 32 | // not sure if this is doing anything to be honest 33 | cookieController.newSession = async(req, res, next) => { 34 | try{ 35 | const foundUser: UserInfo = await User.findOne({ username: res.locals.loginUsername || res.locals.signUpUsername }); 36 | console.log('req.cookies (line 24): ', req.cookies.SSID); 37 | res.locals.newCookie = req.cookies.SSID; 38 | return next(); 39 | } catch(err){ 40 | return next({ 41 | log: `The following error occured: ${err} in newSession`, 42 | status: 400, 43 | message: { err: 'An error occured while trying to create a session' } 44 | }); 45 | }; 46 | }; 47 | 48 | // clears the cookie upon the user clicking the logout button 49 | cookieController.endSession = async (req, res, next) => { 50 | try{ 51 | res.clearCookie('SSID'); 52 | return next(); 53 | } catch(err){ 54 | return next({ 55 | log: `The following error occured: ${err} in endSession`, 56 | status: 400, 57 | message: { err: 'An error occured while trying to end a session' } 58 | }); 59 | }; 60 | }; 61 | 62 | 63 | export default cookieController; -------------------------------------------------------------------------------- /server/controllers/cloudwatch/cloudWatchLogController.ts: -------------------------------------------------------------------------------- 1 | // import specific AWS commands and types 2 | import { CloudWatchLogsClient, DescribeLogStreamsCommand, GetLogEventsCommand, DescribeLogStreamsCommandOutput, GetLogEventsCommandOutput } from '@aws-sdk/client-cloudwatch-logs'; 3 | // import types 4 | import { CloudWatchLogController, FuncNameBody, FunctionArnBody, StreamInfoBody } from '../../types' 5 | 6 | const cloudWatchLogController = {} as CloudWatchLogController; 7 | 8 | 9 | // takes in function name from user 10 | // sends command and packages data into an array 11 | cloudWatchLogController.viewFunctionStreams = async (req, res, next) => { 12 | const { funcName }: FuncNameBody = req.body; 13 | try { 14 | const cloudWatchLogs: CloudWatchLogsClient = new CloudWatchLogsClient({ region: res.locals.creds.region, credentials: res.locals.creds.roleCreds }); 15 | // all log names will begin with this string, so add the function name from the user 16 | const logName: string = `/aws/lambda/${funcName}`; 17 | 18 | const input = { 19 | logGroupName: logName 20 | }; 21 | // create and send the command from the client 22 | const command: DescribeLogStreamsCommand = new DescribeLogStreamsCommand(input); 23 | const logStreamsRes: DescribeLogStreamsCommandOutput = await cloudWatchLogs.send(command); 24 | 25 | const logStreamNames: string[] = []; 26 | // logStreamRes is an object with the logStreams array on it 27 | logStreamsRes.logStreams.forEach(log => { 28 | // push each logStreamName into our logStreamNames array to be sent to frontend 29 | logStreamNames.push(log.logStreamName); 30 | }); 31 | 32 | res.locals.logStreamNames = logStreamNames; 33 | return next(); 34 | } catch (err) { 35 | return next({ 36 | log: `The following error occured: ${err}`, 37 | status: 400, 38 | message: { err: `An error occured while trying to view the log streams for ${funcName}`} 39 | }); 40 | }; 41 | }; 42 | 43 | // takes in the stream name and log name from the user 44 | // sends command and packages data into an array 45 | cloudWatchLogController.viewStreamInfo = async (req, res, next) => { 46 | const { streamName, logName }: StreamInfoBody = req.body; 47 | try{ 48 | const cloudWatchLogs: CloudWatchLogsClient = new CloudWatchLogsClient({ region: res.locals.creds.region, credentials: res.locals.creds.roleCreds }); 49 | 50 | const logGroupName: string = `/aws/lambda/${logName}`; 51 | 52 | const input = { 53 | logGroupName: logGroupName, 54 | logStreamName: streamName, 55 | startFromHead: true 56 | }; 57 | 58 | // create and send command from client 59 | const command: GetLogEventsCommand = new GetLogEventsCommand(input); 60 | const getLogEvents: GetLogEventsCommandOutput = await cloudWatchLogs.send(command); 61 | 62 | // const { events } = getLogEvents; 63 | const eventList: any[] = getLogEvents.events; 64 | 65 | // formatting all the timestamps on the resulting data 66 | eventList.forEach(event => { 67 | const timeStampDate = new Date(event.timestamp); 68 | event.timestamp = timeStampDate.toString(); 69 | 70 | const ingestionDate = new Date(event.ingestionTime); 71 | event.ingestionTime = ingestionDate.toString(); 72 | }); 73 | 74 | res.locals.events = eventList; 75 | return next(); 76 | } catch(err){ 77 | return next({ 78 | log: `The following error occured: ${err}`, 79 | status: 400, 80 | message: { err: 'An error occured while trying to view the user\'s lambda function stream info' } 81 | }); 82 | }; 83 | }; 84 | 85 | 86 | export default cloudWatchLogController; -------------------------------------------------------------------------------- /server/controllers/lambda/lambdaController.ts: -------------------------------------------------------------------------------- 1 | // import necessary AWS commands 2 | import { 3 | LambdaClient, 4 | ListFunctionsCommand, 5 | ListFunctionsCommandOutput, 6 | } from '@aws-sdk/client-lambda'; 7 | 8 | // import types 9 | import { LambdaController, ArrayFiller } from '../../types'; 10 | 11 | const lambdaController = {} as LambdaController; 12 | 13 | // finds the user in MongoDB by the current cookie and grabs their ARN 14 | // from there we are able to return an array of all their functions in the given region 15 | lambdaController.getFunctions = async (req, res, next) => { 16 | try { 17 | const client: LambdaClient = new LambdaClient({ 18 | credentials: res.locals.creds.roleCreds, 19 | region: res.locals.creds.region, //this should come from front end - req.query 20 | }); 21 | // create and send the command from the client 22 | const listFunctions: ListFunctionsCommand = new ListFunctionsCommand({}); 23 | const data: ListFunctionsCommandOutput = await client.send(listFunctions); 24 | 25 | const funcList = data.Functions; 26 | 27 | const functions: ArrayFiller[] = []; 28 | // iterate through the returned function list and push the desired info into our new array 29 | funcList.forEach((el) => { 30 | functions.push({ 31 | name: el.FunctionName, 32 | description: el.Description, 33 | arn: el.FunctionArn, 34 | }); 35 | }); 36 | res.locals.functions = functions; 37 | return next(); 38 | } catch (err) { 39 | return next({ 40 | log: `The following error occured: ${err}`, 41 | status: 400, 42 | message: { 43 | err: "An error occured while trying to get the user's lambda functions", 44 | }, 45 | }); 46 | }; 47 | }; 48 | 49 | export default lambdaController; 50 | -------------------------------------------------------------------------------- /server/controllers/stsController.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | // import necessary AWS commands and types 3 | import { STSClient, AssumeRoleCommand, AssumeRoleCommandOutput, AssumeRoleCommandInput } from '@aws-sdk/client-sts'; 4 | const dotenv = require('dotenv').config(); 5 | import { Request, Response, NextFunction } from 'express'; 6 | 7 | // import model 8 | import User from '../models/userModel'; 9 | // import types 10 | import { STSController, UserInfo, RoleCreds } from '../types' 11 | 12 | 13 | const stsController = {} as STSController; 14 | 15 | // grabs the user cookie which corresponds to their MongoDB ID 16 | // with user info from DB and .env credentials, generate credentials to be used in other middleware 17 | stsController.getCredentials = async (req: Request, res: Response, next: NextFunction) => { 18 | try { 19 | const foundUser: UserInfo = await User.findOne({ _id: req.cookies.SSID }) 20 | const { ARN, region } = foundUser; 21 | const accessKeyId: string = process.env.accessKeyId; 22 | const secretAccessKey: string = process.env.secretAccessKey 23 | const credentials = { 24 | region: region, 25 | credentials: { 26 | accessKeyId: accessKeyId, 27 | secretAccessKey: secretAccessKey 28 | }, 29 | }; 30 | const stsClient: STSClient = new STSClient(credentials); 31 | 32 | // console.log('stsClient: ', stsClient); 33 | 34 | const params: AssumeRoleCommandInput = { 35 | RoleArn: ARN, //this is IAM role arn that we get from frontend 36 | RoleSessionName: 'Serene_Session', 37 | }; 38 | 39 | // create and send the command from the client 40 | const command: AssumeRoleCommand = new AssumeRoleCommand(params); 41 | 42 | const data: AssumeRoleCommandOutput = await stsClient.send(command); 43 | 44 | const roleCreds = { 45 | accessKeyId: data.Credentials.AccessKeyId, 46 | secretAccessKey: data.Credentials.SecretAccessKey, 47 | sessionToken: data.Credentials.SessionToken, 48 | } as RoleCreds; 49 | // roleCreds/region will be passed into every AWS-centric middleware as necessary credentials 50 | res.locals.creds = {roleCreds, region}; 51 | return next(); 52 | } catch (err) { 53 | return next({ 54 | log: `The following error occured: ${err}`, 55 | status: 400, 56 | message: { err: 'An error occured while trying to get user credentials' } 57 | }); 58 | }; 59 | }; 60 | 61 | export default stsController; -------------------------------------------------------------------------------- /server/controllers/userController.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import bcrypt from 'bcrypt'; 3 | import { Request, Response, NextFunction } from 'express'; 4 | 5 | // import Schema 6 | import User from '../models/userModel'; 7 | // import types 8 | import { UserController, ServerError, CreateUserInfo, UserInfo, Login, UpdatedUserInfo } from '../types'; 9 | 10 | 11 | const userController = {} as UserController; 12 | 13 | // takes in a username, password, ARN, and region from the user 14 | // creates a user in MongoDB 15 | // returns the newly created user's username 16 | userController.createUser = async (req: Request, res: Response, next: NextFunction) => { 17 | const { username, password, ARN, region }: CreateUserInfo = req.body; 18 | 19 | // encrypt the password using bcrypt 20 | const hashedPassword: string = await bcrypt.hash(password, 10); 21 | 22 | try { 23 | // create the user in MongoDB with the username, hashedPassword, ARN, and region 24 | const newUser: UserInfo = await User.create({username, password: hashedPassword, ARN, region }); 25 | // send back the username as the signUpUsername 26 | // (wanted to differentiate btwn signup and login usernames) 27 | res.locals.signUpUsername = newUser.username; 28 | return next(); 29 | } catch (err) { 30 | return next({ 31 | log: `The following error occured: ${err}`, 32 | status: 400, 33 | message: { err: 'An error occured while trying to create a new user' } 34 | }); 35 | }; 36 | }; 37 | 38 | userController.getAllUsers = async (req: Request,res: Response,next: NextFunction) => { 39 | try { 40 | // find all users in Mongo and send them back 41 | const allUsers = await User.find({}); 42 | res.locals.allUsers = allUsers; 43 | return next() 44 | } 45 | catch (err) { 46 | return next({ 47 | log: `The following error occured: ${err} in getAllUsers`, 48 | status: 400, 49 | message: { err: 'An error occured while trying to get all users' } 50 | }); 51 | }; 52 | }; 53 | 54 | // takes in the username and password from user 55 | // verifies if the account exists or not 56 | // returns the current user's username if successful 57 | userController.login = async (req: Request, res: Response, next: NextFunction) => { 58 | try{ 59 | const { username, password }: Login = req.body; 60 | // find by username 61 | const userResult: UserInfo = await User.findOne({ username }); 62 | 63 | // if userResult return nothing, throw err 64 | if(userResult === null || userResult === undefined) { 65 | return next({ 66 | log: `The following error occured: input fields not filled properly`, 67 | status: 400, 68 | message: 'invalid username or password' 69 | }); 70 | }; 71 | // if userResult has a value, move on to below comparisons 72 | // pull pw from mongo and use bcrypt.compare to compare hashed pw with inputted pw 73 | const hashedPassword = userResult.password; 74 | const isPasswordMatch: boolean = await bcrypt.compare(password, hashedPassword); 75 | if(!isPasswordMatch) { 76 | return next({ 77 | log: `The following error occured: invalid username or password`, 78 | status: 400, 79 | message: 'invalid username or password' 80 | }); 81 | }; 82 | // console.log('passwords match!') 83 | res.locals.loginUsername = userResult.username; 84 | return next(); 85 | } catch (err) { 86 | return next({ 87 | log: `The following error occured in login: ${err}`, 88 | status: 400, 89 | message: { err: `An error occured while trying to login` } 90 | }); 91 | }; 92 | }; 93 | 94 | 95 | // takes in the fields to update from the user 96 | // finds the user's account in MongoDB based on the current cookie 97 | // updates the user's information and returns the updated user 98 | userController.updateUser = async (req, res, next) => { 99 | const { newARN, newRegion }: UpdatedUserInfo = req.body; 100 | try { 101 | // filter: _id 102 | // new information: ARN, region 103 | if(newARN && newRegion){ 104 | const updated: UserInfo = await User.findOneAndUpdate( 105 | { _id: req.cookies.SSID }, 106 | { ARN: newARN, region: newRegion }, 107 | { new: true } 108 | ); 109 | res.locals.updatedUser = updated; 110 | return next(); 111 | }; 112 | 113 | if(newARN && !newRegion){ 114 | const updated: UserInfo = await User.findOneAndUpdate( 115 | { _id: req.cookies.SSID }, 116 | { ARN: newARN }, 117 | { new: true } 118 | ); 119 | res.locals.updatedUser = updated; 120 | return next(); 121 | }; 122 | 123 | if(!newARN && newRegion){ 124 | const updated: UserInfo = await User.findOneAndUpdate( 125 | { _id: req.cookies.SSID }, 126 | { region: newRegion }, 127 | { new: true } 128 | ); 129 | res.locals.updatedUser = updated; 130 | return next(); 131 | } 132 | } catch (err) { 133 | return next({ 134 | log: `The following error occured: ${err}`, 135 | status: 400, 136 | message: { err: `An error occured while trying to update a user` } 137 | }); 138 | }; 139 | }; 140 | 141 | // deletes the current user from MongoDB based on cookie 142 | userController.deleteUser = async(req, res, next) => { 143 | try { 144 | const deletedUser = await User.findOneAndDelete({ _id: req.cookies.SSID }); 145 | 146 | res.locals.deletedUser = deletedUser; 147 | return next(); 148 | } catch (err) { 149 | return next({ 150 | log: `The following error occured: ${err}`, 151 | status: 400, 152 | message: { err: `An error occured while trying to delete a user` } 153 | }); 154 | }; 155 | }; 156 | 157 | export default userController; -------------------------------------------------------------------------------- /server/controllers/versions/versionHistoryController.ts: -------------------------------------------------------------------------------- 1 | // import necessary AWS commands 2 | import { LambdaClient, ListVersionsByFunctionCommand, GetFunctionCommand, ListAliasesCommand, ListVersionsByFunctionCommandOutput, GetFunctionCommandOutput, ListAliasesCommandOutput } from '@aws-sdk/client-lambda'; 3 | 4 | // import types 5 | import { VersionHistoryController, FuncNameBody, VersionObject, FunctionArnBody, VersionInfo, AliasList } from '../../types'; 6 | 7 | const versionHistoryController = {} as VersionHistoryController; 8 | 9 | 10 | // takes in the function name from the user and returns an array of the versions of that function 11 | versionHistoryController.viewVersionList = async (req, res, next) => { 12 | const { funcName }: FuncNameBody = req.body; 13 | try { 14 | const client: LambdaClient = new LambdaClient({ 15 | credentials: res.locals.creds.roleCreds, 16 | region: res.locals.creds.region, //this should come from front end - req.query 17 | }); 18 | 19 | const params = { 20 | FunctionName: funcName 21 | }; 22 | 23 | // create and send command from client 24 | const command: ListVersionsByFunctionCommand = new ListVersionsByFunctionCommand(params); 25 | const versionRes: ListVersionsByFunctionCommandOutput = await client.send(command); 26 | 27 | // fix any type 28 | const versions = {} as VersionObject; 29 | 30 | 31 | versionRes.Versions.forEach(func => { 32 | versions[func.Version as keyof VersionObject] = func.FunctionArn 33 | }); 34 | 35 | res.locals.versionList = versions; 36 | return next(); 37 | } catch (err) { 38 | return next({ 39 | log: `The following error occured: ${err}`, 40 | status: 400, 41 | message: 'An error occured when trying to view the version list' 42 | }); 43 | }; 44 | }; 45 | 46 | // takes in the function ARN from the user and returns an object housing info about that version 47 | versionHistoryController.viewFunctionVersion = async (req, res, next) => { 48 | const { functionArn }: FunctionArnBody = req.body; 49 | try { 50 | const client: LambdaClient = new LambdaClient({ 51 | credentials: res.locals.creds.roleCreds, 52 | region: res.locals.creds.region, 53 | }); 54 | 55 | const input = { 56 | FunctionName: functionArn 57 | }; 58 | 59 | // create and send command from client 60 | const command: GetFunctionCommand = new GetFunctionCommand(input); 61 | const response: GetFunctionCommandOutput = await client.send(command); 62 | 63 | // we are updating the timeout such that we can manipulate it and send it back easier 64 | let timeout: number | string; 65 | 66 | (response.Configuration.Timeout > 60) ? timeout = '> 1 min' : timeout = response.Configuration.Timeout; 67 | 68 | // extracting only the information we want from the response to send to frontend 69 | const versionInfo = { 70 | description: response.Configuration.Description, 71 | memory: response.Configuration.MemorySize + ' MB', 72 | timeout: timeout + ' sec', 73 | ephemeralStorage: response.Configuration.EphemeralStorage.Size + ' MB', 74 | linkToFunc: response.Code.Location 75 | } as VersionInfo; 76 | 77 | res.locals.versionInfo = versionInfo; 78 | return next(); 79 | } catch (err) { 80 | return next({ 81 | log: `The following error occured: ${err}`, 82 | status: 400, 83 | message: 'An error occured when trying to view the function version details' 84 | }); 85 | }; 86 | }; 87 | 88 | // takes in function name from user and returns an array of aliases of that function 89 | versionHistoryController.getAlias = async (req, res, next) => { 90 | const { funcName }: FuncNameBody = req.body; 91 | try { 92 | const client: LambdaClient = new LambdaClient({ 93 | credentials: res.locals.creds.roleCreds, 94 | region: res.locals.creds.region, //this should come from front end - req.query 95 | }); 96 | 97 | const input = { 98 | FunctionName: funcName 99 | }; 100 | 101 | // create and send command from client 102 | const command: ListAliasesCommand = new ListAliasesCommand(input); 103 | const aliasList: ListAliasesCommandOutput = await client.send(command); 104 | 105 | // extract the array of aliases from the response 106 | const list: any = aliasList.Aliases; 107 | 108 | // the alias only has weight if it has multiple versions on it 109 | // we wrote this so that if the alias was only pointing to one version, it would show a weight of 100% 110 | list.forEach((alias: any)=> { 111 | if(!alias.RoutingConfig) alias.weight = 1.00; 112 | else{ 113 | // console.log('alias.RoutingConfig: ', alias.RoutingConfig); 114 | let val; 115 | for(let key in alias.RoutingConfig.AdditionalVersionWeights){ 116 | val = alias.RoutingConfig.AdditionalVersionWeights[key]; 117 | // console.log('val: ', val); 118 | }; 119 | alias.weight = 1.00 - val; 120 | }; 121 | }); 122 | 123 | res.locals.aliasList = list; 124 | return next(); 125 | } catch(err) { 126 | return next({ 127 | log: `The following error occured: ${err}`, 128 | status: 400, 129 | message: 'An error occured when trying to view the function alias details' 130 | }); 131 | }; 132 | }; 133 | 134 | export default versionHistoryController; -------------------------------------------------------------------------------- /server/controllers/warming/warmingController.ts: -------------------------------------------------------------------------------- 1 | import { LambdaClient, InvokeCommand, InvokeCommandOutput } from '@aws-sdk/client-lambda'; 2 | import { Request, Response, NextFunction } from 'express'; 3 | 4 | import { WarmingController, WarmingReqBody } from '../../types'; 5 | 6 | const warmingController = {} as WarmingController; 7 | 8 | 9 | /* 10 | Notes: 11 | -Unsure how to type the LambdaClient inputs 12 | */ 13 | 14 | // takes in the function ARN, intervalVar (frequency of invocation), and maxDuration (total duration of invoking period) from user 15 | // only sends back a status 16 | // allows user to navigate site while the algorithm completes 17 | warmingController.warmFunction = async (req: Request, res: Response, next: NextFunction) => { 18 | // intervalVar in minutes and maxDuration in hours 19 | const { functionArn, intervalVar, maxDuration }: WarmingReqBody = req.body; 20 | // intervalVar * 60000 21 | const newInterval: number = intervalVar * 60000; 22 | // maxDuration * 3600000 23 | const newDuration: number = maxDuration * 3600000; 24 | try { 25 | const client: LambdaClient = new LambdaClient({ 26 | credentials: res.locals.creds.roleCreds, 27 | region: res.locals.creds.region, //this should come from front end - req.query 28 | }); 29 | // console.log('interval, duration: ', newInterval, newDuration) 30 | const params = { 31 | FunctionName: functionArn, 32 | }; 33 | 34 | // create command 35 | const command: InvokeCommand = new InvokeCommand(params); 36 | 37 | let counter: number = 0; 38 | const warming = setInterval(async () => { 39 | // invoke the function once every interval 40 | const response: InvokeCommandOutput = await client.send(command); 41 | // increment the counter by the interval every invocation 42 | counter += newInterval; 43 | 44 | let percent: number = counter / newDuration; 45 | 46 | console.log(`${percent * 100}% complete`) 47 | 48 | // once the counter is greater than or equal to the maxDuration, kill the interval 49 | if(counter >= newDuration){ 50 | clearInterval(warming); 51 | console.log('finished'); 52 | }; 53 | }, newInterval); // frequency of invocation 54 | 55 | // tested methodology with console.logs 56 | // const warmingTest = setInterval(async () => { 57 | // console.log('in interval') 58 | // counter += newInterval 59 | // console.log(`increment at ${counter} ms`) 60 | 61 | // if(counter >= newDuration){ 62 | // clearInterval(warmingTest); 63 | // console.log('finished') 64 | // } 65 | // }, newInterval) 66 | 67 | return next(); 68 | } catch (err) { 69 | return next({ 70 | log: `The following error occured in warmFunction: ${err}`, 71 | status: 400, 72 | message: 'An error occured when trying to warm the function', 73 | }); 74 | }; 75 | }; 76 | 77 | export default warmingController; 78 | -------------------------------------------------------------------------------- /server/coverage/clover.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /server/coverage/coverage-final.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /server/coverage/coverage-summary.json: -------------------------------------------------------------------------------- 1 | {"total": {"lines":{"total":379,"covered":333,"skipped":0,"pct":87.86},"statements":{"total":381,"covered":335,"skipped":0,"pct":87.92},"functions":{"total":41,"covered":34,"skipped":0,"pct":82.92},"branches":{"total":64,"covered":47,"skipped":0,"pct":73.43},"branchesTrue":{"total":0,"covered":0,"skipped":0,"pct":100}} 2 | ,"C:\\Users\\Wade\\Komodo\\server\\server.js": {"lines":{"total":35,"covered":34,"skipped":0,"pct":97.14},"functions":{"total":4,"covered":3,"skipped":0,"pct":75},"statements":{"total":36,"covered":35,"skipped":0,"pct":97.22},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 3 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\stsController.js": {"lines":{"total":21,"covered":21,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":21,"covered":21,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 4 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\userController.js": {"lines":{"total":42,"covered":35,"skipped":0,"pct":83.33},"functions":{"total":4,"covered":3,"skipped":0,"pct":75},"statements":{"total":42,"covered":35,"skipped":0,"pct":83.33},"branches":{"total":6,"covered":5,"skipped":0,"pct":83.33}} 5 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\authentication\\cookieController.js": {"lines":{"total":28,"covered":22,"skipped":0,"pct":78.57},"functions":{"total":3,"covered":2,"skipped":0,"pct":66.66},"statements":{"total":28,"covered":22,"skipped":0,"pct":78.57},"branches":{"total":4,"covered":4,"skipped":0,"pct":100}} 6 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\cloudwatch\\cloudWatchLogController.js": {"lines":{"total":39,"covered":39,"skipped":0,"pct":100},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":39,"covered":39,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 7 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\cloudwatch\\cloudWatchMetricsController.js": {"lines":{"total":66,"covered":51,"skipped":0,"pct":77.27},"functions":{"total":4,"covered":4,"skipped":0,"pct":100},"statements":{"total":66,"covered":51,"skipped":0,"pct":77.27},"branches":{"total":42,"covered":29,"skipped":0,"pct":69.04}} 8 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\lambda\\lambdaController.js": {"lines":{"total":15,"covered":14,"skipped":0,"pct":93.33},"functions":{"total":2,"covered":2,"skipped":0,"pct":100},"statements":{"total":15,"covered":14,"skipped":0,"pct":93.33},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 9 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\versions\\versionHistoryController.js": {"lines":{"total":47,"covered":47,"skipped":0,"pct":100},"functions":{"total":5,"covered":5,"skipped":0,"pct":100},"statements":{"total":48,"covered":48,"skipped":0,"pct":100},"branches":{"total":4,"covered":3,"skipped":0,"pct":75}} 10 | ,"C:\\Users\\Wade\\Komodo\\server\\controllers\\warming\\warmingController.js": {"lines":{"total":15,"covered":4,"skipped":0,"pct":26.66},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":15,"covered":4,"skipped":0,"pct":26.66},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 11 | ,"C:\\Users\\Wade\\Komodo\\server\\models\\sessionModel.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 12 | ,"C:\\Users\\Wade\\Komodo\\server\\models\\userModel.js": {"lines":{"total":4,"covered":4,"skipped":0,"pct":100},"functions":{"total":0,"covered":0,"skipped":0,"pct":100},"statements":{"total":4,"covered":4,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 13 | ,"C:\\Users\\Wade\\Komodo\\server\\routes\\cloudWatchRouter.js": {"lines":{"total":12,"covered":12,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":12,"covered":12,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 14 | ,"C:\\Users\\Wade\\Komodo\\server\\routes\\lambdaRouter.js": {"lines":{"total":7,"covered":7,"skipped":0,"pct":100},"functions":{"total":1,"covered":1,"skipped":0,"pct":100},"statements":{"total":7,"covered":7,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 15 | ,"C:\\Users\\Wade\\Komodo\\server\\routes\\userRouter.js": {"lines":{"total":26,"covered":22,"skipped":0,"pct":84.61},"functions":{"total":5,"covered":3,"skipped":0,"pct":60},"statements":{"total":26,"covered":22,"skipped":0,"pct":84.61},"branches":{"total":8,"covered":6,"skipped":0,"pct":75}} 16 | ,"C:\\Users\\Wade\\Komodo\\server\\routes\\versionRouter.js": {"lines":{"total":11,"covered":11,"skipped":0,"pct":100},"functions":{"total":3,"covered":3,"skipped":0,"pct":100},"statements":{"total":11,"covered":11,"skipped":0,"pct":100},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 17 | ,"C:\\Users\\Wade\\Komodo\\server\\routes\\warmingRouter.js": {"lines":{"total":7,"covered":6,"skipped":0,"pct":85.71},"functions":{"total":1,"covered":0,"skipped":0,"pct":0},"statements":{"total":7,"covered":6,"skipped":0,"pct":85.71},"branches":{"total":0,"covered":0,"skipped":0,"pct":100}} 18 | } 19 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/base.css: -------------------------------------------------------------------------------- 1 | body, html { 2 | margin:0; padding: 0; 3 | height: 100%; 4 | } 5 | body { 6 | font-family: Helvetica Neue, Helvetica, Arial; 7 | font-size: 14px; 8 | color:#333; 9 | } 10 | .small { font-size: 12px; } 11 | *, *:after, *:before { 12 | -webkit-box-sizing:border-box; 13 | -moz-box-sizing:border-box; 14 | box-sizing:border-box; 15 | } 16 | h1 { font-size: 20px; margin: 0;} 17 | h2 { font-size: 14px; } 18 | pre { 19 | font: 12px/1.4 Consolas, "Liberation Mono", Menlo, Courier, monospace; 20 | margin: 0; 21 | padding: 0; 22 | -moz-tab-size: 2; 23 | -o-tab-size: 2; 24 | tab-size: 2; 25 | } 26 | a { color:#0074D9; text-decoration:none; } 27 | a:hover { text-decoration:underline; } 28 | .strong { font-weight: bold; } 29 | .space-top1 { padding: 10px 0 0 0; } 30 | .pad2y { padding: 20px 0; } 31 | .pad1y { padding: 10px 0; } 32 | .pad2x { padding: 0 20px; } 33 | .pad2 { padding: 20px; } 34 | .pad1 { padding: 10px; } 35 | .space-left2 { padding-left:55px; } 36 | .space-right2 { padding-right:20px; } 37 | .center { text-align:center; } 38 | .clearfix { display:block; } 39 | .clearfix:after { 40 | content:''; 41 | display:block; 42 | height:0; 43 | clear:both; 44 | visibility:hidden; 45 | } 46 | .fl { float: left; } 47 | @media only screen and (max-width:640px) { 48 | .col3 { width:100%; max-width:100%; } 49 | .hide-mobile { display:none!important; } 50 | } 51 | 52 | .quiet { 53 | color: #7f7f7f; 54 | color: rgba(0,0,0,0.5); 55 | } 56 | .quiet a { opacity: 0.7; } 57 | 58 | .fraction { 59 | font-family: Consolas, 'Liberation Mono', Menlo, Courier, monospace; 60 | font-size: 10px; 61 | color: #555; 62 | background: #E8E8E8; 63 | padding: 4px 5px; 64 | border-radius: 3px; 65 | vertical-align: middle; 66 | } 67 | 68 | div.path a:link, div.path a:visited { color: #333; } 69 | table.coverage { 70 | border-collapse: collapse; 71 | margin: 10px 0 0 0; 72 | padding: 0; 73 | } 74 | 75 | table.coverage td { 76 | margin: 0; 77 | padding: 0; 78 | vertical-align: top; 79 | } 80 | table.coverage td.line-count { 81 | text-align: right; 82 | padding: 0 5px 0 20px; 83 | } 84 | table.coverage td.line-coverage { 85 | text-align: right; 86 | padding-right: 10px; 87 | min-width:20px; 88 | } 89 | 90 | table.coverage td span.cline-any { 91 | display: inline-block; 92 | padding: 0 5px; 93 | width: 100%; 94 | } 95 | .missing-if-branch { 96 | display: inline-block; 97 | margin-right: 5px; 98 | border-radius: 3px; 99 | position: relative; 100 | padding: 0 4px; 101 | background: #333; 102 | color: yellow; 103 | } 104 | 105 | .skip-if-branch { 106 | display: none; 107 | margin-right: 10px; 108 | position: relative; 109 | padding: 0 4px; 110 | background: #ccc; 111 | color: white; 112 | } 113 | .missing-if-branch .typ, .skip-if-branch .typ { 114 | color: inherit !important; 115 | } 116 | .coverage-summary { 117 | border-collapse: collapse; 118 | width: 100%; 119 | } 120 | .coverage-summary tr { border-bottom: 1px solid #bbb; } 121 | .keyline-all { border: 1px solid #ddd; } 122 | .coverage-summary td, .coverage-summary th { padding: 10px; } 123 | .coverage-summary tbody { border: 1px solid #bbb; } 124 | .coverage-summary td { border-right: 1px solid #bbb; } 125 | .coverage-summary td:last-child { border-right: none; } 126 | .coverage-summary th { 127 | text-align: left; 128 | font-weight: normal; 129 | white-space: nowrap; 130 | } 131 | .coverage-summary th.file { border-right: none !important; } 132 | .coverage-summary th.pct { } 133 | .coverage-summary th.pic, 134 | .coverage-summary th.abs, 135 | .coverage-summary td.pct, 136 | .coverage-summary td.abs { text-align: right; } 137 | .coverage-summary td.file { white-space: nowrap; } 138 | .coverage-summary td.pic { min-width: 120px !important; } 139 | .coverage-summary tfoot td { } 140 | 141 | .coverage-summary .sorter { 142 | height: 10px; 143 | width: 7px; 144 | display: inline-block; 145 | margin-left: 0.5em; 146 | background: url(sort-arrow-sprite.png) no-repeat scroll 0 0 transparent; 147 | } 148 | .coverage-summary .sorted .sorter { 149 | background-position: 0 -20px; 150 | } 151 | .coverage-summary .sorted-desc .sorter { 152 | background-position: 0 -10px; 153 | } 154 | .status-line { height: 10px; } 155 | /* yellow */ 156 | .cbranch-no { background: yellow !important; color: #111; } 157 | /* dark red */ 158 | .red.solid, .status-line.low, .low .cover-fill { background:#C21F39 } 159 | .low .chart { border:1px solid #C21F39 } 160 | .highlighted, 161 | .highlighted .cstat-no, .highlighted .fstat-no, .highlighted .cbranch-no{ 162 | background: #C21F39 !important; 163 | } 164 | /* medium red */ 165 | .cstat-no, .fstat-no, .cbranch-no, .cbranch-no { background:#F6C6CE } 166 | /* light red */ 167 | .low, .cline-no { background:#FCE1E5 } 168 | /* light green */ 169 | .high, .cline-yes { background:rgb(230,245,208) } 170 | /* medium green */ 171 | .cstat-yes { background:rgb(161,215,106) } 172 | /* dark green */ 173 | .status-line.high, .high .cover-fill { background:rgb(77,146,33) } 174 | .high .chart { border:1px solid rgb(77,146,33) } 175 | /* dark yellow (gold) */ 176 | .status-line.medium, .medium .cover-fill { background: #f9cd0b; } 177 | .medium .chart { border:1px solid #f9cd0b; } 178 | /* light yellow */ 179 | .medium { background: #fff4c2; } 180 | 181 | .cstat-skip { background: #ddd; color: #111; } 182 | .fstat-skip { background: #ddd; color: #111 !important; } 183 | .cbranch-skip { background: #ddd !important; color: #111; } 184 | 185 | span.cline-neutral { background: #eaeaea; } 186 | 187 | .coverage-summary td.empty { 188 | opacity: .5; 189 | padding-top: 4px; 190 | padding-bottom: 4px; 191 | line-height: 1; 192 | color: #888; 193 | } 194 | 195 | .cover-fill, .cover-empty { 196 | display:inline-block; 197 | height: 12px; 198 | } 199 | .chart { 200 | line-height: 0; 201 | } 202 | .cover-empty { 203 | background: white; 204 | } 205 | .cover-full { 206 | border-right: none !important; 207 | } 208 | pre.prettyprint { 209 | border: none !important; 210 | padding: 0 !important; 211 | margin: 0 !important; 212 | } 213 | .com { color: #999 !important; } 214 | .ignore-none { color: #999; font-weight: normal; } 215 | 216 | .wrapper { 217 | min-height: 100%; 218 | height: auto !important; 219 | height: 100%; 220 | margin: 0 auto -48px; 221 | } 222 | .footer, .push { 223 | height: 48px; 224 | } 225 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/block-navigation.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var jumpToCode = (function init() { 3 | // Classes of code we would like to highlight in the file view 4 | var missingCoverageClasses = ['.cbranch-no', '.cstat-no', '.fstat-no']; 5 | 6 | // Elements to highlight in the file listing view 7 | var fileListingElements = ['td.pct.low']; 8 | 9 | // We don't want to select elements that are direct descendants of another match 10 | var notSelector = ':not(' + missingCoverageClasses.join('):not(') + ') > '; // becomes `:not(a):not(b) > ` 11 | 12 | // Selecter that finds elements on the page to which we can jump 13 | var selector = 14 | fileListingElements.join(', ') + 15 | ', ' + 16 | notSelector + 17 | missingCoverageClasses.join(', ' + notSelector); // becomes `:not(a):not(b) > a, :not(a):not(b) > b` 18 | 19 | // The NodeList of matching elements 20 | var missingCoverageElements = document.querySelectorAll(selector); 21 | 22 | var currentIndex; 23 | 24 | function toggleClass(index) { 25 | missingCoverageElements 26 | .item(currentIndex) 27 | .classList.remove('highlighted'); 28 | missingCoverageElements.item(index).classList.add('highlighted'); 29 | } 30 | 31 | function makeCurrent(index) { 32 | toggleClass(index); 33 | currentIndex = index; 34 | missingCoverageElements.item(index).scrollIntoView({ 35 | behavior: 'smooth', 36 | block: 'center', 37 | inline: 'center' 38 | }); 39 | } 40 | 41 | function goToPrevious() { 42 | var nextIndex = 0; 43 | if (typeof currentIndex !== 'number' || currentIndex === 0) { 44 | nextIndex = missingCoverageElements.length - 1; 45 | } else if (missingCoverageElements.length > 1) { 46 | nextIndex = currentIndex - 1; 47 | } 48 | 49 | makeCurrent(nextIndex); 50 | } 51 | 52 | function goToNext() { 53 | var nextIndex = 0; 54 | 55 | if ( 56 | typeof currentIndex === 'number' && 57 | currentIndex < missingCoverageElements.length - 1 58 | ) { 59 | nextIndex = currentIndex + 1; 60 | } 61 | 62 | makeCurrent(nextIndex); 63 | } 64 | 65 | return function jump(event) { 66 | if ( 67 | document.getElementById('fileSearch') === document.activeElement && 68 | document.activeElement != null 69 | ) { 70 | // if we're currently focused on the search input, we don't want to navigate 71 | return; 72 | } 73 | 74 | switch (event.which) { 75 | case 78: // n 76 | case 74: // j 77 | goToNext(); 78 | break; 79 | case 66: // b 80 | case 75: // k 81 | case 80: // p 82 | goToPrevious(); 83 | break; 84 | } 85 | }; 86 | })(); 87 | window.addEventListener('keydown', jumpToCode); 88 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/server/coverage/lcov-report/favicon.png -------------------------------------------------------------------------------- /server/coverage/lcov-report/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for All files 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files

23 |
24 | 25 |
26 | Unknown% 27 | Statements 28 | 0/0 29 |
30 | 31 | 32 |
33 | Unknown% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | Unknown% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | Unknown% 48 | Lines 49 | 0/0 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 |
FileStatementsBranchesFunctionsLines
83 |
84 |
85 |
86 | 91 | 92 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/prettify.css: -------------------------------------------------------------------------------- 1 | .pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} 2 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/controllers/authentication/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/controllers/authentication 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/controllers/authentication

23 |
24 | 25 |
26 | 78.57% 27 | Statements 28 | 22/28 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 4/4 36 |
37 | 38 | 39 |
40 | 66.66% 41 | Functions 42 | 2/3 43 |
44 | 45 | 46 |
47 | 78.57% 48 | Lines 49 | 22/28 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
cookieController.js 84 |
85 |
78.57%22/28100%4/466.66%2/378.57%22/28
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/controllers/cloudwatch/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/controllers/cloudwatch 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/controllers/cloudwatch

23 |
24 | 25 |
26 | 85.71% 27 | Statements 28 | 90/105 29 |
30 | 31 | 32 |
33 | 69.04% 34 | Branches 35 | 29/42 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 8/8 43 |
44 | 45 | 46 |
47 | 85.71% 48 | Lines 49 | 90/105 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
FileStatementsBranchesFunctionsLines
cloudWatchLogController.js 84 |
85 |
100%39/39100%0/0100%4/4100%39/39
cloudWatchMetricsController.js 99 |
100 |
77.27%51/6669.04%29/42100%4/477.27%51/66
113 |
114 |
115 |
116 | 121 | 122 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/controllers/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/controllers 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/controllers

23 |
24 | 25 |
26 | 88.88% 27 | Statements 28 | 56/63 29 |
30 | 31 | 32 |
33 | 83.33% 34 | Branches 35 | 5/6 36 |
37 | 38 | 39 |
40 | 80% 41 | Functions 42 | 4/5 43 |
44 | 45 | 46 |
47 | 88.88% 48 | Lines 49 | 56/63 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
FileStatementsBranchesFunctionsLines
stsController.js 84 |
85 |
100%21/21100%0/0100%1/1100%21/21
userController.js 99 |
100 |
83.33%35/4283.33%5/675%3/483.33%35/42
113 |
114 |
115 |
116 | 121 | 122 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/controllers/lambda/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/controllers/lambda 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/controllers/lambda

23 |
24 | 25 |
26 | 93.33% 27 | Statements 28 | 14/15 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 2/2 43 |
44 | 45 | 46 |
47 | 93.33% 48 | Lines 49 | 14/15 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
lambdaController.js 84 |
85 |
93.33%14/15100%0/0100%2/293.33%14/15
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/controllers/versions/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/controllers/versions 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/controllers/versions

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 48/48 29 |
30 | 31 | 32 |
33 | 75% 34 | Branches 35 | 3/4 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 5/5 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 47/47 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
versionHistoryController.js 84 |
85 |
100%48/4875%3/4100%5/5100%47/47
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/controllers/warming/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/controllers/warming 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/controllers/warming

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 15/15 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 15/15 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
warmingController.js 84 |
85 |
100%15/15100%0/0100%1/1100%15/15
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server

23 |
24 | 25 |
26 | 97.22% 27 | Statements 28 | 35/36 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 75% 41 | Functions 42 | 3/4 43 |
44 | 45 | 46 |
47 | 97.14% 48 | Lines 49 | 34/35 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 |
FileStatementsBranchesFunctionsLines
server.js 84 |
85 |
97.22%35/36100%0/075%3/497.14%34/35
98 |
99 |
100 |
101 | 106 | 107 | 112 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/models/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/models 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files server/models

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 8/8 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 8/8 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |
66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 |
FileStatementsBranchesFunctionsLines
sessionModel.js 84 |
85 |
100%4/4100%0/0100%0/0100%4/4
userModel.js 99 |
100 |
100%4/4100%0/0100%0/0100%4/4
113 |
114 |
115 |
116 | 121 | 122 | 127 | 128 | 129 | 130 | 131 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/models/sessionModel.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/models/sessionModel.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / server/models sessionModel.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 4/4 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 115x 77 | 5x 78 |   79 |   80 | 5x 81 |   82 |   83 |   84 |   85 |   86 | 5x
const mongoose = require('mongoose');
 87 | const { Schema } = mongoose;
 88 |  
 89 |  
 90 | const sessionSchema = new Schema({
 91 |   cookieID: { type: String, required: true, unique: true },
 92 |   createdAt: { type: Date, expires: 30, default: Date.now}
 93 | })
 94 |  
 95 |  
 96 | module.exports = mongoose.model('Session', sessionSchema);
97 | 98 |
99 |
100 | 105 | 106 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/models/userModel.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/models/userModel.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / server/models userModel.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 4/4 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 0/0 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 4/4 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 125x 78 | 5x 79 |   80 | 5x 81 |   82 |   83 |   84 |   85 |   86 |   87 |   88 | 5x
const mongoose = require('mongoose');
 89 | const { Schema } = mongoose;
 90 |  
 91 | const User = new Schema({
 92 |   username: { type: String, required: true },
 93 |   password: { type: String, required: true },
 94 |   ARN: { type: String, required: true },
 95 |   region: { type: String, required: true, default: 'us-west-1' }
 96 | })
 97 |  
 98 |  
 99 | module.exports = mongoose.model('User', User);
100 | 101 |
102 |
103 | 108 | 109 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/routes/cloudWatchRouter.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/routes/cloudWatchRouter.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / server/routes cloudWatchRouter.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 12/12 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 3/3 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 12/12 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 19 85 | 20 86 | 21 87 | 22 88 | 23 89 | 245x 90 | 5x 91 | 5x 92 | 5x 93 | 5x 94 |   95 |   96 | 5x 97 |   98 | 1x 99 |   100 |   101 |   102 | 5x 103 | 1x 104 |   105 |   106 |   107 | 5x 108 | 1x 109 |   110 |   111 | 5x 112 |  
const express = require('express');
113 | const cloudWatchLogController = require('../controllers/cloudwatch/cloudWatchLogController.js');
114 | const stsController = require('../controllers/stsController.js');
115 | const cloudWatchRouter = express.Router();
116 | const cloudWatchMetricsController = require('../controllers/cloudwatch/cloudWatchMetricsController.js');
117 |  
118 | //view function streams - user needs to specify function name
119 | cloudWatchRouter.post('/getLogs', stsController.getCredentials, cloudWatchLogController.viewFunctionStreams, (req, res) => {
120 |  
121 |     return res.status(200).json(res.locals.logStreamNames);
122 | });
123 |  
124 | //view timestamp and message - user needs to specify function name and stream name
125 | cloudWatchRouter.post('/getStreamDetails', stsController.getCredentials, cloudWatchLogController.viewStreamInfo, (req, res) => {
126 |   return res.status(200).json(res.locals.events);
127 | });
128 |  
129 | // 1st req body { region, roleArn } // 2nd req body { funcName, sortBy, period, startDate, region }
130 | cloudWatchRouter.post('/getMetrics', stsController.getCredentials, cloudWatchMetricsController.getMetrics, (req, res) => {
131 |   return res.status(200).json(res.locals.metrics);
132 | });
133 |  
134 | module.exports = cloudWatchRouter;
135 |  
136 | 137 |
138 |
139 | 144 | 145 | 150 | 151 | 152 | 153 | 154 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/routes/lambdaRouter.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/routes/lambdaRouter.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / server/routes lambdaRouter.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 7/7 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 7/7 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 115x 77 | 5x 78 | 5x 79 | 5x 80 |   81 |   82 | 5x 83 | 1x 84 |   85 |   86 | 5x
const express = require('express');
 87 | const stsController = require('../controllers/stsController.js');
 88 | const lambdaController = require('../controllers/lambda/lambdaController.js')
 89 | const lambdaRouter = express.Router();
 90 |  
 91 | //routers go here
 92 | lambdaRouter.get('/functions', stsController.getCredentials, lambdaController.getFunctions, (req, res) => {
 93 |   return res.status(200).json(res.locals.functions);
 94 | });
 95 |  
 96 | module.exports = lambdaRouter;
97 | 98 |
99 |
100 | 105 | 106 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/routes/versionRouter.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/routes/versionRouter.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / server/routes versionRouter.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 11/11 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 3/3 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 11/11 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 12 78 | 13 79 | 14 80 | 15 81 | 16 82 | 17 83 | 18 84 | 195x 85 | 5x 86 | 5x 87 | 5x 88 |   89 |   90 | 5x 91 | 1x 92 |   93 |   94 | 5x 95 | 1x 96 |   97 |   98 | 5x 99 | 1x 100 |   101 |   102 | 5x
const express = require('express');
103 | const versionRouter = express.Router();
104 | const versionHistoryController = require('../controllers/versions/versionHistoryController.js');
105 | const stsController = require('../controllers/stsController.js')
106 |  
107 |  
108 | versionRouter.get('/versionList', stsController.getCredentials, versionHistoryController.viewVersionList, (req, res) => {
109 |   return res.status(200).json(res.locals.versionList)
110 | })
111 |  
112 | versionRouter.get('/functionVersion', stsController.getCredentials, versionHistoryController.viewFunctionVersion, (req, res) => {
113 |   return res.status(200).json(res.locals.versionInfo);
114 | })
115 | //versionHistoryController.viewVersionList, 
116 | versionRouter.get('/getAlias', stsController.getCredentials, versionHistoryController.getAlias, (req, res) => {
117 |   return res.status(200).json(res.locals.aliasList);
118 | })
119 |  
120 | module.exports = versionRouter;
121 | 122 |
123 |
124 | 129 | 130 | 135 | 136 | 137 | 138 | 139 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/server/routes/warmingRouter.js.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Code coverage report for server/routes/warmingRouter.js 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | 19 | 20 |
21 |
22 |

All files / server/routes warmingRouter.js

23 |
24 | 25 |
26 | 100% 27 | Statements 28 | 7/7 29 |
30 | 31 | 32 |
33 | 100% 34 | Branches 35 | 0/0 36 |
37 | 38 | 39 |
40 | 100% 41 | Functions 42 | 1/1 43 |
44 | 45 | 46 |
47 | 100% 48 | Lines 49 | 7/7 50 |
51 | 52 | 53 |
54 |

55 | Press n or j to go to the next uncovered block, b, p or k for the previous block. 56 |

57 | 63 |
64 |
65 |

 66 | 
1 67 | 2 68 | 3 69 | 4 70 | 5 71 | 6 72 | 7 73 | 8 74 | 9 75 | 10 76 | 11 77 | 125x 78 | 5x 79 | 5x 80 | 5x 81 |   82 | 5x 83 | 1x 84 |   85 |   86 |   87 |   88 | 5x
const express = require('express');
 89 | const warmingRouter = express.Router();
 90 | const warmingController = require('../controllers/warming/warmingController.js');
 91 | const stsController = require('../controllers/stsController.js');
 92 |  
 93 | warmingRouter.get('/functions', stsController.getCredentials, warmingController.warmFunction, (req, res) => {
 94 |   res.status(200).json(res.locals.statusCodeRes)
 95 | })
 96 |  
 97 |  
 98 |  
 99 | module.exports = warmingRouter;
100 | 101 |
102 |
103 | 108 | 109 | 114 | 115 | 116 | 117 | 118 | -------------------------------------------------------------------------------- /server/coverage/lcov-report/sort-arrow-sprite.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/server/coverage/lcov-report/sort-arrow-sprite.png -------------------------------------------------------------------------------- /server/coverage/lcov-report/sorter.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | var addSorting = (function() { 3 | 'use strict'; 4 | var cols, 5 | currentSort = { 6 | index: 0, 7 | desc: false 8 | }; 9 | 10 | // returns the summary table element 11 | function getTable() { 12 | return document.querySelector('.coverage-summary'); 13 | } 14 | // returns the thead element of the summary table 15 | function getTableHeader() { 16 | return getTable().querySelector('thead tr'); 17 | } 18 | // returns the tbody element of the summary table 19 | function getTableBody() { 20 | return getTable().querySelector('tbody'); 21 | } 22 | // returns the th element for nth column 23 | function getNthColumn(n) { 24 | return getTableHeader().querySelectorAll('th')[n]; 25 | } 26 | 27 | function onFilterInput() { 28 | const searchValue = document.getElementById('fileSearch').value; 29 | const rows = document.getElementsByTagName('tbody')[0].children; 30 | for (let i = 0; i < rows.length; i++) { 31 | const row = rows[i]; 32 | if ( 33 | row.textContent 34 | .toLowerCase() 35 | .includes(searchValue.toLowerCase()) 36 | ) { 37 | row.style.display = ''; 38 | } else { 39 | row.style.display = 'none'; 40 | } 41 | } 42 | } 43 | 44 | // loads the search box 45 | function addSearchBox() { 46 | var template = document.getElementById('filterTemplate'); 47 | var templateClone = template.content.cloneNode(true); 48 | templateClone.getElementById('fileSearch').oninput = onFilterInput; 49 | template.parentElement.appendChild(templateClone); 50 | } 51 | 52 | // loads all columns 53 | function loadColumns() { 54 | var colNodes = getTableHeader().querySelectorAll('th'), 55 | colNode, 56 | cols = [], 57 | col, 58 | i; 59 | 60 | for (i = 0; i < colNodes.length; i += 1) { 61 | colNode = colNodes[i]; 62 | col = { 63 | key: colNode.getAttribute('data-col'), 64 | sortable: !colNode.getAttribute('data-nosort'), 65 | type: colNode.getAttribute('data-type') || 'string' 66 | }; 67 | cols.push(col); 68 | if (col.sortable) { 69 | col.defaultDescSort = col.type === 'number'; 70 | colNode.innerHTML = 71 | colNode.innerHTML + ''; 72 | } 73 | } 74 | return cols; 75 | } 76 | // attaches a data attribute to every tr element with an object 77 | // of data values keyed by column name 78 | function loadRowData(tableRow) { 79 | var tableCols = tableRow.querySelectorAll('td'), 80 | colNode, 81 | col, 82 | data = {}, 83 | i, 84 | val; 85 | for (i = 0; i < tableCols.length; i += 1) { 86 | colNode = tableCols[i]; 87 | col = cols[i]; 88 | val = colNode.getAttribute('data-value'); 89 | if (col.type === 'number') { 90 | val = Number(val); 91 | } 92 | data[col.key] = val; 93 | } 94 | return data; 95 | } 96 | // loads all row data 97 | function loadData() { 98 | var rows = getTableBody().querySelectorAll('tr'), 99 | i; 100 | 101 | for (i = 0; i < rows.length; i += 1) { 102 | rows[i].data = loadRowData(rows[i]); 103 | } 104 | } 105 | // sorts the table using the data for the ith column 106 | function sortByIndex(index, desc) { 107 | var key = cols[index].key, 108 | sorter = function(a, b) { 109 | a = a.data[key]; 110 | b = b.data[key]; 111 | return a < b ? -1 : a > b ? 1 : 0; 112 | }, 113 | finalSorter = sorter, 114 | tableBody = document.querySelector('.coverage-summary tbody'), 115 | rowNodes = tableBody.querySelectorAll('tr'), 116 | rows = [], 117 | i; 118 | 119 | if (desc) { 120 | finalSorter = function(a, b) { 121 | return -1 * sorter(a, b); 122 | }; 123 | } 124 | 125 | for (i = 0; i < rowNodes.length; i += 1) { 126 | rows.push(rowNodes[i]); 127 | tableBody.removeChild(rowNodes[i]); 128 | } 129 | 130 | rows.sort(finalSorter); 131 | 132 | for (i = 0; i < rows.length; i += 1) { 133 | tableBody.appendChild(rows[i]); 134 | } 135 | } 136 | // removes sort indicators for current column being sorted 137 | function removeSortIndicators() { 138 | var col = getNthColumn(currentSort.index), 139 | cls = col.className; 140 | 141 | cls = cls.replace(/ sorted$/, '').replace(/ sorted-desc$/, ''); 142 | col.className = cls; 143 | } 144 | // adds sort indicators for current column being sorted 145 | function addSortIndicators() { 146 | getNthColumn(currentSort.index).className += currentSort.desc 147 | ? ' sorted-desc' 148 | : ' sorted'; 149 | } 150 | // adds event listeners for all sorter widgets 151 | function enableUI() { 152 | var i, 153 | el, 154 | ithSorter = function ithSorter(i) { 155 | var col = cols[i]; 156 | 157 | return function() { 158 | var desc = col.defaultDescSort; 159 | 160 | if (currentSort.index === i) { 161 | desc = !currentSort.desc; 162 | } 163 | sortByIndex(i, desc); 164 | removeSortIndicators(); 165 | currentSort.index = i; 166 | currentSort.desc = desc; 167 | addSortIndicators(); 168 | }; 169 | }; 170 | for (i = 0; i < cols.length; i += 1) { 171 | if (cols[i].sortable) { 172 | // add the click event handler on the th so users 173 | // dont have to click on those tiny arrows 174 | el = getNthColumn(i).querySelector('.sorter').parentElement; 175 | if (el.addEventListener) { 176 | el.addEventListener('click', ithSorter(i)); 177 | } else { 178 | el.attachEvent('onclick', ithSorter(i)); 179 | } 180 | } 181 | } 182 | } 183 | // adds sorting functionality to the UI 184 | return function() { 185 | if (!getTable()) { 186 | return; 187 | } 188 | cols = loadColumns(); 189 | loadData(); 190 | addSearchBox(); 191 | addSortIndicators(); 192 | enableUI(); 193 | }; 194 | })(); 195 | 196 | window.addEventListener('load', addSorting); 197 | -------------------------------------------------------------------------------- /server/coverage/lcov.info: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/oslabs-beta/Serene/74ba1a8a62e2b8cf060586ffbdfe8b4f08588442/server/coverage/lcov.info -------------------------------------------------------------------------------- /server/models/sessionModel.ts: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { Schema } = mongoose; 3 | 4 | 5 | const sessionSchema = new Schema({ 6 | cookieID: { type: String, required: true, unique: true }, 7 | createdAt: { type: Date, expires: 30, default: Date.now} 8 | }); 9 | 10 | 11 | export default mongoose.model('Session', sessionSchema); -------------------------------------------------------------------------------- /server/models/userModel.ts: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const { Schema } = mongoose; 3 | 4 | const User = new Schema({ 5 | username: { type: String, required: true, unique: true }, 6 | password: { type: String, required: true }, 7 | ARN: { type: String, required: true }, 8 | region: { type: String, required: true, default: 'us-west-1' } 9 | }); 10 | 11 | 12 | export default mongoose.model('User', User); -------------------------------------------------------------------------------- /server/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "serene", 3 | "private": true, 4 | "version": "1.0.0", 5 | "main": "server.ts", 6 | "scripts": { 7 | "start": "nodemon ./server.ts", 8 | "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", 9 | "preview": "vite preview", 10 | "test": "jest --detectOpenHandles" 11 | }, 12 | "dependencies": { 13 | "@aws-sdk/client-cloudwatch": "^3.385.0", 14 | "@aws-sdk/client-cloudwatch-logs": "^3.382.0", 15 | "@aws-sdk/client-lambda": "^3.382.0", 16 | "@aws-sdk/client-sts": "^3.391.0", 17 | "@babel/plugin-transform-async-to-generator": "^7.22.5", 18 | "@babel/runtime": "^7.1.2", 19 | "@jest/globals": "^29.6.3", 20 | "bcrypt": "^5.1.0", 21 | "cookie-parser": "^1.4.6", 22 | "dotenv": "^16.3.1", 23 | "express": "^4.18.2", 24 | "express-session": "^1.17.3", 25 | "install": "^0.13.0", 26 | "js-cookie": "^3.0.5", 27 | "mongodb": "^5.7.0", 28 | "mongoose": "^7.4.1", 29 | "nodemon": "^3.0.1", 30 | "npm": "^9.8.1", 31 | "path": "^0.12.7", 32 | "ts-node": "^10.9.1" 33 | }, 34 | "devDependencies": { 35 | "@babel/core": "^7.1.2", 36 | "@babel/plugin-transform-runtime": "^7.1.0", 37 | "@babel/preset-env": "^7.1.0", 38 | "@babel/preset-react": "^7.0.0", 39 | "@types/bcrypt": "^5.0.0", 40 | "@types/express": "^4.17.17", 41 | "@types/node": "^20.4.5", 42 | "@typescript-eslint/eslint-plugin": "^6.0.0", 43 | "@typescript-eslint/parser": "^6.0.0", 44 | "babel-core": "^7.0.0-bridge.0", 45 | "babel-jest": "^23.6.0", 46 | "babel-loader": "^8.0.4", 47 | "eslint": "^8.45.0", 48 | "jest": "^29.6.2", 49 | "supertest": "^6.3.3", 50 | "tslint": "^6.1.3", 51 | "typescript": "^5.0.2" 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /server/routes/cloudWatchRouter.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import express from 'express'; 3 | import { Request, Response, Router } from 'express'; 4 | 5 | // import middlware 6 | import cloudWatchLogController from '../controllers/cloudwatch/cloudWatchLogController'; 7 | import stsController from '../controllers/stsController'; 8 | import cloudWatchMetricsController from '../controllers/cloudwatch/cloudWatchMetricsController'; 9 | 10 | // initialize router 11 | const cloudWatchRouter: Router = express.Router(); 12 | 13 | // view function streams - user needs to specify function name 14 | cloudWatchRouter.post('/getLogs', stsController.getCredentials, cloudWatchLogController.viewFunctionStreams, (req: Request, res: Response) => { 15 | // send a 200 status and a list of the log stream names (array of strings) 16 | return res.status(200).json(res.locals.logStreamNames); 17 | }); 18 | 19 | // view timestamp and message - user needs to specify function name and stream name 20 | cloudWatchRouter.post('/getStreamDetails', stsController.getCredentials, cloudWatchLogController.viewStreamInfo, (req: Request, res: Response) => { 21 | // send a 200 status and an list of log events (array of objects) 22 | return res.status(200).json(res.locals.events); 23 | }); 24 | 25 | // 1st req body { region, roleArn } // 2nd req body { funcName, sortBy, period, startDate, region } 26 | cloudWatchRouter.post('/getMetrics', stsController.getCredentials, cloudWatchMetricsController.getMetrics, (req: Request, res: Response) => { 27 | // send a 200 status and a list of metrics (nested object) 28 | return res.status(200).json(res.locals.metrics); 29 | }); 30 | 31 | export default cloudWatchRouter; 32 | -------------------------------------------------------------------------------- /server/routes/lambdaRouter.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import express from 'express'; 3 | import { Request, Response, Router } from 'express'; 4 | 5 | // import middlware 6 | import stsController from '../controllers/stsController'; 7 | import lambdaController from '../controllers/lambda/lambdaController'; 8 | 9 | // initialize router 10 | const lambdaRouter: Router = express.Router(); 11 | 12 | // view all lambda functions -- goes based on user cookie 13 | lambdaRouter.get('/functions', stsController.getCredentials, lambdaController.getFunctions, (req: Request, res: Response) => { 14 | // send a 200 status and a list of functions (array of objects) 15 | return res.status(200).json(res.locals.functions); 16 | }); 17 | 18 | export default lambdaRouter; -------------------------------------------------------------------------------- /server/routes/userRouter.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import express from 'express'; 3 | import { Request, Response, Router } from 'express'; 4 | 5 | // import middleware 6 | import userController from '../controllers/userController'; 7 | import cookieController from '../controllers/authentication/cookieController'; 8 | 9 | // initialize router 10 | const userRouter: Router = express.Router(); 11 | 12 | // signup path -- takes a username, password, ARN, and region in req.body (all strings) 13 | userRouter.post('/signup', userController.createUser, cookieController.setSSIDCookie, cookieController.newSession, (req: Request, res: Response) => { 14 | // if username or password are empty, return 400 status 15 | if(req.body.username === '' || req.body.password === '') { 16 | return res.status(400); 17 | } else { 18 | const hours: number = 1; 19 | const maxAgeInMs: number = hours * 60 * 60 * 1000; 20 | // set a cookie named SSID with the value of the user's ID and send 200 status 21 | res.cookie('SSID', res.locals.ssid, {maxAge: maxAgeInMs, httpOnly: true}).sendStatus(200); 22 | } 23 | }); 24 | 25 | // gets all users 26 | userRouter.get('/', userController.getAllUsers, (req: Request, res: Response) => { 27 | // send 200 status and list of all users (array of objects) 28 | return res.status(200).json(res.locals.allUsers); 29 | }); 30 | 31 | 32 | // authenticates user login -- takes username and password in req.body 33 | userRouter.post('/login', userController.login, cookieController.setSSIDCookie, cookieController.newSession, (req: Request, res: Response) => { 34 | if(req.body.username === '' || req.body.password === '') { 35 | // if username or password are empty, send 400 status 36 | return res.status(400); 37 | } else { 38 | const hours: number = 1; 39 | const maxAgeInMs:number = hours * 60 * 60 * 1000; 40 | // set a cookie named SSID with the value being the current user's ID and send 200 status 41 | res.cookie('SSID', res.locals.ssid, {maxAge: maxAgeInMs, httpOnly: true }).sendStatus(200); 42 | } 43 | }); 44 | 45 | // logs user out and deletes the current cookie 46 | userRouter.post('/logout', cookieController.endSession, (req: Request, res: Response) => { 47 | // end current session 48 | return res.end('done'); 49 | }); 50 | 51 | // updates user -- takes newArn and newRegion in req.body 52 | userRouter.patch('/edit', userController.updateUser, (req: Request, res: Response) => { 53 | // send a 200 status and the updated user 54 | return res.status(200).json(res.locals.updatedUser); 55 | }); 56 | 57 | // updates user -- takes newArn and newRegion in req.body 58 | userRouter.delete('/delete', userController.deleteUser, (req: Request, res: Response) => { 59 | // send a 200 status and the updated user 60 | return res.status(200).json(res.locals.deletedUser); 61 | }); 62 | 63 | export default userRouter; -------------------------------------------------------------------------------- /server/routes/versionRouter.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import express from 'express'; 3 | import { Request, Response, Router } from 'express'; 4 | 5 | // import middleware 6 | import versionHistoryController from '../controllers/versions/versionHistoryController'; 7 | import stsController from '../controllers/stsController'; 8 | 9 | // initialize router 10 | const versionRouter: Router = express.Router(); 11 | 12 | // gets all versions of a lambda function -- based on user cookie 13 | versionRouter.post('/versionList', stsController.getCredentials, versionHistoryController.viewVersionList, (req: Request, res: Response) => { 14 | // send a 200 status and a list of versions (array of objects) 15 | return res.status(200).json(res.locals.versionList) 16 | }); 17 | 18 | // gets information on a specific function version -- based on user cookie 19 | versionRouter.post('/functionVersion', stsController.getCredentials, versionHistoryController.viewFunctionVersion, (req: Request, res: Response) => { 20 | // send a 200 status and the version info (nested object) 21 | return res.status(200).json(res.locals.versionInfo); 22 | }); 23 | 24 | // gets alias information for a specific lambda function -- takes the funcName in req.body 25 | versionRouter.post('/getAlias', stsController.getCredentials, versionHistoryController.getAlias, (req: Request, res: Response) => { 26 | // send a 200 status and a list of aliases (array of objects) 27 | return res.status(200).json(res.locals.aliasList); 28 | }); 29 | 30 | export default versionRouter; -------------------------------------------------------------------------------- /server/routes/warmingRouter.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import express from 'express'; 3 | import { Request, Response, Router } from 'express'; 4 | 5 | // import middleware 6 | import warmingController from '../controllers/warming/warmingController'; 7 | import stsController from '../controllers/stsController'; 8 | 9 | // initialize router 10 | const warmingRouter: Router = express.Router(); 11 | 12 | // invokes the function at the user specified frequency and duration (req.body) 13 | warmingRouter.post('/functions', stsController.getCredentials, warmingController.warmFunction, (req: Request, res: Response) => { 14 | // send a 200 status 15 | res.status(200) 16 | }); 17 | 18 | 19 | 20 | export default warmingRouter; -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | // boilerplate 2 | import express, { ErrorRequestHandler, Express, Request, Response, NextFunction, RequestHandler, Router } from 'express'; 3 | import mongoose, { ConnectOptions } from 'mongoose'; 4 | const dotenv = require('dotenv').config(); 5 | import path from 'path'; 6 | 7 | // require in routers 8 | import lambdaRouter from './routes/lambdaRouter'; 9 | import userRouter from './routes/userRouter'; 10 | import cloudWatchRouter from './routes/cloudWatchRouter'; 11 | import versionRouter from './routes/versionRouter'; 12 | import warmingRouter from './routes/warmingRouter'; 13 | 14 | // import necessary types 15 | import { ServerError } from './types.js'; 16 | 17 | // require cookies 18 | const cookieParser = require('cookie-parser'); 19 | // intialize our app 20 | const app: Express = express(); 21 | // declare our port 22 | const PORT: number = 3000; 23 | 24 | // grab our access key from .env and connect to MongoDB 25 | const ACCESS_KEY: string = process.env.ACCESS_KEY! 26 | 27 | console.log("testing"); 28 | 29 | mongoose.connect(ACCESS_KEY, { useNewUrlParser: true, useUnifiedTopology: true } as ConnectOptions); 30 | 31 | mongoose.connection.once('open', () => { 32 | console.log('Connected to Database'); 33 | }); 34 | 35 | // parse cookies and incoming requests 36 | app.use(cookieParser()); 37 | app.use(express.json()); 38 | app.use(express.urlencoded({ extended: true })); 39 | // test 40 | 41 | // route handlers go here 42 | app.use('/api/lambda', lambdaRouter); 43 | app.use('/api/user', userRouter); 44 | app.use('/api/cloudwatch', cloudWatchRouter); 45 | app.use('/api/versions', versionRouter); 46 | app.use('/api/warming', warmingRouter); 47 | 48 | // serve static files 49 | app.use(express.static('./dist')); // ../client/dist 50 | 51 | app.use('*', (req: Request, res: Response) => { 52 | res.sendFile(path.resolve(__dirname, './dist/index.html')); 53 | }); 54 | 55 | // global error handler 56 | app.use((err: ErrorRequestHandler, req: Request, res: Response, next: NextFunction) => { 57 | const defaultErr: ServerError = { 58 | log: 'Express error handler caught unknown middleware error', 59 | status: 400, 60 | message: { err: 'An error occurred' }, 61 | }; 62 | 63 | const errorObj: ServerError = Object.assign({}, defaultErr, err); 64 | res.status(errorObj.status).json(errorObj.message); 65 | }); 66 | 67 | // listen and export app/server 68 | const server = app.listen(PORT, () => console.log(`Listening on port ${PORT}`)); 69 | 70 | export default server; -------------------------------------------------------------------------------- /server/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "noImplicitAny": false, 7 | "moduleResolution": "node", 8 | "sourceMap": true, 9 | "outDir": "dist", 10 | "baseUrl": ".", 11 | "noEmit": true, 12 | // "paths": { 13 | // "*": [ 14 | // "node_modules/*" 15 | // ] 16 | // }, 17 | "allowImportingTsExtensions": true 18 | }, 19 | "include": [ 20 | "*/**/*.ts" 21 | ], 22 | "exclude": ["node_modules"] 23 | } 24 | -------------------------------------------------------------------------------- /server/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "trailing-comma": [ false ], 9 | "no-console": [true, "warning"] 10 | }, 11 | "rulesDirectory": [] 12 | } -------------------------------------------------------------------------------- /server/types.ts: -------------------------------------------------------------------------------- 1 | import { Request, Response, NextFunction} from 'express'; 2 | 3 | // server.ts 4 | export type ServerError = { 5 | log: string; 6 | status: number; 7 | message: { err: string } 8 | } 9 | 10 | // userController.ts 11 | export type UserController = { 12 | createUser: (req: Request, res: Response, next: NextFunction) => Promise 13 | getAllUsers: (req: Request, res: Response, next: NextFunction) => Promise 14 | login: (req: Request, res: Response, next: NextFunction) => Promise 15 | updateUser: (req: Request, res: Response, next: NextFunction) => Promise , 16 | deleteUser: (req: Request, res: Response, next: NextFunction) => Promise 17 | } 18 | 19 | export type CreateUserInfo = { 20 | username: string, 21 | password: string, 22 | ARN: string, 23 | region: string 24 | } 25 | 26 | export type UserInfo = { 27 | _id: string, 28 | username: string, 29 | password: string, 30 | ARN: string, 31 | region: string 32 | } 33 | 34 | export type Login = { 35 | username: string, 36 | password: string 37 | } 38 | 39 | export type UpdatedUserInfo = { 40 | newRegion?: string, 41 | newARN?: string 42 | } 43 | 44 | 45 | // cookieController.ts 46 | export type CookieController = { 47 | setSSIDCookie: (req: Request, res: Response, next: NextFunction) => Promise , 48 | newSession: (req: Request, res: Response, next: NextFunction) => Promise , 49 | endSession: (req: Request, res: Response, next: NextFunction) => Promise 50 | } 51 | 52 | 53 | // stsController.ts 54 | export type STSController = { 55 | getCredentials: (req: Request, res: Response, next: NextFunction) => Promise 56 | } 57 | 58 | export type RoleCreds = { 59 | accessKeyId: string, 60 | secretAccessKey: string, 61 | sessionToken: string 62 | } 63 | 64 | 65 | 66 | // lambdaController.ts 67 | export type LambdaController = { 68 | getFunctions: (req: Request, res: Response, next: NextFunction) => Promise 69 | } 70 | 71 | export type ArrayFiller = { 72 | name: string, 73 | description: string, 74 | arn: string 75 | } 76 | 77 | 78 | // warmingController.ts 79 | export type WarmingController = { 80 | warmFunction: (req: Request, res: Response, next: NextFunction) => Promise , 81 | } 82 | 83 | export type WarmingReqBody = { 84 | functionArn: string, 85 | intervalVar: number, 86 | maxDuration: number 87 | } 88 | 89 | 90 | // versionHistoryController.ts 91 | export type VersionHistoryController = { 92 | viewVersionList: (req: Request, res: Response, next: NextFunction) => Promise , 93 | viewFunctionVersion: (req: Request, res: Response, next: NextFunction) => Promise , 94 | getAlias: (req: Request, res: Response, next: NextFunction) => Promise 95 | } 96 | 97 | 98 | export type VersionObject = { 99 | key: string 100 | } 101 | 102 | 103 | export type VersionInfo = { 104 | description: string, 105 | timeout: string, 106 | ephemeralStorage: string, 107 | linkToFunc: string 108 | } 109 | 110 | export type AliasList = { 111 | AliasArn: string, 112 | Description: string, 113 | FunctionVersion: string, 114 | Name: string, 115 | RevisionId: string, 116 | weight: number, 117 | RoutingConfig?: { 118 | AdditionalVersionWeights?: { 119 | key: number 120 | } 121 | } 122 | } 123 | 124 | 125 | // cloudWatchLogController.ts 126 | export type CloudWatchLogController = { 127 | viewFunctionStreams: (req: Request, res: Response, next: NextFunction) => Promise , 128 | viewStreamInfo: (req: Request, res: Response, next: NextFunction) => Promise 129 | } 130 | 131 | export type StreamInfoBody = { 132 | streamName: string, 133 | logName: string 134 | } 135 | 136 | 137 | // cloudWatchMetricsController.ts 138 | export type CloudWatchMetricsController = { 139 | getMetrics: (req: Request, res: Response, next: NextFunction) => Promise 140 | } 141 | 142 | export type MetricBody = { 143 | funcName: string, 144 | sortBy: string, 145 | period: string, 146 | startDate: string 147 | } 148 | 149 | // export type MetricObject = { 150 | // duration: string, 151 | // invocations: , 152 | // throttles: , 153 | // errors: , 154 | // concurrentExecutions: 155 | // } 156 | 157 | 158 | // misc - repeated type 159 | export type FuncNameBody = { 160 | funcName: string 161 | } 162 | 163 | export type FunctionArnBody = { 164 | functionArn: string 165 | } 166 | --------------------------------------------------------------------------------