diff --git a/.d.ts b/.d.ts new file mode 100644 index 000000000..31dca6bb4 --- /dev/null +++ b/.d.ts @@ -0,0 +1 @@ +declare module '*.png' diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 000000000..887aa77db --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,68 @@ +{ + "globals": { + "NodeJS": true, + "JSX": true, + "Electron": true + }, + "plugins": ["react", "prettier"], + "parser": "babel-eslint", + "parserOptions": { + "ecmaVersion": 2018, + "sourceType": "module", + "ecmaFeatures": { + "jsx": true + } + }, + "env": { + "es6": true, + "browser": true, + "node": true, + "jest": true + }, + "extends": ["airbnb", "eslint:recommended", "plugin:react/recommended", "prettier"], + "rules": { + "react/display-name": "off", + "prettier/prettier": "error", + "no-unused-vars": [ + "warn", + { + "args": "none" + } + ], + "react/prop-types": "off", + "no-console": "warn", + "react/jsx-filename-extension": "off", + "no-alert": "off", + "no-plusplus": "off", + "no-new": "off", + "react/button-has-type": "off", + "react/jsx-one-expression-per-line": "off", + "no-restricted-globals": "off", + "react/no-array-index-key": "warn", + "no-restricted-syntax": "off", + "guard-for-in": "off", + "no-param-reassign": "off", + "import/extensions": [ + "error", + "ignorePackages", + { + "js": "never", + "jsx": "never", + "ts": "never", + "tsx": "never", + "mjs": "never" + } + ] + }, + "settings": { + "import/extensions": [".js", ".mjs", ".jsx", ".ts", ".tsx"], + // "import/parsers": { + // "@typescript-eslint/parser": [".ts", ".tsx"] + // }, + "import/resolver": { + "node": { + "extensions": [".js", ".jsx", ".ts", ".tsx"] + } + } + } +} \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..dd84ea782 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,38 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: '' +assignees: '' + +--- + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected behavior** +A clear and concise description of what you expected to happen. + +**Screenshots** +If applicable, add screenshots to help explain your problem. + +**Desktop (please complete the following information):** + - OS: [e.g. iOS] + - Browser [e.g. chrome, safari] + - Version [e.g. 22] + +**Smartphone (please complete the following information):** + - Device: [e.g. iPhone6] + - OS: [e.g. iOS8.1] + - Browser [e.g. stock browser, safari] + - Version [e.g. 22] + +**Additional context** +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md new file mode 100644 index 000000000..bbcbbe7d6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.md @@ -0,0 +1,20 @@ +--- +name: Feature request +about: Suggest an idea for this project +title: '' +labels: '' +assignees: '' + +--- + +**Is your feature request related to a problem? Please describe.** +A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] + +**Describe the solution you'd like** +A clear and concise description of what you want to happen. + +**Describe alternatives you've considered** +A clear and concise description of any alternative solutions or features you've considered. + +**Additional context** +Add any other context or screenshots about the feature request here. diff --git a/.gitignore b/.gitignore index 3c3629e64..22962df70 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,52 @@ +# JS Project-Specific # +####################### +# node_modules +/dist +/build +release-builds +coverage +__tests__/**/__snapshots__ +.env +databases.txt +settings.json + +# Compiled source # +################### +*.com +*.class +*.dll +*.exe +*.o +*.so + +# Packages # +############ +# it's better to unpack these files and commit the raw source +# git has its own built in compression methods +*.7z +*.dmg +*.gz +*.iso +*.jar +*.rar +*.tar +*.zip + +# Logs and databases # +###################### +*.log +*.sql +*.sqlite + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db node_modules + +out/ \ No newline at end of file diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 000000000..6d96a88fa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,7 @@ +{ + "singleQuote": true, + "useTabs": false, + "endOfLine": "auto", + "arrowParens": "avoid", + "printWidth": 100 +} \ No newline at end of file diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..1953022ec --- /dev/null +++ b/.travis.yml @@ -0,0 +1,18 @@ +language: node_js +node_js: + - 'stable' +os: osx +jobs: + # allow_failures: + # - os: osx + fast_finish: true +install: + - npm install +script: + npm run test:app + # safelist + # branches: + # only: + # - master + # - middleware + # - chronosWebsite diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 000000000..965c9e301 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,45 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "File", + "skipFiles": [ + "/**", + ], + "program": "${file}" + }, + { + "type": "node", + "request": "launch", + "name": "Electron: Main", + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron", + "runtimeArgs": [ + "--remote-debugging-port=9223", + "." + ], + "windows": { + "runtimeExecutable": "${workspaceFolder}/node_modules/.bin/electron.cmd" + } + }, + { + "name": "Electron: Renderer", + "type": "chrome", + "request": "attach", + "port": 9223, + "webRoot": "${workspaceFolder}", + "timeout": 30000, + "url": "http://localhost:8080/", + } + ], + "compounds": [ + { + "name": "Electron: All", + "configurations": [ + "Electron: Main", + "Electron: Renderer" + ] + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..7a73a41bf --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,2 @@ +{ +} \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..3402b90b3 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,37 @@ +# Contributing + +Chronos encourages contributions to this product. + +## Pull Requests + +Chronos welcomes all pull requests. + +1. Fork the repo and create a working branch from `master`. +2. If you've added any code that requires testing, add tests. +3. If you've changed APIs, update the `README.md`. +4. Check to ensure that all tests pass. +5. Make sure code is formatted with `prettier` and follows the [Airbnb React/JSX Style Guide](https://github.com/airbnb/javascript/blob/master/react/README.md). +6. Create a pull request to `master`. + +## Getting started +- `npm run dev:app` and `npm run dev:electron`: Run Node and Electron at the same time to start Chronos app + - To make changes to codebase on the Main Process: + - Files in the main process must be compiled prior to starting the app + - In the terminal in Chronos directory, input `tsc` to compile typescript files + - Once compiled, `npm run dev:app` and `npm run dev:electron` + * Note: If typescript is not installed, `npm install -g typescript` + +## Chronos Website + +The `chronosWebsite` branch holds the code for the website. Edit the website by checking out the branch, modifying the website, and then updating the AWS S3 bucket with the changes. +## Issues + +Please do not hesitate to file issues that detail bugs or offer ways to enhace Chronos. + +Chronos is based off of community feedback and is always looking for ways to get better. The team behind Chronos is interested to hear about your experience and how we can improve it. + +When submitting issues, ensure your description is clear and has instructions to be able to reproduce the issue. + +## Get In Touch + +We use GitHub as a platform of communication between users and developers to promote transparency, community support and feedback. diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 000000000..e96b60909 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Chronos + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index d6c988392..8911e0079 100644 --- a/README.md +++ b/README.md @@ -1 +1,229 @@ + + Chronos + + +
+ +![Build Passing](https://img.shields.io/badge/build-awesome-brightgreen) +[![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/open-source-labs/Chronos) +![License: MIT](https://img.shields.io/badge/License-MIT-brightgreen.svg) +![Release: 11.0](https://img.shields.io/badge/Release-11.0-brightgreen) + + # Chronos + + +### ⭐️ Star us on GitHub! ⭐️ +**Visit our website at [chronoslany.com](https://chronoslany.com/).** + +Chronos is a comprehensive developer tool that monitors the health and web traffic of servers, microservices, containers, and Amazon Web Services (AWS). Use Chronos to see real-time data monitoring and receive automated notifications over Slack or email. + + +## What's New? + +### With Chronos 11.0: + +- Added interactive charting to better visualize metrics and increase user engagement with their data +- Followed best test-driven development practices and increased testing with React Testing and Jest for the front end +- Overhauled user database security, mitigating database breaches and providing a safer experience +- Bug fixes and UI tweaks, creating a more pleasant user experience +- Updated outdated README instructions within the provided Docker, gRPC, Kubernetes, and microservices examples +- Revised README instructions for the `@chronosmicro/tracker` NPM package + + +**Previously implemented updates:** +- Streamlined approach to access and dynamically display Grafana dashboards for deployed EKS clusters (utilizing Prometheus data scraping and generated Grafana dashboards) using the Grafana API. +- Option to choose between cloud hosted services and local services, giving Chronos the ability to monitor instances and clusters on AWS' EC2, ECS, and EKS platforms. +- An updated AWS Graphs Container to dynamically render plots for EC2 or ECS data fetched with Electron using event listeners connecting to AWS CloudWatch w/ the aws-sdk package, as well as utilizing Prometheus data scraping and Grafana integration to fetch and render EKS data. +- Step-by-step instructions on setting up a new, functional EC2 instances, ECS clusters, and EKS clusters, ready to be monitored and tested by the app. + +## Features + +- Cloud-Based Instances: + - Option to choose between cloud hosted services and local services, giving Chronos the ability to monitor instances and clusters on AWS EC2, ECS, and EKS platforms AWS +- Local instances utilitizing `@chronosmicro/tracker` NPM package: + - Enables distributed tracing enabled across microservices applications + - Displays real-time temperature, speed, latency, and memory statistics for local services + - Displays and compares multiple microservice metrics in a single graph + - Allow Kubernetes monitoring via Prometheus server + - Compatible with GraphQL + - Monitor an Apache Kafka cluster via the JMX Prometheus Exporter + - Supports PostgreSQL and MongoDB databases + + +# Installation + +This is for the latest Chronos **version 11.0 release**. + +## NPM Package + +In order to use Chronos within your own application, you must have the `@chronosmicro/tracker` dependency installed. + +The `@chronosmicro/tracker` package tracks your application's calls and scrapes metrics from your system. + +- **NOTE:** The Chronos tracker code is included in the _chronos_npm_package_ folder for ease of development, but the published NPM package can be downloaded by running `npm install @chronosmicro/tracker`. + +For more details on the NPM package and instructions for how to use it, please view the [Chronos NPM Package README](./chronos_npm_package/README.md). + +# + + + + + + + +## Chronos Desktop Application +### WSL2 Environment + +If you wish to launch the Electron Application in an WSL2 envirronment(Ubuntu) you may need the following commands for an Electron window to appear + +- Install VcXsrv + +- Run the following command in the terminal + +``` +sudo apt install libgconf-2-4 libatk1.0-0 libatk-bridge2.0-0 libgdk-pixbuf2.0-0 libgtk-3-0 libgbm-dev libnss3-dev libxss-dev +``` + +- After running your VcXsrv instance, run the following command in the terminal + +``` +export DISPLAY="`sed -n 's/nameserver //p' /etc/resolv.conf`:0" +``` + +### Creating User Database + +**NOTE: You must create your own user database** + +1. Create a MongoDB database in which to store user information and insert it on line 2 within the [UserModel.ts](./electron/models/UserModel.ts) (_electron/models/UserModel.ts_) file. + - This database will privately store user information. +3. Once this is set up, you can create new users, log in, and have your data persist between sessions. +# +### Running the Chronos desktop app in development mode + +1. From the root directory, run `npm install` +2. Run `npm run build` +3. Open a new terminal and run `npm run dev:app` to start the Webpack development server +4. Open a new terminal and run `npm run dev:electron` to start the Electron UI in development mode +# +### Packing the Chronos desktop app into an executable + +1. From the root directory, run `npm run build` +2. Run `npm run package` +3. Find the `chronos.app` executable inside the newly created `release-builds` folder in the root directory. + +# +# Examples + +We provide eight example applications for you to test out both the Chronos NPM package and the Chronos desktop application: + +- AWS + - [EC2 README](./examples/AWS/AWS-EC2/README.md) + - [ECS README](./examples/AWS/AWS-ECS/README.md) + - [EKS README](./examples/AWS/AWS-EKS/README.md) +- Docker + - [Docker README](./examples/docker/README.md) +- gRPC + - [gRPC README](./examples/gRPC/README.md) +- Kubernetes + - [Kubernetes README](./examples/kubernetes/README.md) +- Microservices + - [Microservices README](./examples/microservices/README.md) + +Additional documentation on how Chronos is used **in each example** can be found in the [Chronos NPM Package README](./chronos_npm_package/README.md). + +#### _AWS_ + +The `AWS` folder includes 3 example applications with instructions on how to deploy them in AWS platforms. Note that using AWS services may cause charges. + +- The ECS folder includes an web application ready to be containerized using Docker. The instruction shows how to deploy application to ECS using Docker CLI command, and it will be managed by Fargate services. +- The EC2 folder includes a React/Redux/SQL web application ready to be containerized using Docker. The instruction shows how to deploy application using AWS Beanstalk and connect application to RDS database. Beanstalk service will generate EC2 instance. +- The EKS folder includes a containerized note taking app that uses a Mongo database as its persistent volume. The instructions show how to deploy this application on EKS, how to monitor with Prometheus & Opencost, and how to use Grafana to grab visualizations. + +Refer to the [EC2 README](./examples/AWS/AWS-EC2/README.md), [ECS README](./examples/AWS/AWS-ECS/README.md), and [EKS README](./examples/AWS/AWS-EKS/README.md) example in the _AWS_ folder for more details. + +# +#### _Docker_ + +In the Docker folder within the `master` branch, we provide a sample _dockerized_ microservices application to test out Chronos and to apply distributed tracing across different containers for your testing convenience. + +The `docker` folder includes individual Docker files in their respective directories. A docker-compose.yml is in the root directory in case you'd like to deploy all services together. + +Refer to the [Docker README](./examples/docker/README.md) in the `docker` folder for more details. +# + + +#### _gRPC_ + +The `gRPC` folder includes an HTML frontend and an Express server backend, as well as proto files necessary to build package definitions and make gRPC calls. The _reverse_proxy_ folder contains the server that requires in the clients, which contain methods and services defined by proto files. + +Refer to the [gRPC README](./examples/gRPC/README.md) in the `gRPC` folder for more details. +# + +#### _Kubernetes_ + +The `kubernetes` folder includes a React frontend and an Express server backend, and the Dockerfiles needed to containerize them for Kubernetes deployment. The _launch_ folder includes the YAML files needed to configure the deployments, services, and configurations of the frontend, backend, and Prometheus server. + +Refer to the [Kubernetes README](./examples/kubernetes/README.md) in the `kubernetes` folder for more details. + +# + +#### _Microservices_ + +In the `microservices` folder, we provide a sample microservice application that successfully utilizes Chronos to apply all the powerful, built-in features of our monitoring tool. You can then visualize the data with the Electron app. + +Refer to the [microservices README](./examples/microservices/README.md) in the `microservices` folder for more details. +# + +# Testing + +We've created testing suites for Chronos with React Testing and Jest - instructions on running them can be found in the [testing README](./__tests__/README.md). +# +## Contributing + +Development of Chronos is open source on GitHub through the tech accelerator OS Labs, and we are grateful to the community for contributing bug fixes and improvements. + +Read our [contributing README](../../CONTRIBUTING.md) to learn how you can take part in improving Chronos. + +### **Last Iterating Team** + +#### Chronos 11.0 +- [Lucie Seidler](https://github.com/LucieSeidler) +- [Jeffrey Na](https://github.com/jeffreyNa) +- [Brisa Zhu](https://github.com/beezoo10) +- [Kelsi Webb](https://github.com/kelsicw) +- [Justin Poirier](https://github.com/jcpoirier20) + + +#### Past [Contributors](contributors.md) +# +## Technologies + +- Electron +- React +- JavaScript +- TypeScript +- PostgreSQL +- MongoDB +- Node +- Express +- HTTP +- gRPC +- GraphQL +- Docker +- AWS +- Jest +- Webpack +- Material-UI +- Vis.js +- Plotly.js +- Apache Kafka + + +## License + +[MIT](https://github.com/oslabs-beta/Chronos/blob/master/LICENSE.md) +# + +###### Return to [Top](#chronos) diff --git a/__tests__/README.md b/__tests__/README.md new file mode 100644 index 000000000..7aabd5c36 --- /dev/null +++ b/__tests__/README.md @@ -0,0 +1,62 @@ +# Testing + +### Preparation + +1. React Testing Library versions 13+ require React v18. If your project uses an older version of React, be sure to install version 12 +``` +npm install --save-dev @testing-library/react@12 +``` +2. install additional packages +``` +npm install -g jest +npm i @jest/types +npm i ts-jest +npm i jest-environment-jsdom +npm i --save-dev @types/jest @testing-library/jest-dom + +npm i @types/node +npm install -D ts-node +``` +3. create jest.config.js +``` +module.exports = { + verbose: true, + setupFilesAfterEnv: ['./jest_setup/windowMock.js'], + testEnvironment: "jsdom", + preset: 'ts-jest/presets/js-with-ts', + moduleNameMapper: { + '\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': + '/jest_setup/fileMock.js', + '\\.(css|less|scss)$': '/jest_setup/styleMock.js', + }, + collectCoverage: true, + types: ["jest","node"], +}; +``` +4. make sure jest_setup folder is at root directory of Chronos with styleMock.js and windowMock.js + + styleMock.js + ``` + module.exports = {}; + ``` + windowMock.js + ``` + // Mock window environment + window.require = require; + + // Mock import statements for Plotly + window.URL.createObjectURL = () => {}; + + // Mock get context + HTMLCanvasElement.prototype.getContext = () => {}; + ``` +5. update database info inside test_settings.json + +6. use `npm run test` to run jest tests + +## Contributing + +Chronos hopes to inspire an active community of both users and developers. For questions, comments, or contributions, please submit a pull request. + +Read our [contributing README](../../CONTRIBUTING.md) to further learn how you can take part in improving Chronos. + diff --git a/__tests__/charts/HealthChart.test.tsx b/__tests__/charts/HealthChart.test.tsx new file mode 100644 index 000000000..9e589eebd --- /dev/null +++ b/__tests__/charts/HealthChart.test.tsx @@ -0,0 +1,66 @@ +import React from 'react'; +import HealthChart from '../../app/charts/HealthChart'; +import { render, screen } from '@testing-library/react'; + +// mockData used for testing suite +const mockData = { + ServiceName: { + Metric: { + time: [ + '2023-06-09T15:18:25.195Z', + '2023-06-09T15:18:20.194Z', + '2023-06-09T15:18:15.192Z', + '2023-06-09T15:18:10.191Z', + '2023-06-09T15:18:05.190Z', + ], + value: [1208074240, 1282670592, 1243414528, 1278115840, 117178368], + }, + }, +}; + +jest.mock('electron', () => ({ + ipcRenderer: { + send: () => jest.fn(), + on: () => mockData, + }, +})); + +// test suite for HealthChart.tsx +describe('HealthChart', () => { + const props = { + key: 'testKey', + dataType: 'Memory in Megabytes', + serviceName: 'serviceName', + chartData: mockData, + categoryName: 'Test Name', + sizing: 'all', + colourGenerator: jest.fn(), + }; + + let graph; + beforeEach(() => { + render(); + + graph = screen.getByTestId('Health Chart').firstChild; + }); + + it('Should render', () => { + expect(screen).toBeTruthy(); + }); + + it('Should render graph', () => { + expect(graph).toBeTruthy(); + }); + + it('Should not scroll', () => { + expect(graph.scrollWidth).toBe(0); + expect(graph.scrollHeight).toBe(0); + expect(graph.scrollLeft).toBe(0); + expect(graph.scrollTop).toBe(0); + }); + + it('Should have correct data on y-axis based off mock data', () => { + expect(graph.data[0].y[0]).toBe((mockData.ServiceName.Metric.value[0] / 1000000).toFixed(2)); + expect(graph.data[0].y[1]).toBe((mockData.ServiceName.Metric.value[1] / 1000000).toFixed(2)); + }); +}); diff --git a/__tests__/charts/TrafficChart.test.tsx b/__tests__/charts/TrafficChart.test.tsx new file mode 100644 index 000000000..04ba9bffb --- /dev/null +++ b/__tests__/charts/TrafficChart.test.tsx @@ -0,0 +1,73 @@ +/* eslint-disable no-underscore-dangle */ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import TrafficChart from '../../app/charts/TrafficChart'; +import { CommsContext } from '../../app/context/CommsContext'; +import '@testing-library/jest-dom'; + +const mockCommsData = [ + { + correlatingid: '7bdad8c0', + endpoint: '/customers/createcustomer', + microservice: 'customers', + request: 'GET', + responsemessage: 'OK', + responsestatus: 200, + time: '2020-06-27T05:30:43.567Z', + id: 36, + }, +]; + +jest.mock('electron', () => ({ + ipcRenderer: { + send: () => jest.fn(), + on: () => mockCommsData, + }, +})); +describe('Traffic Chart', () => { + const props = { + commsData: mockCommsData, + setCommsData: jest.fn(), + fetchCommsData: jest.fn(), + }; + let graph; + beforeEach(() => { + render( + + + + ); + graph = screen.getByTestId('Traffic Chart').firstChild; + }); + + it('Should render', () => { + expect(screen).toBeTruthy(); + }); + + it('Should render graph', () => { + expect(graph).toBeTruthy(); + }); + + it('Should be alone', () => { + expect(graph.previousSibling).toBe(null); + expect(graph.nextSibling).toBe(null); + }); + + it('Should not scroll', () => { + expect(graph.scrollWidth).toBe(0); + expect(graph.scrollHeight).toBe(0); + expect(graph.scrollLeft).toBe(0); + expect(graph.scrollTop).toBe(0); + }); + + it('Should have width 300, height 300, and white background', () => { + expect(graph._fullLayout.width).toBe(300); + expect(graph._fullLayout.height).toBe(300); + }); + + it('Should have correct data points based off mock data', () => { + expect(graph.calcdata[0][0].isBlank).toBeFalsy(); + expect(graph.data[0].x).toEqual(['customers']); + expect(graph.data[0].y).toEqual([1, 0, 11]); + }); +}); diff --git a/__tests__/components/About.test.tsx b/__tests__/components/About.test.tsx new file mode 100644 index 000000000..38e9167f5 --- /dev/null +++ b/__tests__/components/About.test.tsx @@ -0,0 +1,28 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import About from '../../app/components/About'; +import DashboardContextProvider from '../../app/context/DashboardContext'; + +describe('About Page', () => { + let element; + beforeAll(() => { + render( + + + + ); + element = screen.getByTestId('aboutPage'); + }); + + it('Should have three p tags', () => { + expect(element.querySelectorAll('p').length).toBe(6); + }); + + it('Should have three h3 tags', () => { + expect(element.querySelectorAll('h3').length).toBe(3); + }); + + it('Should have one div', () => { + expect(element.querySelectorAll('div').length).toBe(1); + }); +}); diff --git a/__tests__/components/Contact.test.tsx b/__tests__/components/Contact.test.tsx new file mode 100644 index 000000000..d22330b66 --- /dev/null +++ b/__tests__/components/Contact.test.tsx @@ -0,0 +1,40 @@ +import React from 'react'; +import { render, screen } from '@testing-library/react'; +import Contact from '../../app/components/Contact'; +import DashboardContextProvider from '../../app/context/DashboardContext'; + +describe('Contact Page', () => { + let element; + beforeAll(() => { + render( + + + + ); + element = screen.getByTestId('contactPage'); + }); + + it('Should have two p tags', () => { + expect(element.querySelectorAll('p').length).toBe(2); + }); + + it('Should have a form', () => { + expect(element.querySelectorAll('form').length).toBe(1); + }); + + it('Should have six labels', () => { + expect(element.querySelectorAll('label').length).toBe(6); + }); + + it('Should have one h1 tags', () => { + expect(element.querySelectorAll('h1').length).toBe(1); + }); + + it('Should have three divs', () => { + expect(element.querySelectorAll('div').length).toBe(3); + }); + + it('Should have have two inputs', () => { + expect(element.querySelectorAll('input').length).toBe(6); + }); +}); diff --git a/__tests__/components/CreateAdmin.test.tsx b/__tests__/components/CreateAdmin.test.tsx new file mode 100644 index 000000000..2aad12c6f --- /dev/null +++ b/__tests__/components/CreateAdmin.test.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { ipcRenderer } from 'electron'; +import CreateAdmin from '../../app/components/CreateAdmin'; +import DashboardContextProvider from '../../app/context/DashboardContext'; + +jest.mock('electron', () => ({ ipcRenderer: { sendSync: jest.fn() } })); + +describe('Create Admin Page', () => { + beforeEach(() => { + console.error = jest.fn(); + render( + + + + ); + }); + + it('Should render', () => { + expect(screen).toBeTruthy(); + }); + + it('Should contain an h1, h2, form, button, and three inputs', () => { + const element = screen.getByTestId('CreateAdmin'); + expect(element.querySelectorAll('h1').length).toBe(1); + expect(element.querySelectorAll('h2').length).toBe(1); + expect(element.querySelectorAll('form').length).toBe(1); + expect(element.querySelectorAll('button').length).toBe(1); + expect(element.querySelectorAll('input').length).toBe(3); + }); + + it('Create Account button should submit email, username, and password to addUser', () => { + const username = screen.getByPlaceholderText('enter username'); + const email = screen.getByPlaceholderText('your@email.here'); + const password = screen.getByPlaceholderText('enter password'); + const createAccountButton = screen.getByRole('button', { name: /create account/i }); + + fireEvent.change(email, { target: { value: 'me@gmail.com' } }); + fireEvent.change(username, { target: { value: 'me' } }); + fireEvent.change(password, { target: { value: 'me123' } }); + + fireEvent.click(createAccountButton); + + expect(ipcRenderer.sendSync).toHaveBeenCalledWith('addUser', { + email: 'me@gmail.com', + username: 'me', + password: 'me123', + }); + }); +}); diff --git a/__tests__/components/Header.test.tsx b/__tests__/components/Header.test.tsx new file mode 100644 index 000000000..ada95d59b --- /dev/null +++ b/__tests__/components/Header.test.tsx @@ -0,0 +1,67 @@ +/* eslint-disable import/no-named-as-default */ +/* eslint-disable import/no-named-as-default-member */ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import Header from '../../app/components/Header'; +import { DashboardContext } from '../../app/context/DashboardContext'; +import { ApplicationContext } from '../../app/context/ApplicationContext'; +import { HashRouter as Router } from 'react-router-dom'; +import mockData from '../mock_data.json'; +import '@testing-library/jest-dom'; + +jest.mock('electron', () => ({ + ipcRenderer: { + send: () => jest.fn(), + on: () => mockData, + }, +})); +describe('Speed Chart', () => { + const props = { + app: 'Test DB', + service: 'Test Service', + setLive: jest.fn(), + live: 'false', + }; + let element; + beforeEach(() => { + // const mockData = [ + // { microservice1: 'Test 1'}, + // { microservice2: 'Test 2'}, + // ] + render( + + + +
+ + + + ); + element = screen.getByTestId('Header'); + }); + + it('Should render', () => { + expect(screen).toBeTruthy(); + }); + + it('Should have an h1, no input, and one button', () => { + expect(element.querySelectorAll('h1').length).toBe(1); + expect(element.querySelectorAll('input').length).toBe(0); + expect(element.querySelectorAll('button').length).toBe(1); + }); + + it('Button should have an onClick function', () => { + expect(typeof element.querySelector('button').onclick).toBe('function'); + }); + + // trying to test the functionality of component not passed as props + it('Should check/uncheck the checkbox when clicking services', () => { + // const checkBox = screen.getByRole('checkbox'); + // fireEvent.click(checkBox); + // expect(checkBox.parentElement).toHaveClass('selected'); + // fireEvent.click(checkBox); + // expect(checkBox.parentElement).not.toHaveClass('selected'); + }); + + it('Should also change selectModal to true or false', () => {}); +}); diff --git a/__tests__/components/Login.test.tsx b/__tests__/components/Login.test.tsx new file mode 100644 index 000000000..0d3addfb0 --- /dev/null +++ b/__tests__/components/Login.test.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import { ipcRenderer } from 'electron'; +import Login from '../../app/components/Login'; +import DashboardContextProvider from '../../app/context/DashboardContext'; +import { HashRouter as Router } from 'react-router-dom'; +import '@testing-library/jest-dom'; + +const navigateMock = jest.fn(); + +jest.mock('electron', () => ({ ipcRenderer: { sendSync: jest.fn() } })); +jest.mock('react-router-dom', () => ({ + ...jest.requireActual('react-router-dom'), + useNavigate: () => navigateMock, +})); + +// testing suite for the CreateAdmin.tsx file +describe('Create Admin Page', () => { + beforeEach(() => { + render( + + + + + + ); + navigateMock.mockReset(); + }); + + it('should render', () => { + expect(screen).toBeTruthy(); + }); + + it('Should contain an h1, h2, form, two buttons, and three inputs', () => { + const element = screen.getByTestId('Login'); + expect(element.querySelectorAll('h1').length).toBe(1); + expect(element.querySelectorAll('h2').length).toBe(1); + expect(element.querySelectorAll('form').length).toBe(1); + expect(element.querySelectorAll('button').length).toBe(2); + expect(element.querySelectorAll('input').length).toBe(2); + }); + + it('Login button should submit username and password to addUser', () => { + const usernameInput = screen.getByPlaceholderText('username'); + const passwordInput = screen.getByPlaceholderText('password'); + const loginButton = screen.getByRole('button', { name: /Login/i }); + + fireEvent.change(usernameInput, { target: { value: 'St1nky' } }); + fireEvent.change(passwordInput, { target: { value: 'me123' } }); + + fireEvent.click(loginButton); + expect(ipcRenderer.sendSync).toHaveBeenCalledWith('login', { + username: 'St1nky', + password: 'me123', + }); + }); + + it('Should reroute user to signup', () => { + const button = screen.getByText(/need an account/i); + expect(button).toBeInTheDocument(); + fireEvent.click(button); + + expect(navigateMock).toHaveBeenCalledTimes(1); + }); +}); diff --git a/__tests__/components/Settings.test.tsx b/__tests__/components/Settings.test.tsx new file mode 100644 index 000000000..17c5c3470 --- /dev/null +++ b/__tests__/components/Settings.test.tsx @@ -0,0 +1,27 @@ +import React from 'react'; +import { render, fireEvent, screen } from '@testing-library/react'; +import Settings from '../../app/components/Settings'; +import { DashboardContext } from '../../app/context/DashboardContext'; +import '@testing-library/jest-dom'; + +describe('Settings', () => { + let changeMode = jest.fn(); + + beforeEach(() => { + render( + + + + ); + }); + + test('Should change mode to light mode on light button click', () => { + fireEvent.click(screen.getByRole('button', { name: 'Light' })); + expect(changeMode).toHaveBeenCalledWith('light'); + }); + + test('Should change mode to dark mode on dark button click', () => { + fireEvent.click(screen.getByRole('button', { name: 'Dark' })); + expect(changeMode).toHaveBeenCalledWith('dark'); + }); +}); diff --git a/__tests__/components/SignUp.test.tsx b/__tests__/components/SignUp.test.tsx new file mode 100644 index 000000000..1f75f90a8 --- /dev/null +++ b/__tests__/components/SignUp.test.tsx @@ -0,0 +1,47 @@ +import React from 'react'; +import { render, fireEvent, screen, getByPlaceholderText } from '@testing-library/react'; +import { ipcRenderer } from 'electron'; +import SignUp from '../../app/components/SignUp'; +import DashboardContextProvider from '../../app/context/DashboardContext'; +import { HashRouter as Router } from 'react-router-dom'; + +jest.mock('electron', () => ({ ipcRenderer: { sendSync: jest.fn() } })); + +describe('Create Signup Page', () => { + beforeEach(() => { + render( + + + + + + ); + }); + + it('should render', () => { + expect(screen).toBeTruthy(); + }); + + it('Should contain an h1, h2, form, two buttons, and three inputs', () => { + const element = screen.getByTestId('SignUp'); + expect(element.querySelectorAll('h1').length).toBe(1); + expect(element.querySelectorAll('h2').length).toBe(1); + expect(element.querySelectorAll('form').length).toBe(1); + expect(element.querySelectorAll('button').length).toBe(2); + expect(element.querySelectorAll('input').length).toBe(4); + }); + + it('Sign up button should submit email, username, and password to addUser', async () => { + screen.debug(); + + const username = screen.getByPlaceholderText('enter username'); + const email = screen.getByPlaceholderText('your@email.here'); + const password = screen.getByPlaceholderText('enter password'); + const signupButton = screen.getByRole('signup'); + + fireEvent.change(email, { target: { value: 'me@gmail.com' } }); + fireEvent.change(username, { target: { value: 'me' } }); + fireEvent.change(password, { target: { value: 'me123' } }); + fireEvent.click(signupButton); + }); +}); diff --git a/__tests__/mock_data.json b/__tests__/mock_data.json new file mode 100644 index 000000000..a7a284a46 --- /dev/null +++ b/__tests__/mock_data.json @@ -0,0 +1,17 @@ +[{ + "activememory": [12, 13], + "blockedprocesses": [98, 43], + "cpuloadpercent": [12, 1], + "cpuspeed": [43569, 32], + "cputemp": [5, 32], + "freememory": [55648, 76], + "id": [6, 8], + "service": ["chronos-mock","chronos-mock"], + "latency": [41, 51], + "runningprocesses": [12, 153], + "sleepingprocesses": [312, 33], + "time": ["", ""], + "totalmemory": [1, 4], + "usedmemory": [3, 6] +}] + diff --git a/__tests__/test_settings.json b/__tests__/test_settings.json new file mode 100644 index 000000000..8600797f7 --- /dev/null +++ b/__tests__/test_settings.json @@ -0,0 +1,14 @@ +{ + "services": [ + [ + "chronosDB", + "MongoDB", + "PUT YOUR MONGO URI HERE", + "", + "Dec 19, 2022 4:50 PM" + ] + ], + "mode": "light", + "splash": true, + "landingPage": "dashBoard" +} diff --git a/app/App.tsx b/app/App.tsx new file mode 100644 index 000000000..b51239b56 --- /dev/null +++ b/app/App.tsx @@ -0,0 +1,18 @@ +import React, { useState } from 'react'; +import Splash from './components/Splash'; +import DashboardContainer from './containers/DashboardContainer'; +import './stylesheets/scrollBar.scss'; + + +// this is the fitness gram pacer test +// React memo helps with rendering optimization. The components within React memo will only be rerendered if prompt has changed +const App: React.FC = React.memo(() => { + return ( +
+ + +
+ ); +}); + +export default App; diff --git a/app/actions/actionTypes.js b/app/actions/actionTypes.js deleted file mode 100644 index 41b11a736..000000000 --- a/app/actions/actionTypes.js +++ /dev/null @@ -1,9 +0,0 @@ -/** - * ************************************ - * - * @module actionTypes.js - * @description Action Type Constants - * - * ************************************ - */ - diff --git a/app/actions/actions.js b/app/actions/actions.js deleted file mode 100644 index e1d955640..000000000 --- a/app/actions/actions.js +++ /dev/null @@ -1,17 +0,0 @@ -/** - * ************************************ - * - * @module actions.js - * @description Action Creators - * - * ************************************ - */ - -//Import all types from constant file (actionTypes); -import * as types from "./actionTypes"; - -//For reducer to grab and use; -export const addMessage = message => ({ - type: types.ADD_MESSAGE, - payload: message // Data OBJECT we sent from the server; -}); diff --git a/app/assets/AntDesign.svg b/app/assets/AntDesign.svg new file mode 100644 index 000000000..56c997a54 --- /dev/null +++ b/app/assets/AntDesign.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/C.svg b/app/assets/C.svg new file mode 100644 index 000000000..83f0bc243 --- /dev/null +++ b/app/assets/C.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/Chronos-Demo-poster.png b/app/assets/Chronos-Demo-poster.png new file mode 100644 index 000000000..e1ce9e813 Binary files /dev/null and b/app/assets/Chronos-Demo-poster.png differ diff --git a/app/assets/Chronos-Demo.gif b/app/assets/Chronos-Demo.gif new file mode 100644 index 000000000..e2aad4ddd Binary files /dev/null and b/app/assets/Chronos-Demo.gif differ diff --git a/app/assets/admin_approval.gif b/app/assets/admin_approval.gif new file mode 100644 index 000000000..674e65c1b Binary files /dev/null and b/app/assets/admin_approval.gif differ diff --git a/app/assets/animated_logo.gif b/app/assets/animated_logo.gif new file mode 100644 index 000000000..a06db77a8 Binary files /dev/null and b/app/assets/animated_logo.gif differ diff --git a/app/assets/apple-icon-black.png b/app/assets/apple-icon-black.png new file mode 100644 index 000000000..64c373af9 Binary files /dev/null and b/app/assets/apple-icon-black.png differ diff --git a/app/assets/aws-icon-white.png b/app/assets/aws-icon-white.png new file mode 100644 index 000000000..8532571dd Binary files /dev/null and b/app/assets/aws-icon-white.png differ diff --git a/app/assets/aws-icon-whitebg.png b/app/assets/aws-icon-whitebg.png new file mode 100644 index 000000000..004fcf1ae Binary files /dev/null and b/app/assets/aws-icon-whitebg.png differ diff --git a/app/assets/aws-logo-color.png b/app/assets/aws-logo-color.png new file mode 100644 index 000000000..c84fae753 Binary files /dev/null and b/app/assets/aws-logo-color.png differ diff --git a/app/assets/clean-sprout.gif b/app/assets/clean-sprout.gif new file mode 100644 index 000000000..78ed457f7 Binary files /dev/null and b/app/assets/clean-sprout.gif differ diff --git a/app/assets/disable_sign_up.gif b/app/assets/disable_sign_up.gif new file mode 100644 index 000000000..4dcb0b7f3 Binary files /dev/null and b/app/assets/disable_sign_up.gif differ diff --git a/app/assets/docker-logo-color.png b/app/assets/docker-logo-color.png new file mode 100644 index 000000000..5f290e448 Binary files /dev/null and b/app/assets/docker-logo-color.png differ diff --git a/app/assets/dotenvSetup.png b/app/assets/dotenvSetup.png new file mode 100644 index 000000000..c775cfe41 Binary files /dev/null and b/app/assets/dotenvSetup.png differ diff --git a/app/assets/dropdown-arrow.png b/app/assets/dropdown-arrow.png new file mode 100644 index 000000000..e23260a3e Binary files /dev/null and b/app/assets/dropdown-arrow.png differ diff --git a/app/assets/electron-logo-color.png b/app/assets/electron-logo-color.png new file mode 100644 index 000000000..266c24c50 Binary files /dev/null and b/app/assets/electron-logo-color.png differ diff --git a/app/assets/email-icon-black.png b/app/assets/email-icon-black.png new file mode 100644 index 000000000..3f7fa1e2e Binary files /dev/null and b/app/assets/email-icon-black.png differ diff --git a/app/assets/enable_sign_up.gif b/app/assets/enable_sign_up.gif new file mode 100644 index 000000000..f36de8346 Binary files /dev/null and b/app/assets/enable_sign_up.gif differ diff --git a/app/assets/enzyme-logo-color.png b/app/assets/enzyme-logo-color.png new file mode 100644 index 000000000..243bc3dcd Binary files /dev/null and b/app/assets/enzyme-logo-color.png differ diff --git a/app/assets/express-logo-color.png b/app/assets/express-logo-color.png new file mode 100644 index 000000000..c58bb46a0 Binary files /dev/null and b/app/assets/express-logo-color.png differ diff --git a/app/assets/fire.png b/app/assets/fire.png new file mode 100644 index 000000000..b4152f9a2 Binary files /dev/null and b/app/assets/fire.png differ diff --git a/app/assets/graphql-logo-color.png b/app/assets/graphql-logo-color.png new file mode 100644 index 000000000..7a9d349d0 Binary files /dev/null and b/app/assets/graphql-logo-color.png differ diff --git a/app/assets/graphs.gif b/app/assets/graphs.gif new file mode 100644 index 000000000..226025eea Binary files /dev/null and b/app/assets/graphs.gif differ diff --git a/app/assets/growing-bean-1.gif b/app/assets/growing-bean-1.gif new file mode 100644 index 000000000..b3fc35e8d Binary files /dev/null and b/app/assets/growing-bean-1.gif differ diff --git a/app/assets/growing-bean-2.gif b/app/assets/growing-bean-2.gif new file mode 100644 index 000000000..12e962baf Binary files /dev/null and b/app/assets/growing-bean-2.gif differ diff --git a/app/assets/growing-bean-3.gif b/app/assets/growing-bean-3.gif new file mode 100644 index 000000000..a8a397960 Binary files /dev/null and b/app/assets/growing-bean-3.gif differ diff --git a/app/assets/grpc-logo-color.png b/app/assets/grpc-logo-color.png new file mode 100644 index 000000000..960fc1296 Binary files /dev/null and b/app/assets/grpc-logo-color.png differ diff --git a/app/assets/http-logo-color.png b/app/assets/http-logo-color.png new file mode 100644 index 000000000..e8bebcc3f Binary files /dev/null and b/app/assets/http-logo-color.png differ diff --git a/app/assets/important.png b/app/assets/important.png new file mode 100644 index 000000000..cd2d1e220 Binary files /dev/null and b/app/assets/important.png differ diff --git a/app/assets/jest-logo-color.png b/app/assets/jest-logo-color.png new file mode 100644 index 000000000..6a1c4e821 Binary files /dev/null and b/app/assets/jest-logo-color.png differ diff --git a/app/assets/js-logo-color.png b/app/assets/js-logo-color.png new file mode 100644 index 000000000..6a83dd367 Binary files /dev/null and b/app/assets/js-logo-color.png differ diff --git a/app/assets/logo.svg b/app/assets/logo.svg new file mode 100644 index 000000000..eee791654 --- /dev/null +++ b/app/assets/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/macbook-logo-color.png b/app/assets/macbook-logo-color.png new file mode 100644 index 000000000..c664b3983 Binary files /dev/null and b/app/assets/macbook-logo-color.png differ diff --git a/app/assets/material-ui-logo-color.png b/app/assets/material-ui-logo-color.png new file mode 100644 index 000000000..e40e5b66c Binary files /dev/null and b/app/assets/material-ui-logo-color.png differ diff --git a/app/assets/mit-logo-color.png b/app/assets/mit-logo-color.png new file mode 100644 index 000000000..f572c74b2 Binary files /dev/null and b/app/assets/mit-logo-color.png differ diff --git a/app/assets/mongo-icon-color.png b/app/assets/mongo-icon-color.png new file mode 100644 index 000000000..ca049ca27 Binary files /dev/null and b/app/assets/mongo-icon-color.png differ diff --git a/app/assets/mongo-icon-green-light.png b/app/assets/mongo-icon-green-light.png new file mode 100644 index 000000000..8ae1c1165 Binary files /dev/null and b/app/assets/mongo-icon-green-light.png differ diff --git a/app/assets/mongo-icon-green.png b/app/assets/mongo-icon-green.png new file mode 100644 index 000000000..31004b55a Binary files /dev/null and b/app/assets/mongo-icon-green.png differ diff --git a/app/assets/mongo-icon-white.png b/app/assets/mongo-icon-white.png new file mode 100644 index 000000000..02a03b592 Binary files /dev/null and b/app/assets/mongo-icon-white.png differ diff --git a/app/assets/mongo-logo-color.png b/app/assets/mongo-logo-color.png new file mode 100644 index 000000000..dea317a6f Binary files /dev/null and b/app/assets/mongo-logo-color.png differ diff --git a/app/assets/mountain.png b/app/assets/mountain.png new file mode 100644 index 000000000..0e4d7a81f Binary files /dev/null and b/app/assets/mountain.png differ diff --git a/app/assets/mountain_longer.png b/app/assets/mountain_longer.png new file mode 100644 index 000000000..bebb4c551 Binary files /dev/null and b/app/assets/mountain_longer.png differ diff --git a/app/assets/node-logo-color.png b/app/assets/node-logo-color.png new file mode 100644 index 000000000..3d48409d4 Binary files /dev/null and b/app/assets/node-logo-color.png differ diff --git a/app/assets/npm-logo-color.png b/app/assets/npm-logo-color.png new file mode 100644 index 000000000..f6d6c14fe Binary files /dev/null and b/app/assets/npm-logo-color.png differ diff --git a/app/assets/plotly-logo-color.png b/app/assets/plotly-logo-color.png new file mode 100644 index 000000000..7ebfd8398 Binary files /dev/null and b/app/assets/plotly-logo-color.png differ diff --git a/app/assets/pngwing.com 2.png b/app/assets/pngwing.com 2.png new file mode 100644 index 000000000..44c2ff38d Binary files /dev/null and b/app/assets/pngwing.com 2.png differ diff --git a/app/assets/pngwing.com.png b/app/assets/pngwing.com.png new file mode 100644 index 000000000..44c2ff38d Binary files /dev/null and b/app/assets/pngwing.com.png differ diff --git a/app/assets/postgres-icon-white.png b/app/assets/postgres-icon-white.png new file mode 100644 index 000000000..84e0605b0 Binary files /dev/null and b/app/assets/postgres-icon-white.png differ diff --git a/app/assets/postgres-icon-yellow-light.png b/app/assets/postgres-icon-yellow-light.png new file mode 100644 index 000000000..75755532a Binary files /dev/null and b/app/assets/postgres-icon-yellow-light.png differ diff --git a/app/assets/postgres-icon-yellow.png b/app/assets/postgres-icon-yellow.png new file mode 100644 index 000000000..7de3144ea Binary files /dev/null and b/app/assets/postgres-icon-yellow.png differ diff --git a/app/assets/postgres-logo-color.png b/app/assets/postgres-logo-color.png new file mode 100644 index 000000000..d8e6c1212 Binary files /dev/null and b/app/assets/postgres-logo-color.png differ diff --git a/app/assets/query_tool.gif b/app/assets/query_tool.gif new file mode 100644 index 000000000..ec5e1ba80 Binary files /dev/null and b/app/assets/query_tool.gif differ diff --git a/app/assets/react-logo-color.png b/app/assets/react-logo-color.png new file mode 100644 index 000000000..b68893ceb Binary files /dev/null and b/app/assets/react-logo-color.png differ diff --git a/app/assets/search.svg b/app/assets/search.svg new file mode 100644 index 000000000..cd27601f4 --- /dev/null +++ b/app/assets/search.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/app/assets/slack-logo-color.png b/app/assets/slack-logo-color.png new file mode 100644 index 000000000..524b6da35 Binary files /dev/null and b/app/assets/slack-logo-color.png differ diff --git a/app/assets/spectron-logo-color.png b/app/assets/spectron-logo-color.png new file mode 100644 index 000000000..c75ec6e86 Binary files /dev/null and b/app/assets/spectron-logo-color.png differ diff --git a/app/assets/ts-logo-color.png b/app/assets/ts-logo-color.png new file mode 100644 index 000000000..007f4237b Binary files /dev/null and b/app/assets/ts-logo-color.png differ diff --git a/app/assets/ts-logo-long-blue.png b/app/assets/ts-logo-long-blue.png new file mode 100644 index 000000000..cbcd5846b Binary files /dev/null and b/app/assets/ts-logo-long-blue.png differ diff --git a/app/assets/ts-logo-long.png b/app/assets/ts-logo-long.png new file mode 100644 index 000000000..670b70a67 Binary files /dev/null and b/app/assets/ts-logo-long.png differ diff --git a/app/assets/vis-logo-color.png b/app/assets/vis-logo-color.png new file mode 100644 index 000000000..ee5292b88 Binary files /dev/null and b/app/assets/vis-logo-color.png differ diff --git a/app/assets/webpack-logo-color.png b/app/assets/webpack-logo-color.png new file mode 100644 index 000000000..8f9f703d6 Binary files /dev/null and b/app/assets/webpack-logo-color.png differ diff --git a/app/charts/AwsChart.tsx b/app/charts/AwsChart.tsx new file mode 100644 index 000000000..a0cc65312 --- /dev/null +++ b/app/charts/AwsChart.tsx @@ -0,0 +1,103 @@ +import moment from 'moment'; +import React, { useState } from 'react'; +import Plot from 'react-plotly.js'; +import { all, solo as soloStyle } from './sizeSwitch'; + +// interface AwsCpuChartProps { +// key: string; +// renderService: string; +// metric: string; +// timeList: any; +// valueList: any; +// sizing: string; +// colourGenerator: Function; +// } + +interface SoloStyles { + height: number; + width: number; +} + +/** + * @params props - the props object containing relevant data. + * @desc Handles AWS Charts. Memoized component to generate an AWS chart with formatted data. + * @returns {JSX.Element} The JSX element with the AWS chart. + */ +const AwsChart: React.FC = React.memo(props => { + const { renderService, metric, timeList, valueList, colourGenerator, sizing } = props; + const [solo, setSolo] = useState(null); + setInterval(() => { + if (solo !== soloStyle) { + setSolo(soloStyle); + } + }, 20); + + const createChart = () => { + const timeArr = timeList?.map((el: any) => moment(el).format('kk:mm:ss')); + // const hashedColour = colourGenerator(renderService); + let plotlyData: { + name: any; + x: any; + y: any; + type: any; + mode: any; + marker: { color: string }; + }; + plotlyData = { + name: metric, + x: timeArr, + y: valueList, + type: 'scattergl', + mode: 'lines', + marker: { color: colourGenerator() }, + }; + const sizeSwitch = sizing === 'all' ? all : solo; + + return ( + + ); + }; + + return ( +
+ {createChart()} +
+ ); +}); + +export default AwsChart; diff --git a/app/charts/DockerChart.tsx b/app/charts/DockerChart.tsx new file mode 100644 index 000000000..34007cd9f --- /dev/null +++ b/app/charts/DockerChart.tsx @@ -0,0 +1,66 @@ +/** From Version 5.2 Team: + * No functional changes; just fixed linting errors. + * Did not test or make use of any Docker data. + */ + +import React, { useContext } from 'react'; + +import moment from 'moment'; +import { DockerContext } from '../context/DockerContext'; + +interface IContainer { + containername: string; + containerid: string; + platform: string; + starttime: string; + memoryusage: number; + memorylimit: number; + memorypercent: number; + cpupercent: number; + networkreceived: number; + networksent: number; + processcount: number; + restartcount: number; +} + +/** + * Table that displays real-time Docker container information using the + * latest data point available ??? + */ +const DockerStatsChart = React.memo(() => { + const { dockerData } = useContext(DockerContext); + + const { + containername, + containerid, + platform, + starttime, + memoryusage, + memorylimit, + memorypercent, + cpupercent, + networkreceived, + networksent, + processcount, + restartcount, + }: IContainer = dockerData; + + return containerid ? ( +
+

Docker Container Stats

+ Container Name: {containername} + Platform: {platform} + Start time: {moment(starttime).format('LLL')} + Memory Usage: {(memoryusage / 1000000).toFixed(2)} + Memory Limit: {(memorylimit / 1000000).toFixed(2)} + Memory Percent: {memorypercent.toFixed(2)}% + CPU percent: {cpupercent.toFixed(2)}% + Network I/O - Received (Kb): {networkreceived / 1000} + Network I/O - Sent (Kb): {networksent / 1000} + Process Count: {processcount} + Restart Count: {restartcount} +
+ ) : null; +}); + +export default DockerStatsChart; diff --git a/app/charts/EventChart.tsx b/app/charts/EventChart.tsx new file mode 100644 index 000000000..28b50d9b9 --- /dev/null +++ b/app/charts/EventChart.tsx @@ -0,0 +1,117 @@ +import moment from 'moment'; +import React, { useState } from 'react'; +import Plot from 'react-plotly.js'; +import { all, solo as soloStyle } from './sizeSwitch'; + +interface EventChartProps { + key: string; + metricName: string; + chartData: { + value: string[], + time: string[] + } + sizing: string; + colourGenerator: Function; +} + +interface SoloStyles { + height: number; + width: number; +} + +type PlotlyData = { + name: string; + x: string[]; + y: string[]; + type: string; + mode: string; + marker: { colors: string[] }; +}; + +/** + * @params {EventChartProps} props - the props object containing relevant data. + * @desc Handles k8s metrics. Memoized component to generate event chart with formatted data + * @returns {JSX.Element} The JSX element with the event chart. + */ +const EventChart: React.FC = React.memo(props => { + const { metricName, chartData, sizing, colourGenerator } = props; + const [solo, setSolo] = useState(null); + + setInterval(() => { + if (solo !== soloStyle) { + setSolo(soloStyle); + } + }, 20); + + // makes time data human-readable, and reverses it so it shows up correctly in the graph + const prettyTimeInReverse = (timeArray: string[]): string[] => { + return timeArray.map((el: any) => moment(el).format('kk:mm:ss')).reverse(); + }; + + // removes underscores from metric names to improve their look in the graph + const prettyMetricName = (metricName: string): string => { + const newName = metricName.replace(/kubernetes-cadvisor\/docker-desktop\//g, ''); + return newName.replace(/_/g, ' '); + }; + + const createChart = () => { + const prettyName = prettyMetricName(metricName); + const prettyTime = prettyTimeInReverse(chartData.time); + + const plotlyDataObject: PlotlyData = { + name: prettyName, + x: prettyTime, + y: chartData.value, + type: 'scattergl', + mode: 'lines', + marker: { + colors: ['#fc4039', '#4b54ea', '#32b44f', '#3788fc', '#9c27b0', '#febc2c'], + }, + }; + const sizeSwitch = sizing === 'all' ? all : solo; + + return ( + + ); + }; + + return ( +
+ {createChart()} +
+ ); +}); + +export default EventChart; diff --git a/app/charts/HealthChart.tsx b/app/charts/HealthChart.tsx new file mode 100644 index 000000000..54768ca36 --- /dev/null +++ b/app/charts/HealthChart.tsx @@ -0,0 +1,136 @@ +import moment from 'moment'; +import React, { useState } from 'react'; +import Plot from 'react-plotly.js'; +import { all, solo as soloStyle } from './sizeSwitch'; + +interface HealthChartProps { + key: string; + dataType: string; + serviceName: string; + chartData: object; + categoryName: string; + sizing: string; + colourGenerator: Function; +} +interface SoloStyles { + height: number; + width: number; +} +type PlotlyData = { + name: string; + x: string[]; + y: string[]; + type: string; + mode: string; + marker: { colors: string[] }; +}; + +/** + * @params {HealthChartProps} props - the props object containing relevant data. + * @desc Handles microservices metrics. Memoized component to generate a health chart with formatted data. + * @returns {JSX.Element} The JSX element with the health chart. + */ +const HealthChart: React.FC = React.memo(props => { + const { dataType, serviceName, chartData, categoryName, sizing, colourGenerator } = props; + const [solo, setSolo] = useState(null); + + // makes time data human-readable, and reverses it so it shows up correctly in the graph + const prettyTimeInReverse = (timeArray: string[]): string[] => { + return timeArray.map((el: any) => moment(el).format('kk:mm:ss')).reverse(); + }; + + // removes underscores from metric names to improve their look in the graph + const prettyMetricName = (metricName: string): string => { + return metricName.replace(/_/g, ' '); + }; + + // generates an array of plotly data objects to be passed into our plotly chart's data prop + const generatePlotlyDataObjects = (chartDataObj: object): object[] => { + const arrayOfPlotlyDataObjects: PlotlyData[] = []; + + // loop through the list of metrics for the current service + for (const metricName in chartDataObj) { + // define the value and time arrays; allow data to be reassignable in case we need to convert the bytes data into megabytes + let dataArray = chartDataObj[metricName].value; + const timeArray = chartDataObj[metricName].time; + // specifically for `Megabyte` types, convert the original data of bytes into a value of megabytes before graphing + if (dataType === 'Memory in Megabytes' || dataType === 'Cache in Megabytes') { + dataArray = dataArray.map(value => (value / 1000000).toFixed(2)); + } + // create the plotly object + const plotlyDataObject: PlotlyData = { + name: prettyMetricName(metricName), + x: prettyTimeInReverse(timeArray), + y: dataArray, + type: 'scattergl', + mode: 'lines', + marker: { + colors: ['#fc4039', '#4b54ea', '#32b44f', '#3788fc', '#9c27b0', '#febc2c'], + }, + }; + // push the plotlyDataObject into the arrayOfPlotlyDataObjects + arrayOfPlotlyDataObjects.push(plotlyDataObject); + } + // return the array of plotlyDataObject + return arrayOfPlotlyDataObjects; + }; + + setInterval(() => { + if (solo !== soloStyle) { + setSolo(soloStyle); + } + }, 20); + + /** + * @desc Takes the chart data and configures Plotly object to render associated health chart. + * @returns {JSX.Element} Configured Plotly object representing health chart. + */ + const createChart = () => { + const dataArray = generatePlotlyDataObjects(chartData); + const sizeSwitch = sizing === 'all' ? all : solo; + + return ( + + ); + }; + + return ( +
+ {createChart()} +
+ ); +}); + +export default HealthChart; diff --git a/app/charts/LogsTable.jsx b/app/charts/LogsTable.jsx new file mode 100644 index 000000000..b5fbc5292 --- /dev/null +++ b/app/charts/LogsTable.jsx @@ -0,0 +1,169 @@ +/** From Version 5.2 Team: + * We didn't really touch this file; mostly just cleaned a little bit of linting errors... + * But we left all the 'Missing "key" props,' 'Prop spreading is forbidden,' and 'Do not nest ternary expressions.' + */ + +import React, { useContext } from 'react'; +import styled from 'styled-components'; +import { useTable, useGroupBy, useExpanded } from 'react-table'; + +import { CommsContext } from '../context/CommsContext'; + +/** + * Styling for the Logs Table + */ + +const Styles = styled.div` + padding: 1rem; + + table { + border-spacing: 0; + border: 1px solid black; + + tr { + :last-child { + td { + border-bottom: 0; + } + } + } + + th, + td { + margin: 0; + padding: 0.5rem; + border-bottom: 1px solid black; + border-right: 1px solid black; + + :last-child { + border-right: 0; + } + } + } +`; + +const Table = ({ columns, data }) => { + const { + getTableProps, + getTableBodyProps, + headerGroups, + rows, + prepareRow, + state: { groupBy, expanded }, + } = useTable({ columns, data }, useGroupBy, useExpanded) + + const numberOfRows = 20; + const firstPageRows = rows.slice(0, numberOfRows); + + return ( +
+ + + {headerGroups.map(headerGroup => ( + + {headerGroup.headers.map((column) => ( + + ))} + + ))} + + + {firstPageRows.map(row => { + prepareRow(row); + return ( + + {row.cells.map(cell => { + return ( + + ) + })} + + ); + })} + +
+ {column.canGroupBy ? ( + + {column.isGrouped ? '➖ ' : '➕ '} + + ): null} + {column.render('Header')} +
+ {cell.isGrouped ? ( + <> + + {row.isExpanded ? '⬇️' : '➡️'} + {' '} + {cell.render('Cell')} ({row.subRows.length}) + + ) : cell.isAggregated ? ( + cell.render('Aggregated') + ) : cell.isPlaceholder ? null : ( + cell.render('Cell') + )} +
+
+ Showing the first {rows.length} of {rows.length} rows +
+
+ ); +}; + +const LogsTable = () => { + const columns = React.useMemo( + () => [ + { + Header: 'Time', + accessor: 'time', + }, + { + Header: 'CorrelatingID', + accessor: 'correlatingid', + aggregate: 'count', + Aggregated: ({ value }) => `${value} logs`, + }, + { + Header: 'Microservice', + accessor: 'microservice', + }, + { + Header: 'Endpoint', + accessor: 'endpoint', + }, + { + Header: 'Request Method', + accessor: 'request', + }, + { + Header: 'Response Status', + accessor: 'responsestatus', + }, + { + Header: 'Response Message', + accessor: 'responsemessage', + }, + ], + [] + ); + + const data = useContext(CommsContext).commsData; + + return ( + + + + ); +}; + +export default LogsTable; diff --git a/app/charts/RequestTypesChart.tsx b/app/charts/RequestTypesChart.tsx new file mode 100644 index 000000000..816cc47e1 --- /dev/null +++ b/app/charts/RequestTypesChart.tsx @@ -0,0 +1,85 @@ +import React, { useContext } from 'react'; +import Plot from 'react-plotly.js'; +import { CommsContext } from '../context/CommsContext'; +import '../stylesheets/constants.scss'; + +const RequestTypesChart: React.FC = React.memo(() => { + const { commsData } = useContext(CommsContext); + + interface IObject { + correlatingid: string; + endpoint: string; + id: number; + microservice: string; + request: string; + responsemessage: string; + responsestatus: string; + time: string; + } + const createRequestChart = () => { + const requestTypes: { [key: string]: number } = { + DELETE: 0, + GET: 0, + PATCH: 0, + POST: 0, + PUSH: 0, + PUT: 0, + }; + + let type; + commsData.forEach((obj: IObject) => { + type = obj.request; + if (type in requestTypes) { + requestTypes[type] += 1; + } else { + requestTypes[type] = 0; + requestTypes[type]++; + } + }); + + return ( + + ); + }; + + return
{createRequestChart()}
; +}); + +export default RequestTypesChart; diff --git a/app/charts/ResponseCodesChart.tsx b/app/charts/ResponseCodesChart.tsx new file mode 100644 index 000000000..b61b85d4e --- /dev/null +++ b/app/charts/ResponseCodesChart.tsx @@ -0,0 +1,136 @@ +/** From Version 5.2 Team: + * We only cleaned up the linting errors + * Did not change any functionality in this page + */ + +import React, { useContext } from 'react'; +import Plot from 'react-plotly.js'; +import { CommsContext } from '../context/CommsContext'; + +interface IObj { + correlatingid: string; + endpoint: string; + id: number; + microservice: string; + request: string; + responsemessage: string; + responsestatus: number; + time: string; +} + +const ResponseCodesChart: React.FC = React.memo(() => { + const { commsData } = useContext(CommsContext); + + const createChart = () => { + const responseCodes: { [key: string]: number } = { + '100-199': 0, + '200-299': 0, + '300-399': 0, + '400-499': 0, + '500-599': 0, + '0': 0, + '1': 0, + '2': 0, + '3': 0, + '4': 0, + '5': 0, + '6': 0, + '7': 0, + '8': 0, + '9': 0, + '10': 0, + '11': 0, + '12': 0, + '13': 0, + '14': 0, + '15': 0, + '16': 0, + }; + + commsData.forEach((obj: IObj) => { + const status = obj.responsestatus; + if (status >= 500) { + responseCodes['500-599'] += 1; + } else if (status >= 400) { + responseCodes['400-499'] += 1; + } else if (status >= 300) { + responseCodes['300-399'] += 1; + } else if (status >= 200) { + responseCodes['200-299'] += 1; + } else if (status >= 100) { + responseCodes['100-199'] += 1; + } else { + responseCodes[status] += 1; + } + }); + + return ( + + ); + }; + + return
{createChart()}
; +}); + +export default ResponseCodesChart; diff --git a/app/charts/RouteChart.jsx b/app/charts/RouteChart.jsx new file mode 100644 index 000000000..ae7448101 --- /dev/null +++ b/app/charts/RouteChart.jsx @@ -0,0 +1,185 @@ +/** From Version 5.2 Team: + * This file does not seem to be showing any data. + * Hope a future team figures it out. + * Best of luck! + */ + +import { makeStyles } from '@material-ui/core/styles'; +import React, { useContext } from 'react'; +import Graph from 'react-graph-vis'; +import { CommsContext } from '../context/CommsContext'; + +const RouteChart = React.memo(() => { + const communicationsData = useContext(CommsContext).commsData; + const resObj = {}; + const dataId = '_id'; + + if (communicationsData.length > 0 && communicationsData[0][dataId]) { + /** From Version 5.2 Team: + * @communicationsData comes back as an array with data in descending order. + * The below sorts it back into ascending order. + */ + + communicationsData.sort((a, b) => { + if (new Date(a.time) > new Date(b.time)) return 1; + if (new Date(a.time) < new Date(b.time)) return -1; + return 0; + }); + for (let i = 0; i < communicationsData.length; i += 1) { + const element = communicationsData[i]; + if (!resObj[element.correlatingid]) resObj[element.correlatingid] = []; + resObj[element.correlatingid].push({ + microservice: element.microservice, + time: element.time, + request: element.request, + }); + } + } else { + for (let i = communicationsData.length - 1; i >= 0; i--) { + const element = communicationsData[i]; + if (resObj[element.correlatingid]) { + resObj[element.correlatingid].push({ + microservice: element.microservice, + time: element.time, + }); + } else { + resObj[element.correlatingid] = [ + { + microservice: element.microservice, + time: element.time, + }, + ]; + } + } + } + + const tracePoints = Object.values(resObj).filter(subArray => subArray.length > 1); + + const useStyles = makeStyles(theme => ({ + paper: { + height: 300, + width: 300, + textAlign: 'center', + color: '#444d56', + whiteSpace: 'nowrap', + backgroundColor: '#ffffff', + borderRadius: 3, + border: '0', + boxShadow: '2px 2px 6px #bbbbbb', + }, + })); + const classes = useStyles({}); + + /** + * Graph Logic Below + */ + + const nodeListObj = {}; + const edgeListObj = {}; + for (const route of tracePoints) { + for (let i = 0; i < route.length; i += 1) { + const id = route[i].microservice; + if (nodeListObj[id] === undefined) { + nodeListObj[id] = { + id, + label: id, + }; + } + + if (i !== 0) { + const from = route[i - 1].microservice; + const to = id; + const { request } = route[i - 1]; + const edgeStr = JSON.stringify({ from, to, request }); + let duration = new Date(route[i].time) - new Date(route[i - 1].time); + + if (edgeListObj[edgeStr]) { + duration = (duration + edgeListObj[edgeStr]) / 2; + } + edgeListObj[edgeStr] = duration; + } + } + } + + const nodeList = Object.values(nodeListObj); + const edgeList = []; + for (const [edgeStr, duration] of Object.entries(edgeListObj)) { + const edge = JSON.parse(edgeStr); + edge.label = edge.request + ? `${edge.request} - ${(duration * 10).toFixed(0)} ms` + : `${(duration * 10).toFixed(0)} ms`; + edgeList.push(edge); + } + + const graph = { + nodes: nodeList, + edges: edgeList, + }; + const options = { + height: '600px', + width: '600px', + layout: { + hierarchical: false, + }, + edges: { + color: '#444d56', + physics: true, + smooth: { + type: 'curvedCCW', + forceDirection: 'none', + roundness: 0.3, + }, + font: { + color: '#444d56', + size: 9, + }, + }, + nodes: { + borderWidth: 0, + color: { + background: '#3788fc', + hover: { + background: '#febc2c', + }, + highlight: { + background: '#fc4039', + }, + }, + shape: 'circle', + font: { + color: '#ffffff', + size: 10, + face: 'roboto', + }, + }, + }; + + // const events = { + // select(event) { + // const { nodes, edges } = event; + // }, + // }; + + if (communicationsData.length > 0 && communicationsData[0].endpoint !== '/') { + return ( +
+ Route Traces + +
+ ); + } + return null; +}); + +export default RouteChart; diff --git a/app/charts/TrafficChart.tsx b/app/charts/TrafficChart.tsx new file mode 100644 index 000000000..0afc67653 --- /dev/null +++ b/app/charts/TrafficChart.tsx @@ -0,0 +1,65 @@ +import React, { useContext } from 'react'; +import Plot from 'react-plotly.js'; +import { CommsContext } from '../context/CommsContext'; + +const TrafficChart = React.memo(() => { + const { commsData } = useContext(CommsContext); + const microserviceCount: { [key: string]: number } = {}; + + for (let i = 0; i < commsData.length; i += 1) { + const curr = commsData[i].microservice; + if (!microserviceCount[curr]) microserviceCount[curr] = 0; + microserviceCount[curr] += 1; + } + + const xAxis = Object.keys(microserviceCount); + + const serverPings: number[] = Object.values(microserviceCount); + + const yAxisHeadRoom: number = Math.max(...serverPings) + 10; + + return ( +
+ +
+ ); +}); + +export default TrafficChart; diff --git a/app/charts/sizeSwitch.js b/app/charts/sizeSwitch.js new file mode 100644 index 000000000..78d1399e1 --- /dev/null +++ b/app/charts/sizeSwitch.js @@ -0,0 +1,24 @@ +let soloWidth = window.innerWidth > 800 ? 800 : window.innerWidth - 270; + +/** From Version 5.2 Team: + * @solo needs to be mutable, but eslint doesn't like exporting mutable variables + */ + +// eslint-disable-next-line import/no-mutable-exports +export let solo = { + height: 600, + width: soloWidth, +}; + +window.addEventListener('resize', () => { + soloWidth = window.innerWidth > 800 ? 800 : window.innerWidth - 270; + solo = { + ...solo, + width: soloWidth, + }; +}); + +export const all = { + height: 400, + width: 400, +}; diff --git a/app/components/About.tsx b/app/components/About.tsx new file mode 100644 index 000000000..7cbdc8ea1 --- /dev/null +++ b/app/components/About.tsx @@ -0,0 +1,60 @@ +import React, { useContext } from 'react'; +import '../stylesheets/About.scss'; +import * as DashboardContext from '../context/DashboardContext'; +import lightAndDark from './Styling'; + +const About: React.FC = React.memo(() => { + const { mode } = useContext(DashboardContext.DashboardContext); + + const currentMode = + mode === 'light' ? lightAndDark.lightModeText : lightAndDark.darkModeText; + + /** + * Enter your OSP group's names into the explicit array of names in nameArray, and it will render them Chronos appropriately. + * Feel free to change the header for the list of names. + * + */ + const nameArray: JSX.Element[] = ['Brisa', 'Kelsi', 'Lucie', 'Jeffrey', 'Justin'].map(name => { + return ( + +

{`${name}`}

+
+ ); + }); + + return ( +
+
+

+ About +

+

+ The Chronos Team has a passion for building tools that are powerful, beautifully + designed, and easy to use. Chronos was conceived as an Open Source endeavor that directly benefits the developer + community. Together, the Chronos application and NPM package make up an all-in-one network and health monitoring + tool for your containerized or non-conatinerized applications or microservices. It can also + monitor applications deployed using AWS, EC2, and ECS from Amazon. +

+

+

+ Current Version Authors +

+
+ {nameArray} +
+
+

+ Past Contributors +

+

+ Snow, Taylor, Tim, Roberto, Nachiket, Tiffany, Bruno, Danny, Vince, Matt, Derek, Kit, + Grace, Jen, Patty, Stella, Michael, Ronelle, Todd, Greg, Brianna, Brian, Alon, Alan, + Ousman, Ben, Chris, Jenae, Tim, Kirk, Jess, William, Alexander, Elisa, Josh, Troy, Gahl +

+
+
+
+ ); +}); + +export default About; diff --git a/app/components/AwaitingApproval.tsx b/app/components/AwaitingApproval.tsx new file mode 100644 index 000000000..3b503b8a8 --- /dev/null +++ b/app/components/AwaitingApproval.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { useNavigate } from 'react-router-dom'; + +// THIS FILE IS NOT DOING ANYTHING RIGHT NOW +const AwaitingApproval: React.FC = () => { + const navigate = useNavigate(); + const reroute = () => navigate('/'); + return ( +
+

+ Your account is awaiting approval. Please contact your administrator if you have any + questions. +

+
+ +
+ ); +} + +export default AwaitingApproval; diff --git a/app/components/AwsEC2Graphs.tsx b/app/components/AwsEC2Graphs.tsx new file mode 100644 index 000000000..2bedae829 --- /dev/null +++ b/app/components/AwsEC2Graphs.tsx @@ -0,0 +1,54 @@ +import React, { useContext, useEffect, useState } from 'react'; +import AwsChart from '../charts/AwsChart'; +import { AwsContext } from '../context/AwsContext'; +import { CircularProgress } from '@material-ui/core'; +import zIndex from '@material-ui/core/styles/zIndex'; + +const AwsEC2Graphs: React.FC = React.memo(props => { + const { awsData, setAwsData, isLoading, setLoadingState } = useContext(AwsContext); + + useEffect(() => { + return () => { + setAwsData({ CPUUtilization: [], NetworkIn: [], NetworkOut: [], DiskReadBytes: [] }); + setLoadingState(true); + }; + }, []); + + const stringToColor = (string: string, recurses = 0) => { + if (recurses > 20) return string; + function hashString(str: string) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let colour = '#'; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 0xff; + colour += `00${value.toString(16)}`.substring(-2); + } + + console.log(colour); + return colour; + } + }; + + return ( +
+ {Object.keys(awsData)?.map(metric => { + return ( + el.time)} + valueList={awsData[metric]?.map(el => el.value)} + colourGenerator={stringToColor} + /> + ); + })} +
+ ); +}); + +export default AwsEC2Graphs; diff --git a/app/components/AwsECSClusterGraphs.tsx b/app/components/AwsECSClusterGraphs.tsx new file mode 100644 index 000000000..81df03014 --- /dev/null +++ b/app/components/AwsECSClusterGraphs.tsx @@ -0,0 +1,68 @@ +import React, { useContext, useEffect } from 'react'; +import AwsChart from '../charts/AwsChart'; +import { AwsContext } from '../context/AwsContext'; + +const AwsECSClusterGraphs: React.FC = React.memo(props => { + const { awsEcsData, setAwsEcsData, setLoadingState } = useContext(AwsContext); + + useEffect(() => { + return () => { + setAwsEcsData({}); + setLoadingState(true); + }; + }, []); + + const stringToColor = (string: string, recurses = 0) => { + if (recurses > 20) return string; + function hashString(str: string) { + let hash = 0; + for (let i = 0; i < str.length; i++) { + hash = str.charCodeAt(i) + ((hash << 5) - hash); + } + let colour = '#'; + for (let i = 0; i < 3; i++) { + const value = (hash >> (i * 8)) & 0xff; + colour += `00${value.toString(16)}`.substring(-2); + } + + console.log(colour); + return colour; + } + }; + + const activeServices = Object.keys(awsEcsData) + .slice(1) + .filter(el => awsEcsData[el].CPUUtilization?.value.length > 0); + const serviceGraphs = activeServices.map(service => { + return ( +
+
+

Service Name:

+ {service} +
+
+ + +
+
+ ); + }); + + return
{serviceGraphs}
; +}); + +export default AwsECSClusterGraphs; diff --git a/app/components/ClusterTable.tsx b/app/components/ClusterTable.tsx new file mode 100644 index 000000000..676eb989c --- /dev/null +++ b/app/components/ClusterTable.tsx @@ -0,0 +1,88 @@ +import React, { useContext, useState } from 'react'; +import { + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from '@material-ui/core'; +import { makeStyles } from '@material-ui/core/styles'; +import { AwsContext } from '../context/AwsContext'; + +const useStyles = makeStyles({ + table: { + minWidth: 650, + }, + tableContainer: { + margin: '0 20px', + }, + activeCell: { + color: 'green', + }, + column: { + display: 'flex', + flexDirection: 'column', + }, + body: { + fontSize: '1.2rem', + }, + title: { + color: '#888888', + }, +}); + +export interface ClusterTableProps { + typeOfService: string; + region: string; +} + +const ClusterTable: React.FC = React.memo(({ region }) => { + const classes = useStyles(); + const { awsEcsData, isLoading } = useContext(AwsContext); + + const activeServices = () => { + const serviceNames = Object.keys(awsEcsData).slice(1); + + return serviceNames.filter(el => awsEcsData[el].CPUUtilization?.value.length > 0); + }; + + return ( +
+ +
+ + + Cluster Name + Status + Services + Tasks + + + + +
+
{isLoading ? 'Loading...' : awsEcsData.clusterInfo?.clusterName}
+
+ {region} +
+
+
+ + {activeServices().length ? 'ACTIVE' : 'INACTIVE'} + + + {isLoading ? 'Loading...' : Object.keys(awsEcsData).length - 1} + + + {isLoading ? 'Loading...' : String(activeServices().length) + '/' + String(Object.keys(awsEcsData).length - 1)} + +
+
+ + + ); +}); + +export default ClusterTable; diff --git a/app/components/Contact.tsx b/app/components/Contact.tsx new file mode 100644 index 000000000..f50a061ee --- /dev/null +++ b/app/components/Contact.tsx @@ -0,0 +1,75 @@ +/* eslint-disable jsx-a11y/label-has-associated-control */ +import React, { useContext } from 'react'; +import '../stylesheets/Contact.scss'; +import { DashboardContext } from '../context/DashboardContext'; +import lightAndDark from './Styling'; + +const Contact:React.FC = React.memo(() => { + const { mode } = useContext(DashboardContext); + + const currentMode = + mode === 'light' ? lightAndDark.lightModeText : lightAndDark.darkModeText; + + return ( +
+
+
+
+

Contact Us

+
+

+ Please feel free to provide any feedback, concerns, or comments. +

+

+ You can find issues the team is currently working on{' '} + + here + + . +

+
+
+
+
+ + + + +