├── .DS_Store
├── .babelrc
├── .eslintrc.json
├── .github
└── workflows
│ └── main.yml
├── .gitignore
├── .history
├── client
│ └── components
│ │ └── Nav_20220815123138.jsx
└── server
│ ├── aws
│ └── getMetrics_20220815123138.js
│ └── controllers
│ └── authController_20220815123138.js
├── LICENSE
├── README.md
├── client
├── .DS_Store
├── App.jsx
├── assets
│ ├── .DS_Store
│ ├── Lumos_Logo.png
│ ├── README
│ │ ├── IAMSetup.png
│ │ ├── IAMSetupPermissions.png
│ │ ├── iamadduser.png
│ │ └── iampermissions.png
│ ├── lotties
│ │ ├── alertIcon.json
│ │ ├── cost.json
│ │ ├── dashboardIcon.json
│ │ ├── duration.json
│ │ ├── errors.json
│ │ ├── lambda-funcs.json
│ │ ├── landing.json
│ │ └── logsIcon.json
│ └── styles.css
├── components
│ ├── .DS_Store
│ ├── Cards
│ │ └── MetricCard.jsx
│ ├── Charts.jsx
│ ├── Errors.jsx
│ ├── Footer.jsx
│ ├── Header.jsx
│ ├── LandingPage.jsx
│ ├── Login.jsx
│ ├── Metrics.jsx
│ ├── Nav.jsx
│ ├── Sidebar.jsx
│ ├── SignIn.jsx
│ ├── Signout.jsx
│ ├── Usage.jsx
│ ├── charts
│ │ ├── Latency.jsx
│ │ ├── TestChart.jsx
│ │ └── UsageDoughnut.jsx
│ ├── dateButton.jsx
│ ├── dayButton.jsx
│ ├── fetchMetrics.jsx
│ ├── monthButton.jsx
│ └── weekButton.jsx
├── containers
│ └── MainContainer.jsx
├── index.html
└── index.jsx
├── gitFlow.md
├── package-lock.json
├── package.json
├── server
├── aws
│ ├── cloudFormation.js
│ ├── cloudWatch.js
│ ├── getEC2.js
│ ├── getEC2Metrics.js
│ ├── getLambdaFuncs.js
│ ├── getLogs.js
│ └── getMetrics.js
├── bcrypt.js
├── controllers
│ ├── authController.js
│ ├── metricController.js
│ └── userController.js
├── lumos_postgres_create.sql
├── models
│ ├── .gitignore
│ ├── .vscode
│ │ └── settings.json
│ ├── UserModels.js
│ └── amplify
│ │ ├── .config
│ │ └── project-config.json
│ │ ├── backend
│ │ ├── backend-config.json
│ │ ├── tags.json
│ │ └── types
│ │ │ └── amplify-dependent-resources-ref.d.ts
│ │ ├── cli.json
│ │ └── team-provider-info.json
├── routes
│ ├── metricRoute.js
│ └── userRoute.js
└── server.js
└── webpack.config.js
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/.DS_Store
--------------------------------------------------------------------------------
/.babelrc:
--------------------------------------------------------------------------------
1 | {
2 | "presets": [
3 | [
4 | "@babel/preset-env",
5 | {
6 | "modules": false
7 | }
8 | ],
9 | "@babel/preset-react"
10 | ]
11 | }
12 |
--------------------------------------------------------------------------------
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es2021": true,
5 | "node": true
6 | },
7 | "extends": ["plugin:react/recommended", "airbnb"],
8 | "parserOptions": {
9 | "ecmaFeatures": {
10 | "jsx": true
11 | },
12 | "ecmaVersion": "latest",
13 | "sourceType": "module"
14 | },
15 | "plugins": ["react"],
16 | "rules": {
17 | "import/extensions": [, "never" | "always" | "ignorePackages"]
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/workflows/main.yml:
--------------------------------------------------------------------------------
1 | name: ci
2 | on:
3 | push:
4 | branches:
5 | - 'backend'
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | steps:
10 | -
11 | name: Checkout
12 | uses: actions/checkout@v3
13 | -
14 | name: Login to Docker Hub
15 | uses: docker/login-action@v2
16 | with:
17 | username: ${{ secrets.DOCKER_HUB_USERNAME }}
18 | password: ${{ secrets.DOCKER_HUB_ACCESS_TOKEN }}
19 | -
20 | name: Set up Docker Buildx
21 | uses: docker/setup-buildx-action@v2
22 | -
23 | name: Build and push
24 | uses: docker/build-push-action@v3
25 | with:
26 | context: .
27 | file: ./Dockerfile
28 | push: true
29 | tags: ${{ secrets.DOCKER_HUB_USERNAME }}/lumos:latest
30 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | .history
--------------------------------------------------------------------------------
/.history/client/components/Nav_20220815123138.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React ,{useContext} from 'react';
3 | import { Box } from '@mui/material';
4 | import Login from '../components/Login.jsx';
5 | import Sign from '../components/SignIn.jsx';
6 | import { InfoContext } from "../containers/MainContainer.jsx";
7 | // import Logo from '../assets/Lumos_Logo.png'
8 |
9 |
10 | function Nav() {
11 | // const logo = require('../assets/Lumos_Logo.png')
12 | const [userInfo, setUserInfo] = useContext(InfoContext);
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | {/* { userInfo.loggedIn === false &&
21 | <> */}
22 |
23 |
24 |
25 |
26 | {/* >
27 | } */}
28 |
29 | );
30 | }
31 |
32 | export default Nav;
33 |
--------------------------------------------------------------------------------
/.history/server/aws/getMetrics_20220815123138.js:
--------------------------------------------------------------------------------
1 | const {
2 | ListMetricsCommand,
3 | GetMetricDataCommand,
4 | GetMetricStatisticsCommand,
5 | CloudWatchClient,
6 | } = require("@aws-sdk/client-cloudwatch");
7 | const getLambdaFuncs = require("./getLambdaFuncs.js");
8 |
9 | const getMetrics = async (
10 | startTime,
11 | endTime,
12 | metricName,
13 | period = 60,
14 | funcName = undefined,
15 | metricStat = "Sum"
16 | ) => {
17 | try {
18 | const client = await new CloudWatchClient({ region: "us-east-1" });
19 | if (funcName === undefined) {
20 | const funcNames = await getLambdaFuncs();
21 | const queries = [];
22 |
23 | funcNames.forEach((func, index) => {
24 | const query = [{
25 | Id: `m${Math.floor(Math.random() * 812903819023)}`,
26 | Label: `Lambda ${metricName} ${func}`,
27 | MetricStat: {
28 | Metric: {
29 | Namespace: `AWS/Lambda`,
30 | MetricName: `${metricName}`,
31 | Dimensions: [
32 | {
33 | Name: `FunctionName`,
34 | Value: `${func}`,
35 | },
36 | ],
37 | },
38 | Period: `${period}`,
39 | Stat: `${metricStat}`,
40 | },
41 | },
42 | {
43 | Id: `m${Math.floor(Math.random() * 897123891273)}`,
44 | Label: `Lambda Errors ${func}`,
45 | MetricStat: {
46 | Metric: {
47 | Namespace: `AWS/Lambda`,
48 | MetricName: `Errors`,
49 | Dimensions: [
50 | {
51 | Name: `FunctionName`,
52 | Value: `${func}`,
53 | },
54 | ],
55 | },
56 | Period: `${period}`,
57 | Stat: `${metricStat}`,
58 | },
59 | }
60 | ];
61 | // console.log(query)
62 | queries.push(query);
63 | });
64 |
65 | // console.log(queries);
66 |
67 | const res = [];
68 |
69 | for (let i = 0; i < queries.length; i += 1) {
70 | const data = await client.send(
71 | new GetMetricDataCommand({
72 | StartTime: new Date(startTime),
73 | EndTime: new Date(endTime),
74 | MetricDataQueries: queries[i],
75 | })
76 | );
77 |
78 | res.push({
79 | funcName: queries[i][0].MetricStat.Metric.Dimensions[0].Value, // label
80 | totalInvocations: data.MetricDataResults[0].Values.reduce((a, b) => a + b, 0), // total sum
81 | totalErrors: data.MetricDataResults[1].Values.reduce((a, b) => a + b, 0),
82 | timestamps: data.MetricDataResults[0].Timestamps,
83 | funcValues: data.MetricDataResults[0].Values,
84 | });
85 | }
86 | // console.log('res: ', res.MetricDataResults);
87 | return res;
88 | } else {
89 |
90 | const data = await client.send(
91 | new GetMetricDataCommand({
92 | StartTime: new Date(startTime), //new Date("August 6, 2022 13:30:30"), <- needs variable where we can send in a start date from frontend
93 | EndTime: new Date(endTime), //new Date("August 6, 2022 16:00:00"),<- needs variable where we can send in a start date from frontend
94 | MetricDataQueries: [
95 | {
96 | Id: "test",
97 | MetricStat: {
98 | Metric: {
99 | MetricName: metricName,
100 | Namespace: "AWS/Lambda",
101 | Dimensions: [
102 | {
103 | Name: "FunctionName",
104 | Value: funcName,
105 | },
106 | ],
107 | },
108 | Period: period,
109 | Stat: metricStat,
110 | },
111 | },
112 | ],
113 | })
114 | );
115 |
116 | return {
117 | funcName: funcName,
118 | totalInvocations: data.MetricDataResults[0].Values.reduce((a, b) => a + b, 0),
119 | timeStamps: data.MetricDataResults[0].Timestamps,
120 | funcValues: data.MetricDataResults[0].Values
121 | };
122 | }
123 | } catch (err) {
124 | return err;
125 | }
126 | };
127 |
128 | getMetrics(
129 | "August 1, 2022 15:30:30",
130 | "August 10, 2022 18:00:00",
131 | "Invocations",
132 | period = 60,
133 | );
134 |
135 | // // we need this getMetrics in a controller function so we can invoke this from a frontend route
136 |
137 | // const getMetrics = async(startTime, endTime, metricName, period = 60, funcName = undefined) => {
138 | // const client = await new CloudWatchClient({region: "us-east-1"})
139 | // try {
140 | // if (!funcName) {
141 | // const funcNames = getLambdaFuncs();
142 | // // funcNames.forEach((func) => {
143 | // // const data = await client.send(
144 | // // new GetMetricDataCommand({
145 | // // StartTime: new Date(startTime), //new Date("August 6, 2022 13:30:30"), <- needs variable where we can send in a start date from frontend
146 | // // EndTime: new Date(endTime), //new Date("August 6, 2022 16:00:00"),<- needs variable where we can send in a start date from frontend
147 | // // MetricDataQueries: [
148 | // // {
149 | // // Id: "test",
150 | // // // Expression: 'SELECT SUM(Invocations) FROM SCHEMA("AWS/Lambda", FunctionName) GROUP BY FunctionName ORDER BY SUM() DESC',
151 | // // MetricStat: {
152 | // // Metric: {
153 | // // MetricName: metricName, //"Errors",
154 | // // Namespace: "AWS/Lambda",
155 | // // Dimensions: [{
156 | // // Name: 'FunctionName',
157 | // // Value: func
158 | // // }]
159 | // // },
160 | // // Period: period,
161 | // // Stat: 'Sum',
162 | // // },
163 | // // },
164 | // // ],
165 |
166 | // // })
167 | // // );
168 | // // })
169 |
170 | // return data.MetricDataResults;
171 | // } else {
172 | // const data = await client.send(
173 | // new GetMetricDataCommand({
174 | // StartTime: new Date(startTime), //new Date("August 6, 2022 13:30:30"), <- needs variable where we can send in a start date from frontend
175 | // EndTime: new Date(endTime), //new Date("August 6, 2022 16:00:00"),<- needs variable where we can send in a start date from frontend
176 | // MetricDataQueries: [
177 | // {
178 | // Id: "test",
179 | // // Expression: 'SELECT SUM(Invocations) FROM SCHEMA("AWS/Lambda", FunctionName) GROUP BY FunctionName ORDER BY SUM() DESC',
180 | // MetricStat: {
181 | // Metric: {
182 | // MetricName: metricName, //"Errors",
183 | // Namespace: "AWS/Lambda",
184 | // Dimensions: [{
185 | // Name: 'FunctionName',
186 | // Value: funcName
187 | // }]
188 | // },
189 | // Period: period,
190 | // Stat: 'Sum',
191 | // },
192 | // },
193 | // ],
194 |
195 | // }))
196 | // }
197 | // } catch (err) {
198 | // return err
199 | // }
200 | // }
201 |
202 | // getMetrics(new Date("August 1, 2022 15:30:30"), new Date("August 10, 2022 18:00:00"), "Invocations", 60);
203 |
204 | // const date = new Date();
205 |
206 | // date.setHours(5)
207 |
208 | // console.log(date)
209 |
210 | module.exports = getMetrics;
211 |
--------------------------------------------------------------------------------
/.history/server/controllers/authController_20220815123138.js:
--------------------------------------------------------------------------------
1 | const jwt = require('jsonwebtoken');
2 |
3 | // Hide this later in env file
4 | const JWT_SECRET = 'f2960658fe4135e6ab6e4f173b522c558c98d43cafb1a5ef8f8015f375529e31f91f05cde7b7d2ea730a45032a5653fe7d70ddb1a2bee093e87a5fc68afed5ad'
5 |
6 | const authController = {};
7 |
8 | authController.sendToken = async (req, res, next) => {
9 | const token = jwt.sign(res.locals.password, JWT_SECRET);
10 | res.send(token);
11 | };
12 |
13 | authController.verifyToken = async (req, res, next) => {
14 | try {
15 | const header = req.headers['authorization'];
16 | const token = header && header.split(' ')[1];
17 |
18 | if (token === null) {
19 | return next({
20 | log: "Error occurred in authController.verifyToken",
21 | status: 401,
22 | message: err,
23 | })
24 | } else {
25 | jwt.verify(token, JWT_SECRET, (error, result) => {
26 | if (err) return next(error)
27 | req.user = result;
28 | return next();
29 | })
30 | }
31 | } catch (err) {
32 | return next(err)
33 | }
34 | };
35 |
36 | module.exports = authController;
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2022 OSLabs Beta
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
Lumos Lambda Metrics Visualizer
6 |
7 |
8 |
9 |
10 | Table of Contents
11 |
12 | About Lumos
13 | Techologies Used
14 | Getting Started
15 | Key Lambda Metrics
16 | How to Contribute
17 | License
18 | Contributors
19 |
20 |
21 |
22 |
23 |
24 | ## About Lumos
25 |
26 | Serverless architecture is an integral benefit of cloud computing which allows developers to build and run services without having to manage the underlying infrastructure. Amazon Web Services' (AWS) Lambda is the dominant service in the serverless market and is relied upon by the world's largest companies due to its cost-effective, event-driven service that runs code in response to events and automatically manages the computing resources required by that code.
27 |
28 | One drawback to using AWS's services is that navigating the AWS console can be quite challenging. Additionally, lack of descriptive documentation pertaining to AWS's Lambda service makes it difficult to visualize key function metrics at a glance. To solve this, we built Lumos, a free and open-source AWS lambda monitoring tool that allows users to connect their AWS account to track and visualize their key lambda metrics on a visually appealing UI.
29 |
30 | ## Technologies Used
31 |
32 | - [React](https://reactjs.org/)
33 | - [Material-UI](https://material-ui.com)
34 | - [Chart.js](https://www.chartjs.org/)
35 | - [AWS Lambda](https://aws.amazon.com/lambda/)
36 | - [AWS SDK](https://aws.amazon.com/sdk-for-javascript/)
37 | - [AWS STS](https://docs.aws.amazon.com/STS/latest/APIReference/welcome.html)
38 | - [AWS CloudWatch](https://aws.amazon.com/cloudwatch/)
39 | - [Node](https://nodejs.org/en/)
40 | - [Express](https://expressjs.com)
41 | - [PostgreSQL](https://postgresql.org)
42 | - [Webpack](https://webpack.js.org/)
43 | - [Bcrypt](https://www.npmjs.com/package/bcrypt)
44 | - [JSON Web Token](https://jwt.io/)
45 |
46 |
47 |
48 | ## Getting Started
49 |
50 | Lumos requires access keys to sign programmatic requests to the AWS SDK. It is best practice to create an IAM user with permanent long-term credentials to interact with AWS services directly.
51 |
52 | 1. In your AWS console, create an new IAM user. Make sure to check off "Access key - Programmatic access" when selecting an AWS credential type. Save these keys in a secure location. **_They cannot be retrieved after you've completed your IAM setup!_** If you lose your key, you will have to delete the access key and create a new one.
53 |
54 | -
55 |
56 | 2. Attach two existing policies directly to your IAM user's security policy:
57 |
58 | -
59 |
60 | 3. [Download and install the AWS CLI.](https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html)
61 |
62 | 4. From your terminal, access the AWS CLI by typing in
63 | `aws configure`. Follow the steps on your screen and input your access key ID and secret access key.
64 |
65 |
66 |
67 | ## Key Lambda Metrics
68 |
69 | The key lambda metrics we found to be of most interest to users were invocations, durations, and errors. One can visualize their lambda functions metrics across three different time periods: 24 hours, 1 week, or 1 month. Users can visualize their insights through two charts: donut and line. A donut chart will give an overview of all active invocations relating to the functions. The line chart will give a visualization of the number of times each lambda function has been invoked across selected time periods. Lumos also approximates the total cost of a user's lambda functions based on the function's duration and memory usage.
70 |
71 |
72 |
73 | ## How to Contribute
74 |
75 | At Lumos, we open-sourced the project with the intention of having amazing people iterate on our project.
76 |
77 | If you have any suggestions on how to improve Lumos, please follow the steps below:
78 |
79 | 1. Fork Lumos
80 | 2. Create your Feature Branch (`git checkout -b feature/YourFeature`)
81 | 3. Add your changes using (`git add .`)
82 | 4. Commit your Changes (`git commit -m 'Add Your Feature'`)
83 | 5. Push to the Branch (`git push origin feature/YourFeature`)
84 | 6. Open a Pull Request
85 | 7. Hit the star button!!
86 |
87 | Thank you and we truly appreciate all your contributions!
88 |
89 |
90 |
91 | ## License
92 |
93 | Distributed under the MIT License.
94 |
95 |
96 |
97 | ## Contributors
98 |
99 | - Adithya Chandrashekar [Github](https://github.com/addychandrashekar) | [Linkedin](https://www.linkedin.com/in/addyc/)
100 | - Adnan Pervez [Github](https://github.com/apervez) | [Linkedin](https://www.linkedin.com/in/adnan-pervez)
101 | - Carmen Hu [Github](https://github.com/BadWithNames) | [Linkedin](https://www.linkedin.com/in/hu-carmen)
102 | - Mario Arraya [Github](https://github.com/marioarraya) | [Linkedin](https://www.linkedin.com/in/mario-arraya/)
103 | - Michael Negron [Github](https://github.com/InternalShadow) | [Linkedin](https://www.linkedin.com/in/MichaelVNegron)
104 |
--------------------------------------------------------------------------------
/client/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/client/.DS_Store
--------------------------------------------------------------------------------
/client/App.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from 'react';
3 | import MainContainer from './containers/MainContainer.jsx';
4 |
5 | function App() {
6 | return (
7 | <>
8 |
9 | >
10 | );
11 | }
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/client/assets/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/client/assets/.DS_Store
--------------------------------------------------------------------------------
/client/assets/Lumos_Logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/client/assets/Lumos_Logo.png
--------------------------------------------------------------------------------
/client/assets/README/iamadduser.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/client/assets/README/iamadduser.png
--------------------------------------------------------------------------------
/client/assets/README/iampermissions.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/client/assets/README/iampermissions.png
--------------------------------------------------------------------------------
/client/assets/lotties/dashboardIcon.json:
--------------------------------------------------------------------------------
1 | {
2 | "v": "5.8.1",
3 | "fr": 24,
4 | "ip": 0,
5 | "op": 48,
6 | "w": 500,
7 | "h": 500,
8 | "nm": "467-dashboard-gauge-gradient",
9 | "ddd": 0,
10 | "assets": [
11 | {
12 | "id": "comp_0",
13 | "nm": "mask",
14 | "fr": 24,
15 | "layers": [
16 | {
17 | "ddd": 0,
18 | "ind": 1,
19 | "ty": 4,
20 | "nm": "outline 3",
21 | "sr": 1,
22 | "ks": {
23 | "o": { "a": 0, "k": 100, "ix": 11 },
24 | "r": {
25 | "a": 1,
26 | "k": [
27 | {
28 | "i": { "x": [0.833], "y": [0.833] },
29 | "o": { "x": [0.333], "y": [0] },
30 | "t": 0,
31 | "s": [-29]
32 | },
33 | {
34 | "i": { "x": [0.833], "y": [0.833] },
35 | "o": { "x": [0.167], "y": [0.167] },
36 | "t": 11,
37 | "s": [92]
38 | },
39 | {
40 | "i": { "x": [0.833], "y": [0.833] },
41 | "o": { "x": [0.167], "y": [0.167] },
42 | "t": 13,
43 | "s": [84]
44 | },
45 | {
46 | "i": { "x": [0.833], "y": [0.833] },
47 | "o": { "x": [0.167], "y": [0.167] },
48 | "t": 15,
49 | "s": [92]
50 | },
51 | {
52 | "i": { "x": [0.833], "y": [0.833] },
53 | "o": { "x": [0.167], "y": [0.167] },
54 | "t": 17,
55 | "s": [84]
56 | },
57 | {
58 | "i": { "x": [0.833], "y": [0.833] },
59 | "o": { "x": [0.167], "y": [0.167] },
60 | "t": 19,
61 | "s": [92]
62 | },
63 | {
64 | "i": { "x": [0.833], "y": [0.833] },
65 | "o": { "x": [0.167], "y": [0.167] },
66 | "t": 21,
67 | "s": [84]
68 | },
69 | {
70 | "i": { "x": [0.833], "y": [0.833] },
71 | "o": { "x": [0.167], "y": [0.167] },
72 | "t": 23,
73 | "s": [92]
74 | },
75 | {
76 | "i": { "x": [0.833], "y": [0.833] },
77 | "o": { "x": [0.167], "y": [0.167] },
78 | "t": 25,
79 | "s": [84]
80 | },
81 | {
82 | "i": { "x": [0.833], "y": [0.833] },
83 | "o": { "x": [0.167], "y": [0.167] },
84 | "t": 27,
85 | "s": [92]
86 | },
87 | {
88 | "i": { "x": [0.26], "y": [1] },
89 | "o": { "x": [0.167], "y": [0.167] },
90 | "t": 29,
91 | "s": [84]
92 | },
93 | { "t": 47, "s": [-29] }
94 | ],
95 | "ix": 10
96 | },
97 | "p": { "a": 0, "k": [250, 315.045, 0], "ix": 2, "l": 2 },
98 | "a": { "a": 0, "k": [0, 65.045, 0], "ix": 1, "l": 2 },
99 | "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
100 | },
101 | "ao": 0,
102 | "shapes": [
103 | {
104 | "ty": "gr",
105 | "it": [
106 | {
107 | "ind": 0,
108 | "ty": "sh",
109 | "ix": 1,
110 | "ks": {
111 | "a": 0,
112 | "k": {
113 | "i": [
114 | [0, 0],
115 | [0, 0]
116 | ],
117 | "o": [
118 | [0, 0],
119 | [0, 0]
120 | ],
121 | "v": [
122 | [0, 65.045],
123 | [0, 65.045]
124 | ],
125 | "c": false
126 | },
127 | "ix": 2
128 | },
129 | "nm": "Path 1",
130 | "mn": "ADBE Vector Shape - Group",
131 | "hd": false
132 | },
133 | {
134 | "ty": "st",
135 | "c": {
136 | "a": 0,
137 | "k": [0.070588238537, 0.074509806931, 0.192156866193, 1],
138 | "ix": 3
139 | },
140 | "o": { "a": 0, "k": 100, "ix": 4 },
141 | "w": {
142 | "a": 0,
143 | "k": 12,
144 | "ix": 5,
145 | "x": "var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 70), comp('467-dashboard-gauge-gradient').layer('Color & Stroke Change').effect('Stroke')('Slider'));"
146 | },
147 | "lc": 2,
148 | "lj": 2,
149 | "bm": 0,
150 | "nm": "Stroke 1",
151 | "mn": "ADBE Vector Graphic - Stroke",
152 | "hd": false
153 | },
154 | {
155 | "ty": "tr",
156 | "p": { "a": 0, "k": [0, 0], "ix": 2 },
157 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
158 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
159 | "r": { "a": 0, "k": 0, "ix": 6 },
160 | "o": { "a": 0, "k": 100, "ix": 7 },
161 | "sk": { "a": 0, "k": 0, "ix": 4 },
162 | "sa": { "a": 0, "k": 0, "ix": 5 },
163 | "nm": "Transform"
164 | }
165 | ],
166 | "nm": "Group 1",
167 | "np": 2,
168 | "cix": 2,
169 | "bm": 0,
170 | "ix": 1,
171 | "mn": "ADBE Vector Group",
172 | "hd": false
173 | }
174 | ],
175 | "ip": 0,
176 | "op": 120,
177 | "st": 0,
178 | "bm": 0
179 | },
180 | {
181 | "ddd": 0,
182 | "ind": 2,
183 | "ty": 4,
184 | "nm": "outline 2",
185 | "sr": 1,
186 | "ks": {
187 | "o": { "a": 0, "k": 100, "ix": 11 },
188 | "r": { "a": 0, "k": 0, "ix": 10 },
189 | "p": { "a": 0, "k": [250, 251, 0], "ix": 2, "l": 2 },
190 | "a": { "a": 0, "k": [0, 1, 0], "ix": 1, "l": 2 },
191 | "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
192 | },
193 | "ao": 0,
194 | "shapes": [
195 | {
196 | "ty": "gr",
197 | "it": [
198 | {
199 | "ind": 0,
200 | "ty": "sh",
201 | "ix": 1,
202 | "ks": {
203 | "a": 0,
204 | "k": {
205 | "i": [
206 | [0, -88.43],
207 | [2.1, -10.344],
208 | [0, 0],
209 | [0, 10.968],
210 | [-88.43, 0]
211 | ],
212 | "o": [
213 | [0, 10.968],
214 | [0, 0],
215 | [-2.1, -10.344],
216 | [0, -88.43],
217 | [88.43, 0]
218 | ],
219 | "v": [
220 | [160.119, 65.045],
221 | [156.915, 97.074],
222 | [-156.915, 97.074],
223 | [-160.119, 65.045],
224 | [0, -95.074]
225 | ],
226 | "c": true
227 | },
228 | "ix": 2
229 | },
230 | "nm": "Path 1",
231 | "mn": "ADBE Vector Shape - Group",
232 | "hd": false
233 | },
234 | {
235 | "ind": 1,
236 | "ty": "sh",
237 | "ix": 2,
238 | "ks": {
239 | "a": 0,
240 | "k": {
241 | "i": [
242 | [0, 0],
243 | [0, 0]
244 | ],
245 | "o": [
246 | [0, 0],
247 | [0, 0]
248 | ],
249 | "v": [
250 | [65.392, -48.218],
251 | [51.653, -24.421]
252 | ],
253 | "c": false
254 | },
255 | "ix": 2
256 | },
257 | "nm": "Path 2",
258 | "mn": "ADBE Vector Shape - Group",
259 | "hd": false
260 | },
261 | {
262 | "ind": 2,
263 | "ty": "sh",
264 | "ix": 3,
265 | "ks": {
266 | "a": 0,
267 | "k": {
268 | "i": [
269 | [0, 0],
270 | [0, 0]
271 | ],
272 | "o": [
273 | [0, 0],
274 | [0, 0]
275 | ],
276 | "v": [
277 | [113.263, -0.347],
278 | [89.466, 13.392]
279 | ],
280 | "c": false
281 | },
282 | "ix": 2
283 | },
284 | "nm": "Path 3",
285 | "mn": "ADBE Vector Shape - Group",
286 | "hd": false
287 | },
288 | {
289 | "ind": 3,
290 | "ty": "sh",
291 | "ix": 4,
292 | "ks": {
293 | "a": 0,
294 | "k": {
295 | "i": [
296 | [0, 0],
297 | [0, 0]
298 | ],
299 | "o": [
300 | [0, 0],
301 | [0, 0]
302 | ],
303 | "v": [
304 | [130.785, 65.045],
305 | [103.306, 65.045]
306 | ],
307 | "c": false
308 | },
309 | "ix": 2
310 | },
311 | "nm": "Path 4",
312 | "mn": "ADBE Vector Shape - Group",
313 | "hd": false
314 | },
315 | {
316 | "ind": 4,
317 | "ty": "sh",
318 | "ix": 5,
319 | "ks": {
320 | "a": 0,
321 | "k": {
322 | "i": [
323 | [0, 0],
324 | [0, 0]
325 | ],
326 | "o": [
327 | [0, 0],
328 | [0, 0]
329 | ],
330 | "v": [
331 | [-130.785, 65.045],
332 | [-103.306, 65.045]
333 | ],
334 | "c": false
335 | },
336 | "ix": 2
337 | },
338 | "nm": "Path 5",
339 | "mn": "ADBE Vector Shape - Group",
340 | "hd": false
341 | },
342 | {
343 | "ind": 5,
344 | "ty": "sh",
345 | "ix": 6,
346 | "ks": {
347 | "a": 0,
348 | "k": {
349 | "i": [
350 | [0, 0],
351 | [0, 0]
352 | ],
353 | "o": [
354 | [0, 0],
355 | [0, 0]
356 | ],
357 | "v": [
358 | [-113.263, -0.347],
359 | [-89.466, 13.392]
360 | ],
361 | "c": false
362 | },
363 | "ix": 2
364 | },
365 | "nm": "Path 6",
366 | "mn": "ADBE Vector Shape - Group",
367 | "hd": false
368 | },
369 | {
370 | "ind": 6,
371 | "ty": "sh",
372 | "ix": 7,
373 | "ks": {
374 | "a": 0,
375 | "k": {
376 | "i": [
377 | [0, 0],
378 | [0, 0]
379 | ],
380 | "o": [
381 | [0, 0],
382 | [0, 0]
383 | ],
384 | "v": [
385 | [-65.392, -48.218],
386 | [-51.653, -24.421]
387 | ],
388 | "c": false
389 | },
390 | "ix": 2
391 | },
392 | "nm": "Path 7",
393 | "mn": "ADBE Vector Shape - Group",
394 | "hd": false
395 | },
396 | {
397 | "ind": 7,
398 | "ty": "sh",
399 | "ix": 8,
400 | "ks": {
401 | "a": 0,
402 | "k": {
403 | "i": [
404 | [0, 0],
405 | [0, 0]
406 | ],
407 | "o": [
408 | [0, 0],
409 | [0, 0]
410 | ],
411 | "v": [
412 | [0, -65.739],
413 | [0, -38.261]
414 | ],
415 | "c": false
416 | },
417 | "ix": 2
418 | },
419 | "nm": "Path 8",
420 | "mn": "ADBE Vector Shape - Group",
421 | "hd": false
422 | },
423 | {
424 | "ty": "st",
425 | "c": {
426 | "a": 0,
427 | "k": [0.070588238537, 0.074509806931, 0.192156866193, 1],
428 | "ix": 3
429 | },
430 | "o": { "a": 0, "k": 100, "ix": 4 },
431 | "w": {
432 | "a": 0,
433 | "k": 12,
434 | "ix": 5,
435 | "x": "var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 70), comp('467-dashboard-gauge-gradient').layer('Color & Stroke Change').effect('Stroke')('Slider'));"
436 | },
437 | "lc": 2,
438 | "lj": 2,
439 | "bm": 0,
440 | "nm": "Stroke 1",
441 | "mn": "ADBE Vector Graphic - Stroke",
442 | "hd": false
443 | },
444 | {
445 | "ty": "tr",
446 | "p": { "a": 0, "k": [0, 0], "ix": 2 },
447 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
448 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
449 | "r": { "a": 0, "k": 0, "ix": 6 },
450 | "o": { "a": 0, "k": 100, "ix": 7 },
451 | "sk": { "a": 0, "k": 0, "ix": 4 },
452 | "sa": { "a": 0, "k": 0, "ix": 5 },
453 | "nm": "Transform"
454 | }
455 | ],
456 | "nm": "Group 1",
457 | "np": 9,
458 | "cix": 2,
459 | "bm": 0,
460 | "ix": 1,
461 | "mn": "ADBE Vector Group",
462 | "hd": false
463 | }
464 | ],
465 | "ip": 0,
466 | "op": 120,
467 | "st": 0,
468 | "bm": 0
469 | },
470 | {
471 | "ddd": 0,
472 | "ind": 3,
473 | "ty": 4,
474 | "nm": "outline",
475 | "parent": 1,
476 | "sr": 1,
477 | "ks": {
478 | "o": { "a": 0, "k": 100, "ix": 11 },
479 | "r": { "a": 0, "k": -60, "ix": 10 },
480 | "p": { "a": 0, "k": [-18.488, 33.023, 0], "ix": 2, "l": 2 },
481 | "a": { "a": 0, "k": [18.488, 33.023, 0], "ix": 1, "l": 2 },
482 | "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
483 | },
484 | "ao": 0,
485 | "shapes": [
486 | {
487 | "ty": "gr",
488 | "it": [
489 | {
490 | "ind": 0,
491 | "ty": "sh",
492 | "ix": 1,
493 | "ks": {
494 | "a": 0,
495 | "k": {
496 | "i": [
497 | [0, 0],
498 | [0, 0]
499 | ],
500 | "o": [
501 | [0, 0],
502 | [0, 0]
503 | ],
504 | "v": [
505 | [36.977, 1],
506 | [0, 65.045]
507 | ],
508 | "c": false
509 | },
510 | "ix": 2
511 | },
512 | "nm": "Path 1",
513 | "mn": "ADBE Vector Shape - Group",
514 | "hd": false
515 | },
516 | {
517 | "ty": "st",
518 | "c": {
519 | "a": 0,
520 | "k": [0.070588238537, 0.074509806931, 0.192156866193, 1],
521 | "ix": 3
522 | },
523 | "o": { "a": 0, "k": 100, "ix": 4 },
524 | "w": {
525 | "a": 0,
526 | "k": 12,
527 | "ix": 5,
528 | "x": "var $bm_rt;\n$bm_rt = $bm_mul($bm_div(value, 70), comp('467-dashboard-gauge-gradient').layer('Color & Stroke Change').effect('Stroke')('Slider'));"
529 | },
530 | "lc": 2,
531 | "lj": 2,
532 | "bm": 0,
533 | "nm": "Stroke 1",
534 | "mn": "ADBE Vector Graphic - Stroke",
535 | "hd": false
536 | },
537 | {
538 | "ty": "tr",
539 | "p": { "a": 0, "k": [0, 0], "ix": 2 },
540 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
541 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
542 | "r": { "a": 0, "k": 0, "ix": 6 },
543 | "o": { "a": 0, "k": 100, "ix": 7 },
544 | "sk": { "a": 0, "k": 0, "ix": 4 },
545 | "sa": { "a": 0, "k": 0, "ix": 5 },
546 | "nm": "Transform"
547 | }
548 | ],
549 | "nm": "Group 1",
550 | "np": 2,
551 | "cix": 2,
552 | "bm": 0,
553 | "ix": 1,
554 | "mn": "ADBE Vector Group",
555 | "hd": false
556 | }
557 | ],
558 | "ip": 0,
559 | "op": 120,
560 | "st": 0,
561 | "bm": 0
562 | }
563 | ]
564 | }
565 | ],
566 | "layers": [
567 | {
568 | "ddd": 0,
569 | "ind": 1,
570 | "ty": 4,
571 | "nm": "watermark",
572 | "sr": 1,
573 | "ks": {
574 | "o": {
575 | "a": 0,
576 | "k": 100,
577 | "ix": 11,
578 | "x": "var $bm_rt;\nvar checkbox = thisComp.layer('02092020').effect('02092020002')('Checkbox');\nif (checkbox == 1) {\n $bm_rt = 20;\n} else {\n $bm_rt = 0;\n}\n;"
579 | },
580 | "r": { "a": 0, "k": 0, "ix": 10 },
581 | "p": { "a": 0, "k": [249.934, 481.369, 0], "ix": 2, "l": 2 },
582 | "a": { "a": 0, "k": [79.934, 0.369, 0], "ix": 1, "l": 2 },
583 | "s": { "a": 0, "k": [265.159, 265.159, 100], "ix": 6, "l": 2 }
584 | },
585 | "ao": 0,
586 | "shapes": [
587 | {
588 | "ind": 0,
589 | "ty": "sh",
590 | "ix": 1,
591 | "ks": {
592 | "a": 0,
593 | "k": {
594 | "i": [
595 | [0, 0],
596 | [0, 0],
597 | [0, 0],
598 | [0, 0],
599 | [0, 0],
600 | [0, 0]
601 | ],
602 | "o": [
603 | [0, 0],
604 | [0, 0],
605 | [0, 0],
606 | [0, 0],
607 | [0, 0],
608 | [0, 0]
609 | ],
610 | "v": [
611 | [1.415, 0],
612 | [11.014, 0],
613 | [11.014, -2.523],
614 | [4.656, -2.523],
615 | [4.656, -14.809],
616 | [1.415, -14.809]
617 | ],
618 | "c": true
619 | },
620 | "ix": 2
621 | },
622 | "nm": "l",
623 | "mn": "ADBE Vector Shape - Group",
624 | "hd": false
625 | },
626 | {
627 | "ind": 1,
628 | "ty": "sh",
629 | "ix": 2,
630 | "ks": {
631 | "a": 0,
632 | "k": {
633 | "i": [
634 | [0, -3.938],
635 | [-1.62, -1.723],
636 | [-1.949, 0],
637 | [-1.641, 1.846],
638 | [0, 2.154],
639 | [1.579, 1.805],
640 | [1.579, 0]
641 | ],
642 | "o": [
643 | [0, 1.354],
644 | [1.354, 1.415],
645 | [1.231, 0],
646 | [1.21, -1.354],
647 | [0, -1.456],
648 | [-1.456, -1.641],
649 | [-5.333, 0]
650 | ],
651 | "v": [
652 | [11.167, -7.199],
653 | [12.992, -1.661],
654 | [18.243, 0.369],
655 | [23.514, -1.743],
656 | [25.381, -7.548],
657 | [23.494, -13.127],
658 | [18.284, -15.137]
659 | ],
660 | "c": true
661 | },
662 | "ix": 2
663 | },
664 | "nm": "o",
665 | "mn": "ADBE Vector Shape - Group",
666 | "hd": false
667 | },
668 | {
669 | "ind": 2,
670 | "ty": "sh",
671 | "ix": 3,
672 | "ks": {
673 | "a": 0,
674 | "k": {
675 | "i": [
676 | [0, 1.415],
677 | [-0.841, 1.026],
678 | [-1.19, 0],
679 | [-0.615, -1.825],
680 | [0, -0.718],
681 | [0.492, -0.738],
682 | [1.292, 0],
683 | [0.451, 0.615]
684 | ],
685 | "o": [
686 | [0, -1.682],
687 | [0.595, -0.759],
688 | [1.518, 0],
689 | [0.308, 0.902],
690 | [0, 2.359],
691 | [-0.595, 0.923],
692 | [-1.477, 0],
693 | [-0.882, -1.149]
694 | ],
695 | "v": [
696 | [14.49, -7.302],
697 | [15.577, -11.609],
698 | [18.305, -12.86],
699 | [21.689, -10.235],
700 | [22.058, -7.589],
701 | [21.053, -3.343],
702 | [18.284, -1.969],
703 | [15.597, -3.159]
704 | ],
705 | "c": true
706 | },
707 | "ix": 2
708 | },
709 | "nm": "o",
710 | "mn": "ADBE Vector Shape - Group",
711 | "hd": false
712 | },
713 | {
714 | "ind": 3,
715 | "ty": "sh",
716 | "ix": 4,
717 | "ks": {
718 | "a": 0,
719 | "k": {
720 | "i": [
721 | [0, 0],
722 | [0, 0],
723 | [0, 0],
724 | [0, 0],
725 | [-0.287, -0.841],
726 | [-0.144, -0.82],
727 | [0, 0],
728 | [0.164, 0.656],
729 | [0.226, 1.743],
730 | [2.236, 0.205],
731 | [0, 2.769],
732 | [0.923, 0.8],
733 | [1.641, -0.021],
734 | [0, 0]
735 | ],
736 | "o": [
737 | [0, 0],
738 | [0, 0],
739 | [0, 0],
740 | [0.533, 0],
741 | [0.205, 0.574],
742 | [0, 0],
743 | [-0.164, -0.246],
744 | [-0.103, -0.41],
745 | [-0.267, -1.928],
746 | [0.718, -0.205],
747 | [0, -0.964],
748 | [-1.19, -1.026],
749 | [0, 0],
750 | [0, 0]
751 | ],
752 | "v": [
753 | [27.381, 0],
754 | [30.622, 0],
755 | [30.622, -5.989],
756 | [33.411, -5.989],
757 | [35.011, -5.148],
758 | [35.811, 0],
759 | [39.318, 0],
760 | [38.867, -1.067],
761 | [38.416, -3.938],
762 | [35.749, -7.343],
763 | [38.847, -10.973],
764 | [37.554, -13.824],
765 | [33.063, -14.829],
766 | [27.381, -14.829]
767 | ],
768 | "c": true
769 | },
770 | "ix": 2
771 | },
772 | "nm": "r",
773 | "mn": "ADBE Vector Shape - Group",
774 | "hd": false
775 | },
776 | {
777 | "ind": 4,
778 | "ty": "sh",
779 | "ix": 5,
780 | "ks": {
781 | "a": 0,
782 | "k": {
783 | "i": [
784 | [0, 0],
785 | [0, 0],
786 | [-0.492, -0.349],
787 | [0, -1.005],
788 | [0.226, -0.164],
789 | [0.369, 0],
790 | [0, 0]
791 | ],
792 | "o": [
793 | [0, 0],
794 | [1.005, 0],
795 | [0.287, 0.185],
796 | [0, 1.046],
797 | [-0.513, 0.41],
798 | [0, 0],
799 | [0, 0]
800 | ],
801 | "v": [
802 | [30.519, -12.491],
803 | [32.652, -12.491],
804 | [34.744, -12.142],
805 | [35.524, -10.481],
806 | [34.703, -8.758],
807 | [33.083, -8.348],
808 | [30.519, -8.348]
809 | ],
810 | "c": true
811 | },
812 | "ix": 2
813 | },
814 | "nm": "r",
815 | "mn": "ADBE Vector Shape - Group",
816 | "hd": false
817 | },
818 | {
819 | "ind": 5,
820 | "ty": "sh",
821 | "ix": 6,
822 | "ks": {
823 | "a": 0,
824 | "k": {
825 | "i": [
826 | [0, 0],
827 | [0, 0],
828 | [-0.554, 0.103],
829 | [0, 4.553],
830 | [1.866, 1.374],
831 | [0.82, 0],
832 | [0, 0]
833 | ],
834 | "o": [
835 | [0, 0],
836 | [1.497, 0],
837 | [2.81, -0.513],
838 | [0, -2.113],
839 | [-1.784, -1.313],
840 | [0, 0],
841 | [0, 0]
842 | ],
843 | "v": [
844 | [41.068, 0],
845 | [45.683, 0],
846 | [48.349, -0.164],
847 | [53.6, -7.609],
848 | [51.077, -13.434],
849 | [45.97, -14.768],
850 | [41.068, -14.788]
851 | ],
852 | "c": true
853 | },
854 | "ix": 2
855 | },
856 | "nm": "d",
857 | "mn": "ADBE Vector Shape - Group",
858 | "hd": false
859 | },
860 | {
861 | "ind": 6,
862 | "ty": "sh",
863 | "ix": 7,
864 | "ks": {
865 | "a": 0,
866 | "k": {
867 | "i": [
868 | [0, 0],
869 | [-0.656, -0.185],
870 | [0, -2.092],
871 | [1.251, -1.251],
872 | [1.354, 0],
873 | [0.349, 0.021]
874 | ],
875 | "o": [
876 | [1.825, -0.082],
877 | [1.99, 0.554],
878 | [0, 0.718],
879 | [-0.923, 0.923],
880 | [-0.369, 0],
881 | [0, 0]
882 | ],
883 | "v": [
884 | [44.288, -12.388],
885 | [47.611, -12.183],
886 | [50.318, -7.609],
887 | [48.985, -3.425],
888 | [45.539, -2.4],
889 | [44.288, -2.441]
890 | ],
891 | "c": true
892 | },
893 | "ix": 2
894 | },
895 | "nm": "d",
896 | "mn": "ADBE Vector Shape - Group",
897 | "hd": false
898 | },
899 | {
900 | "ind": 7,
901 | "ty": "sh",
902 | "ix": 8,
903 | "ks": {
904 | "a": 0,
905 | "k": {
906 | "i": [
907 | [0, 0],
908 | [0, 0],
909 | [0, 0],
910 | [0, 0]
911 | ],
912 | "o": [
913 | [0, 0],
914 | [0, 0],
915 | [0, 0],
916 | [0, 0]
917 | ],
918 | "v": [
919 | [55.669, 0],
920 | [58.849, 0],
921 | [58.849, -14.87],
922 | [55.669, -14.87]
923 | ],
924 | "c": true
925 | },
926 | "ix": 2
927 | },
928 | "nm": "i",
929 | "mn": "ADBE Vector Shape - Group",
930 | "hd": false
931 | },
932 | {
933 | "ind": 8,
934 | "ty": "sh",
935 | "ix": 9,
936 | "ks": {
937 | "a": 0,
938 | "k": {
939 | "i": [
940 | [0, 0],
941 | [3.241, 0],
942 | [0, -4.697],
943 | [-5.107, 0],
944 | [-1.313, 1.354],
945 | [-0.062, 0.882],
946 | [0, 0],
947 | [1.333, 0],
948 | [0, 0.882],
949 | [-2.359, 0],
950 | [-0.062, -0.513]
951 | ],
952 | "o": [
953 | [0, -2.954],
954 | [-4.164, 0],
955 | [0, 3.671],
956 | [1.354, 0],
957 | [1.19, -1.231],
958 | [0, 0],
959 | [-0.062, 1.969],
960 | [-3.097, 0],
961 | [0, -3.056],
962 | [2.154, 0],
963 | [0, 0]
964 | ],
965 | "v": [
966 | [73.104, -9.989],
967 | [67.587, -14.911],
968 | [60.798, -7.097],
969 | [67.566, 0.349],
970 | [71.894, -1.313],
971 | [73.227, -4.799],
972 | [69.884, -4.799],
973 | [67.218, -1.99],
974 | [64.121, -7.076],
975 | [67.464, -12.593],
976 | [69.864, -9.989]
977 | ],
978 | "c": true
979 | },
980 | "ix": 2
981 | },
982 | "nm": "c",
983 | "mn": "ADBE Vector Shape - Group",
984 | "hd": false
985 | },
986 | {
987 | "ind": 9,
988 | "ty": "sh",
989 | "ix": 10,
990 | "ks": {
991 | "a": 0,
992 | "k": {
993 | "i": [
994 | [0, -3.938],
995 | [-1.62, -1.723],
996 | [-1.949, 0],
997 | [-1.641, 1.846],
998 | [0, 2.154],
999 | [1.579, 1.805],
1000 | [1.579, 0]
1001 | ],
1002 | "o": [
1003 | [0, 1.354],
1004 | [1.354, 1.415],
1005 | [1.231, 0],
1006 | [1.21, -1.354],
1007 | [0, -1.456],
1008 | [-1.456, -1.641],
1009 | [-5.333, 0]
1010 | ],
1011 | "v": [
1012 | [74.546, -7.199],
1013 | [76.372, -1.661],
1014 | [81.622, 0.369],
1015 | [86.894, -1.743],
1016 | [88.76, -7.548],
1017 | [86.873, -13.127],
1018 | [81.663, -15.137]
1019 | ],
1020 | "c": true
1021 | },
1022 | "ix": 2
1023 | },
1024 | "nm": "o",
1025 | "mn": "ADBE Vector Shape - Group",
1026 | "hd": false
1027 | },
1028 | {
1029 | "ind": 10,
1030 | "ty": "sh",
1031 | "ix": 11,
1032 | "ks": {
1033 | "a": 0,
1034 | "k": {
1035 | "i": [
1036 | [0, 1.415],
1037 | [-0.841, 1.026],
1038 | [-1.19, 0],
1039 | [-0.615, -1.825],
1040 | [0, -0.718],
1041 | [0.492, -0.738],
1042 | [1.292, 0],
1043 | [0.451, 0.615]
1044 | ],
1045 | "o": [
1046 | [0, -1.682],
1047 | [0.595, -0.759],
1048 | [1.518, 0],
1049 | [0.308, 0.902],
1050 | [0, 2.359],
1051 | [-0.595, 0.923],
1052 | [-1.477, 0],
1053 | [-0.882, -1.149]
1054 | ],
1055 | "v": [
1056 | [77.869, -7.302],
1057 | [78.956, -11.609],
1058 | [81.684, -12.86],
1059 | [85.068, -10.235],
1060 | [85.437, -7.589],
1061 | [84.432, -3.343],
1062 | [81.663, -1.969],
1063 | [78.977, -3.159]
1064 | ],
1065 | "c": true
1066 | },
1067 | "ix": 2
1068 | },
1069 | "nm": "o",
1070 | "mn": "ADBE Vector Shape - Group",
1071 | "hd": false
1072 | },
1073 | {
1074 | "ind": 11,
1075 | "ty": "sh",
1076 | "ix": 12,
1077 | "ks": {
1078 | "a": 0,
1079 | "k": {
1080 | "i": [
1081 | [0, 0],
1082 | [0, 0],
1083 | [0, 0],
1084 | [0, 0],
1085 | [0, 0],
1086 | [0, 0],
1087 | [0, 0],
1088 | [0, 0],
1089 | [0, 0],
1090 | [0, 0]
1091 | ],
1092 | "o": [
1093 | [0, 0],
1094 | [0, 0],
1095 | [0, 0],
1096 | [0, 0],
1097 | [0, 0],
1098 | [0, 0],
1099 | [0, 0],
1100 | [0, 0],
1101 | [0, 0],
1102 | [0, 0]
1103 | ],
1104 | "v": [
1105 | [91.007, 0],
1106 | [94.001, 0],
1107 | [94.001, -12.306],
1108 | [99.744, 0],
1109 | [104.113, 0],
1110 | [104.113, -14.829],
1111 | [101.159, -14.829],
1112 | [101.159, -3.159],
1113 | [95.601, -14.829],
1114 | [91.007, -14.829]
1115 | ],
1116 | "c": true
1117 | },
1118 | "ix": 2
1119 | },
1120 | "nm": "n",
1121 | "mn": "ADBE Vector Shape - Group",
1122 | "hd": false
1123 | },
1124 | {
1125 | "ind": 12,
1126 | "ty": "sh",
1127 | "ix": 13,
1128 | "ks": {
1129 | "a": 0,
1130 | "k": {
1131 | "i": [
1132 | [0, 0],
1133 | [0, 0],
1134 | [0, 0],
1135 | [0, 0]
1136 | ],
1137 | "o": [
1138 | [0, 0],
1139 | [0, 0],
1140 | [0, 0],
1141 | [0, 0]
1142 | ],
1143 | "v": [
1144 | [106.893, 0],
1145 | [109.497, 0],
1146 | [109.497, -2.728],
1147 | [106.893, -2.728]
1148 | ],
1149 | "c": true
1150 | },
1151 | "ix": 2
1152 | },
1153 | "nm": ".",
1154 | "mn": "ADBE Vector Shape - Group",
1155 | "hd": false
1156 | },
1157 | {
1158 | "ind": 13,
1159 | "ty": "sh",
1160 | "ix": 14,
1161 | "ks": {
1162 | "a": 0,
1163 | "k": {
1164 | "i": [
1165 | [0, 0],
1166 | [3.241, 0],
1167 | [0, -4.697],
1168 | [-5.107, 0],
1169 | [-1.313, 1.354],
1170 | [-0.062, 0.882],
1171 | [0, 0],
1172 | [1.333, 0],
1173 | [0, 0.882],
1174 | [-2.359, 0],
1175 | [-0.062, -0.513]
1176 | ],
1177 | "o": [
1178 | [0, -2.954],
1179 | [-4.164, 0],
1180 | [0, 3.671],
1181 | [1.354, 0],
1182 | [1.19, -1.231],
1183 | [0, 0],
1184 | [-0.062, 1.969],
1185 | [-3.097, 0],
1186 | [0, -3.056],
1187 | [2.154, 0],
1188 | [0, 0]
1189 | ],
1190 | "v": [
1191 | [124.04, -9.989],
1192 | [118.523, -14.911],
1193 | [111.734, -7.097],
1194 | [118.502, 0.349],
1195 | [122.83, -1.313],
1196 | [124.163, -4.799],
1197 | [120.82, -4.799],
1198 | [118.154, -1.99],
1199 | [115.057, -7.076],
1200 | [118.4, -12.593],
1201 | [120.8, -9.989]
1202 | ],
1203 | "c": true
1204 | },
1205 | "ix": 2
1206 | },
1207 | "nm": "c",
1208 | "mn": "ADBE Vector Shape - Group",
1209 | "hd": false
1210 | },
1211 | {
1212 | "ind": 14,
1213 | "ty": "sh",
1214 | "ix": 15,
1215 | "ks": {
1216 | "a": 0,
1217 | "k": {
1218 | "i": [
1219 | [0, -3.938],
1220 | [-1.62, -1.723],
1221 | [-1.949, 0],
1222 | [-1.641, 1.846],
1223 | [0, 2.154],
1224 | [1.579, 1.805],
1225 | [1.579, 0]
1226 | ],
1227 | "o": [
1228 | [0, 1.354],
1229 | [1.354, 1.415],
1230 | [1.231, 0],
1231 | [1.21, -1.354],
1232 | [0, -1.456],
1233 | [-1.456, -1.641],
1234 | [-5.333, 0]
1235 | ],
1236 | "v": [
1237 | [125.482, -7.199],
1238 | [127.308, -1.661],
1239 | [132.558, 0.369],
1240 | [137.829, -1.743],
1241 | [139.696, -7.548],
1242 | [137.809, -13.127],
1243 | [132.599, -15.137]
1244 | ],
1245 | "c": true
1246 | },
1247 | "ix": 2
1248 | },
1249 | "nm": "o",
1250 | "mn": "ADBE Vector Shape - Group",
1251 | "hd": false
1252 | },
1253 | {
1254 | "ind": 15,
1255 | "ty": "sh",
1256 | "ix": 16,
1257 | "ks": {
1258 | "a": 0,
1259 | "k": {
1260 | "i": [
1261 | [0, 1.415],
1262 | [-0.841, 1.026],
1263 | [-1.19, 0],
1264 | [-0.615, -1.825],
1265 | [0, -0.718],
1266 | [0.492, -0.738],
1267 | [1.292, 0],
1268 | [0.451, 0.615]
1269 | ],
1270 | "o": [
1271 | [0, -1.682],
1272 | [0.595, -0.759],
1273 | [1.518, 0],
1274 | [0.308, 0.902],
1275 | [0, 2.359],
1276 | [-0.595, 0.923],
1277 | [-1.477, 0],
1278 | [-0.882, -1.149]
1279 | ],
1280 | "v": [
1281 | [128.805, -7.302],
1282 | [129.892, -11.609],
1283 | [132.62, -12.86],
1284 | [136.004, -10.235],
1285 | [136.373, -7.589],
1286 | [135.368, -3.343],
1287 | [132.599, -1.969],
1288 | [129.912, -3.159]
1289 | ],
1290 | "c": true
1291 | },
1292 | "ix": 2
1293 | },
1294 | "nm": "o",
1295 | "mn": "ADBE Vector Shape - Group",
1296 | "hd": false
1297 | },
1298 | {
1299 | "ind": 16,
1300 | "ty": "sh",
1301 | "ix": 17,
1302 | "ks": {
1303 | "a": 0,
1304 | "k": {
1305 | "i": [
1306 | [0, 0],
1307 | [0, 0],
1308 | [0, 0],
1309 | [0, 0],
1310 | [0, 0],
1311 | [0, 0],
1312 | [0, 0],
1313 | [0, 0],
1314 | [0, 0],
1315 | [0, 0],
1316 | [0, 0],
1317 | [0, 0],
1318 | [0, 0]
1319 | ],
1320 | "o": [
1321 | [0, 0],
1322 | [0, 0],
1323 | [0, 0],
1324 | [0, 0],
1325 | [0, 0],
1326 | [0, 0],
1327 | [0, 0],
1328 | [0, 0],
1329 | [0, 0],
1330 | [0, 0],
1331 | [0, 0],
1332 | [0, 0],
1333 | [0, 0]
1334 | ],
1335 | "v": [
1336 | [141.696, 0],
1337 | [144.67, 0],
1338 | [144.67, -12.716],
1339 | [148.629, 0],
1340 | [151.254, 0],
1341 | [155.295, -12.716],
1342 | [155.295, 0],
1343 | [158.453, 0],
1344 | [158.453, -14.829],
1345 | [153.408, -14.829],
1346 | [150.024, -4.041],
1347 | [146.885, -14.829],
1348 | [141.696, -14.829]
1349 | ],
1350 | "c": true
1351 | },
1352 | "ix": 2
1353 | },
1354 | "nm": "m",
1355 | "mn": "ADBE Vector Shape - Group",
1356 | "hd": false
1357 | },
1358 | {
1359 | "ty": "fl",
1360 | "c": { "a": 0, "k": [0, 0, 0, 1], "ix": 4 },
1361 | "o": { "a": 0, "k": 100, "ix": 5 },
1362 | "r": 1,
1363 | "bm": 0,
1364 | "nm": "Fill 1",
1365 | "mn": "ADBE Vector Graphic - Fill",
1366 | "hd": false
1367 | }
1368 | ],
1369 | "ip": 1,
1370 | "op": 10,
1371 | "st": 0,
1372 | "bm": 0
1373 | },
1374 | {
1375 | "ddd": 0,
1376 | "ind": 2,
1377 | "ty": 3,
1378 | "nm": "02092020",
1379 | "sr": 1,
1380 | "ks": {
1381 | "o": { "a": 0, "k": 0, "ix": 11 },
1382 | "r": { "a": 0, "k": 0, "ix": 10 },
1383 | "p": { "a": 0, "k": [-105, 15, 0], "ix": 2, "l": 2 },
1384 | "a": { "a": 0, "k": [60, 60, 0], "ix": 1, "l": 2 },
1385 | "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
1386 | },
1387 | "ao": 0,
1388 | "ef": [
1389 | {
1390 | "ty": 5,
1391 | "nm": "02092020002",
1392 | "np": 3,
1393 | "mn": "ADBE Checkbox Control",
1394 | "ix": 1,
1395 | "en": 1,
1396 | "ef": [
1397 | {
1398 | "ty": 7,
1399 | "nm": "Checkbox",
1400 | "mn": "ADBE Checkbox Control-0001",
1401 | "ix": 1,
1402 | "v": { "a": 0, "k": 0, "ix": 1 }
1403 | }
1404 | ]
1405 | }
1406 | ],
1407 | "ip": 0,
1408 | "op": 120,
1409 | "st": 0,
1410 | "bm": 0
1411 | },
1412 | {
1413 | "ddd": 0,
1414 | "ind": 3,
1415 | "ty": 3,
1416 | "nm": "Color & Stroke Change",
1417 | "sr": 1,
1418 | "ks": {
1419 | "o": { "a": 0, "k": 0, "ix": 11 },
1420 | "r": { "a": 0, "k": 0, "ix": 10 },
1421 | "p": {
1422 | "a": 0,
1423 | "k": [250, 250, 0],
1424 | "ix": 2,
1425 | "l": 2,
1426 | "x": "var $bm_rt;\n$bm_rt = effect('Axis')('Point');"
1427 | },
1428 | "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 },
1429 | "s": {
1430 | "a": 0,
1431 | "k": [100, 100, 100],
1432 | "ix": 6,
1433 | "l": 2,
1434 | "x": "var $bm_rt;\nvar temp;\ntemp = effect('Scale')('Slider');\n$bm_rt = [\n temp,\n temp\n];"
1435 | }
1436 | },
1437 | "ao": 0,
1438 | "ef": [
1439 | {
1440 | "ty": 5,
1441 | "nm": "Stroke",
1442 | "np": 3,
1443 | "mn": "ADBE Slider Control",
1444 | "ix": 1,
1445 | "en": 1,
1446 | "ef": [
1447 | {
1448 | "ty": 0,
1449 | "nm": "Slider",
1450 | "mn": "ADBE Slider Control-0001",
1451 | "ix": 1,
1452 | "v": { "a": 0, "k": 70, "ix": 1 }
1453 | }
1454 | ]
1455 | },
1456 | {
1457 | "ty": 5,
1458 | "nm": "Axis",
1459 | "np": 3,
1460 | "mn": "ADBE Point Control",
1461 | "ix": 2,
1462 | "en": 1,
1463 | "ef": [
1464 | {
1465 | "ty": 3,
1466 | "nm": "Point",
1467 | "mn": "ADBE Point Control-0001",
1468 | "ix": 1,
1469 | "v": { "a": 0, "k": [250, 250], "ix": 1 }
1470 | }
1471 | ]
1472 | },
1473 | {
1474 | "ty": 5,
1475 | "nm": "Scale",
1476 | "np": 3,
1477 | "mn": "ADBE Slider Control",
1478 | "ix": 3,
1479 | "en": 1,
1480 | "ef": [
1481 | {
1482 | "ty": 0,
1483 | "nm": "Slider",
1484 | "mn": "ADBE Slider Control-0001",
1485 | "ix": 1,
1486 | "v": { "a": 0, "k": 100, "ix": 1 }
1487 | }
1488 | ]
1489 | },
1490 | {
1491 | "ty": 5,
1492 | "nm": "Primary",
1493 | "np": 3,
1494 | "mn": "ADBE Color Control",
1495 | "ix": 4,
1496 | "en": 1,
1497 | "ef": [
1498 | {
1499 | "ty": 2,
1500 | "nm": "Color",
1501 | "mn": "ADBE Color Control-0001",
1502 | "ix": 1,
1503 | "v": { "a": 0, "k": [0.847, 0.4, 0.075], "ix": 1 }
1504 | }
1505 | ]
1506 | },
1507 | {
1508 | "ty": 5,
1509 | "nm": "Secondary",
1510 | "np": 3,
1511 | "mn": "ADBE Color Control",
1512 | "ix": 5,
1513 | "en": 1,
1514 | "ef": [
1515 | {
1516 | "ty": 2,
1517 | "nm": "Color",
1518 | "mn": "ADBE Color Control-0001",
1519 | "ix": 1,
1520 | "v": { "a": 0, "k": [0.91, 0.549, 0.188], "ix": 1 }
1521 | }
1522 | ]
1523 | }
1524 | ],
1525 | "ip": 0,
1526 | "op": 120,
1527 | "st": 0,
1528 | "bm": 0
1529 | },
1530 | {
1531 | "ddd": 0,
1532 | "ind": 4,
1533 | "ty": 0,
1534 | "nm": "mask",
1535 | "parent": 3,
1536 | "td": 1,
1537 | "refId": "comp_0",
1538 | "sr": 1,
1539 | "ks": {
1540 | "o": { "a": 0, "k": 100, "ix": 11 },
1541 | "r": { "a": 0, "k": 0, "ix": 10 },
1542 | "p": { "a": 0, "k": [0, 0, 0], "ix": 2, "l": 2 },
1543 | "a": { "a": 0, "k": [250, 250, 0], "ix": 1, "l": 2 },
1544 | "s": { "a": 0, "k": [100, 100, 100], "ix": 6, "l": 2 }
1545 | },
1546 | "ao": 0,
1547 | "w": 500,
1548 | "h": 500,
1549 | "ip": 0,
1550 | "op": 120,
1551 | "st": 0,
1552 | "bm": 0
1553 | },
1554 | {
1555 | "ddd": 0,
1556 | "ind": 5,
1557 | "ty": 4,
1558 | "nm": "gradient",
1559 | "parent": 3,
1560 | "tt": 1,
1561 | "sr": 1,
1562 | "ks": {
1563 | "o": { "a": 0, "k": 100, "ix": 11 },
1564 | "r": {
1565 | "a": 1,
1566 | "k": [
1567 | {
1568 | "i": { "x": [0.4], "y": [1] },
1569 | "o": { "x": [0.333], "y": [0] },
1570 | "t": 0,
1571 | "s": [-94]
1572 | },
1573 | { "t": 48, "s": [266] }
1574 | ],
1575 | "ix": 10
1576 | },
1577 | "p": { "a": 0, "k": [21.941, 20.46, 0], "ix": 2, "l": 2 },
1578 | "a": { "a": 0, "k": [0, 0, 0], "ix": 1, "l": 2 },
1579 | "s": { "a": 0, "k": [240, 240, 100], "ix": 6, "l": 2 }
1580 | },
1581 | "ao": 0,
1582 | "ef": [
1583 | {
1584 | "ty": 29,
1585 | "nm": "Gaussian Blur",
1586 | "np": 5,
1587 | "mn": "ADBE Gaussian Blur 2",
1588 | "ix": 1,
1589 | "en": 1,
1590 | "ef": [
1591 | {
1592 | "ty": 0,
1593 | "nm": "Blurriness",
1594 | "mn": "ADBE Gaussian Blur 2-0001",
1595 | "ix": 1,
1596 | "v": { "a": 0, "k": 175, "ix": 1 }
1597 | },
1598 | {
1599 | "ty": 7,
1600 | "nm": "Blur Dimensions",
1601 | "mn": "ADBE Gaussian Blur 2-0002",
1602 | "ix": 2,
1603 | "v": { "a": 0, "k": 1, "ix": 2 }
1604 | },
1605 | {
1606 | "ty": 7,
1607 | "nm": "Repeat Edge Pixels",
1608 | "mn": "ADBE Gaussian Blur 2-0003",
1609 | "ix": 3,
1610 | "v": { "a": 0, "k": 1, "ix": 3 }
1611 | }
1612 | ]
1613 | }
1614 | ],
1615 | "shapes": [
1616 | {
1617 | "ty": "gr",
1618 | "it": [
1619 | {
1620 | "ind": 0,
1621 | "ty": "sh",
1622 | "ix": 1,
1623 | "ks": {
1624 | "a": 0,
1625 | "k": {
1626 | "i": [
1627 | [-97.478, 0],
1628 | [0, -97.478],
1629 | [97.478, 0],
1630 | [30.894, 26.574],
1631 | [0, 53.531]
1632 | ],
1633 | "o": [
1634 | [97.478, 0],
1635 | [0, 97.478],
1636 | [-43.948, 0],
1637 | [-37.631, -32.369],
1638 | [0, -97.478]
1639 | ],
1640 | "v": [
1641 | [0, -176.5],
1642 | [176.5, 0],
1643 | [0, 176.5],
1644 | [-105.29, 115.869],
1645 | [-176.5, 0]
1646 | ],
1647 | "c": true
1648 | },
1649 | "ix": 2
1650 | },
1651 | "nm": "Path 1",
1652 | "mn": "ADBE Vector Shape - Group",
1653 | "hd": false
1654 | },
1655 | {
1656 | "ty": "fl",
1657 | "c": {
1658 | "a": 0,
1659 | "k": [0.796078443527, 0.368627458811, 0.933333337307, 1],
1660 | "ix": 4,
1661 | "x": "var $bm_rt;\n$bm_rt = comp('467-dashboard-gauge-gradient').layer('Color & Stroke Change').effect('Secondary')('Color');"
1662 | },
1663 | "o": { "a": 0, "k": 100, "ix": 5 },
1664 | "r": 1,
1665 | "bm": 0,
1666 | "nm": "Fill 1",
1667 | "mn": "ADBE Vector Graphic - Fill",
1668 | "hd": false
1669 | },
1670 | {
1671 | "ty": "tr",
1672 | "p": { "a": 0, "k": [113.242, -118.884], "ix": 2 },
1673 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
1674 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
1675 | "r": { "a": 0, "k": 0, "ix": 6 },
1676 | "o": { "a": 0, "k": 100, "ix": 7 },
1677 | "sk": { "a": 0, "k": 0, "ix": 4 },
1678 | "sa": { "a": 0, "k": 0, "ix": 5 },
1679 | "nm": "Transform"
1680 | }
1681 | ],
1682 | "nm": "primary.design",
1683 | "np": 2,
1684 | "cix": 2,
1685 | "bm": 0,
1686 | "ix": 1,
1687 | "mn": "ADBE Vector Group",
1688 | "hd": false,
1689 | "cl": "design"
1690 | },
1691 | {
1692 | "ty": "gr",
1693 | "it": [
1694 | {
1695 | "d": 1,
1696 | "ty": "el",
1697 | "s": { "a": 0, "k": [500, 500], "ix": 2 },
1698 | "p": { "a": 0, "k": [0, 0], "ix": 3 },
1699 | "nm": "Ellipse Path 1",
1700 | "mn": "ADBE Vector Shape - Ellipse",
1701 | "hd": false
1702 | },
1703 | {
1704 | "ty": "fl",
1705 | "c": {
1706 | "a": 0,
1707 | "k": [0.29411765933, 0.882352948189, 0.92549020052, 1],
1708 | "ix": 4,
1709 | "x": "var $bm_rt;\n$bm_rt = comp('467-dashboard-gauge-gradient').layer('Color & Stroke Change').effect('Primary')('Color');"
1710 | },
1711 | "o": { "a": 0, "k": 100, "ix": 5 },
1712 | "r": 1,
1713 | "bm": 0,
1714 | "nm": "Fill 1",
1715 | "mn": "ADBE Vector Graphic - Fill",
1716 | "hd": false
1717 | },
1718 | {
1719 | "ty": "tr",
1720 | "p": { "a": 0, "k": [0, 0], "ix": 2 },
1721 | "a": { "a": 0, "k": [0, 0], "ix": 1 },
1722 | "s": { "a": 0, "k": [100, 100], "ix": 3 },
1723 | "r": { "a": 0, "k": 0, "ix": 6 },
1724 | "o": { "a": 0, "k": 100, "ix": 7 },
1725 | "sk": { "a": 0, "k": 0, "ix": 4 },
1726 | "sa": { "a": 0, "k": 0, "ix": 5 },
1727 | "nm": "Transform"
1728 | }
1729 | ],
1730 | "nm": "secondary.design",
1731 | "np": 2,
1732 | "cix": 2,
1733 | "bm": 0,
1734 | "ix": 2,
1735 | "mn": "ADBE Vector Group",
1736 | "hd": false,
1737 | "cl": "design"
1738 | }
1739 | ],
1740 | "ip": 0,
1741 | "op": 120,
1742 | "st": 0,
1743 | "bm": 0
1744 | }
1745 | ],
1746 | "markers": [
1747 | { "tm": 13, "cm": "1", "dr": 0 },
1748 | { "tm": 34, "cm": "2", "dr": 0 },
1749 | { "tm": 48, "cm": "4", "dr": 0 }
1750 | ],
1751 | "features": ["states"]
1752 | }
1753 |
--------------------------------------------------------------------------------
/client/assets/styles.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Roboto:ital,wght@0,100;0,300;0,400;0,500;0,700;0,900;1,100;1,300;1,400;1,500&display=swap");
2 |
3 | .outerContainer {
4 | background: #ececec;
5 | height: 98vh;
6 | display: grid !important;
7 | grid-template-columns: 160px 1fr 1fr 1fr;
8 | grid-template-rows: 80px 1fr 1fr 1fr 1fr 1fr 80px;
9 | gap: 30px;
10 | grid-template-areas:
11 | "Nav Nav Nav Nav"
12 | "Sidebar Metrics Metrics Errors"
13 | "Sidebar Metrics Metrics Errors"
14 | "Sidebar Charts Charts Usage"
15 | "Sidebar Charts Charts Usage"
16 | "Sidebar Charts Charts Usage"
17 | "Footer Footer Footer Footer";
18 | padding: 30px;
19 | }
20 |
21 | .outerContainerLandingPage {
22 | background: #ececec;
23 | height: 98vh;
24 | display: grid !important;
25 | grid-template-columns: 1fr 1fr;
26 | grid-template-rows: 80px 1fr 80px;
27 | gap: 30px;
28 | grid-template-areas:
29 | "Nav Nav"
30 | "Hero Hero"
31 | "Footer Footer";
32 | padding: 30px;
33 | }
34 |
35 | .HeroContainer {
36 | grid-area: Hero;
37 | display: grid !important;
38 | grid-template-columns: 1fr 1fr;
39 | gap: 30px;
40 | padding: 0px 10%;
41 | }
42 |
43 | ul.HeroTechStack li {
44 | list-style: none;
45 | }
46 |
47 | ul.HeroTechStack li a {
48 | padding: 0;
49 | text-decoration: none;
50 | color: gray;
51 | }
52 |
53 | ul.HeroTechStack li a:hover {
54 | color: #d86613;
55 | }
56 |
57 | ul.HeroTechStack {
58 | padding: 0;
59 | }
60 |
61 | .Hero {
62 | width: 100%;
63 | margin: auto;
64 | }
65 |
66 | .Hero h1 {
67 | font-weight: 300;
68 | font-size: 3em;
69 | letter-spacing: 5px;
70 | }
71 |
72 | .Hero h6 {
73 | margin-bottom: 10px;
74 | text-transform: uppercase;
75 | }
76 |
77 | .Hero p {
78 | text-transform: none;
79 | }
80 |
81 | /* Center CSS */
82 |
83 | .Header,
84 | .Usage,
85 | .Charts,
86 | .Sidebar,
87 | .Footer,
88 | .HeroContainer {
89 | font-family: Roboto;
90 | font-weight: 300;
91 | text-transform: uppercase;
92 | letter-spacing: 1px;
93 | display: grid;
94 | justify-content: center;
95 | align-items: center;
96 | border-radius: 20px;
97 | background-color: whitesmoke;
98 | box-shadow: 9px 9px 16px rgb(163, 177, 198, 0.2),
99 | -9px -9px 16px rgba(255, 255, 255, 0.3);
100 | height: 100%;
101 | }
102 |
103 | .Nav {
104 | display: grid;
105 | grid-template-columns: 160px 1fr;
106 | grid-template-rows: 1fr;
107 | gap: 0px 0px;
108 | grid-template-areas: ". . .";
109 | grid-area: Nav;
110 | border-radius: 20px;
111 | background: whitesmoke;
112 | box-shadow: 9px 9px 16px rgb(163, 177, 198, 0.2),
113 | -9px -9px 16px rgba(255, 255, 255, 0.3);
114 | }
115 |
116 | .Logo {
117 | font-family: Roboto;
118 | font-weight: 300;
119 | text-transform: uppercase;
120 | letter-spacing: 3px;
121 | display: grid;
122 | justify-content: center;
123 | align-items: center;
124 | }
125 |
126 | .LumosLogoBox {
127 | display: flex;
128 | justify-content: center;
129 | align-items: center;
130 | }
131 |
132 | .LumosLogoBox h2 {
133 | font-weight: 300;
134 | letter-spacing: 2px;
135 | }
136 |
137 | .LumosLogo {
138 | height: 100%;
139 | width: 150%;
140 | }
141 |
142 | .LumosLogoBox img {
143 | max-height: 70px;
144 | max-width: 100px;
145 | }
146 |
147 | .Login {
148 | background: #efc9ef;
149 | padding: 10px;
150 | color: white;
151 | font-family: Roboto;
152 | font-weight: 300;
153 | letter-spacing: 2px;
154 | margin-right: 20px;
155 | border-radius: 15px;
156 | }
157 |
158 | .loginButton {
159 | background: #d86613 !important;
160 | color: white !important;
161 | }
162 |
163 | .ButtonContainer {
164 | display: flex;
165 | justify-self: flex-end;
166 | }
167 |
168 | .SidebarButtonContainer {
169 | display: grid;
170 | }
171 |
172 | .LumosButton {
173 | font-family: Roboto !important;
174 | font-weight: 400 !important;
175 | letter-spacing: 2px !important;
176 | background-color: white !important;
177 | display: grid !important;
178 | justify-self: center !important;
179 | align-self: center !important;
180 | height: 45px !important;
181 | margin-right: 20px !important;
182 | border-radius: 25px !important;
183 | padding: 0px 40px !important;
184 | box-shadow: 1px 0px 20px #00000008;
185 | color: #4d4d4d !important;
186 | }
187 |
188 | .LumosButton:hover {
189 | box-shadow: 1px 0px 20px #d8661394;
190 | transition: 1.5s;
191 | }
192 |
193 | .timeButtonContainer {
194 | display: flex;
195 | }
196 |
197 | .LoginModal {
198 | margin: auto;
199 | display: grid;
200 | justify-self: center;
201 | align-self: center;
202 | text-align: center;
203 | background: white;
204 | height: 50vh;
205 | width: 85vw;
206 | }
207 |
208 | .ModalButton {
209 | font-family: Roboto !important;
210 | font-weight: 400 !important;
211 | letter-spacing: 2px !important;
212 | background-color: #d86613 !important;
213 | height: 45px !important;
214 | border-radius: 25px !important;
215 | padding: 0px 40px !important;
216 | box-shadow: 1px 0px 20px #00000008;
217 | color: #ffffff !important;
218 | }
219 |
220 | .ModalButton:hover {
221 | box-shadow: 1px 0px 20px #d8661394;
222 | transition: 1.5s;
223 | }
224 |
225 | .usageChartContainer {
226 | height: 85%;
227 | width: 42vh;
228 | }
229 |
230 | .latencyChartContainer {
231 | height: 80%;
232 | width: 50vw;
233 | }
234 |
235 | .Header {
236 | grid-area: Header;
237 | }
238 |
239 | .Metrics {
240 | grid-template-columns: 1fr 1fr;
241 | grid-template-rows: 1fr 1fr;
242 | gap: 20px;
243 | grid-template-areas:
244 | "Active-Functions Total-Errors"
245 | "Avg-Cost Avg-Duration";
246 | grid-area: Metrics;
247 | font-family: Roboto;
248 | font-weight: 300;
249 | text-transform: uppercase;
250 | letter-spacing: 1px;
251 | display: grid;
252 | justify-content: center;
253 | align-items: center;
254 | height: 100%;
255 | }
256 |
257 | .metricCard {
258 | font-family: Roboto;
259 | font-weight: 300;
260 | text-transform: uppercase;
261 | letter-spacing: 1px;
262 | display: grid;
263 | justify-content: center;
264 | align-items: center;
265 | border-radius: 20px;
266 | background-color: whitesmoke;
267 | box-shadow: 9px 9px 16px rgb(163, 177, 198, 0.2),
268 | -9px -9px 16px rgba(255, 255, 255, 0.3);
269 | grid-template-columns: 1fr 1fr 1fr;
270 | grid-template-areas: "Stats Stats cardIcon";
271 | gap: 0px;
272 | height: 100%;
273 | overflow: hidden;
274 | }
275 |
276 | .iconWrapper {
277 | display: flex;
278 | max-height: 5em;
279 | }
280 |
281 | /* .metricContainer {
282 | } */
283 |
284 | .cardStats {
285 | grid-area: Stats;
286 | justify-self: center;
287 | }
288 | .statsNumber,
289 | .cardPercentage {
290 | display: flex;
291 | justify-content: center;
292 | align-items: stretch;
293 | padding: 2px;
294 | overflow: hidden;
295 | }
296 |
297 | .statsNumber {
298 | font-size: 28px;
299 | }
300 |
301 | .cardIcon {
302 | grid-area: cardIcon;
303 | }
304 |
305 | .Usage {
306 | grid-area: Usage;
307 | }
308 | .Charts {
309 | grid-area: Charts;
310 | }
311 | .Errors {
312 | grid-template-columns: 1fr;
313 | gap: 0px 0px;
314 | grid-area: Errors;
315 | font-family: Roboto;
316 | font-weight: 300;
317 | text-transform: uppercase;
318 | letter-spacing: 3px;
319 | display: grid;
320 | border-radius: 20px;
321 | background-color: whitesmoke;
322 | box-shadow: 9px 9px 16px rgb(163, 177, 198, 0.2),
323 | -9px -9px 16px rgba(255, 255, 255, 0.3);
324 | height: 100%;
325 | }
326 |
327 | .errorsContainer {
328 | padding: 20px;
329 | /* overflow: scroll !important; */
330 | }
331 |
332 | .errHeader {
333 | font-family: Roboto;
334 | font-weight: 300;
335 | font-size: 12px;
336 | margin: 0px 0px 10px 0px !important;
337 | padding: 10px 10px;
338 | box-shadow: 1px 0px 15px #00000015;
339 | border-radius: 15px;
340 | }
341 |
342 | .Errors.children {
343 | background-color: whitesmoke;
344 | height: 100%;
345 | /* display: grid; */
346 | /* overflow: scroll !important; */
347 | }
348 | .errA,
349 | .errB {
350 | align-items: center;
351 | }
352 | .ErrorsChildrenContainer {
353 | border-bottom: 1px dotted rgba(0, 0, 0, 0.15);
354 | margin: 0px !important;
355 | padding: 10px 0px !important;
356 | overflow: hidden;
357 | /* text-align: center; */
358 | }
359 | .Errors.Code,
360 | .Errors.Time,
361 | .Errors.Count {
362 | text-align: center !important;
363 | }
364 |
365 | .err {
366 | grid-area: err;
367 | }
368 | .start {
369 | grid-area: start;
370 | }
371 | .end {
372 | grid-area: end;
373 | }
374 | .type {
375 | grid-area: type;
376 | }
377 | .cost {
378 | grid-area: cost;
379 | }
380 | .rowErr {
381 | grid-area: rowErr;
382 | }
383 | .rowStart {
384 | grid-area: rowStart;
385 | }
386 | .rowEnd {
387 | grid-area: rowEnd;
388 | }
389 | .rowType {
390 | grid-area: rowType;
391 | }
392 | .rowCost {
393 | grid-area: rowCost;
394 | }
395 |
396 | .Sidebar {
397 | grid-area: Sidebar;
398 | align-items: baseline;
399 | /* overflow: scroll !important;
400 | height: 104.5% !important; */
401 | }
402 | .sidebarLambda {
403 | padding-top: 20px;
404 | }
405 |
406 | .sidebarIcon {
407 | max-width: 30%;
408 | margin: 25px auto;
409 | border-radius: 50px;
410 | padding: 10px 12px;
411 | box-shadow: 1px 0px 15px #00000015;
412 | font-size: 12px;
413 | letter-spacing: 0px;
414 | text-align: center;
415 | }
416 | .sidebarIcon:hover {
417 | cursor: pointer;
418 | color: #d86613;
419 | background-color: #f3f3f3;
420 | box-shadow: 1px 0px 25px #00000015;
421 | transition: 1s;
422 | }
423 |
424 | .sideBarLottie {
425 | max-height: 40px;
426 | margin-top: -5px;
427 | }
428 |
429 | .sidebarIcon img {
430 | max-height: 35px;
431 | margin: auto;
432 | }
433 |
434 | .sidebarMainIcon {
435 | background: #d86613 !important;
436 | padding: 16px 13px;
437 | border-radius: 50px;
438 | text-align: center;
439 | width: 30%;
440 | margin: 10px auto 20px auto;
441 | }
442 |
443 | .Footer {
444 | grid-area: Footer;
445 | font-size: 12px;
446 | height: 80px;
447 | justify-content: normal;
448 | }
449 |
450 | .footer-inner-container {
451 | display: grid;
452 | grid-template-columns: 1fr 1fr 1fr;
453 | padding: 0px 30px;
454 | }
455 |
456 | .footer-left {
457 | text-align: left;
458 | }
459 |
460 | .footer-left a {
461 | color: gray;
462 | text-decoration: none;
463 | }
464 |
465 | .footer-left a:hover {
466 | color: #d86613;
467 | text-decoration: none;
468 | }
469 | .footer-right {
470 | text-align: right;
471 | }
472 |
473 | .footer-middle {
474 | text-align: center;
475 | }
476 |
477 | /* Metric Cards CSS */
478 |
479 | .Active-Functions {
480 | grid-area: Active-Functions;
481 | }
482 | .Total-Errors {
483 | grid-area: Total-Errors;
484 | }
485 | .Avg-Cost {
486 | grid-area: Avg-Cost;
487 | }
488 |
489 | .superSet span {
490 | vertical-align: super;
491 | font-size: 50%;
492 | }
493 |
494 | .Avg-Duration {
495 | grid-area: Avg-Duration;
496 | }
497 |
498 | .Credentials {
499 | position: absolute;
500 | top: 50%;
501 | left: 50%;
502 | margin-right: -50%;
503 | transform: translate(-50%, -50%);
504 | border-radius: 50px;
505 | padding: 10px;
506 | }
507 |
508 | /* media query for 13"+ laptops with retina */
509 |
510 | @media (min-width: 1921px) {
511 | .iconWrapper {
512 | max-height: 80px;
513 | }
514 | }
515 |
516 | /* Add
517 | Remove
518 | Edit
519 |
520 | query = 'INSERT into users hi'
521 | query = 'DELETE FROM users where id = ${id}'
522 | query = */
523 |
--------------------------------------------------------------------------------
/client/components/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oslabs-beta/Lumos/4c0a1009065e0991c74dd32847e52ce70d98f72f/client/components/.DS_Store
--------------------------------------------------------------------------------
/client/components/Cards/MetricCard.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import {
3 | Box,
4 | Card,
5 | CardContent,
6 | Divider,
7 | Typography,
8 | CardHeader,
9 | Badge,
10 | } from "@mui/material";
11 | import Lottie from "lottie-react";
12 | import lambdaFuncAnimation from "../../assets/lotties/lambda-funcs.json";
13 | import errorsAnimation from "../../assets/lotties/errors.json";
14 | import costAnimation from "../../assets/lotties/cost.json";
15 | import durationAnimation from "../../assets/lotties/duration.json";
16 | import { InfoContext } from "../../containers/MainContainer.jsx";
17 |
18 | export default function MetricCard() {
19 | const [userInfo, setUserInfo] = useContext(InfoContext);
20 | return (
21 | <>
22 |
23 |
24 |
{userInfo.lambdaActiveInvocations}
25 | {/*
+5%
*/}
26 |
Active Invocations
27 |
28 |
33 |
34 |
35 |
36 |
{userInfo.lambdaTotalErrors}
37 | {/*
+5%
*/}
38 |
Total Errors
39 |
40 |
45 |
46 |
47 |
48 |
49 | {userInfo.lambdaTotalCost.toFixed(10)}
50 |
51 | {/*
+5%
*/}
52 |
53 | Total Cost (10^3)
54 |
55 |
56 |
61 |
62 |
63 |
64 |
65 | {(
66 | userInfo.lambdaAvgDuration / userInfo.lambdaActiveInvocations
67 | ).toFixed(2)}
68 |
69 | {/*
+5%
*/}
70 |
Avg Duration (ms)
71 |
72 |
77 |
78 | >
79 | );
80 | }
81 |
--------------------------------------------------------------------------------
/client/components/Charts.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from "react";
3 | import { Box } from "@mui/material";
4 | import Latency from "../components/charts/Latency.jsx";
5 | import LineChart from "../components/charts/TestChart.jsx";
6 |
7 | function Charts() {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | );
15 | }
16 |
17 | export default Charts;
18 |
--------------------------------------------------------------------------------
/client/components/Errors.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React, { useContext, useEffect } from "react";
3 | import { Box, Grid } from "@mui/material";
4 | import { InfoContext } from "../containers/MainContainer.jsx";
5 |
6 | // :)
7 | function Errors() {
8 | const [userInfo] = useContext(InfoContext);
9 | let ar = [];
10 |
11 | for (let i = 0; i < userInfo.lambdaFuncs.length; i += 1) {
12 | const func = userInfo.lambdaFuncs[i];
13 | const label = func.funcName;
14 | const value = func.totalErrors;
15 | const timestamp = func.formattedTimeStamps
16 | ? func.formattedTimeStamps[0]
17 | : 0;
18 | if (value > 0) {
19 | ar.push({
20 | Label: label,
21 | Status: "500",
22 | Value: value,
23 | Timestamp: timestamp,
24 | });
25 | }
26 | }
27 | return (
28 |
29 |
30 |
31 |
32 | Label
33 |
34 |
40 | Errors
41 |
42 |
48 | Time
49 |
50 |
51 |
52 | {ar.map((a) => (
53 |
60 |
66 | {a.Label}
67 |
68 |
74 | {a.Value}
75 |
76 |
82 | {a.Timestamp}
83 |
84 |
85 | ))}
86 |
87 |
88 | );
89 | //
90 | }
91 | // new Date("August 6, 2022 13:30:30") <- needs to be sent to the backend
92 | const today = new Date();
93 | let date =
94 | today.getMonth() +
95 | 1 +
96 | "-" +
97 | today.getDate() +
98 | "-" +
99 | today.getFullYear() +
100 | " T " +
101 | today.getHours() +
102 | ":" +
103 | today.getMinutes() +
104 | ":" +
105 | today.getSeconds();
106 | /*
107 | label = function that was called
108 | value = amount of error/ less is best
109 | timestamp = when function status occurred
110 | */
111 |
112 | const rows = [
113 | {
114 | Label: "why",
115 | Status: "Complete",
116 | Value: 34,
117 | Timestamp: date,
118 | Cost: 4.0,
119 | },
120 | {
121 | Label: "why",
122 | Status: "Complete",
123 | Value: 34,
124 | Timestamp: date,
125 | Cost: 4.0,
126 | },
127 | {
128 | Label: "why",
129 | Status: "Complete",
130 | Value: 34,
131 | Timestamp: date,
132 | Cost: 4.0,
133 | },
134 | ];
135 | export default Errors;
136 |
--------------------------------------------------------------------------------
/client/components/Footer.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from "react";
3 | import { Box } from "@mui/material";
4 |
5 | function Footer() {
6 | return (
7 |
8 |
9 |
16 |
17 | Accelerated by
OS Labs as an
18 | open-source project.
19 |
20 |
2022 © Lumos Tools
21 |
22 |
23 | );
24 | }
25 |
26 | export default Footer;
27 |
--------------------------------------------------------------------------------
/client/components/Header.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from "react";
3 | import { Grid, Box } from "@mui/material";
4 | import DayButton from "./dayButton.jsx";
5 | import WeekButton from "./weekButton.jsx";
6 | import MonthButton from "./monthButton.jsx";
7 |
8 | function Header() {
9 | return (
10 |
11 | );
12 | }
13 |
14 | export default Header;
15 |
--------------------------------------------------------------------------------
/client/components/LandingPage.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, useEffect, createContext, useContext } from "react";
2 | import { Box, Paper } from "@mui/material";
3 | import Login from "../components/Login.jsx";
4 | import Sign from "../components/SignIn.jsx";
5 | import Footer from "../components/Footer.jsx";
6 |
7 | // lottie icons
8 | import Lottie from "lottie-react";
9 | import landingAnimation from "../assets/lotties/landing.json";
10 |
11 | export default function LandingPage() {
12 | return (
13 | <>
14 |
15 |
16 | Welcome to Lumos
17 |
18 |
About Lumos
19 | Lumos is an open-source offering that provides a solution by merging
20 | analytics with polished UI integrations to improve end-users
21 | interaction with Lambda metrics — resulting in a user-friendly
22 | experience.
23 |
24 |
25 |
59 |
60 |
61 |
62 |
63 | >
64 | );
65 | }
66 |
--------------------------------------------------------------------------------
/client/components/Login.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React, { useState, setOpen, useContext } from "react";
3 | import { Button, TextField, Modal, Slide } from "@mui/material";
4 | import { InfoContext } from "../containers/MainContainer.jsx";
5 | // import ModalUnstyled from '@mui/base/ModalUnstyled'
6 |
7 | export default function Login() {
8 | //modal state
9 | const [open, setOpen] = useState(false);
10 | const handleOpen = () => setOpen(true);
11 | const handleClose = () => setOpen(false);
12 | //container info
13 | const [userInfo, setUserInfo] = useContext(InfoContext);
14 | //register user state
15 | const [data, setData] = useState({
16 | email: "",
17 | password: "",
18 | firstname: "",
19 | lastname: "",
20 | });
21 |
22 | //register user handle change event
23 | const handleChange = (e) => {
24 | setData({ ...data, [e.target.name]: e.target.value });
25 | };
26 |
27 | //register user on submit
28 | const submitHandler = (e) => {
29 | e.preventDefault();
30 | const { email, password, firstname, lastname } = data;
31 | if (
32 | email === "" ||
33 | password === "" ||
34 | firstname === "" ||
35 | lastname === ""
36 | ) {
37 | setData({
38 | email: "",
39 | password: "",
40 | firstname: "",
41 | lastname: "",
42 | });
43 | handleClose();
44 | window.alert("Cannot process request. All fields need to be completed");
45 | }
46 | if (
47 | email !== "" &&
48 | password !== "" &&
49 | firstname !== "" &&
50 | lastname !== ""
51 | ) {
52 | console.log("this is what you're getting back: ", data);
53 | window.alert(
54 | `Thanks for registering ${data.firstname}. Click anywhere to close.`
55 | );
56 |
57 | const result = {
58 | email: email,
59 | password: password,
60 | firstname: firstname,
61 | lastname: lastname,
62 | };
63 |
64 | // post to user endPoint passing result to DB
65 | fetch("/user/register", {
66 | method: "POST",
67 | headers: {
68 | "Content-Type": "application/json",
69 | },
70 | body: JSON.stringify(result),
71 | }).then((h) => console.log("added", { h }));
72 | // */
73 |
74 | handleClose();
75 | }
76 | };
77 | // onclick event makes a post request for register
78 | return (
79 | <>
80 |
81 | {" "}
82 | Register{" "}
83 |
84 |
85 |
93 |
136 |
137 | >
138 | );
139 | }
140 |
--------------------------------------------------------------------------------
/client/components/Metrics.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from 'react';
3 | import { Box } from '@mui/material';
4 | import MetricCard from '../components/Cards/MetricCard.jsx'
5 |
6 | function Metrics() {
7 | return (
8 |
9 |
10 |
11 | );
12 | }
13 |
14 | export default Metrics;
--------------------------------------------------------------------------------
/client/components/Nav.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React, { useContext } from "react";
3 | import { Box } from "@mui/material";
4 | import Login from "../components/Login.jsx";
5 | import Sign from "../components/SignIn.jsx";
6 | import Signout from "../components/Signout.jsx";
7 | import { InfoContext } from "../containers/MainContainer.jsx";
8 |
9 | function Nav() {
10 | const [userInfo, setUserInfo] = useContext(InfoContext);
11 | return (
12 |
13 |
14 |
15 |
19 |
20 |
21 | {/*
22 |
23 | */}
24 | {userInfo.loggedIn === false ? (
25 | <>
26 |
27 |
28 |
29 |
30 | >
31 | ) : (
32 | <>
33 |
34 |
35 |
36 | Welcome to Lumos {userInfo.first_name} {userInfo.last_name}
37 |
38 |
39 |
40 |
41 |
42 |
43 | >
44 | )}
45 |
46 | );
47 | }
48 |
49 | export default Nav;
50 |
--------------------------------------------------------------------------------
/client/components/Sidebar.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from "react";
3 | import { Box, Button, Drawer, Typography } from "@mui/material";
4 |
5 | // sidebar buttons
6 | import DayButton from "./dayButton.jsx";
7 | import WeekButton from "./weekButton.jsx";
8 | import MonthButton from "./monthButton.jsx";
9 |
10 | // lottie icons
11 | import Lottie from "lottie-react";
12 | import dashboardIcon from "../assets/lotties/dashboardIcon.json";
13 | import alertIcon from "../assets/lotties/alertIcon.json";
14 | import logsIcon from "../assets/lotties/logsIcon.json";
15 |
16 | function Sidebar() {
17 | return (
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | export default Sidebar;
34 |
--------------------------------------------------------------------------------
/client/components/SignIn.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React, { useState, setOpen, useContext, useEffect } from "react";
3 | import { Button, TextField, Modal } from "@mui/material";
4 | import { InfoContext } from "../containers/MainContainer.jsx";
5 |
6 | export default function Sign() {
7 | //use effect hook on successful login get all metrics
8 | const [userInfo, setUserInfo] = useContext(InfoContext);
9 | const [metrics, setMetrics] = useState([]);
10 |
11 | //modal state
12 | const [open, setOpen] = useState(false);
13 | const handleOpen = () => setOpen(true);
14 | const handleClose = () => setOpen(false);
15 |
16 | //register user state
17 | const [data, setData] = useState({
18 | email: "",
19 | password: "",
20 | });
21 |
22 | //register user handle change event
23 | const handleChange = (e) => {
24 | setData({ ...data, [e.target.name]: e.target.value });
25 | };
26 |
27 | // onclick event make a post request for login
28 | const submitHandler = (e) => {
29 | e.preventDefault();
30 |
31 | //make a post request to somewhere with this data
32 | const { email, password } = data;
33 |
34 | const result = {
35 | email: email,
36 | password: password,
37 | };
38 |
39 | fetch("/user/login", {
40 | method: "POST",
41 | headers: {
42 | "Content-Type": "application/json",
43 | },
44 | body: JSON.stringify(result),
45 | })
46 | .then((response) => response.json())
47 | .then((loginData) => {
48 | const { verifiedUser, firstName, lastName } = loginData;
49 |
50 | if (verifiedUser == true) {
51 | window.alert(`You're signed in ${data.email}`);
52 | //set flag to true
53 | setUserInfo({
54 | timePeriod: "day",
55 | loggedIn: true,
56 | user_name: "",
57 | first_name: firstName,
58 | last_name: lastName,
59 | user_id: "",
60 | lambdaFuncs: [
61 | {
62 | funcName: "",
63 | totalInvocations: 0,
64 | totalErrors: 0,
65 | timeStamps: [],
66 | funcValues: [],
67 | },
68 | ],
69 | lambdaActiveInvocations: 0,
70 | lambdaTotalErrors: 0,
71 | lambdaTotalCost: 0,
72 | lambdaAvgDuration: 0,
73 | });
74 | } else {
75 | window.alert(
76 | "Email is not registered or the password is incorrect, please try again."
77 | );
78 | }
79 | })
80 | .catch((err) => console.log(err));
81 |
82 | handleClose();
83 | };
84 |
85 | return (
86 | <>
87 |
92 | {" "}
93 | Login{" "}
94 |
95 |
103 |
128 |
129 | >
130 | );
131 | }
132 |
--------------------------------------------------------------------------------
/client/components/Signout.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState, setOpen, useContext, useEffect } from "react";
2 | import { Button, TextField, Modal } from "@mui/material";
3 | import { InfoContext } from "../containers/MainContainer.jsx";
4 |
5 | export default function Signout() {
6 | const [userInfo, setUserInfo] = useContext(InfoContext);
7 | const [metrics, setMetrics] = useState([]);
8 |
9 | //register user state
10 | const [data, setData] = useState({
11 | email: "",
12 | password: "",
13 | });
14 |
15 | //register user handle change event
16 |
17 | // onclick event make a post request for login
18 | const submitHandler = () => {
19 | setData({
20 | email: "",
21 | password: "",
22 | });
23 | localStorage.clear();
24 | window.location.reload();
25 | };
26 | return (
27 |
32 | Signout
33 |
34 | );
35 | }
36 |
--------------------------------------------------------------------------------
/client/components/Usage.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from "react";
3 | import { Box } from "@mui/material";
4 | import UsageDoughnut from "../components/charts/UsageDoughnut.jsx";
5 |
6 | function Usage() {
7 | return (
8 |
9 |
10 |
11 |
12 |
13 | );
14 | }
15 |
16 | export default Usage;
17 |
--------------------------------------------------------------------------------
/client/components/charts/Latency.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import { InfoContext } from "../../containers/MainContainer.jsx";
3 |
4 | import { Paper, Box, Typography } from "@mui/material";
5 | import {
6 | Chart as ChartJS,
7 | CategoryScale,
8 | LinearScale,
9 | PointElement,
10 | LineElement,
11 | Title,
12 | Tooltip,
13 | Legend,
14 | } from "chart.js";
15 | import { Line } from "react-chartjs-2";
16 | // import moment from "moment";
17 |
18 | ChartJS.register(
19 | CategoryScale,
20 | LinearScale,
21 | PointElement,
22 | LineElement,
23 | Title,
24 | Tooltip,
25 | Legend
26 | );
27 |
28 | const options = {
29 | responsive: true,
30 | maintainAspectRatio: false,
31 | plugins: {
32 | legend: {
33 | position: "top",
34 | },
35 | },
36 | // scales: {
37 | // y: {
38 | // type: "time",
39 | // time: {
40 | // displayFormats: {
41 | // //
42 | // },
43 | // },
44 | // },
45 | // x: {
46 | // type: "time",
47 | // time: {
48 | // displayFormats: {
49 | // //
50 | // },
51 | // },
52 | // },
53 | // },
54 | // },
55 | };
56 | // x-axis, this needs to be updated depending on the .length of the timestamps array
57 |
58 | // this all gets done AFTER state object has been populated from Login's successful verification
59 | // we'll need 3 buttons created at the Header for 24 Hour / 7 Day / 30 Day time frames that each make fetch calls with different start/end dates
60 |
61 |
62 | function Latency() {
63 | const [userInfo, setUserInfo] = useContext(InfoContext);
64 |
65 | console.log(userInfo);
66 |
67 | const labels = [
68 | // "8/5", // 50 invocations
69 | // "8/6",
70 | // "8/7",
71 | // "8/8",
72 | // "8/9",
73 | // "8/10",
74 | // "8/11"
75 | ];
76 |
77 | //lambdaMetrics: [], // { invocations: "",
78 |
79 | const data = {
80 | labels,
81 | datasets: [
82 | // new datasets object needs to be created depending on how many Lambda funcs we get back
83 | // {
84 | // // update this name to lambda name
85 | // label: userInfo.lambdaFuncs[1].funcName,
86 | // // we gotta update array of values here
87 | // data: userInfo.lambdaFuncs[1].funcValues,
88 | // // let's change these weird ass colors xD okay;) ( ͡° ͜ʖ ͡°)
89 | // borderColor: "rgba(123, 31, 162)",
90 | // backgroundColor: "rgba(123, 31, 162, 0.5)",
91 | // borderWidth: 1,
92 | // },
93 | // {
94 | // label: "us_east_func_multiply",
95 | // // ( ͡° ͜ʖ ͡°)
96 | // data: [1, 2, 4, 9, 4, 1, 5, 6, 4, 9, 5, 4, 1, 9],
97 | // borderColor: "rgb(255, 125, 69)",
98 | // backgroundColor: "rgb(255, 125, 69, 0.5)",
99 | // borderWidth: 1,
100 | // },
101 | ],
102 | };
103 |
104 | let testLabels = [];
105 |
106 | if (userInfo.timePeriod === "month") {
107 | const date = new Date();
108 | const year = date.getFullYear();
109 | const month = date.getMonth() + 1;
110 | const lastDay = new Date(year, month, 0).getDate();
111 |
112 | for (let i = 1; i <= lastDay; i += 1) {
113 | testLabels.push(`${month}/${i}`);
114 | }
115 |
116 | data.labels = testLabels;
117 | }
118 |
119 | userInfo.lambdaFuncs.forEach((el) => {
120 | const borderColor = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
121 | Math.random() * 256
122 | )}, ${Math.floor(Math.random() * 256)})`;
123 | const backgroundColor = `rgba(${borderColor.slice(
124 | 4,
125 | borderColor.length - 1
126 | )}, 0.5)`;
127 |
128 | let resultArr = [];
129 | for (let x in Object.keys(data.labels)) {
130 | const keys = data.labels[x];
131 | for (let y = 0; y < el.formattedTimeStamps.length; y++) {
132 | const time = el.formattedTimeStamps[y];
133 | console.log(
134 | el.funcName,
135 | "looking @ timestamp",
136 | time,
137 | keys,
138 | "match ?",
139 | time === keys
140 | );
141 |
142 | if (time === keys) {
143 | resultArr.push(10);
144 | }
145 | if (time !== keys) {
146 | resultArr.push(-1);
147 | }
148 | }
149 | }
150 |
151 | data.datasets.push({
152 | label: el.funcName,
153 | data: [el.totalInvocations],
154 | borderColor: borderColor,
155 | backgroundColor: backgroundColor,
156 | borderWidth: 1,
157 | spanGaps: true,
158 | });
159 | });
160 |
161 | useEffect(() => {}, [userInfo]);
162 |
163 | return ;
164 | }
165 |
166 | export default Latency;
167 |
--------------------------------------------------------------------------------
/client/components/charts/TestChart.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import { InfoContext } from "../../containers/MainContainer.jsx";
3 | import {
4 | Chart as ChartJS,
5 | CategoryScale,
6 | LinearScale,
7 | PointElement,
8 | LineElement,
9 | Title,
10 | Tooltip,
11 | Legend,
12 | TimeScale,
13 | } from "chart.js";
14 | import { Line } from "react-chartjs-2";
15 | import "chartjs-adapter-date-fns";
16 |
17 | ChartJS.register(
18 | CategoryScale,
19 | LinearScale,
20 | PointElement,
21 | LineElement,
22 | Title,
23 | Tooltip,
24 | Legend,
25 | TimeScale
26 | );
27 |
28 | function TestChart() {
29 | const [userInfo] = useContext(InfoContext);
30 | useEffect(() => {}, [userInfo]);
31 |
32 | const options = {
33 | responsive: true,
34 | maintainAspectRatio: false,
35 | plugins: {
36 | legend: {
37 | position: "top",
38 | },
39 | },
40 | scales: {
41 | yAxes: {
42 | beginAtZero: true,
43 | },
44 | },
45 | };
46 |
47 | const date = new Date();
48 | const year = date.getFullYear();
49 | const month = date.getMonth();
50 | const lastDay = new Date(year, month, 0).getDate();
51 |
52 | const getLabels = () => {
53 | console.log("labels inside getLabels: ", labels);
54 | if (userInfo.timePeriod === "month") {
55 | return Array.from(Array(lastDay)).map((_, i) =>
56 | new Date(year, month, (i += 1)).toLocaleDateString()
57 | );
58 | }
59 | if (userInfo.timePeriod === "week") {
60 | const lastWeek = new Date(year, month, date.getDate());
61 | const lastWeekArr = [];
62 | for (let i = 0; i < 7; i += 1) {
63 | const lastWeekDate = lastWeek.getDate();
64 | lastWeekArr.push(
65 | new Date(year, month, lastWeekDate - i).toLocaleDateString()
66 | );
67 | }
68 | return lastWeekArr.reverse();
69 | }
70 | if (userInfo.timePeriod === "day") {
71 | const dayArr = [];
72 |
73 | for (let i = 0; i < 24; i++) {
74 | const date = new Date() - i * 3600 * 1000;
75 | dayArr.push(
76 | new Date(date).toLocaleTimeString([], {
77 | hour: "2-digit",
78 | })
79 | );
80 | }
81 |
82 | return dayArr.reverse();
83 | }
84 | };
85 |
86 | const labels = getLabels();
87 | const datasets = [];
88 |
89 | for (let i = 0; i < userInfo.lambdaFuncs.length; i += 1) {
90 | const func = userInfo.lambdaFuncs[i];
91 | const data = [];
92 |
93 | for (let i = 0; i < labels.length; i += 1) {
94 | const time = labels[i];
95 |
96 | if (userInfo.timePeriod !== "day") {
97 | if (func.formattedTimeStamps.includes(time)) {
98 | let sum = 0;
99 |
100 | for (let i = 0; i < func.formattedTimeStamps.length; i++) {
101 | if (func.formattedTimeStamps[i] === time) {
102 | sum += func.invocationsArray[i];
103 | }
104 | }
105 |
106 | data.push({ x: time, y: sum });
107 | } else {
108 | data.push({ x: time, y: 0 });
109 | }
110 | } else {
111 | if (func.formattedTime && func.formattedTime.includes(time)) {
112 | let sum = 0;
113 |
114 | for (let i = 0; i < func.formattedTime.length; i++) {
115 | if (func.formattedTime[i] === time) {
116 | sum += func.invocationsArray[i];
117 | }
118 | }
119 |
120 | data.push({ x: time, y: sum });
121 | } else {
122 | data.push({ x: time, y: 0 });
123 | }
124 | }
125 | }
126 |
127 | const borderColor = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
128 | Math.random() * 256
129 | )}, ${Math.floor(Math.random() * 256)})`;
130 | const backgroundColor = `rgba(${borderColor.slice(
131 | 4,
132 | borderColor.length - 1
133 | )}, 0.5)`;
134 |
135 | datasets.push({
136 | label: func.funcName,
137 | data: data,
138 | borderColor: borderColor,
139 | backgroundColor: backgroundColor,
140 | spanGaps: true,
141 | tension: 0.5,
142 | });
143 | }
144 |
145 | console.log("datasets: ", datasets);
146 | const data = {
147 | labels: labels,
148 | datasets: datasets,
149 | };
150 |
151 | return ;
152 | }
153 |
154 | export default TestChart;
155 |
--------------------------------------------------------------------------------
/client/components/charts/UsageDoughnut.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
3 | import { Doughnut } from "react-chartjs-2";
4 | import { InfoContext } from "../../containers/MainContainer.jsx";
5 |
6 | ChartJS.register(ArcElement, Tooltip, Legend);
7 |
8 | function UsageDoughnut() {
9 | const [userInfo, setUserInfo] = useContext(InfoContext);
10 |
11 | let funcNames = [];
12 | let funcInvocations = [];
13 |
14 | userInfo.lambdaFuncs.forEach((el) => {
15 | funcNames.push(el.funcName);
16 | funcInvocations.push(el.totalInvocations);
17 | });
18 |
19 | const borderColor1 = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
20 | Math.random() * 256
21 | )}, ${Math.floor(Math.random() * 256)})`;
22 |
23 | const borderColor2 = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
24 | Math.random() * 256
25 | )}, ${Math.floor(Math.random() * 256)})`;
26 |
27 | const borderColor3 = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
28 | Math.random() * 256
29 | )}, ${Math.floor(Math.random() * 256)})`;
30 |
31 | const borderColor4 = `rgb(${Math.floor(Math.random() * 256)}, ${Math.floor(
32 | Math.random() * 256
33 | )}, ${Math.floor(Math.random() * 256)})`;
34 |
35 | const data = {
36 | labels: funcNames,
37 | datasets: [
38 | {
39 | label: "# of Invocations",
40 | data: funcInvocations,
41 | backgroundColor: [
42 | borderColor1,
43 | borderColor2,
44 | borderColor3,
45 | borderColor4,
46 | ],
47 | borderColor: [borderColor1, borderColor2, borderColor3, borderColor4],
48 | borderWidth: 1,
49 | },
50 | ],
51 | };
52 |
53 | const options = {
54 | responsive: true,
55 | maintainAspectRatio: false,
56 | plugins: {
57 | legend: {
58 | position: "top",
59 | },
60 | },
61 | };
62 |
63 | return ;
64 | }
65 |
66 | export default UsageDoughnut;
67 |
--------------------------------------------------------------------------------
/client/components/dateButton.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import {FormControl, Button} from '@mui/material'
3 |
4 | export default function dateButton() {
5 | //needs an onchange handler for whatever button state we are looking at
6 | //form control is all about handling state I'm not sure if it's needed for the button.
7 |
8 | return (
9 |
10 |
11 |
12 |
13 | )
14 | }
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/client/components/dayButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext, useEffect } from "react";
2 | import { Button } from "@mui/material";
3 | // gain access to context
4 | import { InfoContext } from "../containers/MainContainer.jsx";
5 |
6 | // lottie icons
7 | import Lottie from "lottie-react";
8 | import dashboardIcon from "../assets/lotties/dashboardIcon.json";
9 |
10 | export default function DayButton() {
11 | const [userInfo, setUserInfo] = useContext(InfoContext);
12 |
13 | const submitHandler = (e) => {
14 | e.preventDefault();
15 | let start = new Date(Math.round(new Date().getTime() - 24 * 3600 * 1000));
16 | start.toISOString();
17 | const end = new Date();
18 |
19 | fetch("/metric", {
20 | method: "POST",
21 | headers: { "Content-Type": "application/json" },
22 | body: JSON.stringify({
23 | startTime: start,
24 | endTime: end,
25 | period: 60,
26 | }),
27 | })
28 | .then((response) => response.json())
29 | .then((data) => {
30 | console.log("RESPONSE DATA: ", data);
31 | console.log("RESPONSE DATA TYPE: ", typeof data);
32 | // update state obj with data values
33 | let activeInvocations = 0;
34 | let totalErrors = 0;
35 | let totalDuration = 0;
36 | let totalCost = 0;
37 | data.metrics.forEach((el) => {
38 | activeInvocations += el.totalInvocations;
39 | totalErrors += el.totalErrors;
40 | totalDuration += el.totalDuration;
41 | totalCost += el.totalCost;
42 | });
43 |
44 | setUserInfo({
45 | loggedIn: true,
46 | timePeriod: "day",
47 | lambdaFuncs: data.metrics,
48 | lambdaActiveInvocations: activeInvocations,
49 | lambdaTotalErrors: totalErrors,
50 | lambdaAvgThrottle: 41,
51 | lambdaTotalCost: totalCost,
52 | lambdaAvgDuration: totalDuration,
53 | });
54 | });
55 | return false;
56 | };
57 | return (
58 | <>
59 |
60 |
61 |
66 |
67 |
Day
68 |
69 | >
70 | );
71 | }
72 |
--------------------------------------------------------------------------------
/client/components/fetchMetrics.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from 'react';
3 |
4 | export default function GetAllMetrics() {
5 | return fetch('/metrics')
6 | .then(data => data.json())
7 | }
8 |
--------------------------------------------------------------------------------
/client/components/monthButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Button } from "@mui/material";
3 | import { InfoContext } from "../containers/MainContainer.jsx";
4 |
5 | // lottie icons
6 | import Lottie from "lottie-react";
7 | import dashboardIcon from "../assets/lotties/dashboardIcon.json";
8 |
9 | export default function MonthButton() {
10 | const [userInfo, setUserInfo] = useContext(InfoContext);
11 | //run a fetch request to metric router time period 24hr
12 | let start = new Date(
13 | Math.round(new Date().getTime()) - 24 * 30 * 3600 * 1000
14 | );
15 | start.toISOString();
16 | const end = new Date();
17 | //if userInfo.loggedIn !== return 'you must be signed in' else submithandler
18 | const submitHandler = (e) => {
19 | e.preventDefault();
20 | fetch("/metric", {
21 | method: "POST",
22 | headers: { "Content-Type": "application/json" },
23 | body: JSON.stringify({ startTime: start, endTime: end }),
24 | })
25 | .then((response) => response.json())
26 | .then((data) => {
27 | // update state obj with data values
28 | let activeInvocations = 0;
29 | let totalErrors = 0;
30 | let totalDuration = 0;
31 | let totalCost = 0;
32 | data.metrics.forEach((el) => {
33 | activeInvocations += el.totalInvocations;
34 | totalErrors += el.totalErrors;
35 | totalDuration += el.totalDuration;
36 | totalCost += el.totalCost;
37 | });
38 |
39 | setUserInfo({
40 | loggedIn: true,
41 | timePeriod: "month",
42 | lambdaFuncs: data.metrics,
43 | lambdaActiveInvocations: activeInvocations,
44 | lambdaTotalErrors: totalErrors,
45 | lambdaAvgThrottle: 41,
46 | lambdaTotalCost: totalCost,
47 | lambdaAvgDuration: totalDuration,
48 | });
49 | });
50 | };
51 | return (
52 |
53 |
54 |
59 |
60 |
Month
61 |
62 | );
63 | }
64 |
--------------------------------------------------------------------------------
/client/components/weekButton.jsx:
--------------------------------------------------------------------------------
1 | import React, { useContext } from "react";
2 | import { Button } from "@mui/material";
3 | import { InfoContext } from "../containers/MainContainer.jsx";
4 |
5 | // lottie icons
6 | import Lottie from "lottie-react";
7 | import dashboardIcon from "../assets/lotties/dashboardIcon.json";
8 |
9 | export default function WeekButton() {
10 | const [userInfo, setUserInfo] = useContext(InfoContext);
11 |
12 | //run a fetch request to metric router time period 24hr
13 | let start = new Date(Math.round(new Date().getTime()) - 24 * 7 * 3600 * 1000);
14 | start.toISOString();
15 | const end = new Date();
16 | const submitHandler = (e) => {
17 | e.preventDefault();
18 | // fetch to metric endpoint
19 | fetch("/metric", {
20 | method: "POST",
21 | headers: { "Content-Type": "application/json" },
22 | body: JSON.stringify({ startTime: start, endTime: end, period: 86400 }),
23 | })
24 | .then((response) => response.json())
25 | .then((data) => {
26 | let activeInvocations = 0;
27 | let totalErrors = 0;
28 | let totalDuration = 0;
29 | let totalCost = 0;
30 | data.metrics.forEach((el) => {
31 | activeInvocations += el.totalInvocations;
32 | totalErrors += el.totalErrors;
33 | totalDuration += el.totalDuration;
34 | totalCost += el.totalCost;
35 | });
36 |
37 | setUserInfo({
38 | loggedIn: true,
39 | timePeriod: "week",
40 | lambdaFuncs: data.metrics,
41 | lambdaActiveInvocations: activeInvocations,
42 | lambdaTotalErrors: totalErrors,
43 | lambdaAvgThrottle: 41,
44 | lambdaTotalCost: totalCost,
45 | lambdaAvgDuration: totalDuration,
46 | });
47 | });
48 | };
49 | return (
50 |
51 |
52 |
57 |
58 |
Week
59 |
60 | );
61 | }
62 |
--------------------------------------------------------------------------------
/client/containers/MainContainer.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React, { useState, useEffect, createContext, useContext } from "react";
3 | import { Grid, Box } from "@mui/material";
4 | import Header from "../components/Header.jsx";
5 | import Footer from "../components/Footer.jsx";
6 | import Nav from "../components/Nav.jsx";
7 | import Sidebar from "../components/Sidebar.jsx";
8 | import Metrics from "../components/Metrics.jsx";
9 | // import MetricCard from '../components/Cards/MetricCard.jsx';
10 | import Usage from "../components/Usage.jsx";
11 | import Charts from "../components/Charts.jsx";
12 | import Errors from "../components/Errors.jsx";
13 | import Landing from "../components/LandingPage.jsx";
14 | // import Card from '@mui/material';
15 | import "../assets/styles.css";
16 | // import '../assets/outerContainer.css';
17 |
18 | import TestChart from "../components/charts/TestChart.jsx";
19 |
20 | export const InfoContext = createContext();
21 |
22 | function MainContainer() {
23 | // const [userInfo, setUserInfo] = useContext(InfoContext);
24 |
25 | const [userInfo, setUserInfo] = useState({
26 | loggedIn: false,
27 | user_name: "",
28 | first_name: "",
29 | last_name: "",
30 | user_id: "",
31 | timePeriod: "day",
32 | lambdaFuncs: [
33 | {
34 | funcName: "",
35 | totalInvocations: 0,
36 | totalErrors: 0,
37 | timeStamps: [],
38 | funcValues: [],
39 | invocationsArray: [],
40 | formattedTimeStamps: [],
41 | formattedTime: [],
42 | },
43 | ],
44 | lambdaActiveInvocations: 0,
45 | lambdaTotalErrors: 0,
46 | lambdaAvgDuration: 0,
47 | lambdaTotalCost: 0,
48 | });
49 |
50 | useEffect(() => console.log("Current State", userInfo), [userInfo]);
51 |
52 | return (
53 | //implement grid template outer container
54 |
55 | {userInfo.loggedIn === false ? (
56 | <>
57 |
58 |
59 |
60 |
61 |
62 | >
63 | ) : (
64 | <>
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | >
75 | )}
76 |
77 | );
78 | }
79 |
80 | export default MainContainer;
81 |
--------------------------------------------------------------------------------
/client/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | Lumos Visualizer
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/client/index.jsx:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | import React from "react";
3 | import { render } from "react-dom";
4 | import { BrowserRouter, Route, Routes } from "react-router-dom";
5 | import { createTheme, ThemeProvider } from "@mui/material";
6 | import App from "./App.jsx";
7 |
8 | import TestChart from "./components/charts/TestChart.jsx";
9 |
10 | const theme = createTheme({
11 | palette: {
12 | primary: {
13 | main: "#7068F4",
14 | light: "#ba68c8",
15 | dark: "#7b1fa2",
16 | },
17 | secondary: {
18 | main: "#ff64b4",
19 | light: "#f06292",
20 | dark: "#c2185b",
21 | },
22 | },
23 | typography: {
24 | fontFamily: "Quicksand",
25 | },
26 | });
27 |
28 | render(
29 |
30 |
31 |
32 | } />
33 |
34 |
35 | ,
36 | document.getElementById("root")
37 | );
38 |
--------------------------------------------------------------------------------
/gitFlow.md:
--------------------------------------------------------------------------------
1 | # AWS-Lambda-Visualizer
2 |
3 | Visual Guild Linked Here
4 | ## Creating a New Branch
5 | 1. Clone repo to your local system.
6 | - `git clone
7 |
8 | 2. Create or go to `dev` branch
9 | - `git checkout dev`
10 |
11 |
12 | 3. Create or go to your branch
13 | - `git checkout -b [your-name/feature]`
14 |
15 | 4. Check what branch you are currently on
16 | - `git branch`
17 |
18 |
19 | ## Starting Workflow
20 | 1. Make sure local branch is up to date with `dev` branch before working
21 | - `git checkout dev` (locally switch to dev branch)
22 | - `git pull origin dev` (pull updates of dev down to your localc system)
23 | - `git checkout ` (switch back to your branch locally)
24 | - `git merge dev` (brings dev int your branch locally)
25 | - Resolve conflicts or `:q` if there aren't any
26 |
27 | 2. Create or go to your branch. PRO TIP: Use tab to autofill
28 | - `git checkout -b [your-name/feature]`
29 |
30 |
31 | 3. Use `git branch` to make sure you are on the correct branch
32 |
33 |
34 | ## Saving and Uploading Code
35 | 1. Before pushing code to GitHub. Merge any updates from `dev`. Solve any conflicts (aka differences) between `dev` and your local branch
36 | - `git checkout dev` (locally switch to dev branch)
37 | - `git pull origin dev` (pull updates of dev down to your localc system)
38 | - `git checkout ` (switch back to your branch locally)
39 | - `git merge dev` (brings dev int your branch locally)
40 | - Resolve conflicts or `:q` if there aren't any
41 |
42 | 2. Git add & git commit your files. MAKE SURE YOU ARE ON YOUR BRANCH BEFORE COMMITING! (use `git branch` to check)
43 | - `git add `
44 | - `git commit -m"`
45 |
46 | 3. Push files to your branch
47 | - `git push origin `
48 |
49 | 4. Create a pull request on GitHub
50 | - base: dev <-- compare: [YOUR BRANCH NAME]
51 | - add comments
52 | - add reviewers (on the right sidebar)
53 | - click create pull request
54 |
55 |
56 | 5. Review, Approve and Merge pull request to `dev` branch
57 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "aws-lambda-visualizer",
3 | "version": "1.0.0",
4 | "description": "Visual Guild Linked Here ## Creating a New Branch 1. Clone repo to your local system. - `git clone",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1",
8 | "dev": "NODE_ENV=development nodemon server/server.js & NODE_ENV=development webpack serve",
9 | "windows-dev": "concurrently \"NODE_ENV=development node server/server.js\" \"NODE_ENV=development webpack serve --open\"",
10 | "win-dev2": "concurrently \"cross-env NODE_ENV=development nodemon server/server.js\" \"cross-env NODE_ENV=development webpack serve --open\"",
11 | "build": "NODE_ENV=production webpack",
12 | "start": "nodemon server/server.js",
13 | "install-client": "npm install",
14 | "heroku-postbuild": "npm run install-client && npm run build"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/oslabs-beta/AWS-Lambda-Visualizer.git"
19 | },
20 | "keywords": [],
21 | "author": "",
22 | "license": "ISC",
23 | "bugs": {
24 | "url": "https://github.com/oslabs-beta/AWS-Lambda-Visualizer/issues"
25 | },
26 | "homepage": "https://github.com/oslabs-beta/AWS-Lambda-Visualizer#readme",
27 | "devDependencies": {
28 | "@babel/core": "^7.18.10",
29 | "@babel/preset-env": "^7.18.10",
30 | "@babel/preset-react": "^7.18.6",
31 | "eslint": "^8.21.0",
32 | "eslint-config-airbnb": "^19.0.4",
33 | "eslint-plugin-import": "^2.26.0",
34 | "eslint-plugin-jsx-a11y": "^6.6.1",
35 | "eslint-plugin-react": "^7.30.1",
36 | "eslint-plugin-react-hooks": "^4.6.0",
37 | "file-loader": "^6.2.0",
38 | "html-webpack-plugin": "^5.5.0",
39 | "install": "^0.13.0",
40 | "nodemon": "^2.0.19",
41 | "npm": "^8.15.1",
42 | "url-loader": "^4.1.1",
43 | "webpack": "^5.74.0",
44 | "webpack-cli": "^4.10.0",
45 | "webpack-dev-server": "^4.9.3"
46 | },
47 | "dependencies": {
48 | "@aws-amplify/cli": "^9.2.1",
49 | "@aws-sdk/client-cloudwatch": "^3.142.0",
50 | "@aws-sdk/client-ec2": "^3.147.0",
51 | "@aws-sdk/client-iam": "^3.142.0",
52 | "@aws-sdk/client-lambda": "^3.145.0",
53 | "@aws-sdk/client-rds": "^3.156.0",
54 | "@emotion/react": "^11.10.0",
55 | "@emotion/styled": "^11.10.0",
56 | "@mui/material": "^5.9.3",
57 | "babel-loader": "^8.2.5",
58 | "bcryptjs": "^2.4.3",
59 | "chart.js": "^3.9.0",
60 | "chartjs-adapter-date-fns": "^2.0.0",
61 | "concurrently": "^7.3.0",
62 | "cookie-parser": "^1.4.6",
63 | "cross-env": "^7.0.3",
64 | "css-loader": "^6.7.1",
65 | "date-fns": "^2.29.2",
66 | "dayjs": "^1.11.5",
67 | "dotenv": "^16.0.1",
68 | "express": "^4.18.1",
69 | "jsonwebtoken": "^8.5.1",
70 | "lottie-react": "^2.3.1",
71 | "mui": "^0.0.1",
72 | "mysql": "^2.18.1",
73 | "path": "^0.12.7",
74 | "pg": "^8.7.3",
75 | "react": "^18.2.0",
76 | "react-chartjs-2": "^4.3.1",
77 | "react-dom": "^18.2.0",
78 | "react-icons": "^4.4.0",
79 | "react-router": "^6.3.0",
80 | "react-router-dom": "^6.3.0",
81 | "sass-loader": "^13.0.2",
82 | "style-loader": "^3.3.1"
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/server/aws/cloudFormation.js:
--------------------------------------------------------------------------------
1 | const {
2 | CloudFormationClient,
3 | CreateStackCommand,
4 | } = require("@aws-sdk/client-cloudformation");
5 |
6 | const createStack = async () => {
7 | const client = await new CloudFormationClient({ region: "us-east-1" });
8 |
9 | const config = {
10 | StackName: "LUMOSSTACK",
11 | Capabilities: ["CAPABILITY_IAM"],
12 | TemplateBody: `
13 | Resources:
14 | LumosUser:
15 | Type: AWS::IAM::User
16 | Properties:
17 | ManagedPolicyArns:
18 | - arn:aws:iam::aws:policy/CloudWatchFullAccess
19 | - arn:aws:iam::aws:policy/AWSLambda_FullAccessa
20 | Outputs:
21 | outputArn:
22 | Description: ARN
23 | Value: !GetAtt LumosUser.Arn
24 | `,
25 | };
26 |
27 | const command = await new CreateStackCommand(config);
28 |
29 | const response = await client.send(command);
30 |
31 | return response;
32 | };
33 |
34 | createStack().then((response) => console.log(response))
35 |
36 | module.exports = createStack;
37 |
--------------------------------------------------------------------------------
/server/aws/cloudWatch.js:
--------------------------------------------------------------------------------
1 | const {
2 | ListMetricsCommand,
3 | GetMetricDataCommand,
4 | GetMetricStatisticsCommand,
5 | CloudWatchClient,
6 | } = require("@aws-sdk/client-cloudwatch");
7 |
8 | const {
9 | CloudWatchLogsClient,
10 | CreateLogStreamCommand,
11 | GetLogEventsCommand,
12 | DescribeLogGroupsCommand,
13 | DescribeLogStreamsCommand,
14 | } = require("@aws-sdk/client-cloudwatch-logs");
15 |
16 |
17 | const getMetric = () => {
18 |
19 | const client = await new CloudWatchClient({region: "us-east-1"});
20 |
21 | const data = await client.send(
22 | new GetMetricDataCommand({
23 | Namespace: "AWS/Lambda",
24 | MetricName: "Errors",
25 | StartTime: new Date("August 6, 2022 13:30:30"),
26 | EndTime: new Date("August 6, 2022 16:00:00"),
27 | MetricDataQueries: [
28 | {
29 | Id: "test",
30 | MetricStat: {
31 | Metric: {
32 | MetricName: "Errors",
33 | Namespace: "AWS/Lambda",
34 | },
35 | Period: 60,
36 | Stat: "Maximum",
37 | },
38 | },
39 | ],
40 | })
41 | );
42 |
43 | console.log(data.MetricDataResults[0]);
44 | }
45 |
46 | getCloudwatchData();
47 |
--------------------------------------------------------------------------------
/server/aws/getEC2.js:
--------------------------------------------------------------------------------
1 | const { EC2Client, DescribeInstancesCommand } = require('@aws-sdk/client-ec2');
2 |
3 | const getEC2 = async () => {
4 | const client = new EC2Client({ region: 'us-east-1' });
5 | const command = new DescribeInstancesCommand({ Filters: [] });
6 | const response = await client.send(command);
7 |
8 | const instances = response.Reservations.map((reservation) => ({
9 | ImageId: reservation.Instances[0].ImageId,
10 | InstanceType: reservation.Instances[0].InstanceType,
11 | LaunchTime: reservation.Instances[0].LaunchTime,
12 | PublicDnsNAme: reservation.Instances[0].PublicDnsName,
13 | State: reservation.Instances[0].State.Name,
14 | Architecture: reservation.Instances[0].Architecture,
15 | }));
16 |
17 | console.log(instances);
18 | return instances;
19 | };
20 |
21 | module.exports = getEC2;
22 |
--------------------------------------------------------------------------------
/server/aws/getEC2Metrics.js:
--------------------------------------------------------------------------------
1 | const {
2 | CloudWatchClient,
3 | GetMetricDataCommand,
4 | } = require('@aws-sdk/client-cloudwatch');
5 |
6 | const getEC2 = require('./getEC2');
7 |
8 | // https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/viewing_metrics_with_cloudwatch.html
9 |
10 | const getEC2Metrics = async (startTime, endTime, metricName, period) => {
11 | try {
12 | // Finish later
13 | const instances = await getEC2();
14 | const client = new CloudWatchClient({ region: 'us-east-1' });
15 | const command = new GetMetricDataCommand({});
16 | const response = await client.send(command);
17 | console.log(response);
18 | } catch (err) {
19 | return err;
20 | }
21 | };
22 |
23 | getEC2Metrics();
24 |
--------------------------------------------------------------------------------
/server/aws/getLambdaFuncs.js:
--------------------------------------------------------------------------------
1 | const {
2 | LambdaClient,
3 | ListFunctionsCommand,
4 | } = require('@aws-sdk/client-lambda');
5 |
6 |
7 |
8 | const getLambdaFuncs = async () => {
9 | try {
10 | const client = new LambdaClient({ region: 'us-east-1' });
11 | const command = new ListFunctionsCommand({ FunctionVersion: 'ALL' });
12 | const response = await client.send(command);
13 | // This returns FunctionArn and FunctionName
14 | const funcNames = response.Functions.map((func) => func.FunctionName)
15 | return funcNames;
16 | } catch (err) {
17 | return err;
18 | }
19 | };
20 |
21 | module.exports = getLambdaFuncs;
--------------------------------------------------------------------------------
/server/aws/getLogs.js:
--------------------------------------------------------------------------------
1 | const {
2 | CloudWatchLogsClient,
3 | CreateLogStreamCommand,
4 | GetLogEventsCommand,
5 | DescribeLogGroupsCommand,
6 | DescribeLogStreamsCommand,
7 | } = require("@aws-sdk/client-cloudwatch-logs");
8 |
9 | const getLogs = async(logGroup, limit) => {
10 |
11 | const client = await new CloudWatchLogsClient({region: "us-east-1"})
12 |
13 | if (!logGroup) {
14 | const logGroupsList = await client.send(
15 | new DescribeLogGroupsCommand(
16 | { limit: 50 }
17 | )
18 | );
19 |
20 | const streamsArr = [];
21 |
22 |
23 | for (let i = 0; i < logGroupsList.logGroups.length; i++) {
24 | const stream = await client.send(
25 | new DescribeLogStreamsCommand({
26 | logGroupName: logGroupsList.logGroups[i].logGroupName,
27 | })
28 | );
29 |
30 | const tempArr = [];
31 |
32 | tempArr.push(logGroupsList.logGroups[i].logGroupName)
33 | tempArr.push(stream);
34 |
35 | streamsArr.push(tempArr);
36 | }
37 |
38 |
39 | for (let i = 0; i < streamsArr.length; i++) {
40 | for (let j = 0; j < streamsArr[i][1].logStreams.length; j ++) {
41 | const events = await client.send(new GetLogEventsCommand({
42 | logGroupName: streamsArr[i][0],
43 | logStreamName: streamsArr[i][1].logStreams[j].logStreamName
44 | }))
45 | console.log(events);
46 | }
47 | }
48 | } else {
49 | const stream = await client.send(
50 | new DescribeLogStreamsCommand({
51 | logGroupName: logGroup
52 | })
53 | );
54 |
55 | console.log(stream)
56 | }
57 | }
58 |
59 | getLogs(undefined, 50);
--------------------------------------------------------------------------------
/server/aws/getMetrics.js:
--------------------------------------------------------------------------------
1 | const {
2 | GetMetricDataCommand,
3 | CloudWatchClient,
4 | } = require("@aws-sdk/client-cloudwatch");
5 |
6 | const dayjs = require("dayjs");
7 |
8 | const getLambdaFuncs = require("./getLambdaFuncs.js");
9 |
10 | const getMetrics = async (
11 | startTime,
12 | endTime,
13 | period = 60,
14 | funcName = undefined
15 | ) => {
16 | try {
17 | const client = new CloudWatchClient({ region: "us-east-1" });
18 | if (funcName === undefined) {
19 | const funcNames = await getLambdaFuncs();
20 | const queries = funcNames.map((func) => [
21 | {
22 | Id: `id${Math.floor(Math.random() * 812903819023)}`,
23 | Label: `Lambda Invocations ${func}`,
24 | MetricStat: {
25 | Period: `${period}`,
26 | Stat: "Sum", //should be sum
27 | Metric: {
28 | Namespace: `AWS/Lambda`,
29 | MetricName: "Invocations",
30 | Dimensions: [
31 | {
32 | Name: `FunctionName`,
33 | Value: `${func}`,
34 | },
35 | ],
36 | },
37 | },
38 | },
39 | {
40 | Id: `id${Math.floor(Math.random() * 897123891273)}`,
41 | Label: `Lambda Errors ${func}`,
42 | MetricStat: {
43 | Period: `${period}`,
44 | Stat: "Sum",
45 | Metric: {
46 | Namespace: "AWS/Lambda",
47 | MetricName: "Errors",
48 | Dimensions: [
49 | {
50 | Name: "FunctionName",
51 | Value: `${func}`,
52 | },
53 | ],
54 | },
55 | },
56 | },
57 | {
58 | Id: `id${Math.floor(Math.random() * 8929031920)}`,
59 | Label: `Lambda Throttles ${func}`,
60 | MetricStat: {
61 | Period: `${period}`,
62 | Stat: "Sum",
63 | Metric: {
64 | Namespace: "AWS/Lambda",
65 | MetricName: "Throttles",
66 | Dimensions: [
67 | {
68 | Name: "FunctionName",
69 | Value: `${func}`,
70 | },
71 | ],
72 | },
73 | },
74 | },
75 | {
76 | Id: `id${Math.floor(Math.random() * 981723891)}`,
77 | Label: `Lambda Duration ${func}`,
78 | MetricStat: {
79 | Period: `${period}`,
80 | Stat: "Sum",
81 | Metric: {
82 | Namespace: "AWS/Lambda",
83 | MetricName: "Duration",
84 | Dimensions: [
85 | {
86 | Name: "FunctionName",
87 | Value: `${func}`,
88 | },
89 | ],
90 | },
91 | },
92 | },
93 | ]);
94 |
95 | const data = [];
96 |
97 | for (let i = 0; i < queries.length; i += 1) {
98 | const queriedData = await client.send(
99 | new GetMetricDataCommand({
100 | StartTime: new Date(startTime),
101 | EndTime: new Date(endTime),
102 | MetricDataQueries: queries[i],
103 | })
104 | );
105 |
106 | data.push({
107 | funcName: queries[i][0].MetricStat.Metric.Dimensions[0].Value,
108 | invocationsArray: queriedData.MetricDataResults[0].Values.reverse(),
109 | totalInvocations: queriedData.MetricDataResults[0].Values.reduce(
110 | (a, b) => a + b,
111 | 0
112 | ),
113 | totalErrors: queriedData.MetricDataResults[1].Values.reduce(
114 | (a, b) => a + b,
115 | 0
116 | ),
117 | totalDuration: queriedData.MetricDataResults[3].Values.reduce(
118 | (a, b) => a + b,
119 | 0
120 | ),
121 | totalCost:
122 | queriedData.MetricDataResults[0].Values.reduce((a, b) => a + b, 0) *
123 | 0.0000006,
124 | timeStamps: queriedData.MetricDataResults[0].Timestamps.reverse(),
125 | formattedTimeStamps: queriedData.MetricDataResults[0].Timestamps.map(
126 | (time) => dayjs(time).format("M/D/YYYY")
127 | ).reverse(),
128 | formattedTime: queriedData.MetricDataResults[0].Timestamps.map(
129 | (time) => dayjs(time).format("hh A")
130 | ),
131 | });
132 | }
133 |
134 | console.log("data: ", data);
135 | return data;
136 | } else {
137 | const queriedData = await client.send(
138 | new GetMetricDataCommand({
139 | StartTime: new Date(startTime),
140 | EndTime: new Date(endTime),
141 | MetricDataQueries: [
142 | {
143 | Id: Math.floor(Math.random() * 64544324),
144 | MetricStat: {
145 | Period: period,
146 | Stat: metricStat,
147 | Metric: {
148 | MetricName: metricName,
149 | Namespace: "AWS/Lambda",
150 | Dimensions: [
151 | {
152 | Name: "FunctionName",
153 | Value: funcName,
154 | },
155 | ],
156 | },
157 | },
158 | },
159 | ],
160 | })
161 | );
162 |
163 | return {
164 | funcName: queries[i][0].MetricStat.Metric.Dimensions[0].Value,
165 | invocationsArray: queriedData.MetricDataResults[0].Values,
166 | totalInvocations: queriedData.MetricDataResults[0].Values.reduce(
167 | (a, b) => a + b,
168 | 0
169 | ),
170 | totalErrors: queriedData.MetricDataResults[1].Values.reduce(
171 | (a, b) => a + b,
172 | 0
173 | ),
174 | totalDuration: queriedData.MetricDataResults[2].Values.reduce(
175 | (a, b) => a + b,
176 | 0
177 | ),
178 | totalCost:
179 | queriedData.MetricDataResults[0].Values.reduce((a, b) => a + b, 0) *
180 | 0.0000006,
181 | timeStamps: queriedData.MetricDataResults[0].Timestamps,
182 | formattedTime: queriedData.MetricDataResults[0].Timestamps.map((time) =>
183 | dayjs(time).format("LT")
184 | ),
185 | };
186 | }
187 | } catch (err) {
188 | return err;
189 | }
190 | };
191 |
192 | getMetrics("August 1, 2022 15:30:30", "August 18, 2022 18:00:00");
193 |
194 | module.exports = getMetrics;
195 |
--------------------------------------------------------------------------------
/server/bcrypt.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require("bcryptjs");
2 |
3 | let result;
4 |
5 | async function bruh() {
6 | const res = await bcrypt.hash(
7 | "KfSQtRB5TgNGpCdXo2CSLO7fmWuYah11cj28OHNe",
8 | 10,
9 | (err, data) => {
10 | // console.log(data);
11 | // result = data;
12 | // console.log(result)
13 | return data;
14 | }
15 | );
16 |
17 | await bcrypt.compare(
18 | "KfSQtRB5TgNGpCdXo2CSLO7fmWuYah11cj28OHNe",
19 | res,
20 | (err, data) => {
21 | if (err) console.log("bruh: ", err);
22 | else {
23 | console.log("data: ", data);
24 | }
25 | }
26 | );
27 | }
28 |
29 | bruh();
30 |
--------------------------------------------------------------------------------
/server/controllers/authController.js:
--------------------------------------------------------------------------------
1 | const jwt = require("jsonwebtoken");
2 |
3 | const authController = {};
4 |
5 |
6 | authController.verifyToken = async (req, res, next) => {
7 | try {
8 | const token = req.cookies["token"];
9 | if (!token) {
10 | const token = await jwt.sign(res.locals.password, process.env.JWT_SECRET);
11 | console.log("token created: ", { token });
12 | res.cookie("token", token);
13 | return next();
14 | } else {
15 | jwt.verify(token, process.env.JWT_SECRET, (error, result) => {
16 | if (error)
17 | return next({
18 | log: "Token could not be verified in authController.verifyToken",
19 | status: 401,
20 | message: err,
21 | });
22 | req.user = result;
23 | console.log("TOKEN VERIFIED");
24 | return next();
25 | });
26 | }
27 | } catch (err) {
28 | return next({
29 | log: "Error occurred in authController.verifyToken",
30 | status: 401,
31 | message: err,
32 | });
33 | }
34 | };
35 |
36 | module.exports = authController;
37 |
--------------------------------------------------------------------------------
/server/controllers/metricController.js:
--------------------------------------------------------------------------------
1 | const getMetrics = require('../aws/getMetrics.js');
2 |
3 | const metricController = {};
4 |
5 | metricController.getMetrics = async (req, res, next) => {
6 |
7 | const { startTime, endTime, period } = req.body;
8 |
9 | try {
10 | let result = await getMetrics(startTime, endTime, period);
11 |
12 |
13 | res.locals.metrics = { metrics: result };
14 |
15 | return next();
16 | } catch (err) {
17 | console.log('error: ', err);
18 | }
19 | };
20 |
21 | metricController.consoleLog = (req, res, next) => {
22 | console.log('entered consoleLOg');
23 |
24 | return next();
25 | };
26 |
27 | module.exports = metricController;
28 |
--------------------------------------------------------------------------------
/server/controllers/userController.js:
--------------------------------------------------------------------------------
1 | const db = require("../models/UserModels");
2 | const bcrypt = require("bcryptjs");
3 |
4 | const userController = {};
5 |
6 | userController.createUser = async (req, res, next) => {
7 | try {
8 | const { email, password, firstname, lastname } = req.body;
9 | bcrypt.hash(password, 10, (err, hash) => {
10 | if (err) return err;
11 | else {
12 | const query = `INSERT INTO users (email, password, firstname, lastname) VALUES ('${email}', '${hash}', '${firstname}', '${lastname}')`;
13 | db.query(query)
14 | .then(() => {
15 | res.locals.password = hash;
16 | return next();
17 | })
18 | .catch(() =>
19 | next({
20 | log: "Cannot create user",
21 | status: 400,
22 | message: "Cannot create user",
23 | })
24 | );
25 | }
26 | });
27 | } catch (err) {
28 | return next({
29 | log: "Error occurred in userController.createUser",
30 | status: 400,
31 | message: err,
32 | });
33 | }
34 | };
35 |
36 | userController.verifyUser = async (req, res, next) => {
37 | try {
38 | const { email, password } = req.body;
39 |
40 | const query = `SELECT email, password, firstname, lastname FROM users WHERE email='${email}'`;
41 | db.query(query)
42 | .then((data) => {
43 | bcrypt.compare(password, data.rows[0].password, (err, result) => {
44 | if (result) {
45 | res.locals.password = result;
46 |
47 | console.log(data.rows[0]);
48 | res.locals.user = {
49 | verifiedUser: true,
50 | firstName: data.rows[0].firstname,
51 | lastName: data.rows[0].lastname,
52 | };
53 | return next();
54 | } else {
55 | res.locals.verifiedUser = false;
56 | return next({
57 | log: "Error occurred in userController.verifyUser",
58 | status: 401,
59 | message: err,
60 | });
61 | }
62 | });
63 | })
64 | .catch((err) => {
65 | console.log('err: ', err);
66 | next({
67 | log: "Error occurred in userController.verifyUser",
68 | status: 401,
69 | message: err,
70 | })
71 | }
72 | );
73 | } catch (err) {
74 | console.log('err: ', err)
75 | return next({
76 | log: "Error occurred in userController.verifyUser",
77 | status: 401,
78 | message: err,
79 | });
80 | }
81 | };
82 |
83 | module.exports = userController;
84 |
--------------------------------------------------------------------------------
/server/lumos_postgres_create.sql:
--------------------------------------------------------------------------------
1 | SET client_encoding = 'UTF8';
2 | SET standard_conforming_strings = on;
3 | SELECT pg_catalog.set_config('search_path', 'public', false);
4 | SET check_function_bodies = false;
5 | SET client_min_messages = warning;
6 | SET row_security = off;
7 | --
8 | CREATE TABLE users (
9 | "_id" serial NOT NULL,
10 | "email" varchar NOT NULL UNIQUE,
11 | "password" varchar NOT NULL,
12 | "firstname" varchar NOT NULL,
13 | "lastname" varchar NOT NULL,
14 |
15 | CONSTRAINT "users_pk" PRIMARY KEY ("_id")
16 |
17 | ) WITH (
18 | OIDS=FALSE
19 | );
20 |
21 | -- INSERT INTO users (email, password, firstname, lastname, arn) VALUES ('addyac2', 'lol', 'wow', 'annoyed', '12375555748439');
22 |
23 |
24 | /*
25 | psql -d -f lumos_postgres_create.sql
26 | */
27 |
28 | -- psql -d postgres://hqtdaxpj:QBKrOx1TsD79gVw-GSLQ89b335iqReba@chunee.db.elephantsql.com/hqtdaxpj -f server/lumos_postgres_create.sql
29 |
30 |
--------------------------------------------------------------------------------
/server/models/.gitignore:
--------------------------------------------------------------------------------
1 | #amplify-do-not-edit-begin
2 | amplify/\#current-cloud-backend
3 | amplify/.config/local-*
4 | amplify/logs
5 | amplify/mock-data
6 | amplify/backend/amplify-meta.json
7 | amplify/backend/.temp
8 | build/
9 | dist/
10 | node_modules/
11 | aws-exports.js
12 | awsconfiguration.json
13 | amplifyconfiguration.json
14 | amplifyconfiguration.dart
15 | amplify-build-config.json
16 | amplify-gradle-config.json
17 | amplifytools.xcconfig
18 | .secret-*
19 | **.sample
20 | #amplify-do-not-edit-end
--------------------------------------------------------------------------------
/server/models/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "files.exclude": {
3 | "amplify/.config": true,
4 | "amplify/**/*-parameters.json": true,
5 | "amplify/**/amplify.state": true,
6 | "amplify/**/transform.conf.json": true,
7 | "amplify/#current-cloud-backend": true,
8 | "amplify/backend/amplify-meta.json": true,
9 | "amplify/backend/awscloudformation": true
10 | }
11 | }
--------------------------------------------------------------------------------
/server/models/UserModels.js:
--------------------------------------------------------------------------------
1 | const { Pool, Client } = require("pg");
2 |
3 | const pool = new Pool({
4 | host: "lumos-postgres.cqw54zvzfbpb.us-east-1.rds.amazonaws.com",
5 | port: 5432,
6 | user: "lumos",
7 | database: "postgresLumos",
8 | password: "lumosTest!",
9 | });
10 |
11 |
12 | pool.connect((err) => {
13 | if (err) {
14 | console.log("error: ", err);
15 | return err;
16 | }
17 |
18 | console.log("it worked");
19 | });
20 |
21 |
22 | module.exports = {
23 | query: (text, params, callback) => {
24 | return pool.query(text, params, callback);
25 | },
26 | };
27 |
--------------------------------------------------------------------------------
/server/models/amplify/.config/project-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "providers": [
3 | "awscloudformation"
4 | ],
5 | "projectName": "AWSLambdaVisualizer",
6 | "version": "3.1",
7 | "frontend": "javascript",
8 | "javascript": {
9 | "framework": "react",
10 | "config": {
11 | "SourceDir": "client",
12 | "DistributionDir": "build",
13 | "BuildCommand": "npm run-script build",
14 | "StartCommand": "npm run-script start"
15 | }
16 | }
17 | }
--------------------------------------------------------------------------------
/server/models/amplify/backend/backend-config.json:
--------------------------------------------------------------------------------
1 | {}
--------------------------------------------------------------------------------
/server/models/amplify/backend/tags.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "Key": "user:Stack",
4 | "Value": "{project-env}"
5 | },
6 | {
7 | "Key": "user:Application",
8 | "Value": "{project-name}"
9 | }
10 | ]
--------------------------------------------------------------------------------
/server/models/amplify/backend/types/amplify-dependent-resources-ref.d.ts:
--------------------------------------------------------------------------------
1 | export type AmplifyDependentResourcesAttributes = {}
--------------------------------------------------------------------------------
/server/models/amplify/cli.json:
--------------------------------------------------------------------------------
1 | {
2 | "features": {
3 | "graphqltransformer": {
4 | "addmissingownerfields": true,
5 | "improvepluralization": false,
6 | "validatetypenamereservedwords": true,
7 | "useexperimentalpipelinedtransformer": true,
8 | "enableiterativegsiupdates": true,
9 | "secondarykeyasgsi": true,
10 | "skipoverridemutationinputtypes": true,
11 | "transformerversion": 2,
12 | "suppressschemamigrationprompt": true,
13 | "securityenhancementnotification": false,
14 | "showfieldauthnotification": false,
15 | "usesubusernamefordefaultidentityclaim": true,
16 | "usefieldnameforprimarykeyconnectionfield": false,
17 | "enableautoindexquerynames": false,
18 | "respectprimarykeyattributesonconnectionfield": false,
19 | "shoulddeepmergedirectiveconfigdefaults": false,
20 | "populateownerfieldforstaticgroupauth": false
21 | },
22 | "frontend-ios": {
23 | "enablexcodeintegration": true
24 | },
25 | "auth": {
26 | "enablecaseinsensitivity": true,
27 | "useinclusiveterminology": true,
28 | "breakcirculardependency": true,
29 | "forcealiasattributes": false,
30 | "useenabledmfas": true
31 | },
32 | "codegen": {
33 | "useappsyncmodelgenplugin": true,
34 | "usedocsgeneratorplugin": true,
35 | "usetypesgeneratorplugin": true,
36 | "cleangeneratedmodelsdirectory": true,
37 | "retaincasestyle": true,
38 | "addtimestampfields": true,
39 | "handlelistnullabilitytransparently": true,
40 | "emitauthprovider": true,
41 | "generateindexrules": true,
42 | "enabledartnullsafety": true
43 | },
44 | "appsync": {
45 | "generategraphqlpermissions": true
46 | },
47 | "latestregionsupport": {
48 | "pinpoint": 1,
49 | "translate": 1,
50 | "transcribe": 1,
51 | "rekognition": 1,
52 | "textract": 1,
53 | "comprehend": 1
54 | },
55 | "project": {
56 | "overrides": true
57 | }
58 | },
59 | "debug": {}
60 | }
--------------------------------------------------------------------------------
/server/models/amplify/team-provider-info.json:
--------------------------------------------------------------------------------
1 | {
2 | "staging": {
3 | "awscloudformation": {
4 | "AuthRoleName": "amplify-amplifye9568f63d0aa4-staging-10231-authRole",
5 | "UnauthRoleArn": "arn:aws:iam::487096115927:role/amplify-amplifye9568f63d0aa4-staging-10231-unauthRole",
6 | "AuthRoleArn": "arn:aws:iam::487096115927:role/amplify-amplifye9568f63d0aa4-staging-10231-authRole",
7 | "Region": "us-east-1",
8 | "DeploymentBucketName": "amplify-amplifye9568f63d0aa4-staging-10231-deployment",
9 | "UnauthRoleName": "amplify-amplifye9568f63d0aa4-staging-10231-unauthRole",
10 | "StackName": "amplify-amplifye9568f63d0aa4-staging-10231",
11 | "StackId": "arn:aws:cloudformation:us-east-1:487096115927:stack/amplify-amplifye9568f63d0aa4-staging-10231/c39f7070-24da-11ed-bdb7-0adf7a2f4377",
12 | "AmplifyAppId": "d3hekcz5dui41c"
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/server/routes/metricRoute.js:
--------------------------------------------------------------------------------
1 | const express = require('express');
2 | const router = express.Router();
3 | //middleware
4 | const metricController = require('../controllers/metricController.js');
5 | const authController = require('../controllers/authController');
6 |
7 | router.post(
8 | '/',
9 | authController.verifyToken,
10 | metricController.getMetrics,
11 | (req, res) => {
12 | res.status(200).json(res.locals.metrics);
13 | },
14 | );
15 |
16 | module.exports = router;
17 |
--------------------------------------------------------------------------------
/server/routes/userRoute.js:
--------------------------------------------------------------------------------
1 | const express = require("express");
2 | const router = express.Router();
3 | //middleware
4 | const userController = require("../controllers/userController.js");
5 | const authController = require("../controllers/authController");
6 |
7 | router.post(
8 | "/register",
9 | userController.createUser,
10 | authController.verifyToken,
11 | (req, res) => res.status(200).send("User created in database")
12 | );
13 |
14 | router.post(
15 | "/login",
16 | userController.verifyUser,
17 | authController.verifyToken,
18 | (req, res) => res.status(200).json(res.locals.user)
19 | );
20 |
21 | module.exports = router;
22 |
--------------------------------------------------------------------------------
/server/server.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | require("dotenv").config();
3 | const express = require("express");
4 | const path = require("path");
5 |
6 | const app = express();
7 | const PORT = 3000;
8 | const cookieParser = require("cookie-parser");
9 |
10 | const userRouter = require("./routes/userRoute");
11 | const metricRouter = require("./routes/metricRoute");
12 |
13 | app.use(express.json());
14 | app.use(express.urlencoded({ extended: true }));
15 | app.use(cookieParser());
16 |
17 | app.use("/user", userRouter);
18 | app.use("/metric", metricRouter);
19 |
20 |
21 | app.use(express.static(path.resolve(__dirname, "../dist")));
22 |
23 | // catch all route handler
24 | app.use("*", (req, res) =>
25 | res.status(404).send("This is not the page you're looking for...")
26 | );
27 |
28 | // Global error handler
29 | app.use((err, req, res, next) => {
30 | const defaultErr = {
31 | log: "Express error handler caught unknown middleware error",
32 | status: 500,
33 | message: { err: "An error occurred" },
34 | };
35 | const errorObj = { ...defaultErr, ...err };
36 | console.log(errorObj.log);
37 | return res.status(errorObj.status).json(errorObj.message);
38 | });
39 |
40 | // start server
41 | app.listen(PORT, () => {
42 | console.log(`Listening on port ${PORT}...`);
43 | });
44 |
45 | module.exports = app;
46 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable linebreak-style */
2 | const path = require('path');
3 | const HtmlWebpackPlugin = require('html-webpack-plugin');
4 |
5 | const config = {
6 | entry: ['./client/index.jsx'],
7 | output: {
8 | path: path.resolve(__dirname, 'dist'),
9 | filename: 'bundle.js',
10 | },
11 | module: {
12 | rules: [
13 | {
14 | test: /\.(js|jsx)$/,
15 | use: 'babel-loader',
16 | exclude: /node_modules/,
17 | },
18 | {
19 | test: /\.css$/,
20 | use: ['style-loader', 'css-loader'],
21 | },
22 | {
23 | test: /\.png$/,
24 | use: [
25 | {
26 | loader: 'url-loader',
27 | options: {
28 | mimetype: 'image/png',
29 | },
30 | },
31 | ],
32 | },
33 | {
34 | test: /\.scss$/,
35 | use: ['style-loader', 'css-loader', 'sass-loader'],
36 | },
37 | {
38 | test: /\.svg$/,
39 | use: 'file-loader',
40 | },
41 | ],
42 | },
43 | plugins: [
44 | new HtmlWebpackPlugin({
45 | template: 'client/index.html',
46 | filename: 'index.html',
47 | }),
48 | ],
49 | devServer: {
50 | host: 'localhost',
51 | port: 8080,
52 | // enable HMR on the devServer
53 | hot: true,
54 | // fallback to root for other urls
55 | historyApiFallback: true,
56 |
57 | headers: { "Access-Control-Allow-Origin": "*" },
58 |
59 | proxy: {
60 | '/': {
61 | target: 'http://localhost:3000',
62 | secure: false,
63 | },
64 | },
65 | },
66 | };
67 |
68 | module.exports = config;
69 |
--------------------------------------------------------------------------------