├── .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 |
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 |
31 |
32 |
33 | {' '}
34 |
35 |
36 |
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 |
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 |
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 |
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | cookieController.js |
83 |
84 |
85 | |
86 | 78.57% |
87 | 22/28 |
88 | 100% |
89 | 4/4 |
90 | 66.66% |
91 | 2/3 |
92 | 78.57% |
93 | 22/28 |
94 |
95 |
96 |
97 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | cloudWatchLogController.js |
83 |
84 |
85 | |
86 | 100% |
87 | 39/39 |
88 | 100% |
89 | 0/0 |
90 | 100% |
91 | 4/4 |
92 | 100% |
93 | 39/39 |
94 |
95 |
96 |
97 | cloudWatchMetricsController.js |
98 |
99 |
100 | |
101 | 77.27% |
102 | 51/66 |
103 | 69.04% |
104 | 29/42 |
105 | 100% |
106 | 4/4 |
107 | 77.27% |
108 | 51/66 |
109 |
110 |
111 |
112 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | stsController.js |
83 |
84 |
85 | |
86 | 100% |
87 | 21/21 |
88 | 100% |
89 | 0/0 |
90 | 100% |
91 | 1/1 |
92 | 100% |
93 | 21/21 |
94 |
95 |
96 |
97 | userController.js |
98 |
99 |
100 | |
101 | 83.33% |
102 | 35/42 |
103 | 83.33% |
104 | 5/6 |
105 | 75% |
106 | 3/4 |
107 | 83.33% |
108 | 35/42 |
109 |
110 |
111 |
112 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | lambdaController.js |
83 |
84 |
85 | |
86 | 93.33% |
87 | 14/15 |
88 | 100% |
89 | 0/0 |
90 | 100% |
91 | 2/2 |
92 | 93.33% |
93 | 14/15 |
94 |
95 |
96 |
97 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | versionHistoryController.js |
83 |
84 |
85 | |
86 | 100% |
87 | 48/48 |
88 | 75% |
89 | 3/4 |
90 | 100% |
91 | 5/5 |
92 | 100% |
93 | 47/47 |
94 |
95 |
96 |
97 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | warmingController.js |
83 |
84 |
85 | |
86 | 100% |
87 | 15/15 |
88 | 100% |
89 | 0/0 |
90 | 100% |
91 | 1/1 |
92 | 100% |
93 | 15/15 |
94 |
95 |
96 |
97 |
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | server.js |
83 |
84 |
85 | |
86 | 97.22% |
87 | 35/36 |
88 | 100% |
89 | 0/0 |
90 | 75% |
91 | 3/4 |
92 | 97.14% |
93 | 34/35 |
94 |
95 |
96 |
97 |
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 | File |
70 | |
71 | Statements |
72 | |
73 | Branches |
74 | |
75 | Functions |
76 | |
77 | Lines |
78 | |
79 |
80 |
81 |
82 | sessionModel.js |
83 |
84 |
85 | |
86 | 100% |
87 | 4/4 |
88 | 100% |
89 | 0/0 |
90 | 100% |
91 | 0/0 |
92 | 100% |
93 | 4/4 |
94 |
95 |
96 |
97 | userModel.js |
98 |
99 |
100 | |
101 | 100% |
102 | 4/4 |
103 | 100% |
104 | 0/0 |
105 | 100% |
106 | 0/0 |
107 | 100% |
108 | 4/4 |
109 |
110 |
111 |
112 |
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
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 | 5x
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
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 | 5x
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
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 | 24 | 5x
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
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 | 5x
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
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 | 5x
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 |
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 |
58 |
59 | Filter:
60 |
61 |
62 |
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 | 5x
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 |
--------------------------------------------------------------------------------